Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

finishTransactionIOS / finishTransaction does nothing for iOS #933

Closed
bperlman opened this issue Feb 19, 2020 · 36 comments
Closed

finishTransactionIOS / finishTransaction does nothing for iOS #933

bperlman opened this issue Feb 19, 2020 · 36 comments
Labels
🙏 help wanted Extra attention is needed 📱 iOS Related to iOS ℹ needs more info Needs more detailed information to move forward 🚶🏻 stale Stale Stale

Comments

@bperlman
Copy link

bperlman commented Feb 19, 2020

Version of react-native-iap

4.4.1

Version of react-native

0.60.4

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

iOS

Expected behavior

Upon calling finishTransactionIOS(purchaseId) or finishTransaction(purchase) for a subscription, the transaction should be finished and not be emitted to the app again on next launch. If there was a problem with this, I would expect something to be returned either to the error listener or in a promise from one of the finishTransaction functions, nothing happens.

Android seems to work fine.

Actual behavior

The transaction is emitted on every single launch unless clearTransactionIOS is called, which seems to have other side effects.

Tested environment (Emulator? Real Device?)

Real Device

Steps to reproduce the behavior

  • Create a subscription product.
  • Buy the subscription
  • Simulate backend validation
  • Call finishTransactionIOS or finishTransaction on that subscription. Has no effect.

I've tried this piles of different ways, and absolutely nothing seems to work, no matter what I do, other than clear the transactions, the purchase is again emitted to the app every single launch. Any ideas what is going on or what I could possibly be doing or understanding wrong?

@ahanusek
Copy link

I have the same issue.

@hyochan
Copy link
Member

hyochan commented Feb 22, 2020

Can you share the ackResult?

          try {
            const ackResult = await finishTransaction(purchase);
            console.log('ackResult', ackResult);
          } catch (ackErr) {
            console.warn('ackErr', ackErr);
          }

@hyochan hyochan added 🙏 help wanted Extra attention is needed 📱 iOS Related to iOS ℹ needs more info Needs more detailed information to move forward labels Feb 22, 2020
@ahanusek
Copy link

@hyochan
In my case:
ackResult undefined

@bperlman
Copy link
Author

@hyochan
In my case:
ackResult undefined

Same here

@gurol
Copy link

gurol commented Feb 23, 2020

Also in my case, the returned value is undefined (real device with a sandbox user).

@hyochan
Copy link
Member

hyochan commented Feb 23, 2020

It looks related to #366. Also for ios finishTransaction won't return anything.
Can you all focus on #366 and come back for the update?

@bperlman
Copy link
Author

It looks related to #366. Also for ios finishTransaction won't return anything.
Can you all focus on #366 and come back for the update?

Just to clarify - since #366 is about testing are you suggesting that this should work fine in production without the need for clearTransactionIOS?

@ahanusek
Copy link

@hyochan When I'm testing debug configuration with sandbox account basically I'm getting notification from Server to Server Notifications(Apple) about a purchase. The problem is that after some time my client listener gets another notification about the purchase but with different transactionId even when purchasing action happened only one time so it looks like problem with properly finish the previous transaction. On Android, everything works fine and I think that is not related to #366 because I don't have any problem to test purchasing process with a sandbox account.

useEffect(() => {
        purchaseUpdateSubscription.current = purchaseUpdatedListener(
            async (purchase: InAppPurchase | SubscriptionPurchase) => {
                const receipt = purchase.transactionReceipt;
                if (receipt) {
                    try {
                        await myBackendHandler({
                            user,
                            purchase,
                        });
                        const result = await RNIap.finishTransaction(purchase, false);
                        console.log('result', result);
                    } catch (e) {
                        // TODO
                    }
                }
            }
        );
        return () => {
            if (purchaseUpdateSubscription.current) {
                purchaseUpdateSubscription.current.remove();
                purchaseUpdateSubscription.current = null;
            }
        };
    }, []);

@dkwl93
Copy link

dkwl93 commented Mar 26, 2020

Hey team, thanks for all the hard work on this library, it really makes our lives easier. I'm commenting because this issue hasn't been resolved and I don't see how old transactions not clearing have anything to do with #366 .

My usecase is

  1. User 1 makes purchase and premium benefit applied to their account.
  2. User 1 signs out
  3. User 2 signs in

Expected result:

  • Transactions from User 1 are not emitted via the purchase listener

Actual result

  • Transactions from User 1 are NOT cleared and are emitted again. Even though a new user has signed in leading to bad data.

This is just an example of why it is important for this issue to be prioritised. Thanks for reading this long comment...

@MehmetDT
Copy link

same here

@iskenz
Copy link

iskenz commented Apr 1, 2020

exactly the same problem as above, looks like stack isnt cleared at all, even after disconnecting sandbox/itunes account, reboot device, reinstall app.. :/

