Check if an Auto-Renewable Subscription is still valid
Here is several ways to do receipt validation to check is user granted to subscription. Here is two ways of doing it correctly:
- Do receipt validation locally as it is written here.
Do receipt validation remotely as it is written here. It is mentioned that receipt should not be sent to App Store within an app. Short summary:
- Your app sends receipt to your backend.
- Your backend sends receipt to Apple backend for validation.
- Your backend gets response from the apple.
- Your backend sends result back to your app is receipt valid or invalid.
In both ways you will get list of in-app purchases. It will contain expired subscriptions as well. You would need to go through all subscriptions and check expiration dates. If it is still valid you must grant user with subscription.
As I understand you are using SwiftyStoreKit and here is open task for local receipt validation.
How to check In App Purchase Auto Renewable Subscription is valid
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.
How to detect and verify a renewal for an auto-renewable subscription?
My experience. Let's assume, we always send initial receipt to Apple's server.
In any case, you'll get JSON with at least two fields: status
(no comments) and receipt
(information about receipt that you've send).
Additionally to that:
1) If the subscription is still active, you'll additionally get latest_receipt
(base64-encoded string) and latest_receipt_info
(information about that receipt).
2) If the subscription is already expired, you'll additionally get latest_expired_receipt_info
(information about last renewing receipt). Yes, you get only information about it, no base64-encoded string.
And yes, AFAIK, that's not documented anywhere. Hope that helps.
How to check purchase status of In-App Purchase (Auto Renewing Subscription)?
The only way to check the subscription status is to verify the receipt with Apple to see if it's still valid using their /verifyReceipt
endpoint - docs.
What you could do is cache some expiration date after the purchase and use that to check if the subscription is valid. If it's passed the expiration date you can re-check the receipt with Apple to see if it's renewed. There are also edge cases where a user is refunded and their subscription is cancelled before the expiration date - you should update your receipts periodically with Apple to check this case. Ideally, this should all be happening server side as well to avoid piracy.
Here's a great post that summarizes the nauces of Apple subscriptions very well: iOS Subscriptions are Hard
Swift How to handle Auto-renewable Subscription receipt and validation
If you do not have the possibility to use a server, you need to validate locally. Since you are already included TPInAppReceipt library, this is relatively easy.
To check if the user has an active premium product and what type it has, you can use the following code:
// Get all active purchases which are convertible to `PurchaseType`.
let premiumPurchases = receipt.activeAutoRenewableSubscriptionPurchases.filter({ PurchaseType(rawValue: $0.productIdentifier) != nil })
// It depends on how your premium access works, but if it doesn't matter what kind of premium the user has, it is enough to take one of the available active premium products.
// Note: with the possibility to share subscriptions via family sharing, the receipt can contain multiple active subscriptions.
guard let product = premiumPurchases.first else {
// User has no active premium product => lock all premium features
return
}
// To be safe you can use a "guard" or a "if let", but since we filtered for products conforming to PurchaseType, this shouldn't fail
let purchaseType = PurchaseType(rawValue: product.productIdentifier)!
// => Setup app corresponding to active premium product type
One point I notice in your code, which could lead to problems, is that you constantly add a new SKPaymentTransactionObserver
. You should have one class conforming to SKPaymentTransactionObserver
and add this only once on app start and not on every public call. Also, you need to remove it when you no longer need it (if you created it only once, you would do it in the deinit
of your class, conforming to the observer protocol.
I assume this is the reason for point 2.
Technically, the behavior described in point 3 is correct because the method you are using asks the payment queue to restore all previously completed purchases (see here).
Apple states restoreCompletedTransactions()
should only be used for the following scenarios (see here):
- If you use Apple-hosted content, restoring completed transactions gives your app the transaction objects it uses to download the content.
- If you need to support versions of iOS earlier than iOS 7, where the app receipt isn’t available, restore completed transactions instead.
- If your app uses non-renewing subscriptions, your app is responsible for the restoration process.
For your case, it is recommended to use a SKReceiptRefreshRequest
, which requests to update the current receipt.
Auto-Renewable Subscription never expires in Sandbox
The only way to determine if the subscription is still active for the user is through receipt validation.
The best way to do this is to store the receipt file on your server and periodically refresh it with Apple's
/verifyReceipt
endpoint to see if anything has changed. (See iOS Subscriptions Are Hard for more detail).If you are just looking for something quick and dirty, you can do the validation locally, and store the expiration date of the subscription in User Defaults. On launch check if the device time is before the expiration date. You'd only need to revalidate if the device time is after the expiration date to check if the subscription renewed.
The second approach is an insecure, non-scalable way to work with subscriptions, but it would "work" for a little side project. The recommended way is through server-side receipt validation and status tracking mentioned in #1.
Related Topics
Why Is Swiftui Picker in Form Repositioning After Navigation
Ios11 Causing Cors Issues in All Mobile Browsers
How to Wait for Method That Has Completion Block (All on Main Thread)
Presenting Uialertcontroller from Uitableviewcell
Apple MACh-O Linker & Ditto Error - Xcode 8
To Change the Color of Unselected Uitabbar Icon in iOS 7
Using Apple's Reachability Class in Swift
Customizing the Mkannotation Callout Bubble
External Framework File/File.H (Parse/Parse.H) File Not Found
Status Bar Visible on iPad Mini Despite Setting Uiviewcontrollerbasedstatusbarappearance to No
Zip and Unzip a File Programmatically in iOS
Autorelease Pools and When Release Is Called Under iOS
Setneedsdisplayinrect: Causes the Whole View to Be Updated
Is It Just the iPhone Simulator That Is Restricted to Intel Only MAC'S