iOS receipt does not include repurchased transaction - ios

If I wipe the data in my app, then re-purchase some managed IAP that I know the account already owns, iOS gives me the native "are you sure you wish to repurchase this item you will not be charged" dialog. That's as expected.
When the purchase returns to my app, I get the receipt with [[NSBundle mainBundle] appStoreReceiptURL]. I then attempt to verify the SKPaymentTransaction's transactionIdentifier with the receipt via my server.
However the receipt's transaction ID for this IAP, because I've already purchased it long ago, is not the same as the SKPaymentTransaction.
How should I be verifying that this is a valid repurchase? Can I get a signed receipt for this repurchase somehow?

All the purchases will be in a single receipt. You do have to check all iAPs there and look for the needed ones.
If you want the id of the original iAP transaction, there is Original Transaction Identifier
For a transaction that restores a previous transaction, the transaction identifier of the original transaction. Otherwise, identical to the transaction identifier.
More information about receipt fields is here: https://developer.apple.com/library/prerelease/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html
Anyway, does it really matter whether this is an original purchase or a repurchase? My checks of this are usually the same.

As far as I understand it you should check the originalTransaction to check that this is valid for transactions that are SKPaymentTransactionStateRestored.
[[NSBundle mainBundle] appStoreReceiptURL] is for the receipt of the main application, not the IAP.

