I'm implementing the In-App Purchase feature in my iOS app for the first time. I passed all test phases correctly and I submitted the app to review, but it was rejected for this reason:
Your app offers a content subscription but does not have a mechanism in place to support the requirement that the subscription content be available to the user on all of their iOS devices.
To fix the issue I have to:
include an optional user registration feature to deliver subscription content to all of the user's iOS devices.
My app don't require a user registration, so I'm wondering if it's really necessary.
Assuming the answer is "YES!", I don't know what really I have to do.
After the user registration, I need to save the receipt data on my host and then retrieve the data if the user change device?
Actually I get the receipt data in this way:
func validateReceipt() {
let receiptUrl = NSBundle.mainBundle().appStoreReceiptURL
let isSandox = receiptUrl?.absoluteString.containsString("sandboxReceipt")
if let receipt: NSData = NSData(contentsOfURL: receiptUrl!) {
let receiptdata: NSString = receipt.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
let transaction = GTLAppleTransactionBeanApiAppleTransactionBean()
transaction.receiptData = receiptdata as String
transaction.sandbox = isSandox
ReceiptService().validate(transaction, completion: validated)
} else {
receiptDetected = false
delegate.subscriptionUpdate()
}
}
The "GTLAppleTransactionBeanApiAppleTransactionBean()" is my GAE Bean and "ReceiptService()" call my GAE Endpoint service. This service check the receipt data with Apple.
A Swift example will be very much appreciated. :P
Sorry for my English.
Thanks, Alessio.
Related
When I reinstall the app with my subscription secret unchanged ,
let receiptFileURL = Bundle.main.appStoreReceiptURL
let receiptData = try? Data(contentsOf: receiptFileURL!)
let receiptBase64String = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
guard let stringReceipt = receiptBase64String else { return }
Above section of code gives me the old and expired response before installing the new version. I swear whether I have logged in my sandbox account or not, the result gives me the response
transaction_id : 1000000558259116
original_transaction_id : 1000000558239264
productId : YP20190527_3
renewalDict : {
"auto_renew_product_id" = "YP20190527_3";
"auto_renew_status" = 0;
"expiration_intent" = 1;
"is_in_billing_retry_period" = 0;
"original_transaction_id" = 1000000558239264;
"product_id" = "YP20190527_3";
}
What should I do next if I want to simulate a completely new iPhone and proceed purchases? I guess this old response force me prompting the Apple sandbox login for all time
Updates: When I try to ping sandbox.itunes.apple.com , it gives 100% packet loss. When I login my sandbox account at iTunes and App Store Section of my iPhone setting , I cannot login even I have got correct password and email.
Still, the system status of Apple gives available service of Sandbox iTunes User? I am not sure whether my in-app subscription can be tested using sandbox account
https://developer.apple.com/system-status/
While visiting this link with browser, it say Service unavailable
Could you please tell us when I can test the in-app subscription now ?
I have a paid iOS App.
I need to get the original_application_version number (the first version purchased by the user) from the Apple AppStore Receipt.
To get the receipt, when my app loads, I use checkReceiptFromAppStore() function:
func checkReceiptFromAppStore() {
let receipt = self.getReceipt()
print("receipt Data is: \(receipt)") // prints this: receipt Data is: Optional(5141 bytes)
}
getReceipt() function is the following:
func getReceipt() -> Data? {
if Bundle.main.appStoreReceiptURL != nil {
print("app receipt: \(Bundle.main.appStoreReceiptURL)")
do {
let receiptData = try Data(contentsOf: Bundle.main.appStoreReceiptURL!)
return receiptData
} catch {
print("error converting receipt to Data: \(error.localizedDescription)")
}
}
return nil
}
I've watched WWDC 2017 Advanced StoreKit video about In App purchases and receipt validation and WWDC 2013 Video about using Receipts, read different
resources related to my problem (this, this, this, this, this, this, and this...), but I still don't understand what to do next to get the
"original_application_version" from the App Store Receipt. I need only this field and don't understand why is it so hard to get it.
I've read this too: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html
I think that the receipt is not nil as long at when I run print("receipt Data is: (receipt)") it prints this: receipt Data is: Optional(5141 bytes)
I can suppose that I should parse the receipt to get that field. Can I do it using Decodable? Is there the easiest way to get this original_application_version field? Is it possible to do this without a receipt validation?
I need to get the original_application_version field only to detect the number of the first version bought by the user. If you know any other solutions to get the first purchased version number, I'd be glad to hear them.
I'm developing in Xcode 9, Swift 4, iOS 11
Any answers appreciated, thanks.
All receipt fields are in binary format within application receipt. You should use any kind of app receipt decoder in order to get original_application_version. It is always good thing to validate app receipt before using its contents. For example, you can use RMStore framework (RMStore). It contains receipt validator as well as decoder. Example Obj-C source:
RMAppReceipt *appReceipt = RMAppReceipt.bundleReceipt;
if (appReceipt != nil &&
[appReceipt.originalAppVersion length] > 0 &&
![appReceipt.originalAppVersion isEqualToString:#"1.0"]) {
//Process your original app version
} else {
//Probably it is sandbox mode or app install via Xcode
//Or maybe you should force app receipt refresh
}
There is one thing I don't quite understand when it comes to In-App Subscription purchase.
I obtain the receipt on iOS client like this:
private func loadReceipt() -> Data? {
guard let url = Bundle.main.appStoreReceiptURL else {
return nil
}
do {
let receipt = try Data(contentsOf: url)
return receipt
} catch {
print("Error loading receipt data: \(error.localizedDescription)")
return nil
}
}
And send it for verification to my server (written n Python).
def verify_receipt(self, receipt):
r = requests.post(config.APPLE_STORE_URL, json=receipt)
request_date_ms = DateUtils.generate_ms_from_current_time()
for item in r.json()['latest_receipt_info']:
expires_date_ms = int(item['expires_date_ms'])
if expires_date_ms > request_date_ms:
return True
return False
I'm not sure if this is the correct way of verifying if a subscription is still valid.
I get the expires_date_ms from latest_receipt_info, and if it's greater than the current time in milliseconds, then the subscription counts as still valid.
However what I noticed is that the separate latest_receipt, which is supposed to be equal to the one I have just sent in, is actually changing every time I call the API. But why? I haven't subscribed to anything new, why is the latest receipt changing?
According to the docs:
latest_receipt
Only returned for receipts containing auto-renewable subscriptions.
For iOS 6 style transaction receipts, this is the base-64 encoded
receipt for the most recent renewal. For iOS 7 style app receipts,
this is the latest base-64 encoded app receipt.
If this is against the sandbox then the subscription is auto-renewing on a predefined period. See:
https://help.apple.com/app-store-connect/#/dev7e89e149d
Hopefully the title is self-explanatory. I'm trying to do something like this:
checkIfUserIsSubscribedToProduct(productID, transactionID: "some-unique-transaction-string", completion: { error, status in
if error == nil {
if status == .Subscribed {
// do something fun
}
}
}
does anything like the hypothetical code I've provided exist? I feel like I'm taking crazy pills
Edit
In similar questions I keep seeing a generic answer of "oh you gotta validate the receipt" but no explanation on how, or even what a receipt is. Could someone provide me with how to "validate the receipt"? I tried this tutorial but didn't seem to work.
Edit - For Bounty
Please address the following situation: A user subscribes to my auto-renewable subscription and gets more digital content because of it - cool, implemented. But how do I check whether that subscription is still valid (i.e. they did not cancel their subscription) each time they open the app? What is the simplest solution to check this? Is there something like the hypothetical code I provided in my question? Please walk me through this and provide any further details on the subject that may be helpful.
I know everyone was very concerned about me and how I was doing on this - fear not, solved my problem. Main problem was that I tried Apple's example code from the documentation, but it wasn't working so I gave up on it. Then I came back to it and implemented it with Alamofire and it works great. Here's the code solution:
Swift 3:
let receiptURL = Bundle.main.appStoreReceiptURL
let receipt = NSData(contentsOf: receiptURL!)
let requestContents: [String: Any] = [
"receipt-data": receipt!.base64EncodedString(options: []),
"password": "your iTunes Connect shared secret"
]
let appleServer = receiptURL?.lastPathComponent == "sandboxReceipt" ? "sandbox" : "buy"
let stringURL = "https://\(appleServer).itunes.apple.com/verifyReceipt"
print("Loading user receipt: \(stringURL)...")
Alamofire.request(stringURL, method: .post, parameters: requestContents, encoding: JSONEncoding.default)
.responseJSON { response in
if let value = response.result.value as? NSDictionary {
print(value)
} else {
print("Receiving receipt from App Store failed: \(response.result)")
}
}
As some comments pointed out there's a couple flaws with these answers.
Calling /verifyReceipt from the client isn't secure.
Comparing expiration dates against the device clock can be spoofed by changing the time (always a fun hack to try after cancelling a free trial :) )
There are some other tutorials of how to set up a server to handle the receipt verification, but this is only part of the problem. Making a network request to unpack and validate a receipt on every app launch can lead to issues, so there should be some caching too to keep things running smoothly.
The RevenueCat SDK provides a good out-of-the box solution for this.
A couple reasons why I like this approach:
Validates receipt server side (without requiring me to set up a server)
Checks for an "active" subscription with a server timestamp so can't be spoofed by changing the device clock
Caches the result so it's super fast and works offline
There's some more details in this question: https://stackoverflow.com/a/55404121/3166209
What it works down to is a simple function that you can call as often as needed and will return synchronously in most cases (since it's cached).
subscriptionStatus { (subscribed) in
if subscribed {
// Show that great pro content
}
}
What are you trying to achieve in particular? Do you want to check for a specific Apple ID?
I highly doubt that this is possible through the SDK. Referring to Is it possible to get the user's apple ID through the SDK? you can see that you can't even ask for the ID directly but rather services attached to it.
What would work is caching all transactions on your own server and search its database locally but that would require the app to ask for the user's Apple ID so the app could update the subscription state whenever it launches as it can check for IAP of the ID associated with the device.
However, the user could just type whatever he wanted - and it's unlikely to get this through Apple's app review process.
I am using MKSoreKit https://github.com/MugunthKumar/MKStoreKit for auto-renew subscriptions.but it is in objective c you can check the library code for solution.I am using it in my code and it is working fine.
using below method you can easily check subscription status..
if([MKStoreManager isProductPurchased:productIdentifier]) {
//unlock it
}
It gets the apple id from device and I think that is user specific
I'm trying to implement an application with auto-renewable subscription. Users should pay to access all functions of my application. I already use Parse as backend for my application. It provides some API methods for inAppPurchases but there is nothing said about auto-renewable type. The only thing I have found is some two years old threads in the blog it is said that receipt verification was implemented only for downloadable purchases.
I have tried to use as it called in docs "Simple purchase" and it works fine but I can't figure out how can I check if my user already bought subscription or not.
Does anybody know is there way to do it via Parse API or This should implemented in another way?
As mentioned, receipt validation is only built into the Parse SDK for downloadable content, but it is fairly simple to to create a Cloud Code function that POSTs the app receipt to the iTunes Store for validation. Here are the Apple docs for server side validation: Validating Receipts with the App Store
Here is a what a basic function would look like:
Parse.Cloud.define('validateReceipt', function (request, response) {
var receiptAsBase64EncodedString = request.params.receiptData;
var postData = {
method: 'POST',
url: 'http://buy.itunes.apple.com/verifyReceipt',
body: { 'receipt-data': receiptAsBase64EncodedString,
'password': SHARED_SECRET }
}
Parse.Cloud.httpRequest(postData).then(function (httpResponse) {
// httpResponse is a Parse.Cloud.HTTPResponse
var json = httpResponse.data; // Response body as a JavaScript object.
var validationStatus = json.status; // App Store validation status code
var receiptJSON = json.receipt; // Receipt data as a JSON object
// TODO: You'll need to check the IAP receipts to retrieve the
// expiration date of the auto-renewing subscription, and
// determine if it is expired yet.
var subscriptionIsActive = true;
if (subscriptionIsActive) {
return response.success('Subscription Active');
}
else {
return response.error('Subscription Expired');
}
});
});
See Receipt Fields for details on interpreting the receipt JSON. It's fairly straight forward for iOS 7+, but auto-renewing subscription receipts for iOS 6 and earlier are tedious.