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 1d8c2b03
authored
2026-07-17 16:54:13 +0800
by
ethanlamzs
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
test
1 parent
cf893e75
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
119 additions
and
120 deletions
lib/services/subscription_service_ios.dart
lib/services/subscription_service_ios.dart
View file @
1d8c2b0
...
@@ -77,7 +77,7 @@ class SubscriptionService {
...
@@ -77,7 +77,7 @@ class SubscriptionService {
// 有产品未在 App Store Connect 配置或配置错误
// 有产品未在 App Store Connect 配置或配置错误
debugPrint
(
'未找到产品:
${response.notFoundIDs}
'
);
debugPrint
(
'未找到产品:
${response.notFoundIDs}
'
);
}
}
_products
=
response
.
productDetails
;
_products
=
response
.
productDetails
;
debugPrint
(
'获取到
${_products.length}
个产品'
);
debugPrint
(
'获取到
${_products.length}
个产品'
);
for
(
var
p
in
_products
)
{
for
(
var
p
in
_products
)
{
...
@@ -86,8 +86,8 @@ class SubscriptionService {
...
@@ -86,8 +86,8 @@ class SubscriptionService {
}
}
// ---------- 购买订阅 ----------
// ---------- 购买订阅 ----------
Future
<
bool
>
buySubscription
(
ProductDetails
product
,
String
userId
,
String
tbxStuId
,
String
yWOrderId
)
async
{
Future
<
bool
>
buySubscription
(
ProductDetails
product
,
String
userId
,
String
tbxStuId
,
String
yWOrderId
)
async
{
_pendingContexts
[
product
.
id
]
=
{
'userId'
:
userId
,
'tbxStuId'
:
tbxStuId
,
"orderId"
:
yWOrderId
};
_pendingContexts
[
product
.
id
]
=
{
'userId'
:
userId
,
'tbxStuId'
:
tbxStuId
,
"orderId"
:
yWOrderId
};
_orderIdMap
[
product
.
id
]
=
yWOrderId
;
// 按产品 ID 存储
_orderIdMap
[
product
.
id
]
=
yWOrderId
;
// 按产品 ID 存储
...
@@ -110,126 +110,124 @@ class SubscriptionService {
...
@@ -110,126 +110,124 @@ class SubscriptionService {
}
}
// ---------- 交易更新回调 ----------
// ---------- 交易更新回调 ----------
void
_onPurchaseUpdate
(
List
<
PurchaseDetails
>
purchaseDetailsList
)
{
void
_onPurchaseUpdate
(
List
<
PurchaseDetails
>
purchaseDetailsList
)
{
for
(
final
purchaseDetails
in
purchaseDetailsList
)
{
for
(
final
purchaseDetails
in
purchaseDetailsList
)
{
print
(
'Purchase update:
${purchaseDetails.productID}
-
${purchaseDetails.status}
'
);
print
(
'Purchase update:
${purchaseDetails.productID}
-
${purchaseDetails.status}
'
);
// 只处理订阅类型产品
// 只处理订阅类型产品
if
(!
_isSubscription
(
purchaseDetails
.
productID
)){
if
(!
_isSubscription
(
purchaseDetails
.
productID
)){
print
(
"Purchase update !_isSubscription "
);
print
(
"Purchase update !_isSubscription "
);
continue
;
continue
;
}
}
// 🔁 去重措施 1:根据原始交易 ID 去重,防止同一笔交易被重复处理
// 🔁 去重措施 1:根据原始交易 ID 去重,防止同一笔交易被重复处理
final
transactionId
=
purchaseDetails
.
verificationData
.
serverVerificationData
.
hashCode
.
toString
();
final
transactionId
=
purchaseDetails
.
verificationData
.
serverVerificationData
.
hashCode
.
toString
();
// 如果能获取到 purchaseID 更可靠,这里用 purchaseID 作为唯一标识
// 如果能获取到 purchaseID 更可靠,这里用 purchaseID 作为唯一标识
final
uniqueId
=
purchaseDetails
.
purchaseID
;
final
uniqueId
=
purchaseDetails
.
purchaseID
;
if
(
_processedTransactionIds
.
contains
(
uniqueId
))
{
if
(
_processedTransactionIds
.
contains
(
uniqueId
))
{
// 已经处理过,直接结束交易并跳过
// 已经处理过,直接结束交易并跳过
if
(
purchaseDetails
.
status
==
PurchaseStatus
.
purchased
||
if
(
purchaseDetails
.
status
==
PurchaseStatus
.
purchased
||
purchaseDetails
.
status
==
PurchaseStatus
.
restored
)
{
purchaseDetails
.
status
==
PurchaseStatus
.
restored
)
{
_iap
.
completePurchase
(
purchaseDetails
);
_iap
.
completePurchase
(
purchaseDetails
);
}
continue
;
}
// 标记为已处理
if
(
uniqueId
!=
null
)
{
_processedTransactionIds
.
add
(
uniqueId
);
}
}
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
);
if
(
ctx
==
null
)
{
if
(
ctx
==
null
)
{
// 打印错误日志,防止崩溃
// 打印错误日志,防止崩溃
print
(
'错误:未找到 productID 对应的上下文缓存'
);
print
(
'错误:未找到 productID 对应的上下文缓存'
);
return
;
return
;
}
}
final
orderId
=
ctx
[
'orderId'
]
as
String
?;
final
orderId
=
ctx
[
'orderId'
]
as
String
?;
final
userId
=
ctx
[
'userId'
]
as
String
?;
final
userId
=
ctx
[
'userId'
]
as
String
?;
final
tbxStuId
=
ctx
[
'tbxStuId'
]
as
String
?;
final
tbxStuId
=
ctx
[
'tbxStuId'
]
as
String
?;
print
(
"orderId ==> "
+
orderId
.
toString
()+
" "
+
userId
.
toString
()
+
". "
+
tbxStuId
.
toString
());
print
(
"orderId ==> "
+
orderId
.
toString
()+
" "
+
userId
.
toString
()
+
". "
+
tbxStuId
.
toString
());
// 根据交易状态处理
// 根据交易状态处理
switch
(
purchaseDetails
.
status
)
{
switch
(
purchaseDetails
.
status
)
{
case
PurchaseStatus
.
purchased
:
case
PurchaseStatus
.
purchased
:
// 购买成功或续期成功
// 购买成功或续期成功
// 1. 验证收据(必须!)
// 1. 验证收据(必须!)
//_verifyReceipt(purchaseDetails,orderId??'',userId??'',"purchased");
//_verifyReceipt(purchaseDetails,orderId??'',userId??'',"purchased");
_handlePurchaseSuccess
(
purchaseDetails
,
orderId
??
''
,
userId
??
''
);
_handlePurchaseSuccess
(
purchaseDetails
,
orderId
??
''
,
userId
??
''
);
break
;
break
;
case
PurchaseStatus
.
error
:
case
PurchaseStatus
.
error
:
// 处理错误
// 处理错误
_handlePurchaseError
(
purchaseDetails
.
error
!);
_handlePurchaseError
(
purchaseDetails
.
error
!);
break
;
break
;
case
PurchaseStatus
.
restored
:
case
PurchaseStatus
.
restored
:
// 恢复购买成功
// 恢复购买成功
// 🔁 去重措施 2:恢复事件只处理最后一个有效的,或者收集后统一处理
// 🔁 去重措施 2:恢复事件只处理最后一个有效的,或者收集后统一处理
// 这里演示:仍然逐个验证,但后端会基于 original_transaction_id 去重
// 这里演示:仍然逐个验证,但后端会基于 original_transaction_id 去重
//_verifyReceipt(purchaseDetails,orderId??'',userId??'',"restored");
//_verifyReceipt(purchaseDetails,orderId??'',userId??'',"restored");
_handleRestoreSuccess
(
purchaseDetails
,
orderId
??
''
,
userId
??
''
);
_handleRestoreSuccess
(
purchaseDetails
,
orderId
??
''
,
userId
??
''
);
break
;
break
;
case
PurchaseStatus
.
canceled
:
case
PurchaseStatus
.
canceled
:
// 用户取消支付
// 用户取消支付
//_iap.completePurchase(purchaseDetails);
//_iap.completePurchase(purchaseDetails);
break
;
break
;
default
:
default
:
break
;
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:所有恢复交易处理完后,统一刷新一次状态(防多次通知)
// 🔁 去重措施 3:所有恢复交易处理完后,统一刷新一次状态(防多次通知)
final
hasRestored
=
purchaseDetailsList
.
any
((
d
)
=>
d
.
status
==
PurchaseStatus
.
restored
);
final
hasRestored
=
purchaseDetailsList
.
any
((
d
)
=>
d
.
status
==
PurchaseStatus
.
restored
);
if
(
hasRestored
)
{
if
(
hasRestored
)
{
// 统一通知 UI 更新,避免多次刷新
// 统一通知 UI 更新,避免多次刷新
onSuccess
?.
call
(
'购买已恢复'
);
onSuccess
?.
call
(
'购买已恢复'
);
onStateChanged
?.
call
();
onStateChanged
?.
call
();
}
}
}
}
}
}
// ---------- 服务端验证收据 ----------
// ---------- 服务端验证收据 ----------
Future
<
void
>
_verifyReceipt
(
PurchaseDetails
purchaseDetails
,
String
orderid
,
String
userid
,
String
scence
)
async
{
Future
<
bool
>
_verifyReceipt
(
PurchaseDetails
purchaseDetails
,
String
orderid
,
String
userid
,
String
scence
)
async
{
debugPrint
(
"[info] _verifyReceipt is checking 3 ["
+
scence
+
"] "
+
purchaseDetails
.
productID
+
" orderid:"
+
orderid
+
" userid:"
+
userid
);
debugPrint
(
"[info] _verifyReceipt is checking 3 ["
+
scence
+
"] "
+
purchaseDetails
.
productID
+
" orderid:"
+
orderid
+
" userid:"
+
userid
);
if
(
orderid
!=
null
&&
orderid
.
length
>
6
)
{
if
(
orderid
!=
null
&&
orderid
.
length
>
6
)
{
debugPrint
(
"[info] _verifyReceipt is start scence ["
+
scence
+
"] "
+
purchaseDetails
.
productID
+
" orderid:"
+
orderid
);
debugPrint
(
"[info] _verifyReceipt is start scence ["
+
scence
+
"] "
+
purchaseDetails
.
productID
+
" orderid:"
+
orderid
);
// 将收据发送到你的后端进行验证
// 将收据发送到你的后端进行验证
final
String
receipt
=
purchaseDetails
.
verificationData
.
serverVerificationData
;
final
String
receipt
=
purchaseDetails
.
verificationData
.
serverVerificationData
;
// 把 base64Receipt 作为 receipt-data 发给你的服务端验证
// 把 base64Receipt 作为 receipt-data 发给你的服务端验证
final
bool
isValid
=
await
_sendReceiptToBackend
(
userid
,
receipt
,
orderid
);
final
bool
isValid
=
await
_sendReceiptToBackend
(
userid
,
receipt
,
orderid
);
if
(
isValid
)
{
if
(
isValid
)
{
// 更新本地订阅状态
// 更新本地订阅状态
_hasActiveSubscription
=
true
;
_hasActiveSubscription
=
true
;
// 保存到期时间等信息
// 保存到期时间等信息
}
}
return
isValid
;
}
return
false
;
}
}
}
// 示例:发送收据到自己的服务器进行二次验证
// 示例:发送收据到自己的服务器进行二次验证
Future
<
bool
>
_sendReceiptToBackend
(
String
userid
,
String
receipt
,
String
orderid
)
async
{
Future
<
bool
>
_sendReceiptToBackend
(
String
userid
,
String
receipt
,
String
orderid
)
async
{
var
result
=
await
_userAuthRepository
.
orderCheck
(
userid
,
receipt
,
orderid
,
"ios"
,
1
);
var
result
=
_userAuthRepository
.
orderCheck
(
userid
,
receipt
,
orderid
,
"ios"
,
1
);
//var data = result['data'] as Map<String, dynamic>?;
//debugPrint(result);
debugPrint
(
"[info] _sendReceiptToBackend is done"
);
debugPrint
(
"[info] _sendReceiptToBackend is done"
);
return
true
;
return
result
!=
null
;
}
}
// ---------- 辅助方法 ----------
// ---------- 辅助方法 ----------
bool
_isSubscription
(
String
productId
)
{
bool
_isSubscription
(
String
productId
)
{
...
@@ -242,18 +240,19 @@ class SubscriptionService {
...
@@ -242,18 +240,19 @@ class SubscriptionService {
}
}
// ---------- 购买成功处理 ----------
// ---------- 购买成功处理 ----------
void
_handlePurchaseSuccess
(
PurchaseDetails
detail
,
String
orderId
,
String
userId
)
{
Future
<
void
>
_handlePurchaseSuccess
(
PurchaseDetails
detail
,
String
orderId
,
String
userId
)
async
{
// 发送收据到后端验证,携带 userId
// 发送收据到后端验证,携带 userId
final
ok
=
await
_verifyReceipt
(
detail
,
orderId
??
''
,
userId
??
''
,
"purchased"
);
_verifyReceipt
(
detail
,
orderId
??
''
,
userId
??
''
,
"purchased"
);
if
(
ok
){
_hasActiveSubscription
=
true
;
// 完成交易
if
(
detail
.
pendingCompletePurchase
){
_iap
.
completePurchase
(
detail
);
await
_iap
.
completePurchase
(
detail
);
}
// 乐观更新本地状态(等后端最终确认再真正解锁功能)
}
else
{
_hasActiveSubscription
=
true
;
onSuccess
?.
call
(
'订阅成功'
);
// 不complete
onStateChanged
?.
call
();
// 等待下次重新验证
}
}
}
// ---------- 恢复成功处理 ----------
// ---------- 恢复成功处理 ----------
...
...
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