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.
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 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}")
}
}
Hi, Our app that implemented in-app payment has been reviewed and passed and is currently in operation.
However, there is a problem with the App Store server notification V2.
According to the url https://developer.apple.com/documentation/appstoreservernotifications/responding_to_app_store_server_notifications,
it is written as follows.
"When you set up the endpoints on your server to receive notifications, configure your server to send a response. Use HTTP status codes to indicate whether the App Store server notification post succeeded:
Send HTTP 200, or any HTTP code between 200 and 206, if the post was successful.
Send HTTP 50x or 40x to have the App Store retry the notification, if the post didn't succeed.
The system considers all other HTTP codes an unsuccessful post. Your server isn’t required to return a data value.
If the App Store server doesn’t receive a success response from your server after the initial notification attempt, it retries as follows:
For version 2 notifications, it retries five times, at 1, 12, 24, 48, and 72 hours after the previous attempt."
We are sending an HTTP status code to the Apple server by 200 or 40x or 50x when we receive an Apple notification from the server as per the document.
Nevertheless, Apple Server continues to send us 5 times App Store server notifications for each transaction.
I would appreciate it if you could share how we can do it.
Also, we can provide the implementation code of our server through code-level support.
Thank you for your support.
Hi, I am testing a consumable in-app purchase on my app, with a Sandbox account on an iPad device.
The transaction was successful, as I saw "You're all set. Your purchase was successful. [Environment: Sandbox].
I set a break point in Xcode after the line await transaction.finish() in following code
private func handle(transactionVerification result: VerificationResult ) async {
switch result {
case let .verified(transaction):
guard
let product = self.products.first(where: {
$0.id == transaction.productID
})
else {
return
}
self.addPurchased(product)
await transaction.finish()
return. <----- breakpoint
And I saw those property values for the transaction
id UInt64 88*****848
originalID UInt64 437****2496
.
Then I use the originalID value 437*****2496 in a server library call in node.js
....
const environment = Environment.SANDBOX
....
const client = new AppStoreServerAPIClient(encodedKey, keyId, issuerId, bundleId, environment)
....
const response = await client.getTransactionInfo("4379072496")
I got
apiError: 4040010, errorMessage: 'Transaction id not found.'
Could someone please tell me if I use the library call correctly with the right id? And why I got the error?
Thank you very much!
Kind regards,
Shih-Chin Yang
[Edited by Moderator]
Hi I am Justin,
I have vs code and I am asking a question and I have never programed in my life or written a code I am just asking when your done writing a code how do you upload it to like say Xbox etc like they all do in the movies or tv shows just asking since I like computers.