Goal
Add a new subscription tier to your mobile app’s paywall. After this recipe, the product will appear in the iap_co_packages array in the mobile JSON payload, the app will offer it for purchase, and a successful App Store / Google Play receipt will record a wc-completed WooCommerce order with the right expiration date.
Prerequisites
- General IAP settings configured per Recipe: Configure IAP settings
- The same product already created in App Store Connect and/or Google Play Console, with a known SKU
- WooCommerce active
- Admin access with permission to edit WooCommerce products
The plugin’s first admin load creates four global product attributes if they don’t exist: Duration, Default Product, IAP Discount Label, IAP Subtitle Label. Duration is seeded with 1 Month and 1 Year; Default Product is seeded with Yes / No. If you need other duration values (e.g., 1 Day, 1 Quarter, One Time), add them manually via Products → Attributes before continuing.
How the SKU connects everything
The SKU on the WooCommerce product is the bridge between three systems:
- App Store Connect / Google Play Console — set this as the product identifier there
- WooCommerce product — set this in the Inventory tab
- Mobile receipt — the app sends this in
$_REQUEST['product']when reporting a purchase
mam_iap_purchase looks up the product by SKU via wc_get_product_id_by_sku(). If the SKUs don’t match exactly, the receipt is silently dropped — no order is created, no error surfaces to the admin. Triple-check spelling and case.
For Android, the plugin auto-generates android_sku by replacing underscores with hyphens (premium_yearly → premium-yearly). If your Play Console product ID uses underscores or some other transform, you’ll need to align them by editing the SKU on the WooCommerce side.
Steps
1. Create a new WooCommerce product
Go to Products → Add New. Give it the name and description that should appear on the paywall — these become iap_product['name'] and iap_product['description'] in the mobile payload.
2. Mark it as an in-app purchase
In the Product data panel, check In APP Purchase (the checkbox added by this plugin). This sets the _in_app_purchase post meta to 'yes' and is what causes phone-manager.php to include the product in iap_co_packages. Products without this flag are invisible to the IAP pipeline.
The product type can be Simple or Subscription — both are queried.
3. Set the SKU and price
In the Inventory tab, set the SKU to match the App Store Connect / Google Play product identifier exactly.
In the General tab, set the Regular price to the user-facing price string (e.g., 4.99). This becomes price, regular_price, sub_amount, and sub_title in the mobile payload. Apple/Google charge whatever they charge regardless — the price you set here is for display only.
4. Set the Duration attribute
In the Attributes tab, add the Duration attribute and pick the term that matches the product:
| Term | Mapped expiration | sub_amount suffix |
|---|---|---|
1 Day |
+1 day |
per 1 Day |
1 Month |
+1 month |
per 1 Month |
1 Quarter |
+3 months |
per 1 Quarter |
1 Year |
+1 year |
per 1 Year |
One Time |
none — treated as a consumable | One Time Purchase |
These are the exact strings the duration → expiration switch in add_purchase() matches against (mam-inapp-purchase-manager.php). Other strings will fall through to “no expiration written” — meaning the WooCommerce order is created, but the expiration_date meta is never set, and the user is not recognized as an active subscriber by Hook: mam_iap_active_subscriber_product.
One Time flips the product from a subscription (type: 'sub', android_type: 'subs') to a consumable (type: 'consumable', android_type: 'inapp').
Tick Visible on the product page if you want the attribute to render on the WooCommerce front end. Used for variations is not required — IAP products are not WooCommerce-variable.
5. Mark it as the default product (optional)
Add the Default Product attribute and set the term to Yes on the one product you want highlighted. This becomes iap_default: 'Yes' on that entry. The mobile app uses it to pre-select the tier on the paywall.
Only set Yes on one product. If multiple products are flagged default, the app’s behavior is undefined.
6. Add a discount label (optional)
Add the IAP Discount Label attribute with a free-text term, e.g. Save 20% or Best Value. This becomes discount_label (note the leading two-space prefix the plugin adds automatically) and renders as a badge on the paywall card.
7. Add a subtitle label (optional)
Add the IAP Subtitle Label attribute with a free-text term. This becomes iap_subtitle_label and renders as secondary text under the product name.
8. Set the product image
The featured image becomes imageurl in the mobile payload, with a computed aspect ratio. If you don’t set one, aspect defaults to 0.5.
9. Save
Publish the product. The next phone-data request will include it in iap_co_packages, sorted by menu order, ascending. Reorder products via the Products list (the standard WooCommerce drag-handle UI) if you want them to appear in a specific order on the paywall.
10. Test the purchase flow
In a sandbox build:
- Sign in as a non-admin user (admins are exempted from the paywall — see Recipe: Configure IAP settings).
- Trigger a sandbox purchase for the product.
- Confirm a new
wc-completedorder shows up under WooCommerce → Orders for that user. - Open the order and verify the post meta
iap_expiration_date,iap_product, andexpiration_dateare all set. - Refresh the app — the user should now see content as a subscriber.
If the order doesn’t appear, the most common causes are: SKU mismatch between WooCommerce and App Store / Play Console; the In APP Purchase checkbox not ticked; or $mam_user_id resolving to 0 because the user isn’t authenticated.
Common gotchas
- Apple and Google are the source of truth, not WooCommerce. A WooCommerce order is a record of what the app told us. If a user disputes a charge, the order is not the authoritative billing record — App Store Connect or Play Console is.
- No server-side receipt validation. This plugin trusts the receipt data the app sends. Don’t grant out-of-band entitlements based on the WooCommerce order existing — the order can be created from a forged request. See the security note in Plugin overview: mam-inapp-purchase-manager.
- Duplicate receipts are silently skipped. If
iap_expiration_date+iap_productalready match an existingwc-completedorder, the plugin won’t create another one. This is intentional — Apple often re-sends receipts. - The
Durationterm is case- and space-sensitive.1 Monthworks;1 month,1Month, or30 daysdo not. - Android SKU transform is one-way. Underscores in the WooCommerce SKU become hyphens in
android_sku. There is no escape — if you need a literal underscore in your Play Console ID, you can’t get there from here.
Verification
This article was last verified against:
- Plugin:
mam-inapp-purchase-managerv2.0 - Source:
mam-inapp-purchase-manager.php(add_global_product_attribute,add_duration_attributes,add_purchase,add_in_app_purchase_product_type_option) - Source:
includes/phone-manager.php(iap_co_packagesbuild)
Re-verify whenever the global attribute slugs (duration, iap_default, iap_discount_label, iap_subtitle_label) change, the duration-string switch in add_purchase() changes, the SKU underscore-to-hyphen transform changes, or the _in_app_purchase meta key is renamed.
Related articles
- Plugin overview: mam-inapp-purchase-manager
- Recipe: Configure IAP settings
- Recipe: Use the single-screen IAP UI
- Hook: mam_iap_purchase
- Hook: mam_iap_active_subscriber_product
Metadata
| Field | Value |
|---|---|
| Article type | Recipe (Admin) |
| Plugin slug | mam-inapp-purchase-manager |
| Applies to plugin version | 2.0+ |
| Category | Building Your App |
| Audience | WordPress admin |
| Estimated time | 15 minutes |
| Prerequisites article | Recipe: Configure IAP settings |
| Last verified | 2026-05-01 |
