This documentation describes what kind of data we should be sending to Apple server, once we are receiving CONSUMPTION_REQUEST
https://developer.apple.com/documentation/appstoreserverapi/consumptionrequest
But, it doesn't describe what kind of data we are receiving, when we are receiving CONSUMPTION_REQUEST?
May I know, is such a document available?
Thank you.
App Store Server Library
RSS for tagApp Store Server Library is the library for the App Store Server API and App Store Server Notifications. It provides an API client, a JWS signed data verifier, a utility to extract a transaction id from a receipt, and a promotional offer signature.
Posts under App Store Server Library tag
30 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
Hello. I am trying to give an update to my app but it again and again gets rejected due to the ATT Prompt. Before this late week I gave the update and it got live without any issue. Now I done some minor changes in the App.
Apple Response.
The app uses the AppTrackingTransparency framework, but we are unable to locate the App Tracking Transparency permission request when reviewed on iPadOS 18.2.
Next Steps
Explain where we can find the App Tracking Transparency permission request in the app. The request should appear before any data is collected that could be used to track the user.
If App Tracking Transparency is implemented but the permission request is not appearing on devices running the latest operating system, review the available documentation and confirm App Tracking Transparency has been correctly implemented.
If your app does not track users, update your app privacy information in App Store Connect to not declare tracking. You must have the Account Holder or Admin role to update app privacy information.
My Response:
Hello Apple Team
Thank you for your feedback.
We have tested the app on iPadOS 18.2, also on iPhone 18.1 and the App Tracking Transparency dialogue is appearing as expected on the main home screen when the user enters the app. To help demonstrate this, we’ve attached a video showing the ATT prompt in action.
If there is still an issue with the dialogue or if it needs to be placed in a different position, we kindly request your guidance on what needs to be adjusted. Please let us know the details so we can address it promptly.
Thank you for your support
"I uploaded a video with images showcasing the ATT prompt but now again they rejected the update with the exact same reply. Which I am thinking it may be a bot reply.
Now what to do how to solve it?
The documentation mentions the following:
Verify your receipt first with the production URL; then verify with the sandbox URL if you receive a 21007 status code. This approach ensures you don’t have to switch between URLs while your app is in testing, in review by App Review, or live in the App Store.
This way, you can use one server environment to handle both Sandbox and Production environments. It is necessary to pass App Review.
However, I'm not manually hitting these URLs - I'm using Apple's libraries.
Specifically, the environment is used in SignedDataVerifier and AppStoreServerAPIClient.
(I can't link to these because, for some reason, the domain apple.github.io is not allowed. The documentation for these is only found there. You can find it quickly by searching these terms and the domain.)
Here is how SignedDataVerifier is being used:
const verifier = new SignedDataVerifier(
appleRootCertificates,
APPLE_ENABLE_ONLINE_CHECKS,
APPLE_ENVIRONMENT,
APPLE_BUNDLE_ID,
APPLE_APP_ID
)
const verifiedNotification: ResponseBodyV2DecodedPayload = await verifier.verifyAndDecodeNotification(signedPayload)
if (!verifiedNotification)
{
// Failure
return
}
Here is how AppStoreServerAPIClient is being used:
const appStoreServerAPIClient = new AppStoreServerAPIClient(
SIGNING_KEY,
APPLE_IAP_KEY_ID,
APPLE_IAP_ISSUER_ID,
APPLE_BUNDLE_ID,
APPLE_ENVIRONMENT
)
const statusResponse: StatusResponse = await appStoreServerAPIClient.getAllSubscriptionStatuses(originalTransactionId, [Status.ACTIVE])
In the source code for SignedDataVerifier.verifyAndDecodeNotification, I can see that it throws a VerificationException(VerificationStatus.INVALID_ENVIRONMENT) error .
So for SignedDataVerifier is it as simple as wrapping my code in a try/catch and checking that the error's status code is 21007? I'm unsure about this because if you scroll to the bottom of the linked source code file, you can see the enumeration VerificationStatus, but it's unclear if this member has a value of 21007.
The source code for AppStoreServerAPIClient only says that it throws an APIException if a response could not be processed, so I'm not too sure about how to handle this one.
Help please. Tell me how to correctly verify a receipt using the apple/app-store-server-library for node.js? After what is the receipt considered verified? Old verifyReceipt is deprecated.
Hello, I am currently encountering an issue while using the server-side API for in-app purchase integration. Suppose a user has already purchased a product, and the App Store returns a transactionId of 1. After some time, the user repeats the purchase for some reason, and the App Store returns a transactionId of 2. However, when I use the Get Transaction History interface to query the transaction information for transactionId 2, I see that the data returned by the App Store does not include the transaction data for transactionId 2; it only contains the transaction data for transactionId 1.
In this situation, my guess is that when the user makes a repeat purchase, the App Store recognizes that the user has already purchased the item and has not executed a refund. Therefore, the App Store generates a new transactionId for this request (the user's purchase) and associates it with the previous purchase's transaction data. This is my inference.
If this user has made 5 repeat purchases, when the user successfully requests a refund, if I query the transaction information through the Get Transaction History interface, will the revocationDate for all 5 transactions in the App Store's official database be modified to the same date? Additionally, after the user successfully refunds and makes another purchase, will the new transactionId still be associated with the previous transaction data?
How can I get the App Store Server Notifications again if my server is down? As far as I know, Apple sends a notification once. What if the server was unavailable? How to get this notification again?
Hello team, why do I occasionally encounter (java. lang. TimeException: com. apple. itunes. storekit. verification. Verification Exception: Verification failed with status INVALID_CHAIN) when calling (BaseAppStoreServerAPIClient. getTransactionInfo) using (app-store-ser-library 3.1.0) in Java
We experienced an issue with delayed App Store Server notifications for an Auto-Renewable Subscription purchase in our app.
On September 27, 2024, at 22:28:28 GMT+0900, a user successfully purchased a Premium membership subscription (transaction ID: 190002223966278, webOrderLineItemId: 190001007274949).
However, our server did not receive the corresponding App Store notification for this transaction after purchase.
The App Store Server Notification was only received 66 minutes later, at 23:35:16 (transaction ID: 190002224000004, webOrderLineItemId: 190001007274949).
The delayed notification contained a different transaction ID than the initial purchase.
We would like to inquire about the cause of this delay and request assistance in understanding why the notification took 66 minutes to be delivered to our server, instead of arriving immediately after the transaction was completed.
Additionally, we would appreciate your guidance on how to prevent or mitigate such delays in the future, ensuring a seamless experience for our users.
Are there any best practices or recommended approaches we should implement to handle potential notification delays more effectively?
We have provided detailed information about the received notification and the related transactions in the Feedback Assistant (FB15330451).
Thank you for your assistance in resolving this matter.
Key Details
Purchase time: 2024-09-27T22:28:28 GMT+0900 (1727443708000)
Notification received: 2024-09-27T23:35:16 GMT+0900 (1727447716463)
Delay: 66 minutes and 48 seconds
Initial Purchase Transaction ID: 190002223966278
Initial Purchase Web Order Line Item ID: 190001007274949
Transaction ID in Delayed Notification: 190002224000004
Web Order Line Item ID in Delayed Notification: 190001007274949
Additional info
Bundle ID: com.reppley.ReppleyApp
SKU: com.reppley.ReppleyApp
Feedback Assistant Number: FB15330451
Hello team,
There is a Function,App create a order;When the app is all paid out,it sends me a credentials; I accept and verify;
verify_and_decode_signed_transaction function have an error;
requests.exceptions.ConnectionError: HTTPConnectionPool(host='ocsp.apple.com', port=80): Max retries exceeded with url: /ocsp03-applerootcag3 (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x1123c7d00>: Failed to establish a new connection: [Errno 61] Connection refused'))
The above exception was the direct cause of the following exception:
appstoreserverlibrary.signed_data_verifier.VerificationException: Verification failed with status VERIFICATION_FAILURE
def verify_purchase(self, request, *args, **kwargs):
try:
receipt_data = request.data.get('receipt_data')
transaction_id = receiptUtilInstance.extract_transaction_id_from_app_receipt(receipt_data)
sendResponse: TransactionInfoResponse = appleClientInstance.client.get_transaction_info(transaction_id)
signedPayLoad: str = sendResponse.signedTransactionInfo
time.sleep(3)
payload = signedDataInstance.client.verify_and_decode_signed_transaction(signedPayLoad)
please help me
My App uses an auto-renewal subscription, and I've been using the CONSUMPTION_REQUEST event to track user refund requests. However, since yesterday morning, I haven't been receiving any CONSUMPTION_REQUEST notifications. All other notifications are functioning normally and I haven't changed anything. What could be the issue?
Hi there,
I'm having trouble using this API: https://developer.apple.com/documentation/appstoreserverapi/extend_subscription_renewal_dates_for_all_active_subscribers
It is returning a 400 response for all product IDs for my app's subscriptions:
(400, 4000023, 'Invalid request. The product id parameter is invalid.')
I have verified these productIds in the App Store Server UI, and using the App Store Connect API to retrieve the product IDs for all subscriptions in the subscription group.
Any tips would be greatly appreciated!
Is there an explicit service compensation somewhere if App Store Server Notifications goes down?
This question is about wanting to know the SLA.
We receive a JWS receipt and successfully verify the x5c pass. However, when we use the transaction_id from the receipt to query the endpoint https://api.storekit.itunes.apple.com/inApps/v1/transactions/, we encounter the following error: {"errorCode":4040010,"errorMessage":"Transaction id not found."}.
Why does this happen? Thanks for your answer.
The jws receipt is:
eyJhbGciOiJFUzI1NiIsIng1YyI6WyJNSUlFTURDQ0E3YWdBd0lCQWdJUWZUbGZkMGZOdkZXdnpDMVlJQU5zWGpBS0JnZ3Foa2pPUFFRREF6QjFNVVF3UWdZRFZRUURERHRCY0hCc1pTQlhiM0pzWkhkcFpHVWdSR1YyWld4dmNHVnlJRkpsYkdGMGFXOXVjeUJEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURUxNQWtHQTFVRUN3d0NSell4RXpBUkJnTlZCQW9NQ2tGd2NHeGxJRWx1WXk0eEN6QUpCZ05WQkFZVEFsVlRNQjRYRFRJek1Ea3hNakU1TlRFMU0xb1hEVEkxTVRBeE1URTVOVEUxTWxvd2daSXhRREErQmdOVkJBTU1OMUJ5YjJRZ1JVTkRJRTFoWXlCQmNIQWdVM1J2Y21VZ1lXNWtJR2xVZFc1bGN5QlRkRzl5WlNCU1pXTmxhWEIwSUZOcFoyNXBibWN4TERBcUJnTlZCQXNNSTBGd2NHeGxJRmR2Y214a2QybGtaU0JFWlhabGJHOXdaWElnVW1Wc1lYUnBiMjV6TVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1Rc3dDUVlEVlFRR0V3SlZVekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCRUZFWWUvSnFUcXlRdi9kdFhrYXVESENTY1YxMjlGWVJWLzB4aUIyNG5DUWt6UWYzYXNISk9OUjVyMFJBMGFMdko0MzJoeTFTWk1vdXZ5ZnBtMjZqWFNqZ2dJSU1JSUNCREFNQmdOVkhSTUJBZjhFQWpBQU1COEdBMVVkSXdRWU1CYUFGRDh2bENOUjAxREptaWc5N2JCODVjK2xrR0taTUhBR0NDc0dBUVVGQndFQkJHUXdZakF0QmdnckJnRUZCUWN3QW9ZaGFIUjBjRG92TDJObGNuUnpMbUZ3Y0d4bExtTnZiUzkzZDJSeVp6WXVaR1Z5TURFR0NDc0dBUVVGQnpBQmhpVm9kSFJ3T2k4dmIyTnpjQzVoY0hCc1pTNWpiMjB2YjJOemNEQXpMWGQzWkhKbk5qQXlNSUlCSGdZRFZSMGdCSUlCRlRDQ0FSRXdnZ0VOQmdvcWhraUc5Mk5rQlFZQk1JSCtNSUhEQmdnckJnRUZCUWNDQWpDQnRneUJzMUpsYkdsaGJtTmxJRzl1SUhSb2FYTWdZMlZ5ZEdsbWFXTmhkR1VnWW5rZ1lXNTVJSEJoY25SNUlHRnpjM1Z0WlhNZ1lXTmpaWEIwWVc1alpTQnZaaUIwYUdVZ2RHaGxiaUJoY0hCc2FXTmhZbXhsSUhOMFlXNWtZWEprSUhSbGNtMXpJR0Z1WkNCamIyNWthWFJwYjI1eklHOW1JSFZ6WlN3Z1kyVnlkR2xtYVdOaGRHVWdjRzlzYVdONUlHRnVaQ0JqWlhKMGFXWnBZMkYwYVc5dUlIQnlZV04wYVdObElITjBZWFJsYldWdWRITXVNRFlHQ0NzR0FRVUZCd0lCRmlwb2RIUndPaTh2ZDNkM0xtRndjR3hsTG1OdmJTOWpaWEowYVdacFkyRjBaV0YxZEdodmNtbDBlUzh3SFFZRFZSME9CQllFRkFNczhQanM2VmhXR1FsekUyWk9FK0dYNE9vL01BNEdBMVVkRHdFQi93UUVBd0lIZ0RBUUJnb3Foa2lHOTJOa0Jnc0JCQUlGQURBS0JnZ3Foa2pPUFFRREF3Tm9BREJsQWpFQTh5Uk5kc2twNTA2REZkUExnaExMSndBdjVKOGhCR0xhSThERXhkY1BYK2FCS2pqTzhlVW85S3BmcGNOWVVZNVlBakFQWG1NWEVaTCtRMDJhZHJtbXNoTnh6M05uS20rb3VRd1U3dkJUbjBMdmxNN3ZwczJZc2xWVGFtUllMNGFTczVrPSIsIk1JSURGakNDQXB5Z0F3SUJBZ0lVSXNHaFJ3cDBjMm52VTRZU3ljYWZQVGp6Yk5jd0NnWUlLb1pJemowRUF3TXdaekViTUJrR0ExVUVBd3dTUVhCd2JHVWdVbTl2ZENCRFFTQXRJRWN6TVNZd0pBWURWUVFMREIxQmNIQnNaU0JEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVMTUFrR0ExVUVCaE1DVlZNd0hoY05NakV3TXpFM01qQXpOekV3V2hjTk16WXdNekU1TURBd01EQXdXakIxTVVRd1FnWURWUVFERER0QmNIQnNaU0JYYjNKc1pIZHBaR1VnUkdWMlpXeHZjR1Z5SUZKbGJHRjBhVzl1Y3lCRFpYSjBhV1pwWTJGMGFXOXVJRUYxZEdodmNtbDBlVEVMTUFrR0ExVUVDd3dDUnpZeEV6QVJCZ05WQkFvTUNrRndjR3hsSUVsdVl5NHhDekFKQmdOVkJBWVRBbFZUTUhZd0VBWUhLb1pJemowQ0FRWUZLNEVFQUNJRFlnQUVic1FLQzk0UHJsV21aWG5YZ3R4emRWSkw4VDBTR1luZ0RSR3BuZ24zTjZQVDhKTUViN0ZEaTRiQm1QaENuWjMvc3E2UEYvY0djS1hXc0w1dk90ZVJoeUo0NXgzQVNQN2NPQithYW85MGZjcHhTdi9FWkZibmlBYk5nWkdoSWhwSW80SDZNSUgzTUJJR0ExVWRFd0VCL3dRSU1BWUJBZjhDQVFBd0h3WURWUjBqQkJnd0ZvQVV1N0Rlb1ZnemlKcWtpcG5ldnIzcnI5ckxKS3N3UmdZSUt3WUJCUVVIQVFFRU9qQTRNRFlHQ0NzR0FRVUZCekFCaGlwb2RIUndPaTh2YjJOemNDNWhjSEJzWlM1amIyMHZiMk56Y0RBekxXRndjR3hsY205dmRHTmhaek13TndZRFZSMGZCREF3TGpBc29DcWdLSVltYUhSMGNEb3ZMMk55YkM1aGNIQnNaUzVqYjIwdllYQndiR1Z5YjI5MFkyRm5NeTVqY213d0hRWURWUjBPQkJZRUZEOHZsQ05SMDFESm1pZzk3YkI4NWMrbGtHS1pNQTRHQTFVZER3RUIvd1FFQXdJQkJqQVFCZ29xaGtpRzkyTmtCZ0lCQkFJRkFEQUtCZ2dxaGtqT1BRUURBd05vQURCbEFqQkFYaFNxNUl5S29nTUNQdHc0OTBCYUI2NzdDYUVHSlh1ZlFCL0VxWkdkNkNTamlDdE9udU1UYlhWWG14eGN4ZmtDTVFEVFNQeGFyWlh2TnJreFUzVGtVTUkzM3l6dkZWVlJUNHd4V0pDOTk0T3NkY1o0K1JHTnNZRHlSNWdtZHIwbkRHZz0iLCJNSUlDUXpDQ0FjbWdBd0lCQWdJSUxjWDhpTkxGUzVVd0NnWUlLb1pJemowRUF3TXdaekViTUJrR0ExVUVBd3dTUVhCd2JHVWdVbTl2ZENCRFFTQXRJRWN6TVNZd0pBWURWUVFMREIxQmNIQnNaU0JEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVMTUFrR0ExVUVCaE1DVlZNd0hoY05NVFF3TkRNd01UZ3hPVEEyV2hjTk16a3dORE13TVRneE9UQTJXakJuTVJzd0dRWURWUVFEREJKQmNIQnNaU0JTYjI5MElFTkJJQzBnUnpNeEpqQWtCZ05WQkFzTUhVRndjR3hsSUVObGNuUnBabWxqWVhScGIyNGdRWFYwYUc5eWFYUjVNUk13RVFZRFZRUUtEQXBCY0hCc1pTQkpibU11TVFzd0NRWURWUVFHRXdKVlV6QjJNQkFHQnlxR1NNNDlBZ0VHQlN1QkJBQWlBMklBQkpqcEx6MUFjcVR0a3lKeWdSTWMzUkNWOGNXalRuSGNGQmJaRHVXbUJTcDNaSHRmVGpqVHV4eEV0WC8xSDdZeVlsM0o2WVJiVHpCUEVWb0EvVmhZREtYMUR5eE5CMGNUZGRxWGw1ZHZNVnp0SzUxN0lEdll1VlRaWHBta09sRUtNYU5DTUVBd0hRWURWUjBPQkJZRUZMdXczcUZZTTRpYXBJcVozcjY5NjYvYXl5U3JNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01Bb0dDQ3FHU000OUJBTURBMmdBTUdVQ01RQ0Q2Y0hFRmw0YVhUUVkyZTN2OUd3T0FFWkx1***5UmhIRkQvM21lb3locG12T3dnUFVuUFdUeG5TNGF0K3FJeFVDTUcxbWloREsxQTNVVDgyTlF6NjBpbU9sTTI3amJkb1h0MlFmeUZNbStZaGlkRGtMRjF2TFVhZ002QmdENTZLeUtBPT0iXX0.eyJ0cmFuc2FjdGlvbklkIjoiMzAwMDAxODE3NTk4MjgwIiwib3JpZ2luYWxUcmFuc2FjdGlvbklkIjoiMzAwMDAxODE3NTk4MjgwIiwiYnVuZGxlSWQiOiJjb20ud2Vqb3kud2VwbGF5LmFyIiwicHJvZHVjdElkIjoiY29pbjYwMF9hciIsInB1cmNoYXNlRGF0ZSI6MTcxNDAwMDE4MjAwMCwib3JpZ2luYWxQdXJjaGFzZURhdGUiOjE3MTQwMDAxODIwMDAsInF1YW50aXR5IjoxLCJ0eXBlIjoiQ29uc3VtYWJsZSIsImRldmljZVZlcmlmaWNhdGlvbiI6Im00SFVMaUxKemVyMzZNK3pkZVNnYzY4MXRlQUZQY3FhUVJ3dEFxU25xdFU2emI0U29haUtmYnM5Mm1QU1FhcjUiLCJkZXZpY2VWZXJpZmljYXRpb25Ob25jZSI6IjMzODU5YzIzLTY5NDQtNDQ1Yi1iYmU3LTliNDY5YjhmZjBlZSIsImFwcEFjY291bnRUb2tlbiI6IjZkNjU4MjZmLTdkZDAtNDIwMi1iZmQ2LTEzMjJhOWQyZTY5NCIsImluQXBwT3duZXJzaGlwVHlwZSI6IlBVUkNIQVNFRCIsInNpZ25lZERhdGUiOjE3MTQwMDAxODA2MDcsImVudmlyb25tZW50IjoiUHJvZHVjdGlvbiIsInRyYW5zYWN0aW9uUmVhc29uIjoiUFVSQ0hBU0UiLCJzdG9yZWZyb250IjoiU0FVIiwic3RvcmVmcm9udElkIjoiMTQzNDc5IiwicHJpY2UiOjM5OTAsImN1cnJlbmN5IjoiU0FSIn0.WNfzeCcChz--9tasyrejhgHw4cCrehptucQKVDF_FU46vHqIIlWvQY_jNUDSDhuT2fVYbL10yVx6uA6q5XfCbw
Greetings for App Store Server Notifications especially sandbox (I have not tried production) it seems that that is no way to associate two transactions to their orignal subscription instance
the docs indicate webLineItemOrderId id but they are different from each subscription
the originalTransactionId is the same among all subscription instances and customers, theres originalPurchaseDate but that is not reliable becuase 2 transactions can occur on the same date what can be done here. I even tried to use it but its like the sandbox is broken with the originalPurchaseDate of one instance being the same as another. is it a bug or will things get in line once I go to production?
We are using Apple Notification V2 and supporting only one subscription service per user (e.g., Netflix with Basic, Premium plans). In Notification V1, we could fetch the latest subscription information using the /verifyReceipt API. However, the documentation for Notification V2 suggests using SignedDataVerifier to decode the data locally.
This raises a concern that the data received via notification might not always be up-to-date. For example, if a user cancels a subscription but the server fails to process the notification for some reason, and later the user upgrades and resumes the subscription, the new notification succeeds. In this scenario, the previously failed cancellation notification will be resent after some time. How should the backend server handle this?
Should we always use APIs like get all subscription statuses to fetch the latest data instead of solely relying on the decoded data from each notification?
Hello,
Do Apple root certificates AppleRootCA-G2.cer and AppleRootCA-G3.cer expire?
if yes, in how long?
thanks in advance.
As of the latest available information, Apple does not directly provide an endpoint for checking refund history for multiple transactions in a single request via their App Store Server API. The endpoint
https://api.storekit.itunes.apple.com/inApps/v2/refund/lookup/{transactionId}
allows you to check the refund status for a single transaction ID at a time. What if you have 8000 subscribers? Are we going to send 8k requests everyday? What's the solution for this scenario? We don't want to implement the notifications by the way. Any solutions?
The application crashes and displays the error "SIGNAL 6:Abort trap:6" while it is in movement. We have implemented telematics functionality, which required us to add permissions for location, motion, and bluetooth. We have attached the log file. Help us to resolve this
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Reason: -[%s %s]: unrecognized selector sent to instance 0x303475170
Termination Reason: SIGNAL 6 Abort trap: 6
Triggered by Thread: 6
Last Exception Backtrace:
0 CoreFoundation 0x1a6594f20 __exceptionPreprocess + 164 (NSException.m:249)
1 libobjc.A.dylib 0x19e426018 objc_exception_throw + 60 (objc-exception.mm:356)
2 CoreFoundation 0x1a669e480 -[NSObject(NSObject) doesNotRecognizeSelector:] + 344 (NSObject.m:161)
3 CoreFoundation 0x1a6531fb4 forwarding + 1572 (NSForwarding.m:3612)
4 CoreFoundation 0x1a65318d0 CF_forwarding_prep_0 + 96 (:-1)
5 Boubyan Takaful Insurance 0x1045199fc -[MendixEncryptedStorageModule getItem:resolver:rejecter:] + 456 (MendixEncryptedStorageModule.m:93)
6 CoreFoundation 0x1a6531814 invoking + 148 (:-1)
7 CoreFoundation 0x1a6530860 -[NSInvocation invoke] + 428 (NSForwarding.m:3411)
8 CoreFoundation 0x1a65a71dc -[NSInvocation invokeWithTarget:] + 64 (NSForwarding.m:3508)
9 Boubyan Takaful Insurance 0x1041fc6fc -[RCTModuleMethod invokeWithBridge:module:arguments:] + 388 (RCTModuleMethod.mm:584)
10 Boubyan Takaful Insurance 0x1041fe6e0 facebook::react::invokeInner(RCTBridge*, RCTModuleData*, unsigned int, folly::dynamic const&, int, (anonymous namespace)::SchedulingContext) + 452 (RCTNativeModule.mm:183)
11 Boubyan Takaful Insurance 0x1041fe330 facebook::react::RCTNativeModule::invoke(unsigned int, folly::dynamic&&, int)::$_0::operator()() const + 68 (RCTNativeModule.mm:104)
12 Boubyan Takaful Insurance 0x1041fe330 invocation function for block in facebook::react::RCTNativeModule::invoke(unsigned int, folly::dynamic&&, int) + 112 (RCTNativeModule.mm:95)
13 libdispatch.dylib 0x1ae43813c _dispatch_call_block_and_release + 32 (init.c:1530)
14 libdispatch.dylib 0x1ae439dd4 _dispatch_client_callout + 20 (object.m:576)
15 libdispatch.dylib 0x1ae441400 _dispatch_lane_serial_drain + 748 (queue.c:3900)
16 libdispatch.dylib 0x1ae441f30 _dispatch_lane_invoke + 380 (queue.c:3991)
17 libdispatch.dylib 0x1ae44ccb4 _dispatch_root_queue_drain_deferred_wlh + 288 (queue.c:6998)
18 libdispatch.dylib 0x1ae44c528 _dispatch_workloop_worker_thread + 404 (queue.c:6592)
19 libsystem_pthread.dylib 0x2033eb934 _pthread_wqthread + 288 (pthread.c:2696)
20 libsystem_pthread.dylib 0x2033e80cc start_wqthread + 8 (:-1)
hread 6 crashed with ARM Thread State (64-bit):
x0: 0x0000000000000000 x1: 0x0000000000000000 x2: 0x0000000000000000 x3: 0x0000000000000000
x4: 0x00000002033112c3 x5: 0x000000016c0aa810 x6: 0x000000000000006e x7: 0x0000000000000000
x8: 0xe50a98ccb19cbcdd x9: 0xe50a98cddd960cdd x10: 0x0000000000000200 x11: 0x000000016c0aa340
x12: 0x0000000000000000 x13: 0x00000000001ff800 x14: 0x0000000000000010 x15: 0x0000000000000000
x16: 0x0000000000000148 x17: 0x000000016c0ab000 x18: 0x0000000000000000 x19: 0x0000000000000006
x20: 0x0000000000002403 x21: 0x000000016c0ab0e0 x22: 0x0000000000000114 x23: 0x000000016c0ab0e0
x24: 0x0000000301846ee8 x25: 0x0000000000000000 x26: 0x0000000000000000 x27: 0x0000000302354e00
x28: 0x0000000000000000 fp: 0x000000016c0aa780 lr: 0x00000002033eec0c
sp: 0x000000016c0aa760 pc: 0x00000001ef64f42c cpsr: 0x40001000
esr: 0x56000080 Address size fault
crashlog.crash
I recently had a request from a Product Owner to implement capability like Netflix has to enable users to switch their payment method from App Store Subscription to Credit Card on our website, as per capability that Netflix has in their account management portal.
We tested it today with a colleague who was paying for Netflix through iOS in-app subscription:
In the Netflix account management pages it showed that he was currently paying via In App Subscription
He updated his payment method to Credit Card in their website's account management portal.
Almost immediately after adding his Credit Card in his iOS Subscription Management settings (we could see the the subscription had been set to no longer renew, with an expiry date)
How is this done - I can't see any API in App Store Server API documentation that gives us a way of cancelling / preventing renewal of subscriptions on behalf of a user... But Netflix can clearly do it somehow...
Hello,
I am currently implementing server-side handling for in-app subscription payments and using App Store Server Notifications V2 to receive notifications with a TestFlight account.
However, I am not receiving EXPIRED notifications, although I am successfully receiving other notifications such as SUBSCRIBED, DID_RENEW, and DID_CHANGE_RENEWAL_PREF.
Here are some details of my observations:
Until a certain point, I was receiving EXPIRED notifications without any issues, but they stopped coming in after that point.
Each subscription renews every 3 minutes in TestFlight account, and I receive DID_RENEW notifications 7 to 12 times. According to the official documentation, the subscriptions should renew up to 12 times, but I am not receiving exactly 12 DID_RENEW notifications. This inconsistency might be related to the issue.
Other notifications (SUBSCRIBED, DID_RENEW, DID_CHANGE_RENEWAL_PREF) are received without any issues.
Could anyone provide insight into why this might be happening and suggest any alternative methods to handle subscription expirations in case the EXPIRED notifications are not reliable?
Thank you for your assistance.
Relevant Official Documentation
App Store Server Notifications V2
App Store Server Notifications V2_notificationType
Testing in-app purchases with sandbox
Current Server Implementation
Below is the Kotlin Spring Boot server code currently implemented for handling App Store Server Notifications V2:
@RestController
class ProductIosController(
private val productIosService: ProductIosService,
private val appStoreNotificationService: AppStoreNotificationService,
) : BaseController {
@PostMapping("/api/v1/ios-products/app-store-notifications-v2")
fun handleNotification(@RequestBody @Valid notification: AppStoreNotificationRequest): CustomResponse {
appStoreNotificationService.processNotification(notification)
return CustomResponse.ok()
}
}
@Service
class AppStoreNotificationService(
@Qualifier("appStoreClient") private val appStoreServerAPIClient: AppStoreServerAPIClient,
@Qualifier("signedVerifier") private val signedDataVerifier: SignedDataVerifier,
) {
@Transactional
fun processNotification(notification: AppStoreNotificationRequest) {
logger.info("signedPayload: ${notification.signedPayload}")
val decodedPayload = verifyAndDecodeSignedPayload(notification.signedPayload)
val notificationType = decodedPayload.notificationType
val signedTransactionInfo = decodedPayload.data.signedTransactionInfo
val transaction = signedDataVerifier.verifyAndDecodeTransaction(signedTransactionInfo)
val (user, product) = fetchUserAndProduct(transaction)
when (notificationType) {
SUBSCRIBED -> processSubscriptionPurchase(user, product, decodedPayload, transaction)
DID_CHANGE_RENEWAL_PREF -> processSubscriptionGradeChange(user, product, decodedPayload, transaction)
DID_CHANGE_RENEWAL_STATUS -> processRenewalStatusChange(transaction)
OFFER_REDEEMED -> processOfferRedeemed(transaction)
DID_RENEW -> processSubscriptionRenewal(user, product, decodedPayload, transaction)
EXPIRED -> processSubscriptionExpiration(user, product)
DID_FAIL_TO_RENEW -> processFailedRenewal(transaction)
GRACE_PERIOD_EXPIRED -> processSubscriptionGracePeriodExpiration(transaction)
PRICE_INCREASE -> processPriceIncrease(transaction)
REFUND -> processSubscriptionRefund(transaction)
REFUND_DECLINED -> processRefundDeclined(transaction)
CONSUMPTION_REQUEST -> processConsumptionRequest(transaction)
RENEWAL_EXTENDED -> processRenewalExtension(transaction)
REVOKE -> processSubscriptionRevocation(transaction)
TEST -> processTestNotification(transaction)
RENEWAL_EXTENSION -> processRenewalExtension(transaction)
REFUND_REVERSED -> processRefundReversed(transaction)
EXTERNAL_PURCHASE_TOKEN -> processExternalPurchaseToken(transaction)
else -> logger.warn("Unsupported notification type: ${notificationType.value}")
}
}