chatapp3-flutter/lib/services/payment/apple_payment_manager.dart
2026-04-09 21:32:23 +08:00

340 lines
10 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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:yumi/services/auth/user_profile_manager.dart';
import 'package:provider/provider.dart';
import 'package:yumi/shared/tools/sc_loading_manager.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:yumi/app_localizations.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';
import '../../shared/data_sources/models/enum/sc_erro_code.dart';
class IOSPaymentProcessor extends ChangeNotifier {
StreamSubscription<List<PurchaseDetails>>? _subscription;
final InAppPurchase iap = InAppPurchase.instance;
// 只复制必要的状态变量
bool _isAvailable = false;
String _errorMessage = '';
List<SelecteProductConfig> _products = [];
late BuildContext context;
Map<String, SCProductConfigRes> productMap = {};
// 公开访问器
bool get isAvailable => _isAvailable;
String get errorMessage => _errorMessage;
List<SelecteProductConfig> get products => _products;
// 初始化 - 只处理iOS逻辑
Future<void> initializePaymentProcessor(BuildContext context) async {
this.context = context;
// 如果不是iOS直接返回
if (!Platform.isIOS) {
_errorMessage = 'This provider is for iOS only';
return;
}
try {
SCLoadingManager.show(context: context);
// 检查支付是否可用
_isAvailable = await iap.isAvailable();
if (!_isAvailable) {
_errorMessage = 'App Store payment service is unavailable';
SCTts.show(_errorMessage);
return;
}
_subscription?.cancel();
// 设置购买流监听
_subscription = iap.purchaseStream.listen(
_handlePurchase,
onError: (error, stackTrace) => _handleError(error, stackTrace),
);
// 获取iOS商品配置
await _loadIOSProductConfig();
// 获取苹果商品信息
await _fetchIOSProducts();
} catch (e) {
SCTts.show("Apple Pay init fail: $e");
_errorMessage = 'Apple Pay init fail: ${e.toString()}';
} finally {
SCLoadingManager.hide();
notifyListeners();
}
}
/// 加载iOS商品配置
Future _loadIOSProductConfig() async {
productMap.clear();
// 假设你的接口支持按平台获取配置
var productConfigs = await SCConfigRepositoryImp().productConfig();
for (var value in productConfigs) {
productMap[value.productPackage!] = value;
}
notifyListeners();
}
// 获取iOS商品信息
Future<void> _fetchIOSProducts() async {
try {
SCLoadingManager.show(context: context);
notifyListeners();
Set<String> productIds = productMap.keys.toSet();
ProductDetailsResponse response = await iap.queryProductDetails(
productIds,
);
if (response.notFoundIDs.isNotEmpty) {
_errorMessage = '未找到商品: ${response.notFoundIDs.join(', ')}';
debugPrint(_errorMessage);
}
var details = response.productDetails;
// 保持你的排序逻辑
if (details.isNotEmpty) {
// 这里可以添加iOS特定的排序逻辑
}
_products.clear();
for (var v in details) {
_products.add(SelecteProductConfig(v, _products.isEmpty)); // 第一个选中
}
} catch (e) {
SCTts.show("Failed to retrieve iOS products: $e");
_errorMessage = 'Failed to get iOS goods: ${e.toString()}';
debugPrint(_errorMessage);
} finally {
SCLoadingManager.hide();
notifyListeners();
}
}
// 发起iOS购买
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: () {
_goIOSBuy(product!);
},
);
},
);
}
// iOS购买流程
void _goIOSBuy(ProductDetails product) async {
try {
SCLoadingManager.show();
notifyListeners();
final PurchaseParam purchaseParam = PurchaseParam(
productDetails: product,
applicationUserName: null,
);
// iOS统一使用buyNonConsumable但实际类型由App Store Connect配置决定
bool success = await iap.buyConsumable(purchaseParam: purchaseParam);
if (!success) {
_errorMessage =
'If the purchase fails to start, check your network connection';
SCTts.show(_errorMessage);
}
} catch (e) {
SCTts.show("iOS Purchase failed: $e");
_errorMessage = 'iOS purchase failed: ${e.toString()}';
debugPrint(_errorMessage);
} finally {
SCLoadingManager.hide();
notifyListeners();
}
}
// 处理iOS购买结果
void _handlePurchase(List<PurchaseDetails> purchases) {
for (var purchase in purchases) {
switch (purchase.status) {
case PurchaseStatus.purchased:
_verifyIOSPayment(purchase);
break;
case PurchaseStatus.error:
if (purchase.error != null) {
_handleIOSError(purchase.error!);
}
break;
case PurchaseStatus.pending:
debugPrint('iOS支付处理中: ${purchase.productID}');
SCTts.show('Payment is being processed, please wait...');
break;
case PurchaseStatus.restored:
// SmartDialog.show(
// tag: "showConfirmDialog",
// alignment: Alignment.center,
// animationType: SmartAnimationType.fade,
// builder: (_) {
// return MsgDialog(
// title: SCAppLocalizations.of(context)!.tips,
// msg: SCAppLocalizations.of(context)!.restorePurchasesTips,
// btnText: SCAppLocalizations.of(context)!.confirm,
// onEnsure: () {
//
// },
// );
// },
// );
_verifyIOSPayment(purchase); // 恢复的购买也需要验证
break;
case PurchaseStatus.canceled:
break;
}
}
notifyListeners();
}
// 验证iOS支付
Future<void> _verifyIOSPayment(PurchaseDetails purchase) async {
SCLoadingManager.show();
try {
String receiptData = purchase.verificationData.serverVerificationData;
if (receiptData.isEmpty) {
receiptData = purchase.verificationData.localVerificationData;
}
// iOS验证 - 使用服务器收据验证
try {
await SCConfigRepositoryImp().applePay(
purchase.productID,
receiptData,
purchase.purchaseID ?? "",
);
// 🔥 iOS关键必须完成交易
await iap.completePurchase(purchase);
// 更新用户信息
Provider.of<SocialChatUserProfileManager>(context, listen: false).fetchUserProfileData();
Provider.of<SocialChatUserProfileManager>(context, listen: false).balance();
SCTts.show('Purchase is successful!');
debugPrint('iOS购买成功: ${purchase.productID}');
} catch (e) {
if (e.toString().endsWith("${SCErroCode.orderExistsCreated.code}")) {
SCTts.show('The order already exists!');
await iap.completePurchase(purchase);
}
}
} catch (e) {
SCTts.show("ios verification fails $e");
_errorMessage = 'iOS verification failed: ${e.toString()}';
debugPrint(_errorMessage);
// 即使验证失败,也要完成交易,否则会卡住
if (purchase.pendingCompletePurchase) {
await iap.completePurchase(purchase);
}
} finally {
SCLoadingManager.hide();
notifyListeners();
}
}
// 交付商品(与安卓相同)
Future<void> _deliverProduct(String productId) async {
debugPrint('交付iOS商品: $productId');
// 使用相同的业务逻辑
}
// iOS错误处理
void _handleIOSError(IAPError error) {
_errorMessage = 'iOS payment error: ${error.message} (${error.code})';
switch (error.code) {
case 'SKErrorPaymentCancelled':
SCTts.show('the purchase has been canceled');
break;
case 'SKErrorPaymentNotAllowed':
SCTts.show('the device does not support in app purchases');
break;
default:
SCTts.show('ios payment failed: ${error.message}');
break;
}
debugPrint(_errorMessage);
SCLoadingManager.hide();
notifyListeners();
}
void _handleError(Object error, StackTrace stackTrace) {
if (error is IAPError) {
_handleIOSError(error);
} else {
_errorMessage = 'iOS Unknown Error: ${error.toString()}';
SCTts.show(_errorMessage);
}
}
// iOS恢复购买
Future<void> recoverTransactions() async {
try {
SCLoadingManager.show(context: context);
notifyListeners();
SCTts.show('recovering ios purchases...');
await iap.restorePurchases();
} catch (e) {
_errorMessage = 'Failed to restore iOS purchases: ${e.toString()}';
SCTts.show(_errorMessage);
} finally {
SCLoadingManager.hide();
notifyListeners();
}
}
void chooseProductConfig(int index) {
for (var v in _products) {
v.isSelecte = false;
}
_products[index].isSelecte = true;
notifyListeners();
}
@override
void dispose() {
_subscription?.cancel();
super.dispose();
}
}