Skip to content
Toggle navigation
Toggle navigation
This project
Loading...
Sign in
ethan
/
appframe
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Network
Compare
Branches
Tags
Commit f9c0a659
authored
2026-07-14 11:06:02 +0800
by
ethanlamzs
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
ios订单订购
1 parent
538db001
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
124 additions
and
43 deletions
lib/services/subscription_service_ios.dart
lib/services/subscription_service_ios.dart
View file @
f9c0a65
...
@@ -16,7 +16,10 @@ class SubscriptionService {
...
@@ -16,7 +16,10 @@ class SubscriptionService {
static
const
String
monthlySubId_12
=
'cn.banxe.appframe.membership.12m'
;
static
const
String
monthlySubId_12
=
'cn.banxe.appframe.membership.12m'
;
// 纯粹的数据容器,不继承任何类
// 纯粹的数据容器,不继承任何类
final
Map
<
String
,
Map
<
String
,
dynamic
>>
_pendingContexts
=
{};
// ---------- 业务上下文映射(productID → 自定义数据) ----------
final
Map
<
String
,
Map
<
String
,
dynamic
>>
_pendingContexts
=
{};
final
Map
<
String
,
String
>
_orderIdMap
=
{};
// key: productID, value: orderId
final
Map
<
String
,
String
>
_orderIdMap
=
{};
// key: productID, value: orderId
final
InAppPurchase
_iap
=
InAppPurchase
.
instance
;
final
InAppPurchase
_iap
=
InAppPurchase
.
instance
;
...
@@ -31,6 +34,14 @@ class SubscriptionService {
...
@@ -31,6 +34,14 @@ class SubscriptionService {
bool
_hasActiveSubscription
=
false
;
bool
_hasActiveSubscription
=
false
;
bool
get
hasActiveSubscription
=>
_hasActiveSubscription
;
bool
get
hasActiveSubscription
=>
_hasActiveSubscription
;
// ---------- 🔁 去重措施:记录已处理过的交易 ID ----------
final
Set
<
String
>
_processedTransactionIds
=
{};
// ---------- 回调函数,用于通知 UI 状态变化 ----------
void
Function
(
String
?
errorMessage
)?
onError
;
void
Function
(
String
message
)?
onSuccess
;
void
Function
()?
onStateChanged
;
// 初始化
// 初始化
Future
<
void
>
initialize
()
async
{
Future
<
void
>
initialize
()
async
{
// 1. 检查是否可用
// 1. 检查是否可用
...
@@ -85,10 +96,17 @@ class SubscriptionService {
...
@@ -85,10 +96,17 @@ class SubscriptionService {
productDetails:
product
,
productDetails:
product
,
);
);
// 发起购买(App Store 会弹出系统支付界面)
try
{
await
_iap
.
buyNonConsumable
(
purchaseParam:
purchaseParam
);
// 发起购买(App Store 会弹出系统支付界面)
await
_iap
.
buyNonConsumable
(
purchaseParam:
purchaseParam
);
return
true
;
// 实际结果在 _onPurchaseUpdate 处理
}
catch
(
e
){
_pendingContexts
.
remove
(
product
.
id
);
// 发起失败,清除上下文
onError
?.
call
(
'购买请求失败:
$e
'
);
return
false
;
}
// 注意:成功或失败会通过 purchaseStream 回调返回
// 注意:成功或失败会通过 purchaseStream 回调返回
return
true
;
// 实际结果在 _onPurchaseUpdate 处理
}
}
// ---------- 交易更新回调 ----------
// ---------- 交易更新回调 ----------
...
@@ -102,6 +120,23 @@ class SubscriptionService {
...
@@ -102,6 +120,23 @@ class SubscriptionService {
continue
;
continue
;
}
}
// 🔁 去重措施 1:根据原始交易 ID 去重,防止同一笔交易被重复处理
final
transactionId
=
purchaseDetails
.
verificationData
.
serverVerificationData
.
hashCode
.
toString
();
// 如果能获取到 purchaseID 更可靠,这里用 purchaseID 作为唯一标识
final
uniqueId
=
purchaseDetails
.
purchaseID
;
if
(
_processedTransactionIds
.
contains
(
uniqueId
))
{
// 已经处理过,直接结束交易并跳过
if
(
purchaseDetails
.
status
==
PurchaseStatus
.
purchased
||
purchaseDetails
.
status
==
PurchaseStatus
.
restored
)
{
_iap
.
completePurchase
(
purchaseDetails
);
}
continue
;
}
// 标记为已处理
if
(
uniqueId
!=
null
)
{
_processedTransactionIds
.
add
(
uniqueId
);
}
//var orderId = _orderIdMap.remove(purchaseDetails.productID); // 取出并删除
//var orderId = _orderIdMap.remove(purchaseDetails.productID); // 取出并删除
final
ctx
=
_pendingContexts
[
purchaseDetails
.
productID
]
;
final
ctx
=
_pendingContexts
[
purchaseDetails
.
productID
]
;
print
(
ctx
);
print
(
ctx
);
...
@@ -123,31 +158,43 @@ class SubscriptionService {
...
@@ -123,31 +158,43 @@ class SubscriptionService {
case
PurchaseStatus
.
purchased
:
case
PurchaseStatus
.
purchased
:
// 购买成功或续期成功
// 购买成功或续期成功
// 1. 验证收据(必须!)
// 1. 验证收据(必须!)
_verifyReceipt
(
purchaseDetails
,
orderId
??
''
,
userId
??
''
,
"purchased"
);
//
_verifyReceipt(purchaseDetails,orderId??'',userId??'',"purchased");
// 2. 通知 App Store 交易完成
_handlePurchaseSuccess
(
purchaseDetails
,
orderId
??
''
,
userId
??
''
);
_iap
.
completePurchase
(
purchaseDetails
)
;
break
;
case
PurchaseStatus
.
error
:
case
PurchaseStatus
.
error
:
// 处理错误
// 处理错误
_handlePurchaseError
(
purchaseDetails
.
error
!);
_handlePurchaseError
(
purchaseDetails
.
error
!);
_iap
.
completePurchase
(
purchaseDetails
);
// 同样需要结束交易
break
;
case
PurchaseStatus
.
restored
:
case
PurchaseStatus
.
restored
:
// 恢复购买成功
// 恢复购买成功
_verifyReceipt
(
purchaseDetails
,
orderId
??
''
,
userId
??
''
,
"restored"
);
// 🔁 去重措施 2:恢复事件只处理最后一个有效的,或者收集后统一处理
_iap
.
completePurchase
(
purchaseDetails
);
// 这里演示:仍然逐个验证,但后端会基于 original_transaction_id 去重
//_verifyReceipt(purchaseDetails,orderId??'',userId??'',"restored");
_handleRestoreSuccess
(
purchaseDetails
,
orderId
??
''
,
userId
??
''
);
break
;
case
PurchaseStatus
.
canceled
:
case
PurchaseStatus
.
canceled
:
// 用户取消支付
// 用户取消支付
_iap
.
completePurchase
(
purchaseDetails
);
//_iap.completePurchase(purchaseDetails);
break
;
default
:
default
:
break
;
}
}
// ✅ 关键:只要不是 pending,都立刻完成交易,清出队列
// // ✅ 关键:只要不是 pending,都立刻完成交易,清出队列
if
(
purchaseDetails
.
status
!=
PurchaseStatus
.
pending
)
{
// if (purchaseDetails.status != PurchaseStatus.pending) {
_iap
.
completePurchase
(
purchaseDetails
);
// _iap.completePurchase(purchaseDetails);
print
(
"[debug] _iap.completePurchase execute "
);
// print("[debug] _iap.completePurchase execute ");
// }
// 🔁 去重措施 3:所有恢复交易处理完后,统一刷新一次状态(防多次通知)
final
hasRestored
=
purchaseDetailsList
.
any
((
d
)
=>
d
.
status
==
PurchaseStatus
.
restored
);
if
(
hasRestored
)
{
// 统一通知 UI 更新,避免多次刷新
onSuccess
?.
call
(
'购买已恢复'
);
onStateChanged
?.
call
();
}
}
}
}
}
}
...
@@ -170,34 +217,68 @@ class SubscriptionService {
...
@@ -170,34 +217,68 @@ class SubscriptionService {
}
}
}
}
// 示例:发送收据到自己的服务器进行二次验证
// 示例:发送收据到自己的服务器进行二次验证
Future
<
bool
>
_sendReceiptToBackend
(
String
userid
,
String
receipt
,
String
orderid
)
async
{
Future
<
bool
>
_sendReceiptToBackend
(
String
userid
,
String
receipt
,
String
orderid
)
async
{
var
result
=
_userAuthRepository
.
orderCheck
(
userid
,
receipt
,
orderid
,
"ios"
,
1
);
var
result
=
_userAuthRepository
.
orderCheck
(
userid
,
receipt
,
orderid
,
"ios"
,
1
);
//var data = result['data'] as Map<String, dynamic>?;
//var data = result['data'] as Map<String, dynamic>?;
//debugPrint(result);
//debugPrint(result);
debugPrint
(
"[info] _sendReceiptToBackend is done"
);
debugPrint
(
"[info] _sendReceiptToBackend is done"
);
return
true
;
return
true
;
}
}
// ---------- 恢复购买 ----------
Future
<
void
>
restorePurchases
()
async
{
await
_iap
.
restorePurchases
();
// 恢复的订阅信息会通过 purchaseStream 以 restored 状态返回
}
// ---------- 辅助方法 ----------
bool
_isSubscription
(
String
productId
)
{
return
productId
==
monthlySubId_1
||
productId
==
monthlySubId_3
||
productId
==
monthlySubId_6
||
productId
==
monthlySubId_12
;
}
void
_handlePurchaseError
(
IAPError
error
)
{
// ---------- 辅助方法 ----------
// 根据 error.code 显示不同提示
bool
_isSubscription
(
String
productId
)
{
debugPrint
(
'购买失败:
${error.message}
'
)
;
return
productId
==
monthlySubId_1
||
productId
==
monthlySubId_3
||
productId
==
monthlySubId_6
||
productId
==
monthlySubId_12
;
}
}
// 释放资源
void
_handlePurchaseError
(
IAPError
error
)
{
void
dispose
()
{
// 根据 error.code 显示不同提示
_purchaseUpdatedSubscription
.
cancel
();
debugPrint
(
'购买失败:
${error.message}
'
);
}
}
// ---------- 购买成功处理 ----------
void
_handlePurchaseSuccess
(
PurchaseDetails
detail
,
String
orderId
,
String
userId
)
{
// 发送收据到后端验证,携带 userId
_verifyReceipt
(
detail
,
orderId
??
''
,
userId
??
''
,
"purchased"
);
// 完成交易
_iap
.
completePurchase
(
detail
);
// 乐观更新本地状态(等后端最终确认再真正解锁功能)
_hasActiveSubscription
=
true
;
onSuccess
?.
call
(
'订阅成功'
);
onStateChanged
?.
call
();
}
// ---------- 恢复成功处理 ----------
void
_handleRestoreSuccess
(
PurchaseDetails
detail
,
String
orderId
,
String
userId
)
{
// 注意:这里只是处理单个 restored,但最终 UI 刷新在 _onPurchaseUpdate 统一完成
_verifyReceipt
(
detail
,
orderId
??
''
,
userId
??
''
,
"purchased"
);
_iap
.
completePurchase
(
detail
);
}
// ---------- 错误处理 ----------
void
_handleError
(
PurchaseDetails
detail
)
{
_pendingContexts
.
remove
(
detail
.
productID
);
_iap
.
completePurchase
(
detail
);
onError
?.
call
(
'支付失败:
${detail.error?.message}
'
);
}
// ---------- 取消处理 ----------
void
_handleCancel
(
PurchaseDetails
detail
)
{
_pendingContexts
.
remove
(
detail
.
productID
);
_iap
.
completePurchase
(
detail
);
onError
?.
call
(
'支付已取消'
);
}
// 释放资源
void
dispose
()
{
_purchaseUpdatedSubscription
.
cancel
();
}
}
}
\ No newline at end of file
\ No newline at end of file
Write
Preview
Styling with
Markdown
is supported
Attach a file
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to post a comment