In application I need to implement auto-renewable subscriptions purchases.
As I understand documentation, I should check AppStore receipt on app launch and then validate that receipt with my server.
In my code I do following to retrieve receipt:
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
Sometimes receipt is becoming nil.
For example:
I've launched an app and made purchase. After that the code above returns non empty receipt.
App is stopped with Xcode.
On next app launch receipt is nil.
I do not understand, why this happens. receiptURL points to file with receipt, but [NSData dataWithContentsOfURL:receiptURL] returns nil.
If I request receipt refresh, then app shows dialog for entering Itunes credentials. This is not good, because this dialog will appear on app launch and this is unexpected behavior.
Am I doing something wrong?
Or maybe this is sandbox-specific case?
In production there will always be a receipt (even if the app is free), that can contain your additional purchase, in case you have bought something or restored from an already purchased item.
In test environment until you buy something the receipt is nil, if you want to test a case close to production, you should first try a restore (now you will have a receipt) and do what you need.
Here it is suggested that your payment procedure is being hacked, by calling the SKPaymentTransactionStatePurchased regardless. You need to validate the receipt on a server before accepting any receipts.
You can find a great description of receipts and hacks here.
Related
I am new to iOS programming and new to in-app purchase.
I am using auto renewal subscription in my app, in which after successful subscription I am verifying receipt through which I update my server for services, so basically my problem is that is user uninstall app or change device then if I restore transaction or refresh receipt it prompt for app store credentials, by this I got the receipt but I don't want to do that instead I want to verify receipt without prompt. So, is there any way to verify receipt?
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
If am using this it gives nil on new app installation, also let me know if I send receiptURL data to my server and through which how can I rewrite that data to [NSBundle mainBundle] on new device or new installation.
Also I am using RMStore library.
Thanks in advance.
There's really only one work-around for this. You link the purchase to a user account on your system and store the purchase date. When they launch your app you know if their subscription is active or not. If it's not active in your system then you need to prompt them to renew.
Otherwise there is no way to do this without querying iTunes, which requires them to authenticate.
It's very common, many of the apps I use require me to "validate purchases" from time to time.
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.
I am currently validating transaction.transactionReceipt base64encoded as a string server side. The problem is that transaction.transactionReceipt is deprecated now and I don't know what to replace this with for in app purchases.
The new documentation suggests using NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
, but the docs say that it is only for app purchase validation and not for in app purchases.
What do I do in this case?
As can be read in other parts of the documentation the receipt does contain the data for IAP as well. I've also verified this from my own experience, as this is the way we did it where I worked. Basically the only change is that instead of having a receipt for each IAP there is a single one for both the app and all IAPs.
My guess however is that the URL remains the same, although I haven't verified it, but the data contained at the URL changes.
I am trying to get the new inapp purchasing API (ios 7+) to work and I see that the receipt data is so much larger when I read receipts in this way:
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
I am doing this inside of:
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
So what I noticed is I send the receipt to
"https://buy.itunes.apple.com/verifyReceipt";
And the data I'm sending is much larger than transaction.transactionReceipt.
In fact, the apple response seems to have dozens of receipts in the "in_app" array. Is this normal? I have finished these transactions yet there is so many receipts in the unified receipt. Is that right? Would my big spenders (those who buy thousands of IAPs) be sending huge receipts with thousands of transactions in them?
Also what is SKReceiptRefreshRequest for?
Update! I found out that in a live environment, all consumable receipts are removed after they calling finishTransaction. On the sandbox environment this is not the case, and it just keeps them, so the number of receipts can get into the hundreds.
iOS7 introduced many IAP changes. Amongst those:
1) receipts are stored in the app bundle (yes, plural on receipts)
2) whether a particular receipt persists in the bundle depends on the kind of product. Eg, I think receipts for consumables don't get persisted
What kind of products do you have?
Look up the API for the receipt refresh. Sometimes the receipt is not in the bundle, and you can refresh it. The user gets prompted for apple id/password so it can be annoying for the user if/when you use it.
Yes, this is normal
Apply sends you a bunch of all receipts. You have to figure out your own what to do with these. That's because even if the transaction is finished you might need the receipt to handle the content of the purchase.
I'm implementing security in my iOS 7 application for iOS In-App purchases. When testing with LocalIAPStore hack I see that when I call the method [[NSBundle mainBundle] appStoreReceiptURL] the returned receipt is null, so it's all right here.
The problem comes when the user purchases an option without having activated LocalIAPStore. If this is done, a receipt is generated. Then, I send it to my server to check that everything is correct and show purchase, but if active again LocalIAPStore, I keep returning a correct receipt, but has actually purchased another option and this is returned as a calidate.
To verify receipt, what else can I do? What should I check locally to avoid it? Any tips? How do you do it?
Thanks!