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