Question

Synchronize purchases with own backend best practices

  • 20 January 2023
  • 2 replies
  • 567 views

Badge +4

We have a special business/premium model, and we need help migrating our model to a flutter app which uses IAP.

Currently, our system works as follows:

Users can purchase "packages" through Stripe. Each package contains one or more "digital accesses" and we send a package redemption ID via email. On our site, users can redeem the package (once they receive the email), and we credit the digital accesses to their account.

Digital access has the following properties:

moduleName - The user gets access to this module
durationInDay - From the redemption, this will be the duration.
 

I think the following would be the best way to extend our business model with IAP:

We consider every successful subscription-related transaction as a package purchase, but this time the user does not need to redeem the package. Instead, we directly credit the package to their account. We set the appUserId to our user's unique identifier (UUID) for every user.

There should be a Revenue Cat endpoint where we can query all previous transactions of a subscriber (every transaction should have a unique ID), and we can sync the payments. When we get the transactions, we find those that have not been synced in our database. Then, we credit the package to the last seen appUserId and store it in a <transactionId, appUserId> format in the database. Let's call this mechanism "sync-with-rc" on our end.

We also need another endpoint, "restore-purchase," on our end. This endpoint queries all previous transactions of a user from Revenue Cat and finds active subscriptions.
After that we revoke access based on <transactionId,appUserId> pair, and set the new app user id for transaction.

 

Purchase: 
1. call purchaseProduct of SDK
2. call syncPurchases of SDK (send the new receipt to RC)
3. then call sync-with-rc endpoint

Restore purchase:
1. call restorePurchase of SDK (this will sync receipt and transfer the subscription in RC if needed)
2. call restore-purchase endpoint

Renewal:​​​​​​​

We can receive this event via webhooks and credit the package to the last seen appUserId. If our server is not available (therefore, the package is not credited), the user can restore their purchase using the described method. 

Unfortunately we cannot do that, because the /subscribers endpoint does not return the user’s transactions.

 

This is the way what we found to solve this problem:

Use webhooks.

Purchase:
1. call purchaseProduct of SDK
2. RC call our endpoint (webhook), and we credit the package directly to the user.

Restore:
1. call restorePurchase of SDK (this will generate a webhook event if a transfer happened)
2. We revoke accesses (with transferred_from), and credit package to user ids. (transferred_to). Btw I don’t really understand, on what transferred_from and transferred_to values depend? 

Renewal:
1. RC call our endpoint, and simply credit the package to user


Everything seemed to be fine until our server not receive the RC webhook. (for any reason, network, maintenance)
When a user makes a purchase and our server is not available, the user is unable to restore their purchase because in this "webhook way", the restore only works if the platform user has a subscription under another app user ID. (This will generate a transfer event) but in this case, we are simply out of sync.

 

As you can see I’m trying my best, but I don’t know how to deal with this problem correctly.
I would gladly accept any help or guidance.


2 replies

Userlevel 5
Badge +9

Hello,

Looks like you’ve thought about this thoroughly, I’ll just answer the few missing parts and questions:

Unfortunately we cannot do that, because the /subscribers endpoint does not return the user’s transactions.

 

This is true (RevenueCat returns the most recent purchases, not all renewals, cancellations, etc.) This is something we’re actively working on. At the current moment, your two options are to either use webhooks like you described, or use a consumable product. RevenueCat returns a list of all purchases of consumable products in the current API, but the tradeoff here is that you have to keep track of the purchase dates so you can compute expiration dates and consumables disappear from the receipt, which can make restorePurchases more difficult.

Btw I don’t really understand, on what transferred_from and transferred_to values depend? 

 

Transfer webhooks happen when one user ID tries to restore a receipt to RevenueCat that already belongs to another user ID. In this case we will transfer the receipt from the other user ID (transferred_from) to the user ID that is restoring (transferred_to). We also return both user IDs aliases so you can update your database accordingly (in case you stored the purchase under a user ID’s alias.)

Everything seemed to be fine until our server not receive the RC webhook. (for any reason, network, maintenance)

 

We really understand the issues that this can cause. In terms of outages and maintenance, RevenueCat will replay webhooks whenever possible. So in most worst case scenarios webhooks will be delayed, not completely dropped. However, there are ways to get your database back in sync with RevenueCat if the worst happens:

  1. You can use our ETL exports, which are data exports containing all of your users’ purchases.
  2. You can use customer list exports, which are data exports containing all of your users (or any subset, for example only users who made purchases today.)
  3. Our API will return the latest status of a users’ purchases. If the disruption is brief, you likely won’t miss much if you simply call our API to resync the latest status, which is what you would have missed during such a scenario.

I hope that helps but let me know if you have any other questions!

 

Badge +4

@sharif 

From the doc:

“The default behavior is to transfer purchases between identified App User IDs if needed. This ensures that the customer restoring gets access to any purchased content, but only one customer at a time can have access. For example, if UserA buys a subscription, then UserB logs into your app on the same device and restores transactions, UserB would now have access to the subscription and it would be revoked from UserA.”

The documentation says “ only one customer at a time can have access”, therefore I don’t understand why transferred_to and transferred_from has multiple values. I can imagine that I get all previous alias in transferred_from (for some unknown reason), but still don’t have any idea about transferred_to.

Could you explain to me through some examples why these fields are array? 

Reply