Auto-Renewal Problem - Receipt not be updated

I am developing a Mac app to support Auto-Renewable Subscriptions. I am using LOCAL RECEIPT VALIDATION only (not using server validation). The receipts in the app bundle should be updated after transaction be finished (by calling queue.finishTransaction(transaction) ), but I found it's impossible to validate the receipt (to validate the users), as the receipts will not be updated correctly.


- Restoring does NOT update the receipt

- If keeping the app running "updatedTransactions" will NOT be called. Restart the app (and let more time go by), "updatedTransactions" will be called

- The receipt will NOT be updated when renewal happened

- AND the receipt will NOT be updated if I did a purchase on another Mac (same testing account in sandbox environment)


But:

- Receipt will be updated if to Purchase a new product or to Refresh Receipt

- I did testing using remote validation method, there is no problem to validate via https://sandbox.itunes.apple.com/verifyReceipt


The problem is that if the local receipt date is not be updated correctly after "finishTransaction" called in the queue "updatedTransactions" methods:

- If purchase renewed, the app has no way to validate it. So the user has to refresh the receipt via the user interface and login Apple Id again - this is a very bad user experience.

- If the user cancelled the subscription, the app has no way to know it (know the new expired_date)


- And I have to use refresh receipt to replace restore receipt operation, as restore receipt will NOT update local receipt.


As the Mac app is a tool app, I don't want to use a server to do remote validation. Would you please help to advice me the solution?


Philip

Answered by in 330240022

Philip,

If there is a pending transaction to be processed for an app, and the app is launched and calls addTransactionObserver and the transaction is not detected, this is a bug report issue. Whether the app implements local or networked receipt validation is a separate issue. However, either means can be used to demonstrate the current state of the appStoreReceipt.


To be specific - if you know that a renewal of an auto-renewing subscription has occurred, then there must be an incompleteTransaction pending on the App Store for the iTunes user. If you then launch the app on a device with an active network connection and the addTransactionObserver call occurs, then it's a bug report issue if the incompleteTransaction is not detected. The bug in this case can only be fixed by the App Store server engineers.


As to a workaround, the only one I can think of requires server validation. If the appStoreReceipt shows that there is at least one auto-renewing subscription In-App Purchase item in the in_app array, then validating the receipt with the App Store verifyReceipt server will provide up-to-date information about the auto-renewing subscription. There is no workaround solution for the app if local appStoreReceipt validation is used.


rich kubota - rkubota@apple.com


developer technical support CoreOS/Hardware/MFI

>The receipts in the app bundle should be updated after transaction be finished (by calling queue.finishTransaction(transaction)


No (for iOS, I don't know about MacOS), the receipt should be updated when there is a call to updatedTransactions whether or not you call finishTransaction on the transaction.


updatedTransactions gets called when there is a renewal and the device has made the original purchase or the device has made a restoreCompletedTransactions or the device does a refresh receipt. If you are not seeing that then there is a bug in the system, please report it.


Your logic though should be to remember the next expiration date and check nothing until the user tries to1) use the subscription 2) after that date. Only then is there an issue. Turn on a transactionObserver first and see if there is a transaction waiting to come. If you get no transaction after 5 seconds then tell the user that you need to check their subscription and then do a restoreCompletedTransactions (or a receipt refresh). That will cause StoreKit to hand the user a log in screen but only once per renewal period and only after you have told them that you need to check the subscription.

@PBK


The "updatedTransactions" has been called correctly and the app called "finishTransaction". The problem is that the receipt in the bundle should be updated, but it is NOT be updated.


- Restoring Purchases: All the historic transactions be triggered in the "updatedTransactions" method, but the receipt in bundle did not be updated

- But refreshing receipt does get the receipt in bundble be changed.


I tracked the change of the receipt using code and manually checked the receipt file size in the bundle.


Thanks

And:


After purchase expired, even the app is at frontend, the "updatedTransactions" not be called, I have to restart the app, then the transaction received.


Best,

Following please help to check the log for "restoring purchases":


🚹 User Call: Restore Purchases

------------ Transactions -------------

✉ User purchased product. com.****.monthly. Transaction date: 2018-08-20 13:50

✉ User purchased product. com.****.monthly. Transaction date: 2018-08-20 13:45

✉ User purchased product. com.****.monthly. Transaction date: 2018-08-20 13:40

✉ User purchased product. com.****.monthly. Transaction date: 2018-08-20 13:35


