Understand the serverVerificationData in_app_purchase Flutter

487

It looks like either your receipt is not correct ( sandbox has issues sometimes ) or your server-side setup is wrong

For the first point, you can try creating a receipt by generating a storeKit config file. This can't be done in flutter, you have to open iOS module with code and setup storekit config file by going here.

After setting up the storekit file, you can either run the app from xCode directly or just close xCode and run from your preferred flutter IDE

Now, iOS will never hit the production purchase identifiers when you try to fetch/buy products from the app, and instead fetch the products from your storekit config and generate a receipt from those. This receipt is accepted by apple sandbox verification endpoint, you can also test refunds and subscription cancellations from xCode using a storekit config.

For the second point, you have to enable the app specific shared secret in iTunes connect and then use that in the 'password' key in the receipt validation API. Here is where you find it AppStoreConnect > Your app > Subscriptions

If it still doesn't solve the issue, I'd be happy to assist further.

App specific shared secret

EDIT: I just tested purchasing an auto renewable subscription purchased in sandbox (not storeki t) and then validating it using the sandbox URL and it returned the correct receipt data. In your post above, you don't need to base64 encode the purchaseDetails.verificationData.serverVerificationData since its already encoded. Have you tested this on postman? It Works there

EDIT: so the request is malformed because you are not sending data as String so you need to dump the dict :

request_body = json.dumps({"receipt-data": token})
Share:
487
Tom3652
Author by

Tom3652

Updated on January 04, 2023

Comments

  • Tom3652
    Tom3652 over 1 year

    I have done all the setup to verify my receipts server side (in python with the requests package).

    Here is my code :

    url = "https://buy.itunes.apple.com/verifyReceipt"
                    request_body = {"receipt-data": token}
                    headers = {'Content-Type': 'application/json'}
                    response = requests.post(url=url, headers=headers, data=request_body)
    

    The only variable here is token that is sent from the client (flutter) and that is : purchaseDetails.verificationData.serverVerificationData.

    I can't verify the receipt because if i pass token as string, i receive always a 21002 error (malformed).
    If i try something like this in python :

    token = base64.b64encode(token)
     
    

    It throws this error : a bytes-like object is required, not 'str' which i don't understand because i am actually passing a bytes object.

    What is the correct format to pass to the POST request to verify the iOS receipt ? Is the flutter one correct or should we encode something ?

    Any concrete example will be accepted because i could not find any.

    PS : i am redirected to the sandbox url "https://sandbox.itunes.apple.com/verifyReceipt" if the production one fails. The sandbox response is the same as the production one (statusCode: 21002)

    • Akshay
      Akshay almost 2 years
      Have you added the password key in the verifyReceipt endpoint ?
    • Tom3652
      Tom3652 almost 2 years
      No i haven't because i have no subscription and it seemed to be used for subscription right ?
  • Tom3652
    Tom3652 almost 2 years
    Hi thanks for the detailed answer, so basically you are telling me that i have a malformed receipt because i don't use the storekit and flutter IAP generates a "Test" receipt when purchasing from an app in debug mode ?
  • Tom3652
    Tom3652 almost 2 years
    Also, when you say "iOS will never hit the production purchase IDs", flutter IAP seems to be retrieving those because i can fake the purchase till the end (it shows the App store sandbox dialog with my product from App Store connect). Or do you mean that i should actually not hit production but the storekit during tests ? And if so, how can i be sure that my app will be validating receipts once on the store ?
  • Tom3652
    Tom3652 almost 2 years
    Could you also clarify this part please : ` #if DEBUG let certificate = "StoreKitTestCertificate" #else let certificate = "AppleIncRootCertificate" #endif ` Does it mean that Flutter IAP will automatically "sign" receipt with this certificate ? And this way Apple will validate my receipts ? - How / where should i add this please ? In the AppDelegate.swift ? Thanks !
  • Akshay
    Akshay almost 2 years
    @Tom3652 Storekit is used to test your purchases locally and doesn't even require internet. Have you tried purchasing with the sandbox tester account on a real device? I don't think the simulator will work in that case. As you test in-app purchases, StoreKit in Xcode generates receipts that are valid only in the test environment. Your app can validate the receipts locally by using a certificate that Xcode provides. Important You can’t validate receipts from the test environment with verifyReceipt because App Store doesn’t sign these receipts, and the verification fails.
  • Akshay
    Akshay almost 2 years
    @Tom3652 I just tested purchasing an auto renewable subscription purchased in sandbox (not storekit ) and then validating it using the sandbox URL and it returned the correct receipt data. In your post above, you don't need to base64 encode the purchaseDetails.verificationData.serverVerificationData since its already encoded. Have you tested this on postman?
  • Akshay
    Akshay almost 2 years
    @Tom3652 I was able to replicate the status code 21002 by removing the "password" field from the request on postman. Have you tried fetching the base64 and calling the sandbox API on postman instead? the issue is definitely server side because I used the same code to fetch my receipts and send to my server.
  • Tom3652
    Tom3652 almost 2 years
    Alright @Akshay, thank you very much for taking time on this. You were right, with Postman i am getting a 0 status code everything is working. When i have tried running a simple POST request in python, this didn't work.
  • Tom3652
    Tom3652 almost 2 years
    Oh i have found the issue, i should do request_body = json.dumps({"receipt-data": token}) before passing it to the request.... It was as simple as that ! I am granted you the bounty for trying to help me. Update your answer with this and i will accept as well :)