Read-only audit of mobile IAP and subscriptions (StoreKit 2, Play Billing, RevenueCat): receipt validation, restore, lifecycle sync, grace periods, entitlements. Use when "audit our IAP" or "restore purchases broken". Web → audit-payment-system.
Installs into .claude/skills of the current project.
Are you the author of Audit Monetization Iap?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/kensaurus-audit-monetization-iap)
---
name: audit-monetization-iap
description: >
Read-only audit of mobile IAP and subscriptions (StoreKit 2, Play Billing,
RevenueCat): receipt validation, restore, lifecycle sync, grace periods,
entitlements. Use when "audit our IAP" or "restore purchases broken". Web →
audit-payment-system.
license: MIT
effort: high
---
# audit-monetization-iap — Store billing & entitlements
**Degree of freedom: MIXED** — Phases 0–1 `[HIGH freedom]`; Phase 2 sandbox
`[LOW freedom — run exactly]`, sandbox only: a real purchase charges a real card and cannot be undone from here.
Read-only. Entitlements must match what users paid for: no unlock-without-pay,
no paid-but-locked, no double-charge, no lost purchase on reinstall.
**The two revenue killers are client-trusted entitlements (piracy) and broken
restore (refunds + 1-star reviews).**
> **Present findings. Do not patch store/server code until approved.**
> Never test against real billing.
## This skill vs neighbors
| Skill | Owns |
|---|---|
| **audit-monetization-iap** (this) | App Store / Play IAP, restore, store notifications |
| `audit-payment-system` | Web money-movement, Stripe, ledgers, PCI |
| `plan-mobile-readiness` | Submission paperwork (IAP via official billing is a checkbox) |
| `plan-aso` | Store listing conversion, not receipt validation |
| `plan-privacy-compliance` | Purchase-data in privacy labels |
Do **not** fire for "Stripe webhook / double charge on the website" →
`audit-payment-system`.
## How to reason
1. **Observe** — quote where "is Pro?" is decided and whether a receipt/token is verified server-side
2. **Interpret** — can a client lie, or can a paid user lose access?
3. **Classify** — client-trust / missing restore / lifecycle hole / correct
4. **Severity** — client-trusted entitlement or broken restore = Critical
## Worked example
> **Observe:** `isPro` reads `RevenueCat.getCustomerInfo()` on-device only;
> no App Store Server Notification handler.
> **Interpret:** a patched client can unlock Pro; refunds never revoke.
> **Classify:** client-trust + missing lifecycle sync.
> **Severity:** Critical.
> **Finding:** entitlements | client-only | Critical | `lib/pro.ts:18` |
> verify on server + ASN V2 / RTDN
---
## Phase 0 — Detect the billing setup [HIGH freedom]
- Layer: raw StoreKit 2 / Play Billing, or RevenueCat / Adapty / Glassfy
- Entitlement check: client-only vs server-verified
- Backend store of subscription state vs trust-the-SDK-at-runtime
- Products: consumable, non-consumable, auto-renewing sub, trial / intro
---
## Phase 1 — Correctness [HIGH freedom]
**Receipt / token validation** — Server-side (App Store Server API / Play
Developer API). Client-trusted "purchase succeeded" is Critical. StoreKit 2
signed transactions still need verification, not just decode.
**Source of truth** — One place answers "does this user have Pro?" Backend
must hear App Store Server Notifications V2 / Play RTDN for cancel, refund,
billing failure — not only check at purchase time.
**Restore** — Discoverable Restore (Apple requires it). Recovers after
reinstall / new device / re-login.
**Lifecycle** — trial, active, grace (billing retry), on hold, paused
(Android), expired, refunded, up/downgrade. Missing grace locks paying users
out. Missing revoke leaves refunded users in.
**Trials** — One trial per user/family; no farm. UI must not promise a trial
the store will refuse.
**Cross-platform** — iOS + Android (+ web): one purchase unlocks via backend,
or the user is charged twice.
**Prices** — From the store (localized), never hardcoded.
**Edges** — Interrupted purchase resumes; sandbox vs prod receipt mix-up;
clock-gamed trials.
---
## Phase 2 — Sandbox only [LOW freedom — never real billing]
Exercise: fresh purchase, restore on clean install, cancel → access ends at
period end, sandbox refund → revoke, trial → convert, interrupted purchase.
Confirm backend entitlement flips.
---
## Definition of Done
- [ ] Layer + products inventoried
- [ ] Every entitlement traced to server verify, or client-trust flagged Critical
- [ ] Store notifications wired or gap logged
- [ ] Restore present and sandbox-verified
- [ ] Lifecycle grid complete
- [ ] Cross-platform double-charge checked
- [ ] Prices from store
- [ ] Nothing patched
## Self-critique before reporting [LOW freedom — do not skip]
1. **Evidenced** — file:line for the entitlement SoT, not "RevenueCat probably verifies"
2. **No real charges** — sandbox only
3. **Severity justified** — Critical = unlock-without-pay or paid-but-locked
4. **Right owner** — web Stripe → `audit-payment-system`
5. **Restore probed** — "button exists" is not restore-verified
## Output format
1. **Setup** — layer, products, entitlement SoT
2. **Findings** — issue | severity | `file:line` | fix shape
3. **Lifecycle grid** — state × handled?
4. **Sandbox log** — scenario | expected | actual
5. **Fix plan** — server verify + restore first
## Related
- `audit-payment-system` — web / Stripe / ledger
- `plan-mobile-readiness` — store review gates
- `plan-aso` — listing, not billing correctness
- `plan-privacy-compliance` — purchase data disclosure