auto-renewable subscription renew? - ios

there I am new to auto-renewable subscriptions.
I have implemented the subscription. My problem is if the user buys the subscription for the first time app will go premium. but in any case, his second payment is not happened because of any reason. how did I know that the payment was not successful and or successful? I followed the WWDC tutorial for in-app purchases. implemented with storekit 2.
func purchase(_ product: Product) async throws -> Transaction? {
//Begin a purchase.
let result = try await product.purchase()
switch result {
case .success(let verification):
let transaction = try checkVerified(verification)
print(transaction)
print(transaction.expirationDate as Any)
//Deliver content to the user.
await updatePurchasedIdentifiers(transaction)
if !purchasedIdentifiers.isEmpty{
IAP.premiumEndDate = transaction.expirationDate
}
//Always finish a transaction.
await transaction.finish()
return transaction
case .userCancelled, .pending:
return nil
default:
return nil
}
}

In IAP's apple generate a receipt against each transaction containing a lot of information like a digital signature to validate the purchase, history of transactions including successful and unsuccessful transaction, the current status of the subscription, expiry date, and much more.
You have to validate the receipt by fetching from the Apple server to get all this information. you can check this to know how to fetch receipt data.
for validation, we can go with 2 techniques
On-device validation(validation on the device locally if you're not using server in your app)
Server-side validation(Recommended)
you can read more about choosing receipt validation technique here.
When you fetch receipt it will give you a receiptData and you will convert it to base64EncodedString and hit API for receipt.
For SandBox use https://sandbox.itunes.apple.com/verifyReceipt and for production use https://buy.itunes.apple.com/verifyReceipt. pass it two parameters Receipt base64String and App-Specific Shared Secret you can find App-Specific Shared Secret on App store on the top right corner of your in-app purchases list.
You will receive a response containing all information about the subscription.
Check this video of title Engineering Subscriptions from WWDC 2018. This video is really helpful in understanding subscriptions.

Related

Incorrect server-side validate Apple's in app purchase (IAP)

I try to validate ios's in app purchase using server-side, as decribe here: https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html
But I found some user can by-pass this validation. They buy a productId many times (in the fact that this productId is supposed to buy only 1 time), but Apple server said that all receipts are valid (status = 0). It make my revenue report is incorrect.
How could I actually verify a Apple's IAP receipt in more trustable way?

iOS In app purchase: server side receipt validation with multiple purchases of consumable products

Regarding the receipt of IAP consumable products, in the Apple documentation I read: "Information about consumable products is added to the receipt when they’re paid for and remains in the receipt until you finish the transaction. After you finish the transaction, this information is removed the next time the receipt is updated—e.g., the next time the user makes a purchase".
In a server to server receipt validation scenario, suppose the user buys the consumable product "A" and, due to a server outage, the purchase can't be immediately verified and the corresponding transaction is not finished. Then, suppose the user buys again another "A" product while the server is still not working.
From what I understand the IAP receipt is unique for all purchases and can be retrieved in this way:
let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped);
let receiptString = receiptData.base64EncodedString(options: []);
Now, since the receipt is unique and the first transaction is not finished, is the second purchase appended to the first one in the receipt? When it comes the moment to validate the purchases should I send the same receipt to my server for both validation?
Thank you in advance

Auto-Renewable subscription for in-app purchase

After buying a product from in-app purchase (Auto-renewable), I hit API on server to give buying information.
How to know that product is re-new for hitting API ?
When user cancel that subscription, how to known that ?
Swift 3: In order for you to detect when the user has cancelled the subscription you need to do the following;
Download the app receipt
Validate the receipt so you can get the json back containing all the in-app purchases and subscriptions dictionaries
Now inside each receipt there is a field in the dictionary called cancellation_date if this is subscription purchase and otherwise not available for other in-app purchases. If this is nil then there's no cancellation occurred, but if this has a value which contains the cancellation date then a cancellation did occurred and according to apple:
Cancellation Date
For a transaction that was canceled by Apple customer support, the time and date of the cancellation.
Treat a canceled receipt the same as if no purchase had ever been made.
then link below explains all the fields you can use inside the receipts;
https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html
Code Example:
// Create receipt request
let receiptRefreshRequest = SKReceiptRefreshRequest()
// Get the receiptUrl from the main bundle
let receiptUrl = Bundle.main().appStoreReceiptURL
//If the receipt file exist on local device
if (receiptUrl as NSURL?)?.checkResourceIsReachableAndReturnError(nil) == true{
// Get the file as data
let receipt: Data = try! Data(contentsOf: receiptUrl!)
}
now you send the receipt to apple server to validate it using your server as apple recommend. After you get callback from the validation you check the cancellation date.
Apple does not provide anything built into iOS or a REST API that gives you simple subscription details, nor are there any callbacks that you can listen for and respond to in regards to renewal or cancellation. Apple does have an API that, when given a user's local receipt and a “shared secret” generated in iTunes Connect, returns a JSON object of the user's purchase history for your app, including their current subscription information.
More Information check this link

