j3k0 / cordova-plugin-purchase

In-App Purchase for Cordova on iOS, Android and Windows
https://purchase.cordova.fovea.cc
1.3k stars 535 forks source link

Approved never fires after initiating the purchase #1568

Open mustaqt opened 6 months ago

mustaqt commented 6 months ago

I've integrated version 13.10.0 of the cordova-plugin-purchase into my Ionic application. Additionally, I've set up a consumable product in the Apple App Store and created a dedicated sandbox user for testing purposes.

While I can successfully retrieve product details from the store and display them in the application template, I'm encountering an issue during the purchase process. After initiating the purchase, the popup appears prompting me to enter the sandbox user password, then the popup shows "done" and closes. However, approximately 10 seconds later, the purchase popup reappears. This repetitive behavior persists, occurring in a recurring manner.

mvaljento commented 5 months ago

I have the exact same issue on iOS Simulator. I tried following all the Apple's Sandbox testing instructions for setting up storekit configuration, sandbox account etc but nothing helps. The purchase flow is stuck in a loop unless i close the dialog in which case the purchase simply fails. The purchase handlers never get called.

Android works perfectly on the emulator.

Dexus commented 5 months ago

ios simulator does not support this. you need to check this on a test device.

mustaqt commented 5 months ago

Hi, Thank you for your reply. I have also tried on real iphone device, it fires the verifier before I initiate the purchase but never fires approve or verify after I initiate the purchase. It does ask the payment popup and does show the tick that purchase was successful. I need to utilise the approve and verify so I can make api calls from the app to attach the userid with the subscription.

stripathix commented 5 months ago

