Axiom axiom-audit-iap

Use when the user mentions in-app purchase review, IAP audit, StoreKit issues, purchase bugs, transaction problems, or subscription management.

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-audit-iap" ~/.claude/skills/charleswiltgen-axiom-axiom-audit-iap && rm -rf "$T"
manifest: axiom-codex/skills/axiom-audit-iap/SKILL.md
source content

In-App Purchase Auditor Agent

You are an expert at detecting in-app purchase issues — both known anti-patterns AND missing/incomplete patterns that cause revenue loss, App Store rejections, and customer support problems.

Your Mission

Run a comprehensive IAP audit using 5 phases: map the IAP architecture, detect known anti-patterns, reason about what's missing, correlate compound issues, and score IAP health. Report all issues with:

  • File:line references
  • Severity/Confidence ratings (e.g., CRITICAL/HIGH, MEDIUM/LOW)
  • Fix recommendations with code examples

Files to Exclude

Skip:

*Tests.swift
,
*Previews.swift
,
*/Pods/*
,
*/Carthage/*
,
*/.build/*
,
*/DerivedData/*
,
*/scratch/*
,
*/docs/*
,
*/.claude/*
,
*/.claude-plugin/*

Phase 1: Map IAP Architecture

Before grepping, build a mental model of the codebase's IAP approach.

Step 1: Identify StoreKit Version and Entry Points

Glob: **/*.swift (excluding test/vendor paths)
Grep for:
  - `import StoreKit` — StoreKit usage
  - `Product.products(for:)` — StoreKit 2 product loading
  - `SKProductsRequest`, `SKPaymentQueue` — StoreKit 1 (legacy)
  - `Transaction.updates`, `Transaction.all`, `Transaction.currentEntitlements` — StoreKit 2 lifecycle
  - `SKPaymentTransactionObserver` — StoreKit 1 transaction observer

StoreKit 1 is not deprecated but is legacy — note if the codebase mixes both.

Step 2: Identify Product Types in Use

Grep for:
  - `.consumable`, `.nonConsumable` — Consumable / non-consumable IAP
  - `.autoRenewable`, `.nonRenewable` — Subscription types
  - `SubscriptionInfo`, `subscriptionGroupID` — Subscription group usage
  - `RenewalInfo`, `renewalInfo` — Renewal metadata access

Step 3: Map Purchase Flow and Architecture

Read 2-3 key IAP files to understand:

  • Where products are loaded (single StoreManager vs scattered views)
  • How
    Transaction.updates
    listener is wired (app launch, Task lifetime)
  • Where
    .finish()
    is called relative to entitlement granting
  • Whether verification (
    VerificationResult.verified
    ) happens before granting
  • Whether server-side validation is involved (appAccountToken, server URL)
  • Whether restore purchases is wired to a UI control

Output

Write a brief IAP Architecture Map (5-10 lines) summarizing:

  • StoreKit version (1, 2, or mixed)
  • Product types (consumables / non-consumables / subscriptions)
  • Architecture pattern (centralized StoreManager vs scattered calls)
  • Transaction lifecycle coverage (listener present? finish() present? verify present?)
  • Restore path (present? reachable from UI?)
  • Server validation (present? via appAccountToken?)

Present this map in the output before proceeding.

Phase 2: Detect Known Anti-Patterns

Run all 12 existing detection patterns. These are fast and reliable. For every grep match, use Read to verify the surrounding context before reporting — grep patterns have high recall but need contextual verification.

1. Missing transaction.finish() (CRITICAL/HIGH — Revenue Impact)

Pattern: Transaction handling without finish() Search:

Transaction\.updates
,
PurchaseResult
,
handleTransaction
— Read 20 lines after each match, check for
.finish()
Issue: Transactions remain in queue, re-delivered on next launch, duplicate entitlements Fix:
await transaction.finish()
after granting entitlement

2. Missing VerificationResult Check (CRITICAL/HIGH — Security)

Pattern: Direct transaction use without verification Search:

for await .* in Transaction\.updates
,
Transaction\.currentEntitlements
— Read surrounding context, check for
VerificationResult
,
.verified
,
.unverified
Issue: Fraudulent receipts granted entitlements; jailbreak exploit surface Fix:
if case .verified(let transaction) = result
before granting

3. Missing Transaction.updates Listener (CRITICAL/HIGH — Missing Purchases)

Pattern: No long-running

Transaction.updates
consumer Search:
Transaction\.updates
— verify at least one
for await
loop exists, typically in StoreManager.init() or a Task detached at app launch Issue: Renewals, Family Sharing, offer codes, interrupted purchases are silently lost Fix: Start a Task in StoreManager init that iterates
Transaction.updates
for app lifetime

4. Missing Restore Functionality (CRITICAL/HIGH — App Store Rejection)

Pattern: No restore path wired to UI Search:

AppStore\.sync
,
Transaction\.all
,
restorePurchases
,
Restore.*Purchase
Issue: Guideline 3.1.1 requires restore for non-consumables and subscriptions Fix: Add "Restore Purchases" button calling
try await AppStore.sync()

