Open staklau opened 7 years ago
Can you explain your usecase better? What do you mean by other processing?
@chirag04 When using consumable IAP items you need to make sure the items are properly processed at your server before you charge your user in the app, the common way to do this seems to be by not calling finishTransaction until you've gotten a positive response from your server.
Hmm. Can you not make a call to your server api before calling purchase product?
@chirag04 Then you would not be able to validate the receipt and anyone could get free stuff. This would be the way to go about it:
Ok. That’s ideal. What do you propose we change here?
I was thinking it would be great to use the same concept as PushNotificationIOS. There you have to "confirm" your push notification by calling PushNotificationIOS.finish()
when you're done processing the notification.
Would it be possible to keep a reference to the current transaction being processed and instead of calling [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
immediately in case SKPaymentTransactionStatePurchased
, you would call InAppUtils.completePurchase();
when you're done?
Something like this in Objective-C:
RCT_EXPORT_METHOD(completePurchase)
{
[[SKPaymentQueue defaultQueue] finishTransaction:currentPurchaseTransaction];
}
And then you could use it like this in JavaScript:
InAppUtils.purchaseProduct(productIdentifier, (error, response) => {
if (error) {
// handle error with no completePurchase() being called
}
if(response && response.productIdentifier) {
// confirming and validating receipt with server
confirmPurchaseWithServer(response, function(err, confirmed) {
if (err) {
// handle error with no completePurchase() being called
}
if (confirmed) {
InAppUtils.completePurchase();
}
})
}
});
I tried implementing this myself but I'm not well-driven in writing Native Modules.
we cannot do [[SKPaymentQueue defaultQueue] finishTransaction:currentPurchaseTransaction];
because there can be multiple transactions going on and there are going to be race conditions.
we can store all transactions and refer to each of them using transactionId.
Also, i don't want to make a breaking change here because not everyone is using this flow.
We can add something like purchaseConsumableProduct
that takes the productId and will not call finish transaction. you will have to manually call finishTransaction
from JS with the transactionId to finish that transaction.
I won't be able to work on it but happy to accept a PR.
@chirag04 I understand. Could you then please explain why the callback in case SKPaymentTransactionStatePurchased: {
is not being called when I remove [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
? This would help get me going.
i see it's removed using [_callbacks removeObjectForKey:key];
. you can manually remove it using removeObjectForKey
Yes but I want it to be called, I don't want to remove it. So why is it not being called when I only remove [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
from the block? It is a little counter-intuitive.
@staklau check you logs if you have a warning No callback registered for transaction with state purchased
@chirag04 No that's not it. The whole SKPaymentTransactionStatePurchased: {
block of code is not fired when I remove the finishTransaction
.
that's really weird. i'm not sure what's going on here.
@chirag04 I suspect the problem with finishTransaction
not always firing has something to do with the order the callback
and finishTransaction
are being called, but I'm not sure. Anyhow, because I couldn't figure out how to call finishTransaction
outside the SKPaymentTransactionStatePurchased
-block, I decided to just put the request to my server directly in the objective-c code before calling finishTransaction
.
This is very relevant for me. I have used cordova-plugin-purchase before and they implement this behaviour by forcing the developer to call a finish()
-method on the purchase before the purchase is cleared off the payment queue.
It looks like this:
store.when("extra chapter").approved(function(product) {
// download the feature
app.downloadExtraChapter().then(function() {
product.finish();
});
});
To elaborate I have an app where users can update their subscription status by performing an in-app purchase. Sometimes an error occurs on the server, and their subscription state has not been updated, but the transaction was processed. This leads to them trying again and be charged twice (I've even had reports of triple purchases).
The correct flow would be: 1) Perform the transaction, adding to payment queue, 2) Update server-side, if error try again 3) Only on succesful server update finish transaction and remove from payment queue
It the app is closed between any of the steps, the library should try to finish any transactions in the payment queue on app start, as described In app purchase best practices.
I really hope to see this feature implemented one way or the other. Perhaps the Cordova plugin's implementation could be consulted.
Instead of calling finishTransaction immediately, would it be possible to pass it as an argument to the callback function and only call it when all other processing is done?