------------ Receipts and State -------------

⚠ com.****.monthly (2018-08-20 13:35 --> 2018-08-20 13:40)

⚙ Subscription State: ⚠ expired (2018-08-20 13:40)



=====================

- After "restore purchases", there are 4 transactions received, one is the purchase transaction, another 3 are renewal transactions.

- But the problem is: After "finishTransaction" be called, then to validate the receipt in the app bundle, only the first transaction in the receipt.


What works correctly:

- If to purchase a product, all the transactions will be received correctly, and the receipt will be updated correctly (that's why in preceding logs, in the receipt, there is only one transaction record).

- If to refresh receipt, the receipt will be updated correctly


I just manually checked the "receipt" file in the app bundle, it's last update time is at 13:35 (same time of the first purchase transaction completed)


I am wondering as Apple is encouraging the developers to try subscription mode, the local verification should work, so I guess I may made a mistake basic, but I can make sure: There is no problem the code to make the "updatedTransactions" be called and the app does called "finishTransaction" for the transactions.

So you are saying:


1) when you restart the app the transaction is received. BUT - if the app is running and you have added an observer then the app does not receive a transaction until you restart it. If that is correct (and you have not removed the transaction observer somewhere in your code) then that is a bug, please report it.


2) the historic transactions be triggered in the "updatedTransactions" method, but the receipt in bundle did not be updated. You are saying that you get calls to updatedTransactions but that is not reflected in the receipt - that is a bug, please report it.

Let me ask you to be more precise.


"User Call: Restore Purchases"

I assume that means that your app called "restoreCompletedTransactions". If not, please explain.


"After "finishTransaction" be called, to validate the receipt in the app bundle" - You will be calling finishTransactions 4 times. I assume your app grabbed the receipt AFTER the last transaction was received in updatedTransactions - not after the first transaction was received. If not, please explain. As I stated earlier, calling finishTransactions is irrelevant, what matters is the call to updatedTransactions.


"there are 4 transactions received" - there should be 5 (or 6?) but certainly more than 4 - can you explain why you only get 4 transactions?


I notice that you never print out your receipt - are you certain you are correctly decoding it? How many case 17's do you get and what are your values for 1708?

Thanks PBK,


About the logs in my preview:


- At 13:35, the user (I as the tester) clicked a button to purchase a product (of cause the app call the store api to do it). After finished, the receipt be updated correctly.

*** no problem ***


- But before 13: 50, there is no transaction (for renewal) come ("updatedTransactions" not be called). The app has no problem to observe the transactions by calling "SKPaymentQueue.default().add()"

*** this is a problem ***


- Then at 13: 50, the user (I as the tester) cliked a button to restore the purchases (the app calls the "restoreFinishedTransactions"). You can see in the log, there are 4 transactions received, one is for purchasing, three for renewal. Please pay attention, at the time 13:50, only 15 minutes passed after purchasing, so only 3 renewal transactions received. You see the app receive the transactions correctly, so no warry on the transaction observer

*** no problem ***


- After "restoreFin..." called, the app will also do the local validating. The receipt was not be updated after "restore..." called (the app get just one record in the receipt which is same as the receipt after purchasing at 13:35). Then I manually check the receipt file in the app bundle, the last modify date is still at 13:35


Following the log for the data in the receipt:

------------ Receipts and State -------------

com.****.monthly (2018-08-20 13:35 --> 2018-08-20 13:40)


*** this is a problem ***


=========================

More

1. I did checked (local verification) many times using the code

2. And I did checked many times manually (check the last modify time, and file size of the recipt file)


The problems are:

- "Restoring" does not update the receipt file in the app bundle

- "updatedTransactions" calling is not stable for RENEWAL transaction ONLY (sometimes it works, sometimes I need wait very long time, or no any response after a very long time. BTW, I know the 5 times limitation, I just test after purchasing a product)


But strangly:

- "Refresh" receipt works well

- "Purchasing" works well (the receipt will be updated correctly, all transaction be triggered well)


From my testing, you may agree:

- The app has no problem to deal with 173 error, and the app has no problem to observe transaction

- No need to worry about the local verification code, as I also manually check the file last modification time and file size (the major problem is after restoring, the receipt is not be updated, just checking the receipt file last modifiy time is enought).

