App Store + RevenueCat setup · & exhaustive done-list

Monetization — Setup Checklist & What's Done

How to use this

Top half is the step-by-step App Store Connect + RevenueCat setup — the biggest remaining non-code block — with the exact IDs to create. Tick boxes as you go (saved locally). Bottom half is the exhaustive list of everything already built in code — the full 6-PR / 59-commit program (backend #514 + #515, admin #163, iOS #309 + #311 + #312) — so the two halves together are the full picture. Confirm the pending product sign-offs (prices, discount duration) before doing section D onward.

Identifiers reference (create exactly these)

Click any dashed value to copy. Names in bold are load-bearing — the backend matches them literally.

ThingExact valueNote
Entitlement (Plus)plusBackend derives tier from this literal id.
Entitlement (Pro)proLiteral; Pro outranks Plus.
Products (6)app.lissin.audio.plus.weekly app.lissin.audio.plus.monthly app.lissin.audio.plus.yearly app.lissin.audio.pro.weekly app.lissin.audio.pro.monthly app.lissin.audio.pro.yearlyNote app.lissin.audio.*, not com.*.
Offeringsdefault · save50 · save65 · winbackdefault = standard paywall (must be "Current"). The iOS rescue ladder requires exact save50/save65 offerings for visible discount stages; missing stages are safely skipped. winback = promo/win-back.
Dev webhook URLhttps://api.dev.lissin.com/api/v1/billing/webhooks/revenuecatAlready configured & verified.
Prod webhook URLhttps://api.lissin.com/api/v1/billing/webhooks/revenuecatSet before go-live.
Public SDK keyappl_XpLTTWDJZdTEnqTAYxrGYTXUBCkAlready ships in the app (Beta+Release).

A · App Store Connect — prerequisites (iOS/Billing)

B · Verify / finalize the 6 products (iOS/Billing)

Backend contract expects Plus < Pro and derives tier from entitlements, not prices — so prices are a store-side decision only.

C · The 3-day free trial (iOS/Billing)

Backend already projects it (period_type=TRIAL → trial phase + restricted plus_trial limits). You only need the App Store intro offer:

Apple constraint to plan around

One introductory offer per subscription group, per user. A user who consumes the 3-day trial is no longer intro-eligible in that group — which directly affects the rescue offers below.

D · Rescue offers — 50% then 65% (iOS/Billing + Product)

For never-subscribed users, Apple promotional offers don't apply (they require a current/previous subscription). The PRD's recommended mechanism is introductory pricing on the standard products, or offer codes. Because of the one-intro-per-group rule, decide the mechanism with product first:

The backend records presented_offering_id/offer_code per event and the iOS rescue ladder drives standard→save50save65 — persisting/resuming its stage, resetting on purchase, and committing a stage only after the exact offering loads. Dismissal routing was corrected in c6305160; missing rescue offerings are skipped, never replaced with standard prices.

E · Promotional & win-back offers (iOS/Billing)

F · RevenueCat dashboard (iOS/Billing)

G · RevenueCat Placements (iOS/Billing)

Create Placements matching the iOS placement registry + backend denial placements, so paywalls can change remotely without an app release:

onboarding
lockedEpisode
secondShow
generationAllowance
higherRecurringAllowance
documentAttach
remix
cadenceChange
proSources
transcript
speed
soundscape
exploreTimeLimit
planBilling

H · Webhooks (dev + prod) (iOS/Billing + Backend)

I · Sandbox end-to-end verification (QA)

J · Go-live gate (Product + Eng)

What's done — exhaustive record

Integrated behind flags

Backend merged through #522 into dev (+ #523/#524 fixes); Admin through #168 into dev; iOS through #317 into develop. Everything remains observe-only / additive / flag-gated until rollout. The dismissal correction c6305160 is pushed separately and must still be merged before the offer ladder is release-ready.

Backend — PR #514 (17 commits), how each works

CommitWhat & how
2c8591eVersioned plan contractplan-contract.ts holds Free/Plus/Pro + plus_trial as typed, JSON-serializable data (credits, minutes, caps, speed, transcript/soundscape/remix). Confirmed numbers test-locked.
8edc016plan_versions table + seeder — the contract published to the DB (one active row per variant); runtime source of truth → dashboard-editable later. npm run seed:plan-versions.
f4adc3fUsage ledger schema — immutable usage_ledger (CREDIT/RESERVE/RELEASE/CONSUME/EXPIRE/ADJUST) + per-window usage_accounts (base + rolloverIn − consumed − reserved).
8a6e08bWindow math — pure UTC-anchored weekly (ISO Mon) / rolling-3-day / billing-month / trial windows + previous-window for rollover. Fully unit-tested.
f546ffcQuota service — balances, reserve/consume/release (idempotent, rollover-aware), and a read-only peekBalance; unlimited meters short-circuit.
17b250aAccess phase + trial fields on billing_access (accessPhase, planVersion, trial dates, productId, offerId, environment).
c9caf70Expanded /billing/access — adds phase, planVersion, serverTime, per-meter balances (read-only peek), trial window. All additive.
17fed30Trial projection — webhook maps period_type=TRIAL → trial phase + window; persists plan version, product, offer, real sandbox/prod environment; EXPIRATION → 'expired'.
fda8643observe() — records real consumption to the ledger (idempotent), flags would-exceed, never denies.
2de1f2eWire generation + upload (observe) — batch create → one-time, schedule-intent show → recurring, upload → files. Fire-and-forget, keyed by resource id.
4804bf4Wire Explore playback + SSE (observe) — debit credited audible seconds per progress receipt (standalone content); generate/live + generate/stream observe once per stream.
9fe7dc2Enforcement engineenforce() pre-flight (capability / file-size / episode-duration / quota-peek) → structured BillingEnforcementException (HTTP 402) behind per-domain flags.
333c46bWire enforcement into generation (batch/live/stream) + document upload.
4e1e491DocsBILLING_ENFORCE[_DOMAIN] flags in .env.example.

The 14 arc-commits above landed first; 3 further commits (→ 17 total) addressed every code-review comment. Re-review green. All defaults remain OFF (observe-only). 3 migrations.

Data model added

Contracts established

Expanded GET /billing/access (additive, all optional)

phase, planVersion, serverTime, trial{startsAt,endsAt}|null,
balances.{recurringGeneration, oneTimeGeneration, explorePlayback(sec), fileUploads}
  = { limit|null, remaining|null, used, reserved, rolloverBalance, resetAt|null, window }

Structured 402 denial (when a domain is enforced)

{ error:"billing_denied", code, message, requiredTier?, meter?, remaining?, limit?,
  resetAt?, maxMinutes?, maxBytes?, placement? }
code ∈ CAPABILITY_REQUIRED | QUOTA_EXHAUSTED | DURATION_LIMIT | FILE_LIMIT | ACCESS_UNKNOWN

Enforcement flags (all default off → observe-only)

BILLING_ENFORCE · BILLING_ENFORCE_GENERATION · BILLING_ENFORCE_FILES · BILLING_ENFORCE_PLAYBACK · BILLING_ENFORCE_CAPABILITIES — per-domain overrides master (incl. explicit false).

Backend — PR #515 · grandfathering (4 commits)

Admin — PR #163 (11 commits)

iOS — PR #309 (9 commits)

CommitWhat
772daf4eDecode the expanded /billing/access contract (optional fields, lenient); extend PlanLimits.
d8d93c06Align the local fallback plan to the credit-based Phase-0 numbers + a plusTrial variant.
7dd7e34bTests for expanded-contract decoding (with + without new fields).
a5a174e3Decode the 402 billing_denied body → APIError.billingDenied + PaywallPlacement(billingDenial:) mapping (dormant until flags flip).

The 4 arc-commits above landed first; 5 further commits (→ 9 total) addressed every review comment. Re-review green.

iOS — PR #311 · offer-stage rescue ladder (6 commits)

iOS — PR #312 · Phase-4 gates, trial & live balances (12 commits)

Verification & safety

Remaining — user / non-code (not shipped in these PRs)

  1. Merge the 6 PRs — iOS in order #309 → #311 → #312; backend one at a time (no concurrency guard on prod migrations).
  2. RevenueCat / App Store catalog — create the default + save50 + save65 offerings, the 3-day trial intro offer, the placements, and the prod webhook (the top half of this doc).
  3. Product price / discount sign-offs — standard prices + rescue discount duration.
  4. Flip enforcement — turn on BILLING_ENFORCE_* per domain → QA → grandfather cohort → gradual % ramp.

Still deferred (code, separate future work)

Sources: lo/lissin-monetization-plan/ (PRD docs 01–08) · code from PRs #514 + #515 (backend→dev), #163 (admin→dev), and #309 + #311 + #312 (iOS→develop). Companion reports in lo/: monetization-implementation-final-report.html, monetization-requirements-status-report.html.