feat(payment): 优化套餐配置和支付服务
- 简化套餐配置结构,删除冗余的 default.yaml 和 us.yaml - 优化 Apple IAP 服务和验证逻辑 - 更新套餐数据模型和协议文档 - 添加支付相关测试用例
This commit is contained in:
@@ -14,25 +14,11 @@ import '../models/apple_purchase_models.dart';
|
||||
|
||||
enum PurchaseFlowState { idle, purchasing, verifying, success, failed }
|
||||
|
||||
class PurchaseResult {
|
||||
const PurchaseResult({
|
||||
required this.state,
|
||||
this.creditsAdded,
|
||||
this.errorMessage,
|
||||
});
|
||||
|
||||
final PurchaseFlowState state;
|
||||
final int? creditsAdded;
|
||||
final String? errorMessage;
|
||||
}
|
||||
|
||||
class AppleIapService with ChangeNotifier {
|
||||
AppleIapService({
|
||||
required ApiClient apiClient,
|
||||
required String userId,
|
||||
}) : _paymentApi = ApplePaymentApi(apiClient: apiClient),
|
||||
_inAppPurchase = InAppPurchase.instance,
|
||||
_appAccountToken = _hashUserId(userId);
|
||||
AppleIapService({required ApiClient apiClient, required String userId})
|
||||
: _paymentApi = ApplePaymentApi(apiClient: apiClient),
|
||||
_inAppPurchase = InAppPurchase.instance,
|
||||
_appAccountToken = _hashUserId(userId);
|
||||
|
||||
final ApplePaymentApi _paymentApi;
|
||||
final InAppPurchase _inAppPurchase;
|
||||
@@ -116,7 +102,7 @@ class AppleIapService with ChangeNotifier {
|
||||
);
|
||||
|
||||
if (!bought) {
|
||||
_setError('Failed to initiate purchase');
|
||||
_setState(PurchaseFlowState.idle);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -130,19 +116,62 @@ class AppleIapService with ChangeNotifier {
|
||||
}
|
||||
|
||||
Future<void> _handlePurchase(PurchaseDetails purchase) async {
|
||||
switch (purchase.status) {
|
||||
case PurchaseStatus.purchased:
|
||||
await _verifyAndComplete(purchase);
|
||||
case PurchaseStatus.canceled:
|
||||
await _inAppPurchase.completePurchase(purchase);
|
||||
_setState(PurchaseFlowState.idle);
|
||||
case PurchaseStatus.error:
|
||||
await _inAppPurchase.completePurchase(purchase);
|
||||
_setError(purchase.error?.message ?? 'Purchase failed');
|
||||
case PurchaseStatus.pending:
|
||||
_setState(PurchaseFlowState.purchasing);
|
||||
case PurchaseStatus.restored:
|
||||
await _inAppPurchase.completePurchase(purchase);
|
||||
try {
|
||||
switch (purchase.status) {
|
||||
case PurchaseStatus.purchased:
|
||||
await _verifyAndComplete(purchase);
|
||||
case PurchaseStatus.canceled:
|
||||
await _completePurchaseSafely(purchase);
|
||||
_setState(PurchaseFlowState.idle);
|
||||
case PurchaseStatus.error:
|
||||
final errorCode = purchase.error?.code;
|
||||
final isUserCancel = errorCode == '2';
|
||||
if (isUserCancel) {
|
||||
await _completePurchaseSafely(purchase);
|
||||
_setState(PurchaseFlowState.idle);
|
||||
} else {
|
||||
_logger.warning(
|
||||
message: 'Purchase error',
|
||||
extra: {
|
||||
'errorCode': errorCode,
|
||||
'errorMessage': purchase.error?.message,
|
||||
},
|
||||
);
|
||||
await _completePurchaseSafely(purchase);
|
||||
_setError(purchase.error?.message ?? 'Purchase failed');
|
||||
}
|
||||
case PurchaseStatus.pending:
|
||||
_setState(PurchaseFlowState.purchasing);
|
||||
case PurchaseStatus.restored:
|
||||
await _completePurchaseSafely(purchase);
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
_logger.error(
|
||||
message: 'Failed to handle purchase',
|
||||
error: e,
|
||||
stackTrace: stackTrace,
|
||||
extra: {
|
||||
'productID': purchase.productID,
|
||||
'purchaseID': purchase.purchaseID,
|
||||
'status': purchase.status.name,
|
||||
},
|
||||
);
|
||||
_setState(PurchaseFlowState.idle);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _completePurchaseSafely(PurchaseDetails purchase) async {
|
||||
try {
|
||||
await _inAppPurchase.completePurchase(purchase);
|
||||
} catch (e, stackTrace) {
|
||||
_logger.warning(
|
||||
message: 'completePurchase failed (likely null purchaseID): $e',
|
||||
stackTrace: stackTrace,
|
||||
extra: {
|
||||
'productID': purchase.productID,
|
||||
'purchaseID': purchase.purchaseID,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +207,12 @@ class AppleIapService with ChangeNotifier {
|
||||
message: 'Purchase verification failed',
|
||||
error: e,
|
||||
stackTrace: stackTrace,
|
||||
extra: {'transactionId': purchase.purchaseID},
|
||||
extra: {
|
||||
'transactionId': purchase.purchaseID,
|
||||
'productID': purchase.productID,
|
||||
'errorType': e.runtimeType.toString(),
|
||||
'errorMessage': e.toString(),
|
||||
},
|
||||
);
|
||||
|
||||
if (_isRetryableError(e)) {
|
||||
@@ -216,7 +250,7 @@ class AppleIapService with ChangeNotifier {
|
||||
String _productCodeFromStoreKitId(String storeKitId) {
|
||||
return switch (storeKitId) {
|
||||
'com.meeyao.qianwen.new_user_pack' => 'new_user_pack',
|
||||
'com.meeyao.qianwen.basic_pack' => 'basic_pack',
|
||||
'com.meeyao.qianwen.starter_pack' => 'starter_pack',
|
||||
'com.meeyao.qianwen.popular_pack' => 'popular_pack',
|
||||
'com.meeyao.qianwen.premium_pack' => 'premium_pack',
|
||||
_ => storeKitId,
|
||||
|
||||
Reference in New Issue
Block a user