Already checked transactions is not being removed from Transactionqueue

Hello,


Recently, We have some issues with in-App purchase (All consumable product).

The problem is that some transactions are not being removed from the transaction queue for some users even after the "finishTransaction:transaction" had called.

Receipts are being checked on the our server. Sadly sometimes we've been receiving receipt which has old transaction info. As you see the code below, "finishTransaction" method is being called even before the checkReceipt. But the "finishTransaction" method is not working for some users, some already processed transactions stayed in the transaction queue for an indefinite period, after that everytime he/she buys a product, then the checkReceipt method is being called for 2 times. 1 for the new transaction and 1 for the already finished transaction(we checked the receipt for the transaction and gave the appropriate item to the user already). But the transaction remain in the transaction queue for an indefinite period and removed from the queue randomly.

We know that the "finishTransaction" method should be called after the receipt check. But in our app now the "finishTransaction" method is being called intentionally before the "checkReceipt" because already processed transactions not stopping to come. Thanks to God, most of our users' receipt can be checked.


Also sometimes we are getting the receipt response which has empty in_app array when we checking the receipt with Apple servers. Some users emailed us that their credit card is charged but can't get the tickets(our iAP product) since we can't accept a receipt with empty in_app array.


Lastly, Yesterday one of our users had a weird issue. According to our access log, He got the "SKErrorPaymentCancelled" error code when he was asked to "Buy tickets?" due to the App update which we released yesterday. But the "checkReceipt" method was called after that, then a receipt sent to our server and checked it with the Apple server and it seems that user has charged. Before the "checkReceipt" method, we already got the "SKErrorPaymentCancelled" error code for that transaction.


Does new transaction start after the failed transaction during app update like "StoreKitFlow process"?


Could you please tell me about why some transactions can not be removed from the transaction queue even after "finishTransaction" method is called?


Also do you know that in which case the empty in_app array is being received with receipt (Our app is free and Users are charged for iAP)?




    for (SKPaymentTransaction * transaction in transactions) {

        switch (transaction.transactionState)
        {
            case SKPaymentTransactionStatePurchased:{
               [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
               [self checkReceipt:transaction];
                break;
                }
            case SKPaymentTransactionStateFailed:
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                [self failedTransaction:transaction];
                break;
            case SKPaymentTransactionStateDeferred:
                // Remove purchase screen
                [[NSNotificationCenter defaultCenter] postNotificationName:@"purchaseStatus" object:@"deferredTransaction"];
                break;
            default:
                break;
        }
    };

Accepted Reply

You use the term "not removed from the transaction queue". A transaction that is not removed from the transaction queue will cause a new call to updatedTransactions each time the observer is added or each time the app comes into foreground with an observer present. Is thatw aht you mean?


Or....do you mean that the in_app_array contains receiupts for consumable purchases even after you finishTransaction? If so there are two possible reasons for this:

1) you need to referesh the receipt by either calling refersh receipt or by making anew pruchse

2) this is a documented bug - the receipt often retains old consumable purchases.


I beleive when a user tries to repurchase a consumable (are you certain your IAPs were marked 'consumable' not 'non-consumable') they get a message like "You already purchased this . Do you want to purchase it again." I know that for a non-renewable IAP they get the message "You already purchased this subscription. Tap 'Buy' to renew or extend the subscription."

Replies

If a user's credit card info is out of date then when they interact with the App Store they get a chance to update their info and go forward with the purchase. if they go forward with the purchase then when they are done the App Store sends two transactions to the app: a failed transaction followed very shortly by a purchased transaction. It is unclear how your code handles that second call to updatedTransactions. ........You will often find that when there are no IAPs in the receipt there were no purchases and your app is being hacked by a fake call to updatedTransactions.

Hello, PBK.


The code in the updatedTransactions is like below. We call [[SKPaymentQueue defaultQueue] finishTransaction:transaction] immediately after SKPaymentTransactionStatePurchased and SKPaymentTransactionStateFailed. But in some case which we couldn't figure out, some transactions after SKPaymentTransactionStatePurchased, not removed from the transaction queue and stayed in there forever. Which causes to call [self checkReceipt:transaction] 2 or more times when user buys product again. As I mentioned before, 1 call is for new transaction, others for old transactions which not removed from the queue. I've attached the receipt below which you can see that 3 transactions for consumable product in the "in_app" array. According to Apple, for consumable products, transaction is removed after finishTransaction call. But sometimes it is not working. All of our products are "Consumable".

Even worse things is that some users can not buy 'same type' of ticket(our IAP product) again. It's like, if you bought ticket_300, you can't buy ticket_300 anymore. It seems that when user tries to buy more "ticket_300" then the old not removed transaction for "ticket_300" processed again and not starting new transaction. Our server receiving a receipt with old transaction values but users said that they tried to buy more "ticket_300" but the App displays that you already bought this item(For Consumable products).

Do you know what causes to transactions not removed even after [[SKPaymentQueue defaultQueue] finishTransaction:transaction] for Consumable products?(by the way, our transaction observer is added at didFinishLaunchingWithOptions and removed at applicationWillTerminate).




