优化:谷歌支付完整流程
This commit is contained in:
parent
bd6f8ba813
commit
d2e7c0ac8f
@ -90,7 +90,24 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. 代码位置速查
|
## 5. 获取未核销订单
|
||||||
|
|
||||||
|
「未核销」指 Google Play 侧尚未被确认(`isAcknowledged == false`)的购买,通常出现在:上次支付成功后未完成 `completePurchase`、或未成功回调服务端即退出应用。
|
||||||
|
|
||||||
|
### 5.1 客户端如何获取
|
||||||
|
|
||||||
|
- **方法**:`GooglePlayPurchaseService.getUnacknowledgedPurchases()`
|
||||||
|
- **实现**:仅 Android,通过 `InAppPurchaseAndroidPlatformAddition.queryPastPurchases()` 查询本地/缓存的购买,再筛选 `billingClientPurchase.isAcknowledged == false` 的项。
|
||||||
|
- **返回**:`List<UnacknowledgedGooglePayPurchase>`,每项包含 `orderId`(Google 订单号)、`productId`、`payload`(purchaseData + signature,用于回调 body 的 merchant/sample)。
|
||||||
|
|
||||||
|
### 5.2 典型用法
|
||||||
|
|
||||||
|
- **应用启动时**:调用 `getUnacknowledgedPurchases()`,若有未核销订单,可逐条上报 `POST /v1/payment/googlepay`(federation 可用该笔的 `orderId`,若服务端支持;否则需先 createPayment 拿到 federation 再回调),上报成功后对该笔购买调用 `InAppPurchase.instance.completePurchase(purchase)` 完成核销。
|
||||||
|
- **注意**:`queryPastPurchases` 不包含已消耗(consumed)的商品;未确认的消耗型商品会一直在列表中直到被确认或消耗。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 代码位置速查
|
||||||
|
|
||||||
| 步骤 | 位置 |
|
| 步骤 | 位置 |
|
||||||
|------|------|
|
|------|------|
|
||||||
@ -99,11 +116,13 @@
|
|||||||
| 调起内购并拿凭据 | `GooglePlayPurchaseService.launchPurchaseAndReturnData(productId)` |
|
| 调起内购并拿凭据 | `GooglePlayPurchaseService.launchPurchaseAndReturnData(productId)` |
|
||||||
| 回调 googlepay | `PaymentApi.googlepay(...)`,在 `_createOrderAndOpenUrl` 内、内购成功后调用 |
|
| 回调 googlepay | `PaymentApi.googlepay(...)`,在 `_createOrderAndOpenUrl` 内、内购成功后调用 |
|
||||||
| 凭据数据结构 | Android:`GooglePlayPurchaseDetails.billingClientPurchase`(orderId, originalJson, signature) |
|
| 凭据数据结构 | Android:`GooglePlayPurchaseDetails.billingClientPurchase`(orderId, originalJson, signature) |
|
||||||
|
| 获取未核销订单 | `GooglePlayPurchaseService.getUnacknowledgedPurchases()`,见第 5 节 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. 小结
|
## 7. 小结
|
||||||
|
|
||||||
1. **创建订单**:仅在选择「Google Pay」且第三方支付开启时调用 createPayment;拿到 **federation** 后才会调起谷歌支付并回调 googlepay。
|
1. **创建订单**:仅在选择「Google Pay」且第三方支付开启时调用 createPayment;拿到 **federation** 后才会调起谷歌支付并回调 googlepay。
|
||||||
2. **调起谷歌支付**:productId 固定为当前商品的 **code(helm)**,与 Play 后台产品 ID 一致。
|
2. **调起谷歌支付**:productId 固定为当前商品的 **code(helm)**,与 Play 后台产品 ID 一致。
|
||||||
3. **回调 googlepay**:body 为四字段 **sample**(signature)、**merchant**(purchaseData/originalJson)、**federation**(订单 id)、**asset**(userId);federation 为空则不回调,可按策略重试创建订单或提示失败。
|
3. **回调 googlepay**:body 为四字段 **sample**(signature)、**merchant**(purchaseData/originalJson)、**federation**(订单 id)、**asset**(userId);federation 为空则不回调,可按策略重试创建订单或提示失败。
|
||||||
|
4. **未核销订单**:通过 `getUnacknowledgedPurchases()` 获取 `isAcknowledged == false` 的购买,可用于启动时补发回调或展示待处理订单。
|
||||||
|
|||||||
@ -8,11 +8,54 @@ import 'package:pets_hero_ai/core/api/api.dart';
|
|||||||
|
|
||||||
import '../../core/log/app_logger.dart';
|
import '../../core/log/app_logger.dart';
|
||||||
import 'models/google_pay_verification_payload.dart';
|
import 'models/google_pay_verification_payload.dart';
|
||||||
|
import 'models/unacknowledged_google_pay_purchase.dart';
|
||||||
|
|
||||||
/// 调起 Google Play 内购,所有内购均通过本方法发起并返回凭据用于服务端回调。
|
/// 调起 Google Play 内购,所有内购均通过本方法发起并返回凭据用于服务端回调。
|
||||||
abstract final class GooglePlayPurchaseService {
|
abstract final class GooglePlayPurchaseService {
|
||||||
static final _log = AppLogger('GooglePlayPurchase');
|
static final _log = AppLogger('GooglePlayPurchase');
|
||||||
|
|
||||||
|
/// 获取当前未核销(未确认)的谷歌支付订单。
|
||||||
|
/// 使用 Android [InAppPurchaseAndroidPlatformAddition.queryPastPurchases] 查询本地/缓存中的购买,
|
||||||
|
/// 筛选 [isAcknowledged == false] 的项。仅 Android 有效,非 Android 返回空列表。
|
||||||
|
/// 可用于应用启动时补发回调或展示待处理订单。
|
||||||
|
static Future<List<UnacknowledgedGooglePayPurchase>> getUnacknowledgedPurchases() async {
|
||||||
|
if (defaultTargetPlatform != TargetPlatform.android) {
|
||||||
|
_log.d('非 Android,无未核销订单');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
final iap = InAppPurchase.instance;
|
||||||
|
if (!await iap.isAvailable()) {
|
||||||
|
_log.w('Billing 不可用');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final androidAddition = iap.getPlatformAddition<InAppPurchaseAndroidPlatformAddition>();
|
||||||
|
final response = await androidAddition.queryPastPurchases();
|
||||||
|
if (response.error != null) {
|
||||||
|
_log.w('queryPastPurchases 错误: ${response.error!.message}');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
final list = <UnacknowledgedGooglePayPurchase>[];
|
||||||
|
for (final p in response.pastPurchases) {
|
||||||
|
final b = p.billingClientPurchase;
|
||||||
|
if (b.isAcknowledged) continue;
|
||||||
|
list.add(UnacknowledgedGooglePayPurchase(
|
||||||
|
orderId: b.orderId,
|
||||||
|
productId: p.productID,
|
||||||
|
payload: GooglePayVerificationPayload(
|
||||||
|
purchaseData: b.originalJson,
|
||||||
|
signature: b.signature,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_log.d('未核销订单数: ${list.length}');
|
||||||
|
return list;
|
||||||
|
} catch (e, st) {
|
||||||
|
_log.w('获取未核销订单失败: $e\n$st');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 发起购买并返回服务端回调所需凭据(purchaseData=originalJson, signature)。
|
/// 发起购买并返回服务端回调所需凭据(purchaseData=originalJson, signature)。
|
||||||
/// 成功返回 [GooglePayVerificationPayload],取消/失败返回 null。调用方填入 id(federation)、userId 后组 merchant 上报。
|
/// 成功返回 [GooglePayVerificationPayload],取消/失败返回 null。调用方填入 id(federation)、userId 后组 merchant 上报。
|
||||||
static Future<GooglePayVerificationPayload?> launchPurchaseAndReturnData(
|
static Future<GooglePayVerificationPayload?> launchPurchaseAndReturnData(
|
||||||
|
|||||||
@ -0,0 +1,21 @@
|
|||||||
|
import 'google_pay_verification_payload.dart';
|
||||||
|
|
||||||
|
/// 未核销(未确认)的谷歌支付订单。
|
||||||
|
/// 通过 [GooglePlayPurchaseService.getUnacknowledgedPurchases] 查询得到,
|
||||||
|
/// 可用于补发回调或本地展示。上报服务端时 federation 可用 [orderId](若服务端支持)或先 createPayment 再回调。
|
||||||
|
class UnacknowledgedGooglePayPurchase {
|
||||||
|
const UnacknowledgedGooglePayPurchase({
|
||||||
|
required this.orderId,
|
||||||
|
required this.productId,
|
||||||
|
required this.payload,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Google Play 订单号(purchase 内 orderId)
|
||||||
|
final String orderId;
|
||||||
|
|
||||||
|
/// 商品 ID(productId)
|
||||||
|
final String productId;
|
||||||
|
|
||||||
|
/// 凭据,用于 POST /v1/payment/googlepay 的 sample/merchant;federation/asset 由调用方填入。
|
||||||
|
final GooglePayVerificationPayload payload;
|
||||||
|
}
|
||||||
@ -283,7 +283,16 @@ class _RechargeScreenState extends State<RechargeScreen>
|
|||||||
);
|
);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
if (googlepayRes.isSuccess) {
|
if (googlepayRes.isSuccess) {
|
||||||
_showSnackBar(context, 'Purchase completed.');
|
final resData = googlepayRes.data is Map<String, dynamic>
|
||||||
|
? googlepayRes.data as Map<String, dynamic>
|
||||||
|
: null;
|
||||||
|
final line = (resData?['line']?.toString() ?? '').toUpperCase();
|
||||||
|
if (line == 'SUCCESS') {
|
||||||
|
await refreshAccount();
|
||||||
|
}
|
||||||
|
if (mounted) {
|
||||||
|
_showSnackBar(context, 'Purchase completed.');
|
||||||
|
}
|
||||||
AdjustEvents.trackPurchaseSuccess();
|
AdjustEvents.trackPurchaseSuccess();
|
||||||
} else {
|
} else {
|
||||||
_showSnackBar(
|
_showSnackBar(
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:adjust_sdk/adjust_attribution.dart';
|
||||||
import 'package:adjust_sdk/adjust_config.dart';
|
import 'package:adjust_sdk/adjust_config.dart';
|
||||||
import 'package:adjust_sdk/adjust.dart';
|
import 'package:adjust_sdk/adjust.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@ -6,6 +7,7 @@ import 'package:flutter/services.dart';
|
|||||||
|
|
||||||
import 'app.dart';
|
import 'app.dart';
|
||||||
import 'core/auth/auth_service.dart';
|
import 'core/auth/auth_service.dart';
|
||||||
|
import 'core/log/app_logger.dart';
|
||||||
import 'core/referrer/referrer_service.dart';
|
import 'core/referrer/referrer_service.dart';
|
||||||
import 'core/theme/app_colors.dart';
|
import 'core/theme/app_colors.dart';
|
||||||
|
|
||||||
@ -34,5 +36,27 @@ void _initAdjust() {
|
|||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
config.logLevel = AdjustLogLevel.verbose;
|
config.logLevel = AdjustLogLevel.verbose;
|
||||||
}
|
}
|
||||||
|
config.attributionCallback = _onAdjustAttribution;
|
||||||
Adjust.initSdk(config);
|
Adjust.initSdk(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final _adjustLog = AppLogger('Adjust');
|
||||||
|
|
||||||
|
void _onAdjustAttribution(AdjustAttribution attribution) {
|
||||||
|
_adjustLog.d('归因信息: '
|
||||||
|
'trackerToken=${attribution.trackerToken}, '
|
||||||
|
'trackerName=${attribution.trackerName}, '
|
||||||
|
'network=${attribution.network}, '
|
||||||
|
'campaign=${attribution.campaign}, '
|
||||||
|
'adgroup=${attribution.adgroup}, '
|
||||||
|
'creative=${attribution.creative}, '
|
||||||
|
'clickLabel=${attribution.clickLabel}, '
|
||||||
|
'costType=${attribution.costType}, '
|
||||||
|
'costAmount=${attribution.costAmount}, '
|
||||||
|
'costCurrency=${attribution.costCurrency}, '
|
||||||
|
'fbInstallReferrer=${attribution.fbInstallReferrer}');
|
||||||
|
if (attribution.jsonResponse != null &&
|
||||||
|
attribution.jsonResponse!.isNotEmpty) {
|
||||||
|
_adjustLog.d('归因 jsonResponse: ${attribution.jsonResponse}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user