setPurchaseListener方法中的Async方法在执行订阅时多次调用



我正在使用expo-in-app-purchases来完成我的购买。

当我第一次做测试订阅时,processNewPurchase(purchase)只调用一次(正确的行为)。但是5分钟后(因为它是一个测试订阅)订阅被取消,并再次尝试执行订阅processNewPurchase(purchase)调用两次,以此类推,(下一次是三次)。

InAppPurchases.setPurchaseListener(
({ responseCode, results, errorCode }) => {
// Purchase was successful
if (responseCode === InAppPurchases.IAPResponseCode.OK) {
results.forEach(async (purchase) => {
if (!purchase.acknowledged) {
await processNewPurchase(purchase)
// finish the transaction on platform's end
InAppPurchases.finishTransactionAsync(purchase, true)
}
})
// handle particular error codes
} else if (
responseCode === InAppPurchases.IAPResponseCode.USER_CANCELED
) {
console.log('User canceled the transaction')
} else if (responseCode === InAppPurchases.IAPResponseCode.DEFERRED) {
console.log(
'User does not have permissions to buy but requested parental approval (iOS only)'
)
} else {
console.warn(
`Something went wrong with the purchase. Received errorCode ${errorCode}`
)
}
setProcessing(false)
}
)

由于setPurchaseListener应该处于全局状态,因此我在App.js中使用<IAPManagerWrapped>

export default function App() {
return (
<ErrorBoundary onError={errorHandler}>
<Auth>
<IAPManagerWrapped>
<Provider store={store}>
<NavigationContainer ref={navigationRef}>
<StatusBar barStyle='dark-content' />
<Main navigationRef={navigationRef} />
</NavigationContainer>
</Provider>
</IAPManagerWrapped>
</Auth>
</ErrorBoundary>
)
}

在我的购买屏幕(PurchaseScreen.js)中,我使用useIAP()钩子来获取产品详细信息。

const { getProducts } = useIap()
const [subscription, setSubscription] = useState([])
useEffect(() => {
getProducts().then((results) => {
console.log(results)
if (results && results.length > 0) {
const sub = results.find((o) => o.productId === subscribeId)
if (sub) {
setSubscription(sub)
}
}
})
return () => {}
}, [])

为什么在setPurchaseListener中多次调用processNewPurchase(purchase)?

谢谢。

我在消耗性产品中遇到了同样的问题。在花费大量时间调试之后,我注意到购买处理程序只被调用一次;但在android上,它包含超过1个产品,包括列表中以前购买的产品。

在这里的官方文档中提到

https://docs.expo.dev/versions/latest/sdk/in-app-purchases/inapppurchasessetpurchaselistenercallback-result-iapqueryresponse——空白

在Android上,它将返回已完成和未完成的购买,因此是数组返回类型。这是因为Google Play计费API检测购买更新,但不区分哪个项目是刚买的,所以没有很好的方法来判断,但总的来说它将是已确认的购买设置为false,所以那些是你必须在响应中处理的。消费物品但是不会被返回,所以如果你消费了一个记录将会消失,不再出现在结果数组时,一个新的

虽然它说

消耗的物品不会返回,所以如果你消耗了一个物品该记录将消失,不再出现在结果数组中

但是我仍然在列表中获得以前购买的物品,即使它在服务器端被确认并在客户端被消费。但仍然在同一会话(应用程序没有重新启动和新的购买),它显示为acknowledged: false在数组列表中。因此,我们在代码!purchase.acknowledged中的检查没有过滤掉以前购买的商品。

results.forEach(async (purchase) => {
if (!purchase.acknowledged) {
await processNewPurchase(purchase)
// finish the transaction on platform's end
InAppPurchases.finishTransactionAsync(purchase, true)
}
})

,从代码中我们可以看到,我们的processNewPurchase函数在循环中,因此它会根据数组的长度被调用多次。

所以问题是库没有在同一会话中正确标记已确认/消费的产品,并在下次购买时将它们返回为非已确认/消费的项目(不知道它是在谷歌方面还是在库内的错误),并且我们的循环应该只运行一次,运行不止一次导致多次调用我们的成功代码。

解决方案幸运的是,在数组中,当它返回以前的购买时,它返回与每个购买相关的唯一purchaseToken,我们可以使用它们来知道哪些购买已经处理,哪些尚未处理。这是我实现的sudo代码来解决这个问题。

1. Send all the purchases to server
2. Filter the unprocessed purchase from the incoming purchases. [recorded in step 3]
3. Process the filtered unprocessed purchase and keep the record of the processed purchase to help filtering the unprocessed purchase next time.

重要提示

如果我们调用getPurchaseHistoryAsync

,每次都会调用Purchase Listener,因此它不会仅在进行新购买时调用。为了确保我们授予正确的权利,从Google服务器购买的验证是必要的。

https://developer.android.com/google/play/billing/security

最新更新