Axiom axiom-implement-iap
Use when the user wants to add in-app purchases, implement StoreKit 2, or set up subscriptions.
install
source · Clone the upstream repo
git clone https://github.com/CharlesWiltgen/Axiom
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/CharlesWiltgen/Axiom "$T" && mkdir -p ~/.claude/skills && cp -r "$T/axiom-codex/skills/axiom-implement-iap" ~/.claude/skills/charleswiltgen-axiom-axiom-implement-iap && rm -rf "$T"
manifest:
axiom-codex/skills/axiom-implement-iap/SKILL.mdsource content
In-App Purchase Implementation Agent
You are an expert at implementing production-ready in-app purchases using StoreKit 2.
Your Mission
Implement complete IAP following testing-first workflow:
- Create StoreKit configuration FIRST
- Implement centralized StoreManager
- Add transaction listener and verification
- Implement purchase flows
- Add subscription management (if applicable)
- Implement restore purchases
- Provide testing instructions
Phase 1: Gather Requirements
Ask the user:
- Product types: Consumables, non-consumables, subscriptions?
- Product IDs: Format
com.company.app.product_name - Server backend: For appAccountToken integration?
- Subscription details: Group ID, tiers, trial duration?
Phase 2: Create StoreKit Configuration (FIRST!)
CRITICAL: Create
.storekit file BEFORE any Swift code!
- Create via Xcode: File → New → File → StoreKit Configuration File
- Add products with ID, name, price
- Configure scheme: Edit Scheme → Run → Options → StoreKit Configuration
- Test products load before proceeding
Phase 3: Implement StoreManager
Create
StoreManager.swift with these essential components:
@MainActor final class StoreManager: ObservableObject { @Published private(set) var products: [Product] = [] @Published private(set) var purchasedProductIDs: Set<String> = [] private var transactionListener: Task<Void, Never>? init(productIDs: [String]) { // Start transaction listener IMMEDIATELY transactionListener = listenForTransactions() Task { await loadProducts(); await updatePurchasedProducts() } } // CRITICAL: Transaction listener handles ALL purchase sources func listenForTransactions() -> Task<Void, Never> { Task.detached { [weak self] in for await result in Transaction.updates { await self?.handleTransaction(result) } } } private func handleTransaction(_ result: VerificationResult<Transaction>) async { guard let transaction = try? result.payloadValue else { return } if transaction.revocationDate != nil { // Handle refund await transaction.finish() return } await grantEntitlement(for: transaction) await transaction.finish() // CRITICAL: Always finish await updatePurchasedProducts() } func purchase(_ product: Product, confirmIn scene: UIWindowScene) async throws -> Bool { let result = try await product.purchase(confirmIn: scene) switch result { case .success(let verification): guard let tx = try? verification.payloadValue else { return false } await grantEntitlement(for: tx) await tx.finish() return true case .userCancelled, .pending: return false @unknown default: return false } } func restorePurchases() async { try? await AppStore.sync() await updatePurchasedProducts() } }
Key Requirements:
- ✅ Transaction listener (handles ALL purchase sources)
- ✅ Transaction verification
- ✅ Always calls finish()
- ✅ Handles refunds
- ✅ @MainActor for UI state
Phase 4: Purchase UI
Custom View or StoreKit Views (iOS 17+):
// Custom Button(product.displayPrice) { Task { _ = try await store.purchase(product, confirmIn: scene) } } // StoreKit Views (simpler) StoreKit.StoreView(ids: productIDs) SubscriptionStoreView(groupID: "pro_tier")
Phase 5: Subscription Management (If Applicable)
Check subscription status via:
let statuses = try? await Product.SubscriptionInfo.status(for: groupID) // Handle: .subscribed, .expired, .inGracePeriod, .inBillingRetryPeriod
Phase 6: Restore Purchases (REQUIRED)
App Store Requirement: Non-consumables/subscriptions MUST have restore:
Button("Restore Purchases") { Task { await store.restorePurchases() } }
Deliverables
- Configuration fileProducts.storekit
- Centralized IAP managerStoreManager.swift- Purchase UI (custom or StoreKit views)
- Settings with restore button
- Testing instructions
Implementation Checklist
- StoreKit config created and tested
- StoreManager with transaction listener
- Purchase flow with verification
- transaction.finish() always called
- Entitlements tracked
- Restore purchases implemented
- Subscription states handled (if applicable)
Critical Pitfalls to Avoid
- ❌ Writing code before .storekit file
- ❌ No Transaction.updates listener
- ❌ Forgetting transaction.finish()
- ❌ No restore button (App Store rejection)
- ❌ Ignoring refunds (revocationDate)
Testing Instructions
- Local: Run with Products.storekit in scheme
- Sandbox: Create sandbox account in App Store Connect
- TestFlight: Upload build, test real flows
- Production: Use promo codes
Related
For detailed patterns:
axiom-integration (skills/in-app-purchases.md)
For API reference: axiom-integration (skills/storekit-ref.md)
For auditing: iap-auditor agent