React-native-iap: iOS in-app purchase failure when payment method added live when there is not payment method already present

Created on 31 Oct 2018  ·  27Comments  ·  Source: dooboolab/react-native-iap

Version of react-native-iap

react-native-iap": "^2.3.17

Platforms you faced the error (IOS or Android or both?)

iOS

Expected behavior

Payment should go through only when finishTransaction is being called

Actual behavior

The amount is being detected after a payment method like credit card is added.
But the RNIap.buyProductWithoutFinishTransaction(sku) method reached.

Tested environment (Emulator? Real Device?)

Real Device - iPhone 6s

Steps to reproduce the behavior

  • Check to ensure the Apple account in the device the payments method is set to none or some invalid payment details
  • Make the in-app purchase through the app
  • User is taken to the Account settings page to add a valid payment method
  • Payment method is charged but the transaction receipt is not received in the below mentioned code
ComponentDidMount(){
     await RNIap.initConnection();
     await RNIap.consumeAllItems();
     const prod = await RNIap.getProducts(product);

} 

  async componentWillUnmount() {
       RNIap.endConnection()
}

buyProduct(sku){
await RNIap.clearTransaction();

RNIap.buyProductWithoutFinishTransaction(sku)
.then(purchase => {
  // not reached
 if(calltoserverisSuccess){
    RNIap.finishTransaction();
 }
})
.catch(error => {
 // code enters catch case if ever
}}
📱 iOS 🙏 help wanted

Most helpful comment

I think we need to contact apple for this and this looks really terrible that there is a different test case which is not reproducible in sandbox environment. For those who want to understand the problem, I've recorded the screen and you can see the clip here. @anandwahed Could you also contact apple for this? Because I know they aren't that supportive so it is better for more people to have contact with them. Let's gather up to solve this issue.

All 27 comments

Sorry to say this but your code seem to have many syntax error. Please refer to our example project in our repo and compare with your code first.

@dooboolab
Thanks for the reply, the code that is posted over here is just for an example, this is not the exact that is being used in the real application.
i will try to explain what is the issue

  1. componentDidMount

    • connection in initiated by calling RNIap.initConnection();

    • then products are fetched and store in state by calling RNIap.getProducts(product)

  1. when user click on purchase button

    • RNIap.clearTransaction(); is called just to make sure no pending transactions left.
    • RNIap.buyProductWithoutFinishTransaction(sku) is called in success case a call to application server is triggered if server gives success case true then RNIap.finishTransaction(); is called to complete the payment.
  2. componentWillUnmount()

    • RNIap.endConnection() to end connection.

This process works fine if user have already payment method added. When user don't have a payment method added it takes to adding payment method page and amount is charged before reaching the RNIap.finishTransaction();

Thanks for the detail. Your issue looks clear now. cc @JJMoon

@JJMoon

@zohaibahmed-22 Is this sandbox test case ?

@JJMoon No, its a real environment case.

I understand this error happens when the user is in logged out state, or no credit card info.
If the methods work fine, the cause of this error lies somewhere else.
We need to prepare this action, which leaves the app, viewDidDisappear etc.
In sandbox mode, this symptom happens somewhat different way.
When I make a new sandbox account, and the first purchasing doesn't work.
At the second time, the device is logged in state, and works fine.
I don't have a clue for this error.

@dooboolab and @JJMoon also i think i found the issue when going through our iOS code. In the updatedTransactions method when we receive a failure message in the SKPaymentTransactionStateDeferred or SKPaymentTransactionStateFailed we are not suppose to finishTransaction.

Since in many cases the SKPaymentTransactionStateFailed result is followed by a SKPaymentTransactionStatePurchased as mentioned in the above thread 6431. I am not sure about the Objective-C code so kindly check and confirm if we do clear the transaction on failure.

@anandwahed I agree on you. It's my fault. Sorry for that.
I will look over the Apple's thread and take care of the issue.

@anandwahed I guess it's right way to finish transaction when it's failed. Please look up this thread. https://stackoverflow.com/questions/11008636/inapp-purchase-skpaymentqueue-finish-transaction-doesnt-work
When it fails, there will be no receipt, so you don't apply the purchase product. And the finishing transaction doesn't always mean 'purchasing'.
Meanwhile, I will investigate thread 6431.

I just read the thread 6431 (https://forums.developer.apple.com/thread/6431#14562)
The bottom line. Problem not solved since 2015. Wow..
There are two different ways to handle this 'store kit flow' effect.

  1. Do not show any user alert.
  2. Show the result.

And I learned two things.
A. We have to finishTransaction when it failed. (I guess with or without option, always)
B. Storekit flow makes failed and success with receipt both. (it's bad)

I suggest you all to read this thread, and return to this issue.

I guess, which way you choose (1 or 2), it's on your own.
We might need a call back for failure response.

@JJMoon does it mean we shouldn't use buyProductWithoutFinishTransaction ?

@maxs15 I didn't mean that. Sorry for confusing.
The StoreKit flow can happen at any purchasing. It's iOS issue, not this module.
For now, I don't have any clue, either. This thing happens in any native iOS apps. Right?
We will dig more in our free time.

Today, I've tried to debug this issue because I have generated this problem. The payment finishTransanction and is completed but not getting the callback when payMethod has changed. I've tried to debug writing some console.log but I couldn't test real billing in dev environment. Could anyone suggest me how to debug this process so I can debug this for real purchase? Should I have to use this in sandbox mode? It is working perfectly in sandbox so I have no idea how to debug this. This is very reluctant.

Let's gather some ideas because I think this is very important to be fixed.

I am always getting this error when I attempt the purchase with non sandbox user.

@hyochan If you connect the real server (yours), whether you are in debug mode or release mode doesn't matter. I guess you have 2 options.

  1. You run on the real device in debug mode in Xcode. Use JS console log.
  2. You run on the real device in release mode in Xcode. Use NSLog in objective-c code.
    Both methods should work.

@JJMoon Yeah, I've figured that out already but still I couldn't make live purchase testing in ios. Is this stackoverflow true? Then how can I solve this problem? We have to test the live purchase.

Anyone who is facing this problem, I am sure all of them do, please give us an idea of how to encounter this problem. in-app purchase fail when payment method added live as described in issue title. How could I debug this? @anandwahed Did you ever contact apple about this problem?

@hyochan No we didn't contact Apple support.

I think we need to contact apple for this and this looks really terrible that there is a different test case which is not reproducible in sandbox environment. For those who want to understand the problem, I've recorded the screen and you can see the clip here. @anandwahed Could you also contact apple for this? Because I know they aren't that supportive so it is better for more people to have contact with them. Let's gather up to solve this issue.

Today we received answer from Apple. There may be callback returned again after failure. We are working on workaround solution in #348 but I am afraid this could be very nasty.

I've released to 2.4.0-beta1, trying to make a workaround for this problem. The PR #348 has been added to this version and you can also see the readme of this feature. Note that this is under testing.

I've tested this in a live purchase and seems to be working. However, keep in mind that you should only add listener when there was a failure or it may be duplicated with a successful result.

Guys, thank you for investing time in this problem! 🙏

I think the current documentation needs to be clearer around the usage of addAdditionalSuccessPurchaseListenerIOS. That's something I tried to address at #414.

But I also think this leaves room from improvement to the API. Would you guys be open to discussing a change in the API that can better accommodate the so called 'store kit flow'? Something maybe using RxJS, for example:

const observable = RNIap.buyProduct('com.example.coins100')
                        .subscribe(
                            purchase => console.log(purchase), // successful payment
                            err => console.log(err) // err.code and err.message are available
                        )

Or something different that better hides the additional subscription for iOS?

@Edgpaez That's great but that will change the behavior of the purchase being done in android. I hope I can investigate version 3 of this module in 2019.

hi @hyochan, AFAICT, we don't need to change the internal behavior of the package, only the public facing interface. We can keep the buyItemByType method resolving promises and simply add a bit of Rx on top of that on index.js.

for example, we'd have this in index.js:

export const buyProduct = (sku) => Platform.select({
  android: () => Observable.of(RNIapModule.buyItemByType(ANDROID_ITEM_TYPE_IAP, sku, null, 0)), // returns an observable that emits when the RNIapModule.buyItemByType promise resolves
  ios: ... // ios would do the same but taking into account the usage of addAdditionalSuccessPurchaseListenerIOS
})();

My goal is not this specific implementation but to hide details specific to the 'store kit flow'.
Do you think this is a good idea?
Would you be interested in a PR?

@Edgpaez Ok. I understand the detail now. However, I feel adding RxJS is bit too much for implementing the feature since I think this can be covered without it.

Also, I feel the below would be something different.

RNIap.buyProduct('com.example.coins100')
                        .subscribe(
                            purchase => console.log(purchase), // successful payment
                            err => console.log(err) // err.code and err.message are available
                        )

If you call buyProduct for two items like below,

RNIap.buyProduct('com.example.coins100');
RNIap.buyProduct('com.example.coins200');

We can't guarantee which one would finish first so I think we should handle this in native to sendEvent out to JS.

I feel like implementation should look like

RNIap.buyProduct('com.example.coins100');
RNIap.buyProduct('com.example.coins200');

// receiving events
const subs =  RNIap.purchaseUpdateListener(purchase => {
  ...
});

Please tell me if there is something I've missed.

Lets handle further disccustin in #423

Was this page helpful?
0 / 5 - 0 ratings

Related issues

curiousdustin picture curiousdustin  ·  30Comments

companytest1206 picture companytest1206  ·  34Comments

marcosmartinez7 picture marcosmartinez7  ·  32Comments

mtakac picture mtakac  ·  31Comments

the-dut picture the-dut  ·  57Comments