#react-native #expo #in-app-purchase
Вопрос:
Я использую expo-in-app-покупки для совершения покупок.
когда я делаю тестовую подписку в первый раз 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 amp;amp; results.length > 0) {
const sub = results.find((o) => o.productId === subscribeId)
if (sub) {
setSubscription(sub)
}
}
})
return () => {}
}, [])
Какова причина для processNewPurchase(purchase)
setPurchaseListener
многократного звонка внутрь ?
Спасибо.
Ответ №1:
Я столкнулся с той же проблемой с расходными материалами. Потратив много времени на отладку, я заметил, что обработчик покупок вызывается только один раз; Но на Android он содержит более 1 продуктов, включая предыдущие покупки в списке.
Как указано в официальных документах здесь
На Android он будет возвращать как законченные, так и незаконченные покупки, следовательно, тип возврата массива. Это связано с тем, что API выставления счетов Google Play обнаруживает обновления покупок, но не различает, какой товар был только что куплен, поэтому нет хорошего способа определить, но в целом это будет та покупка, которая была признана ложной, так что это те, которые вы должны обработать в ответе. Однако потребленные товары не будут возвращены, поэтому, если вы потребляете товар, эта запись исчезнет и больше не будет отображаться в массиве результатов при совершении новой покупки.
Хотя в нем говорится, что
Однако потребленные товары не будут возвращены, поэтому, если вы потребляете товар, эта запись исчезнет и больше не будет отображаться в массиве результатов при совершении новой покупки.
Но я все еще получал ранее купленный товар в списке, даже несмотря на то, что он был подтвержден на стороне сервера и потреблен на стороне клиента. Но все равно, когда в том же сеансе (приложение не было перезапущено и была совершена новая покупка), оно отображалось как 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
функция находится внутри цикла, поэтому она вызывается несколько раз в зависимости от длины массива.
Проблема
Таким образом, проблема в том, что библиотека неправильно помечает подтвержденные/потребленные продукты в одном сеансе и возвращает их обратно как не подтвержденные/потребленные товары при следующей покупке (не знаю, является ли это ошибкой со стороны Google или внутри библиотеки), и наш цикл, который должен был выполняться только один раз, выполняется несколько раз, что приводит к многократному вызову нашего кода успеха.
Решение
К счастью, в массиве, когда он возвращает предыдущие покупки, он возвращает связанную уникальную покупку, указанную при каждой покупке, и мы можем использовать их, чтобы узнать, какая покупка уже была обработана, а какая еще не была обработана. Вот код 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
, поэтому он не вызывается только при совершении новой покупки. Чтобы убедиться, что мы предоставляем правильные права, необходимо подтвердить покупку с серверов Google.
Комментарии:
1. Спасибо вам за подробный ответ. Однако сейчас я использую
react-native-iap
версию7.5.0
, которую можно использовать без прослушивателя покупок. В новой версии используетсяwithIAPContext
крюк. Для получения более подробной информации см. github.com/dooboolab/react-native-iap/blob/master/docs/docs/…