497 lines
15 KiB
Dart
497 lines
15 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:in_app_purchase/in_app_purchase.dart';
|
|
import 'package:in_app_purchase_android/in_app_purchase_android.dart';
|
|
import 'package:yumi/app_localizations.dart';
|
|
import 'package:yumi/services/auth/user_profile_manager.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:yumi/shared/tools/sc_string_utils.dart';
|
|
import 'package:yumi/shared/tools/sc_loading_manager.dart';
|
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
|
|
|
import 'package:yumi/ui_kit/components/dialog/dialog_base.dart';
|
|
import 'package:yumi/ui_kit/components/sc_tts.dart';
|
|
import 'package:yumi/shared/data_sources/sources/repositories/sc_config_repository_imp.dart';
|
|
import 'package:yumi/shared/business_logic/models/res/sc_product_config_res.dart';
|
|
import 'package:yumi/modules/wallet/recharge/recharge_page.dart';
|
|
|
|
class AndroidPaymentProcessor extends ChangeNotifier {
|
|
StreamSubscription<List<PurchaseDetails>>? _subscription;
|
|
final InAppPurchase iap = InAppPurchase.instance;
|
|
|
|
// 状态管理变量
|
|
bool _isAvailable = false;
|
|
String _errorMessage = '';
|
|
List<SelecteProductConfig> _products = [];
|
|
List<PurchaseDetails> _purchases = [];
|
|
|
|
///商品列表
|
|
List<SCProductConfigRes> productConfigs = [];
|
|
|
|
// 维护商品类型映射
|
|
final Map<String, String> _productTypeMap = {};
|
|
|
|
// 公开访问器
|
|
bool get isAvailable => _isAvailable;
|
|
|
|
String get errorMessage => _errorMessage;
|
|
|
|
List<SelecteProductConfig> get products => _products;
|
|
|
|
List<PurchaseDetails> get purchases => _purchases;
|
|
late BuildContext context;
|
|
Map<String, SCProductConfigRes> productMap = {};
|
|
|
|
Map<String, String> productTypes = {};
|
|
|
|
// 初始化支付系统
|
|
Future<void> initializePaymentProcessor(BuildContext context) async {
|
|
this.context = context;
|
|
try {
|
|
SCLoadingManager.show(context: context);
|
|
|
|
// 检查支付是否可用
|
|
_isAvailable = await iap.isAvailable();
|
|
if (!_isAvailable) {
|
|
_errorMessage = 'Google Play payment service is unavailable';
|
|
SCTts.show("Google Play payment service is unavailable");
|
|
return;
|
|
}
|
|
_subscription?.cancel();
|
|
// 设置购买流监听
|
|
_subscription = iap.purchaseStream.listen(
|
|
_handlePurchase,
|
|
onError: (error, stackTrace) => _handleError(error, stackTrace),
|
|
);
|
|
|
|
// 先获取商品配置
|
|
await fetchProductConfiguration();
|
|
|
|
// 然后获取谷歌商品信息
|
|
await _fetchProducts();
|
|
|
|
// 最后恢复购买,处理未完成交易
|
|
await recoverTransactions();
|
|
} catch (e) {
|
|
SCTts.show("init fail: $e");
|
|
_errorMessage = 'init fail: ${e.toString()}';
|
|
} finally {
|
|
SCLoadingManager.hide();
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
///用户获取商店配置的商品列表
|
|
Future fetchProductConfiguration() async {
|
|
productMap.clear();
|
|
productConfigs = await SCConfigRepositoryImp().productConfig();
|
|
int index = 0;
|
|
for (var value in productConfigs) {
|
|
productMap[value.productPackage!] = value;
|
|
productTypes[value.productPackage!] = "consumable";
|
|
index = index + 1;
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
// 获取商品信息
|
|
Future<void> _fetchProducts() async {
|
|
try {
|
|
SCLoadingManager.show(context: context);
|
|
notifyListeners();
|
|
|
|
Set<String> productIds = productTypes.keys.toSet();
|
|
|
|
ProductDetailsResponse response = await iap.queryProductDetails(
|
|
productIds,
|
|
);
|
|
if (response.notFoundIDs.isNotEmpty) {
|
|
_errorMessage = '未找到商品: ${response.notFoundIDs.join(', ')}';
|
|
debugPrint(_errorMessage);
|
|
}
|
|
var dtails = response.productDetails;
|
|
if (dtails.isNotEmpty) {
|
|
dtails.sort((a, b) {
|
|
int ia = 0;
|
|
int ib = 0;
|
|
if (a.id.contains(".")) {
|
|
ia = SCStringUtils.convertToInteger(a.id.split(".").last);
|
|
}
|
|
if (b.id.contains(".")) {
|
|
ib = SCStringUtils.convertToInteger(b.id.split(".").last);
|
|
}
|
|
return ia.compareTo(ib);
|
|
});
|
|
}
|
|
int index = 0;
|
|
_products.clear();
|
|
for (var v in dtails) {
|
|
if (index == 0) {
|
|
_products.add(SelecteProductConfig(v, true));
|
|
} else {
|
|
_products.add(SelecteProductConfig(v, false));
|
|
}
|
|
index++;
|
|
}
|
|
|
|
// 初始化商品类型映射
|
|
for (var product in _products) {
|
|
if (productTypes.containsKey(product.produc.id)) {
|
|
_productTypeMap[product.produc.id] = productTypes[product.produc.id]!;
|
|
} else {
|
|
_productTypeMap[product.produc.id] = 'nonConsumable'; // 默认类型
|
|
}
|
|
}
|
|
|
|
// 打印商品信息用于调试
|
|
for (var product in _products) {
|
|
debugPrint(
|
|
'商品: ${product.produc.id}, 类型: ${_productTypeMap[product.produc.id]}, 价格: ${product.produc.price}',
|
|
);
|
|
}
|
|
} catch (e) {
|
|
SCTts.show("Failed to retrieve the product: $e");
|
|
_errorMessage = '获取商品失败: ${e.toString()}';
|
|
debugPrint(_errorMessage);
|
|
} finally {
|
|
SCLoadingManager.hide();
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
// 获取商品类型
|
|
String _getProductType(String productId) {
|
|
return _productTypeMap[productId] ?? 'nonConsumable';
|
|
}
|
|
|
|
// 发起购买
|
|
Future<void> processPurchase() async {
|
|
ProductDetails? product;
|
|
_products.forEach((d) {
|
|
if (d.isSelecte) {
|
|
product = d.produc;
|
|
}
|
|
});
|
|
if (product == null) {
|
|
SCTts.show(SCAppLocalizations.of(context)!.pleaseSelectaItem);
|
|
return;
|
|
}
|
|
SmartDialog.show(
|
|
tag: "showConfirmDialog",
|
|
alignment: Alignment.center,
|
|
debounce: true,
|
|
animationType: SmartAnimationType.fade,
|
|
builder: (_) {
|
|
return MsgDialog(
|
|
title: SCAppLocalizations.of(context)!.tips,
|
|
msg: SCAppLocalizations.of(context)!.areYouRureRoRecharge,
|
|
btnText: SCAppLocalizations.of(context)!.confirm,
|
|
onEnsure: () {
|
|
_goBuy(product!);
|
|
},
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
// 处理购买结果
|
|
void _handlePurchase(List<PurchaseDetails> purchases) {
|
|
_purchases = purchases;
|
|
|
|
for (var purchase in purchases) {
|
|
switch (purchase.status) {
|
|
case PurchaseStatus.purchased:
|
|
_verifyPayment(purchase);
|
|
break;
|
|
|
|
case PurchaseStatus.error:
|
|
if (purchase.error != null) {
|
|
_handleIAPError(purchase.error!);
|
|
// 特别处理 "item-already-owned" 错误
|
|
if (purchase.error!.code == 'item-already-owned') {
|
|
_handleAlreadyOwnedItem(purchase);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PurchaseStatus.pending:
|
|
debugPrint('支付处理中: ${purchase.productID}');
|
|
break;
|
|
|
|
case PurchaseStatus.restored:
|
|
// 处理恢复的购买
|
|
_verifyPayment(purchase);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
notifyListeners();
|
|
}
|
|
|
|
// 新增:处理已拥有商品的情况
|
|
Future<void> _handleAlreadyOwnedItem(PurchaseDetails purchase) async {
|
|
try {
|
|
debugPrint('检测到已拥有商品: ${purchase.productID},尝试消耗');
|
|
|
|
// 如果是消耗型商品,尝试消耗
|
|
if (_getProductType(purchase.productID) == 'consumable') {
|
|
await consumePurchase(purchase);
|
|
// 消耗后重新获取余额
|
|
Provider.of<SocialChatUserProfileManager>(context, listen: false).balance();
|
|
SCTts.show('outstanding purchases have been reinstated');
|
|
}
|
|
} catch (e) {
|
|
debugPrint('处理已拥有商品失败: $e');
|
|
}
|
|
}
|
|
|
|
// 验证支付凭证
|
|
Future<void> _verifyPayment(PurchaseDetails purchase) async {
|
|
try {
|
|
// 1. 基本验证
|
|
if (purchase.verificationData.serverVerificationData.isEmpty) {
|
|
_errorMessage = '无效的支付凭证';
|
|
SCTts.show("Invalid payment voucher");
|
|
return;
|
|
}
|
|
|
|
// 2. 检查是否已经处理过这个购买
|
|
if (!purchase.pendingCompletePurchase) {
|
|
debugPrint('购买已处理过: ${purchase.productID}');
|
|
return;
|
|
}
|
|
|
|
// 3. 获取购买数据
|
|
String purchaseData = purchase.verificationData.localVerificationData;
|
|
String signature = "";
|
|
|
|
if (purchase is GooglePlayPurchaseDetails) {
|
|
signature = purchase.billingClientPurchase.signature;
|
|
}
|
|
|
|
// 4. 服务器验证
|
|
await SCConfigRepositoryImp().googlePay(
|
|
purchase.productID,
|
|
signature,
|
|
purchaseData,
|
|
);
|
|
|
|
// 5. 交付商品
|
|
await _deliverProduct(purchase.productID);
|
|
|
|
// 6. 完成购买
|
|
await iap.completePurchase(purchase);
|
|
|
|
// 7. 如果是消耗型商品,确保消耗
|
|
if (_getProductType(purchase.productID) == 'consumable') {
|
|
await _ensurePurchaseConsumed(purchase);
|
|
}
|
|
|
|
// 8. 更新用户余额
|
|
Provider.of<SocialChatUserProfileManager>(context, listen: false).fetchUserProfileData();
|
|
Provider.of<SocialChatUserProfileManager>(context, listen: false).balance();
|
|
|
|
SCTts.show('purchase successful');
|
|
debugPrint('购买成功: ${purchase.productID}');
|
|
} catch (e) {
|
|
SCTts.show("verification failed: $e");
|
|
_errorMessage = '验证失败: ${e.toString()}';
|
|
debugPrint(_errorMessage);
|
|
} finally {
|
|
SCLoadingManager.hide();
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
// 交付商品
|
|
Future<void> _deliverProduct(String productId) async {
|
|
debugPrint('交付商品: $productId');
|
|
// 实现您的业务逻辑
|
|
// 例如:增加用户余额、解锁功能等
|
|
}
|
|
|
|
// 新增:确保购买被消耗
|
|
Future<void> _ensurePurchaseConsumed(PurchaseDetails purchase) async {
|
|
try {
|
|
// 等待一段时间确保购买完成
|
|
await Future.delayed(Duration(seconds: 1));
|
|
|
|
// 尝试消耗购买
|
|
await consumePurchase(purchase);
|
|
|
|
debugPrint('商品已成功消耗: ${purchase.productID}');
|
|
} catch (e) {
|
|
debugPrint('消耗商品失败,可能已自动消耗: $e');
|
|
}
|
|
}
|
|
|
|
void _handleError(Object error, StackTrace stackTrace) {
|
|
SCTts.show("Payment error: $error");
|
|
if (error is IAPError) {
|
|
// 处理IAP特定错误
|
|
_handleIAPError(error);
|
|
} else {
|
|
// 处理其他类型的错误
|
|
_errorMessage = '未知错误: ${error.toString()}';
|
|
SCTts.show(_errorMessage);
|
|
}
|
|
}
|
|
|
|
// 添加了专门的IAP错误处理方法
|
|
void _handleIAPError(IAPError error) {
|
|
_errorMessage = '支付错误: ${error.message} (${error.code})';
|
|
|
|
// 处理特定错误代码
|
|
switch (error.code) {
|
|
case 'payment-invalid':
|
|
debugPrint('支付无效: 商品ID可能配置错误');
|
|
SCTts.show(
|
|
'Payment configuration error, please contact customer service',
|
|
);
|
|
break;
|
|
case 'item-already-owned':
|
|
debugPrint('商品已拥有,尝试消耗商品');
|
|
SCTts.show('Unfinished purchase detected, processing...');
|
|
break;
|
|
case 'user-cancelled':
|
|
debugPrint('用户取消购买');
|
|
SCTts.show('Purchase cancelled');
|
|
break;
|
|
case 'service-timeout':
|
|
case 'service-unavailable':
|
|
debugPrint('Google Play服务不可用');
|
|
SCTts.show(
|
|
'Google Play services are temporarily unavailable, please try again later',
|
|
);
|
|
break;
|
|
default:
|
|
SCTts.show('Payment failed: ${error.message}');
|
|
break;
|
|
}
|
|
|
|
debugPrint(_errorMessage);
|
|
SCLoadingManager.hide();
|
|
notifyListeners();
|
|
}
|
|
|
|
// 恢复购买
|
|
Future<void> recoverTransactions() async {
|
|
try {
|
|
SCLoadingManager.show(context: context);
|
|
notifyListeners();
|
|
|
|
await iap.restorePurchases();
|
|
|
|
// 给恢复操作一些时间
|
|
await Future.delayed(Duration(milliseconds: 1000));
|
|
} catch (e) {
|
|
_errorMessage = '恢复购买失败: ${e.toString()}';
|
|
debugPrint(_errorMessage);
|
|
SCTts.show("Failed to restore purchase: ${e.toString()}");
|
|
} finally {
|
|
SCLoadingManager.hide();
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
// 消耗型商品手动消耗
|
|
Future<void> consumePurchase(PurchaseDetails purchase) async {
|
|
try {
|
|
if (Platform.isAndroid) {
|
|
final InAppPurchaseAndroidPlatformAddition androidAddition =
|
|
iap.getPlatformAddition<InAppPurchaseAndroidPlatformAddition>();
|
|
await androidAddition.consumePurchase(purchase);
|
|
debugPrint('已消耗商品: ${purchase.productID}');
|
|
}
|
|
} catch (e) {
|
|
_errorMessage = '消耗商品失败: ${e.toString()}';
|
|
debugPrint(_errorMessage);
|
|
}
|
|
}
|
|
|
|
// 购买流程
|
|
void _goBuy(ProductDetails product) async {
|
|
try {
|
|
SCLoadingManager.show(context: context);
|
|
notifyListeners();
|
|
|
|
// 购买前检查是否有未完成的购买
|
|
await _checkPendingPurchases();
|
|
|
|
final PurchaseParam purchaseParam = PurchaseParam(
|
|
productDetails: product,
|
|
applicationUserName: null,
|
|
);
|
|
|
|
String productType = _getProductType(product.id);
|
|
|
|
bool success;
|
|
if (productType == 'consumable') {
|
|
success = await iap.buyConsumable(
|
|
purchaseParam: purchaseParam,
|
|
autoConsume: kReleaseMode, // 生产环境自动消耗,调试时手动控制
|
|
);
|
|
} else {
|
|
success = await iap.buyNonConsumable(purchaseParam: purchaseParam);
|
|
}
|
|
|
|
if (!success) {
|
|
_errorMessage = '购买启动失败,请检查网络连接';
|
|
SCTts.show(_errorMessage);
|
|
}
|
|
} catch (e) {
|
|
SCTts.show("Purchase failed: $e");
|
|
_errorMessage = '购买失败: ${e.toString()}';
|
|
debugPrint(_errorMessage);
|
|
} finally {
|
|
SCLoadingManager.hide();
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
// 新增:检查未完成的购买
|
|
Future<void> _checkPendingPurchases() async {
|
|
try {
|
|
// 恢复购买以获取所有未完成交易
|
|
await iap.restorePurchases();
|
|
|
|
// 给一点时间处理恢复的购买
|
|
await Future.delayed(Duration(milliseconds: 550));
|
|
} catch (e) {
|
|
debugPrint('检查未完成购买时出错: $e');
|
|
}
|
|
}
|
|
|
|
// 添加调试方法
|
|
void logCurrentPurchaseStatus() {
|
|
debugPrint('=== 当前购买状态 ===');
|
|
debugPrint('可用商品数量: ${_products.length}');
|
|
debugPrint('未完成购买数量: ${_purchases.length}');
|
|
|
|
for (var purchase in _purchases) {
|
|
debugPrint('商品: ${purchase.productID}, 状态: ${purchase.status}');
|
|
debugPrint('待完成: ${purchase.pendingCompletePurchase}');
|
|
}
|
|
debugPrint('==================');
|
|
}
|
|
|
|
// 释放资源
|
|
@override
|
|
void dispose() {
|
|
_subscription?.cancel();
|
|
super.dispose();
|
|
}
|
|
|
|
void chooseProductConfig(int index) {
|
|
for (var v in _products) {
|
|
v.isSelecte = false;
|
|
}
|
|
_products[index].isSelecte = true;
|
|
notifyListeners();
|
|
}
|
|
}
|