8
votes

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

2
LOL I was just to ask the same question man.. I accidentaly found this and saw that you asked this 8 hours ago...this just sux, I don't have a clue also what to do with all those receipts...Adrian
And I wanna do the same kind of checks you wanted to do here. I hope someone sees this and answers it! :\Adrian
I think the key is to get the transaction id of the current purchase on the app side and send that, along with the receipt, up to the server. You can get the transaction id on the app side from the transaction object.Chris Prince
Thanks for the comment. But here is the drawback of the approach: there is no way to understand that this receipt is connected with this transaction id.user3489820

2 Answers

0
votes

@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:

  1. The original transaction identifier of the receipt that you are validating with itunes server, associate this with the user Id in your database.
  2. 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.

0
votes

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
}