Thank you for your reply.

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction * transaction in transactions) {
        switch (transaction.transactionState)
        {
            case SKPaymentTransactionStatePurchased:{
               [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
               [self checkReceipt:transaction];
                break;
            }
            case SKPaymentTransactionStateFailed:
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                [self failedTransaction:transaction];
                break;
            case SKPaymentTransactionStateDeferred:
                // Remove purchase screen
                [[NSNotificationCenter defaultCenter] postNotificationName:@"purchaseStatus" object:@"deferredTransaction"];
                break;
            default:
                break;
          }
     }
}

You use the term "not removed from the transaction queue". A transaction that is not removed from the transaction queue will cause a new call to updatedTransactions each time the observer is added or each time the app comes into foreground with an observer present. Is thatw aht you mean?


Or....do you mean that the in_app_array contains receiupts for consumable purchases even after you finishTransaction? If so there are two possible reasons for this:

1) you need to referesh the receipt by either calling refersh receipt or by making anew pruchse

2) this is a documented bug - the receipt often retains old consumable purchases.


I beleive when a user tries to repurchase a consumable (are you certain your IAPs were marked 'consumable' not 'non-consumable') they get a message like "You already purchased this . Do you want to purchase it again." I know that for a non-renewable IAP they get the message "You already purchased this subscription. Tap 'Buy' to renew or extend the subscription."

Thank you for your reply.

You are right! The [in_app] array contains old consumable purchases even after the finishTransaction call. And sometimes it's empty.

I am pretty sure that all the products were marked "Consumable".

Exactly what you are saying is happening. Some users get a message like "You already purchased this . Do you want to purchase it again.".



At the beginning the problem was the receipt coming to our server has old and new purchases data in it's "in_app" array. It was fine, because user could make purchases. We filtered old transaction ids. So there was no problem.

receipt_a -> in_app => {...transaction_id:12_old , transaction_id:21_new..}

receipt_b -> in_app => {...transaction_id:12_old , transaction_id:34_new...}

receipt_c -> in_app => {...transaction_id:12_old , transaction_id:45_new...}

receipt_d -> in_app => {...transaction_id:12_old , transaction_id:67_new...}



But now some users' receipt has only old transaction data in it's "in_app" array. Which becomes the major problem that users to complain about can not make purchase, even after many tries.

It seems like new purchase is not being able to refresh the receipt for some users.

(after a failed attemt they tried to purchase same product for 3 - 5 times, which could't help. Diffrent receipts with same in_app array posted to our server after every purchase attemt).

receipt_a -> in_app => {...transaction_id:12_old ...}

receipt_b -> in_app => {...transaction_id:12_old ...}

receipt_c -> in_app => {...transaction_id:12_old ...}

receipt_d -> in_app => {...transaction_id:12_old ...}




Is refreshing receipt workaround for this problem now?

I also reported a bug to Apple. They asked encoded receipt string and I sent it to them. But now there is no response from them for over 3 weeks.


Really appreciate you for your explanation and corrections.

The content of the receipt should not change the ability of a user to purchase a consumable IAP over and over again.

@kaihatsu

We see ~150 unique empty in_app receipts a day for our app. We have been trying to track this down for a long time and still have no luck. A few questions for you that may help both of us if you haven't already resolved the issue:


  1. We only notice the empty in_app receipts for ask-to-buy purchases (where the child has to get permission from the parent)
  2. We are using Unity and their billing system for our client but validate the receipts on the server. We are unsure if the bug is on Unity's client code or something with iOS. Are you using Unity and their billing system?
  3. We also see old consumables get stuck in the in_app array and again, this seems to be tied to ask-to-buy purchases. The receipts aren't getting updated/refreshed on some A2B purchases so the old purchases are stuck in the receipt until something causes it to refresh (another successful purchase or an actual refresh event). We see these rarely because we think it has to do with making a non-A2B purchase (which gets put in the receipt) and then enabling A2B and attemping another purchase. The receipt doesn't always update so the old purchase sticks around.


Are you seeing similar things?


Cheers!

You would expect this type of receipt when updatedTransactions is called with an ask-to-buy state. There is no purchase at that time so there should be no IAP. Similarly, if a user purchases a consumable, the receipt will contain the consumable purchase until you call finishTransactions and refresh the recipt. It is reasonable that if an ask-to-buy call is made to updatedTransactions after a consumable purchase/finishTransaction then the receipt may not be refreshed yet and the consumable may remain in the receipt.


Nothing you listed sounds like a bug. Perhaps the issue is that your server has been asked to examine the receipt after an ask-to-buy 'purchase' has been started. Until the parent approves, there is no reason to have asked your server to respond.

PBK,


Thanks for responding. We aren't seeing this when updatedTrasnactions is called with an ask-to-buy state such as SKPaymentTransactionStateDeferred - we are seeing these issues occuring on the actual purchase state, after the parent has approved the purchase.

That's a bug. Report it using the bug 'Report Bugs' below.

Please refer to my comments in the posting

https://forums.developer.apple.com/thread/85380

on how to file a bug report on the matter of the in_app array being empty.

rich kubota - rkubota@apple.com

developer technical support CoreOS/Hardware/MFI

This scenario sounds interesting - can you replicate the empty in_app receipt by implementing Ask-to-buy. I'd be happy to try if I knew there was a means for the in_app array to be checked. Does your app process do this. If so, please file a bug report and send me the bug report number. Make sure to include a means as to how we can obtain a copy of the base64 encoded receipt so that we can see the empty in_app array.


rich kubota - rkubota@apple.com

developer technical support CoreOS/Hardware/MFI