import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:voitrans/data/models/user_Info.dart'; import '../../../routes/app_routes.dart'; import '../../../data/services/pay_service.dart'; import '../../../data/services/version_update_service.dart'; import '../../../data/models/goods.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import '../../../data/services/network/dio_manager.dart'; import '../../../data/services/network/api.dart'; import 'package:flutter/foundation.dart'; /// goods 模块基础控制器,封装通用的商品加载与支付流程 abstract class GoodsBaseController extends GetxController { final RxList items = [].obs; final RxBool loading = false.obs; final RxString error = ''.obs; late final PayService _payService = Get.find(); @override void onInit() { super.onInit(); _init(); } Future _init() async { loading.value = true; error.value = ''; try { final list = await fetchProducts(); items.assignAll(list); } catch (e) { error.value = e.toString(); } finally { loading.value = false; } } /// 子类实现:返回商品列表(统一使用 DBGoods) Future> fetchProducts(); /// 统一入口:根据后端配置决定是否弹出支付方式选择面板 Future purchase(DBGoods item) async { // 读取渠道配置 final enabled = _getEnabledPayMethods(); // 无配置或多个渠道:弹出选择面板 _PayMethod? method; if (enabled.length != 1) { method = await _showPayMethodSheet(enabled); if (method == null) return; } else { // 仅一个渠道:直接走对应支付 method = enabled.first; } bool ok = false; String failMsg = '支付失败,请重试'; if (method == _PayMethod.wechat) { ok = await payWechat(item, onResult: (success, msg) { if (!success) failMsg = msg ?? failMsg; }); } else if (method == _PayMethod.alipay) { ok = await payAlipay(item, onResult: (success, msg) { if (!success) failMsg = msg ?? failMsg; }); } else if (method == _PayMethod.paypal) { ok = await payPaypal(item, onResult: (success, msg) { if (!success) failMsg = msg ?? failMsg; }); } else if (method == _PayMethod.apple) { ok = await payAppleIAP(item, onResult: (success, msg) { if (!success) failMsg = msg ?? failMsg; }); } else if (method == _PayMethod.google) { ok = await payGoogleIAP(item, onResult: (success, msg) { if (!success) failMsg = msg ?? failMsg; }); } if (!ok) { Get.snackbar('提示', failMsg, snackPosition: SnackPosition.BOTTOM, margin: const EdgeInsets.all(12)); } } /// 解析后端 paychannels 并结合平台过滤可用支付方式 List<_PayMethod> _getEnabledPayMethods() { try { final paychannels = VersionUpdateService.to.cachedAppInfo?.paychannels ?? ''; final raw = paychannels .split(',') .map((e) => e.trim().toLowerCase()) .where((e) => e.isNotEmpty) .toList(); final bool isIOS = GetPlatform.isIOS && !kIsWeb; final bool isAndroid = GetPlatform.isAndroid && !kIsWeb; final List<_PayMethod> methods = []; for (final ch in raw) { switch (ch) { case 'wxpay': case 'wechat': methods.add(_PayMethod.wechat); break; case 'alipay': methods.add(_PayMethod.alipay); break; case 'paypal': methods.add(_PayMethod.paypal); break; case 'apple': if (isIOS) methods.add(_PayMethod.apple); break; case 'google': if (isAndroid) methods.add(_PayMethod.google); break; default: break; } } // 若后端未下发或全部被平台过滤,则回退为展示所有平台支持的方式 if (methods.isEmpty) { final fallback = <_PayMethod>[ _PayMethod.wechat, _PayMethod.alipay, _PayMethod.paypal ]; if (isIOS) fallback.add(_PayMethod.apple); if (isAndroid) fallback.add(_PayMethod.google); return fallback; } return methods; } catch (_) { // 任意异常则回退为旧逻辑:展示所有 final bool isIOS = GetPlatform.isIOS && !kIsWeb; final bool isAndroid = GetPlatform.isAndroid && !kIsWeb; final fallback = <_PayMethod>[ _PayMethod.wechat, _PayMethod.alipay, _PayMethod.paypal ]; if (isIOS) fallback.add(_PayMethod.apple); if (isAndroid) fallback.add(_PayMethod.google); return fallback; } } /// 底部弹窗:选择支付方式(仅展示后端允许的渠道) Future<_PayMethod?> _showPayMethodSheet(List<_PayMethod> allowed) async { final context = Get.context; final isDark = context != null && Theme.of(context).brightness == Brightness.dark; final isIOS = GetPlatform.isIOS && !kIsWeb; final isAndroid = GetPlatform.isAndroid && !kIsWeb; return await Get.bottomSheet<_PayMethod>( SafeArea( child: Container( decoration: BoxDecoration( color: isDark ? Colors.grey[900] : Colors.white, borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.symmetric(vertical: 12), child: Container( width: 40, height: 4, decoration: BoxDecoration( color: isDark ? Colors.white24 : Colors.black12, borderRadius: BorderRadius.circular(2), ), ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( '选择支付方式', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: isDark ? Colors.white : Colors.black, ), ), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ if (allowed.contains(_PayMethod.wechat)) GestureDetector( onTap: () => Get.back(result: _PayMethod.wechat), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 56, height: 56, decoration: BoxDecoration( shape: BoxShape.circle, color: isDark ? Colors.white10 : Colors.black12, border: Border.all( color: isDark ? Colors.white24 : Colors.black12, ), ), child: Icon(Icons.wechat, color: Colors.green[600]), ), const SizedBox(height: 8), Text( '微信支付', style: TextStyle( fontSize: 13, color: isDark ? Colors.white : Colors.black, ), ), ], ), ), if (allowed.contains(_PayMethod.alipay)) GestureDetector( onTap: () => Get.back(result: _PayMethod.alipay), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 56, height: 56, decoration: BoxDecoration( shape: BoxShape.circle, color: isDark ? Colors.white10 : Colors.black12, border: Border.all( color: isDark ? Colors.white24 : Colors.black12, ), ), child: Icon(Icons.payment, color: Colors.blue[600]), ), const SizedBox(height: 8), Text( '支付宝', style: TextStyle( fontSize: 13, color: isDark ? Colors.white : Colors.black, ), ), ], ), ), if (allowed.contains(_PayMethod.paypal)) GestureDetector( onTap: () => Get.back(result: _PayMethod.paypal), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 56, height: 56, decoration: BoxDecoration( shape: BoxShape.circle, color: isDark ? Colors.white10 : Colors.black12, border: Border.all( color: isDark ? Colors.white24 : Colors.black12, ), ), child: Icon( Icons.account_balance_wallet, color: Colors.indigo[600], ), ), const SizedBox(height: 8), Text( 'PayPal', style: TextStyle( fontSize: 13, color: isDark ? Colors.white : Colors.black, ), ), ], ), ), if (isIOS && allowed.contains(_PayMethod.apple)) GestureDetector( onTap: () => Get.back(result: _PayMethod.apple), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 56, height: 56, decoration: BoxDecoration( shape: BoxShape.circle, color: isDark ? Colors.white10 : Colors.black12, border: Border.all( color: isDark ? Colors.white24 : Colors.black12, ), ), child: Icon( Icons.apple, color: isDark ? Colors.white : Colors.black, ), ), const SizedBox(height: 8), Text( 'Apple 内购', style: TextStyle( fontSize: 13, color: isDark ? Colors.white : Colors.black, ), ), ], ), ), if (isAndroid && allowed.contains(_PayMethod.google)) GestureDetector( onTap: () => Get.back(result: _PayMethod.google), child: Column( mainAxisSize: MainAxisSize.min, children: [ Container( width: 56, height: 56, decoration: BoxDecoration( shape: BoxShape.circle, color: isDark ? Colors.white10 : Colors.black12, border: Border.all( color: isDark ? Colors.white24 : Colors.black12, ), ), child: Icon( Icons.shop, color: Colors.green[700], ), ), const SizedBox(height: 8), Text( 'Google 内购', style: TextStyle( fontSize: 13, color: isDark ? Colors.white : Colors.black, ), ), ], ), ), ], ), const SizedBox(height: 12), ], ), ), ], ), ), ), isScrollControlled: false, ignoreSafeArea: false, backgroundColor: Colors.transparent, ); } /// 指定微信支付(直接下单并拉起微信) Future payWechat(DBGoods item, {PaymentResultCallback? onResult}) async { final cents = item.price; // DBGoods.price 单位:分 final serverUrl = dotenv.env['SERVER_URL'] ?? DioManager().baseUrl; final paymentData = await _payService.createWechatPayOrder( productId: item.id, productName: item.name, productDesc: item.desc, amount: cents, notifyUrl: '$serverUrl/api/home/pay_wechatnotify', ); if (paymentData == null) { onResult?.call(false, '创建微信订单失败'); return false; } final orderId = paymentData['orderId'] as String? ?? _payService.lastOrderId; return _payService.wechatPay( paymentData: paymentData, onResult: (success, msg) async { // 先透传给外部回调 onResult?.call(success, msg); if (success) { await _confirmOrderAndShow(orderId, channel: '微信', initialMessage: msg); } else { _showPayResultDialog(false, msg ?? '支付失败'); } }, ); } /// 指定支付宝支付(直接下单并拉起支付宝) Future payAlipay(DBGoods item, {PaymentResultCallback? onResult}) async { final cents = item.price; // DBGoods.price 单位:分 final orderInfo = await _payService.createAlipayOrder( productId: item.id, productName: item.name, productDesc: item.desc, amount: cents, notifyUrl: 'https://api.ideapsound.com/api/home/pay_alipaynotify', ); if (orderInfo == null) { onResult?.call(false, '创建支付宝订单失败'); return false; } final orderId = _payService.lastOrderId; // 创建订单时已缓存 return _payService.alipay( orderInfo: orderInfo, onResult: (success, msg) async { onResult?.call(success, msg); if (success) { await _confirmOrderAndShow(orderId, channel: '支付宝', initialMessage: msg); } else { _showPayResultDialog(false, msg ?? '支付失败'); } }, ); } /// 指定 Apple 内购(App Store IAP) Future payAppleIAP(DBGoods item, {PaymentResultCallback? onResult}) async { // 使用商品id作为内购的 productId(与后台保持一致) final productId = item.id; return _payService.processApplePayment( productId: productId, onResult: (success, msg) { // 透传并根据阶段展示 onResult?.call(success, msg); if (!success) { _showPayResultDialog(false, msg); } else { // 支付成功或额度待刷新时弹窗,其余阶段用轻提示 final showDialog = msg.contains('支付成功'); if (showDialog) { _showPayResultDialog(true, msg); } else { Get.snackbar('提示', msg, snackPosition: SnackPosition.BOTTOM, margin: const EdgeInsets.all(12)); } } }, ); } /// 指定 Google Play 内购(Play Billing IAP) Future payGoogleIAP(DBGoods item, {PaymentResultCallback? onResult}) async { // 使用商品id作为内购的 productId(与后台保持一致) final productId = item.id; // 调试:查询并打印当前商品的内购详情,便于确认 ID await _payService.debugQueryProducts({productId}); return _payService.processGooglePayment( productId: productId, onResult: (success, msg) { onResult?.call(success, msg); if (!success) { _showPayResultDialog(false, msg); } else { final showDialog = msg.contains('支付成功'); if (showDialog) { _showPayResultDialog(true, msg); } else { Get.snackbar('提示', msg, snackPosition: SnackPosition.BOTTOM, margin: const EdgeInsets.all(12)); } } }, ); } /// 调试:打印当前页面所有商品的 Google IAP 查询结果 Future debugPrintGoogleIapProducts() async { if (!GetPlatform.isAndroid || kIsWeb) { print('[IAP] 当前平台非 Android 或为 Web,跳过商品查询调试。'); return; } try { final products = await fetchProducts(); final ids = products.map((e) => e.id).toSet(); await _payService.debugQueryProducts(ids); } catch (e) { print('[IAP] 调试查询商品异常: $e'); } } Future _confirmOrderAndShow(String? orderId, {required String channel, String? initialMessage}) async { // 若无法获取订单号,直接弹成功提示但提醒稍后刷新 if (orderId == null || orderId.isEmpty) { _showPayResultDialog(true, '${channel}支付成功,订单处理中,请稍后查看。'); return; } String message = initialMessage ?? '${channel}支付成功'; bool success = false; try { // 简单轮询回查,避免回调延迟 for (int i = 0; i < 5; i++) { final resp = await Api.payaskorder({'order_id': orderId}); print("查询订单 resp: $resp"); final order = resp?['order'] as Map?; final user = resp?['user'] as Map?; if (order != null && user != null) { final orderStatus = order["status"]; if (orderStatus == 1) { User.instance.updateUserInfo(user); success = true; break; } } await Future.delayed(const Duration(milliseconds: 1500)); } // 若标记成功但未拿到用户信息,使用token登录接口回拉用户资料,确保额度刷新 if (success) { try { final response = await Api.tokenlogin(); if (response is Map && response['user'] is Map) { User.instance .updateUserInfo(response['user'] as Map); } } catch (_) {} } } catch (e) { message = '查询支付结果异常: $e'; } _showPayResultDialog(success, message); } void _showPayResultDialog(bool success, String message) { final context = Get.context; final isDark = context != null && Theme.of(context).brightness == Brightness.dark; Get.dialog( Dialog( insetPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), backgroundColor: isDark ? Colors.grey[900] : Colors.white, child: Padding( padding: const EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 48, height: 48, decoration: BoxDecoration( color: success ? (isDark ? Colors.green[800] : Colors.green[100]) : (isDark ? Colors.red[800] : Colors.red[100]), borderRadius: BorderRadius.circular(12), ), child: Icon( success ? Icons.check_circle : Icons.error, color: success ? Colors.green[600] : Colors.redAccent[200], size: 28, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( success ? '支付成功' : '支付失败', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w700, color: isDark ? Colors.white : Colors.black, ), ), const SizedBox(height: 6), Text( message, style: TextStyle( fontSize: 13, color: isDark ? Colors.white70 : Colors.grey[700], ), ), ], ), ), ], ), const SizedBox(height: 16), Divider(color: isDark ? Colors.white24 : Colors.black12), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () => Get.back(), child: const Text('知道了'), ), ], ), ], ), ), ), barrierDismissible: true, ); } /// 指定 PayPal 支付(创建订单并打开授权链接) Future payPaypal(DBGoods item, {PaymentResultCallback? onResult}) async { final cents = item.price; // DBGoods.price 单位:分 final approvalUrl = await _payService.createPaypalOrder( productId: item.id, productName: item.name, productDesc: item.desc, amount: cents, notifyUrl: 'https://api.ideapsound.com/api/home/pay_paypalnotify', ); if (approvalUrl == null) { onResult?.call(false, '创建 PayPal 订单失败'); return false; } return _payService.paypalPay(approvalUrl: approvalUrl, onResult: onResult); } } enum _PayMethod { wechat, alipay, paypal, apple, google }