How to check In App Purchase Auto Renewable Subscr

2019-01-21 11:23发布

问题:

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?

回答1:

See this for docs:

http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/StoreKitGuide/VerifyingStoreReceipts/VerifyingStoreReceipts.html#//apple_ref/doc/uid/TP40008267-CH104-SW1

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.

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.



回答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.



回答3:

Better to use a local solution before making any calls to the Apple api. Every time the app runs it's good a good practice to validate the local receipt and if you need to check if an user has the active subscription, you can first retrieve the purchases from the local receipt and find out is the purchase is still active for today.

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)
}


回答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.