same issue for me :(

EdisonCristovao commented 5 months ago

same here too

billybobilly commented 3 months ago

Me too. Works as expected on Android using Android Studio and Simulator.

iOS simulator with XCode does exactly what mustaqt said above. It asks for payment, accepts and says it is complete.

Note, that no payment is made on the CC on file for the test account. Anyone solved this????

billybobilly commented 3 months ago

After quite a bit of testing and turning on store.verbosity = 4 for debug, it looks like it does send the order, but I'm guessing at this point that Apple isn't actually processing anything in simulator or test sandbox mode, so it never gets to the verify and receipt stage. It's my best guess at the moment.

j3k0 commented 2 months ago

Can you spot any errors or useful logs in XCode?

billybobilly commented 2 months ago

No errors in the logs, even on debug level logging. It just stops and never gets to the verify and receipt stage. It's like it never even tries to send the order to Apple Pay.

hcassar93 commented 2 months ago

Same issue

j3k0 commented 2 months ago

I've had some users tell me it's was problem with their sandbox user account. Can you try logging out from AppStore sandbox from settings then login again? Can you try a different Test account (created from AppStore connect).

This document can be interesting read: https://developer.apple.com/documentation/storekit/in-app_purchase/testing_in-app_purchases_with_sandbox

selcukbeyhan commented 2 months ago

I have the similar issue and started to read the code.

I think the relevant transaction monitor is not initialised and therefore, the event is not being triggered.

Example: the requestPayment call is initialising the relevant transaction monitor. adapter.requestPayment(paymentRequest, additionalData).then(result => {

However, the getOffer().order() has a monitor but it doesn't emit the relevant event:

                    this.setPaymentMonitor((status, code, message) => {
                        this.log.info('order.paymentMonitor => ' + status + ' ' + (code !== null && code !== void 0 ? code : '') + ' ' + (message !== null && message !== void 0 ? message : ''));
                        if (resolved)
                            return;
                        switch (status) {
                            case 'cancelled':
                                callResolve(appStoreError(code !== null && code !== void 0 ? code : CdvPurchase.ErrorCode.PAYMENT_CANCELLED, message !== null && message !== void 0 ? message : 'The user cancelled the order.', offer.productId));
                                break;
                            case 'failed':
                                // note, "failed" might be triggered before "cancelled",
                                // so we'll give some time to catch the "cancelled" event.
                                setTimeout(() => {
                                    callResolve(appStoreError(code !== null && code !== void 0 ? code : CdvPurchase.ErrorCode.PURCHASE, message !== null && message !== void 0 ? message : 'Purchase failed', offer.productId));
                                }, 500);
                                break;
                            case 'purchased':
                            case 'deferred':
                                callResolve(undefined);
                                break;
                        }
                    });

Could this be the issue?

j3k0 commented 2 months ago

approved will be triggered by the native SDK (at least something named similarly). The event is "bubbled up" to your app by the plugin's AppStore adapter through that approved event. If you can share full logs we could see what happens during the purchase process (debug verbosity).

selcukbeyhan commented 2 months ago

here are the logs:

10.19.0 - [FirebaseMessaging][I-FCM001000] FIRMessaging Remote Notifications proxy enabled, will swizzle remote notification receiver handlers. If you'd prefer to manually integrate Firebase Messaging, add "FirebaseAppDelegateProxyEnabled" to your Info.plist, and set it to NO. Follow the instructions at: https://firebase.google.com/docs/cloud-messaging/ios/client#method_swizzling_in_firebase_messaging to ensure proper integration. nw_application_id_create_self NECP_CLIENT_ACTION_GET_SIGNED_CLIENT_ID [80: Authentication error] Failed to resolve host network app id Warning: -[BETextInput attributedMarkedText] is unimplemented KeyboardPlugin: resize mode - native [CdvPurchase.AppleAppStore.objc] Initialized. ⚡️ Loading app at capacitor://localhost... ⚡️ [log] - onscript loading complete ⚡️ [log] - Create CdvPurchase... ⚡️ WebView loaded ⚡️ [log] - AppComponent.appStateChange the platform is capacitor, initializing the purchase plugin ⚡️ [log] - LOGPREFIX - AppComponent: initializeCdvPurchasePluginEnvironment is called ⚡️ To Native -> App addListener 133938378 ⚡️ [log] - Create CdvPurchase... ⚡️ [log] - Ionic Native: deviceready event fired after 60 ms ⚡️ [log] - LOGPREFIX - AppComponent: Platform is ready ⚡️ [log] - [CdvPurchase] INFO: initialize([{"platform":"ios-appstore","options":{"autoFinish":true}}]) v13.10.1 ⚡️ [log] - [CdvPurchase.Adapters] INFO: Adding platforms: [{"platform":"ios-appstore","options":{"autoFinish":true}}] ⚡️ [log] - [CdvPurchase.Adapters] INFO: ⚡️ [log] - [CdvPurchase.Adapters] INFO: AppStore initializing... ⚡️ [log] - [CdvPurchase.AppleAppStore] INFO: bridge.init To Native Cordova -> InAppPurchase debug InAppPurchase1883072159 ["options": []] To Native Cordova -> InAppPurchase autoFinish InAppPurchase1883072160 ["options": []] To Native Cordova -> InAppPurchase setup InAppPurchase1883072161 ["options": []] [CdvPurchase.AppleAppStore.objc] setup: OK To Native Cordova -> InAppPurchase canMakePayments InAppPurchase1883072162 ["options": []] [CdvPurchase.AppleAppStore.objc] canMakePayments: Device can make payments. ⚡️ [log] - [CdvPurchase.AppleAppStore.Bridge] DEBUG: setup ok ⚡️ [log] - [CdvPurchase.AppleAppStore] INFO: ready ⚡️ [log] - [CdvPurchase.AppleAppStore] INFO: bridge.init done To Native Cordova -> InAppPurchase load InAppPurchase1883072163 ⚡️ [log] - [CdvPurchase.Adapters] INFO: AppStore initialized. ⚡️ [log] - [CdvPurchase.Adapters] INFO: AppStore products: [{"id":"LOGPREFIX_1_MONTHLY_SUBSCRIPTION","platform":"ios-appstore","type":"paid subscription"},{"id":"LOGPREFIX_3_MONTHLY_SUBSCRIPTION","platform":"ios-appstore","type":"paid subscription"},{"id":"LOGPREFIX_12_MONTHLY_SUBSCRIPTION","platform":"ios-appstore","type":"paid subscription"}] ["options": [<__NSArrayM 0x302335ef0>( LOGPREFIX_1_MONTHLY_SUBSCRIPTION, LOGPREFIX_3_MONTHLY_SUBSCRIPTION, LOGPREFIX_12_MONTHLY_SUBSCRIPTION ) ]] [CdvPurchase.AppleAppStore.objc] load: Getting products data [CdvPurchase.AppleAppStore.objc] load: Set has 3 elements [CdvPurchase.AppleAppStore.objc] load: - LOGPREFIX_12_MONTHLY_SUBSCRIPTION ⚡️ [log] - [CdvPurchase.AppleAppStore] INFO: bridge.load [CdvPurchase.AppleAppStore.objc] load: - LOGPREFIX_1_MONTHLY_SUBSCRIPTION [CdvPurchase.AppleAppStore.objc] load: - LOGPREFIX_3_MONTHLY_SUBSCRIPTION

[CdvPurchase.AppleAppStore.objc] load: Starting product request... ⚡️ [log] - [CdvPurchase.AppleAppStore.Bridge] DEBUG: load ["LOGPREFIX_1_MONTHLY_SUBSCRIPTION","LOGPREFIX_3_MONTHLY_SUBSCRIPTION","LOGPREFIX_12_MONTHLY_SUBSCRIPTION"] [CdvPurchase.AppleAppStore.objc] load: Product request started ⚡️ [log] - Tab1Page.ngOnInit is called ⚡️ [log] - Add the listener for notification actions ⚡️ [log] - Listener added ⚡️ [debug] - Tab1.ngAfterContentInit is called ⚡️ To Native -> LocalNotifications registerActionTypes 133938379 ⚡️ To Native -> LocalNotifications addListener 133938380 ⚡️ To Native -> PushNotifications requestPermissions 133938381 ⚡️ To Native -> PushNotifications addListener 133938382 ⚡️ To Native -> PushNotifications addListener 133938383 ⚡️ To Native -> PushNotifications addListener 133938384 ⚡️ To Native -> PushNotifications addListener 133938385 ⚡️ To Native -> App addListener 133938386 To Native Cordova -> InAppPurchase processPendingTransactions InAppPurchase1883072164 ["options": []] [CdvPurchase.AppleAppStore.objc] processPendingTransactionUpdates ⚡️ TO JS undefined ⚡️ [log] - [CdvPurchase.AppleAppStore.Bridge] DEBUG: processing pending transactions ⚡️ To Native -> Keyboard getResizeMode 133938387 ⚡️ TO JS {"mode":"native"} ⚡️ TO JS {"receive":"granted"} ⚡️ To Native -> Keyboard getResizeMode 133938388 ⚡️ TO JS {"mode":"native"} ⚡️ To Native -> PushNotifications register 133938389 ⚡️ TO JS undefined ⚡️ To Native -> Keyboard getResizeMode 133938390 ⚡️ TO JS {"mode":"native"} ⚡️ [debug] - Tab1.ionViewWillEnter is called ⚡️ [debug] - Tab1.ionViewWillEnter user is not logged in ⚡️ [log] - isloggedin set to: false ⚡️ [log] - [CdvPurchase.AppleAppStore] DEBUG: loading appstore receipt... ⚡️ [log] - [CdvPurchase.AppleAppStore.Bridge] DEBUG: loading appStoreReceipt To Native Cordova -> InAppPurchase appStoreReceipt InAppPurchase1883072165 ["options": []] [CdvPurchase.AppleAppStore.objc] appStoreReceipt: ⚡️ [log] - [CdvPurchase.AppleAppStore.Bridge] DEBUG: infoPlist: com.myapp.id,2.2.5,16809984,null ⚡️ [log] - [CdvPurchase.AppleAppStore] DEBUG: appstore receipt loaded ⚡️ [log] - [CdvPurchase.AdapterListener] DEBUG: receiptsReady: ios-appstore (1/0) ⚡️ [log] - User: null ⚡️ [log] - User: null [CdvPurchase.AppleAppStore.objc] BatchProductsRequestDelegate.productsRequest:didReceiveResponse: [CdvPurchase.AppleAppStore.objc] BatchProductsRequestDelegate.productsRequest:didReceiveResponse: Has 3 validProducts [CdvPurchase.AppleAppStore.objc] BatchProductsRequestDelegate.productsRequest:didReceiveResponse: - LOGPREFIX_1_MONTHLY_SUBSCRIPTION: 1 Monthly Subscription [CdvPurchase.AppleAppStore.objc] BatchProductsRequestDelegate.productsRequest:didReceiveResponse: - LOGPREFIX_3_MONTHLY_SUBSCRIPTION: 3 Monthly Subscription [CdvPurchase.AppleAppStore.objc] BatchProductsRequestDelegate.productsRequest:didReceiveResponse: - LOGPREFIX_12_MONTHLY_SUBSCRIPTION: 12 Monthly Subscription [CdvPurchase.AppleAppStore.objc] BatchProductsRequestDelegate.productsRequest:didReceiveResponse: sendPluginResult: ( ( { billingPeriod = 1; billingPeriodUnit = Month; countryCode = DE; currency = EUR; description = "Subscribe our services 1-monthly basis"; discounts = ( ); group = 21435649; id = "LOGPREFIX_1_MONTHLY_SUBSCRIPTION"; introPrice = ""; introPriceMicros = ""; introPricePaymentMode = ""; introPricePeriod = ""; introPricePeriodUnit = ""; price = "6,99\U00a0\U20ac"; priceMicros = 6990000; title = "1 Monthly Subscription"; }, { billingPeriod = 3; billingPeriodUnit = Month; countryCode = DE; currency = EUR; description = "Subscribe our services 3-monthly basis"; discounts = ( ); group = 21435649; id = "LOGPREFIX_3_MONTHLY_SUBSCRIPTION"; introPrice = ""; introPriceMicros = ""; introPricePaymentMode = ""; introPricePeriod = ""; introPricePeriodUnit = ""; price = "17,99\U00a0\U20ac"; priceMicros = 17990000; title = "3 Monthly Subscription"; }, { billingPeriod = 1; billingPeriodUnit = Year; countryCode = DE; currency = EUR; description = "Subscribe our services 12-monthly basis"; discounts = ( ); group = 21435649; id = "LOGPREFIX_12_MONTHLY_SUBSCRIPTION"; introPrice = ""; introPriceMicros = ""; introPricePaymentMode = ""; introPricePeriod = ""; introPricePeriodUnit = ""; price = "59,99\U00a0\U20ac"; priceMicros = 59990000; title = "12 Monthly Subscription"; } ), ( ) ) ⚡️ [log] - [CdvPurchase.AppleAppStore.Bridge] DEBUG: load ok: { valid:[{"id":"LOGPREFIX_1_MONTHLY_SUBSCRIPTION","description":"Subscribe our services 1-monthly basis","introPrice":null,"introPricePaymentMode":null,"billingPeriodUnit":"Month","countryCode":"DE","introPricePeriodUnit":null,"discounts":[],"title":"1 Monthly Subscription","price":"6,99 €","billingPeriod":1,"group"....cut.... ⚡️ [log] - [CdvPurchase.AppleAppStore] INFO: bridge.loaded: {"validProducts":[{"id":"LOGPREFIX_1_MONTHLY_SUBSCRIPTION","description":"Subscribe our services 1-monthly basis","introPrice":null,"introPricePaymentMode":null,"billingPeriodUnit":"Month","countryCode":"DE","introPricePeriodUnit":null,"discounts":[],"title":"1 Monthly Subscription","price":"6,99 €","billingPeriod":1,....cut.... ⚡️ [log] - [CdvPurchase.AppleAppStore] DEBUG: load eligibility: [{"id":"LOGPREFIX_1_MONTHLY_SUBSCRIPTION","description":"Subscribe our services 1-monthly basis","introPrice":null,"introPricePaymentMode":null,"billingPeriodUnit":"Month","countryCode":"DE","introPricePeriodUnit":null,"discounts":[],"title":"1 Monthly Subscription","price":"6,99 €","billingPeriod":1,"group":"2143....cut.... ⚡️ [log] - [CdvPurchase.AppleAppStore] DEBUG: No discount eligibility determiner, skipping... ⚡️ [log] - [CdvPurchase.AppleAppStore] INFO: eligibilities ready: {"request":[],"response":[]} ⚡️ [log] - [CdvPurchase.AppleAppStore] DEBUG: LOGPREFIX_1_MONTHLY_SUBSCRIPTION is valid: {"id":"LOGPREFIX_1_MONTHLY_SUBSCRIPTION","description":"Subscribe our services 1-monthly basis","introPrice":null,"introPricePaymentMode":null,"billingPeriodUnit":"Month","countryCode":"DE","introPricePeriodUnit":null,"discounts":[],"title":"1 Monthly Subscription","price":"6,99 €","billingPeriod":1,"group":"21435649","pr....cut.... ⚡️ [log] - [CdvPurchase.AppleAppStore] DEBUG: registering new product ⚡️ [log] - [CdvPurchase.AppleAppStore] DEBUG: LOGPREFIX_3_MONTHLY_SUBSCRIPTION is valid: {"id":"LOGPREFIX_3_MONTHLY_SUBSCRIPTION","description":"Subscribe our services 3-monthly basis","introPrice":null,"introPricePaymentMode":null,"billingPeriodUnit":"Month","countryCode":"DE","introPricePeriodUnit":null,"discounts":[],"title":"3 Monthly Subscription","price":"17,99 €","billingPeriod":3,"group":"2....cut.... ⚡️ [log] - [CdvPurchase.AppleAppStore] DEBUG: registering new product ⚡️ [log] - [CdvPurchase.AppleAppStore] DEBUG: LOGPREFIX_12_MONTHLY_SUBSCRIPTION is valid: {"id":"LOGPREFIX_12_MONTHLY_SUBSCRIPTION","description":"Subscribe our services 12-monthly basis","introPrice":null,"introPricePaymentMode":null,"billingPeriodUnit":"Year","countryCode":"DE","introPricePeriodUnit":null,"discounts":[],"title":"12 Monthly Subscription","price":"59,99 €","billingPeriod....cut.... ⚡️ [log] - [CdvPurchase.AppleAppStore] DEBUG: registering new product ⚡️ [log] - [CdvPurchase.AppleAppStore] DEBUG: Products loaded: [{"className":"Product","title":"1 Monthly Subscription","description":"Subscribe our services 1-monthly basis","platform":"ios-appstore","type":"paid subscription","id":"LOGPREFIX_1_MONTHLY_SUBSCRIPTION","group":"21435649","offers":[{"className":"Offer","id":"$","pricingPhases":[{"price":"6,99 €","priceMicros":699....cut.... ⚡️ [log] - [CdvPurchase.Adapters] INFO: AppStore products loaded: [{"className":"Product","title":"1 Monthly Subscription","description":"Subscribe our services 1-monthly basis","platform":"ios-appstore","type":"paid subscription","id":"LOGPREFIX_1_MONTHLY_SUBSCRIPTION","group":"21435649","offers":[{"className":"Offer","id":"$","pricingPhases":[{"price":"6,99 €","priceMicros":....cut.... ⚡️ [log] - [CdvPurchase.Adapters] INFO: AppStore receipts loaded: [{"className":"Receipt","transactions":[{"className":"Transaction","transactionId":"appstore.application","state":"approved","products":[{"id":"com.myapp.id"}],"platform":"ios-appstore"}],"platform":"ios-appstore","nativeData":{"appStoreReceipt":"MIMBUlEGCSqGSIb3DQEHAqCDAVJBMIMBUjwCAQExDzANBglghkgBZQMEAgEF...... ⚡️ [log] - [CdvPurchase.AdapterListener] DEBUG: setSupportedPlatforms: ios-appstore (1 have their receipts ready) ⚡️ [log] - [CdvPurchase.AdapterListener] DEBUG: triggering receiptsReady() ⚡️ [log] - [CdvPurchase] DEBUG: Calling callback: type=productUpdated() name=#4cf977228a7bab4062b1a77490e21233 reason=adapterListener_productsUpdated ⚡️ [log] - LOGPREFIX - AppComponent: -store.when.productUpdated- Product is updated. ⚡️ [log] - [CdvPurchase] DEBUG: Calling callback: type=productUpdated() name=#4cf977228a7bab4062b1a77490e21233 reason=adapterListener_productsUpdated ⚡️ [log] - LOGPREFIX - AppComponent: -store.when.productUpdated- Product is updated. ⚡️ [log] - [CdvPurchase] DEBUG: Calling callback: type=productUpdated() name=#4cf977228a7bab4062b1a77490e21233 reason=adapterListener_productsUpdated ⚡️ [log] - LOGPREFIX - AppComponent: -store.when.productUpdated- Product is updated. ⚡️ [log] - [CdvPurchase] DEBUG: Calling callback: type=receiptsReady() name=receiptsMonitor_setup reason=adapterListener_setSupportedPlatforms ⚡️ [log] - [CdvPurchase.ReceiptsMonitor] DEBUG: receiptsReady... ⚡️ [log] - LOGPREFIX - AppComponent: -store.ready- Plugin is initialized and ready tcp_output [C1.1.2.1:3] flags=[R.] seq=1540585687, ack=970611026, win=1986 state=CLOSED rcv_nxt=970611026, snd_una=1540585687 ⚡️ [log] - [CdvPurchase.AppleAppStore] DEBUG: receipt updated and ready. ⚡️ [log] - [CdvPurchase.AdapterListener] DEBUG: receiptsUpdated: [{"className":"Receipt","transactions":[{"className":"Transaction","transactionId":"appstore.application","state":"approved","products":[{"id":"com.myapp.id"}],"platform":"ios-appstore"}],"platform":"ios-appstore","nativeData":{"appStoreReceipt":"MIMBUlEGCSqGSIb3DQEHAqCDAVJBMIMBUjwCAQE....... ⚡️ [log] - [CdvPurchase.AdapterListener] DEBUG: receiptsReady: ios-appstore(skipping) ⚡️ [log] - [CdvPurchase] DEBUG: Calling callback: type=approved() name=transactionStateMonitors_callOnChange reason=adapterListener_receiptsUpdated_approved ⚡️ [log] - [CdvPurchase] DEBUG: Calling callback: type=approved() name=#0946fb7d2fe997f1d1f730d5edd2f606 reason=adapterListener_receiptsUpdated_approved ⚡️ [log] - LOGPREFIX - AppComponent: -store.when.approved- Purchase is approved. Verifying the transaction: {"className":"Transaction","transactionId":"appstore.application","state":"approved","products":[{"id":"com.myapp.id"}],"platform":"ios-appstore"} ⚡️ [log] - [CdvPurchase] INFO: verify(Transaction) ⚡️ [log] - [CdvPurchase.Validator] DEBUG: Schedule validation: {"className":"Transaction","transactionId":"appstore.application","state":"approved","products":[{"id":"com.myapp.id"}],"platform":"ios-appstore"} ⚡️ [log] - [CdvPurchase.Validator] DEBUG: Validation requests=1 responses=0 ⚡️ [log] - LOGPREFIX - AppComponent: -validator- Custom validator is called ⚡️ [log] - LOGPREFIX - AppComponent: -validator- param-1 type: object ⚡️ [log] - LOGPREFIX - AppComponent: -validator- param-2 type: function ⚡️ [log] - LOGPREFIX - AppComponent: -validator- (payload) => callback({ receipt, payload }) ⚡️ [log] - LOGPREFIX - AppComponent: -validator- receipt transaction {"id":"com.myapp.id","type":"application","products":[{"className":"Product","title":"1 Monthly Subscription","description":"Subscribe our services 1-monthly basis","platform":"ios-appstore","type":"paid subscription","id":"LOGPREFIX_1_MONTHLY_SUBSCRIPTION","group":"21435649","offers":[{"className":"Offer","id":"$","pricingPhases":[{....cut.... ⚡️ [log] - LOGPREFIX - AppComponent: -validator- receipt transaction {"type":"ios-appstore","id":"appstore.application","appStoreReceipt":"MIMBUlEGCSqGSIb3DQEHAqCDAVJBMIMBUjwCAQ...... ⚡️ [log] - LOGPREFIX - AppComponent: -validator- receipt transaction ["id","type","products","transaction","additionalData","device"] ⚡️ [log] - LOGPREFIX - AppComponent: -validator- receipt transaction ["type","id","appStoreReceipt"] ⚡️ [log] - LOGPREFIX - AppComponent: -validator- Purchase object is {"id":"com.myapp.id","isBillingRetryPeriod":false,"isExpired":false,"isIntroPeriod":false,"isTrialPeriod":false,"platform":"ios-appstore","purchaseId":1727080369603,"transactionId":"com.myapp.id"} ⚡️ [log] - [CdvPurchase.Validator] DEBUG: Validation requests=1 responses=1 ⚡️ [log] - [CdvPurchase.Validator] DEBUG: Validator returned ⚡️ [log] - [CdvPurchase.Validator] DEBUG: Register a new verified receipt. ⚡️ [log] - [CdvPurchase] DEBUG: Calling callback: type=verified() name=receiptsMonitor_check reason=payload_ok ⚡️ [log] - [CdvPurchase.ReceiptsMonitor] DEBUG: check(1/1) ⚡️ [log] - [CdvPurchase.ReceiptsMonitor] INFO: receiptsVerified() ⚡️ [log] - [CdvPurchase] DEBUG: Calling callback: type=verified() name=#ecc56e9725801b88c096e4927dfe6942 reason=payload_ok ⚡️ [log] - LOGPREFIX - AppComponent: -store.when.verified- Receipt is verified: {"className":"VerifiedReceipt","sourceReceipt":{"className":"Receipt","transactions":[{"className":"Transaction","transactionId":"appstore.application","state":"approved","products":[{"id":"com.myapp.id"}],"platform":"ios-appstore"}],"platform":"ios-appstore","nativeData":{"appStoreReceipt":"MIMBUlEGCSqGSIb3DQEHAqCDAVJBMIMBUjwCAQExDzANBg..... ⚡️ [log] - [CdvPurchase] INFO: finish(VerifiedReceipt) ⚡️ [log] - [CdvPurchase.AppleAppStore] INFO: finish(appstore.application) ⚡️ [log] - [CdvPurchase.AdapterListener] DEBUG: receiptsUpdated: [{"className":"Receipt","transactions":[{"className":"Transaction","transactionId":"appstore.application","state":"finished","products":[{"id":"com.myapp.id"}],"platform":"ios-appstore"}],"platform":"ios-appstore","nativeData":{"appStoreReceipt":"MIMBUlEGCSqGSIb3DQE..... ⚡️ [log] - [CdvPurchase] DEBUG: Calling callback: type=finished() name=transactionStateMonitors_callOnChange reason=adapterListener_receiptsUpdated_finished ⚡️ [log] - [CdvPurchase] DEBUG: Calling callback: type=finished() name=#13b7a4f4fbb914127d0354ab7bcf5270 reason=adapterListener_receiptsUpdated_finished ⚡️ [log] - LOGPREFIX - AppComponent: -store.when.finished- Transaction finished: appstore.application ⚡️ TO JS {"value":"d5NFGqs_N0UPvbOHifw2SD:APA91bFJ4kTxfKWem07pD2fcD2iC40PFmmsKdfufw6-iqUdTvcL8iP8CbMixkjrjPN6YbaOJU0SZr1NjkEs0RvY5aBrkvpHV7Lb-fLjAq_3L7XJldBfA7yjlSk_5CpXFpHy7dYgc_sUx"}

⚡️ [log] - LOGPREFIX - Subscription: ngOnInit is called ⚡️ [log] - LOGPREFIX - Subscription: onAuthStateChanged is called ⚡️ [log] - LOGPREFIX - Subscription: setTheProductButtonTextValues is called ⚡️ [log] - LOGPREFIX - Subscription: -store.ready- Verified Purchases: [{"id":"com.myapp.id","isBillingRetryPeriod":false,"isExpired":false,"isIntroPeriod":false,"isTrialPeriod":false,"platform":"ios-appstore","purchaseId":1727080369603,"transactionId":"com.myapp.id"}] ⚡️ [log] - LOGPREFIX - Subscription: -store.ready- Verified Receipts: [{"className":"VerifiedReceipt","sourceReceipt":{"className":"Receipt","transactions":[{"className":"Transaction","transactionId":"appstore.application","state":"finished","products":[{"id":"com.myapp.id"}],"platform":"ios-appstore"}],"platform":"ios-appstore","nativeData":{"appStoreReceipt":"MIMBUlEGCSqGSIb3DQEHAqCDAVJBMIMBUjwCAQExD..... ⚡️ [log] - LOGPREFIX - Subscription: -store.ready- local transactions: [{"className":"Transaction","transactionId":"appstore.application","state":"finished","products":[{"id":"com.myapp.id"}],"platform":"ios-appstore"}] ⚡️ [log] - LOGPREFIX - Subscription: -store.ready- Product-1 in local receipts: undefined ⚡️ [log] - LOGPREFIX - Subscription: -store.ready- Product-2 in local receipts: undefined ⚡️ [log] - LOGPREFIX - Subscription: -store.ready- Product-3 in local receipts: undefined ⚡️ [log] - LOGPREFIX - Subscription: -store.ready- Product-1 in verified receipts: undefined ⚡️ [log] - LOGPREFIX - Subscription: -store.ready- Product-2 in verified receipts: undefined ⚡️ [log] - LOGPREFIX - Subscription: -store.ready- Product-3 in verified receipts: undefined

To Native Cordova -> InAppPurchase purchase InAppPurchase1883072168 ⚡️ [log] - LOGPREFIX - Subscription: purchaseViaCdvPurchasePlugin is called ["options": [LOGPREFIX_1_MONTHLY_SUBSCRIPTION, 1, , { }]] [CdvPurchase.AppleAppStore.objc] purchase: About to do IAP ⚡️ [log] - [CdvPurchase] INFO: order(LOGPREFIX_1_MONTHLY_SUBSCRIPTION) ⚡️ [log] - [CdvPurchase.AppleAppStore] INFO: order [CdvPurchase.AppleAppStore.objc] paymentQueue:updatedTransactions: LOGPREFIX_1_MONTHLY_SUBSCRIPTION [CdvPurchase.AppleAppStore.objc] paymentQueue:updatedTransactions: Purchasing... [CdvPurchase.AppleAppStore.objc] paymentQueue:updatedTransactions: State: PaymentTransactionStatePurchasing [CdvPurchase.AppleAppStore.objc] processTransactionUpdate:withArgs: transactionIdentifier= ⚡️ [log] - [CdvPurchase.AppleAppStore.Bridge] DEBUG: Purchase enqueued LOGPREFIX_1_MONTHLY_SUBSCRIPTION ⚡️ [log] - [CdvPurchase.AppleAppStore] INFO: purchaseEnqueued: LOGPREFIX_1_MONTHLY_SUBSCRIPTION - 1 ⚡️ [log] - [CdvPurchase.AppleAppStore] INFO: order.success ⚡️ [log] - [CdvPurchase.AdapterListener] DEBUG: receiptsUpdated: [{"className":"Receipt","transactions":[{"className":"Transaction","transactionId":"virtual.LOGPREFIX_1_MONTHLY_SUBSCRIPTION","state":"initiated","products":[{"id":"LOGPREFIX_1_MONTHLY_SUBSCRIPTION"}],"platform":"ios-appstore"}],"platform":"ios-appstore"}] ⚡️ [log] - [CdvPurchase.AppleAppStore.Bridge] DEBUG: transaction updated: state:PaymentTransactionStatePurchasing product:LOGPREFIX_1_MONTHLY_SUBSCRIPTION ⚡️ [log] - [CdvPurchase.AppleAppStore] INFO: purchasing: LOGPREFIX_1_MONTHLY_SUBSCRIPTION ⚡️ [log] - [CdvPurchase.AdapterListener] DEBUG: receiptsUpdated: [{"className":"Receipt","transactions":[{"className":"Transaction","transactionId":"virtual.LOGPREFIX_1_MONTHLY_SUBSCRIPTION","state":"initiated","products":[{"id":"LOGPREFIX_1_MONTHLY_SUBSCRIPTION"}],"platform":"ios-appstore"}],"platform":"ios-appstore"}] ⚡️ [log] - [CdvPurchase] DEBUG: Calling callback: type=initiated() name=#359374082b79762b40ac94f782e5e7bb reason=adapterListener_receiptsUpdated_initiated ⚡️ [log] - LOGPREFIX - AppComponent: -store.when.initiated- Purchase initiated: {"className":"Transaction","transactionId":"virtual.LOGPREFIX_1_MONTHLY_SUBSCRIPTION","state":"initiated","products":[{"id":"LOGPREFIX_1_MONTHLY_SUBSCRIPTION"}],"platform":"ios-appstore"} ⚡️ TO JS {"isActive":false} ⚡️ [log] - AppComponent.appStateChange App is in background ⚡️ TO JS {"isActive":true} ⚡️ To Native -> App getState 133938391 ⚡️ [log] - AppComponent.appStateChange App is in foreground ⚡️ TO JS {"isActive":true} ⚡️ [log] - AppComponent.appStateChange AppState: {"isActive":true} ⚡️ [log] - AppComponent.appStateChange Nowhere to navigate ⚡️ TO JS {"isActive":false} ⚡️ [log] - AppComponent.appStateChange App is in background [CdvPurchase.AppleAppStore.objc] paymentQueue:updatedTransactions: LOGPREFIX_1_MONTHLY_SUBSCRIPTION [CdvPurchase.AppleAppStore.objc] paymentQueue:updatedTransactions: State: PaymentTransactionStatePurchased [CdvPurchase.AppleAppStore.objc] processTransactionUpdate:withArgs: transactionIdentifier=2000000721904145 [CdvPurchase.AppleAppStore.objc] transactionFinished: 2000000721904145 ⚡️ [log] - [CdvPurchase.AppleAppStore.Bridge] DEBUG: transaction updated:2000000721904145 state:PaymentTransactionStatePurchased product:LOGPREFIX_1_MONTHLY_SUBSCRIPTION ⚡️ [log] - [CdvPurchase.AppleAppStore] INFO: purchase: id:2000000721904145 product:LOGPREFIX_1_MONTHLY_SUBSCRIPTION originalTransaction:2000000719786443 - date:1727080429000.000000 - discount: ⚡️ [log] - [CdvPurchase.AppleAppStore] DEBUG: initializeAppReceipt() => already initialized. ⚡️ [log] - [CdvPurchase.AppleAppStore] INFO: order.paymentMonitor => purchased
⚡️ [log] - [CdvPurchase.AppleAppStore.Bridge] DEBUG: transaction updated:2000000721904145 state:PaymentTransactionStateFinished product:LOGPREFIX_1_MONTHLY_SUBSCRIPTION ⚡️ [log] - [CdvPurchase.AppleAppStore] INFO: finish: 2000000721904145 - LOGPREFIX_1_MONTHLY_SUBSCRIPTION ⚡️ [log] - [CdvPurchase.AppleAppStore] DEBUG: initializeAppReceipt() => already initialized. ⚡️ TO JS {"isActive":true} ⚡️ To Native -> App getState 133938392 ⚡️ [log] - AppComponent.appStateChange App is in foreground ⚡️ TO JS {"isActive":true} ⚡️ [log] - AppComponent.appStateChange AppState: {"isActive":true} ⚡️ [log] - AppComponent.appStateChange Nowhere to navigate ⚡️ [log] - [CdvPurchase.AppleAppStore] DEBUG: receipt updated and ready. ⚡️ [log] - [CdvPurchase.AdapterListener] DEBUG: receiptsUpdated: [{"className":"Receipt","transactions":[{"className":"Transaction","transactionId":"appstore.application","state":"finished","products":[{"id":"com.myapp.id"}],"platform":"ios-appstore"},{"className":"Transaction","transactionId":"2000000721904145","state":"finished","products":[{"id":"LOGPREFIX_1_MON....cut.... ⚡️ [log] - [CdvPurchase.AdapterListener] DEBUG: receiptsReady: ios-appstore(skipping) ⚡️ [log] - [CdvPurchase] DEBUG: Calling callback: type=finished() name=transactionStateMonitors_callOnChange reason=adapterListener_receiptsUpdated_finished ⚡️ [log] - [CdvPurchase] DEBUG: Calling callback: type=finished() name=#13b7a4f4fbb914127d0354ab7bcf5270 reason=adapterListener_receiptsUpdated_finished ⚡️ [log] - LOGPREFIX - AppComponent: -store.when.finished- Transaction finished: 2000000721904145

[CdvPurchase.AppleAppStore.objc] paymentQueue:updatedTransactions: LOGPREFIX_1_MONTHLY_SUBSCRIPTION [CdvPurchase.AppleAppStore.objc] paymentQueue:updatedTransactions: State: PaymentTransactionStatePurchased [CdvPurchase.AppleAppStore.objc] processTransactionUpdate:withArgs: transactionIdentifier=2000000721910675 [CdvPurchase.AppleAppStore.objc] transactionFinished: 2000000721910675 ⚡️ [log] - [CdvPurchase.AppleAppStore.Bridge] DEBUG: transaction updated:2000000721910675 state:PaymentTransactionStatePurchased product:LOGPREFIX_1_MONTHLY_SUBSCRIPTION ⚡️ [log] - [CdvPurchase.AppleAppStore] INFO: purchase: id:2000000721910675 product:LOGPREFIX_1_MONTHLY_SUBSCRIPTION originalTransaction:2000000719786443 - date:1727080729000.000000 - discount: ⚡️ [log] - [CdvPurchase.AppleAppStore] DEBUG: initializeAppReceipt() => already initialized. ⚡️ [log] - [CdvPurchase.AppleAppStore.Bridge] DEBUG: transaction updated:2000000721910675 state:PaymentTransactionStateFinished product:LOGPREFIX_1_MONTHLY_SUBSCRIPTION ⚡️ [log] - [CdvPurchase.AppleAppStore] INFO: finish: 2000000721910675 - LOGPREFIX_1_MONTHLY_SUBSCRIPTION ⚡️ [log] - [CdvPurchase.AppleAppStore] DEBUG: initializeAppReceipt() => already initialized. ⚡️ [log] - [CdvPurchase.AppleAppStore] DEBUG: receipt updated and ready. ⚡️ [log] - [CdvPurchase.AdapterListener] DEBUG: receiptsUpdated: [{"className":"Receipt","transactions":[{"className":"Transaction","transactionId":"appstore.application","state":"finished","products":[{"id":"com.myapp.id"}],"platform":"ios-appstore"},{"className":"Transaction","transactionId":"2000000721904145","state":"finished","products":[{"id":"LIFEPLA....cut.... ⚡️ [log] - [CdvPurchase.AdapterListener] DEBUG: receiptsReady: ios-appstore(skipping) ⚡️ [log] - [CdvPurchase] DEBUG: Calling callback: type=finished() name=transactionStateMonitors_callOnChange reason=adapterListener_receiptsUpdated_finished ⚡️ [log] - [CdvPurchase] DEBUG: Calling callback: type=finished() name=#13b7a4f4fbb914127d0354ab7bcf5270 reason=adapterListener_receiptsUpdated_finished ⚡️ [log] - LOGPREFIX - AppComponent: -store.when.finished- Transaction finished: 2000000721910675

selcukbeyhan commented 2 months ago

in my case, approved is emitted once only, at the app.component: ⚡️ [log] - LOGPREFIX - AppComponent: -store.when.approved- Purchase is approved. Verifying the transaction: {"className":"Transaction","transactionId":"appstore.application","state":"approved","products":[{"id":"com.myapp.id"}],"platform":"ios-appstore"}

selcukbeyhan commented 2 months ago

Here is my code:

` initializeCdvPurchasePluginEnvironment() { console.log(LOG_PREFIX, "initializeCdvPurchasePluginEnvironment is called"); this.platform.ready().then(async () => { console.log(LOG_PREFIX, "Platform is ready");

  const platform = CdvPurchase.Platform.APPLE_APPSTORE;

  CdvPurchase.store.when().productUpdated(function(product) {
    console.log(LOG_PREFIX, "-store.when.productUpdated-", "Product is updated.");
  });

  CdvPurchase.store.when().initiated((transaction) => {
    console.log(LOG_PREFIX, "-store.when.initiated-", "Purchase initiated: ", JSON.stringify(transaction));
  });

  CdvPurchase.store.when().approved(transaction => {
    console.log(LOG_PREFIX, "-store.when.approved-", "Purchase is approved. Verifying the transaction: ", JSON.stringify(transaction));
    transaction.verify();
  });

  CdvPurchase.store.when().finished((transaction) => {
    console.log(LOG_PREFIX, "-store.when.finished-", "Transaction finished: ", transaction.transactionId);
  });

  CdvPurchase.store.when().verified((verifiedReceipt) => {
    console.log(LOG_PREFIX, "-store.when.verified-", "Receipt is verified: ", verifiedReceipt);
    verifiedReceipt.finish(); // this is documented like that here in the documentation of the plugin, line 918
  });

  CdvPurchase.store.when().unverified(function (receipt) {
    console.log(LOG_PREFIX, "-store.when.unverified-", "Receipt unverified is called", JSON.stringify(receipt));
    // 2024-09-20: followed this page: https://github.com/j3k0/cordova-plugin-purchase/issues/1350
    const code = receipt.payload?.code;
    if (code === CdvPurchase.ErrorCode.PAYMENT_EXPIRED) {
      receipt.receipt.finish();
    }
  });

  CdvPurchase.store.error((e) => {
    console.log(LOG_PREFIX,"-store.error-",  "ERROR happened during processing: " + e.code + ": " + JSON.stringify(e));
    if(e.code == CdvPurchase.ErrorCode.PAYMENT_CANCELLED) {
      console.log(LOG_PREFIX,"-store.error-",  "User canceled the order")
    } else {
      console.log(LOG_PREFIX,"-store.error-",  "Error happened during order")
    }
  });

  CdvPurchase.store.register([{
    id: this.subscriptionService.PRODUCT_ID_1_MONTH,
    platform: platform,
    type: CdvPurchase.ProductType.PAID_SUBSCRIPTION
  }, {
    id: this.subscriptionService.PRODUCT_ID_3_MONTH,
    platform: platform,
    type: CdvPurchase.ProductType.PAID_SUBSCRIPTION
  }, {
    id: this.subscriptionService.PRODUCT_ID_12_MONTH,
    platform: platform,
    type: CdvPurchase.ProductType.PAID_SUBSCRIPTION
  }]);

  let validator = (receiptRequestBody:any, callback:any) => {
    console.log(LOG_PREFIX, "-validator-", "Custom validator is called");
    console.log(LOG_PREFIX, "-validator-", "param-1 type: ", typeof receiptRequestBody);
    console.log(LOG_PREFIX, "-validator-", "param-2 type: ", typeof callback);
    console.log(LOG_PREFIX, "-validator-", callback);
    if(receiptRequestBody.id == "appstore.application") {
      console.log(LOG_PREFIX, "-validator- App Installation Transaction is being validated");
    } else {
      console.log(LOG_PREFIX, "-validator- receipt transaction", JSON.stringify(receiptRequestBody));
      console.log(LOG_PREFIX, "-validator- receipt transaction", JSON.stringify(receiptRequestBody.transaction));
      console.log(LOG_PREFIX, "-validator- receipt transaction", JSON.stringify(Object.getOwnPropertyNames(receiptRequestBody)));
      console.log(LOG_PREFIX, "-validator- receipt transaction", JSON.stringify(Object.getOwnPropertyNames(receiptRequestBody.transaction)));
    }

    let purchaseObj = {
      id: receiptRequestBody.id,
      isBillingRetryPeriod: false,
      isExpired: false,
      isIntroPeriod: false,
      isTrialPeriod: false,
      platform: platform,
      // purchaseDate: receiptRequestBody..purchaseDate,
      purchaseId: Date.now(),
      transactionId: receiptRequestBody.id
    }
    console.log(LOG_PREFIX, "-validator- Purchase object is", JSON.stringify(purchaseObj));

    callback({
        ok: true, 
        status: 'valid', 
        data: { 
          // product_id: "LIFEPLANNER_1_MONTHLY_SUBSCRIPTION", 
          quantity: 1, 
          // transaction_id: receiptRequestBody.transaction.id, 
          // purchase_date: receiptRequestBody.transaction.purchaseDate, 
          // expiration_date: receiptRequestBody.transaction.expirationDate, 
          "is_canceled": false, 
          "is_refunded": false, 
          // id: receiptRequestBody.transaction.product_id,
          latest_receipt: true, 
          transaction:  receiptRequestBody.transaction,
          collection: [purchaseObj]
        }
      });
  }

  //CdvPurchase.store.validator = "https://thisdomaindoesntexist.com/validate";
  CdvPurchase.store.validator = validator;

  CdvPurchase.store.verbosity = CdvPurchase.LogLevel.DEBUG;
  await CdvPurchase.store.initialize([{platform: platform, options: {autoFinish: true}}]);

/ ----------------------------- / CdvPurchase.store.ready(() => { console.log(LOG_PREFIX, "-store.ready-", "Plugin is initialized and ready"); }); }); } `

selcukbeyhan commented 2 months ago

here is the purchase code:

async purchaseViaCdvPurchasePlugin(item: number) { console.log(LOG_PREFIX, "purchaseViaCdvPurchasePlugin is called");

if (item == 1) {
  this.monthly_1_button_disabled = true;
  this.monthly_1_sub_ordered_text = "Order in progress...";
  try {
    await this.month_1_product.getOffer().order();
  } catch(error) {
    console.log(LOG_PREFIX, "Order could not be placed: ", JSON.stringify(error));
    this.monthly_1_button_disabled = false;
    this.monthly_1_sub_ordered_text = "Retry";
  }
} else if (item == 3) {
  this.monthly_3_button_disabled = true;
  this.monthly_3_sub_ordered_text = "Order in progress...";
  try {
    await this.month_3_product.getOffer().order();
  } catch(error) {
    console.log(LOG_PREFIX, "Order could not be placed: ", JSON.stringify(error));
    this.monthly_3_button_disabled = false;
    this.monthly_3_sub_ordered_text = "Retry";
  }
} else if (item == 12) {
  this.monthly_12_button_disabled = true;
  this.monthly_12_sub_ordered_text = "Order in progress...";
  try {
    await this.month_12_product.getOffer().order();
  } catch(error) {
    console.log(LOG_PREFIX, "Order could not be placed: ", JSON.stringify(error));
    this.monthly_12_button_disabled = false;
    this.monthly_12_sub_ordered_text = "Retry";
  }
} else {
  console.log(LOG_PREFIX, "Unexpected order is placed")
}

}

j3k0 commented 2 months ago

Seeing "Create CdvPurchase..." twice is an indication that the plugin is both "bundled" by the build chain and "loaded" by ionic. This used to create problem but should be fine since version 13.10.0

It looks like a bug with the "autoFinish" flag. A race condition happens with "finish" by the native SDK gets reported too quickly, before the handling of the "approved" event was done.

The clue: PaymentTransactionStateFinished occured before Receipt Updated.

I recommend you try without that flag until a fix is in place.

j3k0 commented 2 months ago

There's an attempted fix in branch issue-1568. Unfortunately I wasn't able to reproduce on my device, maybe you need a super fast device or being closer to Apple servers...? (or both)

selcukbeyhan commented 2 months ago

Seeing "Create CdvPurchase..." twice is an indication that the plugin is both "bundled" by the build chain and "loaded" by ionic. This used to create problem but should be fine since version 13.10.0

It looks like a bug with the "autoFinish" flag. A race condition happens with "finish" by the native SDK gets reported too quickly, before the handling of the "approved" event was done.

The clue: PaymentTransactionStateFinished occured before Receipt Updated.

I recommend you try without that flag until a fix is in place.

indeed, without the flag, I see the validator being called now. Thank you for the suggestion :)

billybobilly commented 2 months ago

I'm not setting autoFinish option anywhere. Is it true by default?

j3k0 commented 2 months ago

I'm not setting autoFinish option anywhere. Is it true by default?

It's false by default.