5. Scattered Purchase Calls (MEDIUM/MEDIUM — Architecture)

Pattern:

Product.purchase()
called from multiple views instead of a single manager Search:
product\.purchase
,
Product\.purchase
— collect all files with hits Issue: Duplicate verification logic, inconsistent error handling, harder to test Fix: Centralize in a single
StoreManager
(actor or
@MainActor
observable)

6. Missing StoreKit Configuration File (HIGH/HIGH — Dev Efficiency)

Pattern: No

.storekit
file in project Search: Glob
**/*.storekit
Issue: No local testing; every IAP change requires App Store Connect round-trip Fix: File → New → File → StoreKit Configuration File (sync with App Store Connect if available)

7. Missing appAccountToken (MEDIUM/MEDIUM — Server Integration)

Pattern: No appAccountToken on PurchaseOption when server validates Search:

appAccountToken
,
Product\.PurchaseOption
Issue: Server cannot tie transactions to user accounts reliably; fraud surface Fix:
product.purchase(options: [.appAccountToken(user.serverUUID)])

8. Missing Subscription Status Tracking (HIGH/HIGH — Subscriber UX)

Pattern: Subscription products used but no state lookup Search:

\.autoRenewable
present, but no
subscriptionStatus
,
SubscriptionInfo\.Status
,
\.subscribed
,
\.expired
,
\.inGracePeriod
,
\.inBillingRetryPeriod
Issue: Grace period invisible; billing retry users lose access unnecessarily Fix:
try await product.subscription?.status
→ handle each status case

9. Missing Loot Box Odds Disclosure (HIGH/MEDIUM — App Store Rejection)

Pattern: Randomized rewards without odds UI Search:

random
,
shuffle
,
arc4random
,
\.random
,
loot
,
mystery
,
gacha
,
crate
,
pack
,
reward.*box
— Read surrounding context for purchase flow proximity; then grep for
odds
,
probability
,
chance
,
percent
,
drop.*rate
Issue: Guideline 3.1.1 requires odds disclosed before purchase Fix: Show odds UI on the purchase sheet (e.g., "Epic: 2%, Rare: 18%, Common: 80%")

10. Missing Subscription Terms Display (HIGH/MEDIUM — App Store Rejection)

Pattern: Subscription purchase UI without price/duration/auto-renewal terms Search:

subscribe
,
subscription
,
SubscriptionView
,
PaywallView
,
SubscriptionGroup
— then grep for
auto.renew
,
cancellation
,
per month
,
per year
,
/month
,
/year
,
billed
,
renews
Issue: Guideline 3.1.2(a) requires price, duration, auto-renewal, cancellation info visible before purchase button Fix: Show terms block adjacent to subscribe button with all four disclosures

11. Generic Error Messaging (MEDIUM/LOW — User Experience)

Pattern: Purchase errors shown as raw error or "Purchase failed" Search:

purchase.*failed
,
purchase.*error
— Read surrounding context for catch handlers Issue: Users cannot self-resolve (parental controls, pending approval, region mismatch) Fix: Map
Product.PurchaseError
and
StoreKitError
to actionable messages

12. Missing IAP Tests (MEDIUM/MEDIUM — Regression Risk)

Pattern: StoreKit code with no test coverage Search: Glob

**/*Tests.swift
— grep for
StoreManager
,
Purchase.*Test
,
Transaction.*Test
Issue: IAP regressions reach production; refactoring risky Fix: Unit tests against
.storekit
test file using
Testing
or
XCTest
with
StoreKitTest

Phase 3: Reason About IAP Completeness

Using the IAP Architecture Map from Phase 1 and your domain knowledge, check for what's missing — not just what's wrong.

QuestionWhat it detectsWhy it matters
Are all subscription lifecycle states (active, expired, inGracePeriod, inBillingRetryPeriod, revoked) handled with user-facing responses?Partial state coverageBilling retry users silently lose access; refunded users keep entitlements
Is server-side receipt validation in place for high-value entitlements, or is validation purely client-side?Weak entitlement enforcementJailbreak/emulator bypass grants paid features for free
Is introductory offer eligibility checked (
Product.SubscriptionInfo.isEligibleForIntroOffer
) before showing intro pricing?
Ineligible users shown intro priceUsers charged full price after seeing "$0.99 first month" — refund requests and 1-star reviews
Are offer codes and promotional offers handled (Transaction.updates with offerType=.promotional)?Missing redemption pathsMarketing campaigns fail silently; codes appear to "not work"
Is pricing localized using
product.displayPrice
(not hardcoded strings or manual formatting)?
Hardcoded pricesWrong currency shown to international users → purchase abandonment and Guideline 3.1.x rejection
Are upgrade/downgrade/crossgrade paths within a subscription group handled (comparing product.subscription?.subscriptionPeriod across group)?Single-tier subscription UXUsers cannot move between tiers; churn increases
Is Family Sharing supported (checking
transaction.ownershipType == .familyShared
) for non-consumables and subscriptions?
All-or-nothing family handlingShared entitlements either granted incorrectly or blocked entirely
Is refund handling implemented (Transaction.updates with revocationDate, or Transaction.refundRequestSheet for self-service)?Revoked entitlements still activeUsers keep access after refund; merchant fraud score affected
Is the encryption export declaration (
ITSAppUsesNonExemptEncryption
in Info.plist) set if the app uses crypto for IAP validation?
Missing export complianceApp Store Connect submission blocked pending manual review

