Lost connectivity before receiving purchase receipt from apple In-app purchase - ios

I have implemented In-app purchase in my app. So what I do is, after user completes payment with app store & the control goes to
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
then I update the purchase receipt to our server. Rest of the operations are done by server.
But one of our users faced an issue where he completed the payment but he lost internet connection before the above method was called. Resulting, our app does not know about the payment.
I know that the receipt is stored in the device. But is it the right way to send the receipt to the server from device every time user uses app??

Well, What i did to avoid this was, Keep a Check (like a boolean : transactionInProgress) when the transaction begin and when I have sent data to the server I change the value to "NO".
In this way the next time the app connects. just check if the transactionInProgress was checked, if it is then there was a transaction on going and you can fetch the receipt that must be stored by SKPaymentTransactions using
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
if (!receipt) { /* No local receipt -- handle the error. */ }

Related

iOS - See contents of a receipt

I'm working on handling a scenario, where my app was paid on the app store and now I'm making free to download with in app purchase.
However anyone who has previously purchased I want it to be unlocked for them so they don't pay again.
I have the following code which actually gets the receipt:
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
if (!receipt) {
NSLog(#"NO RECP");
} else {
NSLog(#"GOTACH");
}
However looking at that NDData receipt its all binary data. How can I actually see whats inside that? I've seen various posts about making requests to some url - do I need to do that? I just want to see whatever in that file (hoping for something like version/bundle id when purchased or even date when first purchased).
And my second question, will that receipt only get the receipt for my app, or does it return all receipts on the device?
Thanks.

What is the point of checking the transactions queue if we can just check the receipts?

For Apple in app purchase, it seems we are supposed to observe the transactions queue:
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:
...
}
but we also need to validate the receipt (parsing the content at [[NSBundle mainBundle] appStoreReceiptURL]];). But then... can we just take the shortcut to look at the receipt directly? What's the difference?
To validate the receipt you should create custom server and send the receipt to the server. Also receipt contains all transactions and you should find last transaction in it, it is not very easy way to show to a user some message about purchasing.
I use transactions for statistics and for quick information and the receipt for validation of purchasing.
Also you can use receipt for double check of purchasing, if the receipt is not contain appropriate transaction, you can restrict access to the content.

iOS - Validation for consumable products

I am wondering where I should store the receipt data when the validation for a consumable item failed.
To validate the receipt, we have to send a receipt data which is gained from below code to our server, but for some reasons it might be failed.
// Load the receipt from the app bundle.
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
if (!receipt) { /* No local receipt -- handle the error. */ }
/* ... Send the receipt data to your server ... */
In case for that, we have to store the receipt data in the app to send the receipt data again.
I tried using NSUserDefaults but I found that the data will disappear when the user delete the app itself.
How do you deal with the case?

LocallAPStore IAP cracker - how to protect?