Auto-renewal Subscription in App purchase

I am working on a project for that I need auto-renewal subscription. I am done with back-end work but now my doubt is, "how to get status of subscription. If user cancels or turns off the auto-renewal before renew the subscription". Please help.
You need to parse the receipt of the in-app purchase to check the expiration date. You can parse it yourself from the NSBundle.mainBundle().appStoreReceiptURL or you can send it to apple and parse it from JSON response. See Receipt Validation guide
I have implemented a small library 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)
}

iOS - IAP problems, confused about app store receipt

I made a game. I didnt expect it to become wildly popular so I didn't care to write complicated cryptography code for receipt validation. My game's IAP (to remove ads) was working perfectly in sandbox mode. The app has been released live for about the last 16 hours and there is a problem. The app is behaving as if everyone has purchased the "remove ads" IAP.
this is my extremely basic receipt validation code:
override init(){
super.init()
// storekit delegation
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
if SKPaymentQueue.canMakePayments() {
let request = SKProductsRequest(productIdentifiers: NSSet(object: self.productID))
request.delegate = self
request.start()
}
self.checkReceipt()
}
func checkReceipt(){
if let url = NSBundle.mainBundle().appStoreReceiptURL {
if let receipt = NSData(contentsOfURL: url) {
self.adsRemoved = true
}
}
}
Questions:
in my sandbox environment.. the receipt variable wouldn't get unwrapped unless I had purchased the IAP. self.adsRemoved wasn't being set to true. It seems like on the app store itself, these variables always get unwrapped. What's up with that? What should i do.
I know now that it's standard for IAP to become active a day or two after app submission. Could this be related to that? I know if I hit my restore button nothing happens. In my sandbox environment it asks for credentials.. shows a success message, etc.
in my sandbox environment.. the receipt variable wouldn't get
unwrapped unless I had purchased the IAP. self.adsRemoved wasn't
being set to true. It seems like on the app store itself, these
variables always get unwrapped. What's up with that? What should i do.
Your checkReceipt() method checks for the presence of the receipt at the URL given by -appStoreReceiptURL.
This is problematic because every app that is downloaded from the App Store will contain a receipt, whether the app is free or paid.
This is documented in Apple's Receipt Validation Programming Guide:
When an application is installed from the App Store, it contains an
application receipt that is cryptographically signed, ensuring that
only Apple can create valid receipts. The receipt is stored inside the
application bundle. Call the appStoreReceiptURL method of the NSBundle
class to locate the receipt.
So why did it work for you in sandbox mode? This is because sandbox apps installed via Xcode contains no receipt, until either of the following occurs:
A receipt is requested via SKReceiptRefreshRequest (link)
When an in-app purchase is made, or
When previous transactions are restored via -restoreCompletedTransactions
So in a nut shell, your checkReceipt method will definitely set adsRemoved to true for anyone who has downloaded your game from the App Store, because you're checking for the PRESENCE of a receipt, which for reasons above, does not mean anything at all to you.
What you need to be doing is validating whether the receipt is (1) legit, and (2) whether it indicates that the correct in-app purchase has been made.
I know now that it's standard for IAP to become active a day or two after app submission. Could this be related to that? I know if I hit
my restore button nothing happens. In my sandbox environment it asks
for credentials.. shows a success message, etc.
Not true that IAP takes a day or two to activate after app approval, and no—the problem occurred because you're checking for the presence of the receipt, and not validating the receipt.
Unfortunately, receipt validation is complex, but necessary if you want to achieve what you want to achieve (i.e. check if an IAP has been made). You'd want to refer to this objc.io article on receipt validation for more information about how to actually validate the receipt. At bare minimum, the steps involved are:
Locate the receipt, which is what you're already doing.
Verify the receipt is legit and not tampered with.
Parse the receipt to figure out the IAP has been made, because a receipt exist even without purchasing the IAP
You have to validate the receipt sending the receipt data for Apple provided url. Later, based on the response you will receive you have to set the value self.adsRemoved = true/false.

Resources