136 lines
7.4 KiB
Markdown
136 lines
7.4 KiB
Markdown
# 支付流程(当前实现)
|
||
|
||
本文档描述充值页「Buy」点击后的完整支付流程,与 `recharge_screen.dart`、`PaymentApi`、`GooglePlayPurchaseService` 实现一致。
|
||
|
||
---
|
||
|
||
## 1. 流程总览
|
||
|
||
```
|
||
用户点击 Buy
|
||
│
|
||
├─ enable_third_party_payment === true 且已登录
|
||
│ │
|
||
│ ├─ getPaymentMethods(activityId)
|
||
│ ├─ 弹窗选择支付方式(_PaymentMethodDialog)
|
||
│ ├─ createPayment(activityId, productId, paymentMethod, subPaymentMethod)
|
||
│ │
|
||
│ ├─ 若选中的是 Google Pay(resource/ceremony == "GooglePay")
|
||
│ │ ├─ 调起 Google Play 内购(productId = item.code)
|
||
│ │ ├─ 拿到 serverVerificationData
|
||
│ │ └─ POST /v1/payment/googlepay(merchant, federation, asset)
|
||
│ │
|
||
│ └─ 否则(其他支付方式)
|
||
│ └─ 打开 createPayment 返回的 payUrl(convert)在外部浏览器完成支付
|
||
│
|
||
└─ enable_third_party_payment !== true 或未登录
|
||
└─ 仅 Android:直接调起 Google Play 内购(productId = item.code),无 createPayment
|
||
```
|
||
|
||
---
|
||
|
||
## 2. 支付分支依据
|
||
|
||
- **数据来源**:`/v1/user/common_info` 响应中的 **surge**(JSON 字符串),解析得到 **enable_third_party_payment**。
|
||
- **客户端状态**:`UserState.enableThirdPartyPayment`(登录后由 AuthService 从 common_info 写入)。
|
||
- **分支**:
|
||
- **第三方支付**:`enable_third_party_payment == true` 且 `UserState.userId` 非空 → 走「获取支付方式 → 弹窗选择 → 创建订单 → 按支付方式分支」。
|
||
- **直接谷歌支付**:否则(未开三方或未登录)→ 仅 Android 下直接调起 Google Play 内购,不调 getPaymentMethods / createPayment。
|
||
|
||
---
|
||
|
||
## 3. 支付界面与商品展示
|
||
|
||
- **界面**:`recharge_screen.dart`。
|
||
- **商品来源**:`GET /v1/payment/getGooglePayActivities`(Android)或 getApplePayActivities(iOS),列表为 data.summon(activitys)。
|
||
- **单条商品字段(V2 映射)**:
|
||
|
||
| 字段(映射后) | 说明 |
|
||
|----------------|------|
|
||
| helm | 产品代码,即 **Google Pay 商品 ID**(与 Play 后台「产品 ID」必须一致) |
|
||
| warrior | 活动 ID(activityId),getPaymentMethods / createPayment 必传 |
|
||
| guardian | 实际金额,界面带 **$** 显示 |
|
||
| curriculum | 原价,界面**中划线**显示 |
|
||
| forge | 赠送积分,界面展示 |
|
||
| glossary | 标题;greaves=积分数,familiar=货币等 |
|
||
|
||
- **界面规则**:价格 / 原价 / 赠送积分同一行展示;仅当前点击的 item 的 Buy 按钮显示 loading(_loadingProductId)。
|
||
|
||
---
|
||
|
||
## 4. 第三方支付流程(enable_third_party_payment === true)
|
||
|
||
### 4.1 步骤顺序
|
||
|
||
| 步骤 | 说明 |
|
||
|------|------|
|
||
| 1 | 用户点击某商品的 Buy,得到该商品的 **activityId**(warrior)、**productId**(helm/code)。 |
|
||
| 2 | **POST /v1/payment/get-payment-methods**,body:warrior=activityId,vambrace=国家(可选)。 |
|
||
| 3 | 弹窗 **支付方式列表**(renew),用户选择一项 → 得到 resource(paymentMethod)、ceremony(subPaymentMethod)。 |
|
||
| 4 | **POST /v1/payment/createPayment**,body:sentinel, asset(userId), warrior(activityId), resource, ceremony;得到 federation(订单 ID)、convert(payUrl)等。 |
|
||
| 5a | 若 **resource 或 ceremony 为 "GooglePay"**(不区分大小写):调起 Google Play 内购(productId=item.code),成功后用 serverVerificationData 作为 merchant 调用 **POST /v1/payment/googlepay**(merchant, federation, asset),不打开 payUrl。 |
|
||
| 5b | 否则:若有 **convert**(payUrl)则用 url_launcher 在外部浏览器打开;无则仅提示订单已创建。 |
|
||
|
||
### 4.2 支付方式弹窗
|
||
|
||
- 在 get-payment-methods 成功后,展示 **Select payment method** 弹窗(_PaymentMethodDialog),列表项来自 **renew**(PaymentMethodItem:resource, ceremony, name, icon, recommend 等)。
|
||
- 用户选择一项后关闭弹窗,用选中的 resource、ceremony 调用 createPayment;取消弹窗则不创建订单并清除 loading。
|
||
|
||
### 4.3 选中 Google Pay 时的子流程
|
||
|
||
- 条件:`_isGooglePay(paymentMethod, subPaymentMethod)` 为 true(即 paymentMethod 或 subPaymentMethod 转为小写后等于 `"googlepay"`)。
|
||
- 仅 Android 执行;非 Android 提示 "Google Pay is only available on Android" 并结束。
|
||
- 调用 **GooglePlayPurchaseService.launchPurchaseAndReturnData(productId)**:
|
||
- productId 为当前商品的 **code**(helm)。
|
||
- 成功返回 **serverVerificationData**(merchant),失败/取消返回 null。
|
||
- 若有 merchant 且订单 ID(federation)存在,调用 **PaymentApi.googlepay(merchant, federation, asset)**;根据返回提示成功或失败并打点(AdjustEvents)。
|
||
|
||
---
|
||
|
||
## 5. 直接谷歌支付(enable_third_party_payment !== true)
|
||
|
||
- 仅 **Android**:调用 **GooglePlayPurchaseService.launchPurchase(item.code)**,不请求 getPaymentMethods / createPayment。
|
||
- 成功/失败通过 SnackBar 与 AdjustEvents 打点;商品 ID 仍为 **item.code**(须与 Play 后台产品 ID 一致)。
|
||
- 非 Android:提示 "Google Pay is only available on Android"。
|
||
|
||
---
|
||
|
||
## 6. 接口与字段速查
|
||
|
||
- **GET /v1/payment/getGooglePayActivities**
|
||
Query:sentinel, portal;响应 data.summon / data.cleanse,单项含 helm, warrior, guardian, curriculum, forge 等。
|
||
|
||
- **POST /v1/payment/get-payment-methods**
|
||
Body:warrior(activityId), vambrace(可选)。响应 data.renew:每项 resource, ceremony, brigade, greylist, deny 等。
|
||
|
||
- **POST /v1/payment/createPayment**
|
||
Query:sentinel, asset(userId)。Body:sentinel, asset, warrior, resource, ceremony(可选)。
|
||
响应 data:federation(订单ID), convert(payUrl), destroy(状态), handshake, transplant 等。
|
||
|
||
- **POST /v1/payment/googlepay**
|
||
Query:sentinel, asset(userId)。Body:merchant(购买凭据 serverVerificationData), federation(订单ID), asset, sample(签名可选)。
|
||
响应 data:federation, line(状态), awaken(是否加积分) 等。
|
||
|
||
- 请求/响应 V2 加解密与字段映射以 **petsHeroAI_client_guide.md** 为准。
|
||
|
||
---
|
||
|
||
## 7. 代码与文档对应
|
||
|
||
| 功能 | 位置 |
|
||
|------|------|
|
||
| 支付分支与 Buy 入口 | recharge_screen.dart:_onBuy → enableThirdPartyPayment ? _runThirdPartyPayment : _runGooglePay |
|
||
| 第三方:获取支付方式 + 弹窗 | _runThirdPartyPayment:PaymentApi.getPaymentMethods → _PaymentMethodDialog |
|
||
| 第三方:创建订单 + Google Pay / 打开链接 | _createOrderAndOpenUrl:createPayment → _isGooglePay ? launchPurchaseAndReturnData + googlepay : launchUrl(convert) |
|
||
| 直接谷歌支付 | _runGooglePay → _launchGooglePlayPurchase → GooglePlayPurchaseService.launchPurchase |
|
||
| 谷歌内购 + 凭据上报 | google_play_purchase_service.dart:launchPurchaseAndReturnData / launchPurchase;PaymentApi.googlepay |
|
||
| 商品未找到排查 | docs/google_pay_product_not_found.md |
|
||
|
||
---
|
||
|
||
## 8. 小结
|
||
|
||
- **三方开**:getPaymentMethods → 弹窗选支付方式 → createPayment → 选 Google Pay 则内购 + googlepay 回调,否则打开 payUrl。
|
||
- **三方关**:仅 Android 直接内购(item.code),无 createPayment。
|
||
- **商品 ID**:始终使用接口返回的 **helm**(item.code),须与 Google Play 后台「产品 ID」完全一致,否则会出现「系统无法找到您要购买的商品」。
|