You can use RMStore library, and check all purchases:
RMStoreAppReceiptVerificator *verificator = [[RMStoreAppReceiptVerificator alloc] init];
if ([verificator verifyAppReceipt])
{
NSArray *inAppPurchases = [RMAppReceipt bundleReceipt].inAppPurchases;
for (RMAppReceiptIAP *inAppPurchase in inAppPurchases)
{
NSLog(#"productIdentifier %# originalPurchaseDate %#", inAppPurchase.productIdentifier, inAppPurchase.originalPurchaseDate);
}
}
If your receipt is nil you can refresh receipt:
[[RMStore defaultStore] refreshReceiptOnSuccess:^{
//get purchases
} failure:^(NSError *error) {
}];

Related

inApp purchase, dowload free products

I am implementing inApp features with download hosted content feature. All products are free for unlimited time. Each product have one download associated and that is audio file.
Is possible to start download without user see dialog to buy free product. First user must give his apple id password, than he see dialog to buy free product and than dialog that product is succesfully purchased.Than download can be requested.
But since all products are free can somehow download be reuested without user see confiramtion dialogs and messages and start download imedietly.
If item was previously be purchased/downloaded than user see message that will buy free item again. I know for restore functionalities, but do I need to track previously downloaded items and in that case start download from restore state.
Thanks!
1) Yes, You can download without dialogs. get product detail using store kit, and if product price is free (0), download item without processing purchase request from Storekit.
Code to request product detail:
KProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers: [NSSet setWithArray:productIdentifiers]];
// Keep a strong reference to the request.
self.request = productsRequest;
productsRequest.delegate = self;
[productsRequest start];
// SKProductsRequestDelegate protocol method
- (void)productsRequest:(SKProductsRequest *)request
didReceiveResponse:(SKProductsResponse *)response
{
self.products = response.products;
for (NSString *invalidIdentifier in response.invalidProductIdentifiers) {
// Handle any invalid product identifiers. or check that product you are looking for dozens has its identifier here
}
for (SKProduct *product in self.products)
{
//for example, product you are looking for has identifier "com.product.free"
if([product.productIdentifier isEqualToString:#"com.product.free"] && [product.price compareWithInt:0])
{
// Startdownloading without purchase
}
}
}
2) If you have non-consumable inappt purchase, you must provide option for restore. for downloading, you can write your own logic. check if item is downloaded and re-download only if its not available.

Apple In-App Purchase Receipt - validation on the server-side

I have a problem with validating an apple receipt on the server-side.
I tried to find a solution in the internet, but haven't succeeded.
So, description:
First of all, application is made for iOS7. Secondly, I have a few items ( type = Non-Renewing Subscription ). So user can buy one or multiple items and then he should manually renew them ( buy again ).
Applications sends a receipt to the server-side, I make a request to the Apple and get the result with a lot of in_app receipts. Something like:
"in_app":[
{
"quantity":"1", "product_id":"...", "transaction_id":"...",
"original_transaction_id":"...", "purchase_date":"...",
"purchase_date_ms":"...", "purchase_date_pst":"...",
"original_purchase_date":"...",
"original_purchase_date_ms":"...", "original_purchase_date_pst":"...",
"is_trial_period":"..."},
{
"quantity":"1", "product_id":"...",
"transaction_id":"...","original_transaction_id":"...",
"purchase_date":"...", "purchase_date_ms":"...",
"purchase_date_pst":"...", "original_purchase_date":"...",
"original_purchase_date_ms":"...", "original_purchase_date_pst":"...",
"is_trial_period":"..."}
]
So, each "receipt" in "in_app" has transaction_id. But how I can identify the transactionId of the current purchase? I would like to validate it as well and make sure that this is unique.
My concern is: if somebody will get one valid receipt, he will be able to hack our server-side API and make unlimited numbers of in-app purchases with the same valid receipt.
Should I somehow decrypt and check for transaction_id the "original" receipt, the one what I send to Apple for verification?
Any help/suggestions would be highly appreciated.
Thank you in advance.
Regards,
Maksim
#Doug Smith
https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html
If you go through the different fields on this page, you will find
Original Transaction Identifier::
For a transaction that restores a previous transaction, the transaction identifier of the original transaction. Otherwise, identical to the transaction identifier.
This value corresponds to the original transaction’s transactionIdentifier property.
All receipts in a chain of renewals for an auto-renewable subscription have the same value for this field.
So for your non-auto renewable subscriptions, you have to keep track of two things on your server side:
The original transaction identifier of the receipt that you are validating with itunes server, associate this with the user Id in your database.
Whether the request that you received from the client side is of a Purchase or of a Restore Purchase.
Once you have these two things with you, you can write your logic on these two parameters like below:
::If a request is of type "Purchase" and you already have the original transaction identifier of that receipt associated with some other user Id, you can block that purchase.
::If a request is of type "Restore Purchase" and request is coming from the same user id against which the original transaction identifier is associated in your DB than allow him otherwise block his restore.
Furthermore, you can derive your own logic based on these things, according to your needs.
Let me know if you have any further doubts.
For each new transaction apple send a new receipt which is unique, encode it so no one can forge data.
Get the transaction receipt from the completed transaction encode it and send it to your server, and on the server side decode it and match with the one apple send to server.
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
_transactionArray = transactions;
for (SKPaymentTransaction * transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased: {
NSData *receipt = transaction.transactionReceipt;
[self sendReceiptToServer];
} break;
case SKPaymentTransactionStateFailed: {
// finish this transaction
} break;
case SKPaymentTransactionStateRestored:
NSData *receipt = transaction.transactionReceipt;
[self sendReceiptToServer:receipt];
} break;
default:
break;
}
};
}
-(void)sendReceiptToServer:(NSData *)receipt {
// encode receipt
// send receipt to server
// add success and error callback
}
-(void) receiptSuccess {
// finish transaction here
}
-(void) receiptError {
// try again sending receipt to server
}

Restore button and end date from receipt for auto renewable IAP

