How to check In App Purchase Auto Renewable Subscription is valid

31,275

Solution 1

IF you want to check on it from a web server, you ping their API and it returns the status of the auto-renewable subscription and info about the last payment. link

If you are on the device then you probably have to call restoreCompletedTransactions which I guess asks for the password.

I don't see any other method. I suppose from the device you could verify the subscription by contacting the same web service used on the server side? I don't know how the pros and cons of that.

Solution 2

Today, I have trouble with this problem.

Follow Apple doc here, I used this way to check subscription is expired or not. My idea: user APPLE REST API response: (request time + expired time) to check expired or not

+ (BOOL)checkInAppPurchaseStatus
{
    // Load the receipt from the app bundle.
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
    if (receipt) {
        BOOL sandbox = [[receiptURL lastPathComponent] isEqualToString:@"sandboxReceipt"];
        // Create the JSON object that describes the request
        NSError *error;
        NSDictionary *requestContents = @{
                                          @"receipt-data": [receipt base64EncodedStringWithOptions:0],@"password":@"SHARE_SECRET_CODE"
                                          };
        NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
                                                              options:0
                                                                error:&error];

        if (requestData) {
            // Create a POST request with the receipt data.
            NSURL *storeURL = [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"];
            if (sandbox) {
                storeURL = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];
            }
            NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
            [storeRequest setHTTPMethod:@"POST"];
            [storeRequest setHTTPBody:requestData];

            BOOL rs = NO;
            //Can use sendAsynchronousRequest to request to Apple API, here I use sendSynchronousRequest
            NSError *error;
            NSURLResponse *response;
            NSData *resData = [NSURLConnection sendSynchronousRequest:storeRequest returningResponse:&response error:&error];
            if (error) {
                rs = NO;
            }
            else
            {
                NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:resData options:0 error:&error];
                if (!jsonResponse) {
                    rs = NO;
                }
                else
                {
                    NSLog(@"jsonResponse:%@", jsonResponse);

                    NSDictionary *dictLatestReceiptsInfo = jsonResponse[@"latest_receipt_info"];
                    long long int expirationDateMs = [[dictLatestReceiptsInfo valueForKeyPath:@"@max.expires_date_ms"] longLongValue];
                    long long requestDateMs = [jsonResponse[@"receipt"][@"request_date_ms"] longLongValue];
                    NSLog(@"%lld--%lld", expirationDateMs, requestDateMs);
                    rs = [[jsonResponse objectForKey:@"status"] integerValue] == 0 && (expirationDateMs > requestDateMs);
                }
            }
            return rs;
        }
        else
        {
            return NO;
        }
    }
    else
    {
        return NO;
    }
}

Hope this help.

Solution 3

Better to validate a receipt locally before making any calls to the Apple API. Every time the app runs it's a good practice to validate the local receipt and if you need to check whether user has any active subscriptions, you can retrieve all purchases from the local receipt and see if there is a purchase which is still active.

I have implemented a small library written in Swift to simplify to work with In-App Receipt locally. You can easily fetch the object that represents the receipt (InAppReceipt) and retrieve an active purchase/all purchases.

Feel free to use. Github link

Here is an example of solving your problem:

import TPInAppReceipt

do {
    let receipt = try InAppReceiptManager.shared.receipt()
    
    //retrive active auto renewable subscription for a specific product and date
    let purchase = receipt.activeAutoRenewableSubscriptionPurchases(ofProductIdentifier: "ProductName", forDate: Date())
    
    //retrive all auto renewable subscription purchases for a specific product
    let allAutoRenewableSubscriptionPurchases = receipt.purchases(ofProductIdentifier: "productName").filter({ return $0.isRenewableSubscription })
} catch {
    print(error)
}

Solution 4

I am starting a campaign around this issue. Here is my observation and campaign:

Upon auto-renewal, the App Store calls the paymentQueue and posts a transaction. The transaction is posted with transaction.transactionState==SKPaymentTransactionStateRestored.

The issue is that unfortunately this gets posted only to one device. A second device does not get the posting. Therefore, to detect the auto-renewal, or rather to detect the lack of an autorenewal and deny the device a continuing subscription, you have to do a restoreCompletedTransaction or "http post a 64-bit encoded JSON containing the last transaction". If the former, the user needs to give their password; that's intrusive - as you have pointed out above. If the latter, lots of additional coding is required. So, my question is...why doesn't StoreKit have a command:

(does not exist) - [[SKPaymentQueue defaultQueue] restoreAttachedTransactions:(NSArray *)transactions];

This command would flow just like a restoreCompletedTransactions but it would only restore the attached transactions and, most importantly, it would not require log-in by the user. It has the same security protection as the "http post a 64-bit encoded JSON containing the last transaction" and it allows the entire In App Purchase process to be done in StoreKit rather than requiring web posting code.

If this makes sense to you, please suggest how to get this to Apple....thanks.

Solution 5

Late to the party, but since Apple directly has provided a comprehensive back end solution in WWDC 2020, I am compelled to write it here.

The entitlement engine published by Apple works with Node JS back end, and one can roll out their own based on their back end sample code too.

What the entitlement engine does:

  • It takes receipt base64 string as input (request param)
  • It sends it to Apple verifyReceipt endpoint.
  • It parses the decoded receipt fields (see below for some of the fields interpretation)
  • It provides subscription-wise status. Basically, if the subscription.entitlementCode > 0.0, customer is eligible to receive the unlocked content.

