# 支付流程(多客户端通用) ## 1. 概述 本文档描述换皮应用可复用的支付全流程,覆盖:商品获取、支付方式选择、订单创建、平台内购回调、订单核销、补单与账户刷新。 文档以“能力与流程”表达,不绑定具体客户端项目、目录路径或某一语言实现。 --- ## 2. 支付流程总览 ```text 用户点击充值 / Buy │ ├─ 已登录 且 开启第三方支付 │ │ │ ├─ 拉取支付方式(基于商品/活动) │ ├─ 用户选择支付方式 │ ├─ 创建订单(服务端生成业务订单) │ │ │ ├─ 若为平台内购(如 Google Play) │ │ ├─ 调起商店购买 │ │ ├─ 获取 purchaseData + signature + storeOrderId │ │ └─ 回调服务端校验与入账 │ │ │ └─ 若为外部支付 │ └─ 打开 payUrl 在 Web/外部浏览器完成支付 │ └─ 未登录 或 未开启第三方支付 └─ 可走平台内购直购分支(由业务策略决定) ``` --- ## 3. 支付分支依据(通用) | 条件 | 说明 | |------|------| | 是否登录 | 通常需要用户身份用于订单归属与回调入账 | | 是否开启第三方支付 | 由服务端用户配置或渠道策略控制 | | 当前平台能力 | Android / iOS 对内购与外部支付支持差异 | | 商品配置 | 某些商品只允许特定支付方式 | 建议将分支判断集中在统一策略层,避免同一逻辑散落在多个页面。 --- ## 4. 商品展示与获取 ### 4.1 获取时机 - 进入充值页时拉取一次; - 下拉刷新或切换国家/渠道后重新拉取; - 支付成功后建议刷新,确保档位与余额展示一致。 ### 4.2 商品字段(通用语义) | 字段语义 | 说明 | |------|------| | `productId` | 商店商品 ID(内购必填) | | `activityId` | 服务端活动/档位 ID(建单常用) | | `actualAmount` | 实付金额 | | `originAmount` | 原价(可用于划线价) | | `bonus` / `bonusCredits` | 赠送积分 | | `credits` | 到账积分(如与赠送分开展示) | | `title` | 商品文案(可能由服务端直接下发) | --- ## 5. 第三方支付流程(通用) ### 5.1 拉取支付方式 以商品或活动为上下文请求支付方式列表,常见字段包括: - `paymentMethod`:主支付方式(例如平台内购、钱包、卡支付等) - `subPaymentMethod`:子方式(可选) - `name` / `icon`:展示信息 - `recommend`:是否推荐 ### 5.2 创建订单 创建订单请求通常包含(**逻辑字段名**): - 应用标识(`app`) - 用户标识(`userId`) - 商品或活动标识(`activityId` / `productId`) - 支付主方式(`paymentMethod`) - 支付类型(`paymentType`,按渠道语义填写) - 子支付方式(`subPaymentMethod`,如卡种/钱包子通道) > 关键点:`subPaymentMethod` 是独立语义字段。 > 仅传 `paymentType` 不会自动补出 `subPaymentMethod`。 #### 5.2.1 推荐入参组合(避免丢子支付方式) - 卡支付场景(示例): - `paymentMethod = MIFAPAY` - `paymentType = MIFAPAY`(或按后端约定值) - `subPaymentMethod = CreditCard` - 若传成 `paymentType = CreditCard` 且未传 `subPaymentMethod`,常见结果是: - 有 `paymentMethod` - 有 `paymentType` - **缺少 `subPaymentMethod`** 响应通常返回: - `orderId`(业务订单号) - `payUrl`(外部支付链接;仅外部支付场景) - `status`(订单状态) - 可选 `federation`(用于与商店订单映射) ### 5.3 分支执行 - **平台内购**:拉起商店购买 -> 取回凭据 -> 回调服务端核验 -> 成功后完成/消费交易。 - **外部支付**:打开 `payUrl` 完成支付 -> 回前台后查询订单状态或拉取账户余额。 --- ## 6. 平台内购标准链路(Android/iOS 可类比) 1. 用 `productId` 调起商店支付; 2. 拿到商店返回的交易凭据(如 `purchaseData`、`signature`、`storeOrderId`); 3. 调服务端回调接口完成验签与入账; 4. 服务端成功后,客户端执行“完成交易/消费交易”(防止重复拥有问题); 5. 刷新账户余额与订单状态。 > 关键原则:**先服务端验单成功,再本地完成/消费交易**,避免凭据丢失导致资产不一致。 --- ## 7. 补单机制(订单恢复) ### 7.1 触发时机 - 应用启动后; - 进入充值页时; - 支付异常返回时(例如网络中断、回调超时)。 ### 7.2 恢复流程 1. 拉取未完成/未核销的商店订单(历史订单 + 监听流中的待处理订单); 2. 对每笔订单尝试找到业务订单映射(如 `federation` / `orderId`); 3. 若可映射:再次走服务端回调核验,成功后完成/消费交易; 4. 若不可映射:按产品策略处理(仅完成/消费以解除占用,或进入人工排查队列); 5. 恢复结束后刷新账户与充值记录。 --- ## 8. API 能力清单(通用命名) | 能力 | 典型方法 | 说明 | |------|------|------| | 获取商品列表 | `GET` | Android/iOS 可分端点 | | 获取支付方式 | `POST`/`GET` | 入参通常含 `activityId` | | 创建订单 | `POST` | 返回 `orderId`、`payUrl` 等 | | 平台内购回调 | `POST` | 提交 `purchaseData`、`signature` 等 | | 订单列表 | `GET` | 历史支付记录 | | 订单详情 | `GET` | 查询单笔订单状态 | --- ## 9. 核心字段对照(通用) | 业务含义 | 常见请求字段 | 常见响应字段 | |------|------|------| | 应用标识 | `app` | - | | 用户标识 | `userId` | - | | 活动/档位 | `activityId` | - | | 支付方式 | `paymentMethod` | - | | 子支付方式 | `paymentType` | - | | 订单标识 | `orderId` / `id` | `orderId` | | 购买签名 | `signature` | - | | 购买数据 | `purchaseData` | - | | 商品 ID | - | `productId` | | 实付金额 | - | `actualAmount` | | 原价 | - | `originAmount` | | 赠送积分 | - | `bonus` / `bonusCredits` | | 支付链接 | - | `payUrl` | | 订单状态 | - | `status` | --- ## 10. 常见问题 ### 10.1 商品未找到 - **现象**:发起内购时提示商品不存在; - **常见原因**:客户端 `productId` 与商店后台配置不一致,或商品未上架到当前测试轨道; - **排查建议**:核对包名/应用 ID、商品 ID、地区、测试账号与轨道配置。 ### 10.2 回调成功但积分未更新 - **现象**:支付侧显示成功,账户余额未变; - **常见原因**:回调后未刷新账户、订单状态未落库、幂等冲突; - **排查建议**:检查回调响应、订单状态接口与账户刷新链路。 ### 10.3 重复购买被拦截(已拥有此内容) - **现象**:商店提示已拥有,无法再次购买; - **常见原因**:上一笔交易未完成/未消费; - **排查建议**:执行补单恢复流程并确保完成/消费逻辑成功。 ### 10.4 创建订单请求缺少子支付方式字段 - **现象**:抓包中 `createPayment` 入参有 `paymentMethod`,但缺少 `subPaymentMethod`; - **常见原因**:只传了 `paymentType`,没有传 `subPaymentMethod`; - **排查建议**: - 检查应用侧建单调用是否显式传入 `subPaymentMethod`; - 对照请求日志逐项核对三元组:`paymentMethod` / `paymentType` / `subPaymentMethod`; - 若项目启用了字段映射,再额外确认映射配置与服务端契约一致。 --- ## 11. 实施建议(多客户端复用) - 以“流程能力层”封装支付,不把分支逻辑写死在页面; - 页面只关心:档位展示、用户选择、状态反馈; - 建单、回调、补单、轮询统一由支付服务层负责; - 统一错误码与文案映射,避免各端提示不一致; - 对关键节点埋点:拉起支付、建单成功、回调成功、补单成功、入账成功; - 保持接口字段可映射,支持不同换皮应用的字段命名差异。