diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 915595a3f..c2de8317d 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -109,6 +109,20 @@ + + + + + + + + + + + + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 7720e0d46..c2c5f976b 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -54,6 +54,15 @@ voitrans + + + CFBundleURLName + deepvoiceclient.payment + CFBundleURLSchemes + + deepvoiceclient + + CFBundleVersion $(FLUTTER_BUILD_NUMBER) diff --git a/lib/data/services/network/dio_manager.dart b/lib/data/services/network/dio_manager.dart index 0496c11f9..e0141cc36 100644 --- a/lib/data/services/network/dio_manager.dart +++ b/lib/data/services/network/dio_manager.dart @@ -34,6 +34,16 @@ class DioManager { dio = Dio(options); dio.interceptors.add(AuthInterceptor()); + // 调试:输出请求与响应的详细日志,便于排查服务端创建订单或查询订单问题 + dio.interceptors.add(LogInterceptor( + request: true, + requestHeader: true, + requestBody: true, + responseHeader: false, + responseBody: true, + error: true, + logPrint: (obj) => print('[DIO] $obj'), + )); } // 请求,返回参数为 T diff --git a/lib/data/services/pay_service.dart b/lib/data/services/pay_service.dart index abda1ca0a..bbe89c42b 100644 --- a/lib/data/services/pay_service.dart +++ b/lib/data/services/pay_service.dart @@ -774,6 +774,9 @@ class PayService extends GetxService { 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, @@ -783,9 +786,13 @@ class PayService extends GetxService { '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 = [ @@ -839,6 +846,8 @@ class PayService extends GetxService { hrefRaw.isNotEmpty) { // 清理日志中可能出现的反引号等噪音字符 final href = hrefRaw.replaceAll('`', '').trim(); + print( + '解析到 PayPal 授权链接(links.approve): $href, appOrderId: $appOrderId'); return href; } } @@ -862,6 +871,7 @@ class PayService extends GetxService { for (final key in urlKeys) { final url = response[key]; if (url is String && url.isNotEmpty) { + print('解析到 PayPal 授权链接($key): $url, appOrderId: $appOrderId'); return url; } } @@ -869,6 +879,8 @@ class PayService extends GetxService { for (final key in urlKeys) { final url = data[key]; if (url is String && url.isNotEmpty) { + print( + '解析到 PayPal 授权链接(data.$key): $url, appOrderId: $appOrderId'); return url; } } @@ -890,6 +902,7 @@ class PayService extends GetxService { PaymentResultCallback? onResult, }) async { try { + print('准备打开 PayPal 授权页面: $approvalUrl'); final uri = Uri.parse(approvalUrl); final ok = await launchUrl(uri, mode: LaunchMode.externalApplication); if (ok) { diff --git a/lib/modules/goods/controllers/goods_base_controller.dart b/lib/modules/goods/controllers/goods_base_controller.dart index 0ff6faf38..846b35dc6 100644 --- a/lib/modules/goods/controllers/goods_base_controller.dart +++ b/lib/modules/goods/controllers/goods_base_controller.dart @@ -9,6 +9,8 @@ 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'; +import 'dart:async'; +import 'package:app_links/app_links.dart'; /// goods 模块基础控制器,封装通用的商品加载与支付流程 abstract class GoodsBaseController extends GetxController { @@ -17,11 +19,22 @@ abstract class GoodsBaseController extends GetxController { final RxString error = ''.obs; late final PayService _payService = Get.find(); + StreamSubscription? _deeplinkSub; + late final AppLinks _appLinks = AppLinks(); @override void onInit() { super.onInit(); _init(); + // 处理冷启动场景:如果 App 通过深链直接启动 + _handleInitialDeepLink(); + } + + @override + void onClose() { + _deeplinkSub?.cancel(); + _deeplinkSub = null; + super.onClose(); } Future _init() async { @@ -559,8 +572,13 @@ abstract class GoodsBaseController extends GetxController { } } - Future _confirmOrderAndShow(String? orderId, - {required String channel, String? initialMessage}) async { + Future _confirmOrderAndShow( + String? orderId, { + required String channel, + String? initialMessage, + int retries = 5, + int intervalMs = 1500, + }) async { // 若无法获取订单号,直接弹成功提示但提醒稍后刷新 if (orderId == null || orderId.isEmpty) { _showPayResultDialog(true, '${channel}支付成功,订单处理中,请稍后查看。'); @@ -571,7 +589,7 @@ abstract class GoodsBaseController extends GetxController { bool success = false; try { // 简单轮询回查,避免回调延迟 - for (int i = 0; i < 5; i++) { + for (int i = 0; i < retries; i++) { final resp = await Api.payaskorder({'order_id': orderId}); print("查询订单 resp: $resp"); final order = resp?['order'] as Map?; @@ -584,7 +602,7 @@ abstract class GoodsBaseController extends GetxController { break; } } - await Future.delayed(const Duration(milliseconds: 1500)); + await Future.delayed(Duration(milliseconds: intervalMs)); } // 若标记成功但未拿到用户信息,使用token登录接口回拉用户资料,确保额度刷新 @@ -728,7 +746,88 @@ abstract class GoodsBaseController extends GetxController { onResult?.call(false, '创建 PayPal 订单失败'); return false; } - return _payService.paypalPay(approvalUrl: approvalUrl, onResult: onResult); + final orderId = _payService.lastOrderId; // 创建订单时已缓存 + print('[PayPal] 创建完成,orderId: $orderId, approvalUrl: $approvalUrl'); + + // 启动深链监听:当浏览器重定向到 deepvoiceclient://paypal/return 或 /cancel 时处理 + _deeplinkSub?.cancel(); + _deeplinkSub = _appLinks.uriLinkStream.listen((uri) async { + if (uri == null) return; + print('[PayPal] 深链回跳: ${uri.toString()}'); + final token = uri.queryParameters['token'] ?? ''; + if (token.isNotEmpty) { + print('[PayPal] 回跳 token(订单ID): $token'); + } + final scheme = uri.scheme; + final host = uri.host; + final path = uri.path; + if (scheme == 'deepvoiceclient' && host == 'paypal') { + if (path == '/return') { + print('[PayPal] 检测到 /return,使用 orderId 回查: $orderId'); + await _confirmOrderAndShow( + orderId, + channel: 'PayPal', + initialMessage: '支付完成,正在确认', + retries: 1, + intervalMs: 0, + ); + } else if (path == '/cancel') { + _showPayResultDialog(false, '已取消支付'); + } + // 一次性监听,处理后即取消 + _deeplinkSub?.cancel(); + _deeplinkSub = null; + } + }, onError: (e) { + // 忽略监听错误,保持现有轮询逻辑 + }); + return _payService.paypalPay( + approvalUrl: approvalUrl, + onResult: (success, msg) async { + onResult?.call(success, msg); + if (success) { + // 仅提示已打开授权页面,不在此处回查与弹窗,等待深链回跳后再处理 + print('[PayPal] 已打开授权页面,等待回跳后再确认。orderId: $orderId'); + Get.snackbar('提示', msg ?? '已打开 PayPal,请在浏览器完成支付', + snackPosition: SnackPosition.BOTTOM, + margin: const EdgeInsets.all(12)); + } else { + // 无法打开授权页才提示失败 + _showPayResultDialog(false, msg ?? '无法打开 PayPal 支付页面'); + } + }, + ); + } + + Future _handleInitialDeepLink() async { + try { + final uri = await _appLinks.getInitialLink(); + if (uri == null) return; + print('[PayPal] 冷启动深链: ${uri.toString()}'); + final token = uri.queryParameters['token'] ?? ''; + if (token.isNotEmpty) { + print('[PayPal] 冷启动 token(订单ID): $token'); + } + if (uri.scheme == 'deepvoiceclient' && uri.host == 'paypal') { + if (uri.path == '/return') { + final orderId = _payService.lastOrderId; + if (orderId != null) { + print('[PayPal] 冷启动 /return,使用 orderId 回查: $orderId'); + await _confirmOrderAndShow( + orderId, + channel: 'PayPal', + initialMessage: '支付完成,正在确认', + retries: 1, + intervalMs: 0, + ); + } + } else if (uri.path == '/cancel') { + _showPayResultDialog(false, '已取消支付'); + } + } + } catch (_) { + // 忽略异常,维持原有逻辑 + } } } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index f443e8bbe..382b3ca70 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -19,6 +20,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); + g_autoptr(FlPluginRegistrar) gtk_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); + gtk_plugin_register_with_registrar(gtk_registrar); g_autoptr(FlPluginRegistrar) record_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "RecordLinuxPlugin"); record_linux_plugin_register_with_registrar(record_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 346ccb75e..bd3f71e4d 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_linux flutter_secure_storage_linux + gtk record_linux url_launcher_linux window_size diff --git a/pubspec.yaml b/pubspec.yaml index 5943a452f..26889ffc7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -67,6 +67,7 @@ dependencies: url_launcher: ^6.3.1 in_app_purchase: ^3.1.11 in_app_purchase_android: ^0.4.0+5 + app_links: firebase_core: ^4.0.0 firebase_auth: ^6.0.1 google_sign_in: ^6.2.2 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index fae16f5da..227ab6e38 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,7 @@ #include "generated_plugin_registrant.h" +#include #include #include #include @@ -19,6 +20,8 @@ #include void RegisterPlugins(flutter::PluginRegistry* registry) { + AppLinksPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AppLinksPluginCApi")); ConnectivityPlusWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); FileSelectorWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 44fbd12be..17287648d 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + app_links connectivity_plus file_selector_windows firebase_auth