Add scan flow MVP and local Axiom skill workspace
This snapshot establishes the camera-to-result recognition flow and related tests while checking in the project skill/docs assets required for the configured local tooling.
This commit is contained in:
172
.claude/skills/axiom-implement-iap/SKILL.md
Normal file
172
.claude/skills/axiom-implement-iap/SKILL.md
Normal file
@@ -0,0 +1,172 @@
|
||||
---
|
||||
name: axiom-implement-iap
|
||||
description: Use when the user wants to add in-app purchases, implement StoreKit 2, or set up subscriptions.
|
||||
license: MIT
|
||||
disable-model-invocation: true
|
||||
---
|
||||
# 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:
|
||||
1. Create StoreKit configuration FIRST
|
||||
2. Implement centralized StoreManager
|
||||
3. Add transaction listener and verification
|
||||
4. Implement purchase flows
|
||||
5. Add subscription management (if applicable)
|
||||
6. Implement restore purchases
|
||||
7. Provide testing instructions
|
||||
|
||||
## Phase 1: Gather Requirements
|
||||
|
||||
Ask the user:
|
||||
1. **Product types**: Consumables, non-consumables, subscriptions?
|
||||
2. **Product IDs**: Format `com.company.app.product_name`
|
||||
3. **Server backend**: For appAccountToken integration?
|
||||
4. **Subscription details**: Group ID, tiers, trial duration?
|
||||
|
||||
## Phase 2: Create StoreKit Configuration (FIRST!)
|
||||
|
||||
**CRITICAL**: Create `.storekit` file BEFORE any Swift code!
|
||||
|
||||
1. Create via Xcode: File → New → File → StoreKit Configuration File
|
||||
2. Add products with ID, name, price
|
||||
3. Configure scheme: Edit Scheme → Run → Options → StoreKit Configuration
|
||||
4. Test products load before proceeding
|
||||
|
||||
## Phase 3: Implement StoreManager
|
||||
|
||||
Create `StoreManager.swift` with these essential components:
|
||||
|
||||
```swift
|
||||
@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+):
|
||||
```swift
|
||||
// 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:
|
||||
```swift
|
||||
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:
|
||||
```swift
|
||||
Button("Restore Purchases") {
|
||||
Task { await store.restorePurchases() }
|
||||
}
|
||||
```
|
||||
|
||||
## Deliverables
|
||||
|
||||
1. `Products.storekit` - Configuration file
|
||||
2. `StoreManager.swift` - Centralized IAP manager
|
||||
3. Purchase UI (custom or StoreKit views)
|
||||
4. Settings with restore button
|
||||
5. 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
|
||||
|
||||
1. ❌ Writing code before .storekit file
|
||||
2. ❌ No Transaction.updates listener
|
||||
3. ❌ Forgetting transaction.finish()
|
||||
4. ❌ No restore button (App Store rejection)
|
||||
5. ❌ Ignoring refunds (revocationDate)
|
||||
|
||||
## Testing Instructions
|
||||
|
||||
1. **Local**: Run with Products.storekit in scheme
|
||||
2. **Sandbox**: Create sandbox account in App Store Connect
|
||||
3. **TestFlight**: Upload build, test real flows
|
||||
4. **Production**: Use promo codes
|
||||
|
||||
## Related
|
||||
|
||||
For detailed patterns: `axiom-in-app-purchases` skill
|
||||
For API reference: `axiom-storekit-ref` skill
|
||||
For auditing: `iap-auditor` agent
|
||||
Reference in New Issue
Block a user