Browse Source

上传paypal支付代码

newdev_shunjiawei
liwei1dao 11 months ago
parent
commit
95dacb86d7
  1. 14
      android/app/src/main/AndroidManifest.xml
  2. 9
      ios/Runner/Info.plist
  3. 10
      lib/data/services/network/dio_manager.dart
  4. 13
      lib/data/services/pay_service.dart
  5. 109
      lib/modules/goods/controllers/goods_base_controller.dart
  6. 4
      linux/flutter/generated_plugin_registrant.cc
  7. 1
      linux/flutter/generated_plugins.cmake
  8. 1
      pubspec.yaml
  9. 3
      windows/flutter/generated_plugin_registrant.cc
  10. 1
      windows/flutter/generated_plugins.cmake

14
android/app/src/main/AndroidManifest.xml

@ -109,6 +109,20 @@
<category android:name="android.intent.category.BROWSABLE"/> <category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="voitrans"/> <data android:scheme="voitrans"/>
</intent-filter> </intent-filter>
<!-- PayPal 网页回跳的深链:deepvoiceclient://paypal/return 与 /cancel -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="deepvoiceclient" android:host="paypal" android:pathPrefix="/return" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="deepvoiceclient" android:host="paypal" android:pathPrefix="/cancel" />
</intent-filter>
</activity> </activity>
<!-- Don't delete the meta-data below. <!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->

9
ios/Runner/Info.plist

@ -54,6 +54,15 @@
<string>voitrans</string> <string>voitrans</string>
</array> </array>
</dict> </dict>
<!-- PayPal 回跳的自定义 Scheme -->
<dict>
<key>CFBundleURLName</key>
<string>deepvoiceclient.payment</string>
<key>CFBundleURLSchemes</key>
<array>
<string>deepvoiceclient</string>
</array>
</dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string> <string>$(FLUTTER_BUILD_NUMBER)</string>

10
lib/data/services/network/dio_manager.dart