I am developing one commercial app (app having different tools to calculate) where I implemented auto-renewable subscriptions. I am able to test it properly.
How my app works:
App web services allow user to use app free for three months.
User is asked to get subscribe for an year to use tools.
Once payment is done, receipt is validate and stored at web services.
Once subscription period get collapsed, auto renewable subscription come to play.
My questions:
Is adding restore button is any way compulsory? Referring Is restore button necessary for Auto-renewable subscriptions?, it says restore button is not required in auto subscription.
After payment; receipt is been passed to web server and web server validate receipt. Is their any mechanism to get expiry date from receipt or with the help of receipt?
Thanks!
For
query 1: Currently i am stock to
Is restore button necessary for Auto-renewable subscriptions?
solution.
and for query 2 i found that i can extract below details from
receipt:
NSString *kReceiptBundleIdentifier = #"BundleIdentifier";
NSString *kReceiptBundleIdentifierData = #"BundleIdentifierData";
NSString *kReceiptVersion = #"Version";
NSString *kReceiptOpaqueValue = #"OpaqueValue";
NSString *kReceiptHash = #"Hash";
NSString *kReceiptInApp = #"InApp";
NSString *kReceiptOriginalVersion = #"OrigVer";
NSString *kReceiptExpirationDate = #"ExpDate";
NSString *kReceiptInAppQuantity = #"Quantity";
NSString *kReceiptInAppProductIdentifier = #"ProductIdentifier";
NSString *kReceiptInAppTransactionIdentifier =
#"TransactionIdentifier";
NSString *kReceiptInAppPurchaseDate = #"PurchaseDate";
NSString *kReceiptInAppOriginalTransactionIdentifier =
#"OriginalTransactionIdentifier";
NSString *kReceiptInAppOriginalPurchaseDate =
#"OriginalPurchaseDate";
NSString *kReceiptInAppSubscriptionExpirationDate = #"SubExpDate";
NSString *kReceiptInAppCancellationDate = #"CancelDate";
NSString *kReceiptInAppWebOrderLineItemID = #"WebItemId";
As per my knowledge on inapppurchase .. Auto-renewable subscription is acceptable for magazines ..like this only ... see the guidelines... I kept Auto-renewable for my app like webservices ...they reject my app and they suggest me to keep non-renewable subscriptions.. Now coming up to storing the receipt in your webserver .. how you can store your receipt in your webserver? based on the appleid we want to store the receipt .. but apple doesnot provide User appleid ...because all transactions is based on apple id only..i think storing the receipt is not the right way...You can keep Restore button to restore the previous transactions .. in these restore process you can get the previous transactions like product id and the transaction receipt .. you can pass this receipt to the storekit framework . then you will get the response 0 or 21006 like this based on the response you want to unlock the content...

In-App Purchase Receipt Validation Contradiction

