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

iOS doesn't recognise purchase after closing/reopening the app #1606

Open
kreso22 opened this issue Sep 25, 2024 · 11 comments
Open

iOS doesn't recognise purchase after closing/reopening the app #1606

kreso22 opened this issue Sep 25, 2024 · 11 comments

Comments

@kreso22
Copy link

kreso22 commented Sep 25, 2024

Observed behavior

Making a purchase works (product is owned).
Next app load - receipt shows product is not owned.
Only after hitting restorePurchase does the receipt show the product as owned

Not using validation.
App has one single product.

Expected behavior

Product owned persists.

Code

Currently forcing the restorePurchases() on random event (when app starts). Otherwise, product will be shown as not owned.


		// Register the product
		CdvPurchase.store.register([{
			id: PROD_ID,
			platform: PLATFORM,
			type: CdvPurchase.ProductType.NON_CONSUMABLE
		}]);

		// Handle product updates
		CdvPurchase.store.when().productUpdated(product => {
			if(product.id == PROD_ID) {
				processIAP(product.owned);			
			}
		});

		CdvPurchase.store.when().receiptUpdated(localReceipt => {
			const product = CdvPurchase.store.get(PROD_ID);
			
			if (product) {
				processIAP(product.owned);
			}
		});

		CdvPurchase.store.when().receiptsReady(() => {
			MyDebug.log("STORE", "receiptsReady! ...");			
			CdvPurchase.store.restorePurchases();
		});

		
		CdvPurchase.store.when().approved(transaction => {
			processIAP(true);
			transaction.finish();
		});


		CdvPurchase.store.initialize([
			{
				platform: PLATFORM,
				options: { needAppReceipt: false }
			}
		]);

	}


initiatePurchase() {
		CdvPurchase.store.get(PROD_ID).getOffer().order().then(error => {
				if (error) {
					// failed to buy
					alert("Purchase not successful. Error:" + error.code);
				}
			});
	}
@j3k0
Copy link
Owner

j3k0 commented Oct 2, 2024

I understand it's a workaround but do not do this:

CdvPurchase.store.when().receiptsReady(() => {
	CdvPurchase.store.restorePurchases();
});

As mentioned in the documentation, "restorePurchases" should only be called when the user clicks the "restore purchases" button. This call will ask for the user appstore password (except if they already logged in in the last 15 minutes, like just after downloading the app).

Can you share the startup logs when the non consumable is owned (without that call)?

@kreso22
Copy link
Author

kreso22 commented Oct 3, 2024

I understand it's a workaround but do not do this:

CdvPurchase.store.when().receiptsReady(() => {
	CdvPurchase.store.restorePurchases();
});

As mentioned in the documentation, "restorePurchases" should only be called when the user clicks the "restore purchases" button. This call will ask for the user appstore password (except if they already logged in in the last 15 minutes, like just after downloading the app).

Can you share the startup logs when the non consumable is owned (without that call)?

Sure!

Here is the related Xcode startup log:

[CDVTimer][console] 0.012994ms
[CDVTimer][handleopenurl] 0.012040ms
[CDVTimer][intentandnavigationfilter] 0.587940ms
[CDVTimer][gesturehandler] 0.012994ms
[CDVTimer][applovinmax] 0.067949ms
[CDVTimer][inappbrowser] 0.017047ms
[CdvPurchase.AppleAppStore.objc] Initialized.
[CDVTimer][inapppurchase] 10.787964ms
[CDVTimer][screenedgesplugin] 0.082016ms
[CDVTimer][cdvwkwebviewfilexhr] 49.003005ms
[CDVTimer][socialsharing] 0.018001ms
[CDVTimer][TotalPluginStartup] 60.752034ms
[CdvPurchase.AppleAppStore.objc] (before init): WARNING: Your app should be single page to use in-app-purchases. onReset is not supported.
Create CdvPurchase...

And from inside the app once started:

[STORE] - "initializeStore" - "CVD Platform:" - "ios-appstore" - "Product name:" XXXX
[STORE] - "[productUpdated]. product:" - SKProduct {className: "Product", title: "Remove ads", description: "No more ads", ...
[STORE] - "Processing ownership. Owned:" - false
[STORE] - "[receiptsReady]."
[STORE] - "[receiptUpdated]. localreceipt:" - SKApplicationReceipt {className: "Receipt", transactions: [1, platform: "ios-appstore", ...}
[STORE] - "Processing ownership. Owned:" - false
[STORE] - "[receiptUpdated]. localreceipt:" - Receipt {className:
"Receipt", transactions: [1, platform: "ios-appstore", ...}
[STORE] - "Processing ownership. Owned: " - false

The first receiptUpdated is the app. Second receiptUpdate looks empty (no transactions, nothing ...).

On each receiptUpdate I check if it is for desired product (I have only 1 in my app) like so:


CdvPurchase.store.when().receiptUpdated(localReceipt => {

     log("STORE", "[receiptUpdated]. localreceipt:", localReceipt);

     const product = CdvPurchase.store.get(PROD_ID);
			
     if (product) {
          processOwnership(product.owned);
     }
});

@j3k0
Copy link
Owner

j3k0 commented Oct 6, 2024

Sorry the logs do not contain enough information to see what happens. Can you please include the content of the localReceipt? Set logs level to debug CdvPurchase.store.verbosity = CdvPurchase.LogLevel.DEBUG and include everything in logs that contain the "CdvPurchase" tag.

@kreso22
Copy link
Author

kreso22 commented Oct 6, 2024

No problem! Here is the log with DEBUG verbosity.
I have redacted actual receipt, and product and app ids.

[CdvPurchase.AppleAppStore.objc] Initialized.
[CdvPurchase.AppleAppStore.objc] (before init): WARNING: Your app should be single page to use in-app-purchases. onReset is not supported.
Create CdvPurchase...
[CdvPurchase] INFO: initialize([{"platform":"ios-appstore","options":{"needAppReceipt":false}}]) v13.11.1
[CdvPurchase.Adapters] INFO: Adding platforms: [{"platform":"ios-appstore","options":{"needAppReceipt":false}}]
[CdvPurchase.Adapters] INFO:
[CdvPurchase.Adapters] INFO: AppStore initializing...
[CdvPurchase.AppleAppStore] INFO: bridge.init
[CdvPurchase.AppleAppStore.objc] setup: OK
[CdvPurchase.AppleAppStore.Bridge] DEBUG: setup ok
[CdvPurchase.AppleAppStore] INFO: ready
[CdvPurchase.AppleAppStore] INFO: bridge.init done
[CdvPurchase.AppleAppStore.objc] canMakePayments: Device can make payments.
[CdvPurchase.Adapters] INFO: AppStore initialized.
[CdvPurchase.Adapters] INFO: AppStore products: [{"id":"com.my.productid","platform":"ios-appstore","type":"non consumable"}]
[CdvPurchase.AppleAppStore] INFO: bridge.load
[CdvPurchase.AppleAppStore.Bridge] DEBUG: load ["com.my.productid"]
[CdvPurchase.AppleAppStore.objc] load: Getting products data
[CdvPurchase.AppleAppStore.objc] load: Set has 1 elements
[CdvPurchase.AppleAppStore.objc] load:  - com.my.productid
[CdvPurchase.AppleAppStore.objc] load: Starting product request...
[CdvPurchase.AppleAppStore.objc] load: Product request started
[CdvPurchase.AppleAppStore.Bridge] DEBUG: processing pending transactions
[CdvPurchase.AppleAppStore.objc] processPendingTransactionUpdates
[CdvPurchase.AppleAppStore] DEBUG: loading appstore receipt...
[CdvPurchase.AppleAppStore.Bridge] DEBUG: loading appStoreReceipt
[CdvPurchase.AppleAppStore.objc] appStoreReceipt:
[CdvPurchase.AppleAppStore.Bridge] DEBUG: infoPlist: com.my.appid,1.1.60,0,????
[CdvPurchase.AppleAppStore] DEBUG: appstore receipt loaded
[CdvPurchase.AdapterListener] DEBUG: receiptsReady: ios-appstore (1/0)
[CdvPurchase.AppleAppStore] DEBUG: receipt updated and ready.
[CdvPurchase.AdapterListener] DEBUG: receiptsUpdated: [{"className":"Receipt","transactions":[],"platform":"ios-appstore","nativeData":{"appStoreReceipt":"MII...Q==","bundleIdentifier":"com.my.appid","bundleShortVersion":"1.1.60","bundleNumericVersion":0,"bundleSignature":"????"}},{"className":"Receipt","transactions":[],"platform":"ios-appstore"}]
[CdvPurchase] DEBUG: Calling callback: type=receiptUpdated() name=#5121f05a236978d1ebad4e26ac18ce54 reason=adapterListener_receiptsUpdated
[CdvPurchase] DEBUG: Calling callback: type=receiptUpdated() name=#5121f05a236978d1ebad4e26ac18ce54 reason=adapterListener_receiptsUpdated
[CdvPurchase.AppleAppStore.objc] BatchProductsRequestDelegate.productsRequest:didReceiveResponse:
[CdvPurchase.AppleAppStore.objc] BatchProductsRequestDelegate.productsRequest:didReceiveResponse: Has 1 validProducts
[CdvPurchase.AppleAppStore.objc] BatchProductsRequestDelegate.productsRequest:didReceiveResponse:  - com.my.productid: Remove ads
[CdvPurchase.AppleAppStore.objc] BatchProductsRequestDelegate.productsRequest:didReceiveResponse: sendPluginResult: (
        (
                {
            billingPeriod = 0;
            billingPeriodUnit = Day;
            countryCode = US;
            currency = USD;
            description = "No more ads";
            discounts =             (
            );
            group = "<null>";
            id = "com.my.productid";
            introPrice = "<null>";
            introPriceMicros = "<null>";
            introPricePaymentMode = "<null>";
            introPricePeriod = "<null>";
            introPricePeriodUnit = "<null>";
            price = "$1.99";
            priceMicros = 1990000;
            title = "Remove ads";
        }
    ),
        (
    )
)
[CdvPurchase.AppleAppStore.Bridge] DEBUG: load ok: { valid:[{"id":"com.my.productid","description":"No more ads","introPrice":null,"introPricePaymentMode":null,"billingPeriodUnit":"Day","countryCode":"US","introPricePeriodUnit":null,"discounts":[],"title":"Remove ads","price":"$1.99","billingPeriod":0,"group":null,"priceMicros":1990000,"currency":"USD","introPricePeriod":null,"introPriceMicros":null}] invalid:[] }
[CdvPurchase.AppleAppStore] INFO: bridge.loaded: {"validProducts":[{"id":"com.my.productid","description":"No more ads","introPrice":null,"introPricePaymentMode":null,"billingPeriodUnit":"Day","countryCode":"US","introPricePeriodUnit":null,"discounts":[],"title":"Remove ads","price":"$1.99","billingPeriod":0,"group":null,"priceMicros":1990000,"currency":"USD","introPricePeriod":null,"introPriceMicros":null}],"invalidProducts":[]}
[CdvPurchase.AppleAppStore] DEBUG: load eligibility: [{"id":"com.my.productid","description":"No more ads","introPrice":null,"introPricePaymentMode":null,"billingPeriodUnit":"Day","countryCode":"US","introPricePeriodUnit":null,"discounts":[],"title":"Remove ads","price":"$1.99","billingPeriod":0,"group":null,"priceMicros":1990000,"currency":"USD","introPricePeriod":null,"introPriceMicros":null}]
[CdvPurchase.AppleAppStore] DEBUG: No discount eligibility determiner, skipping...
[CdvPurchase.AppleAppStore] INFO: eligibilities ready: {"request":[],"response":[]}
[CdvPurchase.AppleAppStore] DEBUG: com.my.productid is valid: {"id":"com.my.productid","description":"No more ads","introPrice":null,"introPricePaymentMode":null,"billingPeriodUnit":"Day","countryCode":"US","introPricePeriodUnit":null,"discounts":[],"title":"Remove ads","price":"$1.99","billingPeriod":0,"group":null,"priceMicros":1990000,"currency":"USD","introPricePeriod":null,"introPriceMicros":null}
[CdvPurchase.AppleAppStore] DEBUG: registering new product
[CdvPurchase.AppleAppStore] DEBUG: Products loaded: [{"className":"Product","title":"Remove ads","description":"No more ads","platform":"ios-appstore","type":"non consumable","id":"com.my.productid","offers":[{"className":"Offer","id":"$","pricingPhases":[{"price":"$1.99","priceMicros":1990000,"currency":"USD","paymentMode":"UpFront","recurrenceMode":"NON_RECURRING"}],"productId":"com.my.productid","productType":"non consumable","platform":"ios-appstore","offerType":"Default"}],"raw":{"id":"com.my.productid","description":"No more ads","introPrice":null,"introPricePaymentMode":null,"billingPeriodUnit":"Day","countryCode":"US","introPricePeriodUnit":null,"discounts":[],"title":"Remove ads","price":"$1.99","billingPeriod":0,"group":null,"priceMicros":1990000,"currency":"USD","introPricePeriod":null,"introPriceMicros":null},"countryCode":"US"}]
[CdvPurchase.Adapters] INFO: AppStore products loaded: [{"className":"Product","title":"Remove ads","description":"No more ads","platform":"ios-appstore","type":"non consumable","id":"com.my.productid","offers":[{"className":"Offer","id":"$","pricingPhases":[{"price":"$1.99","priceMicros":1990000,"currency":"USD","paymentMode":"UpFront","recurrenceMode":"NON_RECURRING"}],"productId":"com.my.productid","productType":"non consumable","platform":"ios-appstore","offerType":"Default"}],"raw":{"id":"com.my.productid","description":"No more ads","introPrice":null,"introPricePaymentMode":null,"billingPeriodUnit":"Day","countryCode":"US","introPricePeriodUnit":null,"discounts":[],"title":"Remove ads","price":"$1.99","billingPeriod":0,"group":null,"priceMicros":1990000,"currency":"USD","introPricePeriod":null,"introPriceMicros":null},"countryCode":"US"}]
[CdvPurchase.Adapters] INFO: AppStore receipts loaded: [{"className":"Receipt","transactions":[],"platform":"ios-appstore","nativeData":{"appStoreReceipt":"MII...Q==","bundleIdentifier":"com.my.appid","bundleShortVersion":"1.1.60","bundleNumericVersion":0,"bundleSignature":"????"}},{"className":"Receipt","transactions":[],"platform":"ios-appstore"}]
[CdvPurchase.AdapterListener] DEBUG: setSupportedPlatforms: ios-appstore (1 have their receipts ready)
[CdvPurchase.AdapterListener] DEBUG: triggering receiptsReady()
[CdvPurchase] DEBUG: Calling callback: type=productUpdated() name=#3532c2af0630224c668390e441c804b7 reason=adapterListener_productsUpdated
[CdvPurchase] DEBUG: Calling callback: type=receiptsReady() name=receiptsMonitor_setup reason=adapterListener_setSupportedPlatforms
[CdvPurchase.ReceiptsMonitor] DEBUG: receiptsReady...
[CdvPurchase] DEBUG: Calling callback: type=receiptsReady() name=#62332256874e449c2d0c447e06ef51b9 reason=adapterListener_setSupportedPlatforms
[CdvPurchase.ReceiptsMonitor] DEBUG: check(0/0)
[CdvPurchase.ReceiptsMonitor] INFO: receiptsVerified()

And here is what I see after I press RESTORE PURCHASE:

[CdvPurchase.AppleAppStore.objc] paymentQueue:updatedTransactions: com.my.productid
[CdvPurchase.AppleAppStore.objc] paymentQueue:updatedTransactions: State: PaymentTransactionStateRestored
[CdvPurchase.AppleAppStore.objc] processTransactionUpdate:withArgs: transactionIdentifier=2000000734344505
[CdvPurchase.AppleAppStore.objc] paymentQueueRestoreCompletedTransactionsFinished:
[CdvPurchase.AppleAppStore.Bridge] DEBUG: transaction updated:2000000734344505 state:PaymentTransactionStateRestored product:com.my.productid
[CdvPurchase.AppleAppStore] INFO: restore: 2000000734344505 - com.my.productid
[CdvPurchase.AppleAppStore] DEBUG: initializeAppReceipt() => already initialized.
[CdvPurchase.AppleAppStore] INFO: restoreCompleted
[CdvPurchase.AppleAppStore.Bridge] DEBUG: refreshing appStoreReceipt
[CdvPurchase.AppleAppStore.objc] appStoreRefreshReceipt: Request to refresh app receipt
[CdvPurchase.AppleAppStore.objc] appStoreRefreshReceipt: Starting receipt refresh request...
[CdvPurchase.AppleAppStore.objc] appStoreRefreshReceipt: Receipt refresh request started
[CdvPurchase.AppleAppStore.objc] RefreshReceiptDelegate.requestDidFinish: Got refreshed receipt
[CdvPurchase.AppleAppStore.objc] RefreshReceiptDelegate.requestDidFinish: Send new receipt data
[CdvPurchase.AppleAppStore.Bridge] DEBUG: infoPlist: com.my.appid,1.1.60,0,????
[CdvPurchase.AppleAppStore] INFO: receiptsRefreshed
[CdvPurchase.AppleAppStore] DEBUG: receipt updated and ready.
[CdvPurchase.AdapterListener] DEBUG: receiptsUpdated: [{"className":"Receipt","transactions":[{"className":"Transaction","transactionId":"2000000734344505","state":"approved","products":[{"id":"com.my.productid"}],"platform":"ios-appstore"}],"platform":"ios-appstore","nativeData":{"appStoreReceipt":"MII.../4N","bundleIdentifier":"com.my.appid","bundleShortVersion":"1.1.60","bundleNumericVersion":0,"bundleSignature":"????"}},{"className":"Receipt","transactions":[],"platform":"ios-appstore"}]
[CdvPurchase.AdapterListener] DEBUG: receiptsReady: ios-appstore(skipping)
[CdvPurchase] DEBUG: Calling callback: type=receiptUpdated() name=#5121f05a236978d1ebad4e26ac18ce54 reason=adapterListener_receiptsUpdated
[CdvPurchase] DEBUG: Calling callback: type=approved() name=transactionStateMonitors_callOnChange reason=adapterListener_receiptsUpdated_approved
[CdvPurchase] DEBUG: Calling callback: type=approved() name=#667b6c0a81ad70b56ce13d01a4e6794d reason=adapterListener_receiptsUpdated_approved
[CdvPurchase] INFO: finish(Transaction)
[CdvPurchase.AppleAppStore] INFO: finish(2000000734344505)
[CdvPurchase.AppleAppStore.objc] finishTransaction: Transaction 2000000734344505 finished.
[CdvPurchase.AppleAppStore.objc] transactionFinished: 2000000734344505
[CdvPurchase] DEBUG: Calling callback: type=receiptUpdated() name=#5121f05a236978d1ebad4e26ac18ce54 reason=adapterListener_receiptsUpdated
[CdvPurchase.AppleAppStore.Bridge] DEBUG: transaction updated:2000000734344505 state:PaymentTransactionStateFinished product:com.my.productid
[CdvPurchase.AppleAppStore] INFO: finish: 2000000734344505 - com.my.productid
[CdvPurchase.AppleAppStore] DEBUG: initializeAppReceipt() => already initialized.
[CdvPurchase.AdapterListener] DEBUG: receiptsUpdated: [{"className":"Receipt","transactions":[{"className":"Transaction","transactionId":"2000000734344505","state":"finished","products":[{"id":"com.my.productid"}],"platform":"ios-appstore"}],"platform":"ios-appstore","nativeData":{"appStoreReceipt":"MII.../4N","bundleIdentifier":"com.my.appid","bundleShortVersion":"1.1.60","bundleNumericVersion":0,"bundleSignature":"????"}}]
[CdvPurchase] DEBUG: Calling callback: type=receiptUpdated() name=#5121f05a236978d1ebad4e26ac18ce54 reason=adapterListener_receiptsUpdated
[CdvPurchase] DEBUG: Calling callback: type=finished() name=transactionStateMonitors_callOnChange reason=adapterListener_receiptsUpdated_finished
[CdvPurchase.AppleAppStore] DEBUG: receipt updated and ready.
[CdvPurchase.AdapterListener] DEBUG: receiptsUpdated: [{"className":"Receipt","transactions":[{"className":"Transaction","transactionId":"2000000734344505","state":"finished","products":[{"id":"com.my.productid"}],"platform":"ios-appstore"}],"platform":"ios-appstore","nativeData":{"appStoreReceipt":"MII.../4N","bundleIdentifier":"com.my.appid","bundleShortVersion":"1.1.60","bundleNumericVersion":0,"bundleSignature":"????"}},{"className":"Receipt","transactions":[],"platform":"ios-appstore"}]
[CdvPurchase.AdapterListener] DEBUG: receiptsReady: ios-appstore(skipping)
[CdvPurchase] DEBUG: Calling callback: type=receiptUpdated() name=#5121f05a236978d1ebad4e26ac18ce54 reason=adapterListener_receiptsUpdated
[CdvPurchase] DEBUG: Calling callback: type=receiptUpdated() name=#5121f05a236978d1ebad4e26ac18ce54 reason=adapterListener_receiptsUpdated

@kreso22
Copy link
Author

kreso22 commented Oct 16, 2024

Perhaps this may be related to iOS 18? It seemed to have worked prior to some few weeks ago when I started getting user emails.

Perhaps if I added server-side validation of receipt it would work? If I add server-side validation now, would that ignore previous purchases?

Thank you in advance.

@kreso22
Copy link
Author

kreso22 commented Nov 16, 2024

I spent many more days trying to figure this out.
The plugin simply silently fails, and there is no information on why.

While I can't possibly know if this is true - perhaps it could have to do with using a different IAP adapter for Cordova a while back. Possibly receipts that were created by the previous platform somehow are not recognized when the app initializes today - but are recognized when Restore is initiated.

Could this be the issue?

@DesignerApparelSales
Copy link

DesignerApparelSales commented Nov 17, 2024

Perhaps this may be related to iOS 18? It seemed to have worked prior to some few weeks ago when I started getting user emails.

Perhaps if I added server-side validation of receipt it would work? If I add server-side validation now, would that ignore previous purchases?

Thank you in advance.

I have the same problem and my real device is not ios 18. I have test in different devices in the sandbox. all the devices have the same problem. only restore purchase seem to fix it. Did you ever find the cause and solution?

@kreso22
Copy link
Author

kreso22 commented Nov 17, 2024

Unfortunately I was unable to figure this out so I moved to Revenuecat's plugin. My app works now.
Good luck!

@DesignerApparelSales
Copy link

Unfortunately I was unable to figure this out so I moved to Revenuecat's plugin. My app works now. Good luck!

Thanks for the reply, I'm using cordova, revenuecat is still supporting cordova? I think i'll jump over there too. i have been messing with this plugin for 3 days with no luck.

@kreso22
Copy link
Author

kreso22 commented Nov 17, 2024

Yes, you are right. They are gradually dropping support - but we should be good for another 1-2 years.
I will reconsider my options when that moment arrives.

@DesignerApparelSales
Copy link

I also saw this https://qonversion.io/ while searching for alternative to this plugin. Do you know if qonversion any good?

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

No branches or pull requests

3 participants