On this page (10 sections)
Start here: which of these is your problem?
| What you are seeing | What to do next | Where it is covered |
|---|---|---|
| App Review could not find or complete the purchase | Read the exact notice, then check product visibility to the reviewer and your review notes | Guideline 2.1, then the layers below |
| Apple disputes how you charge — payment method, subscription presentation, purchase type | Check the cited rule and the storefront conditions it applies to | Guideline 3.1 |
| Products are missing in one environment only (Xcode, sandbox, TestFlight or production) | Compare the environment and configuration before changing any code | Sandbox and TestFlight, below |
| The purchase completes but the user does not get what they paid for | Check transaction handling, entitlement derivation and server validation | Restores and entitlements and server validation, below |
This points you at the right evidence; it does not diagnose your app. One of our sample diagnoses works through a case of this kind: a subscription paywall that stayed empty for TestFlight testers after the same build had shown its products on the developer’s own device.
The five layers, in the order to check them
| Layer | Symptom | Evidence to look at |
|---|---|---|
| 1. Agreements and banking | No products anywhere, all environments | App Store Connect → Business: Paid Applications agreement active, tax and banking complete |
| 2. Product configuration | Products missing for one app or one product | Each product’s status; Missing/incomplete metadata; availability; price |
| 3. Identifiers and bundle | Some products returned, others not | Product IDs in code vs App Store Connect; bundle ID and team of the running build |
| 4. StoreKit code | Empty product array, no error shown to the user | The response from the products request, including invalid identifiers; error handling |
| 5. Environment | Works in production but not in sandbox, or vice versa | Sandbox Apple Account, TestFlight vs App Store build, StoreKit configuration file in Xcode |
What the App Store Connect status is telling you
Apple's in-app purchase statuses are specific, and most “not showing” reports are simply a product that was never submittable:
- Prepare for Submission — “Your In-App Purchase has been created, but you haven't yet submitted it for review.” Missing required metadata keeps it here.
- Ready for Review — added to a submission, not yet sent. Only reference name, pricing and availability remain editable.
- Waiting for Review / In Review — with Apple; same editing restrictions.
- Accepted — accepted, but another item in the same submission was rejected, so it is not approved yet.
- Approved — approved; it also needs country or region availability to appear.
- Rejected / Developer Rejected — fix and use Update Review, or re-add for review.
- Developer Removed from Sale / Removed from Sale — existing customers keep access, new purchases stop.
Required fields before a product can be submitted include the reference name, product ID, type, pricing, at least one localisation with display name and description, a review screenshot and review notes. A subscription also needs its group to have a localised display name — a missing group localisation keeps every subscription in the group incomplete no matter how complete each product looks.
Products not returned by StoreKit
- Compare identifiers character by character. Product IDs are case-sensitive and often differ by a stray suffix between the constant in code and App Store Connect.
- Check the bundle ID and team of the build you are running — a debug bundle ID (
com.example.app.debug) will never see the products ofcom.example.app. - Read the invalid identifiers. Both StoreKit generations tell you which identifiers were rejected; log them. An empty result with no invalid identifiers usually means the request never reached the store.
- Wait for propagation. New or edited products take time to reach the sandbox; Apple notes metadata changes can take up to an hour to appear there.
- Check the StoreKit configuration file. If Xcode is running against a local
.storekitfile, the app is not talking to App Store Connect at all — useful for development, misleading when debugging a real outage. - Handle failure visibly. A paywall that silently renders an empty list is indistinguishable from a broken store connection; surface the error state in debug builds.
Sandbox and TestFlight behave differently
- Sandbox purchases need a Sandbox Apple Account signed in under Settings → Developer, not the account you use for the App Store.
- Subscription periods are accelerated in sandbox, and renewals stop after a fixed number of cycles — subscription “expiry” bugs found there are often sandbox behaviour, not your code.
- Apps installed from TestFlight operate in the sandbox environment — they read your real App Store Connect product configuration but transact in sandbox, and subscription renewals are accelerated there too. A product missing in TestFlight is therefore usually a configuration problem rather than a code one, and it does not mean the product must first be approved for sale.
- Receipts from sandbox and production come from different environments; server code must handle both without hard-coding one.
Restores, entitlements and “I paid but it is gone”
- Non-consumables and subscriptions must be restorable, and Apple expects a visible restore path.
- Entitlement state should be derived from current transactions (StoreKit 2's
Transaction.currentEntitlements) or from your server's record, not from a local flag set at purchase time. - Pending purchases (Ask to Buy, SCA challenges) complete later — the app has to grant access when the transaction finally arrives, and finish the transaction only then.
- Unfinished transactions replay on every launch until they are finished; a purchase loop is almost always a missing
finish().
Server-side validation
If entitlements depend on your backend, that is where “worked in test, fails in production” usually comes from:
- The legacy
verifyReceiptendpoint is deprecated; Apple's current path is the App Store Server API with signed transaction data, or verifying theAppTransaction/TransactionJWS the app already holds. - On-device receipt validation had to move to SHA-256 after the receipt-signing intermediate certificate expired on 24 January 2025 — old validation code silently fails on that.
- Subscription state changes (renewal, billing retry, grace period, refund, revocation) arrive through App Store Server Notifications; without them your backend's idea of “subscribed” drifts from Apple's.
- App Store Server API keys are per team — after an app transfer they must be reissued, and the shared secret rotated.
Configuration problem or rejection?
If App Review rejected the app because digital content was sold outside In-App Purchase or because subscription terms were not shown, that is a rules problem: see Guideline 3.1 rejections. If the reviewer simply could not find or complete the purchase, the rule in play is usually Guideline 2.1 app completeness, which requires in-app purchases to be complete, up to date, visible to the reviewer and functional, and asks you to explain in the review notes why a configured product cannot be reviewed. So a technical failure is not outside App Review’s view — the same broken product can arrive as a 2.1 rejection, a 3.1 rejection or none at all. Fix the plumbing before resubmitting either way.
What we do
- Reproduce the failure in the environment you report it in, and read the actual products response rather than the UI symptom.
- Reconcile App Store Connect configuration, identifiers, agreements and availability with what the build requests.
- Fix the StoreKit layer: product loading, purchase and restore flows, entitlement derivation, transaction finishing, pending purchases.
- Fix the server layer: migrate off deprecated receipt validation, implement App Store Server Notifications, make entitlement state authoritative and idempotent.
- Re-test in sandbox and TestFlight, then submit with the purchase reachable for the reviewer.
Google Play side of the same feature: Play Billing Library 8.
Checklist
- Paid Applications agreement active; tax and banking complete
- Every product has complete metadata, availability and a status that allows it to be sold
- Subscription groups have a localised display name
- Product IDs, bundle ID and team match the running build exactly
- Invalid product identifiers are logged and the paywall shows an error state
- Restore path present; entitlements derived from current transactions or the server
- Pending and unfinished transactions handled; transactions finished exactly once
- Server validation uses the App Store Server API and handles both environments
- App Store Server Notifications received and acted on
Frequently asked questions
Why are my in-app purchases not showing up in the app?
Almost always one of five layers rather than your purchase code: the Paid Applications agreement or banking and tax details are not complete; the product is not in a state that makes it available for testing or sale; the product identifier in the app does not match App Store Connect exactly; StoreKit is querying before the product has propagated or in the wrong environment; or the build is running in a sandbox or TestFlight context that behaves differently from production. Work down the layers in that order — it is faster than reading the purchase code again.
Related: In-App Purchases Not Working, IAP / Payment Rejection (3.1)
Purchases work in Xcode or sandbox but not in TestFlight or production. What changed?
The environment did, so work out how each build is actually buying before you change any code. An Xcode build may be running against a local StoreKit configuration file, which never contacts App Store Connect at all, or against Apple's sandbox. Apps installed from TestFlight operate in the sandbox environment, with accelerated subscription renewals. An App Store release uses production. Compare four things across the builds that work and the one that does not: the product identifiers and their configuration in App Store Connect, the bundle ID and team the build is signed with, the account signed in on the test device, and which environment your server accepts transactions from. A purchase that succeeds locally proves nothing about TestFlight or production.
Related: In-App Purchases Not Working
Is a rejected in-app purchase the same problem as one that will not load?
They overlap more often than developers expect. A Guideline 3.1 rejection is a reviewer's judgement about how you are charging — external payment, missing restore, unclear subscription terms, the wrong purchase type. A purchase that will not load is usually a configuration or environment problem, but it is not invisible to App Review: Guideline 2.1 requires in-app purchases to be complete, up to date, visible to the reviewer and functional, and says that if a configured product cannot be found or reviewed you must explain why in the review notes. So the same broken product can surface as a 2.1 rejection, a 3.1 rejection or no rejection at all, depending on what the reviewer hit. Start from the exact wording of the notice and the purchase step that fails, not from the guideline number alone — the number tells you which rule Apple applied, not what is technically broken.
Related: IAP / Payment Rejection (3.1), In-App Purchases Not Working, Guideline 2.1 — App Completeness