j3k0 / cordova-plugin-purchase

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

Access to the Apple's appStoreReceipt. #1495

Open bushev opened 6 months ago

bushev commented 6 months ago

Hi,

I am currently in the process of upgrading to a newer version of the cordova-plugin-purchase plugin and have encountered an issue concerning the retrieval of the native Apple purchase receipt.

In prior versions, I was able to access the encoded receipt string via product.transaction.appStoreReceipt in the approved(product => ...) callback. However, with the update, I'm only seeing a transaction with certain parsed receipt metadata.

Our server relies upon using the aforementioned string for validation processes. Could you provide some guidance as to how I may retrieve the receipt in this case?

lilmnm-kamikaze- commented 5 months ago

https://github.com/j3k0/cordova-plugin-purchase/issues/1466#issuecomment-1741730584

use app store server api on your server to verify the receipt. the new v13 version of this plugin just sends the cached appStoreReceipt which doesnt change after a purchase. You will need to use the transactionID and send that to an end point to the app store server api. You can get most of the information you need with the apple documentation on the app store server api and a few google searches.

henkkelder commented 4 months ago

I have implemented a validator method on my server and there I am trying to replace the deprecated IOS VerifyReceipt endpoints with logic that calls the app store server api. However I do not understand how I should get a valid transactionID from the plugin.

Does anybody know?

lilmnm-kamikaze- commented 3 months ago

It is the transaction Id in the json response sent to your server when you purchase an item. Such as

{
    // other purchase information
    "transaction":
        "id": "this is where the transaction id will be"
   // other purchase information
} 

I read online the endpoint you would use would be the transactions endpoint and send a request to that endpoint with the transaction id and see if it returns with a valid response (will be a json response with information on the purchase). If not then its invalid and it would show an error of some sort.

lilmnm-kamikaze- commented 3 months ago

Also to note, you must use a jwt when sending that request to the apple server api.

henkkelder commented 3 months ago

In my iPad the validator is called with this: image

The transaction has an id but it is NOT the transaction id. So what transaction id do you mean?

manuelsc commented 3 months ago

I'm in the same boat as @henkkelder