@hyochan
Copy link
Member

hyochan commented Apr 1, 2020

Could you verify that the finishTransaction is really doing nothing in RNIapIos.m file?
I've just checked it out and it correctly finishes its transaction.

Please check clearTransaction or finishTransactionWithIdentifier method and try to put any logs inside. Try to find below codes.

RCT_EXPORT_METHOD(clearTransaction) {
    NSArray *pendingTrans = [[SKPaymentQueue defaultQueue] transactions];
    NSLog(@"\n\n\n  ***  clear remaining Transactions. Call this before make a new transaction   \n\n.");
    for (int k = 0; k < pendingTrans.count; k++) {
        [[SKPaymentQueue defaultQueue] finishTransaction:pendingTrans[k]];
    }
-(void)finishTransactionWithIdentifier:(NSString *)transactionIdentifier {
    SKPaymentQueue *queue = [SKPaymentQueue defaultQueue];
    for(SKPaymentTransaction *transaction in queue.transactions) {
        if([transaction.transactionIdentifier isEqualToString:transactionIdentifier]) {
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
        }
    }
}

I can't move this issue forward because I am not getting any idea what's happening. I hope somebody can debug things in their environment and share 🙏

@selimhancakir
Copy link

any update about this issue ?

@baiross
Copy link

baiross commented May 21, 2020

up, I am experiencing this problem too.

@Mitdd9707
Copy link

same here finishTransactionIOS/finishTransaction this is not working in IOS

@ixuz
Copy link

ixuz commented May 27, 2020

I seem to experience the same thing.
The purchase of IAP completes successfully, but the final call to finishTransaction returns undefined.

// Finish transaction
const ackResult = await finishTransaction(purchase);
console.log("Ack result: ", ackResult); // Ack result: undefined

Real device "iPhone 6s" using App Store Sandbox environment.

What is the expected result from finishTransaction? Is undefined a good result or should I expect something else?

@lachlanglen
Copy link

lachlanglen commented Jun 16, 2020

I am also experiencing this issue on iOS, where neither finishTransactionIOS nor finishTransaction is actually removing those transactions, which is preventing me from releasing app. Any update?

@lachlanglen
Copy link

@hyochan Though I definitely am not fluent in Objective-C, I added some logs in finishTransaction and finishTransactionWithIdentifier and it seems they are running correctly (id is being passed and matched). The only thing I can think of is that [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; isn't actually connecting to StoreKit and finishing the transaction? I don't really know where to go from here but hopefully this is helpful as you debug

@espenjanson
Copy link

espenjanson commented Jun 18, 2020

Are you also handling subscription status notifications from the App Store? In my case, I had this problem until I made sure my server handled all notification events properly and responded with a 200.

@lachlanglen
Copy link

@espenjanson Interesting. No, I wasn't. But I actually just switched to expo's InAppPurchases module yesterday and it's all working great now.

@espenjanson
Copy link

@lachlanglen Nevermind. It was just a single strike of luck (?) or something else. Built another version of the app and the problem is there again. Thinking of doing the same but a little worried that the expo repo is not very well maintained.

@juanctecdam
Copy link

I was having a look into it and it's correct that it returns undefined since the finishTransactions call is not meant to return anything in obj-c.
I've done some tests and in my case, it seems to finish them correctly(simple test, pull pendingTransactions, if there's nothing, it's working good)

@ziyoshams
Copy link

@espenjanson I don't know what makes you think that Expo packages are not well-maintained. I think Expo released their iap module pretty recently. If i knew expo had iap module at the time I was implementing iap, i would've picked expo without thinking twice.

@zatloeri
Copy link

Is there some way to solve this?
I have tested this on TestFlight even with new sandbox user and it still keeps happening.

My purchaseUpdateListener keeps receiving "old" transactions.
I am calling finishTransaction(purchase,false) as I am using autorenewable subscriptions.
The result of the call is undefined.

Can anybody offer a workaround or explanation why this is happening?

@ziyoshams
Copy link

@zatloeri That is apparently a desired behavior. It took me a while to realize that. App Store will be putting new receipt for renewal in StoreKit which triggers the observer. You should process that receipt and finish the transaction.

https://developer.apple.com/videos/play/wwdc2018/705/
https://developer.apple.com/videos/play/wwdc2020/10671

(watch in Safari for HD quality)

@zatloeri
Copy link

@ziyoshams Thanks for the quick reply and for the resources which I will look through in a bit.

But first I just want to point out one thing cause I don't think I made it apperent.
What you are describing seems logical to me and I expected this to happen, but what puzzles me is this:

Product A has expired as verified by receipt validation.
I receive one old (from previous day) transaction for product A when I start or foreground the app.
Then the next time I foreground the app again I recieve another one old (maybe a little newer then the previous, not sure) transaction for product A.
On another foreground possibly another and so on.

I would expect to get all of them on one app start if the subscription is already after its expiration.
Is this also expected behavior, or is there something fishy happening?

@ziyoshams
Copy link

@zatloeri Every transaction that is not finished will appear in that queue, even if it is expired. For monthly subscriptions you should get that receipt every 5 mins or so. Like if you purchase something and come back to the app the next day, you will get 5-6 receipts at the same time. I highly recommend watching those WWDC videos. They are so helpful.

@sufyan297
Copy link

I am having same issue on iOS, transaction is not getting finished so, Queue is not getting empty and receipt is reappearing every time whenever app launches. i have verified receipt at server side, then finished transaction. Please have a look at my code below

i have mentioned few snippets of my code below...

import RNIap, {
  InAppPurchase,
  Product,
  PurchaseError,
  Subscription,
  SubscriptionPurchase,
  finishTransaction,
  finishTransactionIOS,
  purchaseErrorListener,
  purchaseUpdatedListener,
  clearTransactionIOS,
} from 'react-native-iap';

async componentDidMount() {
  const result = await RNIap.initConnection();
  const itemSubs = Platform.select({
      android: [
          get(this.props, 'subscriptionStore.subscription.android_product_id', null)
      ],
      ios: [
          get(this.props, 'subscriptionStore.subscription.ios_product_id', null)
      ]
  });

  const subscriptions = await RNIap.getSubscriptions(itemSubs);
  purchaseUpdateSubscription = purchaseUpdatedListener(
      async (purchase) => {
          const receipt = purchase.transactionReceipt;
          let subscription = {
            subscription_id: get(this.props, 'subscriptionStore.subscription.id', null)
          };
          if (Platform.OS === 'ios') {
              subscription = {
                ...subscription,
                order_id: get(purchase, 'transactionId', null),
                purchase_token: get(purchase, 'originalTransactionIdentifierIOS', null),
                receipt: get(purchase, 'transactionReceipt', null), // (Store it in TEXT)
                os: 'IOS'
              }
          } else if (Platform.OS === 'android') {
              const data = JSON.parse(receipt);
              subscription = {
                ...subscription,
                order_id: get(data, 'orderId', null),
                purchase_token: get(data, 'purchaseToken', null),
                os: 'ANDROID'
              }
          }
          try {
              finishTransaction(purchase, false); //this is here for temp, it will be placed on success later on.
              this.props.notificationStore.showToast('Subscribed successfully. Please wait...', 'success', 5000);
              const resp = await this.props.subscriptionStore.onPurchase(subscription); //verifying transactions at server end.
              if (resp.success) { //success response
                  this.props.userStore.setUser({ is_premium: 1 });
                  this.props.navigation.replace('FixFooter');
              }
          } catch (error) {
              console.log("onPurchase API ERROR: ", error);
          }
      }
  )

  purchaseErrorSubscription = purchaseErrorListener(
      async (error) => {
          console.log('purchase error: ', error);
      }
  )
}

/* onPurchase Button */
async onPurchase() {
  try {
    const request = await RNIap.requestSubscription(Platform.OS === 'android' ?
        get(this.props, 'subscriptionStore.subscription.android_product_id', null) :
        get(this.props, 'subscriptionStore.subscription.ios_product_id', null))
  } catch (error) {
    console.log('error: ', error)
  }
}

@Allyday
Copy link

Allyday commented Sep 14, 2020

@sufyan297 can you please share a snippet from your server-side code? I'm also trying to set up a receipt verification thingy but Apple's document is extremely confusing

@bcbcbcbcbcl
Copy link

@ziyoshams Are you getting null also using expo iap module finishTransaction method?

@ziyoshams
Copy link

@bcbcbcbcbcl I am not using the expo module.

@n0umankhan
Copy link

+1 to me it returns undefined

@samson88888
Copy link

@hyochan any update on this issue? We have been been stuck for quite some time and unable to publish our app.

@stale
Copy link

stale bot commented Dec 24, 2020

Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as "For Discussion" or "Good first issue" and I will leave it open. Thank you for your contributions.

@stale stale bot added the 🚶🏻 stale Stale label Dec 24, 2020
@stale
Copy link

stale bot commented Jun 9, 2021

Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as "For Discussion" or "Good first issue" and I will leave it open. Thank you for your contributions.

@stale
Copy link

stale bot commented Jul 21, 2021

Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please feel free to create a new issue with up-to-date information.

@stale stale bot closed this as completed Jul 21, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🙏 help wanted Extra attention is needed 📱 iOS Related to iOS ℹ needs more info Needs more detailed information to move forward 🚶🏻 stale Stale Stale
Projects
None yet
Development

No branches or pull requests