- The "paymentQueue(..., updatedTransactions ...)" and "paymentQueueRestoreCompletedTransactionsFinished" are always called by the app every time after "restoreFinishedTransactions" be called, the problem is that then the receipt file will not be updated (checked by local verification code, and by manual checking the file last modifiy date ...)


Thanks much.

I only know that what you are describing is not what happens in iOS when the system is working correctly.


Somewhere in your code you do something like this (modified for MacOS)

NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];

NSData *receiptData=[NSData dataWithContentsOfURL:receiptURL];


If that is done after the call to updatedTransactions it should have a receipt that includes the purchase reported in that call to updatedTransactions.

Since you are testing in the sandbox environment, auto-renewing subscription items renew at an accelerated pace. This creates an interesting problem with renewals and the notifications via the updatedTransactions delegate method.


Transaction notifications to a StoreKit app are detected by request. The notification is not like the push notification process. When the SKPaymentQueue.default().add() call is made, there is a request submitted to the App Store - as to incompleteTransactions to be processed by the application per the appBundleID/iTunes user account. If none are detected, there is no call to the updatedTransactions delegate method.


From this point on, the transactionObserver will only activate when

1. an addPayment call is initiated by the app to watch for the App Store response or

2. when the application is moved from the background environment to the foreground environment.


This mechanism is the way the transactionObserver works in both iOS and macOS.


I mentioned that the accelerated renewal process can create an issue. Let me ask - when you wait for the renewal to occur, does the macOS app remain in the foreground. If so, the transactionObserver will never activate. If instead, the app is allowed to move to the background, then moved to the foreground before the renewal, this is also a problem since the renewal incompleteTransaction will not be created yet and the transactionObserver check will show nothing pending. So what has to happen, the app must be moved to the background, then brought forward, after the renewal has occurred. The App Store will have created the renewal transaction as an incompleteTransaction and the transactionObserver will now detect it. When this happens, the updatedTransactions delegate method is called and the appStoreReceipt is updated.


The restoreCompletedTransactions method will only report "completed transactions" - those which have been acknowledged by the app via a notification of the updatedTransactions delegate method. However, if there is a restored transaction, the appStoreReceipt should be updated and reflect all of the purchases to date. This sounds like a bug report if this is not happening.


If you are bringing the app to the foreground after the renewal has occurred and there is no call to the updatedTransactions delegate method, this is a bug report.


rich kubota - rkubota@apple.com

developer technical support CoreOS/Hardware/MFI

Thank @rich


The major problem is that after "restore purchased transactions", the receipt in the macOS app bundle will not be updated (checked manually and using code).


The code works well on the iOS device (the receipt will be updated correctly)


For the macOS app, I have to write code to save the transaction date (monitor by transactionObserver) in safe area, and write code to validate if the purchase expired.


This problem was reported at beginging of this year at https://forums.developer.apple.com/message/290288#290288 and a year ago at https://forums.developer.apple.com/message/258440#258440 by other users.


I thought I may made a mistake, it's hard to believe it's a bug as it's so important and so basic for the local verification of auto-renewal subscriptions.


Thanks,

Accepted Answer

Philip,

If there is a pending transaction to be processed for an app, and the app is launched and calls addTransactionObserver and the transaction is not detected, this is a bug report issue. Whether the app implements local or networked receipt validation is a separate issue. However, either means can be used to demonstrate the current state of the appStoreReceipt.


To be specific - if you know that a renewal of an auto-renewing subscription has occurred, then there must be an incompleteTransaction pending on the App Store for the iTunes user. If you then launch the app on a device with an active network connection and the addTransactionObserver call occurs, then it's a bug report issue if the incompleteTransaction is not detected. The bug in this case can only be fixed by the App Store server engineers.


As to a workaround, the only one I can think of requires server validation. If the appStoreReceipt shows that there is at least one auto-renewing subscription In-App Purchase item in the in_app array, then validating the receipt with the App Store verifyReceipt server will provide up-to-date information about the auto-renewing subscription. There is no workaround solution for the app if local appStoreReceipt validation is used.


rich kubota - rkubota@apple.com


developer technical support CoreOS/Hardware/MFI

Thanks @rich,


Yes, remote-validation works.


Using remote validation needs addtional investment on the hosting and server side application. My solution is not the remote validation, the app still use local verification, but added more logic to analyze all the transaction data and store the data in safe area (like another copy of receipt)


Best,


Philip

Auto-Renewal Problem - Receipt not be updated
 
 
Q