import 'dart:async'; import 'dart:convert'; import 'package:agent_service/agent_service.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import '/data/services/network/api.dart'; import 'package:tobias/tobias.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:in_app_purchase/in_app_purchase.dart'; import 'package:in_app_purchase_android/in_app_purchase_android.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import '../../data/models/user_Info.dart'; import '../../modules/login/controllers/weixinLogin.dart'; typedef PaymentResultCallback = void Function(bool success, String message); /// 支付服务 class PayService extends GetxService { late FlWX _flwx; late Tobias _tobias; // 添加 Tobias 实例 String? _lastOrderId; // 最近一次创建的订单号 // 内购 final InAppPurchase _inAppPurchase = InAppPurchase.instance; StreamSubscription>? _iapSubscription; final RxBool iapAvailable = false.obs; PaymentResultCallback? _iapOnResult; // 最近一次 IAP 购买上下文,用于主动查询时携带验证参数 PurchaseDetails? _lastIapPurchase; ProductDetails? _lastIapProduct; String? get lastOrderId => _lastOrderId; @override void onInit() { super.onInit(); //微信 FlWX().register( appSecret: '9ceb39df312b3ba95a229b2aa0d15496', appId: 'wx4b5ec8957ad5e425', partnerId: '1719983673', universalLink: 'https://yimai.games/app/', params: FlWXCallbackParams(onHttp: (url) async { return ''; })); _flwx = FlWX(); // 初始化支付宝SDK _tobias = Tobias(); // 初始化 Tobias 实例 _initAlipay(); _initIAP(); } /// 初始化支付宝SDK Future _initAlipay() async { try { // 检查支付宝是否安x装 - 修复用法 final isInstalled = await _tobias.isAliPayInstalled; print('支付宝是否安装: $isInstalled'); } catch (e) { print('初始化支付宝SDK失败: $e'); } } /// 初始化内购(Apple/Google) Future _initIAP() async { try { // Web 环境不支持内购 if (kIsWeb) { iapAvailable.value = false; return; } final available = await _inAppPurchase.isAvailable(); iapAvailable.value = available; if (!available) return; // 监听购买结果流 _iapSubscription?.cancel(); _iapSubscription = _inAppPurchase.purchaseStream.listen( _handlePurchaseUpdates, onDone: () => _iapSubscription?.cancel(), onError: (e) { _iapOnResult?.call(false, '内购异常: $e'); }, ); } catch (e) { print('初始化内购失败: $e'); } } /// 调试:批量查询并打印内购商品详情(用于确认商品ID是否存在) Future debugQueryProducts(Set productIds) async { try { // 先原样打印传入的 productIds,便于核对入参 print('[IAP] debugQueryProducts 输入 IDs(Set): $productIds'); final idsList = productIds.toList(); print('[IAP] debugQueryProducts 输入 IDs(List): ${idsList.join(', ')}'); for (final id in productIds) { print('[IAP] 待查询商品ID: $id'); } // 仅在非 Web 平台执行;平台信息用于日志 final platform = _platformString(); if (!iapAvailable.value) { final available = await _inAppPurchase.isAvailable(); iapAvailable.value = available; } if (!iapAvailable.value) { print('[IAP-$platform] 当前平台内购不可用,无法查询商品。'); return; } print('[IAP-$platform] 开始查询商品,IDs=${productIds.join(', ')}'); final resp = await _inAppPurchase.queryProductDetails(productIds); if (resp.error != null) { final err = resp.error!; print( '[IAP-$platform] 查询错误: code=${err.code}, source=${err.source}, message=${err.message}, details=${err.details}'); } if (resp.productDetails.isEmpty) { print('[IAP-$platform] 未返回任何商品。notFound=${resp.notFoundIDs}'); } else { for (final p in resp.productDetails) { print( '[IAP-$platform] 商品: id=${p.id}, title=${p.title}, price=${p.price}, currency=${p.currencyCode}'); } if (resp.notFoundIDs.isNotEmpty) { print('[IAP-$platform] 未找到的IDs: ${resp.notFoundIDs}'); } } } catch (e) { print('[IAP] 调试查询异常: $e'); } } Future _queryIapProduct(String productId) async { try { final pid = productId.trim(); print('[IAP] 开始查询单个商品: id="$pid"'); final resp = await _inAppPurchase.queryProductDetails({pid}); if (resp.error != null) { final err = resp.error!; print( '[IAP] 查询商品失败: code=${err.code}, source=${err.source}, message=${err.message}, details=${err.details}'); } if (resp.productDetails.isEmpty) { print('[IAP] 查询结果为空。notFound=${resp.notFoundIDs}'); return null; } for (final p in resp.productDetails) { print( '[IAP] 命中商品: id=${p.id}, title=${p.title}, price=${p.price}, currency=${p.currencyCode}'); } // 避免 Dart 泛型推断在 Android 平台出现 orElse 类型不匹配(ProductDetails vs GooglePlayProductDetails) ProductDetails? matched; for (final p in resp.productDetails) { if (p.id == pid) { matched = p; break; } } return matched ?? resp.productDetails.first; } catch (e) { print('[IAP] 查询内购商品异常: $e'); return null; } } /// Apple/Google 内购统一流程(消耗型) Future _processIapPayment({ required String productId, PaymentResultCallback? onResult, }) async { _iapOnResult = onResult; print( '[IAP] 准备发起购买: id="${productId.trim()}" iapAvailable=${iapAvailable.value}'); if (!iapAvailable.value) { onResult?.call(false, '内购不可用'); return false; } final product = await _queryIapProduct(productId); if (product == null) { onResult?.call(false, '未找到内购商品: $productId'); return false; } try { // 记录最近一次用于购买的商品详情,便于后续构建查询参数 _lastIapProduct = product; final isAndroid = !kIsWeb && defaultTargetPlatform == TargetPlatform.android; bool ok; if (isAndroid && product is GooglePlayProductDetails) { // 通过环境变量决定订阅或一次性商品路径,避免访问不兼容的属性 final isSubscription = _getEnvInt('APP_IAP_GOOGLE_IS_SUBSCRIPTION', defaultValue: 0) == 1; if (isSubscription) { print('[IAP-android] 订阅商品(ENV),使用 buyNonConsumable'); final gpParam = GooglePlayPurchaseParam(productDetails: product); print( '[IAP-android] 调用 buyNonConsumable 参数: productId=${product.id}'); ok = await _inAppPurchase.buyNonConsumable(purchaseParam: gpParam); print('[IAP-android] buyNonConsumable 返回: $ok'); } else { // 普通一次性/消耗型商品,使用 buyConsumable final purchaseParam = PurchaseParam(productDetails: product); print( '[IAP-android] 调用 buyConsumable 参数: productId=${product.id} autoConsume=true'); ok = await _inAppPurchase.buyConsumable( purchaseParam: purchaseParam, autoConsume: true, ); print('[IAP-android] buyConsumable 返回: $ok'); } } else { // iOS 或非订阅商品,按消耗型处理(如需支持 iOS 订阅,可通过 ENV 切换调用 buyNonConsumable) final purchaseParam = PurchaseParam(productDetails: product); print( '[IAP-ios] 调用 buyConsumable 参数: productId=${product.id} autoConsume=true'); ok = await _inAppPurchase.buyConsumable( purchaseParam: purchaseParam, autoConsume: true, ); print('[IAP-ios] buyConsumable 返回: $ok'); } if (!ok) { onResult?.call(false, '无法发起内购'); print('[IAP] 发起内购失败:平台=${_platformString()} productId=${product.id}'); } else { onResult?.call(true, '已发起内购,请完成支付'); print('[IAP] 已发起内购:等待购买流回调(可能受旧交易阻塞或商店限制)'); } return ok; } catch (e) { if (e is PlatformException && (e.code.contains('purchase_cancelled') || e.code.contains('storekit2_purchase_cancelled'))) { onResult?.call(false, '已取消支付'); print( '[IAP] 用户取消支付(同步抛出) code=${e.code} message=${e.message} product=${product.id}'); return false; } onResult?.call(false, '发起内购失败: $e'); print('[IAP] 发起内购异常: $e'); return false; } } Future _handlePurchaseUpdates(List detailsList) async { for (final details in detailsList) { // 记录最近一次 IAP 购买详情 _lastIapPurchase = details; print( '[IAP] 更新: status=${details.status} product=${details.productID} purchaseID=${details.purchaseID} error=${details.error}'); switch (details.status) { case PurchaseStatus.pending: _iapOnResult?.call(true, '支付处理中…'); break; case PurchaseStatus.error: print( '[IAP] 错误: code=${details.error?.code} message=${details.error?.message}'); _iapOnResult?.call(false, details.error?.message ?? '支付错误'); break; case PurchaseStatus.purchased: final txId = details.purchaseID ?? ''; final ok = await _verifyIapWithServer(details); if (!ok) { print( '[IAP] 验证失败 purchased txId=${details.purchaseID} product=${details.productID}'); _iapOnResult?.call(false, details.error?.message ?? '支付错误'); } if (ok) { try { await _inAppPurchase.completePurchase(details); print('[IAP] 完成交易 purchased txId=${details.purchaseID}'); _iapOnResult?.call(true, details.error?.message ?? '支付成功'); } catch (e, st) { print('[IAP] 完成交易异常 purchased txId=${details.purchaseID} err=$e'); print('[IAP] 完成交易异常堆栈 purchased $st'); } } break; case PurchaseStatus.restored: final txId = details.purchaseID ?? ''; final ok = await _verifyIapWithServer(details); if (!ok) { print( '[IAP] 验证失败 restored(silent) txId=${details.purchaseID} product=${details.productID}'); _iapOnResult?.call(false, details.error?.message ?? '支付错误'); } if (ok) { try { await _inAppPurchase.completePurchase(details); _iapOnResult?.call(true, details.error?.message ?? '支付成功'); print('[IAP] 完成交易 restored(silent) txId=${details.purchaseID}'); } catch (e, st) { print( '[IAP] 完成交易异常 restored(silent) txId=${details.purchaseID} err=$e'); print('[IAP] 完成交易异常堆栈 restored(silent) $st'); } } break; case PurchaseStatus.canceled: _iapOnResult?.call(false, '已取消支付'); print('[IAP] 用户取消支付 product=${details.productID}'); break; } } } Future _verifyIapWithServer(PurchaseDetails details) async { try { final params = await buildIapAskParams(); final resp = await Api.payverifyorder(params); Map? user; Map? order; if (resp is Map) { if (resp['user'] is Map) { user = resp['user'] as Map; } else if (resp['data'] is Map && (resp['data']['user'] is Map)) { user = resp['data']['user'] as Map; } if (resp['order'] is Map) { order = resp['order'] as Map; } else if (resp['data'] is Map && (resp['data']['order'] is Map)) { order = resp['data']['order'] as Map; } } final int? status = order != null ? (order['status'] is int ? order['status'] as int : int.tryParse('${order['status']}')) : null; final bool ok = status == 1 || user != null; if (ok && user != null) { User.instance.updateUserInfo(user); setVipExptime(User.instance.vipexptime); } if (!ok) { final code = (resp is Map) ? resp['code'] : null; final msg = (resp is Map) ? (resp['message'] ?? resp['msg']) : null; print( '[IAP] 验证失败详情 status=$status code=$code msg=$msg order_id=${params['order_id']}'); } return ok; } catch (e) { print('[IAP] 服务器验证异常: $e'); return false; } } /// 构建 Apple/Google 主动查询(pay_askorder)所需的验证参数 /// 返回与后端 PayAskOrCancelReq 对应的 Map(不再携带 apple_sandbox) Future> buildIapAskParams() async { final params = {}; if (_lastOrderId != null && _lastOrderId!.isNotEmpty) { params['order_id'] = _lastOrderId; } final platform = _platformString(); if (platform == 'ios') { final receiptOrData = _lastIapPurchase?.verificationData.serverVerificationData ?? ''; final txId = _lastIapPurchase?.purchaseID ?? ''; // 是否按交易ID校验,默认 false,可通过环境变量开启 final useTxIdEnv = _getEnvBool('APP_IAP_USE_TXID', defaultValue: false); params['apple_receipt_data'] = useTxIdEnv ? txId : receiptOrData; params['apple_use_transaction_id'] = useTxIdEnv; } else if (platform == 'android') { final packageInfo = await PackageInfo.fromPlatform(); final pkg = packageInfo.packageName; final token = _lastIapPurchase?.verificationData.serverVerificationData ?? ''; final productId = _lastIapPurchase?.productID ?? _lastIapProduct?.id ?? ''; // 订阅标识,默认 false;如需严格区分,可在商品元数据或环境变量中配置 final isSub = _getEnvBool('APP_IAP_GOOGLE_IS_SUBSCRIPTION', defaultValue: false); params['google_package_name'] = pkg; params['google_product_id'] = productId; params['google_purchase_token'] = token; params['google_is_subscription'] = isSub; } return params; } bool _getEnvBool(String key, {required bool defaultValue}) { try { final v = dotenv.env[key]; if (v != null && v.isNotEmpty) { final low = v.toLowerCase(); return low == 'true' || low == '1' || low == 'yes'; } } catch (_) {} return defaultValue; } int _getEnvInt(String key, {required int defaultValue}) { try { final v = dotenv.env[key]; if (v != null && v.isNotEmpty) { return int.tryParse(v.trim()) ?? defaultValue; } } catch (_) {} return defaultValue; } String _platformString() { if (kIsWeb) return 'web'; switch (defaultTargetPlatform) { case TargetPlatform.iOS: return 'ios'; case TargetPlatform.android: return 'android'; case TargetPlatform.macOS: return 'macos'; case TargetPlatform.windows: return 'windows'; case TargetPlatform.linux: return 'linux'; case TargetPlatform.fuchsia: return 'fuchsia'; } } /// 解析 Apple 的真实商品ID(支持环境前缀/后缀或显式映射) /// 优先级:显式映射(APP_IOS_IAP_ID_MAP JSON) > 前缀(APP_IOS_IAP_PREFIX) > 后缀(APP_IOS_IAP_SUFFIX) > 原始ID /// 示例: /// - APP_IOS_IAP_PREFIX=com.voitrans. /// - APP_IOS_IAP_ID_MAP={"ai_001":"com.voitrans.ai001","ai_002":"com.voitrans.ai002"} String resolveAppleProductId(String backendId) { final raw = backendId.trim(); Map? mapped; try { final jsonStr = dotenv.env['APP_IOS_IAP_ID_MAP']; if (jsonStr != null && jsonStr.isNotEmpty) { mapped = json.decode(jsonStr) as Map; } } catch (e) { print('[IAP-ios] 解析 APP_IOS_IAP_ID_MAP 失败: $e'); } if (mapped != null) { final m = mapped[raw]; if (m is String && m.trim().isNotEmpty) { final resolved = m.trim(); print('[IAP-ios] 使用映射ID: $raw -> $resolved'); return resolved; } } final prefix = dotenv.env['APP_IOS_IAP_PREFIX'] ?? ''; final suffix = dotenv.env['APP_IOS_IAP_SUFFIX'] ?? ''; final resolved = '${prefix}${raw}${suffix}'; if (resolved != raw) { print('[IAP-ios] 使用前后缀ID: $raw -> $resolved'); } else { print('[IAP-ios] 未配置映射/前缀,使用原始ID: $raw'); } return resolved; } /// 发起 Apple 内购 Future processApplePayment({ required String productId, PaymentResultCallback? onResult, }) async { if (kIsWeb || defaultTargetPlatform != TargetPlatform.iOS) { onResult?.call(false, '当前平台不支持 Apple 内购'); return false; } if (!iapAvailable.value) { await _initIAP(); if (!iapAvailable.value) { onResult?.call(false, '内购不可用'); return false; } } // 解析为 App Store 的真实商品ID final iosId = resolveAppleProductId(productId); print('[IAP-ios] 准备使用商品ID购买: backend="$productId" -> store="$iosId"'); return _processIapPayment(productId: iosId, onResult: onResult); } /// 发起 Google Play 内购 Future processGooglePayment({ required String productId, PaymentResultCallback? onResult, }) async { if (kIsWeb || defaultTargetPlatform != TargetPlatform.android) { onResult?.call(false, '当前平台不支持 Google 内购'); return false; } if (!iapAvailable.value) { await _initIAP(); if (!iapAvailable.value) { onResult?.call(false, '内购不可用'); return false; } } return _processIapPayment(productId: productId, onResult: onResult); } /// 查询支付结果 /// /// [orderId] 订单ID /// 返回支付状态:success, failed, pending Future queryPaymentStatus(String orderId) async { try { // TODO: 调用后端API查询支付状态 // final response = await Api.queryPaymentStatus({'orderId': orderId}); // return response['status'] as String?; // 临时返回,实际使用时需要删除 return 'pending'; } catch (e) { print('查询支付状态失败: $e'); return null; } } /// 微信支付 /// /// [paymentData] 支付参数,包含以下字段: /// - appId: 应用ID /// - partnerId: 商户号 /// - prepayId: 预支付交易会话ID /// - packageValue: 扩展字段,固定值"Sign=WXPay" /// - nonceStr: 随机字符串 /// - timestamp: 时间戳 /// - sign: 签名 /// /// [onResult] 支付结果回调 Future wechatPay({ required Map paymentData, PaymentResultCallback? onResult, }) async { try { // 检查微信是否安装 if (!await _flwx.isInstalled) { onResult?.call(false, '微信未安装'); return false; } // 将支付数据转换为Payment对象 final payment = paymentData.toPayment(); if (payment == null) { onResult?.call(false, '支付参数格式错误'); return false; } // 调用微信支付 final result = await _flwx.pay( payment, onSuccess: () { onResult?.call(true, '支付成功'); }, onResponse: (response) { if (response.errCode == 0) { onResult?.call(true, '支付成功'); } else if (response.errCode == -2) { onResult?.call(false, '用户取消支付'); } else { onResult?.call(false, '支付失败: ${response.errStr}'); } }, ); return result; } catch (e) { onResult?.call(false, '支付异常: $e'); return false; } } /// 创建微信支付订单 /// /// [productId] 商品ID /// [productName] 商品名称 /// [productDesc] 商品描述 /// [amount] 支付金额(分) /// [paymentMethod] 支付方式(1=微信支付) /// [clientIp] 客户端IP(可选) /// [notifyUrl] 通知URL(可选) /// [extraData] 扩展数据(可选) /// /// 返回支付参数,用于调用 [wechatPay] 方法 Future?> createWechatPayOrder({ required String productId, required String productName, String? productDesc, required int amount, int paymentMethod = 4, // 1表示微信支付 String? clientIp, String? notifyUrl, String? extraData, }) async { try { // 调用后端API创建支付订单 final response = await Api.paycreateorder({ 'product_id': productId, 'product_name': productName, 'product_desc': productDesc ?? '', 'amount': amount, 'payment_method': 4, 'client_ip': clientIp ?? '', 'notify_url': notifyUrl ?? '', 'extra_data': extraData ?? '', }); print('创建支付订单: $response'); // 检查响应是否成功 if (response != null) { // 尝试提取订单号,兼容多种返回字段 String? orderId; final candidates = [ 'order_id', 'orderId', 'out_trade_no', 'outTradeNo', ]; for (final key in candidates) { final v = response[key]; if (v is String && v.isNotEmpty) { orderId = v; break; } } if (orderId == null && response['data'] is Map) { final data = response['data'] as Map; for (final key in candidates) { final v = data[key]; if (v is String && v.isNotEmpty) { orderId = v; break; } } } _lastOrderId = orderId; final pay_params = response["pay_params"] as Map; // 返回微信支付所需的参数 final result = { 'appId': pay_params['appid'], 'partnerId': pay_params['partnerid'], 'prepayId': pay_params['prepayid'], 'packageValue': pay_params['package'] ?? 'Sign=WXPay', 'nonceStr': pay_params['noncestr'], 'timestamp': pay_params['timestamp'], 'sign': pay_params['sign'], }; if (orderId != null) { result['orderId'] = orderId; } return result; } else { print('创建支付订单失败: ${response?['message'] ?? '未知错误'}'); return null; } } catch (e) { print('创建支付订单异常: $e'); return null; } } Future processWechatPayment({ required String productId, required int amount, required String description, String? userId, PaymentResultCallback? onResult, }) async { try { // 1. 创建支付订单 final paymentData = await createWechatPayOrder( productId: productId, productName: '会员充值', productDesc: '1个月会员服务', amount: 1, // 29.99元,单位:分 paymentMethod: 4, // 微信支付 notifyUrl: 'https://pay.ymaikj.com/api/home/pay_wechatnotify', ); if (paymentData == null) { onResult?.call(false, '创建支付订单失败'); return false; } // 2. 调用微信支付 return await wechatPay( paymentData: paymentData, onResult: onResult, ); } catch (e) { onResult?.call(false, '支付流程异常: $e'); return false; } } /// 完整的支付宝支付流程 /// /// [productId] 产品ID /// [amount] 支付金额(分) /// [description] 商品描述 /// [onResult] 支付结果回调 Future processAlipayPayment({ required String productId, required int amount, required String description, PaymentResultCallback? onResult, }) async { try { // 检查支付宝是否安装 final isInstalled = await _tobias.isAliPayInstalled; if (!isInstalled) { onResult?.call(false, '支付宝未安装'); return false; } // 调用现有的 createAlipayOrder 方法创建订单 final orderInfo = await createAlipayOrder( productId: productId, productName: description, productDesc: description, amount: amount, notifyUrl: 'https://pay.ymaikj.com/api/home/pay_alipaynotify', ); if (orderInfo == null) { onResult?.call(false, '创建支付订单失败'); return false; } print('支付宝支付订单: $orderInfo'); // 调用支付宝支付 final result = await _tobias.pay(orderInfo); print('支付宝支付结果: $result'); // 处理支付结果 if (result != null) { final resultStatus = result['resultStatus'] as String?; final memo = result['memo'] as String?; if (resultStatus == '9000') { onResult?.call(true, '支付成功'); return true; } else { onResult?.call(false, memo ?? '支付失败'); return false; } } else { onResult?.call(false, '支付结果为空'); return false; } } catch (e) { onResult?.call(false, '支付异常: $e'); return false; } } /// 支付宝支付 /// /// [orderInfo] 支付宝订单信息字符串 /// [onResult] 支付结果回调 Future alipay({ required String orderInfo, PaymentResultCallback? onResult, }) async { try { // 检查支付宝是否安装 - 使用实例方法 final isInstalled = await _tobias.isAliPayInstalled; if (!isInstalled) { onResult?.call(false, '支付宝未安装'); return false; } // 调用支付宝支付 - 使用实例方法 final result = await _tobias.pay(orderInfo); if (result != null) { // 解析支付结果 final resultStatus = result['resultStatus'] as String?; final memo = result['memo'] as String?; if (resultStatus == '9000') { // 支付成功 onResult?.call(true, '支付成功'); return true; } else { // 支付失败或取消 onResult?.call(false, memo ?? '支付失败'); return false; } } else { onResult?.call(false, '支付结果为空'); return false; } } catch (e) { onResult?.call(false, '支付异常: $e'); return false; } } /// 创建支付宝支付订单 /// /// [productId] 商品ID /// [productName] 商品名称 /// [productDesc] 商品描述 /// [amount] 支付金额(分) /// [paymentMethod] 支付方式(2=支付宝) /// [clientIp] 客户端IP(可选) /// [notifyUrl] 通知URL(可选) /// [extraData] 扩展数据(可选) /// /// 返回支付宝订单信息字符串,用于调用 [alipay] 方法 Future createAlipayOrder({ required String productId, required String productName, String? productDesc, required int amount, String? clientIp, String? notifyUrl, String? extraData, }) async { try { // 调用后端API创建支付订单 final response = await Api.paycreateorder({ 'product_id': productId, 'product_name': productName, 'product_desc': productDesc ?? '', 'amount': amount, 'payment_method': 3, 'client_ip': clientIp ?? '', 'notify_url': notifyUrl ?? '', 'extra_data': extraData ?? '', }); print('创建支付宝订单: $response'); // 检查响应是否成功 if (response != null && response['alipay_pay_params'] != null) { // 解析订单号并缓存 String? orderId; final candidates = [ 'order_id', 'orderId', 'out_trade_no', 'outTradeNo', ]; for (final key in candidates) { final v = response[key]; if (v is String && v.isNotEmpty) { orderId = v; break; } } if (orderId == null && response['data'] is Map) { final data = response['data'] as Map; for (final key in candidates) { final v = data[key]; if (v is String && v.isNotEmpty) { orderId = v; break; } } } _lastOrderId = orderId; // 返回支付宝订单信息字符串 return response['alipay_pay_params'] as String?; } else { print('创建支付宝订单失败: ${response?['message'] ?? '未知错误'}'); return null; } } catch (e) { print('创建支付宝订单异常: $e'); return null; } } /// 创建 PayPal 支付订单 /// 返回需要打开的支付授权链接(approval_url)或支付页面URL Future createPaypalOrder({ required String productId, required String productName, String? productDesc, required int amount, String? clientIp, String? notifyUrl, String? extraData, }) async { try { // 回跳地址:自定义 Scheme,分别用于支付完成与取消 const returnUrl = 'deepvoiceclient://paypal/return'; const cancelUrl = 'deepvoiceclient://paypal/cancel'; final response = await Api.paycreateorder({ 'product_id': productId, 'product_name': productName, 'product_desc': productDesc ?? '', 'amount': amount, 'payment_method': 5, 'client_ip': clientIp ?? '', 'notify_url': notifyUrl ?? '', 'extra_data': extraData ?? '', // 供后端设置 PayPal application_context 用于网页授权回跳 'return_url': returnUrl, 'cancel_url': cancelUrl, }); if (response != null) { print('创建 PayPal 订单响应: $response'); // 1) 提取并缓存平台订单号(用于回查) String? appOrderId; const candidates = [ 'order_id', 'orderId', 'out_trade_no', 'outTradeNo', ]; for (final key in candidates) { final v = response[key]; if (v is String && v.isNotEmpty) { appOrderId = v; break; } } final data = response['data']; if (appOrderId == null && data is Map) { for (final key in candidates) { final v = data[key]; if (v is String && v.isNotEmpty) { appOrderId = v; break; } } } _lastOrderId = appOrderId; // 记录APP侧订单号(如 APP2025...),用于 pay_askorder // 2) 优先解析 paypal_pay_params 字段(后端返回JSON字符串,含 links) String? rawParams; final topParams = response['paypal_pay_params']; final dataParams = (data is Map) ? data['paypal_pay_params'] : null; if (topParams is String && topParams.isNotEmpty) { rawParams = topParams; } else if (dataParams is String && dataParams.isNotEmpty) { rawParams = dataParams; } if (rawParams != null) { try { final parsed = jsonDecode(rawParams); if (parsed is Map) { final links = parsed['links']; if (links is List) { for (final item in links) { if (item is Map) { final rel = item['rel']?.toString(); final hrefRaw = item['href']?.toString(); if (rel == 'approve' && hrefRaw != null && hrefRaw.isNotEmpty) { // 清理日志中可能出现的反引号等噪音字符 final href = hrefRaw.replaceAll('`', '').trim(); print( '解析到 PayPal 授权链接(links.approve): $href, appOrderId: $appOrderId'); return href; } } } } } } catch (e) { print('解析 paypal_pay_params 失败: $e'); } } // 3) 回退:尝试从常见字段直接取URL final urlKeys = [ 'paypal_pay_url', 'approval_url', 'paypalApprovalUrl', 'pay_url', 'payment_url', 'url', ]; for (final key in urlKeys) { final url = response[key]; if (url is String && url.isNotEmpty) { print('解析到 PayPal 授权链接($key): $url, appOrderId: $appOrderId'); return url; } } if (data is Map) { for (final key in urlKeys) { final url = data[key]; if (url is String && url.isNotEmpty) { print( '解析到 PayPal 授权链接(data.$key): $url, appOrderId: $appOrderId'); return url; } } } } print('创建 PayPal 订单失败或未返回有效URL: $response'); return null; } catch (e) { print('创建 PayPal 订单异常: $e'); return null; } } /// 拉起 PayPal 支付(打开授权链接)。 /// 注意:该方法仅负责跳转至 PayPal 页面,结果需要后端回调通知。 Future paypalPay({ required String approvalUrl, PaymentResultCallback? onResult, }) async { try { print('准备打开 PayPal 授权页面: $approvalUrl'); final uri = Uri.parse(approvalUrl); final ok = await launchUrl(uri, mode: LaunchMode.externalApplication); if (ok) { onResult?.call(true, '已打开 PayPal,请完成支付'); } else { onResult?.call(false, '无法打开 PayPal 支付页面'); } return ok; } catch (e) { onResult?.call(false, '打开 PayPal 异常: $e'); return false; } } /// 一体化的 PayPal 支付流程:创建订单 -> 打开授权链接 Future processPaypalPayment({ required String productId, required int amount, required String description, PaymentResultCallback? onResult, }) async { try { final approvalUrl = await createPaypalOrder( productId: productId, productName: description, productDesc: description, amount: amount, notifyUrl: 'https://pay.ymaikj.com/api/home/pay_paypalnotify', ); if (approvalUrl == null) { onResult?.call(false, '创建 PayPal 订单失败'); return false; } return await paypalPay(approvalUrl: approvalUrl, onResult: onResult); } catch (e) { onResult?.call(false, 'PayPal 支付流程异常: $e'); return false; } } /// 创建 Apple 内购订单,仅用于在服务端生成并记录订单号,后续回查使用 /// 返回服务端生成的 `order_id`,若失败则返回 null Future createAppleIapOrder({ required String productId, required String productName, String? productDesc, required int amount, }) async { try { final resp = await Api.paycreateorder({ 'product_id': productId, 'product_name': productName, 'product_desc': productDesc ?? '', 'amount': amount, 'payment_method': 6, }); if (resp != null) { String? orderId; const candidates = [ 'order_id', 'orderId', 'out_trade_no', 'outTradeNo' ]; for (final k in candidates) { final v = resp[k]; if (v is String && v.isNotEmpty) { orderId = v; break; } } if (orderId == null && resp['data'] is Map) { final data = resp['data'] as Map; for (final k in candidates) { final v = data[k]; if (v is String && v.isNotEmpty) { orderId = v; break; } } } _lastOrderId = orderId; return orderId; } return null; } catch (e) { print('创建 Apple IAP 订单异常: $e'); return null; } } /// 创建 Google 内购订单,仅用于在服务端生成并记录订单号,后续回查使用 /// 返回服务端生成的 `order_id`,若失败则返回 null Future createGoogleIapOrder({ required String productId, required String productName, String? productDesc, required int amount, }) async { try { final resp = await Api.paycreateorder({ 'product_id': productId, 'product_name': productName, 'product_desc': productDesc ?? '', 'amount': amount, 'payment_method': 7, }); if (resp != null) { String? orderId; const candidates = [ 'order_id', 'orderId', 'out_trade_no', 'outTradeNo' ]; for (final k in candidates) { final v = resp[k]; if (v is String && v.isNotEmpty) { orderId = v; break; } } if (orderId == null && resp['data'] is Map) { final data = resp['data'] as Map; for (final k in candidates) { final v = data[k]; if (v is String && v.isNotEmpty) { orderId = v; break; } } } _lastOrderId = orderId; return orderId; } return null; } catch (e) { print('创建 Google IAP 订单异常: $e'); return null; } } /// 请求蓝牙权限 void setVipExptime(int vipexptime) { try { AgentService.setVipExptime(vipexptime); } catch (e) { print('请求蓝牙权限失败: ${e.toString()}'); } } }