RankPaySdk 接入与接口文档
面向游戏宿主(Android原生)的接入指南,以及面向合作方服务端的 CP API 接口说明。
- SDK 版本 1.0.0(versionCode 10000)
- minSdk 21 / compileSdk 33
- 正式域名 https://sdkapi.rankrank.com
- CP 接口 4 个(3 GET + 1 POST)
- Apifox 共享文档 https://uf5xm7yovl.apifox.cn/ · 密码 MtwIowMA
目录
- 1. 概览
- 2. 环境与依赖
- 3. 引入方式
- 4. AndroidManifest
- 5. 初始化
- 6. 功能与 API
- 7. 错误处理
- 8. 混淆与安全
- 9. 微信 / 支付宝
- 10. 出包检查清单
- 11. 常见问题
- 1. 文档信息
- 2. 全局说明
- 3. 接口总览
- 4. 接口详情
- 5. 错误码
客户端接入指南(Android 原生宿主)
RankPaySdk 1.0.0 的宿主接入主链路:环境依赖、初始化、登录、实名、支付、角色上报、分包与出包检查清单。
RankPaySdk 接入文档
面向游戏宿主(Android 原生)的接入指南。SDK 版本 1.0.0(versionCode 10000)。
1. 概览
RankPaySdk 是渠道支付 SDK,一个 AAR 提供:账号(验证码/密码登录、令牌维护、注销)、实名认证、下单与收银台拉起(微信 / 支付宝 / H5)、商品与订单查询、角色当前态上报(等级/VIP/战力/公会,见 6.9b)、游戏内悬浮球。本文只展开接入主链路:登录、支付、分包。
getAdSlots / reportAdWatched)与数据埋点(refreshEventDict / track / flushEvents),本文不展开:这两块要接入时向 SDK 维护人索取接口说明,别自己按方法名猜语义。- • minSdk 21 / compileSdk 33,Java 8 兼容,全部对外方法可在主线程调用;
- • 所有回调(
SdkCallback)保证在主线程派发,且成功/失败二选一、恰好一次,可直接刷 UI; - • 登录、实名等界面由 SDK 提供(半透明 Activity),不侵入宿主 UI;也可以完全免 UI 自绘(见 6.3、6.4)。
接入方需要提供的东西
| 项 | 说明 |
|---|---|
gameId | 后管分配的游戏 ID,SdkConfig 必填 |
| 服务端地址 | 无需宿主配置:baseUrl 由 SDK 内置正式域名 ;仅联调需要测试库时才显式传入 |
| 微信 AppId | 需要微信支付时配 wxAppId,并在宿主包内声明 WXEntryActivity(见 9.2) |
| 渠道分包 | 由后台打进 APK,宿主不用配 |
2. 环境与依赖
引入方式决定这张表要不要宿主自己抄:用第 3 节的方式 A(源码模块)时这张表一行都不用写(标 api 的进宿主编译期,标「内部」的随运行期自动带上);用方式 B(aar 直挂)时这张表每一行都不会自动带上、包括标「内部」的那些(标「内部」只说明它对宿主的编译期不可见,运行时照样需要),要逐条写进宿主 build.gradle。版本冲突时以宿主工程为准做对齐:
| 依赖 | 版本 | 传递性 |
|---|---|---|
| retrofit / converter-gson | 2.9.0 | api(宿主可见) |
| okhttp | 4.11.0 | api(宿主可见) |
| okhttp logging-interceptor | 4.11.0 | 内部(SDK 的日志脱敏那一层用到) |
| gson | 2.10.1 | 内部 |
| 微信 opensdk | 6.8.24 | 内部(宿主已有可 exclude,见 9.2) |
| 支付宝 alipaysdk-android | 15.8.15 | 内部(同上) |
| androidx.appcompat | 1.6.1 | 内部(SDK 自带界面用到) |
| androidx.annotation | 1.6.0 | 内部 |
| kotlin-stdlib | 1.8.10 | 内部(宿主是 Kotlin 工程时本来就带;纯 Java 工程走方式 B 要补这一行) |
Walle payload_reader-1.1.7.jar | — | 实体 jar 已在产物 AAR 的 libs/ 里,但不写进 POM——走方式 B 时同样建议自己补一条,见第 3 节末 |
3. 引入方式
产物 AAR 直挂(files() / flatDir)
implementation files('libs/sdk-release.aar')4. AndroidManifest
AAR 已自动合并以下内容,宿主无需重复声明:
- • 权限:
INTERNET、ACCESS_NETWORK_STATE(不申请 IMEI、不申请悬浮窗权限); - •
<queries>:com.tencent.mm、com.eg.android.AlipayGphone、https VIEW(Android 11+ 拉起收银台必需,宿主若自己声明过 package 可见性,注意不要覆盖掉这两条;https 那条现在也服务协议阅读页的「在浏览器中打开」兜底); - • SDK 内部界面:
LoginActivity、RealnameActivity、AgreementActivity(协议阅读页,2026-10-29 加;三个都exported=false、都只由 SDK 内部拉起,宿主不需要为它们写任何声明)。
宿主必须自己声明的只有微信回调页(见 9.2)。正式包请不要带 android:usesCleartextTraffic="true"(demo 里那行仅用于本地联调)。
5. 初始化
5.1 SdkConfig
val config = SdkConfig.builder() // baseUrl 无需配置,SDK 内置域名
.gameId("8766431358") // 必填,空即 build() 抛异常
.debug(BuildConfig.DEBUG)
.collectDeviceId(false) // 隐私政策同意后再开
.floatingBall(true) // 游戏内悬浮球,默认 false
.realnameDialog(true) // SDK 实名弹窗,默认 true
.heartbeatEnabled(true) // 防沉迷心跳,默认 false(2026-10-29 起),要采集得显式传 true
.wxAppId("wx1234567890abcdef") // 用微信支付时必填
.sendTokenProvider { riskService.getToken() } // 见 6.2
.build()
RankPaySdk.init(context, config)
RankPaySdk.setSessionListener(sessionListener) // 必须处理 onSessionExpired规则与默认值要点:
| 项 | 规则 |
|---|---|
baseUrl | 宿主不用配置,builder() 无参重载即取 SDK 内置的正式地址 https://sdkapi.rankrank.com(域名全库仅此一处常量)。联调要连测试库 http://test-sdkapi.xyren.com/ 或本地 mock 时才用 builder(baseUrl) 显式传,且它们是 http 明文、必须同时 allowHttp(true)。出包前自查 config.redact(): |
| http 明文 | 内置正式域名为 HTTPS,正常无需关心;任何 http 地址(含测试库、局域网 mock)一律必须显式 allowHttp(true),且只允许出现在联调构建里 |
channelKey / gameKey | 都不在这里配。前者随 Walle 分包进 APK,后者登录成功时下发(RankPaySdk.hasSigningKey() 可查有无) |
collectDeviceId | 默认 false。必须在用户同意隐私政策之后才开启;运行时切换用 setCollectDeviceId()(适合 Application 里先 init、隐私弹窗后置的老游戏时序,不必 release + 重 init) |
| 超时/重试 | 默认 connect 15s、read/write 20s、幂等请求重试 2 次;资金链路(下单/重取收银台)不自动重试 |
payPolling(interval, timeout) | 支付结果轮询,默认 1.5s / 300s;越界自动夹到 [1s,10s] / [10s,30min] |
eventFlushInterval(ms, batch, maxQueue) | 埋点队列,batch 上限 50(服务端硬约束) |
pinCertificates(...) | 可选 SPKI SHA-256 锁定;不配则只校验系统信任链 |
重复 init | 幂等忽略并打 warn;换配置需先 release()(一般只在切渠道包/退出时做) |
5.2 初始化时序建议
Application/启动页 → (隐私弹窗同意后)init → refreshInitConfig 不用调(init 内自发一次 GET /partner/init)
进入游戏 → login(已登录时直接回缓存会话 = 自动登录)
进游戏/切角色 → reportRole(...)(角色当前态上报,见 6.9b;只有游戏自己知道等级与公会)
充值 → pay(...)
退出到后台 → onBackground()(落盘未发送埋点)init 阶段 SDK 会自动拉一次服务端配置(功能开关、心跳间隔、支付方式限制),不等结果、不阻塞,拉不到就当服务端没说过。后管改了开关、或运行期换了 gameId,再调 refreshInitConfig() 重取。
6. 功能与 API
门面是 RankPaySdk 单例(全 @JvmStatic,Java 可直接调)。未 init 就调用任何方法,回调 SDK_NOT_INITIALIZED,不会崩。
回调 SdkCallback<T> 是抽象类,不是函数式接口——onSuccess / onFailure 两个方法都必须给,所以 RankPaySdk.login(activity) { … } 这种尾随 lambda 写法编译不过(Type mismatch)。能写的只有下面两种,本文其余示例里的 cb / callback 都指它们:
SdkCallback.of(onOk = { r -> /* 成功 */ }, onErr = { e -> /* 失败,见第 7 节 */ })
// onErr 有默认空实现,只关心成功时可以只给 onOk:SdkCallback.of(onOk = { r -> … })
object : SdkCallback<LoginResult>() { // 或自己实现(Java 侧 new SdkCallback<LoginResult>() { … })
override fun onSuccess(result: LoginResult) { }
override fun onFailure(error: SdkException) { }
}6.1 会话与登录
RankPaySdk.login(activity, SdkCallback.of(
onOk = { result -> /* LoginResult,已在主线程,直接刷 UI */ },
onErr = { e -> /* 玩家关掉登录页回 SDK_CANCELLED,属正常分支 */ }
)) // 已登录直接回缓存;否则弹 SDK 登录页
RankPaySdk.login(activity, forceUi = true, cb) // 强制重弹登录页
RankPaySdk.isLoggedIn(); RankPaySdk.partnerOpenId(); RankPaySdk.token()
RankPaySdk.logout() // 清本地会话,并向服务端吊销这一枚令牌(尽力而为)
RankPaySdk.logout(cb) // 可选回调:回报的是**服务端吊销**的结果(本地退出在调用当时已完成)
RankPaySdk.checkToken(callback) // 主动校验并续期;失效时 SDK 已自动清会话logout() 顺序仍是先清本地、后发网络,发送失败也不回滚(不然弱网下点「退出」要转圈等一次 RTT,悬浮球还挂在屏幕上)。接入时因此要记住五点:
- • 不要拿这一发的回调当「退出完成」:本地会话与
SessionListener.onLogout()在调用当时已同步完成,UI 直接跟着onLogout切就好。 - • 收到失败时不要提示玩家「退出失败」:他确实已经退出去了,代价只是旧令牌要在服务端活到自然过期(重登会换新令牌)。想埋这个点才需要传
cb。 - • 这一发回的
PARTNER_TOKEN_INVALID不会再触发onSessionExpired:去吊销令牌却被告知「令牌已无效」等于目的达成,SDK 刻意不把它升级成全局被踢(否则宿主刚收到onLogout紧接着又收到一次会话过期,常见后果是莫名多弹一个重新登录框)。 - • 只吊销「当前渠道 + 当前游戏」这一维度的令牌,同一个账号在另一个游戏的登录不受影响;与 6.5 的注销不是一回事(那一发连账号映射一起禁用)。
- • 未登录时调用直接回成功(幂等,一个请求也不发),重复点「退出」不会报错。
LoginResult:partnerOpenId / passId / token / maskedTel(脱敏手机号,仅 UI 展示)/ realnameStatus(1 已实名 / 0 未 / -1 服务端未下发)。toString() 已对 token 与密钥遮码,可放心打日志。
登录成功后 realnameStatus=0 时 SDK 自动弹实名页(受 5.3 两道开关控制);玩家点「暂不认证」不算登录失败——会话有效,只是充值时还会被拦。
usesCleartextTraffic="true"(第 5 章开头、第 10 章检查清单都钉了这条):那时 http:// 的条款页会直接落到加载失败态。SessionListener(务必注册并处理 onSessionExpired):
RankPaySdk.setSessionListener(object : SessionListener {
override fun onLoginSuccess(result: LoginResult) { /* 刷新 UI / 上报角色 */ }
override fun onSessionExpired(error: SdkException) { /* 引导重新登录,否则玩家会在下单时才撞到失效码 */ }
override fun onLogout() { /* 悬浮球「退出登录」、注销成功、本地清会话都走这里 */ }
})6.2 免 UI 登录(宿主自绘登录界面)
RankPaySdk.sendSmsCode(tel, callback) // 防刷 token 走 SdkConfig.sendTokenProvider
RankPaySdk.sendSmsCode(tel, sendToken, callback) // 或显式传入
RankPaySdk.loginBySms(tel, smsCode, smsToken, serverId, cb) // 验证码登录需回传发送码时拿到的 smsToken
RankPaySdk.loginByPassword(tel, password, serverId, cb)老游戏已有登录态、不想让玩家再登一次:
RankPaySdk.adoptSession(partnerOpenId, token, expireTime = "", callback)
// 种入后立刻向服务端 token/check 复核;判定无效会清掉刚写入的会话并回 PARTNER_TOKEN_INVALID6.3 实名认证
RankPaySdk.requestRealname(activity, cb) // 主动拉认证页(已实名直接回成功)
RankPaySdk.submitRealname(name, idCard, cb) // 免 UI 直提,校验与签名照常
RankPaySdk.queryRealnameStatus(cb) // 服务端权威结论(回脱敏姓名/证件号);未实名是正常结果走 onSuccess
RankPaySdk.realnameStatus(); RankPaySdk.isRealnameVerified()弹窗开关有两道,都只能关不能开:宿主 realnameDialog(false) 与服务端 init 回 realnameEnabled=0。关掉弹窗不等于绕过实名——服务端拦截码 PARTNER_REALNAME_REQUIRED 会原样回到 onFailure,只是换成宿主自己收集证件。姓名/证件号只在提交瞬间持有:不落盘、不进日志、不进埋点,且仅 HTTPS 发送。
6.4 支付
val options = OrderOptions(
serverId = "s101", // 必填
roleId = "role_8848", // 必填,SDK 无从获取,只能游戏传
productName = "月卡", // 必填(product 重载可自动补)
productDescription = "", // 选填
ext = "" // 透传给支付回调,参与签名
)
// 方式一:指定支付方式
RankPaySdk.pay(activity, productId, amount /* 分 */, PayType.ALIPAY_APP, options, cb)
// 方式二:支付方式由 SDK 弹窗让玩家选
RankPaySdk.pay(activity, productId, amount, options, cb)
// 方式三/四:用商品对象(金额取服务端下发值,productName/serverId 自动补,宿主给的非空值优先)
RankPaySdk.pay(activity, product, PayType.WECHAT_APP, options, cb)
RankPaySdk.pay(activity, product, options, cb)流程编排(SDK 内部完成,宿主只有一个回调):未登录先弹登录 → 未实名先弹实名 → 下单 → 拉起收银台 → 轮询服务端确认。
- •
serverId/roleId/productName缺一即在本地拒绝(SDK_INVALID_ARGUMENT,不发请求);金额与服务端商品登记价不一致回PARTNER_AMOUNT_MISMATCH——不要写死价格,优先用getProducts()的ProductInfo重载。 - • 支付方式常量:
PayType.WECHAT_APP(WechatPay_APP)、ALIPAY_APP、WECHAT_H5;渠道开通集合 = 激活/⑭ 下发的payTypes(supportedPayTypes()可查),与实际可拉起器取交集后:空 →SDK_INVALID_ARGUMENT(配置问题);一种 → 直接用它不弹;多种 → 弹选择框,玩家关掉回SDK_PAY_CANCELLED。 - • 已下单需换方式/重新拉起:
relaunchPay(activity, gameOrderId, [payType,] cb)——不会重复建单,只是拿已有订单重取收银台参数。 - • 超时必须以服务端为准对账:收到
SDK_PAY_RESULT_TIMEOUT后钱可能已扣,用queryOrder(gameOrderId)做最终确认(服务端「查即补单」会把状态拉齐),切勿直接判失败漏发道具。 - • 订单状态判定用
OrderStatus.paid(= 1/2/3),便捷方法isPaidStatus(status)。不要写status >= 1——4是后管撤销,数值更大但一分没收到;轮询见到 4 应立刻停(SDK_ORDER_REVOKED),让玩家重新下单。
data class PayResult(gameOrderId, productId, amount /* 分 */, payType, status, payTime)
data class OrderInfo(gameOrderId, passOrderId, productId, amount, status, notifyCount, createTime, payTime)6.5 注销账号(不可逆,务必谨慎)
RankPaySdk.cancelAccount(cb)与 logout() 完全不是一回事:注销会让服务端吊销令牌并把这个账号置为停用(服务端改的是这条渠道上登记的账号映射),该账号再登录被直接拒绝(PARTNER_ACCOUNT_CANCELLED),只能客服/运营后台恢复,玩家自己重登无效。SDK 悬浮球里的入口已带二次确认;宿主直接调这个方法时必须自己拦这道确认。成功后 SDK 清会话并通知 onLogout;失败(网络等)会话原样保留,不会踢玩家下线。注销不到底层通行证账号:通行证账号、实名信息、已完成订单不受影响。悬浮球那张确认卡的文案按「账号」说、不出现「渠道」二字(2026-10-29 口径:玩家注销的就是当前登录的这个账号)。
6.7 悬浮球
floatingBall(true) 或运行时 setFloatingBall(enabled)。挂在宿主 Activity 内容视图上(不用悬浮窗权限、不跨 App),仅已登录时显示;静止 4 秒半隐到屏边,可拖动、位置记忆。面板入口:用户信息/实名、退出登录(带二次确认,确认后 = logout():先清本地会话、再向服务端吊销令牌,见 6.1)、注销账号(带二次确认 = 6.5)。
6.9b 角色上报(宿主在合适的时机自己调)
SDK 自己看不到玩家的等级/VIP/战力/公会——下单链路只带 serverId/roleId,成长数据必须由游戏方在自己的节点上报回来(POST /partner/role/report),后台的「玩家角色列表」才有得看。
// ① 登录成功、玩家进游戏那一次——这是唯一该把 loginReport 置 true 的一发
RankPaySdk.reportRole(
RoleReport(
serverId = game.serverId, // 必填
roleId = game.roleId, // 必填,SDK 无从获取,只能游戏传
serverName = "一区·龙渊", // 选填
roleName = "剑十一", // 选填
level = "87", vipLevel = "9", combatPower = "1250000", // 选填,字符串
guildId = "g_12", guildName = "龙渊阁", // 选填
loginReport = true
),
null // 回调可空(`SdkCallback<Unit>?`),传 null = 不关心结果(SDK 自己记日志)
)
// ② 切区服 / 切角色时;③ 等级、VIP、战力、公会发生变化时(loginReport 不要再置 true)
RankPaySdk.reportRole(
RoleReport(serverId = game.serverId, roleId = game.roleId, level = "88"),
SdkCallback.of(
onOk = { /* 这一发已经落库 */ },
onErr = { e -> /* 透传类错误一般不用管;PARTNER_ACCOUNT_CANCELLED 要停止上报 */ }
)
)口径要点(字段名就是上报用的那些,无额外包装):
- • 必须已登录。
gameId/partnerOpenId/token都由 SDK 从会话里取(gameId取登录下发的那个,宿主没有传它的入参);未登录时本地直接回PARTNER_OPENID_INVALID,一个请求都不发。放在SessionListener.onLoginSuccess之后调最稳。 - • 必填只有
serverId与roleId(它们与 openId 组成唯一键),其余全部可留空;缺了、或格式/长度不对都在本地先拒(SDK_INVALID_ARGUMENT,message会点出字段名),省一次往返。 - • 数值字段一律用字符串:
level/vipLevel/combatPower传"87"而不是87,必须是非负整数(0是合法值、照常覆盖),combatPower可超 int、单位由接入方自定义。字符串上限:定位类(serverId/roleId/guildId)64,名称类(serverName/roleName/guildName)128。 - • 留空 = 本次不上报这一项,不是清空。服务端只对非空值做覆盖,所以「玩家退出公会」这种想置空的场景本接口做不到,别拿空串或占位值绕(占位值会被当正常数据入库)。
- • 重复上报安全、不需要定时轮询:服务端按唯一键 upsert,同一角色再报只覆盖数据、不会产生第二行;网络失败可以直接重试(SDK 内部也按幂等处理)。带不带
reqId不影响落库(它只进链路日志)。 - •
loginReport = true只刷服务端那个「最后登录时间」,所以只在进游戏那一次置 true,平时带上会污染这个口径。 - • 定位提醒:它记的是当前态快照,不是角色事件流水,也不是账号交易/结算口径的权威角色档案(那份在通行证域),不参与任何金额计算与发货判定,别拿它当对账依据。带
serverName时服务端会顺带登记区服名,与角色写入同事务,游戏后台没配好时会整单失败(PARTNER_GAME_KEY_MISSING)。 - • Unity 桥本期没扩这个方法(与心跳/注销同口径):引擎侧要用得先在
adapters/unity/补一个桥接方法,不要在 C# 里拼 HTTP 绕开签名。
6.10 其它
| 方法 | 用途 |
|---|---|
refreshInitConfig(cb) / initConfig() | 重取/读取服务端配置(init 已自发一次;注意升级四件套等字段服务端仍是占位值,别拿来做灰度强更决策) |
supplyDeviceIds(imei, oaid, androidId) | 宿主已有合规设备标识(如厂商 OAID SDK)时注入,仅在 collectDeviceId(true) 时才发出 |
setLogger { level, tag, msg -> } | 接管 SDK 日志(level 同 android.util.Log);传 null 恢复默认。日志已对密钥/证件脱敏 |
packageId() | 只读:当前包的分码(空串 = 该包没打渠道) |
applyPackageIdFromChannel(ctx) | 仅一种场景需要:覆盖安装了另一个渠道包且进程还活着时强制重读渠道。正常接入不用调(init 已读)。必须在 login 之前,登录后再换会吃 PARTNER_OPENID_INVALID |
hasSigningKey() | 登录下发的签名密钥是否已落盘。排查 PARTNER_SIGN_ERROR 的第一站 |
serverTimeOffsetMillis() | 服务端时间补偿量,排查 PARTNER_REPLAY(设备时间漂移)用 |
deliverWxResult(errCode, errStr) | 宿主自有 WXEntryActivity 时转交微信结果(见 9.2) |
registerPayLauncher(l) | 注入自定义收银台拉起器,优先级高于内置(宿主有自己的支付中台时用) |
7. 错误处理
失败统一回 SdkException(code, message, category, httpStatus):message 已是可上屏中文;retryable(网络/超时/5xx)与 needRelogin(令牌类)两个判定属性可直接用。按 category 粗分处理即可:
| SdkErrorCategory | 建议处理 |
|---|---|
| NETWORK / TIMEOUT | 提示重试(retryable=true 的接口 SDK 内部已重试) |
| AUTH | 引导重新登录(onSessionExpired 也会通知) |
| VALIDATION | 宿主自身参数/配置问题,改调用方,不要重试 |
| BUSINESS | 透传 message 给玩家 |
| PAY | 区分「取消」(可再买)「超时」(必须 queryOrder 对账)「被撤销」(重新下单) |
| PROTOCOL / INTERNAL | 上报排查,玩家侧给通用文案 |
高频错误码速查:
| code | 含义 | 处理 |
|---|---|---|
SDK_NOT_INITIALIZED | 未 init | 检查初始化时序 |
SDK_INVALID_ARGUMENT | 本地校验拒绝(必填缺失 / 无可用支付方式 / 角色上报的字段超长或不是非负整数) | 补参;支付方式为空是渠道配置或没装微信/支付宝;角色上报看 message 里点出的字段名 |
SDK_PAY_CANCELLED | 玩家在收银台或选择弹窗取消 | 正常分支,不报错 |
SDK_PAY_RESULT_TIMEOUT | 轮询超时,结果未知 | 必须 queryOrder 对账后再决定发不发货 |
SDK_ORDER_REVOKED | 订单被后管撤销(status=4) | 让玩家重新下单 |
PARTNER_TOKEN_INVALID / PARTNER_OPENID_INVALID / PARTNER_GAME_KEY_MISSING | 登录态问题 | 重新登录(needRelogin=true) |
PARTNER_SIGN_ERROR | 验签失败 | 先查 hasSigningKey();再确认 Walle 分包是否正确写入 channelKey、设备时间是否漂移 |
PARTNER_REPLAY / PARTNER_TS_ERROR | 时间戳异常/重放 | 让用户校准系统时间 |
PARTNER_REALNAME_REQUIRED | 未实名拦截 | SDK 默认会自动补弹实名页并重试一次;关弹窗时宿主自行引导 |
PARTNER_ANTI_ADDICTION_PAY_LIMIT | 未成年消费限额 | 透传文案,重试无意义 |
PARTNER_ACCOUNT_CANCELLED | 账号已注销/禁用(通行证账号本身还在) | 透传文案(需客服处理),不要引重登死循环 |
PARTNER_PACKAGE_INVALID | 分包码未登记/停用 | 出包配置问题,联系运营,玩家侧无法解决 |
PARTNER_AMOUNT_MISMATCH | 下单金额与商品登记价不符 | 改用 ProductInfo 重载,别写死价格 |
PARTNER_PARAM_INVALID | 服务端判定参数不合法(数值不是非负整数、字符串超列宽);角色上报 SDK 已把同一判据前置到本地 | 按 message 改调用方的值,重试无意义 |
PARTNER_GAME_MISMATCH | 请求里的 gameId 与登录令牌绑定的游戏不是同一个(角色上报会撞)。gameId 由 SDK 取登录下发值,正常情况下不该出现 | 出现了就是登录态串了(多开/换账号),清会话重新登录 |
PARTNER_CHANNEL_NOT_FOUND / PARTNER_CHANNEL_DISABLED | 渠道定位失败 | 检查分包 channelKey 与渠道启用状态 |
8. 混淆与安全
- • SDK 自带
consumer-rules.pro(随 AAR 生效),宿主无需为 SDK 添加任何规则;建议宿主开minifyEnabled true(R8),能提高签名密钥提取成本。 - • 宿主需要自己 keep 的只有反射入口(如微信回调 Activity 按类名查找):
-keep class ${你的包名}.wxapi.WXEntryActivity { *; }。 - • 敏感数据口径:姓名/证件号仅提交瞬间持有、只在 HTTPS 发送;
maskedTel、实名查询返回的姓名证件号均为脱敏文本。
9. 微信 / 支付宝
9.1 支付宝
内置拉起,无宿主工作:配好 gameId 即可,payType=AliPay_APP。
9.2 微信
- 1.
SdkConfig.builder(...).wxAppId("wx…")——不配则微信拉起直接回错误码; - 2. 在宿主 applicationId 对应包下建空壳类并在 Manifest 声明(微信 SDK 只认
${applicationId}.wxapi.WXEntryActivity这个固定路径,AAR 无法代提供):
package com.your.game.wxapi
class WXEntryActivity : com.leju.paysdk.wx.RankPayWXEntryActivity()
<activity android:name=".wxapi.WXEntryActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity="com.your.game"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />- 3. 宿主已有自己的
WXEntryActivity(在做微信登录/分享、不能改继承基类)时,保持原实现,在onResp里转交一次支付结果即可:
override fun onResp(resp: BaseResp?) {
if (resp?.type == ConstantsAPI.COMMAND_PAY_BY_WX) {
RankPaySdk.deliverWxResult(resp.errCode, resp.errStr) // 0 成功 / -1 错误 / -2 取消
}
super.onResp(resp) // 宿主自己的登录/分享分支照旧
}宿主已自行接入微信开放 SDK 的,可 exclude SDK 的 wechat-sdk-android 传递依赖(支付宝同理),注意版本需覆盖 6.8.24 的接口面。
10. 出包检查清单
- □ 未做
baseUrl宿主配置(走 SDK 内置正式域名),并核对redact()日志中的地址为https://sdkapi.rankrank.com、无(TEST)标记; - □ 正式包无
allowHttp(true)、无usesCleartextTraffic="true"; - □
gameId与后管登记一致; - □ APK 已由后台用 Walle 写入渠道(
RankPaySdk.packageId()非空、登录后签名可用); - □ 用方式 B(aar 直挂)时:第 2 节那张表已在宿主
build.gradle里逐条写全,payload_reader也照 3 节末补了一道(漏了不会崩,只是packageId()恒空、分包校验过不了); - □ 需要微信支付时:
wxAppId已配、${applicationId}.wxapi.WXEntryActivity已声明并可被微信拉回; - □
SessionListener.onSessionExpired有真实处理(引导重登); - □ 隐私政策同意后调
setCollectDeviceId(true)(或直接开配置),之前保持 false; - □ 真机跑通一条完整资金链路:登录 → (实名)→ 下单 → 拉起 → 支付成功回调
status=PAID/NOTIFIED; - □ 断网/取消/超时三个失败分支各试一次:取消不报错、超时走
queryOrder对账。
11. 常见问题
| 现象 | 原因与处置 |
|---|---|
登录后所有需签名接口 PARTNER_SIGN_ERROR | 先 hasSigningKey():false = 登录响应没带密钥(服务端问题);true = 多为设备时间漂移或分包 channelKey 与服务端登记不符 |
点支付直接回调 SDK_INVALID_ARGUMENT | OrderOptions 三个必填没齐,或渠道开通的支付方式在本机都没有拉起器(没装微信/支付宝且未 registerPayLauncher) |
| 支付成功但宿主没收到回调 | 检查是否在 SDK_PAY_RESULT_TIMEOUT 分支自己吞了失败——超时是「未确认」,必须 queryOrder 补判 |
| 换了渠道包后 openId 对不上 | 覆盖安装渠道包后进程未死,缓存里还是旧渠道值:login 之前调 applyPackageIdFromChannel(ctx) 强读一次 |
| 悬浮球不出现 | 三个条件缺一不挂:配置开启、已登录、宿主 Activity 处于前台;SDK 自己的弹窗(登录/实名)弹出期间球会暂时收起属正常 |
| 未成年玩家充值被拒 | PARTNER_ANTI_ADDICTION_PAY_LIMIT,服务端实名,客户端无法绕过也不该重试 |
| 后台看不到玩家的等级/战力/公会 | 这些只有游戏自己能报(6.9b),SDK 不会自己采集:确认宿主真的调过 reportRole,且已登录、serverId/roleId 与下单用的是同一套值 |
reportRole 回调 SDK_INVALID_ARGUMENT | 必填只有 serverId/roleId 两个;level 之类传了不是非负整数的值(-1/1.5/V9)或字符串超 64/128 也会被本地拦下,message 里点了字段名 |
本文与 SDK 1.0.0 同步维护;二者冲突时以代码为准并请反馈文档维护人。
合作方服务端接口(CP API)
面向合作方服务端的 HTTP 接口:订单查询、用户信息查询、有效性验证、订单发货回执,以及全局鉴权约定与错误码。接口以最新版《RankRank SDK 接口文档》与《SDK 接口文档 —— 订单发货回执》为准。
合作方(CP)服务端接口
整理日期:2026-11-04
1. 文档信息
| 项目 | 内容 |
|---|---|
| 文档名称 | SDK接口(Apifox 共享文档站点标题) |
| 访问地址 | https://uf5xm7yovl.apifox.cn/ |
| 访问方式 | 需要密码访问(MtwIowMA) |
| Apifox 项目 ID | 9011689(由各接口页 "Run in Apifox" 链接解析得到) |
| 接口数量 | 4 个(3 个 GET + 1 个 POST) |
| 说明性页面 | 2 个(全局说明、错误码) |
2. 全局说明
以下为原文「全局说明」页面的全部内容:
请求的header里需要加下面参数:SDK-Authorization:授权码,SDK分配,测试环境:b3883a603bb5438aba6978d3e3555416,正式环境:a81e3fe5c92a4261b78320a9811d256c
测试url:http://test-sdkapi.xyren.com正式url:https://sdkapi.rankrank.com| 项目 | 约定 |
|---|---|
| 服务地址(Base URL) | https://sdkapi.rankrank.com |
| 鉴权方式 | HTTP 请求头 SDK-Authorization,值为 SDK 分配的授权码 |
| 测试环境授权码 | b3883a603bb5438aba6978d3e3555416 |
| 正式环境授权码 | a81e3fe5c92a4261b78320a9811d256c |
| 鉴权失败行为 | fail-closed:缺失或不匹配时返回错误码 CP_UNAUTHORIZED(见第 5 节) |
| 接口风格 | 统一为 GET + query 参数;响应为 JSON |
| 响应外壳 | 查询订单信息、有效性验证接口的 schema 为 {code, message, data};查询用户信息的 schema 未定义该外壳,但官方示例带外壳(差异见第 6 节第 2 条) |
| 成功码 | code = succ |
3. 接口总览
| 序号 | 接口名称 | 方法 | 路径 | 状态 |
|---|---|---|---|---|
| 1 | 查询订单信息 | GET | /cp/api/order/query | 已发布 |
| 2 | 查询用户信息 | GET | /cp/api/user/info | 已发布 |
| 3 | 有效性验证接口 | GET | /cp/api/user/verify | 已发布 |
| 4 | 订单发货回执 | POST | /cp/api/order/deliver_notify | 见 4.4 |
四个接口都要求请求头携带 SDK-Authorization;
4. 接口详情
GET4.1 查询订单信息
GET /cp/api/order/query
请求参数
| 参数名 | 位置 | 类型 | 必填 | 说明 | 示例值 |
|---|---|---|---|---|---|
| orderId | query | string | 是 | 订单id | |
| openId | query | string | 是 | 用户Id 既SDK里面的gamerId | |
| gameId | query | string | 是 | 游戏 Id | |
| SDK-Authorization | header | string | 是 | 授权码,SDK 分配 | a81e3fe5c92a4261b78320a9811d256c |
响应字段(HTTP 200,响应名「成功」)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| code | string | 是 | 结果码;示例取值 succ |
| message | null | 是 | 消息;schema 类型标注为 null,示例值为 null |
| data | object | 是 | 订单数据,字段见下 |
| data.gameOrderId | string | 是 | 我方订单号 |
| data.passOrderId | string | 是 | (原文未给字段释义;示例中与 gameOrderId 相同) |
| data.productId | string | 是 | 商品 ID;示例 P_648 |
| data.amount | integer | 是 | 支付金额,单位分;示例 64800(即 648 元) |
| data.status | integer | 是 | 支付维度:0 待支付 / 1 已支付 / 4 已退款 / 5 已关闭 |
| data.notifyStatus | integer | 是 | 发货回调维度:0 未通知 / 1 已通知 / 2 通知失败 |
| data.notifyCount | integer | 是 | 发货回调重试次数 |
| data.createTime | string | 是 | 下单时间 |
| data.payTime | string | 是 | 支付时间,未支付为空 |
响应说明(原文 200 描述)
支付成功以 status=1 为准;status=1 且 notifyStatus=1 表示支付完成且发货已确认;status=1 且 notifyStatus=2 表示支付完成但发货失败,SDK 会自动重推。
响应示例
{ "code": "succ", "message": null, "data": { "gameOrderId": "2039000000000000001", "passOrderId": "2039000000000000001", "productId": "P_648", "amount": 64800, "status": 1, "notifyStatus": 1, "notifyCount": 1, "createTime": "2026-11-05 10:00:00", "payTime": "2026-11-05 10:01:12" }}请求示例(渲染页给出的 cURL)
curl --location '/cp/api/order/query?orderId=&openId=undefined' \ --header 'SDK-Authorization: a81e3fe5c92a4261b78320a9811d256c'deliverStatus / deliverTime / deliverCpNo,与出站 notifyStatus 并列。上表按原文保留,未增补这三行。GET4.2 查询用户信息
GET /cp/api/user/info
请求参数
| 参数名 | 位置 | 类型 | 必填 | 说明 | 示例值 |
|---|---|---|---|---|---|
| openId | query | string | 是 | 用户Id | |
| gameId | query | string | 是 | 游戏 Id | |
| SDK-Authorization | header | string | 是 | 授权码,SDK 分配 |
响应字段(HTTP 200,响应名「成功」)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| partnerOpenId | string | 是 | SDK 域对外的唯一用户标识 |
| gamerCode | string | 是 | 游戏家唯一 code |
| nickName | string | 是 | 用户名称 |
| avatar | string | 是 | 头像完整路径 |
| realnameStatus | integer | 是 | 是否实名:0 未实名 / 1 已实名 |
| realName | string | 是 | 真实姓名 |
| idCardNo | string | 是 | 身份证Id |
响应示例
{ "code": "succ", "message": null, "data": { "partnerOpenId": "PO_10001", "gamerCode": "RR88888", "nickName": "玩家小明", "avatar": "https://cdn.example.com/avatar/10001.png", "realnameStatus": 1, "realName": "张*三", "idCardNo": "110***********1234" }}请求示例(渲染页给出的 cURL)
curl --location '/cp/api/user/info?openId=undefined&gameId=undefined' \ --header 'SDK-Authorization;'GET4.3 有效性验证接口
GET /cp/api/user/verify
请求参数
| 参数名 | 位置 | 类型 | 必填 | 说明 | 示例值 |
|---|---|---|---|---|---|
| openId | query | string | 是 | 用户Id | |
| gameId | query | string | 是 | 游戏Id | |
| SDK-Authorization | header | string | 是 | 授权码,SDK 分配 |
响应字段(HTTP 200,响应名「成功」)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| code | string | 是 | 结果码;示例取值 succ |
| message | null | 是 | 消息; |
| data | boolean | 是 | 验证结果;原文未说明 true / false 的语义与失败时的 code |
响应说明(原文 200 描述)
响应示例
{ "code": "succ", "message": null, "data": true}请求示例(渲染页给出的 cURL)
curl --location 'cp/api/user/verify?openId=undefined&gameId=undefined' \ --header 'SDK-Authorization;'POST4.4 订单发货回执
SDK 接口文档 —— 订单发货回执(CP 发货后通知我方)
环境域名
| 环境 | 对外域名(拼在 URL 前) | SDK-Authorization(请求头取值) |
|---|---|---|
| 正式 | https://sdkapi.rankrank.com | a81e3fe5c92a4261b78320a9811d256c |
两枚 SDK-Authorization 为对应环境服务端配置 partner.cp.authorization 的当前值,正式/测试互相独立、不可混用;变更以服务端实际配置为准。
一、接口用途与定位
支付成功、我方(SDK)按 yxj_partner_game.cp_notify_url 通知 CP 去发货(出站)之后,CP 完成发货再回执我方(入站)本次发货结果,写入订单的 deliver_status。
两个方向、两列状态,互不覆盖(务必区分):
notify_status(出站,我方→CP)= 我方通知 CP 去发货的回调结果,0 未通知 / 1 已通知 / 2 通知失败;deliver_status(入站,CP→我方,本接口写入)= CP 发货完成后回执我方的结果,0 未回执 / 1 已发货 / 2 发货失败。
notify_status=1 只代表"发货请求已送达 CP",是否真正发货以 deliver_status=1 为准。
二、面向与鉴权
本接口只开放 CP 免验签面 /cp/api/**(服务端到服务端),没有客户端 /partner 面版本:实际调用方是 CP 后台,它不持有玩家登录 token、也不持有 gameKey/channelKey,出站发货通知只回传了 gameOrderId+partnerOpenId,故回执入口按全局唯一 orderId 定位订单即可。鉴权沿用本面统一口径:
- 仅以全局共享密钥请求头
SDK-Authorization鉴权:值取自服务端配置partner.cp.authorization,每次请求在 HTTP 头携带,无需按参数签名;缺失或不符返回CP_UNAUTHORIZED(服务端未配置时 fail-closed 全拒)。头名统一用连字符SDK-Authorization,早期下划线写法SDK_Authorization会被 Nginx 等网关默认丢弃(underscores_in_headers off)导致鉴权失败,服务端仍兼容旧名,但 CP 侧请用连字符名。 - 不校验
sign、不校验token、不做ts/nonce防重放:安全边界靠该全局密钥仅限 CP 服务端持有、严禁下发客户端或写入前端来保证。 - 非资金的状态写入:只改
deliver_status/deliver_time/deliver_cp_no,不触碰status、notify_status等支付与资金字段。
三、URL
- 正式:
https://sdkapi.rankrank.com/cp/api/order/deliver_notify
四、HTTP 请求方式
POST,业务参数走 application/x-www-form-urlencoded 表单或 URL query(鉴权走请求头 SDK-Authorization)。
五、请求参数
| 参数 | 必填 | 类型 | 说明 |
|---|---|---|---|
orderId | 是 | String | 我方订单号(gameOrderId,全局唯一,出站发货通知已下发给 CP),服务端据此反查订单自身渠道 |
gameId | 是 | String | 游戏ID(对外业务标识);与本面订单查询同口径必填,服务端先据此判断游戏是否已接入(未接入报 PARTNER_GAME_NOT_FOUND),并要求与订单归属游戏一致(跨游戏回执拒绝) |
deliverStatus | 是 | String | 发货回执状态,仅 1 已发货 / 2 发货失败,其它值报 PARTNER_DELIVER_STATUS_INVALID |
openId | 否 | String | 下单用户 openId(partnerOpenId);传则校验订单归属,不符按"订单不存在"处理 |
cpOrderNo | 否 | String | CP 侧发货流水号,透传落 deliver_cp_no 仅作留痕/对账,不参与状态判定 |
SDK-Authorization | 是 | String(请求头) | CP 全局共享密钥,须与服务端配置一致 |
免传 channelKey、gameKey、token、ts、nonce、sign:channelKey 由服务端按 orderId 读订单自身渠道得出;gameId 仅用于前置游戏接入校验与跨游戏一致性判定,不用于验签。
六、校验与落库规则
SDK-Authorization鉴权(全局密钥,fail-closed);gameId必填且须已在 SDK 登记(yxj_partner_game),未接入直接拒绝,便于 CP 区分"游戏标识配错"与"订单问题";deliverStatus合法性校验:仅接受1/2,其它值(含0/空/非数字)拒绝;- 归属校验:按
orderId读取订单,订单不存在、或传了openId但与下单openId不符,统一报PARTNER_ORDER_NOT_FOUND(不透露差异,防用订单号试探他人交易); - 游戏一致性:请求
gameId须与订单已绑gameId一致(跨游戏回执拒绝;迁移期老订单gameId为空放过); - 状态守卫:仅
status=1(已支付)订单可被回执,否则报PARTNER_ORDER_NOT_PAID;SQL 侧带status=1guard 兜底并发(支付/退款与回执交错时以最终态为准); deliver_time由服务端取当前时间落库(不接收 CP 上报时间,防篡改与时区不一致),cpOrderNo空串归一为null;- 幂等:重复回执后到覆盖(last-write-wins),不报错,只保留 CP 最后一次上报的状态。
七、返回结果
统一返回信封,成功与否以 code 判定(业务失败同样返回 HTTP 200):
{
"code": "succ",
"message": null,
"data": true
}| 字段 | 类型 | 说明 |
|---|---|---|
code | String | succ 表示成功;失败为错误码(见第九节) |
message | String | 失败原因文案,成功时通常为 null |
data | Boolean | 回执处理成功恒为 true |
回执结果可通过本面订单查询读取:GET /cp/api/order/query,返回体含 deliverStatus / deliverTime / deliverCpNo 三个入站字段(与出站 notifyStatus 并列)。
八、请求示例
POST /cp/api/order/deliver_notify HTTP/1.1
Host: test-sdkapi.xyren.com
Content-Type: application/x-www-form-urlencoded
SDK-Authorization: a81e3fe5c92a4261b78320a9811d256c
orderId=2039000000000000001&gameId=5781921412&deliverStatus=1&openId=PO_10001&cpOrderNo=CP-DLV-20261105-0001若本次无 CP 流水号,cpOrderNo 可省略或传空串,服务端按空值(null)落库;openId 亦为选填,传则追加归属校验。
九、错误码
| 错误码 | 触发条件 |
|---|---|
CP_UNAUTHORIZED | 请求头 SDK-Authorization 缺失/与服务端密钥不符,或服务端未配置 partner.cp.authorization(fail-closed) |
PARTNER_GAME_MISSING | gameId 为空 |
PARTNER_GAME_NOT_FOUND | gameId 未在 SDK 登记(游戏未接入或已删除),说明 CP 侧游戏标识配错,与订单本身无关 |
PARTNER_DELIVER_STATUS_INVALID | deliverStatus 非数字,或不为 1(已发货)/2(发货失败) |
PARTNER_ORDER_NOT_FOUND | orderId 为空 / 订单不存在 / 传了 openId 但归属不符 / 订单 gameId 与请求不符(统一按订单不存在处理,不透露差异) |
PARTNER_ORDER_NOT_PAID | 订单非已支付状态(status != 1)无法回执,或并发下状态已变回执未生效 |
valid.fail | 缺少必填参数 orderId、gameId 或 deliverStatus |
error.unknown | 服务端未预期异常 |
十、联调注意事项
- 密钥保密:
SDK-Authorization是本面唯一的鉴权凭据,替代了逐请求签名,一旦泄露即全量可写/可查且无防重放,严禁下发到客户端或写入前端代码;正式/测试取值不同,不可混用。 gameId必须与订单归属游戏一致:同一订单只能在下单游戏下回执,跨游戏回执会被拒绝。openId为选填的加固项:建议 CP 回传出站通知里带的partnerOpenId,可防止用订单号试探他人交易。- 幂等重试安全:CP 因超时重发回执不会报错,以最后一次
deliverStatus为准;如需避免"先失败后成功"被覆盖,请在 CP 侧保证上报顺序。 - 本接口不返回资金字段,
deliver_time为我方服务端落库时刻,非 CP 上报时间。
5. 错误码
原文「错误码」页面的表格存在一行串行:valid.fail(缺少 userId)那一行与 PARTNER_ORDER_NOT_FOUND 行被合并渲染,渲染页里 PARTNER_ORDER_NOT_FOUND 整行丢失、并残留了半截 `user 与 Id`。下表按上下文还原,需与后端核对后回改 Apifox(见第 6 节第 11 条)。
| 错误码 | 触发条件 |
|---|---|
| CP_UNAUTHORIZED | 请求头 SDK-Authorization 缺失/与服务端密钥不符,或服务端未配置 |
| PARTNER_OPENID_INVALID | openId为空,或按 openId 未查到用户映射 |
| PARTNER_USERINFO_MISSING | 主平台正常响应但未返回资料 |
| PARTNER_ORDER_NOT_FOUND | orderId 为空 / 订单不存在 / 传了 openId 但与订单归属不符(统一按订单不存在处理,不透露差异) |
| valid.fail | 缺少必填 query 参数 openId |
| valid.fail | 缺少必填 query 参数 orderId |
| error.unknown | 服务端未预期异常 |
| PARTNER_GAME_MISSING | gameId 为空(订单发货回执) |
| PARTNER_GAME_NOT_FOUND | gameId 未在 SDK 登记(游戏未接入或已删除),属 CP 侧游戏标识配错,与订单本身无关(订单发货回执) |
| PARTNER_DELIVER_STATUS_INVALID | deliverStatus 非数字,或不为 1(已发货)/ 2(发货失败)(订单发货回执) |
| PARTNER_ORDER_NOT_PAID | 订单非已支付状态(status != 1)无法回执,或并发下状态已变回执未生效(订单发货回执) |
按错误码可反推出的行为约定:
- 鉴权是 fail-closed,缺授权码或服务端漏配 partner.cp.authorization 都会直接拒绝,不会降级放行。
- 订单查询在「orderId 为空」「订单不存在」「openId与订单归属不符」三种情况下统一返回 PARTNER_ORDER_NOT_FOUND,刻意不区分,避免透露订单归属差异。
- valid.fail 用于必填 query 参数缺失(openId / orderId),属于参数校验层错误。
- error.unknown 兜底,且会把主平台不可达的情况透传出来。