Skip to content

in_app_purchase: How to validate receipts on iOS? #77232

Closed

Description

@ValeriusGC

Hi!
I've made flutter app with auto-renewable subscriptions. It works well on Android, but from Appstore i received this letter:
photo_2021-03-04_14-11-52
Please give me advice, how to deal with it?

Activity

  1. changed the title [-]in_app_purchase: How validate receipts on iOS?[/-] [+]in_app_purchase: How to validate receipts on iOS?[/+] on Mar 4, 2021
  2. nt4f04uNd commented on Mar 4, 2021

    @nt4f04uNd
    Member

    duplicate #61339

  3. nt4f04uNd commented on Mar 4, 2021

    @nt4f04uNd
    Member

    oh actually i think no. they are similar and can be closed by one PR but not duplicate

  4. ValeriusGC commented on Mar 4, 2021

    @ValeriusGC
    Author

    duplicate #61339

    There is very laconic advice there. I have no idea how to realize this in flutter neither local nor server... (((
    Maybe anyone flutter guru can send me steo-by-step lesson or so to deal with this quest?
    Actually a lot of dart pub libraries (such a local_auth, etc) have samples what code one need to write in native - swift or kotlin to achive a result, but not in_app_purchase alas. I really got stucked. Some double-trouble it is.

  5. nt4f04uNd commented on Mar 4, 2021

    @nt4f04uNd
    Member

    @ValeriusGC this actually goes beyond what flutter handles, and this is really up to you how you handle purchase validation

    here are basic steps to get server validation:

    1. get serverVerificationData from in_app_purchase - on iOS this is base64 encoded string
    Details
    if (purchaseDetails != null) {
      var verificationData = purchaseDetails.verificationData;
      if (Platform.isIOS && verificationData == null) {
        verificationData = await InAppPurchaseConnection.instance.refreshPurchaseVerificationData();
      }
      final bool subscriptionActive = 
        Platform.isAndroid || // we don't necessarily need validation on android
        await validatePurchase( // call to backend
        verificationData.serverVerificationData
      );
    // ...
    }
    1. on server setup such function (i use nodejs firebase cloud functions for example)
    Details
    /**  
    *  This function validates the user purchase on iOS.
    *  It queries the data from the Apple verifyReceipt method, checks whether subscription is still active
    *  and returns a boolean value.
    */
    export const validatePurchase = functions.https.onCall(async (data, context) => {
       const { verificationData } = data;
       if (verificationData === undefined) {
          throw new functions.https.HttpsError('invalid-argument', 'Required `verificationData` field is missing');
       }
       const requestData = JSON.stringify({
          'receipt-data': verificationData,
          'password': itunes.password, // this is apple shared secret key
          'exclude-old-transactions': true,
       });
       const headers = {
          'Content-Type': 'application/json',
          'Content-Length': requestData.length,
       };
       let res = await axios.post('https://buy.itunes.apple.com/verifyReceipt', requestData, { headers: headers });
       if (res.data.status === 21007) {
          res = await axios.post('https://sandbox.itunes.apple.com/verifyReceipt', requestData, { headers: headers });
       }
       const latestInfo = res.data?.latest_receipt_info;
       const pendingRenewalInfo = res.data?.pending_renewal_info;
       if (!latestInfo || !pendingRenewalInfo || !latestInfo[0] || !pendingRenewalInfo[0]) {
          functions.logger.log(`{ verificationData: ${verificationData}, status: ${res.data?.status}, false, description: no latest_receipt_info or pending_renewal_info }`);
          return false;
       }
       const graceMs = pendingRenewalInfo[0].grace_period_expires_date_ms;
       const now = Date.now();
       if (graceMs) {
          const value = now < graceMs;
          functions.logger.log(`{ verificationData: ${verificationData}, ${value}, description: grace period }`);
          return value;
       }
       const expiresMs = latestInfo[0].expires_date_ms;
       const value = now < expiresMs;
       functions.logger.log(`{ verificationData: ${verificationData}, ${value}, description: default period (no grace) }`);
       return value;
    });

    on how to get shared secret see https://developers.facebook.com/docs/app-events/getting-started-app-events-ios/app-shared-secret/

  6. nt4f04uNd commented on Mar 4, 2021

    @nt4f04uNd
    Member

    local validation:

  7. ValeriusGC commented on Mar 4, 2021

    @ValeriusGC
    Author

    It turns out that, that no way to realize it without making my own server?

  8. nt4f04uNd commented on Mar 4, 2021

    @nt4f04uNd
    Member

    yes, refer to #77232 (comment)

  9. nt4f04uNd commented on Mar 4, 2021

    @nt4f04uNd
    Member

    i reconsidered, it's actually a duplicate of #61339 @TahaTesser

  10. ValeriusGC commented on Mar 4, 2021

    @ValeriusGC
    Author

    yes, refer to #77232 (comment)

    I am really shocked! Do thousands of lone programmers build their own servers to get subscriptions? don't make me wrong, please, i can not believe this is really so complex.

  11. nt4f04uNd commented on Mar 4, 2021

    @nt4f04uNd
    Member

    i didn't say it's complex. you can always implement it yourself by writing some native code
    i updated my previous comment a bit as well

  12. nt4f04uNd commented on Mar 4, 2021

    @nt4f04uNd
    Member

    summarized all from above in #61339 (comment)

  13. added
    P3Issues that are less important to the Flutter project
    on Mar 4, 2021
  14. mvanbeusekom commented on May 20, 2021

    @mvanbeusekom

    Apple and Google both highly recommend handling the verification of receipts on the server-side. Since yesterday a codelab is available which will take you through the steps on setting up server-side receipt verification using Firebase. Even if you don't want to use Firebase as a backend solution, the codelab might be helpful as it discusses all steps necessary to verify a receipt in detail.

    More information can be found in the store specific documentation:

    Local validation is also possible as the in_app_purchase API provides all the data necessary to do so, however since it is not recommended no examples have been worked out.

    I will close this issue as I feel with the codelab there is clear information on how to handle the verification (be it server-side). However please don't hesitate to leave a comment if you have any more specific questions and I would be happy to re-open it.

  15. github-actions commented on Aug 1, 2021

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  16. locked as resolved and limited conversation to collaborators on Aug 1, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Issues that are less important to the Flutter projectp: in_app_purchasePlugin for in-app purchasepackageflutter/packages repository. See also p: labels.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions