在 OnPurchaseConfirmed 里立即调用 CheckEntitlement 存在竞态条件(race condition)
参看 unity 内购订阅 示例代码 BuyingSubscription.cs ,可以看到如下内容:
void OnPurchaseConfirmed(Order order)
{
CheckSubscription();
}
void CheckSubscription()
{
var product = m_StoreController.GetProducts().FirstOrDefault(p => p.definition.id == subscriptionProductId);
m_StoreController.CheckEntitlement(product);
}
void OnCheckEntitlement(Entitlement entitlement)
{
if (entitlement.Product.definition.id == subscriptionProductId)
{
switch (entitlement.Status)
{
case EntitlementStatus.FullyEntitled:
isSubscribed = true;
UpdateUI();
break;
default:
isSubscribed = false;
UpdateUI();
break;
}
}
}上面的代码 OnCheckEntitlement 函数中,entitlement.Status 不为 EntitlementStatus.FullyEntitled ,获取到到订阅信息以及是否订阅有效的数据都是不对的。原因在于 在 OnPurchaseConfirmed 里立即调用 CheckEntitlement 存在竞态条件(race condition)
Unity 官方(SashaM,Unity IAP 团队)在论坛上明确回复过一模一样的场景: 在 OnPurchaseConfirmed 回调里调用 CheckEntitlement 可能出现竞态条件,这个问题已经被记录为已知 issue,但目前没有修复时间表。
更关键的是,Unity 的另一位工程师(Yannick_D)给出了正确做法: 其实根本不需要在 OnPurchaseConfirmed 里再调用 CheckEntitlement——你已经拿到了确认(Confirmed)的订单。对于非消耗品和订阅来说,一个 ConfirmedOrder 就意味着用户已经拥有该商品了,这和你去做 FetchPurchases 拿 ConfirmedOrder,或者调用 CheckEntitlement 判断是否拥有,本质上是等价的。
也就是说,你现在的写法(收到 OnPurchaseConfirmed → 马上 CheckEntitlement(product))本身就是触发这个 bug 的标准姿势。 因此,正确的处理代码如下:
void OnPurchaseConfirmed(Order order)
{
var product = GetFirstProductInOrder(order);
if ( product == null ) {
SetCurrentState(IAPProcessState.Failure, IAP_PRODUCT_NOT_FOUND);
OnIAPError?.Invoke(IAP_PRODUCT_NOT_FOUND);
return;
}
if (order is ConfirmedOrder confirmedOrder)
{
string productId = product.definition.id;
if (productId == subscriptionProductId)
{
isSubscribed = true;
SubInfo = GetBestSubscriptionFromOrder(confirmedOrder);
}
SetCurrentState(IAPProcessState.Success);
}
else if (order is FailedOrder failedOrder)
{
string reason = $"{failedOrder.FailureReason}: {failedOrder.Details}";
SetCurrentState(IAPProcessState.Failure, reason);
OnIAPError?.Invoke(reason);
}
else
{
string msg = $"Unexpected order type in OnPurchaseConfirmed: {order?.GetType().Name}";
SetCurrentState(IAPProcessState.Failure, msg);
OnIAPError?.Invoke(msg);
}
}
SubscriptionInfo GetBestSubscriptionFromOrder(ConfirmedOrder confirmedOrder)
{
return confirmedOrder.Info?.PurchasedProductInfo?
.FirstOrDefault()?.subscriptionInfo;
}这样 OnPurchaseConfirmed 这条路径就完全不依赖 CheckEntitlement,自然也就绕开了 Unity 官方确认的那个竞态 bug。CheckEntitlement 只保留在启动时 OnPurchasesFetched/RestoreTransactions 之后去做批量刷新就够了。


