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

Listen for purchase events + optional promise support + other updates #128

Open
wants to merge 38 commits into
base: master
Choose a base branch
from

Conversation

superandrew213
Copy link
Contributor

@superandrew213 superandrew213 commented Oct 10, 2017

Updates:

  • Listen for purchase events initiated from App Store and subscription renewals
  • Support promises
  • canMakePurchase returns error arg too now *
  • Always return error object for errors *
  • Added helpers shouldFinishTransactions, finishCurrentTransaction and clearCompletedTransactions. See readme for more info.
  • Call addTransactionObserver after js code initialises *

* breaking change

To install and test:

npm i --save https://github.com/superandrew213/react-native-in-app-utils#listen-for-purchase-event

To trigger purchase initiated from App Store, open this link in safari. Replace bundleId and productIdentifier with your own.

itms-services://?action=purchaseIntent&bundleId=com.example.app&productIdentifier=product_name

@jeremyhicks
Copy link

jeremyhicks commented Oct 12, 2017

@superandrew213 I tested on the device for In-App purchases using the itms link and it works great. Thanks for also setting up index.js that makes it easier to use. I'm actually submitting to the App Store tonight.

@chirag04
Copy link
Owner

awesome! i will find some time over the weekend for this.

@jeremyhicks
Copy link

@superandrew213 and @chirag04 I discovered an issue. My app has a Keyboard Extension target that bundles and runs the RN app. The extension crashes with this updated code in place. I have more discovery to do but wanted to let you know. I do not use this package in the code running inside of the extension, that is not allowed anyway, but having it linked and available is causing it to crash.

@superandrew213
Copy link
Contributor Author

@jeremyhicks what is the error output?

@jeremyhicks
Copy link

@superandrew213 I wasn't getting any in the console. I need to explore further. When opening the Keyboard Extension it would crash. Debugging that is not easy.

@jeremyhicks
Copy link

@superandrew213 @chirag04 I have found the source of the issue when this library is included in a project that gets bundled in an iOS extension. The libInAppUtils.a library still needs to be included even though the extension itself doesn't support IAP. I've updated my apps to use this branch and it is working good, no crashes.

@superandrew213
Copy link
Contributor Author

@chirag04 will you have some time soon to take this in soon? I've had it in a production app for over 3 weeks now and have had no issues.

@chirag04
Copy link
Owner

chirag04 commented Nov 5, 2017

@superandrew213 sorry i haven't had a chance to look at it yet. i'm still planning to review and merge this soon.

@superandrew213
Copy link
Contributor Author

@chirag04 sure no problem!

@merryejik
Copy link

Hi! Any changes here?

@merryejik
Copy link

merryejik commented Nov 30, 2017

@superandrew213 I started test your solution, but this link don't work for me itms-services://?action=purchaseIntent&bundleId=com.example.app&productIdentifier=product_name (with my bundleId and productIdentifier, for purchase testing).

I use your branch and subscribe on events as written in your readme.

@superandrew213
Copy link
Contributor Author

@merryejik what happens when you press the link? Does it open your app?

@merryejik
Copy link

No...

@superandrew213
Copy link
Contributor Author

Hmm there must be something wrong with your bundleId.

Paste this in your safari browser on your phone and make sure you have the right bundleId. Just to trigger app to open productIdentifier doesn't matter as long as you have a value.

itms-services://?action=purchaseIntent&bundleId=com.example.app&productIdentifier=product_name

@merryejik
Copy link

Safari wants to open this link in iTunes and opens nothing after that. I will investigate SKPaymentTransactionObserver more carefully. Maybe there is need to be some changes in Info.plist or in AppDelegate?

@raduflp
Copy link

raduflp commented Dec 29, 2017

would love to see this merged

…se-event

# Conflicts:
#	InAppUtils/InAppUtils.m
index.js Outdated
? InAppUtils.loadProducts(products, cb)
: promisify(InAppUtils.loadProducts)(products),

canMakePayments: cb => cb
Copy link

@joserocha3 joserocha3 Jan 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@superandrew213 - If canMakePayments returns true, your promisfy function will reject the promise. You can create a special function just for canMakePayments like this one:

const promisifyBool = fn => (...args) => new Promise((resolve, reject) => {
    fn(...args, res => res === true || false ? resolve(res) : reject(res))
})

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about this:

