--- name: store-iap-setup description: Create and price in-app purchases (consumables, non-consumables like "remove ads", coin/gem packs, starter packs) on Google Play and App Store Connect from one product list - regional prices, local overrides, a price sheet CSV for many products and regions (diff first, future-dated App Store changes), activation, review screenshots, submitting first IAPs with a version, license/sandbox testers. Use when the user sets up, prices, re-prices or tests in-app products/IAPs, or hits Play onetimeproducts / inappproducts 403 or App Store IAP submission errors. --- # In-app purchases on both stores CLI: `storebound` (or `bunx storebound` in a project that has it, or `bun /cli/index.ts`). One list in `storebound.config.ts` `products[]` drives both stores: `id` (same on both), `type` (consumable | non_consumable), `usd` base price, optional `overrides` (`{ region: 'TR', currency: 'TRY', price: 99.99 }`), `listings` per language (title <= 30, description <= 45 for Apple), `reviewScreenshot`. ## Prerequisites - Play: a build with `com.android.vending.BILLING` uploaded to any track; payments profile set up. - Apple: Paid Applications agreement + banking + tax active (Business > Agreements), the app record exists. ## Google Play (API) 1. `storebound check`, then `storebound play products --dry` (shows create/update and region count per product). 2. `storebound play products`. Per product: `pricing:convertRegionPrices` (USD -> every region at local price points, and the `regionsVersion`), your overrides on top, `PATCH .../onetimeproducts/{id}?allowMissing=true®ionsVersion.version=...` (create or update), then `oneTimeProducts/{id}/purchaseOptions:batchUpdateStates` to activate (new options are DRAFT). 3. Verify: `storebound play status` lists every product with `buy:ACTIVE`. 4. Test: Play Console > Settings > License testing: add tester Gmail accounts; install from the internal testing link; purchases are free test orders. ## App Store (API + UI) 1. `storebound apple iap --dry`, then `storebound apple iap`: creates missing products, localizations, a price schedule (USA base + overrides; Apple equalizes the rest) and availability. Existing schedules are not changed (change prices in the UI if needed). 2. Review screenshot per product (a real device size, e.g. 1290 x 2796, showing the item in the shop): `storebound apple iap-review`. States move to READY_TO_SUBMIT when complete. 3. **UI**: each IAP page > "Add for Review"; this puts them into the version's draft submission. Then submit the version (owner, see appstore-first-release). 4. Test: Sandbox testers (Users and Access > Sandbox) on device; or a StoreKit configuration file in Xcode for local runs (those transactions are signed locally and fail server-side receipt checks). ## Many prices at once: the price sheet (both stores) For price changes after the products exist (sales, regional pricing, a new price everywhere), keep one CSV: `product,store,region,currency,price,start_date` (`store` = apple | play | both; `region` ISO alpha-2; `start_date` App Store only, a future YYYY-MM-DD). 1. `storebound prices export` writes the current prices of both stores to `store/prices.csv` (`--with-automatic` adds Apple's equalized prices as `kind=auto`, for reference; they are ignored on import). 2. Edit the rows. Apple only takes its own price points: an exact value per territory (the error lists the neighbours); Play takes any price in the region's currency. 3. `storebound prices import --dry` (MCP: `prices_diff`) prints the diff table: product, store, region, from, current, new, change %. Show it to the owner. 4. The owner runs `storebound prices import` (asks first; not an MCP tool). Apple: one new price schedule per changed product (base territory, default the current one or `prices.baseTerritory`, plus every manual price; territories without one follow Apple's equalization). Play: the purchase option's regional prices are replaced; regions the product has no price for get `convertRegionPrices` from the USD base, `--reconvert` resets every region the CSV does not list. - Future-dated changes (`start_date`) are App Store only; Play has no scheduled prices (change them on the day). - Apple's IAP pricing guide warns that a price **increase cannot be reverted** once it is in effect: tell the owner. - Prices are public the moment the stores apply them. Never import without the owner reading the diff. ## Gotchas - Play legacy `inappproducts` returns 403 "Please migrate to the new publishing API" -> use `monetization.onetimeproducts`. - Path casing: PATCH works on `onetimeproducts`, but list + `purchaseOptions:batchUpdateStates` need `oneTimeProducts`; the wrong casing answers with an HTML 404 page. - Play has no consumable flag: the app consumes the purchase after granting it. Use `legacyCompatible: true` so older billing libraries see the product by id. - Apple: first IAPs must ship with an app version ("FIRST_*_MUST_BE_SUBMITTED_ON_VERSION" from inAppPurchaseSubmissions). If the version is already in review without them: cancel the review submission (`storebound apple status --cancel-submission `, ask the owner), re-add via "Add for Review", resubmit. - Reported, **verify** before relying on it: App Store Connect API 4.4.1 manages IAPs as versions (`inAppPurchaseVersions`) and its release notes say a review submission item can take an `inAppPurchaseVersion`. That should let **later** IAP versions be added to `reviewSubmissions` through the API; the **first** attachment of a new app's first IAPs still needed the UI "Add for Review" in our runs. storebound does not automate either yet. - Apple needs an exact existing price point for each manual price; the error lists the nearest ones. - Apple IAP display name 2-30 characters, description max **45** (App Store Connect Help > Reference > "In-app purchase information"; an older App Store marketing page says 55, but App Store Connect enforces 45). - Product ids can never be reused after deletion on either store. Never delete without the owner. - Do not put prices or product names in store descriptions; say "offers optional in-app purchases". - If the game ships without real billing yet, hide every purchase surface in store builds (review rejects mock purchases) and answer "digital purchases: No" in IARC.