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

How does restorePurchases() determine the correct chargebee customer? #79

Open
ciriousjoker opened this issue Nov 29, 2023 · 16 comments
Open
Assignees

Comments

@ciriousjoker
Copy link
Contributor

As per ios sdk documentation:

let customer = CBCustomer(customerID: "Test123",firstName: "CB",lastName: "Test",email: "[email protected]")
CBPurchase.shared.restorePurchases(includeInActiveProducts: true, customer: customer) { result in
      switch result {
      case .success(let response):
        for subscription in response {
          if subscription.storeStatus.rawValue == StoreStatus.Active.rawValue{
            print("Successfully restored purchases")
          }
        }
      case .failure(let error):
        // Handle error here
        print("Error:",error)
      }
    }

The ios sdk requires a customer, but the flutter sdk doesn't. How is that possible?? If we call this function without a customer, which customer is the subscription restored to?

@ciriousjoker
Copy link
Contributor Author

Speculation:
This is kind of important, because calling this could result in a new customer being created in Chargebee because the subscription is being imported without the existing customer.

@ciriousjoker
Copy link
Contributor Author

Can someone please look at this?

@cb-haripriyan cb-haripriyan self-assigned this Dec 18, 2023
@cb-anurags
Copy link
Contributor

@ciriousjoker Thanks for raising the issue, we will fix this for flutter soon.

@ciriousjoker
Copy link
Contributor Author

ciriousjoker commented Dec 18, 2023

@cb-anurags
I have one important question about this as well:

  • We already call the store's native restorePurchases functionality on app startup. The store then probably calls the same callback that the chargebee sdk uses internally. Could this lead to any issues?

@cb-haripriyan
Copy link
Contributor

cb-haripriyan commented Dec 19, 2023

@ciriousjoker
By "call the store's native restorePurchases functionality", do you refer to the Chargebee Android and iOS SDKs' Restore purchases method (with the customer object)?
If yes, then it should pretty much work as expected.
If not, can you elaborate on the methods you are using?

@ciriousjoker
Copy link
Contributor Author

@cb-haripriyan

Im specifically talking about this function:
https://pub.dev/packages/in_app_purchase#restoring-previous-purchases

We call that package's restore purchase functionality which gives us all the receipts in a stream. This is our way to ensure that users get access to the app even chargebee has the incorrect status.

In the past we tried relying on Chargebee alone but that lead to trust issues because lots of customer complained due to incorrect data and we therefore decided to keep the fallback mechanism permanently.

@cb-haripriyan
Copy link
Contributor

@ciriousjoker That method should restore the user subscriptions and return the stream of purchase tokens/receipts.
But how do you sync these receipts to be stored in Chargebee?

@ciriousjoker
Copy link
Contributor Author

@cb-haripriyan
We don't. Currently we only store them in-memory to have a guaranteed to be accurate subscription status inside the app. Also, we store the latest receipt inside our database.

@ciriousjoker
Copy link
Contributor Author

I was just wondering if the stream is also picked up by the Chargebee sdk and since we don't pass a customer id anywhere it might break something.

@ciriousjoker
Copy link
Contributor Author

@cb-haripriyan
After the general call, iirc the conclusion was that this might be causing issues because Chargebee doesn't get the uid if the stream is triggered by us and not via the Chargebee method.
Do you have an ETA of when you know if this is causing issues and when a fix will be available?

@cb-haripriyan
Copy link
Contributor

@ciriousjoker
Looking at the implementation of the native restore method of flutter package (https://pub.dev/packages/in_app_purchase#restoring-previous-purchases), the android restore method is synchronous - That is the native method would receive the restored purchases receipt. This doesn't affect the subscription in Chargebee.
However the iOS method is async and the response of the restored purchases reach the SKPaymentTransactionObserver listener which is registered inside the Chargebee iOS SDK. Hence the internal CB SDK listener would the receipt and it tries to resolve it to the customer which wouldn't be set. This will result in the restored subscription being moved to a customer with ID which is that of the subscription ID.

We have released a patch today with the 0.4.2 version, which takes in the customer object and restores the subscriptions. Would you be able to give it a try and let us know if this works?

@ciriousjoker
Copy link
Contributor Author

ciriousjoker commented Dec 21, 2023

@cb-haripriyan perfect, thanks for the investigation and the quick fix!

We'll roll out an update over the coming days. Unfortunately it'll be hard to evaluate if the error comes back since there's no way to find affected users directly, users just contacted our support with the issue.

@ciriousjoker
Copy link
Contributor Author

@cb-haripriyan I've reviewed the v1.0.28 of the ios sdk as well as the v0.4.2 of the flutter sdk. I couldn't find any code that prevents the ios callback from being called in cases where the restorePurchases() is called via the in_app_purchase package.

What am I missing here. We cannot call Chargebee's sdk method directly, we need to call the method from in_app_purchases, at least for now. And if I understood you correctly, in those cases, on iOS (not Android), the purchase receipts are still handled by Chargebee's sdk and will be imported as new customers in Chargebee since Chargebee's restorePurchases() is never called.

@cb-haripriyan
Copy link
Contributor

@ciriousjoker The version 0.4.2 fixes the originally raised issue which was that the restorePurchases method of the Chargebee Flutter SDK doesn't allow the passing of a Customer object. Now the restorepurchase method of Chargebee takes in a customer and when it is invoked it would restore the subscriptions correctly to the customer.

Now with regards to using in_app_purchases library along side the Chargebee SDK - since both the libraries are for similar functionality, the use of one along with the other can cause effects. That is, when you call the restore purchases method of Chargebee SDK, the in_app_purchases SDK listener will also receive the events as well, since both the SDKs register their listeners on initialization. (This is behavior in iOS since the responses are handled via delegates/listeners).

Also when the restorePurchases method of Chargebee SDK is called, the in_app_purchase listener would also be called (for iOS).

A couple of workarounds I can think of right now:

  1. For Android, call restorePurchase of both SDKs (for Chargebee method, pass the customer object). For iOS, call the restorePurchase of Chargebee SDK (with customer). The in_app_purchases listener would still receive the response since the listener is already set.
  2. After calling the restorePurchase of in_app_purchases and receiving all responses, you can follow it up a Chargebe SDK call with customer object which should sync the correct state. This however would cause back and forth changes of customer ID. We are looking at ways to prevent the Customer ID change, which should atleast prevent changing the customer to a default value.

@ciriousjoker
Copy link
Contributor Author

@cb-haripriyan
The workaround seems like it should work and we'll try that.

However, one change I would suggest for the Chargebee sdk on iOS is this:

  • in restorePurchases(), set a flag to true to indicate that the function was called
  • in the stream handler, check if the flag is set to true, otherwise ignore the events in the stream

This would make the sdk more robust. Unfortunately we can't fork the iOS sdk directly, but I can create a PR nonetheless if you'd like me to.

@cb-haripriyan
Copy link
Contributor

@ciriousjoker Thanks for the input.
This is indeed one of the ways we were considering. However we would need to take into account the overall SDK and parity of features b/w ios and andriod (and not just the restorePurchase method for iOS).
We are considering few alternative approaches, which we solve all cases like these.

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

No branches or pull requests

3 participants