To answer the question about receipt fields:

  • is_trial_period field within the Apple verifyReceipt endpoint response is the flag one should check to know if the free trial is in effect.
  • purchase.expires_date_ms tells the subscription expiration date.
  • cancellation_date_ms tells when it was cancelled by Apple support.
  • This link has latest info about various fields in verifyReceipt endpoint response.

Last but not the least, those field names will be different if you are using entitlement engine sample code. So be careful not to mix up the two things.

Share:
31,275
Adam Swinden
Author by

Adam Swinden

Updated on July 15, 2020

Comments

  • Adam Swinden
    Adam Swinden almost 4 years

    I'm looking to implement the new Auto Renewable subscriptions using In App purchase but I am unsure how or when to check if the user is currently subscribed. My understanding is that when the user initially subscribes the app can use the purchase date along with the subscription date to calculate how long their subscription would last. What happens after this date has passed? How do we check if the user has auto renewed or cancelled?

    If I use restoreCompletedTransactions to get a transaction and receipt for each renewal the user will be prompted to enter their iTunes password. Does this mean that if they have bought a 7 day subscription they will have to enter their password every 7 days when the app checks if the subscription is still valid?

  • Adam Swinden
    Adam Swinden about 13 years
    What you describe seems to match what I have found with further playing so I have decided to just implement a server component. Although it is a pain because it requires extra coding, the server component is pretty simple and is probably worth it for the audit trail and receipt verification.
  • Jonny
    Jonny over 12 years
    I think the deal is - you have to use a server component anyway since you have to also supply the shared secret as a parameter in the call to Apple's server when doing the receipt validity check. I don't think you're supposed to put the shared secret in your iOS app client code because then the secret wouldn't be very secret anymore...
  • Ashish P
    Ashish P over 9 years
    Is the shared secret password needed to validate the receipt on both sandbox and production mode for auto renewal subscriptions ? @Jonny
  • Yohan
    Yohan almost 8 years
    Hi you have said "you ping their API and it returns the status of the auto-renewable subscription and info about the last payment" What is the API to check the status in server side ?
  • Dobes Vandermeer
    Dobes Vandermeer almost 8 years
    Did you read the documentation I linked to? I think all the information you need should be linked to from there.
  • philipkd
    philipkd over 7 years
    Thank you. Recommended supplements: NSURLSession tutorial so you can do an asynchronous check raywenderlich.com/110458/nsurlsession-tutorial-getting-start‌​ed and also this on how to test Sandbox subscriptions (toward end): savvyapps.com/blog/…
  • Loint
    Loint almost 7 years
    link doc is unavailable
  • Ali Yucel Akgul
    Ali Yucel Akgul almost 5 years
    Does this still ask for mail/pass of user for itunes?
  • Nirav Bhatt
    Nirav Bhatt almost 4 years
    Was wondering to know how to detect if free trial is in effect/not, and came across your library. It's great! But it seems from issue #18 it is not possible check free trial locally as of today? Will highly appreciate your feedback...
  • tikhop
    tikhop almost 4 years
    Hi @NiravBhatt, sorry for the delayed response. Since some version you can do that just by checking subscriptionTrialPeriod property of the receipt.
  • Nirav Bhatt
    Nirav Bhatt almost 4 years
    thanks @tikhop, I guess the name is is_trial_periodand there is detailed back end example by Apple, I got it here: developer.apple.com/documentation/storekit/in-app_purchase/…
  • tikhop
    tikhop almost 4 years
    @NiravBhatt you can do it either way, depends on validation technique you use
  • Nirav Bhatt
    Nirav Bhatt almost 4 years
    @tikhop sorry i lost context of your github link. Thanks for the clarification.
  • ekashking
    ekashking almost 3 years
    It works!!! Just wanna clarify: request_date_ms is the time when you request the receipt? ... since Apple docs are extremely UNFRIENDLY for the average developers.
  • ekashking
    ekashking almost 3 years
    Check the answer by @Clover03ti05 below. Most people are here for a workable solid block of code that answers the question. And then all we do is copy and paste. The worst thing is a link to apple docs - the most UNFRIENDLY docs for an average developer.
  • nivritgupta
    nivritgupta almost 3 years
    how to verify future transactions in case of auto-renewal subscription
  • Nirav Bhatt
    Nirav Bhatt almost 3 years
    The engine in the link itself has different return values for every possible state. Read about entitlement code in the source file comments.
  • Luke Irvin
    Luke Irvin almost 3 years
    @tikhop Is this safe to use in a live app / it won't be rejected by Apple?
  • tikhop
    tikhop almost 3 years
    @LukeIrvin 100% safe, I use it in all my projects and I personally know around 10 people who use it as well.
  • Luke Irvin
    Luke Irvin almost 3 years
    @tikhop is there an example of getting the product info from a receipt, such as the productIdentifier and price?
  • tikhop
    tikhop over 2 years
    @LukeIrvin there is a tutorial written by someone (fluffy.es/in-app-purchase-receipt-local), it's a little bit outdated but still relevant. You can check github page and see what's changed. Basically you need to get a local receipt and go thru purchases, every purchase contains info you need.
  • Tulon
    Tulon about 2 years
    Can any of you please take have a look on my this question? Thanks! stackoverflow.com/questions/71121487/…