Apple offers two documents about receipt validation, with apparently contradictory statements.
In "Verifying Store Receipts":
Note: On iOS, the contents and format of the store receipt is private
and subject to change. Your application should not attempt to parse
the receipt data directly.
Yet, in "In-App Purchase Receipt Validation on iOS" sample code is provided in which the store receipt is parsed and verified, as part of a "mitigation strategy" for a security vulnerability:
// Check the validity of the receipt. If it checks out then also ensure the transaction is something
// we haven't seen before and then decode and save the purchaseInfo from the receipt for later receipt validation.
- (BOOL)isTransactionAndItsReceiptValid:(SKPaymentTransaction *)transaction
{
if (!(transaction && transaction.transactionReceipt && [transaction.transactionReceipt length] > 0))
{
// Transaction is not valid.
return NO;
}
// Pull the purchase-info out of the transaction receipt, decode it, and save it for later so
// it can be cross checked with the verifyReceipt.
NSDictionary *receiptDict = [self dictionaryFromPlistData:transaction.transactionReceipt];
NSString *transactionPurchaseInfo = [receiptDict objectForKey:#"purchase-info"];
NSString *decodedPurchaseInfo = [self decodeBase64:transactionPurchaseInfo length:nil];
NSDictionary *purchaseInfoDict = [self dictionaryFromPlistData:[decodedPurchaseInfo dataUsingEncoding:NSUTF8StringEncoding]];
NSString *transactionId = [purchaseInfoDict objectForKey:#"transaction-id"];
NSString *purchaseDateString = [purchaseInfoDict objectForKey:#"purchase-date"];
NSString *signature = [receiptDict objectForKey:#"signature"];
// Convert the string into a date
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"yyyy-MM-dd HH:mm:ss z"];
NSDate *purchaseDate = [dateFormat dateFromString:[purchaseDateString stringByReplacingOccurrencesOfString:#"Etc/" withString:#""]];
if (![self isTransactionIdUnique:transactionId])
{
// We've seen this transaction before.
// Had [transactionsReceiptStorageDictionary objectForKey:transactionId]
// Got purchaseInfoDict
return NO;
}
// Check the authenticity of the receipt response/signature etc.
BOOL result = checkReceiptSecurity(transactionPurchaseInfo, signature,
(__bridge CFDateRef)(purchaseDate));
if (!result)
{
return NO;
}
// Ensure the transaction itself is legit
if (![self doTransactionDetailsMatchPurchaseInfo:transaction withPurchaseInfo:purchaseInfoDict])
{
return NO;
}
// Make a note of the fact that we've seen the transaction id already
[self saveTransactionId:transactionId];
// Save the transaction receipt's purchaseInfo in the transactionsReceiptStorageDictionary.
[transactionsReceiptStorageDictionary setObject:purchaseInfoDict forKey:transactionId];
return YES;
}
If I understand correctly, if I verify the receipt my app could stop working when Apple decides to change the format of the receipt.
And if I don't verify the receipt, I'm not following Apple's "mitigation strategy" and my app is vulnerable to attacks.
Damned if I do, damned if I don't. Is there something I'm missing?
They heavily recommend to use your own server as an intermediary for validation, as this will allow a clear and secure passage to the App Store for all versions of iOS. That's really the best route to not be damned either way.
If you must perform validation directly from the device to the App Store, then you utilise their mitigation strategy only when the application is run on 5.1.x and below. For iOS6 and above, use the recommended means provided.
Whilst it was always the case that you shouldn't parse the receipt directly, the discovered vulnerability put Apple between a rock and a hard place in how to address this, and decided that application developers implement a check. It means when a user updates the application, the receipts are now secured again (irrespective of iOS version), thereby providing better fix coverage. As a side effect, it means you have to break from how you should normally do it (but Apple have give you permission to do so).
I agree the documentation isn't wholly clear on this, and could be clarified a bit more (you should give them feedback from the documentation pages at the bottom). Apple have published a mitigation strategy, and they do state it should be used on iOS 5.1.x and below to address the vulnerability. The onus is on them if they change the format/contents of IAP receipts.
Apple is also currently recommending on-device receipt validation. See Validating receipts locally and WWDC 2013 talk Using receipts to protect your digital sales.

Keep Getting the UIAlert which ask for iTunes password

I have an iOS app which has in-app purchase and restore purchases functionality.
When I was testing the app in sandbox mode I use to get the alert all times asking for the password of iTunes account (UserName was already populated).
Now my app is live and I installed it and did the in-app purchase and then restored the purchases, So I am not in sandbox mode still I keep getting the alerts asking for password of iTunes account.
Following is a code which gives the ProductIds that I have purchased earlier,Then I pass those productIds to delegate which changes the status of those from 'Buy' to 'Purchased'
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
//NSLog(#"Restored Transactions are once again in Queue for purchasing %#",[queue transactions]);
NSMutableArray *purchasedItemIDs = [[NSMutableArray alloc] init];
//NSLog(#"received restored transactions: %i", queue.transactions.count);
for (SKPaymentTransaction *transaction in queue.transactions)
{
NSString *productID = transaction.payment.productIdentifier;
[purchasedItemIDs addObject:productID];
// NSLog (#"product id is %#" , productID);
}
if ( mDelegate != nil && [mDelegate respondsToSelector:#selector(purchasedProductList:)] ){
[mDelegate purchasedProductList:purchasedItemIDs];
}
[purchasedItemIDs release];
}
Any idea how to avoid this?
Thanks
You need to ensure that finishTransaction: is called. It must always be called and calling it on a transaction multiple times is fine. If it isn't called, the transaction remains in the queue and the app will try to process it again later. It doesn't matter how the transaction was added to the queue, once it has been acted upon it should be 'finished'.
Note that when restoring new transactions are created, which are effectively wrappers around the original transactions, and they need to be 'finished'. From the restore docs:
The payment queue will deliver a new transaction for each previously completed transaction that can be restored. Each transaction includes a copy of the original transaction.

Resources