verifyReceipt is being deprecated (https://developer.apple.com/documentation/appstorereceipts/verifyreceipt) and Apple recommends to use https://developer.apple.com/documentation/appstoreserverapi/get_all_subscription_statuses instead.

In order to do that one needs the transactionID or the OriginalTransactonID. However transaction.id when being inside the custom validate method (see screenshot above) is not an transaction ID that can be used with that endpoint to get the subscription status from Apple, I don't know what kind of ID that is (though it is numerical on my end and not "appstore.application").

Would it be possible to expose the transactionID and OriginalTransactionID sow e can migrate away from the deprecated endpoint - Or am I missing something obvious and it is possible to use the new endpoint with the current version of the plugin?

lilmnm-kamikaze- commented 3 months ago

In my iPad the validator is called with this: image

The transaction has an id but it is NOT the transaction id. So what transaction id do you mean?

That is the transaction id (to my knowledge) of your app. When the plugin starts it checks the receipt of you owning the application that it running. even on free apps. The transaction ID in the receipt data (that is sent to the CdvPurchase.store.validator url.

On your server you will get the transaction id after you purchase an in app item. If you are doing local validation i am little help there but the data sent with the .verify() method sends the valid transaction id of the purchase where you see "appstore.application" in the screenshot.

I'm in the same boat as @henkkelder

verifyReceipt is being deprecated (https://developer.apple.com/documentation/appstorereceipts/verifyreceipt) and Apple recommends to use https://developer.apple.com/documentation/appstoreserverapi/get_all_subscription_statuses instead.

In order to do that one needs the transactionID or the OriginalTransactonID. However transaction.id when being inside the custom validate method (see screenshot above) is not an transaction ID that can be used with that endpoint to get the subscription status from Apple, I don't know what kind of ID that is (though it is numerical on my end and not "appstore.application").

Would it be possible to expose the transactionID and OriginalTransactionID sow e can migrate away from the deprecated endpoint - Or am I missing something obvious and it is possible to use the new endpoint with the current version of the plugin?

To my knowledge and testing the get_all_subscription_statuses works for subscriptions and not for consumables which is why i suggested to use the transactions endpoint as it will work win consumables and non-consumables. Also the transaction id is there with the receipt data that is sent to the validation server.

henkkelder commented 3 months ago

I am using the validator method to do validation on my server. That happens each time I initialize the store. Which is nice, because I have noticed that there are users that ask for a refund after installing the non-consumable.

Using the payload (the Body) of the validator I can use the (deprecated) verifyReceipt method that gives me, amongst other things, the transaction Id. That id can be used against appstore api to validate the validity of it.

Are you saying I should act on the Verify() callback from the plugin to get the proper transactionId?

lilmnm-kamikaze- commented 3 months ago

The appStoreReceipt in the json sent to the validator url is the cached receipt when the plugin initializes for the first time (from my experience upgrading my app to use v13.x of this plugin) the transaction id will be the correct id of that current purchase which can be used as is to use with the apple server api.

henkkelder commented 3 months ago

the transaction id will be the correct id of that current purchase which can be used as is to use with the apple server api.

That is not what I think I see. I get what I showed in the image earlier.

Maybe I just don't understand what you are trying to say? Your last reply is one very long sentence. So I don't understand to what part of the sentence the part between the () refers.

manuelsc commented 3 months ago

To my knowledge and testing the get_all_subscription_statuses works for subscriptions and not for consumables which is why i suggested to use the transactions endpoint as it will work win consumables and non-consumables. Also the transaction id is there with the receipt data that is sent to the validation server.

I figured it out, sorry it was my mistake. Leaving it here in case anyone stumbled across the same.

So what put me off was the fact that the ID I got from the plugin had more digits than the "normal" ones I resolved via the deprecated verifyReceipt endpoint to get the originalTransactionID. I was using the _get_all_subscriptionstatuses endpoint but got hit with a {errorCode: 4040010, errorMessage: "Transaction id not found.", retryAfter: 0}

Turns out test purchases from your developer AppleID can't be verified on the production endpoint anymore like it was possible with the verifyReceipt endpoint. After trying this transaction ID against the sandbox endpoint I got it working again.

henkkelder commented 3 months ago

I am not sure I understand. Currently I use the appStoreReceipt against verifyReceipt endpoint to get the original transactionId. Test purchases return 21007 and need to be verified agains the sandbox version.

After that I can use that TransactionId against the https://api.storekit.itunes.apple.com/inApps/v1/transactions endpoint.

But I still need to call the deprecated verifyReceipt to get from the appStoreReceipt to the transactionid. Or am I missing something?

manuelsc commented 3 months ago

I could use the it from transaction.id in the verify method. Unfortunately I'm not sure why it is "appstore.application" on your end, on my end it was an ID that I could use against the endpoint. Did you try checking the id after you made a purchase?

lilmnm-kamikaze- commented 3 months ago

The "appstore.application" is the transaction id of the app when you first load up the plugin. In earlier versions it was set to something else. It checks that te user owns the app eve on free apps. But i agree, something is happening that i am not understanding on his end. I am also able to use the transaction id as is from the plugin that is sent to my validation server. I only see the "appstore.application" as the transaction id when the plugin initializes. which i have logic to see it and not try to validate it further by sending it to apple server api.

@henkkelder Maybe share the code you have to use the plugin in your app. With items changed for privacy and security sake that are pertaining to your app. This way we can help further.

ersingencturk commented 3 months ago

The appStoreReceipt in the json sent to the validator url is the cached receipt when the plugin initializes for the first time (from my experience upgrading my app to use v13.x of this plugin) the transaction id will be the correct id of that current purchase which can be used as is to use with the apple server api.

store.getAdapter("ios-appstore").refreshReceipt(); //refreshes the receipt in the latest version you can use that

henkkelder commented 3 months ago

I must confess I think we are miscommunicating...

As I understand it there are currently 2 ways of validating a purchase.

The verifyReceipt endpoint still works, but is deprecated. See https://developer.apple.com/documentation/appstorereceipts/verifyreceipt.

So I am trying to switch to the storekit endpoint. In order to do so I need a transactionId. But I have not yet found a way that the plugin gives me a valid transactionId when initializing the plugin. And yes, I know I do get a valid one on purchase. But the problem with non-consumables is that a user can ask for a refund. Therefor I want to check the validity of purchases everytime the user visits my 'purchases' page in the app.

Many suggestions here are token about getting an actual receipt, but that is not my problem.

For now I still use the VerifyPurchase method. That works, but somewhere in the future it will stop to work.

lilmnm-kamikaze- commented 3 months ago

There are two ways that i know of that you can check if a product was refunded and that is to use the Get refund history endpoint. you will need a past transaction id of an item that user has made. If you are saving a list of transaction ids on your validation server this would be pretty easy to pull up a past transaction id and get all of the refunds of that user.

The other way to get if a user asked for a refund is to use the refund notification from the Appstore server notofications (which is stated in the refund history endpoint above) Which will send a notification to your validation server as soon as the user gets a refund for the product even if they are not in the app which you can then update your back end with a refunded flag or how ever you choose to do it.

the endpoint is very useful if there is a server outage either on apples side or on yours and you can pull missed refund notifications from that user.

reinos commented 3 months ago

For what is worth; there is a node package that can handle the validation --> https://github.com/apple/app-store-server-library-node

GoodLuc commented 2 weeks ago

I must confess I think we are miscommunicating...

As I understand it there are currently 2 ways of validating a purchase.

The verifyReceipt endpoint still works, but is deprecated. See https://developer.apple.com/documentation/appstorereceipts/verifyreceipt.

So I am trying to switch to the storekit endpoint. In order to do so I need a transactionId. But I have not yet found a way that the plugin gives me a valid transactionId when initializing the plugin. And yes, I know I do get a valid one on purchase. But the problem with non-consumables is that a user can ask for a refund. Therefor I want to check the validity of purchases everytime the user visits my 'purchases' page in the app.

Many suggestions here are token about getting an actual receipt, but that is not my problem.

For now I still use the VerifyPurchase method. That works, but somewhere in the future it will stop to work.

I found out that if the transactionId is coming back as "0", it's because even if you are running the app in your phone through xcode, the purchase is being done through the simulator. To get a valid transactionId number you need to enable the sandbox mode from your AppStore Connect configuration and set up a bogus sandbox AppleId account to use in your app.

Additionally, you need to set the StoreKit configuration on the build to "none" (in Product > Scheme > Edit), otherwise it will run the purchase through a temporary xcode account (that's why the purchase dialog will read "Environment: Xcode". It should read "Environment: Sandbox")

image