3

I have set up a sandbox user and successfully subscribed them to a one-month auto-renewable subscription on my iPhone device. How can I detect whether or not the user's subscription has ended?

I have the following code:

[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];

It will eventually call this method and logic:

- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
    for (SKPaymentTransaction *transaction in queue.transactions) {
        if (transaction.transactionState == SKPaymentTransactionStateRestored) {
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            [self setSubscriptionActive]; // <-- Probably not the right way to restore subscriptions?
            return; // Return because we have successfully set the subscribed state of the app.
        }
    }
    [self purchase:self.validProduct]; // No valid transactions, so ask them to purchase.
}

My problem is that given my current logic, its been two hours now and this test user always gets to the line of code, [self setSubscriptionActive];, thus the only payment I've made under his test account always returns SKPaymentTransactionStateRestored. I would expect it to expire by now according to the documentation from Table 3-1 (Subscription durations for testing)...so now I am wondering, am I doing something wrong, and/or does anyone know what the correct way is of validating whether or not this one-month/timely subscription has expired?

I even tried to JSON query the receipt on Apple's sandbox server, following their Receipt Validation articles. Look at this data carefully and see for yourself how broken this all is:

"original_purchase_date" = "2013-08-01 07:00:00 Etc/GMT";
"original_purchase_date_ms" = 1375340400000;
"original_purchase_date_pst" = "2013-08-01 00:00:00 America/Los_Angeles";
"receipt_creation_date" = "2016-08-18 01:05:09 Etc/GMT";
"receipt_creation_date_ms" = 1471482309000;
"receipt_creation_date_pst" = "2016-08-17 18:05:09 America/Los_Angeles";
"receipt_type" = ProductionSandbox;
"request_date" = "2016-08-18 01:05:09 Etc/GMT";
"request_date_ms" = 1471482309971;
"request_date_pst" = "2016-08-17 18:05:09 America/Los_Angeles";

There's two problems I see:

  1. This is the first purchase, but yet it shows an original_purchase_date of 2013-08-01 07:00:00 Etc/GMT. I purchased it today. This time is nowhere near August 17th. This is very much incorrect.

  2. receipt_creation_date is 2016-08-18 01:05:09 Etc/GMT. request_date is 2016-08-18 01:05:09 Etc/GMT...I would have had to make the web request the split second I created the receipt. I am assuming since request_date (like much of this garbage API) isn't documented its the effective internet time so you can use it to compare (instead of the local time) the delta time between it and the receipt_creation_date. God this is such a nightmare...why would anyone let anyone make such a complicated, ridiculous back-end system like this? This is absolutely horrible. Its such a shame, because Apple's other API's have generally been good.

Alexandru
  • 12,264
  • 17
  • 113
  • 208

3 Answers3

4

Don't calculate the date manually. According to Apple's Validate App Store Receipt, you should use expires_date.

This key is only present for auto-renewable subscription receipts. Use this value to identify the date when the subscription will renew or expire, to determine if a customer should have access to content or service. After validating the latest receipt, if the subscription expiration date for the latest renewal transaction is a past date, it is safe to assume that the subscription has expired.

Justin Jia
  • 91
  • 7
2

you get pending_renewal_info Array from receipt like below.

"pending_renewal_info" =     (
                {
            "auto_renew_product_id" = "YOUR_IN_APP_ID";
            "auto_renew_status" = 0;
            "expiration_intent" = 1;
            "is_in_billing_retry_period" = 0;
            "original_transaction_id" = 1000000XXXXXXXXX;
            "product_id" = "XXXXX";
        },
                {
            "auto_renew_product_id" = YOUR_IN_APP_ID;
            "auto_renew_status" = 0;
            "expiration_intent" = 1;
            "is_in_billing_retry_period" = 0;
            "original_transaction_id" = 1000000398636152;
            "product_id" = "XXXXXXX";
        }
    );

you should check expiration_intent. if expiration_intent is 1 that means your subscription pack is expired. And if expiration_intent key is not available in dictionary that means your pack is still valid.

I hope this will help you.

0

I found a solution since I know my subscription is good for a month, thus I can increment the transaction.transactionDate by a month and compare it to UTC time (by the way, a good idea here is to get the UTC time out of process so that you don't have situations where users circumvent their subscriptions by setting back their system clock):

- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
    // Check all transactions for any that might be restoration candidates.
    for (SKPaymentTransaction *transaction in queue.transactions) {
        if (transaction.transactionState == SKPaymentTransactionStateRestored) {
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            NSCalendar *calendar = [NSCalendar currentCalendar];
            NSDate *expiration = [calendar dateByAddingUnit:NSCalendarUnitMonth value:1 toDate:transaction.originalTransaction.transactionDate options:0];
            if ([expiration earlierDate:self.currentUTCTime] == self.currentUTCTime) {
                [self setSubscriptionInterfaceActive:true];
                return;
            }
        }
    }
    // There are no valid restorations, so we ask the user to subscribe here (this may obviously differ in your application as I am using a Purchase or Restore Subscription button in my app).
    [self setSubscriptionInterfaceActive:false];
    [self purchase:self.validProduct];
}
Alexandru
  • 12,264
  • 17
  • 113
  • 208