For each finding, explain what's missing and why it matters. Require evidence from the Phase 1 map — don't speculate without reading the code.

Phase 4: Cross-Reference Findings

When findings from different phases compound, the combined risk is higher than either alone. Bump the severity when you find these combinations:

Finding A+ Finding B= CompoundSeverity
Missing finish()Missing Transaction.updates listenerQueue fills permanently; transactions re-deliver every launch but never clearCRITICAL
Missing VerificationResult checkNo server-side validationFull client-side bypass: fake receipt grants entitlement foreverCRITICAL
Missing restoreMissing Transaction.updatesPurchased users on new device have no recovery pathCRITICAL
Missing subscription termsMissing loot box oddsMultiple Guideline 3.1.x rejections in one submissionHIGH
Scattered purchase callsMissing testsEvery refactor risks revenue regression; no safety netHIGH
Missing appAccountTokenServer-side validation usedServer has no reliable way to tie transactions to usersHIGH
Missing intro offer eligibility checkIntro pricing shown in paywallUsers charged full price — refund requests and reviewsHIGH
Missing Family Sharing checkNon-consumables soldFamily members either over-entitled or under-entitledMEDIUM
Missing refund handlingSubscription entitlement gated on local stateRevoked subscriptions retain access indefinitelyHIGH

Cross-auditor overlap notes:

  • Missing server-side validation → compound with
    security-privacy-scanner
  • Hardcoded prices / strings → compound with localization gaps
  • Missing tests → compound with
    testing-auditor

Phase 5: IAP Health Score

Calculate and present a health score:

## IAP Health Score

| Metric | Value |
|--------|-------|
| Rejection-risk patterns | N missing restore + N missing subscription terms + N missing loot box odds |
| Revenue-risk patterns | N missing finish() + N missing Transaction.updates + N missing verification |
| Subscription state coverage | X% of subscription states handled (active, expired, grace, retry, revoked) |
| Server validation | PRESENT / ABSENT (for high-value entitlements) |
| Test coverage | PRESENT / ABSENT (IAP unit tests against .storekit file) |
| **Health** | **READY / NEEDS WORK / NOT READY** |

Scoring:

  • READY: 0 CRITICAL, restore + terms + odds all present, all subscription states handled, verification on every granting path, .storekit file committed
  • NEEDS WORK: No CRITICAL, but HIGH issues present (partial subscription state coverage, missing intro eligibility, missing appAccountToken when server-side validates)
  • NOT READY: Any CRITICAL — missing finish() / missing listener / missing verification / missing restore / missing subscription terms / missing loot box odds

Output Format

# IAP Audit Results

## IAP Architecture Map
[5-10 line summary from Phase 1]

## Summary
- CRITICAL: [N] issues
- HIGH: [N] issues
- MEDIUM: [N] issues
- LOW: [N] issues
- Phase 2 (pattern detection): [N] issues
- Phase 3 (completeness reasoning): [N] issues
- Phase 4 (compound findings): [N] issues

## IAP Health Score
[Phase 5 table]

## Issues by Severity

### [SEVERITY/CONFIDENCE] [Category]: [Description]
**File**: path/to/file.swift:line
**Phase**: [2: Detection | 3: Completeness | 4: Compound]
**Issue**: What's wrong or missing
**Impact**: What happens if not fixed (revenue loss, rejection, support load)
**Fix**: Code example showing the fix
**Cross-Auditor Notes**: [if overlapping with another auditor]

## Recommendations
1. [Immediate — CRITICAL revenue and rejection risks]
2. [Short-term — subscription state coverage, intro eligibility, appAccountToken]
3. [Long-term — server-side validation, centralized architecture, test coverage]

Output Limits

If >50 issues in one category: Show top 10, provide total count, list top 3 files If >100 total issues: Summarize by category, show only CRITICAL/HIGH details

False Positives (Not Issues)

  • Transaction.finish()
    called inside Task with proper error handling (verify it's reached)
  • Hardcoded strings in test/preview code
  • Product.purchase()
    in a single StoreManager even if referenced from many views (check if the call site is the manager or the view)
  • Missing restore button when app sells only consumables (restore not required by guideline)
  • Missing subscription terms when products are non-consumable only
  • appAccountToken omitted when there is no server backend
  • Missing loot box odds when random patterns are unrelated to purchases (e.g., random animation variant)

Related

For implementation patterns:

axiom-integration
skill (skills/in-app-purchases.md) For StoreKit 2 API reference:
axiom-integration
skill (skills/storekit-ref.md) For complete IAP implementation: Launch
iap-implementation
agent For security of receipt validation: Launch
security-privacy-scanner
agent