Implement or review in-app purchases and subscriptions with StoreKit 2, including paywalls, transactions, entitlement verification, offers, renewal state, testing, Family Sharing, Ask to Buy, refunds, and billing recovery. Route physical-goods checkout to passkit.
Build, review, and debug digital-goods purchases with modern StoreKit. Preserve the project's deployment target and existing StoreKit abstraction. Use original SKProduct/SKPaymentQueue APIs only when legacy support or an existing migration boundary requires them.
Route physical goods and real-world services to passkit; full submission/privacy/rejection audits to app-store-review; metadata and conversion work to app-store-optimization.
For product loading, StoreKit views, PurchaseAction, direct purchases, options, AppTransaction, or durable content delivery, read products, merchandising, and purchases.
For Transaction.updates, current entitlements, subscription status, revocation, Family Sharing, restore, or reconciliation, read entitlements and subscription state.
For offer eligibility, subscription surfaces, offer codes, and disclosures, read StoreKit offers and merchandising.
For StoreKit configuration files, SKTestSession, renewal controls, and test matrices, read StoreKit Test and Sandbox.
For entitlement recovery, subscriptions, refunds, revocations, Family Sharing, Ask to Buy, or unfinished transactions, read StoreKit recovery and state transitions.
For the focused reference index, read advanced StoreKit. Use complete advanced StoreKit recipes only for broad end-to-end examples or migration.
For digital-goods payment rules, reader/external-link boundaries, subscription disclosures, or IAP rejection risks, read App Review IAP guidance.
Do not load offer, compliance, or subscription material for a narrow consumable/non-consumable task.
Core invariants
Verify every VerificationResult before granting access or using signed transaction state.
Deliver or persist fulfillment durably and idempotently before calling transaction.finish().
Start one retained Transaction.updates listener during app initialization, not when a paywall appears.
Rebuild entitlement state from verified transaction sequences; don't use a local Boolean as purchase authority.
Handle .pending, .userCancelled, errors, refunds/revocations, and recovery as distinct states.
Use StoreKit-localized product names, descriptions, prices, and subscription terms.
Call AppStore.sync() only from an explicit user restore action because it may prompt for authentication.
Implementation workflow
Confirm the business model, product types, identifiers, subscription groups, App Store Connect state, platforms, and deployment target.
Decide whether StoreKit views or a custom purchase surface best fits the product.
Define one verified, idempotent fulfillment boundary shared by purchase completion and transaction updates.
Start lifetime transaction observation and load current entitlement state.
Implement purchase, pending/cancel/error UI, restore, and subscription recovery paths.
Test with a StoreKit configuration, sandbox where needed, and realistic renewal/refund/revocation/Ask to Buy states.
Verify the built paywall on the target platform with real localized product data before release.
Choose the purchase surface
Prefer StoreKit views for standard merchandising:
ProductView for one product;
StoreView for a collection;
SubscriptionStoreView for auto-renewable subscriptions in one group.
They load localized product information and initiate purchases. Configure visible restore/redeem controls and policy destinations according to the product and platform.
For custom SwiftUI buttons, prefer PurchaseAction. Use purchase(confirmIn:options:) for UIKit/AppKit or lower-level product.purchase(options:) where the platform/custom flow requires it. Don't introduce a parallel purchasing abstraction when the project already centralizes verification and fulfillment correctly.
Product type
Entitlement responsibility
Consumable
Persist delivered balance/history outside current entitlements
Non-consumable
Grant durable ownership from verified transaction state
Auto-renewable
Reconcile verified entitlement and subscription renewal state
Non-renewing
App/server defines and persists the access-expiration policy
Purchase and fulfillment
Handle all PurchaseResult cases:
switch try await purchase(product) {
case .success(let result):
let transaction = try verified(result)
try await fulfillment.deliver(transaction)
await transaction.finish()
case .pending:
purchaseState = .pendingApproval
case .userCancelled:
purchaseState = .idle
@unknown default:
purchaseState = .failed
}
Never use unsafePayloadValue to unlock content. Never finish before delivery. If durable delivery fails, leave the transaction unfinished and recover it through the lifetime listener or Transaction.unfinished.
Make fulfillment safe when the initiating purchase flow and Transaction.updates observe the same transaction. For consumables, reconcile server/app balance before finish; currentEntitlements doesn't retain consumed quantity.
Transaction updates and entitlements
Start one listener at app launch. It can deliver Ask to Buy approvals, purchases from another device, renewals, refunds, revocations, Family Sharing changes, and unfinished transactions.
Transaction.currentEntitlements represents verified current access for non-consumables, active or grace-period auto-renewable subscriptions, and non-renewing subscription transactions. It excludes consumables and refunded/revoked products. Apply the app's expiration policy to non-renewing subscriptions.
Rebuild a fresh entitlement set so removed or revoked access disappears. Reconcile at launch and on transaction updates; refresh on foreground only where the product needs it, without creating duplicate listeners.
UI hints such as a StoreKit view's entitlement configuration can customize merchandising but aren't access authority. Grant access from verified transaction information.
Subscriptions, restore, and recovery
Use verified subscription status/renewal information when the UI needs more than yes/no access. Treat .subscribed and .inGracePeriod as entitled; distinguish billing retry, expiration, and revocation according to product policy.
StoreKit automatically makes transaction information available after reinstall or on a new device. Derive access proactively from current entitlements and expose a user-initiated Restore Purchases path. Do not call AppStore.sync() automatically at launch.
Offers require both configured offer data and current eligibility. A raw winBackOffers list is not eligibility; compare it with verified renewal information. Keep StoreKit configuration and sandbox coverage for promotional offers, win-back, offer codes, Ask to Buy, renewal transitions, refunds, revocations, and Family Sharing.
Correction reviews
When reviewing flawed StoreKit code, name the broken contract:
No lifetime listener: start retained Transaction.updates observation during app initialization.
Access from unverified result/local flag: verify and reconcile from transaction state.
Finish immediately: deliver durably first; unfinished transactions are the recovery mechanism.
Pending treated as failure/success: keep pending UI and wait for a verified update.
Restore at launch: use current entitlements automatically and reserve AppStore.sync() for explicit user action.
Hardcoded price/trial copy: use StoreKit localized product and offer data.
Every win-back offer shown: filter raw offers by verified eligible IDs.
Legacy offer fields: use current transaction.offer?.type and .id APIs for the supported SDK.
Broad IAP/external-link claim: defer to current guideline, entitlement, region, and storefront evidence.
Common Mistakes
Creating transaction listeners from paywalls or views.
Updating UI but not durable fulfillment before finish.
1---2name: storekit3description: Implement or review in-app purchases and subscriptions with StoreKit 2, including paywalls, transactions, entitlement verification, offers, renewal state, testing, Family Sharing, Ask to Buy, refunds, and billing recovery. Route physical-goods checkout to passkit.4---56# StoreKit 2 In-App Purchases and Subscriptions78Build, review, and debug digital-goods purchases with modern StoreKit. Preserve the project's deployment target and existing StoreKit abstraction. Use original `SKProduct`/`SKPaymentQueue` APIs only when legacy support or an existing migration boundary requires them.910Route physical goods and real-world services to `passkit`; full submission/privacy/rejection audits to `app-store-review`; metadata and conversion work to `app-store-optimization`.1112## Contents1314- [Route by task](#route-by-task)15- [Core invariants](#core-invariants)16- [Implementation workflow](#implementation-workflow)17- [Choose the purchase surface](#choose-the-purchase-surface)18- [Purchase and fulfillment](#purchase-and-fulfillment)19- [Transaction updates and entitlements](#transaction-updates-and-entitlements)20- [Subscriptions, restore, and recovery](#subscriptions-restore-and-recovery)21- [Correction reviews](#correction-reviews)22- [Common mistakes](#common-mistakes)23- [Review checklist](#review-checklist)2425## Route by task2627Read only the references required by the request:2829- For product loading, StoreKit views, `PurchaseAction`, direct purchases, options, AppTransaction, or durable content delivery, read [products, merchandising, and purchases](references/products-and-purchases.md).30- For `Transaction.updates`, current entitlements, subscription status, revocation, Family Sharing, restore, or reconciliation, read [entitlements and subscription state](references/entitlements-and-subscriptions.md).31- For offer eligibility, subscription surfaces, offer codes, and disclosures, read [StoreKit offers and merchandising](references/storekit-offers.md).32- For StoreKit configuration files, `SKTestSession`, renewal controls, and test matrices, read [StoreKit Test and Sandbox](references/storekit-testing.md).33- For entitlement recovery, subscriptions, refunds, revocations, Family Sharing, Ask to Buy, or unfinished transactions, read [StoreKit recovery and state transitions](references/storekit-recovery.md).34- For the focused reference index, read [advanced StoreKit](references/storekit-advanced.md). Use [complete advanced StoreKit recipes](references/storekit-advanced-complete.md) only for broad end-to-end examples or migration.35- For digital-goods payment rules, reader/external-link boundaries, subscription disclosures, or IAP rejection risks, read [App Review IAP guidance](references/app-review-guidelines.md).3637Do not load offer, compliance, or subscription material for a narrow consumable/non-consumable task.3839## Core invariants40411. Verify every `VerificationResult` before granting access or using signed transaction state.422. Deliver or persist fulfillment durably and idempotently before calling `transaction.finish()`.433. Start one retained `Transaction.updates` listener during app initialization, not when a paywall appears.444. Rebuild entitlement state from verified transaction sequences; don't use a local Boolean as purchase authority.455. Handle `.pending`, `.userCancelled`, errors, refunds/revocations, and recovery as distinct states.466. Use StoreKit-localized product names, descriptions, prices, and subscription terms.477. Call `AppStore.sync()` only from an explicit user restore action because it may prompt for authentication.4849## Implementation workflow50511. Confirm the business model, product types, identifiers, subscription groups, App Store Connect state, platforms, and deployment target.522. Decide whether StoreKit views or a custom purchase surface best fits the product.533. Define one verified, idempotent fulfillment boundary shared by purchase completion and transaction updates.544. Start lifetime transaction observation and load current entitlement state.555. Implement purchase, pending/cancel/error UI, restore, and subscription recovery paths.566. Test with a StoreKit configuration, sandbox where needed, and realistic renewal/refund/revocation/Ask to Buy states.577. Verify the built paywall on the target platform with real localized product data before release.5859## Choose the purchase surface6061Prefer StoreKit views for standard merchandising:6263- `ProductView` for one product;64- `StoreView` for a collection;65- `SubscriptionStoreView` for auto-renewable subscriptions in one group.6667They load localized product information and initiate purchases. Configure visible restore/redeem controls and policy destinations according to the product and platform.6869For custom SwiftUI buttons, prefer `PurchaseAction`. Use `purchase(confirmIn:options:)` for UIKit/AppKit or lower-level `product.purchase(options:)` where the platform/custom flow requires it. Don't introduce a parallel purchasing abstraction when the project already centralizes verification and fulfillment correctly.7071| Product type | Entitlement responsibility |72|---|---|73| Consumable | Persist delivered balance/history outside current entitlements |74| Non-consumable | Grant durable ownership from verified transaction state |75| Auto-renewable | Reconcile verified entitlement and subscription renewal state |76| Non-renewing | App/server defines and persists the access-expiration policy |7778## Purchase and fulfillment7980Handle all `PurchaseResult` cases:8182```swift83switch try await purchase(product) {84case .success(let result):85 let transaction = try verified(result)86 try await fulfillment.deliver(transaction)87 await transaction.finish()8889case .pending:90 purchaseState = .pendingApproval9192case .userCancelled:93 purchaseState = .idle9495@unknown default:96 purchaseState = .failed97}98```99100Never use `unsafePayloadValue` to unlock content. Never finish before delivery. If durable delivery fails, leave the transaction unfinished and recover it through the lifetime listener or `Transaction.unfinished`.101102Make fulfillment safe when the initiating purchase flow and `Transaction.updates` observe the same transaction. For consumables, reconcile server/app balance before finish; `currentEntitlements` doesn't retain consumed quantity.103104## Transaction updates and entitlements105106Start one listener at app launch. It can deliver Ask to Buy approvals, purchases from another device, renewals, refunds, revocations, Family Sharing changes, and unfinished transactions.107108`Transaction.currentEntitlements` represents verified current access for non-consumables, active or grace-period auto-renewable subscriptions, and non-renewing subscription transactions. It excludes consumables and refunded/revoked products. Apply the app's expiration policy to non-renewing subscriptions.109110Rebuild a fresh entitlement set so removed or revoked access disappears. Reconcile at launch and on transaction updates; refresh on foreground only where the product needs it, without creating duplicate listeners.111112UI hints such as a StoreKit view's entitlement configuration can customize merchandising but aren't access authority. Grant access from verified transaction information.113114## Subscriptions, restore, and recovery115116Use verified subscription status/renewal information when the UI needs more than yes/no access. Treat `.subscribed` and `.inGracePeriod` as entitled; distinguish billing retry, expiration, and revocation according to product policy.117118StoreKit automatically makes transaction information available after reinstall or on a new device. Derive access proactively from current entitlements and expose a user-initiated Restore Purchases path. Do not call `AppStore.sync()` automatically at launch.119120Offers require both configured offer data and current eligibility. A raw `winBackOffers` list is not eligibility; compare it with verified renewal information. Keep StoreKit configuration and sandbox coverage for promotional offers, win-back, offer codes, Ask to Buy, renewal transitions, refunds, revocations, and Family Sharing.121122## Correction reviews123124When reviewing flawed StoreKit code, name the broken contract:125126- No lifetime listener: start retained `Transaction.updates` observation during app initialization.127- Access from unverified result/local flag: verify and reconcile from transaction state.128- Finish immediately: deliver durably first; unfinished transactions are the recovery mechanism.129- Pending treated as failure/success: keep pending UI and wait for a verified update.130- Restore at launch: use current entitlements automatically and reserve `AppStore.sync()` for explicit user action.131- Hardcoded price/trial copy: use StoreKit localized product and offer data.132- Every win-back offer shown: filter raw offers by verified eligible IDs.133- Legacy offer fields: use current `transaction.offer?.type` and `.id` APIs for the supported SDK.134- Broad IAP/external-link claim: defer to current guideline, entitlement, region, and storefront evidence.135136## Common Mistakes137138- Creating transaction listeners from paywalls or views.139- Updating UI but not durable fulfillment before finish.140- Treating consumables as current entitlements.141- Forgetting non-renewing subscription expiration policy.142- Keeping revoked/refunded products in a cached entitlement set.143- Hardcoding prices, duration, trial, or renewal terms.144- Starting duplicate purchases or swallowing `.pending`.145- Using StoreKit view configuration as authorization to unlock content.146- Testing only the immediate happy-path purchase.147- Mixing physical-goods checkout or full App Review work into StoreKit implementation.148149## Review Checklist150151- [ ] Product types, IDs, groups, platforms, and deployment target are explicit.152- [ ] Purchase surface matches the product and existing architecture.153- [ ] StoreKit-localized product/price/terms are displayed.154- [ ] Verification precedes every entitlement or fulfillment decision.155- [ ] Fulfillment is durable and idempotent before finish.156- [ ] One retained transaction listener starts during app initialization.157- [ ] Current entitlement logic handles product type, expiration, and revocation.158- [ ] Pending, cancellation, error, refund, and recovery states are distinct.159- [ ] Restore is visible and `AppStore.sync()` is user initiated.160- [ ] Subscription policy links and required disclosures are present.161- [ ] Offers are filtered by current verified eligibility.162- [ ] StoreKit configuration/sandbox tests cover changed state transitions.163- [ ] Server reconciliation uses signed transaction data when the server is authoritative.164- [ ] App Review, ASO, and physical-goods work are routed to the correct skill.165166## References167168- [Choosing a StoreKit API](https://sosumi.ai/documentation/storekit/choosing-a-storekit-api-for-in-app-purchases)169- [In-App Purchase](https://sosumi.ai/documentation/storekit/in-app-purchase)170- [Transaction.updates](https://sosumi.ai/documentation/storekit/transaction/updates)171- [Transaction.currentEntitlements](https://sosumi.ai/documentation/storekit/transaction/currententitlements)172- [SubscriptionStoreView](https://sosumi.ai/documentation/storekit/subscriptionstoreview)
Run npx skillmds@latest add thiennc-tesoglobal/storekit in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Implement or review in-app purchases and subscriptions with StoreKit 2, including paywalls, transactions, entitlement verification, offers, renewal state, testing, Family Sharing, Ask to Buy, refunds, and billing recovery. Route physical-goods checkout to passkit. It is listed under Coding & Dev Tools on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
thiennc-tesoglobal (@thiennc-tesoglobal) published this skill. Their other Agent Skills are listed on their SkillMD profile.