Browse Source

上传google支付

newdev_shunjiawei
liwei1dao 11 months ago
parent
commit
7e6f906013
  1. 9
      lib/data/services/network/api.dart
  2. 250
      lib/data/services/pay_service.dart
  3. 154
      lib/modules/goods/controllers/goods_base_controller.dart
  4. 1
      pubspec.yaml

9
lib/data/services/network/api.dart

@ -190,6 +190,15 @@ class Api {
); );
} }
//内购收据验证
static payiapverify([params]) {
return DioManager().request(
NWMethod.post,
'/api/home/pay_iapverify',
params: params,
);
}
//获取模版数据 //获取模版数据
static getEchomeetTemplates([params]) { static getEchomeetTemplates([params]) {
return DioManager().request( return DioManager().request(

250
lib/data/services/pay_service.dart

@ -1,8 +1,12 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'dart:convert'; import 'dart:convert';
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:voitrans/data/services/network/api.dart'; import 'package:voitrans/data/services/network/api.dart';
import 'package:tobias/tobias.dart'; import 'package:tobias/tobias.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:voitrans/data/models/user_Info.dart';
import '../../modules/login/controllers/weixinLogin.dart'; import '../../modules/login/controllers/weixinLogin.dart';
@ -13,6 +17,11 @@ class PayService extends GetxService {
late FlWX _flwx; late FlWX _flwx;
late Tobias _tobias; // 添加 Tobias 实例 late Tobias _tobias; // 添加 Tobias 实例
String? _lastOrderId; // 最近一次创建的订单号 String? _lastOrderId; // 最近一次创建的订单号
// 内购
final InAppPurchase _inAppPurchase = InAppPurchase.instance;
StreamSubscription<List<PurchaseDetails>>? _iapSubscription;
final RxBool iapAvailable = false.obs;
PaymentResultCallback? _iapOnResult;
String? get lastOrderId => _lastOrderId; String? get lastOrderId => _lastOrderId;
@ -33,6 +42,7 @@ class PayService extends GetxService {
// 初始化支付宝SDK // 初始化支付宝SDK
_tobias = Tobias(); // 初始化 Tobias 实例 _tobias = Tobias(); // 初始化 Tobias 实例
_initAlipay(); _initAlipay();
_initIAP();
} }
/// 初始化支付宝SDK /// 初始化支付宝SDK
@ -46,6 +56,246 @@ class PayService extends GetxService {
} }
} }
/// 初始化内购(Apple/Google)
Future<void> _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<void> debugQueryProducts(Set<String> 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 平台执行;平台信息用于日志
await _initIAP();
final platform = _platformString();
if (!iapAvailable.value) {
print('[IAP-$platform] 当前平台内购不可用,无法查询商品。');
return;
}
print('[IAP-$platform] 开始查询商品,IDs=${productIds.join(', ')}');
final resp = await _inAppPurchase.queryProductDetails(productIds);
if (resp.error != null) {
print('[IAP-$platform] 查询错误: ${resp.error}');
}
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<ProductDetails?> _queryIapProduct(String productId) async {
try {
final pid = productId.trim();
print('[IAP] 开始查询单个商品: id="$pid"');
final resp = await _inAppPurchase.queryProductDetails({pid});
if (resp.error != null) {
print('[IAP] 查询商品失败: ${resp.error}');
}
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<bool> _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 {
final purchaseParam = PurchaseParam(productDetails: product);
final ok = await _inAppPurchase.buyConsumable(
purchaseParam: purchaseParam,
autoConsume: true,
);
if (!ok) {
onResult?.call(false, '无法发起内购');
} else {
onResult?.call(true, '已发起内购,请完成支付');
}
return ok;
} catch (e) {
onResult?.call(false, '发起内购失败: $e');
return false;
}
}
Future<void> _handlePurchaseUpdates(List<PurchaseDetails> detailsList) async {
for (final details in detailsList) {
switch (details.status) {
case PurchaseStatus.pending:
_iapOnResult?.call(true, '支付处理中…');
break;
case PurchaseStatus.error:
_iapOnResult?.call(false, details.error?.message ?? '支付错误');
break;
case PurchaseStatus.purchased:
case PurchaseStatus.restored:
// 完成购买并进行服务端收据验证
try {
await _verifyIapWithServer(details);
} catch (e) {
_iapOnResult?.call(false, '收据验证失败: $e');
}
break;
case PurchaseStatus.canceled:
_iapOnResult?.call(false, '已取消支付');
break;
}
if (details.pendingCompletePurchase) {
try {
await _inAppPurchase.completePurchase(details);
} catch (_) {}
}
}
}
Future<void> _verifyIapWithServer(PurchaseDetails details) async {
final platform = _platformString();
final verificationData =
details.verificationData.serverVerificationData ?? '';
final transactionId = details.purchaseID ?? '';
final params = {
'platform': platform,
'product_id': details.productID,
'transaction_id': transactionId,
'server_verification_data': verificationData,
};
try {
final resp = await Api.payiapverify(params);
// 兼容多种返回结构,优先拿到用户信息
Map<String, dynamic>? user;
if (resp is Map) {
if (resp['user'] is Map<String, dynamic>) {
user = resp['user'] as Map<String, dynamic>;
} else if (resp['data'] is Map &&
(resp['data']['user'] is Map<String, dynamic>)) {
user = resp['data']['user'] as Map<String, dynamic>;
}
}
if (user != null) {
User.instance.updateUserInfo(user);
_iapOnResult?.call(true, '支付成功');
} else {
_iapOnResult?.call(true, '支付成功,额度将稍后刷新');
}
} catch (e) {
_iapOnResult?.call(false, '服务器验证异常: $e');
}
}
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 内购
Future<bool> processApplePayment({
required String productId,
PaymentResultCallback? onResult,
}) async {
if (kIsWeb || defaultTargetPlatform != TargetPlatform.iOS) {
onResult?.call(false, '当前平台不支持 Apple 内购');
return false;
}
// 重新初始化内购,避免早期可用性判断为 false
await _initIAP();
return _processIapPayment(productId: productId, onResult: onResult);
}
/// 发起 Google Play 内购
Future<bool> processGooglePayment({
required String productId,
PaymentResultCallback? onResult,
}) async {
if (kIsWeb || defaultTargetPlatform != TargetPlatform.android) {
onResult?.call(false, '当前平台不支持 Google 内购');
return false;
}
// 重新初始化内购,避免早期可用性判断为 false
await _initIAP();
return _processIapPayment(productId: productId, onResult: onResult);
}
/// 查询支付结果 /// 查询支付结果
/// ///
/// [orderId] 订单ID /// [orderId] 订单ID

154
lib/modules/goods/controllers/goods_base_controller.dart

@ -7,6 +7,7 @@ import '../../../data/models/goods.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; 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';
/// goods 模块基础控制器,封装通用的商品加载与支付流程 /// goods 模块基础控制器,封装通用的商品加载与支付流程
abstract class GoodsBaseController extends GetxController { abstract class GoodsBaseController extends GetxController {
@ -57,6 +58,14 @@ abstract class GoodsBaseController extends GetxController {
ok = await payPaypal(item, onResult: (success, msg) { ok = await payPaypal(item, onResult: (success, msg) {
if (!success) failMsg = msg ?? failMsg; 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) { if (!ok) {
@ -71,6 +80,8 @@ abstract class GoodsBaseController extends GetxController {
final context = Get.context; final context = Get.context;
final isDark = final isDark =
context != null && Theme.of(context).brightness == Brightness.dark; context != null && Theme.of(context).brightness == Brightness.dark;
final isIOS = GetPlatform.isIOS && !kIsWeb;
final isAndroid = GetPlatform.isAndroid && !kIsWeb;
return await Get.bottomSheet<_PayMethod>( return await Get.bottomSheet<_PayMethod>(
SafeArea( SafeArea(
child: Container( child: Container(
@ -208,6 +219,78 @@ abstract class GoodsBaseController extends GetxController {
], ],
), ),
), ),
if (isIOS)
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)
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), const SizedBox(height: 12),
@ -287,6 +370,75 @@ abstract class GoodsBaseController extends GetxController {
); );
} }
/// 指定 Apple 内购(App Store IAP)
Future<bool> 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<bool> 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<void> 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<void> _confirmOrderAndShow(String? orderId, Future<void> _confirmOrderAndShow(String? orderId,
{required String channel, String? initialMessage}) async { {required String channel, String? initialMessage}) async {
// 若无法获取订单号,直接弹成功提示但提醒稍后刷新 // 若无法获取订单号,直接弹成功提示但提醒稍后刷新
@ -429,4 +581,4 @@ abstract class GoodsBaseController extends GetxController {
} }
} }
enum _PayMethod { wechat, alipay, paypal } enum _PayMethod { wechat, alipay, paypal, apple, google }

1
pubspec.yaml

@ -64,6 +64,7 @@ dependencies:
fluwx: ^5.7.1 fluwx: ^5.7.1
tobias: ^5.2.0 # 支付宝SDK tobias: ^5.2.0 # 支付宝SDK
url_launcher: ^6.3.1 url_launcher: ^6.3.1
in_app_purchase: ^3.1.11
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

Loading…
Cancel
Save