We have a verification server set up that receives purchase receipts from our iOS app over SSL. The receipts are then validated against the iTunes Connect server successfully and all works well. However, if we jailbreak the iOS device and install LocalIAPStore via Cydia, the device can purchase anything without the user being charged. The receipts are still sent to our server and successfully validated, but the user on the device is never charged and no real transaction ever occurs. How could it be that Apple servers successfully verify the receipt?!?
Has anyone ran into this scenario with LocalIAPStore?
You wrote "The receipts are still sent to our server and successfully validated". I suspect that you are being handed the same old receipt over and over again. Your server can log the transaction_id in the receipt and refuse to validate any duplicate transaction_id.
Here is something you can try out even tho this will not prevent them from using it.
if ([[NSFileManager defaultManager] fileExistsAtPath:#"/Library/MobileSubstrate/DynamicLibraries/LocalIAPStore.dylib"]) {
NSLog(#"Local IAP Store detected");
}
This in not very effective, but it might stop someone from doing it. You may want to make your own server (server-sided app) for the app to make it impossible for them to use LocalIAPStore and such.

Auto-renewing subscription: app-store completes transaction despite finishTransaction not called

I'm simulating purchases of an Auto-renewing subscription in my app on an iPhone. My issue is that the purchase is considered to be done by the App store while it is not.
Here is what is going on:
The user presses a button to purchase the renewing subscription
The user gives his iTunes password and confirms the purchase
The app submits the receipt received from the app store to my server to check validity
The server returns a "ok" or "not ok" string. The app calls finishTransaction only on "ok"
I have an issue when there is a network failure on step 3. I can't validate the receipt. But if the user tries to purchase a second time, the app store tells him that he has already subscribed, even though I didn't call the finishTransaction method to complete the purchase!
Is this an expected behavior? Shouldn't the app-store treat non-finished transactions as non-finished, or am I missing something?
I would welcome any suggestion to solve this issue.
-(void) userPurchase:(SKProduct*) product{
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
NSLog(#"paymentQueue updatedTransaction");
for (SKPaymentTransaction * transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
break;
case SKPaymentTransactionStatePurchased:
[self recordSubscription:transaction];
break;
case SKPaymentTransactionStateFailed:
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self recordSubscription:transaction];
break;
default: NSLog(#"Default");
break;
}
};
}
-(void) recordSubscription:(SKPaymentTransaction*)transaction{
NSString *jsonObjectString = [self encode:(uint8_t *)transaction.transactionReceipt.bytes length:transaction.transactionReceipt.length];
NSMutableDictionary *params = [[NSMutableDictionary alloc] initWithObjectsAndKeys:jsonObjectString,#"receiptdata", nil];
[[AFNetworkSubClass sharedClient] postPath:#"myserver" params:params
success:^(AFHTTPRequestOperation *operation, id output) {
/* some code */
if([valstring isEqualToString:#"ok"]){
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
}
}failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"validation failed");
}
I think before you call recordSubscription method, you can call finish finishTransaction method to remove payment from payment queue because you have finished the purchase action with apple server.
If network failure make you can't validate app receipt, just record the receipt and validate the receipt to your own server again or validate when network is reachable another time.
I hope it can help you.
Users cannot purchase same subscription until it expires. In your case, the purchasing transaction was finished, but you didn't provide the service because of the network failure.
I used to have similar problem. You may need to ask the users to restore all completed transactions.
Then completeTransaction function will be called. Good luck!
If you haven't called finishTransaction that doesn't meant the transaction has not taken money from the user. All that call does is prevent StoreKit from notifying your App on the transaction every launch. Its just a means for you to tell StoreKit that you have finished with the transaction i.e. unlocked the users content and saved that to your backend etc.
So even with a network error you should be retrying your API call. If you close the App and reopen you should get a transaction update to indicate it has been 'purchased' giving you time to submit to your server again:)
This is the expected behaviour otherwise you would be double/triple billing users.
Just a heads up if your App doesn't fit within the following statement then you will not be able to use Auto-renewing Subscriptions. The following is from the App Review Guidelines;
11.15 Apps may only use auto renewing subscriptions for periodicals (newspapers, magazines), business Apps (enterprise, productivity,
professional creative, cloud storage) and media Apps (video, audio,
voice), or the App will be rejected
In case your app does fit into this bracket then what you could do setup your app so it considers itself to be temporarily "Subscribed" and it keeps trying to authenticate with the server (notify the user if it has been too long since it was connected to the internet).
I used to have issues like this with both the app store purchases and the game center. And I know this is going to sound like a non answer, but it worked for me.
I deleted the app and all its data from my device, restarted xcode and cleaned the project. Turned the device on and setup a fresh deployment. And the app started to receive the correct response from the App Store. I'm not sure if it was because something was cached on the device or if the App Store just needed more time to work correctly but things just fell into place after that.
Although I have never worked with subscription based purchases, nothing in your code stands out as incorrect as far as I can tell.
Good luck.

Resources