I want to add in-app purchasing to my app, but I can't figure out what part of my workflow is wrong.
I created a product for my app in iTunes Connect (the product ID is com.mycompany.products.***) and it's in "Ready to submit" status.
I created a sandbox test user for this app.
I connected to iTunes on a real device using the sandbox AppleID.
I went back to XCode and added in-app purchasing to my app.
I turned on developer mode on the real device and logged in as the sandbox user.
I built the app and ran it on a real device (not the simulator).
I tried to get product information (com.mycompany.products.***) but nothing was returned.
In-app purchasing is registered in App Store Connect and the status is "Ready to submit".
The code only retrieves product information in a simple way, so I don't think there's a problem.
inAppPurchase.getProducts(["com.mycompany.products.***"]).then(console.log).catch(console.error);
But it only returns an empty array.
What could be wrong?
Any help would be much appreciated.
StoreKit
RSS for tagSupport in-app purchases and interactions with the App Store using StoreKit.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Created
We are running into exceptions when trying to parse Purchase Date and Original Purchase Date from the base64 encoded receipt.
Expected RFC 3339 format of ASN.1 Field Value is: yyyy-MM-ddTHH:mm:ssZ but we end up getting back 2025-04-22T19.49.03Z.
Started to happen on 3rd Dec 2024.
Issue Description
When using the App Store Server API endpoint GET v2/history/{transactionId} to retrieve transaction history for a specific transaction, I'm observing unexpected changes in the appTransactionId field across related transactions in the same subscription group.
Important Context: This is a "clean" auto-renewable subscription with no user intervention - the user has had continuous auto-renewals without any upgrades, downgrades, cancellations, or resubscriptions. The subscription has been renewing automatically and successfully throughout the entire period.
API Call
GET v2/history/1000000000000001
Response Data
The API returns the following transaction history, where I notice the appTransactionId values are inconsistent across what should be a straightforward auto-renewal sequence:
Note: The data below has been sanitized for privacy protection (IDs, bundle identifiers, etc. have been replaced with example values), but the logical relationships, date sequences, and the core issue remain identical to the original data.
Array
(
[0] => Array
(
[transactionId] => 1000000000000001
[originalTransactionId] => 1000000000000001
[webOrderLineItemId] => 1000000000000001
[bundleId] => com.example.myapp
[productId] => MonthlySubscription
[subscriptionGroupIdentifier] => 20000000
[purchaseDate] => 1743784032000
[originalPurchaseDate] => 1743784034000
[expiresDate] => 1746376032000
[quantity] => 1
[type] => Auto-Renewable Subscription
[inAppOwnershipType] => PURCHASED
[signedDate] => 1751868174651
[environment] => Production
[transactionReason] => PURCHASE // Original purchase
[storefront] => USA
[storefrontId] => 143441
[price] => 100000
[currency] => USD
[appTransactionId] => 700000000000000001 // Different value
)
[1] => Array
(
[transactionId] => 1000000000000002
[originalTransactionId] => 1000000000000001
[webOrderLineItemId] => 1000000000000002
[bundleId] => com.example.myapp
[productId] => MonthlySubscription
[subscriptionGroupIdentifier] => 20000000
[purchaseDate] => 1746376032000
[originalPurchaseDate] => 1746347349000
[expiresDate] => 1749054432000
[quantity] => 1
[type] => Auto-Renewable Subscription
[inAppOwnershipType] => PURCHASED
[signedDate] => 1751868174651
[environment] => Production
[transactionReason] => RENEWAL // First auto-renewal
[storefront] => USA
[storefrontId] => 143441
[price] => 100000
[currency] => USD
[appTransactionId] => 700000000000000002 // Same for renewals
)
[2] => Array
(
[transactionId] => 1000000000000003
[originalTransactionId] => 1000000000000001
[webOrderLineItemId] => 1000000000000003
[bundleId] => com.example.myapp
[productId] => MonthlySubscription
[subscriptionGroupIdentifier] => 20000000
[purchaseDate] => 1749054432000
[originalPurchaseDate] => 1749025657000
[expiresDate] => 1751646432000
[quantity] => 1
[type] => Auto-Renewable Subscription
[inAppOwnershipType] => PURCHASED
[signedDate] => 1751868174651
[environment] => Production
[transactionReason] => RENEWAL // Second auto-renewal
[storefront] => USA
[storefrontId] => 143441
[price] => 100000
[currency] => USD
[appTransactionId] => 700000000000000002 // Same as previous renewal
)
[3] => Array
(
[transactionId] => 1000000000000004
[originalTransactionId] => 1000000000000001
[webOrderLineItemId] => 1000000000000004
[bundleId] => com.example.myapp
[productId] => MonthlySubscription
[subscriptionGroupIdentifier] => 20000000
[purchaseDate] => 1751646432000
[originalPurchaseDate] => 1751617840000
[expiresDate] => 1754324832000
[quantity] => 1
[type] => Auto-Renewable Subscription
[inAppOwnershipType] => PURCHASED
[signedDate] => 1751868174651
[environment] => Production
[transactionReason] => RENEWAL // Third auto-renewal
[storefront] => USA
[storefrontId] => 143441
[price] => 100000
[currency] => USD
[appTransactionId] => 700000000000000002 // Same as previous renewals
)
)
Questions
Is this behavior expected? Should the appTransactionId change between the original purchase and subsequent renewals within the same subscription group, especially when there are no user actions (upgrades/downgrades/cancellations/resubscriptions)?
What determines the appTransactionId value? The documentation doesn't clearly explain when this identifier might change or what triggers a new value. This is particularly puzzling since this is a straightforward auto-renewal scenario.
How should we handle this in our backend logic? Should we treat transactions with different appTransactionId values as separate entities, or should we rely on originalTransactionId for grouping related subscription transactions?
Is this a known issue? We've seen similar concerns in the community regarding transaction ID inconsistencies, but this specific case involves a clean auto-renewal flow without any complicating factors.
Hi,
I am trying to obtain the subscription expiry date or purchase date from server using Storekit2 for my project.
I have an account which is already having an active subscription but it still says no purchase found.
Thanks
One of our apps has 85% stuck in Billing Retry -- We are so confused. All the users are from the US, and have a one-week free trial.
We had 1,000 subscriptions expire from this issue.
So any help would be so appreciated.
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
Subscriptions
StoreKit
App Store Connect
App Store Server API
Is there an App Store Server API available that allows cancellation of specific subscriptions by specifying transaction_id or similar identifiers?
Background of these questions:
We occasionally suspend user accounts due to violations of our service terms and conditions. In such cases, we would like to forcibly cancel their subscriptions if possible. However, we could not find relevant information in the documentation, which is why we are reaching out with these questions.
Please let us know.
I’m testing an auto-renewable subscription on TestFlight. Now the user can't re-purchase the same product – Apple just restores the old (expired) one, and no payment sheet appears.
How can I let the same TestFlight user re-subscribe to an expired product?
Do I have to create a new productId for every test cycle?
Hi there 👋🏻
We are facing an issue that started on 24 June 2025 where some users that have an active subscription with an offer are not being able to use/restore their subscription since Transaction.currentEntitlements is empty.
We have tried to call the server with this endpoint https://developer.apple.com/documentation/appstoreserverapi/get-transaction-history and it's returning the correct transactions correctly.
Any idea what is happening?
In the app we're distributing, we've identified a user where the Get All Subscription Statuses API returns two original transaction IDs in the data response. This user has gone through cancellations and re-subscriptions since their initial purchase.
Meanwhile, despite maintaining a continuous subscription, a notification suddenly arrives on the renewal date with original_transaction_id2.
+---------------------------+---------------------------+---------------------+
| original_transaction_id | notification_type | subtype |
+---------------------------+---------------------------+---------------------+
| original_transaction_id1 | SUBSCRIBED | INITIAL_BUY |
~ Repeated cancellations and re-subscriptions ~
| original_transaction_id1 | SUBSCRIBED | RESUBSCRIBE |
| original_transaction_id2 | SUBSCRIBED | INITIAL_BUY |
+---------------------------+---------------------------+---------------------+
Although it can be inferred from the interface definition of the API itself that multiple original transaction IDs may be returned, what causes multiple original transaction IDs to be returned?
Furthermore, is it possible to reproduce the case where multiple original transaction IDs are returned in the sandbox environment provided by Apple?
As for this app, it:
Uses subscriptions through in-app purchases.
Does not offer upgrades, downgrades, or crossgrades, having only one product.
Has never undergone price increases or decreases.
Is distributed exclusively in Japan.
Topic:
App & System Services
SubTopic:
StoreKit
Hi everyone,
I’m seeing a consistent one-day discrepancy between the expiresDate returned by the App Store Server API and the “Expires on” date shown in the iOS Settings / App Store subscription list. I’d like to confirm whether this behavior is expected or if I’m misunderstanding the way Apple rounds dates.
Reproduction steps
Step
Action
Result
1
Purchase a 1-month auto-renewable subscription on 23 June 2025 14:00 JST (UTC+9)
Transaction succeeds
2
Immediately fetch the transaction with GET /inApps/v1/subscriptions/{transactionId}
Response contains "expiresDate": "2025-07-23T05:00:00Z" (= 23 July 2025 14:00 JST)
3
On the same device open Settings › Apple ID › Subscriptions (or App Store › Account › Subscriptions)
UI shows Expires on: 22 July 2025
The same happens for every monthly renewal and on multiple devices. Region is Japan, device time zone Asia/Tokyo.
What I understand so far (and my hypothesis)
Apple’s docs say a monthly subscription renews “on the same calendar date” of the next month, so renewal in this example is 23 July.
If the renewal is scheduled for 23 July at 14:00 JST, the subscription is fully usable until the end of 22 July in calendar terms, because the new billing period starts the moment the 23rd begins in Apple’s canonical time zone.
Therefore, it might be intentional for the UI to display 22 July—i.e., “you can keep using it through the 22nd; on the 23rd it renews.”
This hypothesis makes sense internally, yet it still looks confusing to end users who read “Expires on 22 July” and assume access ends at 00:00 on the 22nd, a whole day earlier than in reality.
Questions
Is showing the day before the renewal date the official/expected behavior? If so, could Apple clarify that the “Expires on” label represents the last full calendar day rather than the exact expiry timestamp?
Which value should we surface in-app when telling users “Your subscription is valid until …”?
The server’s expiresDate (precise to the second, converted to user time zone), or
A UI-style date that’s one day earlier, matching Settings / App Store?
Does Apple have a public document describing this rounding/visual convention?
Have other developers encountered user confusion about the apparent 1-day “shortening” and, if so, how did you word your in-app messaging?
Any insight from Apple engineers or fellow developers would be greatly appreciated.
Thank you!
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
Subscriptions
App Store
StoreKit
App Store Server API
I've been stuck with this rejection. Trial button 'greyed out' or non responsive. What they are seeing is my subscriptions not loading into the pay wall. We think its because the subscriptions haven't been reviewed and approved in order for them to populate the pay wall (activating the pay wall so it isn't 'greyed out'. Has anyone had and solved this issue?
Topic:
App & System Services
SubTopic:
StoreKit
Hi everyone,
I'm an indie developer and recently noticed some odd behavior in my app's subscription metrics. There’s been a sudden spike in annual subscriptions, which is very unusual for my app — historically, users rarely choose that option.
What’s more concerning is that these subscriptions are almost immediately canceled and refunded. Upon checking analytics (RevenueCat + App Store Connect), I also noticed inconsistencies in user location data: users appear to be from one region (like Singapore), but deeper tracking shows actual usage from Vietnam, a market I’ve never targeted and where I do no advertising.
This behavior seems coordinated and is affecting my app’s refund rate, which I worry could raise red flags on the App Store side.
Has anyone experienced anything similar? Is there a recommended way to report suspicious subscription/refund activity to Apple or prevent potential abuse like this?
Any advice would be really appreciated.
Thanks!
Where the problem occurs:
In-app purchase
Non-ApplePay
Non-local authentication login
Environment where the problem occurs:
Sandbox environment (Development environment, TestFlight environment)
Problem handling process:
Open page A in the app and purchase product B (auto-renewable subscription) on that page.
User authentication is required to purchase product B.
During the authentication process, the user needs to enter the Apple account and Apple account password.
After completing the authentication, complete the purchase of product B.
Problem in step 3:
Why is FaceID or TouchID not used for authentication?
Note: Face ID and Password -> iTunes Store and App Store -> Status is Enabled
I has sandbox account with Japanese local. When i build app directly to check, price is displayed in Japanese Currency. But when I install app from the Test Flight, price is always displayed in USD Currency.
the issue is appear in iOS 18.5
How can i fix this issue ?
Hello,
I use Storekit2 to test the purchase of subscription products. After purchasing a subscription product in the sandbox, it will automatically renew 12 times, and then it will no longer automatically renew. When I click to purchase again, calling the
try await product.purchase()
method does not pop up the purchase pop-up window. In fact, it will directly go to the
case let .success(.verified(transaction)):
step, and the
Transaction.currentEntitlements is empty
I’m trying to determine the most appropriate modern method for detecting whether a user originally downloaded a paid app (prior to transitioning the app to freemium/IAP-based access).
Historically, this was done by checking for a valid App Store receipt and using SKReceiptRefreshRequest to ensure a fresh one was available. However, SKReceiptRefreshRequest and many related aspects of StoreKit receipt handling are now deprecated in iOS 17+.
The current Apple documentation on receipt validation still refers to SKReceiptRefreshRequest, which makes things unclear. With so many deprecations and the push toward StoreKit 2, what’s the recommended path to:
Check for a valid App Store receipt
Confirm that the app was originally purchased (as a paid app, not via IAP)
Persist this info to exempt the user from paywalling the app in the future
I don’t need to validate purchases of IAPs — just to detect a legacy paid app download.
Any guidance on best practice for this use case, preferably using non-deprecated APIs (StoreKit 2 or otherwise), would be appreciated.
Topic:
App & System Services
SubTopic:
StoreKit
this error occurred during the StoreKit1 payment process. Could you please tell me the reason?
SKErrorDomain Code=0 "发生未知错误\" UserInfo= {NSLocalizedDescription=发生未知错误,NSUnderlyingError=0x17652ee50 {Error Domain=ASDErrorDomain Code=500"(null)\"Userlnfo={NSUnderlyingError=0x17652d530 {Error Domain=AMSErrorDomain Code=203 "(null)" Userlnfo= {NSUnderlyingError=0x17652c3c0 {Error Domain=AMSErrorDomain
Code=203"(null)\"UserInfo=0x1663e5940(not
displayed)}}}
Question on token testing in development:
Hello,
I'm developing a React Native application using StoreKit External Purchase. I'm having difficulties testing the transaction token retrieval in the development environment.
Specific questions:
Is it possible to test the transaction token retrieval in development/sandbox environment?
Is there a special testing mode for developers that would allow simulating token reception without going through the App Store?
Are there specific debugging tools to verify proper token reception?
Question on native implementation:
Regarding the native implementation of StoreKit External Purchase:
Is there detailed documentation on creating native modules for React Native that implement StoreKit External Purchase?
Can you confirm if the following approach is correct for native implementation:
Using NSClassFromString(@"ExternalPurchase")
Calling presentNoticeSheetWithURL:completion:
Retrieving the token in the completion block
Are there any code examples for complete native implementation?
Question on implementation validation:
For validating StoreKit External Purchase implementation:
How can we verify that our implementation is correct before App Store submission?
Are there validation tools or automated tests to check compliance?
What are common errors to avoid during implementation?
Question on best practices:
Regarding implementation best practices:
What's the best way to handle potential errors during token retrieval?
How to handle cases where the user cancels the transaction?
What are the recommended security checks for server-side token validation?
Question on documentation:
I'm looking for additional resources on StoreKit External Purchase:
Is there specific documentation for integration with frameworks like React Native?
Are there complete code examples for native implementation?
Where can I find information about testing and debugging best practices?
Additional technical question:
Technical implementation details:
What's the correct way to handle the ExternalPurchase class initialization in React Native native modules?
Are there specific requirements for the URL passed to presentNoticeSheetWithURL?
How should we handle the token in the completion block to ensure it's properly passed back to React Native?
Question on testing workflow:
Testing workflow questions:
What's the recommended testing workflow for External Purchase implementation?
How can we verify the token format and validity before production deployment?
Are there any tools or methods to simulate the complete purchase flow in development?
Hi,
Using StoreKit 2 with App Store Server notifications like so:
User selects a purchase
App calls Product.purchase()
If successful, App Store notifies our backend with the transaction details, importantly with a UUID for the transaction ID.
This works fine, but when I try to test contingent pricing via the handy StoreKit config Transaction Manager in Xcode by creating a PurchaseIntent if I then complete the purchase in the app the Transaction ID is sequential, (0 for the first, 1 for the second etc), which doesn't work for us as the backend might already have that ID stored so the purchase never completes.
If I disable the config file it works fine, but then I can't use the Transaction Manager debug tool. Is there a way to override the ID of a custom transaction that's created via the StoreKit configuration?
Thanks
Hi,
We're currently experiencing an issue with consumable In-App Purchases on our production iOS app. Until the end of May, everything was working as expected, but starting in early June, our app no longer receives any products when calling queryProductDetails() using Flutter’s in_app_purchase plugin (which utilizes StoreKit).
Here’s what we’ve confirmed so far:
The product IDs are correctly configured in App Store Connect, and all items are marked as “Approved.”
No recent changes have been made to the bundle ID or the product IDs.
The “Base Territory” setting was updated for each IAP item in early May. After that change, product retrieval and purchases were working normally through the end of May.
This issue is happening on real devices in production, and multiple users are affected.
The same functionality continues to work correctly on Android.
All requested product IDs are being returned in the notFoundIDs list of the queryProductDetails() response.
We're quite puzzled by this issue as no clear cause has been identified so far.
Any thoughts on this issue would be much appreciated.
Thank you!