Some checks failed
ci / gate (push) Successful in 2s
ci / acceptance (bun, 22, ubuntu-22.04, Linux, bun) (push) Successful in 28s
ci / acceptance (bun, 22, windows, Windows, bun) (push) Successful in 33s
ci / acceptance (node20, 20, ubuntu-22.04, Linux, node) (push) Successful in 1m20s
ci / ci (push) Successful in 2m3s
ci / acceptance (node22, 22, ubuntu-22.04, Linux, node) (push) Successful in 1m23s
ci / acceptance (node20, 20, windows, Windows, node) (push) Has been cancelled
ci / linux (push) Has been cancelled
ci / acceptance (node22, 22, windows, Windows, node) (push) Has been cancelled
ci / windows (push) Has been cancelled
ci / record (push) Has been cancelled
71 lines
6.4 KiB
Markdown
71 lines
6.4 KiB
Markdown
---
|
|
name: store-subscriptions
|
|
description: Set up auto-renewable subscriptions on the App Store and Google Play from one config - Apple subscription groups, levels, localizations, equalized prices with overrides and a price CSV, introductory offers, promotional offers, offer codes and custom codes; Play subscriptions with base plans, regional prices, intro and developer-chosen offers; first submission with a version, the disclosure Apple 3.1.2 and Play's subscription policy require, Terms of Use (EULA) links, price changes and the catalog parity report. Use when the user sells subscriptions, adds a free trial, changes subscription prices, or gets a 3.1.2 / subscription policy rejection.
|
|
---
|
|
|
|
# Subscriptions on both stores
|
|
|
|
CLI: `storebound apple subscriptions`, `storebound play subscriptions` (both `--dry` first; they print the plan and
|
|
ask before writing), `storebound catalog diff` (read-only parity of the whole catalog), `storebound validate`.
|
|
Config: `subscriptions: [{ name, listings, subscriptions: [{ id, period, level, usd, overrides, listings, intro,
|
|
offers, offerCodes, reviewScreenshot, basePlanId, gracePeriod, platforms }] }]`, `app.termsUrl`.
|
|
|
|
## Model
|
|
- **Apple**: a subscription group per product line (people hold one subscription per group); `level` 1 = highest
|
|
service (upgrades and downgrades follow it). Prices: the USA price point for `usd`, its equalizations for every
|
|
territory, then `overrides` and rows from `--csv store/prices.csv` (same CSV as `storebound prices`, rows with a
|
|
future `start_date` become scheduled price changes). `preserveCurrentPrice` is always true: existing subscribers keep
|
|
their price; raising it for them is a separate, consent-bound step in App Store Connect.
|
|
- **Google Play**: no groups. Each entry is a Play subscription with one auto-renewing base plan (`basePlanId`
|
|
default from the period: monthly, yearly, ...), prices from `convertRegionPrices` + overrides + CSV. A base plan price
|
|
change applies to new subscribers; existing ones keep their price until you run a price migration in the Console.
|
|
- **Intro offer** (`intro`): free trial, pay as you go (`periods` at `usd`), or pay up front. Apple: per territory,
|
|
first-time subscribers of the group. Play: an offer for people who never had this subscription (pay as you go is a
|
|
relative discount, so it follows every regional price).
|
|
- **Promotional offers** (`offers`, Apple + Play): Apple promotional offers (your server signs them at purchase
|
|
time); Play offers with an offer tag and no acquisition targeting (your app decides who sees them).
|
|
- **Offer codes** (`offerCodes`, Apple): a campaign (eligibility NEW / EXISTING / EXPIRED, replace or stack with the
|
|
intro offer). Custom codes are created only with `--codes` from a terminal that confirms (anyone with the code can
|
|
redeem it). One-time-use codes: the owner generates them in App Store Connect. **Play promo codes have no API**:
|
|
Play Console > Monetize > Promo codes.
|
|
|
|
## Steps
|
|
1. Prerequisites: Apple Paid Applications agreement, banking and tax; Play payments profile and a build with the
|
|
billing library on a track.
|
|
2. `storebound check` (limits: Apple name 30 / description 45; Play description 200, up to 4 benefits).
|
|
3. `storebound apple subscriptions --dry` and `storebound play subscriptions --dry`; show the plan, then run them
|
|
yourself (`--yes`): new subscriptions, first prices, listings, base plans and offers are written. A price change
|
|
of an existing subscription is a live price and `--yes` never applies it: the run writes everything else, prints
|
|
`price change on <id> (<from> -> <to>) needs the owner: re-run with --allow-price-changes` and exits 3. After the
|
|
owner's yes in chat for that diff, run it again with `--yes --allow-price-changes`. Flows never pass that flag.
|
|
4. Review screenshot of the paywall per subscription (`reviewScreenshot`), uploaded by `apple subscriptions`.
|
|
5. **First subscriptions go with an app version** (and a new group with at least one of its subscriptions):
|
|
`storebound apple iap-submit --with-version --dry` plans one review submission with the app version, the
|
|
subscription versions and the group version; the owner runs it without `--dry` (it asks, and submitting it submits
|
|
the app), or adds `--no-submit` and presses "Submit for Review" in App Store Connect. Details and the exact limits:
|
|
store-iap-setup skill, step 3.
|
|
5b. **Win-back offers** for lapsed subscribers (`winBack` on a subscription, `storebound apple win-back`) once the
|
|
subscription is approved: app-store-promotion skill.
|
|
6. `storebound validate` (states, localizations, group names, review screenshots, active Play base plans, terms URL)
|
|
and `storebound catalog diff` (both stores against the config, prices per region).
|
|
|
|
## Disclosure (review checks it)
|
|
- Purchase screen: plan name, length, **full renewal price per period as the most prominent price**, what is included,
|
|
trial length and what is charged after it, how to cancel, Restore Purchases, links to the **Terms of Use** and the
|
|
**Privacy Policy** (Apple 3.1.2 and "Clearly describing subscriptions",
|
|
https://developer.apple.com/app-store/subscriptions/; Google Play subscriptions policy,
|
|
https://support.google.com/googleplay/android-developer/answer/9900533).
|
|
- Metadata: the Terms of Use link in the App Store description, or a custom EULA in App Information > License
|
|
Agreement; the privacy policy URL field (`storebound apple listing`). `validate` warns when a description has no
|
|
Terms link. 3.1.2(a): a subscription must give ongoing value and last at least 7 days.
|
|
- Review notes: where the paywall is, that the demo account can buy in the sandbox (`review-access`).
|
|
- Remote paywalls (RevenueCat Paywalls, Superwall, Adapty, Qonversion, Purchasely, Apphud) or flags: say in the review
|
|
notes that paywall designs / prices shown change remotely (2.3.1, `scan` warns; store-pitfalls 74); moving features
|
|
behind the subscription after review needs a new review and a description that discloses it.
|
|
|
|
## Gotchas
|
|
- A product id, a billing period or a subscription's group cannot change; make a new id.
|
|
- Apple applies one introductory offer per customer per group; offers cannot be edited, only replaced.
|
|
- Account deletion does not cancel a subscription: say so (`account-deletion`).
|
|
- Unverified (no live account during development): free promotional offers and offer codes are sent with
|
|
territories but no price point; check the first run in App Store Connect.
|