const promisify = fn => (...args) => new Promise((resolve, reject) => {
  fn(...args, (err, res) => {
    if (err !== undefined && err instanceof Error) reject(err);
    // If only one argument is given and it's not an error
    if (err !== undefined && res === undefined) resolve(err);
    resolve(res);
  });
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! Thanks for catching that!

Copy link

@joserocha3 joserocha3 Jan 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@superandrew213 just realized that err is not always of type Error. When calling restorePurchases if the user cancels, err comes back as a string "restore_failed". Seems the rejection types are bit inconsistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm ... it would be best if we enforce an error type. We can use callback(@[RCTMakeError(@"restore_failed", nil, nil)]); where restore_failed would be the error message. This will be a breaking change though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have to do a breaking change, lets keep it simple and just change canMakePayments to canMakePayments(err, res). @chirag04 what do you think? It would also be good to use error objects too.

@superandrew213 superandrew213 changed the title Listen for purchase events + optional promise support Listen for purchase events + optional promise support + other updates Jan 2, 2018
@Dexwell
Copy link

Dexwell commented Dec 6, 2018

@superandrew213 Those latest additions are fantastic. Is there a way to get unfinished purchases, e.g. on start or resume, so I can unlock stuff server-side, and then call InAppUtils.finishCurrentTransaction()?

Since this package is on life support, have you considered publishing and maintaining your fork as your own package on NPM, e.g. as react-native-in-app-purchases?

@superandrew213
Copy link
Contributor Author

@Dexwell that's plan. Just haven't had any time. So it has been easier & faster to just use github fork for now.

Have you tried InAppUtils.addListener('purchaseCompleted', purchase => ...) on app start?

Or do you mean for purchases that are still in purchasing state?

@Dexwell
Copy link

Dexwell commented Dec 6, 2018

Or do you mean for purchases that are still in purchasing state?

Yeah

@chirag04
Copy link
Owner

@superandrew213 This PR is looking great! I won't have the time review it further or merge. Would you be interested in managing this library?

@superandrew213
Copy link
Contributor Author

@chirag04 I just don't have the time to maintain it. I am just adding the features that I need.

It might not be best to merge this since it is a breaking change. If we will be doing a breaking change then we should consider using events (while still supporting callbacks if possible). This PR is heading towards that direction.

I will continue working on it. When it is ready, then we can release it as v7.

@pennersr
Copy link
Contributor

With these changes I am able to catch auto renewable subscriptions on application startup just fine by listening for the purchaseCompleted event. However, this event is also emitted for regular purchases (as in, purchases that are user initiated through purchaseProduct).

@superandrew213 Is there some way of distinguishing the automatic events from the user initiated ones?

@superandrew213
Copy link
Contributor Author

@pennersr you can track the purchases that have been processed by using the transactionId.

Otherwise you could remove the listener as soon as you have caught the subscription purchase initiated from the AppStore. You could also set a timeout incase there are no purchases.

@pennersr
Copy link
Contributor

pennersr commented Jan 10, 2019

@superandrew213 I am not sure how you would use transactionId reliably, as it assumes a certain ordering. For example, when the user initiates InAppUtils.purchaseProduct() and a transaction ID is received from it, one cannot be certain that the purchaseCompleted event did not already emit that very same transaction ID first, unless, ofcourse, we can rely on ordering here. Using timeouts or unsubscribing feels a bit kludgy.

@superandrew213
Copy link
Contributor Author

@pennersr the callback will be triggered first.

@Eyesonly88
Copy link

Hey @superandrew213, thank you for all your work. I'm wondering, what's left on this PR to get it merged? and if this is not going to be merged and stay here, how would someone use it (since you mention there are breaking changes)?
Any docs would be appreciated.

@superandrew213
Copy link
Contributor Author

@Eyesonly88 you can install it like this:

yarn add https://github.com/superandrew213/react-native-in-app-utils#listen-for-purchase-event

or

npm i https://github.com/superandrew213/react-native-in-app-utils#listen-for-purchase-event

The docs on my fork tell you how to use it. I have kept them updated.

The only breaking changes are related to how errors are handled. My fork returns error objects instead of just a string.

@thanakij
Copy link

thanakij commented Apr 3, 2021

Just came across this, @superandrew213 so while we are waiting for v7.

How about changing your default branch in your repo from master to listen-for-purchase-event for now.
This would definitely help improve the visibility of your doc (especially, on the listener part).

@superandrew213
Copy link
Contributor Author

@thanakij done 🙂

@caifu23
Copy link

caifu23 commented Nov 3, 2021

In-app purchase, after the product has been purchased and paid for. I didn't call finishTransaction to end the transaction. Why can't I get the pending transaction the next time I come in and print an empty array

@caifu23
Copy link

caifu23 commented Nov 3, 2021

` // to purchase product
InAppUtils.shouldFinishTransactions(false);

        InAppUtils.purchaseProduct(productIdentifier, (error, response) => {

          // NOTE for v3.0: User can cancel the payment which will be available as error object here.

          if (response && response.productIdentifier) {

            //unlock store here. transactionReceipt
          } else {

          }
          if (error) {

            this.setState({ showLoading: false }, () => {

              console.log(error.message)
            });
          }
        });

// in app componentDidMount use
// InAppUtils.restorePurchases or InAppUtils.getPurchaseTransactions
//and then response is []
`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet