--- name: gplay-iap-setup description: In-app products, subscriptions, base plans, and offers setup for Google Play monetization, including bulk-localizing subscription display names, descriptions, and benefits across all locales. Use when configuring in-app purchases or subscription products. --- # In-App Purchase Setup for Google Play Use this skill when you need to set up monetization for your Android app. ## Two APIs: Legacy vs New Monetization Google Play has two APIs for one-time products: | | Legacy (`gplay iap`) | New Monetization (`gplay onetimeproducts`) | |---|---|---| | API | `inappproducts` | `monetization.onetimeproducts` | | Price format | `priceMicros`/`currency` | `units`/`nanos`/`currencyCode` | | Structure | Flat `prices` map | `purchaseOptions` with `regionalPricingAndAvailabilityConfigs` | | States | `active`/`inactive` | `DRAFT` → `ACTIVE` (requires explicit activation) | | Regional pricing | `--auto-convert-prices` flag | `--regions-version` required | **Prefer the new monetization API** (`gplay onetimeproducts`) for new products. It supports purchase options, better regional pricing control, and is the actively developed API. **Use the legacy API** (`gplay iap`) only for managing existing legacy products. **Never mix the two APIs for the same product.** A product created via `gplay iap create` cannot be managed via `gplay onetimeproducts` and vice versa. ## Critical: Product IDs Are Permanent **Google Play permanently reserves product IDs after deletion.** If you create `premium_unlock` and later delete it, the ID `premium_unlock` can never be reused — not even with a different API. Choose product IDs carefully. This means: - Do NOT create a "test" product with a good ID and then delete it - Do NOT create via the legacy API and then try to recreate via the new API - If you burn an ID, you must choose a new one (e.g., `premium_unlock_v2`) ## One-Time Products (New Monetization API) ### List products ```bash gplay onetimeproducts list --package com.example.app ``` ### Create product **`--regions-version` is required** — the `create` command uses PATCH with `allowMissing=true` internally: ```bash gplay onetimeproducts create \ --package com.example.app \ --product-id premium_unlock \ --json @product.json \ --regions-version "2025/03" ``` ### product.json (new monetization format) ```json { "productId": "premium_unlock", "listings": [ { "languageCode": "en-US", "title": "Premium Unlock", "description": "Unlock all premium features" }, { "languageCode": "es-ES", "title": "Desbloqueo Premium", "description": "Desbloquea todas las funciones premium" } ], "purchaseOptions": [ { "buyOption": { "legacyCompatible": true }, "newRegionsConfig": { "availability": "AVAILABLE", "usdPrice": { "currencyCode": "USD", "units": "9", "nanos": 990000000 }, "eurPrice": { "currencyCode": "EUR", "units": "9", "nanos": 990000000 } }, "regionalPricingAndAvailabilityConfigs": [ { "regionCode": "US", "availability": "AVAILABLE", "price": { "currencyCode": "USD", "units": "9", "nanos": 990000000 } }, { "regionCode": "GB", "availability": "AVAILABLE", "price": { "currencyCode": "GBP", "units": "7", "nanos": 990000000 } }, { "regionCode": "IN", "availability": "AVAILABLE", "price": { "currencyCode": "INR", "units": "249", "nanos": 990000000 } } ] } ] } ``` ### Activate the purchase option New products start in **DRAFT** state. You must activate before users can purchase: ```bash gplay purchase-options batch-update-states \ --package com.example.app \ --product-id premium_unlock \ --json '{"requests":[{"activatePurchaseOptionRequest":{"packageName":"com.example.app","productId":"premium_unlock","purchaseOptionId":"default"}}]}' ``` ### Update product ```bash gplay onetimeproducts patch \ --package com.example.app \ --product-id premium_unlock \ --json @product-updated.json \ --regions-version "2025/03" \ --update-mask "purchaseOptions" ``` ### Get product ```bash gplay onetimeproducts get --package com.example.app --product-id premium_unlock ``` ### Delete product ```bash gplay onetimeproducts delete \ --package com.example.app \ --product-id premium_unlock \ --confirm ``` ### Batch operations ```bash # Get multiple products gplay onetimeproducts batch-get \ --package com.example.app \ --product-ids "premium_unlock,coins_100" # Update multiple products (regionsVersion goes inside JSON) gplay onetimeproducts batch-update \ --package com.example.app \ --json @products-batch.json ``` ## Legacy In-App Products (IAP) Use only for managing existing legacy products. ### List products ```bash gplay iap list --package com.example.app ``` ### Create product `iap create` has no `--sku` flag — the SKU/productId lives in the JSON body: ```bash gplay iap create \ --package com.example.app \ --json @product.json ``` ### product.json (legacy format) ```json { "sku": "premium_upgrade", "status": "active", "purchaseType": "managedUser", "defaultPrice": { "priceMicros": "990000", "currency": "USD" }, "prices": { "US": { "priceMicros": "990000", "currency": "USD" }, "GB": { "priceMicros": "799000", "currency": "GBP" } }, "listings": { "en-US": { "title": "Premium Upgrade", "description": "Unlock all premium features" }, "es-ES": { "title": "Actualización Premium", "description": "Desbloquea todas las funciones premium" } } } ``` ### Update / Batch / Delete ```bash # Update gplay iap update --package com.example.app --sku premium_upgrade --json @product-updated.json # Batch update gplay iap batch-update --package com.example.app --json @products.json # Batch get gplay iap batch-get --package com.example.app --skus "premium,coins_100,coins_500" # Delete (permanent — ID cannot be reused) gplay iap delete --package com.example.app --sku premium_upgrade --confirm ``` ## Subscriptions ### List subscriptions ```bash gplay subscriptions list --package com.example.app ``` ### Create subscription ```bash gplay subscriptions create \ --package com.example.app \ --json @subscription.json ``` ### subscription.json Subscriptions use the `units`/`nanos`/`currencyCode` price format: ```json { "productId": "premium_monthly", "basePlans": [ { "basePlanId": "monthly", "state": "ACTIVE", "regionalConfigs": [ { "regionCode": "US", "newSubscriberAvailability": true, "price": { "currencyCode": "USD", "units": "4", "nanos": 990000000 } } ], "autoRenewingBasePlanType": { "billingPeriodDuration": "P1M" } }, { "basePlanId": "yearly", "state": "ACTIVE", "regionalConfigs": [ { "regionCode": "US", "newSubscriberAvailability": true, "price": { "currencyCode": "USD", "units": "49", "nanos": 990000000 } } ], "autoRenewingBasePlanType": { "billingPeriodDuration": "P1Y" } } ], "listings": [ { "languageCode": "en-US", "title": "Premium Subscription", "description": "Get all premium features" } ] } ``` ### Bulk-localize subscriptions across locales Subscription listings are an **array** of per-locale objects (not an object keyed by locale). Each entry uses `languageCode`, `title`, `benefits` (array, max 4), and `description`. One `subscriptions update` call sets every locale atomically — use `--update-mask listings` so base plans and pricing are left untouched. **1. Discover the locales your app already ships** (cover at least these): ```bash EDIT_ID=$(gplay edits create --package com.example.app | jq -r '.id') gplay listings list --package com.example.app --edit "$EDIT_ID" --output table ``` **2. Build a listings-only JSON file** (`subscription-listings.json`): ```json { "listings": [ { "languageCode": "en-US", "title": "Premium Monthly", "benefits": ["Unlimited access", "No ads"], "description": "Premium access to all features." }, { "languageCode": "de-DE", "title": "Premium Monatlich", "benefits": ["Unbegrenzter Zugang", "Keine Werbung"], "description": "Premium-Zugang zu allen Funktionen." }, { "languageCode": "es-ES", "title": "Premium Mensual", "benefits": ["Acceso ilimitado", "Sin anuncios"], "description": "Acceso premium a todas las funciones." }, { "languageCode": "ja-JP", "title": "プレミアム月額", "benefits": ["無制限アクセス", "広告なし"], "description": "すべての機能にプレミアムアクセス。" } ] } ``` **3. Apply to one subscription:** ```bash gplay subscriptions update \ --package com.example.app \ --product-id premium_monthly \ --json @subscription-listings.json \ --update-mask listings ``` **4. Loop over every subscription in the app:** ```bash PACKAGE="com.example.app" gplay subscriptions list --package "$PACKAGE" --paginate \ | jq -r '.[].productId' \ | while read -r PRODUCT_ID; do gplay subscriptions update \ --package "$PACKAGE" \ --product-id "$PRODUCT_ID" \ --json @subscription-listings.json \ --update-mask listings done ``` Verify with `gplay subscriptions get --package com.example.app --product-id premium_monthly --pretty` and confirm every `languageCode` appears in the `listings` array. Constraints: title max 55 chars, description max 80 chars, benefits max 4 items. When the user gives a single display name, reuse it for all locales; when they give per-locale translations, use each locale's own text. ## Base Plans Base plans define the billing period and price for subscriptions. ### Activate base plan ```bash gplay baseplans activate \ --package com.example.app \ --product-id premium_monthly \ --base-plan-id monthly ``` ### Deactivate base plan ```bash gplay baseplans deactivate \ --package com.example.app \ --product-id premium_monthly \ --base-plan-id monthly ``` ### Migrate prices ```bash gplay baseplans migrate-prices \ --package com.example.app \ --product-id premium_monthly \ --base-plan-id monthly \ --json @migration.json ``` ## Subscription Offers Offers provide discounts, free trials, or introductory pricing. ### List offers ```bash gplay offers list \ --package com.example.app \ --product-id premium_monthly \ --base-plan-id monthly ``` ### Create offer ```bash gplay offers create \ --package com.example.app \ --product-id premium_monthly \ --base-plan-id monthly \ --json @offer.json ``` ### offer.json (Free trial) ```json { "offerId": "trial_7day", "state": "ACTIVE", "phases": [ { "duration": "P7D", "pricingType": "FREE_TRIAL" } ], "regionalConfigs": [ { "regionCode": "US" } ] } ``` ### offer.json (Introductory price) ```json { "offerId": "intro_50_off", "state": "ACTIVE", "phases": [ { "duration": "P1M", "pricingType": "SINGLE_PAYMENT", "price": { "priceMicros": "2490000", "currency": "USD" } } ] } ``` ### Activate/Deactivate offer ```bash # Activate gplay offers activate \ --package com.example.app \ --product-id premium_monthly \ --base-plan-id monthly \ --offer-id trial_7day # Deactivate gplay offers deactivate \ --package com.example.app \ --product-id premium_monthly \ --base-plan-id monthly \ --offer-id trial_7day ``` ## OTP Purchase Option Offers Manage offers on one-time product purchase options: ```bash # List offers gplay otp-offers list --package com.example.app --product-id premium_unlock --purchase-option-id default # Activate offer gplay otp-offers activate --package com.example.app --product-id premium_unlock --purchase-option-id default --offer-id promo_50off # Deactivate offer gplay otp-offers deactivate --package com.example.app --product-id premium_unlock --purchase-option-id default --offer-id promo_50off ``` ## Regional Pricing ### Convert prices ```bash gplay pricing convert \ --package com.example.app \ --json @price-request.json ``` ### price-request.json (ConvertRegionPricesRequest) The body is a single base `price` as Money — `units` is the whole-currency amount as a string, `nanos` is the fractional part (990000000 = .99): ```json { "price": { "currencyCode": "USD", "units": "9", "nanos": 990000000 } } ``` The response returns converted prices for all supported regions plus a `regionVersion` you can pass as `--regions-version` to subscriptions, base plans, offers, and one-time product commands. ## Common Monetization Patterns ### Pattern 1: New One-Time Product (recommended) ```bash # 1. Create product gplay onetimeproducts create \ --package com.example.app \ --product-id premium_unlock \ --json @premium.json \ --regions-version "2025/03" # 2. Activate purchase option gplay purchase-options batch-update-states \ --package com.example.app \ --product-id premium_unlock \ --json '{"requests":[{"activatePurchaseOptionRequest":{"packageName":"com.example.app","productId":"premium_unlock","purchaseOptionId":"default"}}]}' # 3. Verify gplay onetimeproducts get --package com.example.app --product-id premium_unlock ``` ### Pattern 2: Subscription with Free Trial ```bash # 1. Create subscription gplay subscriptions create \ --package com.example.app \ --json @sub.json # 2. Create free trial offer gplay offers create \ --package com.example.app \ --product-id premium \ --base-plan-id monthly \ --json @trial.json ``` ### Pattern 3: Multi-Tier Subscription ```json { "productId": "premium", "basePlans": [ { "basePlanId": "basic_monthly", "regionalConfigs": [{ "regionCode": "US", "newSubscriberAvailability": true, "price": { "currencyCode": "USD", "units": "2", "nanos": 990000000 } }], "autoRenewingBasePlanType": { "billingPeriodDuration": "P1M" } }, { "basePlanId": "premium_monthly", "regionalConfigs": [{ "regionCode": "US", "newSubscriberAvailability": true, "price": { "currencyCode": "USD", "units": "4", "nanos": 990000000 } }], "autoRenewingBasePlanType": { "billingPeriodDuration": "P1M" } }, { "basePlanId": "premium_yearly", "regionalConfigs": [{ "regionCode": "US", "newSubscriberAvailability": true, "price": { "currencyCode": "USD", "units": "49", "nanos": 990000000 } }], "autoRenewingBasePlanType": { "billingPeriodDuration": "P1Y" } } ] } ``` ## Testing ### Use test purchases In your app code, use test product IDs: - `android.test.purchased` - `android.test.canceled` - `android.test.refunded` - `android.test.item_unavailable` ### License testing Add test accounts in Play Console: Settings → License Testing → Add license testers ## Best Practices 1. **Use clear product IDs** - e.g., `premium_monthly`, not `prod_001`. IDs are permanent and cannot be reused after deletion. 2. **Prefer the new monetization API** - Use `gplay onetimeproducts` for new products, not `gplay iap`. 3. **Localize descriptions** - Provide listings for all supported languages. 4. **Set up regional pricing** - Use PPP pricing (see `gplay-ppp-pricing` skill) instead of same price everywhere. 5. **Activate after creation** - New OTP products start in DRAFT. Use `gplay purchase-options batch-update-states` to activate. 6. **Discover commands** - Run `gplay --help` to see all command groups. Purchase option management is under `gplay purchase-options`, not under `gplay onetimeproducts`. 7. **Test thoroughly** - Use test accounts and test product IDs. 8. **Monitor conversions** - Track which products/offers perform best. 9. **Update prices carefully** - Price changes affect existing subscribers. ## Billing Periods - `P1W` - 1 week - `P1M` - 1 month - `P3M` - 3 months - `P6M` - 6 months - `P1Y` - 1 year