Commit bfe36a4a by ethanlamzs

debug支付

1 parent 45d44847
......@@ -26,7 +26,7 @@ end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
# pod 'TOCropViewController', :path => '/Users/ethanlam/works/base_env/flutter_envirs/extlibs/TOCropViewController.git/2.8.0' , :podspec => '/Users/ethanlam/works/base_env/flutter_envirs/extlibs/TOCropViewController.git/2.8.0/TOCropViewController.podspec'
#pod 'TOCropViewController', :path => '/Users/ethanlam/works/base_env/flutter_envirs/extlibs/TOCropViewController.git/2.8.0' , :podspec => '/Users/ethanlam/works/base_env/flutter_envirs/extlibs/TOCropViewController.git/2.8.0/TOCropViewController.podspec'
pod 'SDWebImage', :git => 'https://gitee.com/mirrors/SDWebImage.git', :branch => '5.21.3', :modular_headers => true
......
......@@ -72,7 +72,7 @@
DC39D7892EFB988000D795A8 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "zh-Hans"; path = "zh-Hans.lproj/Info.plist"; sourceTree = "<group>"; };
DC4A6BC3E644F00288FAFDCD /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DC7B80912FF3602D0006BCCE /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
DCCFA01F2FF3F74300E9FFD3 /* LocalIAP.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; name = LocalIAP.storekit; path = Runner/LocalIAP.storekit; sourceTree = "<group>"; };
DCEFFEE63000FA7000F7B036 /* Testing.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; name = Testing.storekit; path = Runner/Testing.storekit; sourceTree = "<group>"; };
E94A65B4132ADF0336017236 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
......@@ -119,7 +119,7 @@
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
DCCFA01F2FF3F74300E9FFD3 /* LocalIAP.storekit */,
DCEFFEE63000FA7000F7B036 /* Testing.storekit */,
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
......
......@@ -73,6 +73,9 @@
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<StoreKitConfigurationFileReference
identifier = "../Runner/Testing.storekit">
</StoreKitConfigurationFileReference>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
......
......@@ -2,9 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "group:Runner/LocalIAP.storekit">
</FileRef>
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
......
import UIKit
import Flutter
import StoreKit
// Add these two import lines
import TIMPush
......@@ -21,6 +22,22 @@ import tencent_cloud_chat_push
fatalError("rootViewController is not a FlutterViewController")
}
let channel = FlutterMethodChannel(
name: "cn.banxe.appframe/receipt",
binaryMessenger: controller.binaryMessenger
)
channel.setMethodCallHandler { (call, result) in
switch call.method {
case "getReceipt":
self.getReceiptBase64(result: result)
case "refreshReceipt":
self.refreshReceipt(result: result)
default:
result(FlutterMethodNotImplemented)
}
}
// 注册插件
GeneratedPluginRegistrant.register(with: self)
......@@ -85,6 +102,24 @@ import tencent_cloud_chat_push
}
private func getReceiptBase64(result: @escaping FlutterResult) {
guard let receiptURL = Bundle.main.appStoreReceiptURL,
FileManager.default.fileExists(atPath: receiptURL.path),
let receiptData = try? Data(contentsOf: receiptURL) else {
result(FlutterError(code: "NO_RECEIPT",
message: "收据不存在,请先刷新",
details: nil))
return
}
result(receiptData.base64EncodedString())
}
private func refreshReceipt(result: @escaping FlutterResult) {
let request = SKReceiptRefreshRequest()
request.delegate = RefreshDelegate(result: result)
request.start()
}
}
// 可选:防止冲突的手势识别设置
......@@ -93,4 +128,19 @@ extension AppDelegate: UIGestureRecognizerDelegate {
// 如果需要同时识别,比如与 WebView 的滚动手势共存
return true
}
}
class RefreshDelegate: NSObject, SKRequestDelegate {
let result: FlutterResult
init(result: @escaping FlutterResult) { self.result = result }
func requestDidFinish(_ request: SKRequest) {
result(true)
}
func request(_ request: SKRequest, didFailWithError error: Error) {
result(FlutterError(code: "REFRESH_FAILED",
message: error.localizedDescription,
details: nil))
}
}
\ No newline at end of file
{
"appPolicies" : {
"eula" : "",
"policies" : [
{
"locale" : "en_US",
"policyText" : "",
"policyURL" : ""
}
]
},
"identifier" : "6C86460D",
"nonRenewingSubscriptions" : [
{
"displayPrice" : "49.90",
"familyShareable" : false,
"internalID" : "6785393224",
"localizations" : [
{
"description" : "智学会员1个月有效期",
"displayName" : "智学VIP月卡",
"locale" : "zh_Hans"
}
],
"productID" : "cn.banxe.appframe.membership.1m",
"referenceName" : "1个月时长会员",
"type" : "NonRenewingSubscription"
},
{
"displayPrice" : "129",
"familyShareable" : false,
"internalID" : "6788337604",
"localizations" : [
{
"description" : "智学会员服务1个季度的有效期",
"displayName" : "智学VIP季卡",
"locale" : "zh_Hans"
}
],
"productID" : "cn.banxe.appframe.membership.3m",
"referenceName" : "1季度会员",
"type" : "NonRenewingSubscription"
},
{
"displayPrice" : "299",
"familyShareable" : false,
"internalID" : "6788342136",
"localizations" : [
{
"description" : "智学VIP会员服务1年有效期",
"displayName" : "智学VIP年卡",
"locale" : "zh_Hans"
}
],
"productID" : "cn.banxe.appframe.membership.12m",
"referenceName" : "一年会员",
"type" : "NonRenewingSubscription"
},
{
"displayPrice" : "209",
"familyShareable" : false,
"internalID" : "6788341520",
"localizations" : [
{
"description" : "智学VIP会员服务半年有效期",
"displayName" : "智学VIP半年卡",
"locale" : "zh_Hans"
}
],
"productID" : "cn.banxe.appframe.membership.6m",
"referenceName" : "半年会员",
"type" : "NonRenewingSubscription"
},
{
"displayPrice" : "1",
"familyShareable" : false,
"internalID" : "6789528182",
"localizations" : [
{
"description" : "智学会员服务1天有效",
"displayName" : "会员1天",
"locale" : "zh_Hans"
}
],
"productID" : "cn.banxe.appframe.membership.0m",
"referenceName" : "测试会员",
"type" : "NonRenewingSubscription"
}
],
"products" : [
],
"settings" : {
"_applicationInternalID" : "6757346215",
"_askToBuyEnabled" : false,
"_billingGracePeriodEnabled" : false,
"_billingIssuesEnabled" : false,
"_developerTeamID" : "42794B2KBU",
"_disableDialogs" : false,
"_failTransactionsEnabled" : false,
"_lastSynchronizedDate" : 805370487.02650404,
"_locale" : "zh_Hans",
"_renewalBillingIssuesEnabled" : false,
"_storefront" : "CHN",
"_storeKitErrors" : [
],
"_timeRate" : 0
},
"subscriptionGroups" : [
],
"version" : {
"major" : 4,
"minor" : 0
}
}
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:appframe/config/constant.dart';
import 'package:appframe/config/locator.dart';
......@@ -57,9 +58,8 @@ class XeCreateOrderHandler extends MessageHandler {
'ext=$ext');
// 1. 请求后端获取预支付订单信息
final deviceType = 'ios';
print("[debug]. handleMessage 1 ");
final deviceType = Platform.isIOS?"ios":"andorid";
print("[debug]. handleMessage 1 "+deviceType);
if (deviceType == 'andorid'){
await _wx_pay_proc_(userCode, tbxStuId, chargeCode, deviceType, phone, stuId, ext, duration, durationType, totalFee, renew, useCoin);
}else if (deviceType == 'ios'){
......@@ -212,7 +212,7 @@ class XeCreateOrderHandler extends MessageHandler {
// map[nonceStr:qNfHK6a95jjJkwzDkh0h3fhfUVuS0jZ9 paySign: timeStamp:1783497438 wxTradeNo:to1281644408384671744]
var data = result['data'] as Map<String, dynamic>?;
if (data == null) {
throw Exception('支付参数获取失败');
throw Exception('订单生成失败');
}
print("[debug]. _ios_pay_proc_ 13 ");
......@@ -238,13 +238,27 @@ class XeCreateOrderHandler extends MessageHandler {
// 为了调试,先模拟
var product;
for (var p in _subService.products) {
debugPrint('x1=====> 产品ID: ${p.id}, 标题: ${p.title}, 价格: ${p.price}');
// 因为带了一个¥符号
debugPrint('checking ==> 产品ID: ${p.id}, 标题: ${p.title}, 价格: ${p.price}');
if (p.price.toString().indexOf(totalFee.toString().substring(0,2))==1){
product = p;
debugPrint('match =====> 产品ID: ${p.id}, 标题: ${p.title}, 价格: ${p.price}');
break;
}
}
product = _subService.products[0];
_subService.buySubscription(product, userCode, tbxStuId,wxTradeNo);
if(product==null){
throw Exception('订单生成失败');
}
print("match product is "+product.price);
var isOk = _subService.buySubscription(product, userCode, tbxStuId,wxTradeNo);
if (isOk==true){
return {"orderUnique": wxTradeNo, "orderState": 1};
}else{
return {"orderUnique": wxTradeNo, "orderState": 0};
}
// throw Exception('已取消支付');
}
......
......@@ -4,6 +4,7 @@ import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:flutter/widgets.dart';
import 'package:appframe/data/repositories/user_auth_repository.dart';
import 'package:appframe/config/locator.dart';
import 'package:appframe/utils/ios_receiptHelper.dart';
class SubscriptionService {
......@@ -42,7 +43,10 @@ class SubscriptionService {
// 2. 监听交易更新(最重要!务必在调用购买之前设置)
_purchaseUpdatedSubscription =
_iap.purchaseStream.listen(_onPurchaseUpdate);
_iap.purchaseStream.listen(
_onPurchaseUpdate,
onError: (error) => print('purchaseStream error: $error'),
);
// 3. 从 App Store 获取产品信息
await _fetchProducts();
......@@ -65,9 +69,9 @@ class SubscriptionService {
_products = response.productDetails;
debugPrint('获取到 ${_products.length} 个产品');
for (var p in _products) {
debugPrint('x1=====> 产品ID: ${p.id}, 标题: ${p.title}, 价格: ${p.price}');
}
// for (var p in _products) {
// debugPrint('x1=====> 产品ID: ${p.id}, 标题: ${p.title}, 价格: ${p.price}');
// }
}
......@@ -83,11 +87,6 @@ class SubscriptionService {
// 订阅必须使用 PurchaseParam 并设置购买类型为订阅
final PurchaseParam purchaseParam = PurchaseParam(
productDetails: product,
// 对于订阅,可在此传入升级/降级信息,例如:
// changeSubscriptionParam: ChangeSubscriptionParam(
// oldPurchaseDetails: oldPurchase,
// prorationMode: ProrationMode.immediateWithTimeProration,
// ),
);
// 发起购买(App Store 会弹出系统支付界面)
......@@ -98,13 +97,16 @@ class SubscriptionService {
// ---------- 交易更新回调 ----------
void _onPurchaseUpdate(List<PurchaseDetails> purchaseDetailsList) {
for (final purchaseDetails in purchaseDetailsList) {
print('Purchase update: ${purchaseDetails.productID} - ${purchaseDetails.status}');
// 只处理订阅类型产品
if (!_isSubscription(purchaseDetails.productID)) continue;
// 根据交易状态处理
debugPrint("[info] _onPurchaseUpdate status is "+ purchaseDetails.status.toString());
switch (purchaseDetails.status) {
case PurchaseStatus.pending:
// 等待支付确认(例如需要家长批准),不做处理
break;
......@@ -117,7 +119,7 @@ class SubscriptionService {
print(ctx);
// 1. 验证收据(必须!)
_verifyReceipt(purchaseDetails,orderId??'');
_verifyReceipt(purchaseDetails,orderId??'',"purchased");
// 2. 通知 App Store 交易完成
_iap.completePurchase(purchaseDetails);
break;
......@@ -130,7 +132,7 @@ class SubscriptionService {
case PurchaseStatus.restored:
// 恢复购买成功
_verifyReceipt(purchaseDetails,"");
_verifyReceipt(purchaseDetails,"","restored");
_iap.completePurchase(purchaseDetails);
break;
......@@ -138,21 +140,33 @@ class SubscriptionService {
// 用户取消支付
_iap.completePurchase(purchaseDetails);
break;
default:
break;
}
// ✅ 关键:只要不是 pending,都立刻完成交易,清出队列
if (purchaseDetails.status != PurchaseStatus.pending) {
_iap.completePurchase(purchaseDetails);
}
}
}
// ---------- 服务端验证收据 ----------
Future<void> _verifyReceipt(PurchaseDetails purchaseDetails,String orderid) async {
if (purchaseDetails.verificationData.serverVerificationData.isEmpty) {
debugPrint('无收据数据');
return;
}
debugPrint("[info] _verifyReceipt is start "+purchaseDetails.productID);
Future<void> _verifyReceipt(PurchaseDetails purchaseDetails,String orderid,String scence) async {
debugPrint("[info] _verifyReceipt is start scence ["+scence+"] "+purchaseDetails.productID);
// 将收据发送到你的后端进行验证
final String receipt = purchaseDetails.verificationData.serverVerificationData;
final bool isValid = await _sendReceiptToBackend(receipt,orderid);
//final String receipt = purchaseDetails.verificationData.serverVerificationData;
// 可选:沙盒环境经常需要先刷新一下
await ReceiptHelper.refreshReceipt();
final base64Receipt = await ReceiptHelper.getBase64Receipt();
// 把 base64Receipt 作为 receipt-data 发给你的服务端验证
final bool isValid = await _sendReceiptToBackend(base64Receipt,orderid);
debugPrint("[info] _verifyReceipt is step 1 ");
if (isValid) {
// 更新本地订阅状态
......
import 'dart:convert';
import 'package:flutter/services.dart';
class ReceiptHelper {
static const _channel = MethodChannel('cn.banxe.appframe/receipt');
/// 获取 Base64 编码的收据数据,直接可用于 /verifyReceipt
static Future<String> getBase64Receipt() async {
try {
final String? base64 = await _channel.invokeMethod('getReceipt');
if (base64 == null || base64.isEmpty) {
throw Exception('收据为空,请先刷新收据');
}
return base64;
} on MissingPluginException {
throw Exception('原生插件未注册,请检查 iOS 代码');
}
}
/// 强制刷新收据(沙盒环境常需要)
static Future<void> refreshReceipt() async {
try {
await _channel.invokeMethod('refreshReceipt');
} catch (e) {
throw Exception('刷新收据失败: $e');
}
}
}
\ No newline at end of file
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!