Posts

Post not yet marked as solved
3 Replies
Hello BraXel, You asked - "What if the server notification is a CANCEL notification and we cannot find a matching original_transaction_id? Should we then re-validate all our receipts in the database? We are expecting millions of subscriptions (at least we hope)." I've only ever have seen the original_transaction_id change (increment) on a renewal, specifically an upgrade or downgrade renewal event. Having said this, I've not seen such a report in quite a while. That said, save the original_transaction_id along with the purchase_date. When the CANCEL event is received, the original_transaction_id should match to that for a RENEWAL notice with a matching purchase_date. If anyone from the App Store Server team can suggest a better means to manage this, I welcome their comments. rich kubota developer technical support CoreOS/Hardware/MFI
Post marked as solved
1 Replies
To clear out an existing sandbox user account, open the App Store application, select App Store -> Preferences. The last item is where you should find the Sandbox Account user along with a "Sign Out" button. rich kubota developer technical support CoreOS/Hardware/MFI
Post not yet marked as solved
3 Replies
I'm not aware of a means to trigger a REFUND notification in he sandbox. I suggest that you use the Apple Developer Feedback Assistant web page to submit a request that there be a means to simulate a REFUND event in the sandbox. rich kubota developer technical support CoreOS/Hardware/MFI
Post not yet marked as solved
1 Replies
For any app which runs in the sandbox, there is never an appStoreReceipt at first launch after the app has been installed via Xcode, TestFlight (or in App Review). Many apps implement the SKReceiptRefreshRequest method to have the app implement a provisional appStoreReceipt for this case. In App Review, when there is the resulting Authentication dialog, the reviewer will always cancel the dialog and the app needs to handle this case. There is no requirement in the App Store Guidelines that there be an appStoreReceipt present before the first purchase is made. As I understand, App Review will check that user can restore previous purchases and will look for a "Restore" button after re-installing the app. Many StoreKit implementations have a tiny "restore" button below the "BUY IT NOW" button. Pressing "restore" causes the app to make the restoreCompletedTransactions call - which will result in the updatedTransactions delegate called with the .restore transactionState. You may want to do otherwise, but make the button easy to find. rich kubota developer technical support CoreOS/Hardware/MFI
Post not yet marked as solved
1 Replies
If you delete a paid app that was purchased on the production App Store, then re-install the same app from the production App Store, the appStoreReceipt in the updated app will be the same as it was when you deleted the app. Whenever the production App Store installs any app to a system, it includes the most recent version of the appStoreReceipt. However, this is not the case with the sandbox App Store. When you install an app via TestFlight or Xcode, there will never be an appStoreReceipt until one of the following occurs - a successful In-App Purchase is made the restoreCompletedTransactions call is successful in restoring previous sandbox purchases or the app uses the SKReceiptRefreshRequest method and the user authenticates the use of this method. Usually this issue is raised with regards to the "Paid-to-freemium" transition. If this is your case, refer to my response in this developer forum posting. rich kubota developer technical support CoreOS/Hardware/MFI
Post not yet marked as solved
3 Replies
You stated - "We are developing a mobile app with in-app purchases. We plan to offer one-time purchases and subscriptions. In order to support multiple platforms (iOS and others) we plan to track subscriptions via server-to-server notifications and update client licenses accordingly." I refer you to the Apple Developer article "Offering a Subscription Across Multiple Apps". You will implement unified account management on your server, to support the user on multiple platforms. On every login, the app process will check with the server to determine what subscription services to activate for the user. The app process may cache active services locally in to support out of network service use. After login, the app should know which services to offer for In-App Purchase. Whenever an initial purchase is made, for iOS/macOS save the appStoreReceipt to the user account for use to track renewals. Check out the Apple Developer article "Handling Subscription Billing" You stated - "Now we are struggling to match server notifications to receipts in the database. We have to support iOS14, so we can't use the appAccountToken field." Response - you are correct to consider using the original_transaction_id. It is documented as being unique. As to your questions Q1. Is original_transaction_id unique a) globally (across all app store user accounts) ? Yes b) in the context of a user account ? no - take a look at the documentation on original_transaction_id. Renewal transactions will show the same original_transaction_id. c) in the context of a subscription group? Not clear what you are asking here. Q3. Can an original_transaction_id change? Response - I'm told that there are cases where this can happen. When you match original_transaction_id make sure to do so for the same transaction period. For example, if you receive a RENEWAL notice for the period 02/01/22 - 02/28/22 - you can check for an existing user record using the same original_transaction_id. But if for some reason, a change occurred - check accounts which a renewal is expected, validate the appStoreReceipt, then compare the original_transaction_id for the same period 02/01/22 - 02/28/22. I say this having asked developers who have reported that the original_transaction_id changed +1 from the previous period's transaction, to submit a bug report. For a specific period, the original_transaction_id will be the same between the server-to-server notification and for the appStoreReceipt. From this point on, the "new" original_transaction_id" will be used. Please note - for thousands of developers, this never happens. What this means is that on every renewal, you may want to have the server validate the save appStoreReceipt to extract the information on the "latest_receipt_info" as well as from the "pending_renewal_info" section. One more suggestion, if you aren't aware - always submit the JSON request with the "exclude_old_transactions": true key so that the contents of the "latest_receipt_info" has only the most recent transaction(s) and not the complete transaction history. rich kubota developer technical support CoreOS/Hardware/MFI
Post not yet marked as solved
2 Replies
Your finding is correct. You can use the Apple Developer Feedback Assistant web page to submit an API enhancement request to the App Store Server team to consider.
Post marked as solved
2 Replies
As I understand, there has been an update to how the verifyReceipt endpoint handles refunded transactions. When you receive a REFUND notification for a refunded transaction of a non-consumable or non-renewing subscription item, you can send the latest appStoreReceipt and include the password (shared-secret) key in the request payload to the verifyReceipt endpoint. If there has been a recent refund applied to a non-consumable or non-renewing subscription item, the "latest_receipt_info" section of the validated JSON response will include that transaction. The transaction information will include the cancellation_date field. Check this out and respond to this forum posting if indeed this is correct. rich kubota developer technical support CoreOS/Hardware/MFI
Post marked as solved
1 Replies
In response to your questions Q. Is it necessary to retrieve the receipt data from the device and send it to the application server (to then be sent to Apple's server to verify and get the decoded data) if the App Store server is going to be sending a SUBSCRIBED + INITIAL_BUY notification around the same time to the application server anyway? Response - yes. The server-to-server notification contains no information to identify the user so as to be able to manage a user account on the app. By having the app pass the receipt to the server - and saving the initial appStoreReceipt to a user account, your server can validate the appStoreReceipt and decode the JWT signedTransaction in the server-to-server notification to match the original_transaction_id. From that point on, if the user never relaunches the app, the server-to-server RENEWAL notifications (and other notifications) can be matched to the appropriate user account. Q. If it is still necessary to verify the receipt data from the local device. Response - the recommendation is to implement server-side receipt validation to reduce the possibility of a man-in-the-middle attack. The app process can perform app side validation. There is no App Store requirement that server side validation be performed. This is an app security recommendation. You noted - Looking at the JWSTransactionDecodedPayload, it includes a field called appAccountToken however, it appears as though this can only be utilized with the iOS15+ StoreKit2 API. Response - there is no means with the StoreKit 1 API to set the accAccountToken. The app can only do so using the StoreKit 2 APIs. You asked - It would be greatly appreciated if someone could clarify the required steps for managing an initial purchase on both the client and server for: StoreKit 1 + Server Notifications v2 StoreKit 2 + Server Notifications v2 For both scenarios, I refer you to the WWDC 2021 Session "Manage in-app purchases on your server". Another document to read is "Handling Subscription Billing" rich kubota developer technical support CoreOS/Hardware/MFI
Post not yet marked as solved
2 Replies
I wonder if you are currently working in the sandbox. In the sandbox environment, auto-renewing subscriptions renew at an accelerated pace. I could easily understand that for a 1 mo subscription where it renews every 5 minutes for 5 times, that if you relaunch the app after a 30 minute delay, the transactionObserver would detect the renewals, but validation of the appStoreReceipt would show all expired subscriptions. In the production environment, auto-renewing subscription continuously renew, not like in the sandbox. The customer can stop renewals in their iTunes Account "Manage Subscription" panel. In response to your concerns, take a look at the Apple Developer Article Handling Subscriptions Billing. rich kubota developer technical support CoreOS/Hardware/MFI
Post marked as solved
4 Replies
The answer to your question involves appStoreReceipt validation. This is something that both a StoreKit 1 as well as a StoreKit 2 app can perform. When a developer has an existing paid application, but would like to make the app Free with In-App Purchase the transition process is called the “Paid to Freemium” process. The free version of the app will be a upgrade to the existing "paid" version with an upgraded version string. You will modify the app so that at first launch, the app validates the appStoreReceipt. Assuming that the validation status is 0, the app process then checks the JSON payload, for the “original_application_version” value to determine which version of the app, the iTunes user first installed. If the “original_application_version” value is for a previous “paid” app version, the application provides the user with access to the premium content. If the “original_app_version” value is for the current “free” version, then the user must make the In-App Purchase to have access to the premium content. If the user is a “free” app customer, then the user has access to only minimal functionality, but they can access the “Buy” button to obtain premium content access. The “paid” app version customer should never be presented with the “Buy” button. To determine whether a customer should have access to the content check the "original_app_version" field. The specific process for a paid to freemium app on first launch, the app determines whether it has checked the applicationReceipt pointed to by NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; (The following step is primarily a sandbox environment issue) If the receipt is not present, the app alerts the user that the applicationReceipt may need refreshing and asks whether the user want to refresh the receipt. If the user agrees, the app makes the SKReceiptRefreshRequest call and process the resulting receipt. See step 2. If the user declines to refresh the receipt, the application makes the assumption that the user is a new user who has no privileges to access premium content and exits the receipt validation process and proceeds to the main part of the app. The gotcha here is that when you install the app using Xcode,or TestFlight there will not be an appStoreReceipt when the app is first installed. This is true whenever the app is installed in the sandbox environment and not by the production app store. This also happens in App Review which reviews the app in the sandbox environment. One other note, if the app finds no appReceipt, it must not force the issue to require the use of SKReceiptRefreshRequest to have a receipt installed. The SKReceiptRefreshRequest presents the user with an Authentication dialog. If the user cancels this dialog, then treat the user in the same manner as if the appStoreReceipt was present, but the contents of the in_app array is empty or that the user is using the most recent version of the app. The contents of the in_app array is an array of purchase records of In-App Purchases made by the user within the app. Validating the receipt. I refer you to the Apple Developer documentation. https://developer.apple.com/documentation/storekit/in-app_purchase/validating_receipts_with_the_app_store There are 2 validation methods - static or App Store Server receipt validation. For an iOS app, the static method is more complicated in that you are required to obtain and build your own OpenSSL library to use to validate the receipt with. The recommended process is to forward the base64 encoded appStoreReceipt to your server. Your server then forwards the receipt to the iTunes Store verifyReceipt server for validation (known as server validation). An application may send the base64 encoded appStoreReceipt directly to the verifyReceipt server (which makes the validation process susceptible to a man-in-the-middle attack. The advantage with static receipt validation is that no network connection is required. However, if your application offers an auto-renewable subscription In-App Purchase type, server validation provides specific validation support advantages which is not available under static validation. What url should I use to verify my receipt? Use the sandbox URL https://sandbox.itunes.apple.com/verifyReceipt while testing your application in the sandbox and while your application is in review. Use the production URL https://buy.itunes.apple.com/verifyReceipt once your application is live in the App Store. Important: The App Review team reviews apps in the sandbox. Always verify your receipt first with the production URL; proceed to verify with the sandbox URL if you receive a 21007 status code. Following this approach ensures that you do not have to switch between URLs while your application is being tested or reviewed in the sandbox or is live in the App Store. The 21007 status code indicates that this receipt is a sandbox receipt, but it was sent to the production service for verification. A status of 0 indicates that the receipt was properly verified. Parsing the validated receipt. Assuming that the validation status result is 0, then parse the JSON results and look for the field “original_application_version”. The field “original_application_version” is a UTF8String and must be compared to the CFBundleVersion for the paid version of the app. If the original_application_version is equal to or less than the paid version of the app, the user is a paid app version user and should be given access to the paid content of the app. If the original_application_version is greater than the paid version of the app, then this is a new user who must purchase premium access using In-App Purchase. The gotcha here is that in the sandbox, using SKReceiptRefreshRequest to install an appStoreReceipt will result in one where the original_application_version is “1.0”. What you want to make sure, is to compare this field with the paid-app version - lets say that the paid app version is “4.0” make sure to compare against this value in the production build of the app. So the issue becomes, if there’s no appStoreReceipt, and the app uses the SKReceiptRefreshRequest call to refresh the receipt, the original_app_version field with be “1.0” - so use that as your test case for a paid app customer. Use the lack of receipt as the new app user test case so that you can test the in app purchase process. When the app makes it to the production App Store, and a paid user installs the app from the app store, the appStoreReceipt will be present and the original_app_version will match the version the user first installed - or it could be the current version of the app, if for a new user. rich kubota developer technical support CoreOS/Hardware/MFI
Post not yet marked as solved
2 Replies
Something else to consider - an upgrade in App Store Connect is not a logical concept, it's positional. If the one month subscription identifier is listed higher in the subscription group order than the twelve month identifier, the one month subscription would be considered an upgrade to the twelve month identifier. Which means if the user has purchased a one month subscription then attempts to purchase the twelve month subscription item, the app store would deem this a subscription downgrade and present the user with the alert that the twelve month subscription will take effect once the one month subscription expires. The contents of the pending_renewal_info reflects this situation. As to whether this can be switched, please contact app store connect. rich kubota developer technical support CoreOS/Hardware/MFI
Post not yet marked as solved
1 Replies
In response to your finding, this appears to be a bug report issue. Please use the Apple Developer Feedback Assistant web site to submit a bug report. In the bug report, please include the app ID of your app, the registered server-to-server address and recent notifications that have been received. rich kubota developer technical support CoreOS/Hardware/MFI
Post not yet marked as solved
2 Replies
In the production App Store, the restoreCompletedTransactions method is a StoreKit 1 API. It will restore all finished transactions made by the registered iTunes user that are either of type non-consumable are auto-renewing subscription. At the same time, when there are item to restore, the appStoreReceipt is updated to reflect the current transactions charged to the user. All of these transactions will be listed in the in_app array of the validated appStoreReceipt. It's the responsibility of the app process to detect whether individual transactions have been refunded, whether auto-renewing subscription transactions have expired or which auto-renewing subscription is active. In the case of refunded transactions or for current auto-renewing subscriptions, pass in the "exclude-old-transactions" : true key then review the contents of the "latest_info_section" of the validated response to get the current information on such items. To my knowledge, there is no clearing of transactions in the production App Store receipt. I would consider this a bug report issue if such a case were to be found. Rich Kubota - DTS
Post marked as solved
3 Replies
There is an aspect to the above solution - using the verifyReceipt endpoint to validate the appStoreReceipt as means to restoring In-App Purchases made by the user - which has an un-anticipated effect that affects some apps during App Review. In the sandbox environment, when the app is first installed via Xcode or by TestFlight, the appStoreReceiptURL is always nil. The solution then appears to be to make use of the SKReceiptRefreshRequest which will cause the sandbox App Store Server to return a temporary appStoreReceipt. There are 2 potential issues - 1. using SKReceiptRefreshRequest will cause the Authentication dialog to be raised as this request must be authenticated. 2. This call may be cancelled and the app should handle this case and not force the requirement that the appStoreReceipt be refreshed. For case 1. if the app checks for the presence of the appStoreReceipt at app launch and finds that it's nil, then uses SKReceiptRefreshRequest it's possible for an app to be rejected for trying to fish for private user information. Why is the authentication dialog raised at start-up when the user is just launching the app? If you are going to use this method, first present an alert to indicate that there appears to be an issue with the appStoreReceipt, then if the user OK's the request to proceed, call SKReceiptRefreshRequest. Otherwise assume that the user has not purchased any In-App Purchases, is not eligible for promotional offers and must make a standard In-App Purchase to access premium content. For case 2. if the SKReceiptRefreshRequest is made, allow for the authentication dialog to be cancelled or for the request to fail - here again make the assumption that the user has not purchased anything, is not eligible for promotional offers and must make a standard In-App Purchase to access premium content. The fact that the appStoreReceipt is nil only happens in the sandbox. Once the app is approved and is released to the App Store, it will always be installed with an appStoreReceipt by the App Store. rich kubota developer technical support CoreOS/Hardware/MFI