Recipe: Create an IAP product

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_yearlypremium-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:

  1. Sign in as a non-admin user (admins are exempted from the paywall — see Recipe: Configure IAP settings).
  2. Trigger a sandbox purchase for the product.
  3. Confirm a new wc-completed order shows up under WooCommerce → Orders for that user.
  4. Open the order and verify the post meta iap_expiration_date, iap_product, and expiration_date are all set.
  5. 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_product already match an existing wc-completed order, the plugin won’t create another one. This is intentional — Apple often re-sends receipts.
  • The Duration term is case- and space-sensitive. 1 Month works; 1 month, 1Month, or 30 days do 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-manager v2.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_packages build)

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.


  • 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
Contents

    Need Support?

    Can’t find the answer you’re looking for? Don’t worry we’re here to help!