restoreCompletedTransactions for Auto-Renewable subscriptions - ios

I'm trying to implement Auto-Renewable subscriptions in my app and having a problem: it looks like restoreCompletedTransactions restores transactions only from the last call of restoreCompletedTransactions to now.
For example, if the subscription started on June 1 and I call restoreCompletedTransactions on June 15, it returns all transactions from June 1 to June, 15. Next time I call restoreCompletedTransactions on June 16 and it returns transactions from June 15 to June 16. If there are no autorenew transactions since last call of restoreCompletedTransactions it returns nothing.
Is this correct? How can I retrieve information about previous transactions?

This is counter to what I've experienced. In my experience, when you call restoreCompletedTransactions it sends you a boat-load of receipts that seems to increase each time you make that call.
But to answer your question, what Apple recommends is that you store and verify all receipts from your own server. So any time the app receives a receipt, you're supposed to send it to your server for verification and possible storage. That way it wouldn't matter if restoreCompletedTransactions is really only giving you new transactions.
In addition, when you verify an auto-renewable receipt with Apple, they'll send you the latest receipt that pertains to that subscription.

Not sure if this is a good solution, but it is an extension of the above.
Do a restoreCompletedTransactions, and check the date on purchase transactions. Maybe on restored transactions (maybe someone can let us know>
// in your main code somewhere
#import <StoreKit/StoreKit.h>
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
// then, update the StoreKit callback function. kProductId is the productId for the subscription in the app store
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
NSLog(#"Purchasing");
break;
case SKPaymentTransactionStatePurchased:
if ([transaction.payment.productIdentifier
isEqualToString:kProductID]) {
NSLog(#"Purchased ");
// update based on transaction.transactionDate
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
NSLog(#"Restored ");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
if ([transaction.payment.productIdentifier
isEqualToString:kProductID]) {
// update based on transaction.transactionDate
}
break;
case SKPaymentTransactionStateFailed:
// NSLog(#"Purchase failed ");
default:
break;
}
}
}

Related

Catch "The In-App Purchase has already been bought" event

-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
for (SKPaymentTransaction* transaction in transactions)
{
CCLOG("transactionState = %ld", transaction.transactionState);
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self completeTransaction:transaction];
break;
default:
break;
}
}
The code above is for tracking the purchase state we know.
But it seems that the code won't track the event of "The In-App Purchase has already been bought".
[[SKPaymentQueue defaultQueue] addPayment:payment];
Each time I use the code above to purchase an IAP which is already purchased, the IOS confirm purchasing dialog will show out with transactionState = SKPaymentTransactionStatePurchasing(The output is "transactionState = 0"). Then I buy the item and "The In-App Purchase has already been bought" dialog show out with no other state code output.
I wish to catch the event above. I bet it must be catchable. Does the event is threw in other place?
In my case the problem was caused by adding the SKPaymentTransactionObserver to the SKPaymentQueue after another library had already registered its own observer. Switching the order of initialization so that my app registered its own observer first fixed the problem.
I've provided in more detail here
We had a similar Issue when our server receipt validation stopped working and the transactions weren't finished. So I've modified the purchase flow. Before the App adds a new product payment the app iterates now through all open transactions ([SKPaymentQueue defaultQueue].transactions) and stops the payment when an open transaction has the same product identifier as the new product payment.

How to get app receipt after purchase using test account in iOS

I am working with IAP, I want to get the receipt so that I can validate it.
I have tried this
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState)//Each transaction should be processed by examining transactionState property.
{
case SKPaymentTransactionStatePurchased:
{
if([transaction.payment.productIdentifier isEqualToString:#"TC0001"])
{
}
**NSData *data = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
NSError *error;
NSDictionary *response = [NSJSONSerialization JSONObjectWithData: data options: 0 error: &error]; //I am using sbjson to parse
NSLog(#"%#",response);**
//Finish transaction deletes the transaction from the default queue
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
break;
default:
break;
}
}
}
I hope I am doing right, because this is what mentioned in docs. But I get "null for response. So is that I am missing something.
The receipt is not stored in plain-text JSON.
It's actually in the format of a PKCS7 encoded container which is a binary format based on an old-time telecom encoding format called ASN.1.
I can't point you to any examples because no one has shared their code for decoding the container. Developers are reluctant to share their code because Apple pointed out that if many apps share similar code for handling IAP, a single security exploit would threaten not just one, but many apps.
There's a WWDC presentation: "WWDC 2013: Using Receipts to Protect Your Digital Sales" where they provide some suggestions on how to get started, and there's also a document: "Receipt Validation Programming Guide" in the Xcode docs.
Read those and then Google for pertinent terms, you should find enough bits and pieces to assemble a solution.

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
}

StoreKit change stores notification?

Is there a way to know when the user has changed stores with the StoreKit framework?
This is for if I have already pulled in a list of products and the user changes stores, so I can refresh the prices for the new store's locale.
I found a solution, a bit of a 'trick', not as obvious as an explicit 'store did change notification'.
You can listen to the errors of each transaction in - paymentQueue:updatedTransactions:
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for(SKPaymentTransaction* transaction in transactions)
{
NSError* transactionError = transaction.error;
if(transactionError != nil && transactionError.code == SKErrorUnknown)
{
NSLog(#"User potentially switched stores");
[self refreshAllProductInfo];
}
}
}
This may trigger during other errors, but so far I've only seen it to trigger when you change stores.
With this, when the user sees for example, $USD prices and then logs in with a $GBP account, the prices will refresh to their GBP versions.

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