@ -34,6 +34,16 @@ class DioManager {
dio = Dio(options); dio = Dio(options);
dio.interceptors.add(AuthInterceptor()); 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 // 请求,返回参数为 T

13
lib/data/services/pay_service.dart

@ -774,6 +774,9 @@ class PayService extends GetxService {
String? extraData, String? extraData,
}) async { }) async {
try { try {
// 回跳地址:自定义 Scheme,分别用于支付完成与取消
const returnUrl = 'deepvoiceclient://paypal/return';
const cancelUrl = 'deepvoiceclient://paypal/cancel';
final response = await Api.paycreateorder({ final response = await Api.paycreateorder({
'product_id': productId, 'product_id': productId,
'product_name': productName, 'product_name': productName,
@ -783,9 +786,13 @@ class PayService extends GetxService {
'client_ip': clientIp ?? '', 'client_ip': clientIp ?? '',
'notify_url': notifyUrl ?? '', 'notify_url': notifyUrl ?? '',
'extra_data': extraData ?? '', 'extra_data': extraData ?? '',
// 供后端设置 PayPal application_context 用于网页授权回跳
'return_url': returnUrl,
'cancel_url': cancelUrl,
}); });
if (response != null) { if (response != null) {
print('创建 PayPal 订单响应: $response');
// 1) 提取并缓存平台订单号(用于回查) // 1) 提取并缓存平台订单号(用于回查)
String? appOrderId; String? appOrderId;
const candidates = [ const candidates = [
@ -839,6 +846,8 @@ class PayService extends GetxService {
hrefRaw.isNotEmpty) { hrefRaw.isNotEmpty) {
// 清理日志中可能出现的反引号等噪音字符 // 清理日志中可能出现的反引号等噪音字符
final href = hrefRaw.replaceAll('`', '').trim(); final href = hrefRaw.replaceAll('`', '').trim();
print(
'解析到 PayPal 授权链接(links.approve): $href, appOrderId: $appOrderId');
return href; return href;
} }
} }
@ -862,6 +871,7 @@ class PayService extends GetxService {
for (final key in urlKeys) { for (final key in urlKeys) {
final url = response[key]; final url = response[key];
if (url is String && url.isNotEmpty) { if (url is String && url.isNotEmpty) {
print('解析到 PayPal 授权链接($key): $url, appOrderId: $appOrderId');
return url; return url;
} }
} }
@ -869,6 +879,8 @@ class PayService extends GetxService {
for (final key in urlKeys) { for (final key in urlKeys) {
final url = data[key]; final url = data[key];
if (url is String && url.isNotEmpty) { if (url is String && url.isNotEmpty) {
print(
'解析到 PayPal 授权链接(data.$key): $url, appOrderId: $appOrderId');
return url; return url;
} }
} }
@ -890,6 +902,7 @@ class PayService extends GetxService {
PaymentResultCallback? onResult, PaymentResultCallback? onResult,
}) async { }) async {
try { try {
print('准备打开 PayPal 授权页面: $approvalUrl');
final uri = Uri.parse(approvalUrl); final uri = Uri.parse(approvalUrl);
final ok = await launchUrl(uri, mode: LaunchMode.externalApplication); final ok = await launchUrl(uri, mode: LaunchMode.externalApplication);
if (ok) { if (ok) {

109
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/dio_manager.dart';
import '../../../data/services/network/api.dart'; import '../../../data/services/network/api.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'dart:async';
import 'package:app_links/app_links.dart';
/// goods 模块基础控制器,封装通用的商品加载与支付流程 /// goods 模块基础控制器,封装通用的商品加载与支付流程
abstract class GoodsBaseController extends GetxController { abstract class GoodsBaseController extends GetxController {
@ -17,11 +19,22 @@ abstract class GoodsBaseController extends GetxController {
final RxString error = ''.obs; final RxString error = ''.obs;
late final PayService _payService = Get.find<PayService>(); late final PayService _payService = Get.find<PayService>();
StreamSubscription<Uri?>? _deeplinkSub;
late final AppLinks _appLinks = AppLinks();
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
_init(); _init();
// 处理冷启动场景:如果 App 通过深链直接启动
_handleInitialDeepLink();
}
@override
void onClose() {
_deeplinkSub?.cancel();
_deeplinkSub = null;
super.onClose();
} }
Future<void> _init() async { Future<void> _init() async {
@ -559,8 +572,13 @@ abstract class GoodsBaseController extends GetxController {
} }
} }
Future<void> _confirmOrderAndShow(String? orderId, Future<void> _confirmOrderAndShow(
{required String channel, String? initialMessage}) async { String? orderId, {
required String channel,
String? initialMessage,
int retries = 5,
int intervalMs = 1500,
}) async {
// 若无法获取订单号,直接弹成功提示但提醒稍后刷新 // 若无法获取订单号,直接弹成功提示但提醒稍后刷新
if (orderId == null || orderId.isEmpty) { if (orderId == null || orderId.isEmpty) {
_showPayResultDialog(true, '${channel}支付成功,订单处理中,请稍后查看。'); _showPayResultDialog(true, '${channel}支付成功,订单处理中,请稍后查看。');
@ -571,7 +589,7 @@ abstract class GoodsBaseController extends GetxController {
bool success = false; bool success = false;
try { try {
// 简单轮询回查,避免回调延迟 // 简单轮询回查,避免回调延迟
for (int i = 0; i < 5; i++) { for (int i = 0; i < retries; i++) {
final resp = await Api.payaskorder({'order_id': orderId}); final resp = await Api.payaskorder({'order_id': orderId});
print("查询订单 resp: $resp"); print("查询订单 resp: $resp");
final order = resp?['order'] as Map<String, dynamic>?; final order = resp?['order'] as Map<String, dynamic>?;
@ -584,7 +602,7 @@ abstract class GoodsBaseController extends GetxController {
break; break;
} }
} }
await Future.delayed(const Duration(milliseconds: 1500)); await Future.delayed(Duration(milliseconds: intervalMs));
} }
// 若标记成功但未拿到用户信息,使用token登录接口回拉用户资料,确保额度刷新 // 若标记成功但未拿到用户信息,使用token登录接口回拉用户资料,确保额度刷新
@ -728,7 +746,88 @@ abstract class GoodsBaseController extends GetxController {
onResult?.call(false, '创建 PayPal 订单失败'); onResult?.call(false, '创建 PayPal 订单失败');
return false; 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<void> _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 (_) {
// 忽略异常,维持原有逻辑
}
} }
} }

4
linux/flutter/generated_plugin_registrant.cc

@ -8,6 +8,7 @@
#include <file_selector_linux/file_selector_plugin.h> #include <file_selector_linux/file_selector_plugin.h>
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h> #include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
#include <gtk/gtk_plugin.h>
#include <record_linux/record_linux_plugin.h> #include <record_linux/record_linux_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h> #include <url_launcher_linux/url_launcher_plugin.h>
#include <window_size/window_size_plugin.h> #include <window_size/window_size_plugin.h>
@ -19,6 +20,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); 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 = g_autoptr(FlPluginRegistrar) record_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "RecordLinuxPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "RecordLinuxPlugin");
record_linux_plugin_register_with_registrar(record_linux_registrar); record_linux_plugin_register_with_registrar(record_linux_registrar);

1
linux/flutter/generated_plugins.cmake

@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
file_selector_linux file_selector_linux
flutter_secure_storage_linux flutter_secure_storage_linux
gtk
record_linux record_linux
url_launcher_linux url_launcher_linux
window_size window_size

1
pubspec.yaml

@ -67,6 +67,7 @@ dependencies:
url_launcher: ^6.3.1 url_launcher: ^6.3.1
in_app_purchase: ^3.1.11 in_app_purchase: ^3.1.11
in_app_purchase_android: ^0.4.0+5 in_app_purchase_android: ^0.4.0+5
app_links:
firebase_core: ^4.0.0 firebase_core: ^4.0.0
firebase_auth: ^6.0.1 firebase_auth: ^6.0.1
google_sign_in: ^6.2.2 google_sign_in: ^6.2.2

3
windows/flutter/generated_plugin_registrant.cc

@ -6,6 +6,7 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <app_links/app_links_plugin_c_api.h>
#include <connectivity_plus/connectivity_plus_windows_plugin.h> #include <connectivity_plus/connectivity_plus_windows_plugin.h>
#include <file_selector_windows/file_selector_windows.h> #include <file_selector_windows/file_selector_windows.h>
#include <firebase_auth/firebase_auth_plugin_c_api.h> #include <firebase_auth/firebase_auth_plugin_c_api.h>
@ -19,6 +20,8 @@
#include <window_size/window_size_plugin.h> #include <window_size/window_size_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
AppLinksPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
ConnectivityPlusWindowsPluginRegisterWithRegistrar( ConnectivityPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
FileSelectorWindowsRegisterWithRegistrar( FileSelectorWindowsRegisterWithRegistrar(

1
windows/flutter/generated_plugins.cmake

@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
app_links
connectivity_plus connectivity_plus
file_selector_windows file_selector_windows
firebase_auth firebase_auth

Loading…
Cancel
Save