Signature
apply_filters( 'mam_iap_active_subscriber_product', string $default_product, int $user_id = 0 );
| Parameter | Type | Description |
|---|---|---|
$default_product |
string | The product SKU (or other token) to return if no active subscription is found. Pass-through value the caller can fall back to. |
$user_id |
int | Optional user ID. Note: the plugin’s own callback ignores this argument and reads the global $mam_user_id instead. |
Returns: string — the SKU of the user’s active subscription product, or $default_product if none is active.
Purpose
Answers “is this user actively subscribed, and to which product?” The plugin’s default callback walks the user’s WooCommerce orders looking for one with a future expiration_date meta and returns the SKU of the product on that order.
Subscribe to this filter to override the resolution — for example, to honor entitlements from a non-WooCommerce source (a SaaS license server, a CRM, a staff comp list), or to map several SKUs to a single canonical “tier” identifier the rest of your code can switch on.
When it runs
Wherever any plugin or theme code applies the filter. Within MAM Suite, the typical caller is mam-main (or a sibling plugin) deciding whether to gate a feature. The plugin itself does not apply this filter — it only registers a callback against it.
The plugin’s callback runs at priority 10 with 2 accepted args.
Default behavior
mam_in_app_purchase_manager::mam_iap_active_subscriber_product() does the following:
- Reads
$mam_user_idfrom the global scope. - Calls
wc_get_orders( [ 'customer_id' => $mam_user_id, 'status' => 'any' ] ). - Walks each order, reading the
expiration_datepost meta. - On the first order whose
expiration_dateis in the future, returns the SKU of the first item’s product. - If no qualifying order is found, returns
$default_productunchanged.
Implications:
- A user with multiple overlapping subscriptions returns whichever order WooCommerce hands back first. Order is not guaranteed.
- Cancelled or refunded orders are still considered if their
expiration_dateis in the future, because the query usesstatus: 'any'. - A user with a
wc-completedorder but noexpiration_datemeta (e.g., aOne Timepurchase, or a duration string the plugin’s switch didn’t recognize — see Hook: mam_iap_purchase) is treated as not subscribed.
Example: bundle a SaaS license with IAP
You sell a non-IAP B2B license that should also unlock the app. Add a callback that returns the license SKU when a user has an active license, and otherwise yields to the default chain:
add_filter( 'mam_iap_active_subscriber_product', 'my_app_b2b_license', 5, 2 );
function my_app_b2b_license( $default_product, $user_id = 0 ) {
$user_id = $user_id ?: ( $GLOBALS['mam_user_id'] ?? 0 );
if ( $user_id <= 0 ) {
return $default_product;
}
$license_expires = (int) get_user_meta( $user_id, 'b2b_license_expires', true );
if ( $license_expires > time() ) {
return 'b2b_premium';
}
return $default_product;
}
Register at priority 5 so your check runs before the plugin’s WooCommerce-order walk. Returning anything other than $default_product short-circuits — the plugin’s callback at priority 10 overwrites your value if you yield.
To run after the plugin and only override its $default_product fallback, register at priority > 10:
add_filter( 'mam_iap_active_subscriber_product', 'my_app_fallback_to_b2b', 20, 2 );
function my_app_fallback_to_b2b( $resolved, $user_id = 0 ) {
if ( $resolved === '' || $resolved === null ) {
// The plugin found no WooCommerce subscription. Try the B2B path.
return ( my_app_user_has_b2b_license( $user_id ) ) ? 'b2b_premium' : $resolved;
}
return $resolved;
}
Example: collapse multiple SKUs to a single tier
If the rest of your code only cares whether a user has any premium access, normalize:
add_filter( 'mam_iap_active_subscriber_product', 'my_app_normalize_premium', 20 );
function my_app_normalize_premium( $sku ) {
$premium_skus = [ 'premium_monthly', 'premium_yearly', 'premium_lifetime' ];
return in_array( $sku, $premium_skus, true ) ? 'premium' : $sku;
}
Run at priority > 10 so this sees the plugin’s resolved value.
Gotchas
- The
$user_idarg is decorative on the default callback. The plugin reads$mam_user_idfrom globals regardless of what’s passed. If your callsite needs to ask about a specific other user, you’ll need to set$mam_user_idyourself before applying the filter, or write your own callback at higher priority. status: 'any'includes refunded and cancelled orders. Ifexpiration_datemeta is in the future on a refunded order, the plugin will still return its SKU. If you refund subscriptions in WooCommerce, also clear or back-date theexpiration_datemeta — or filter out non-wc-completedorders in your own callback.- Order traversal stops at the first match. The first order in the list that has a future
expiration_datewins, regardless of which one is “newest” or “longest-running.” If a user has overlapping subs, you may not get the answer you’d guess. - Empty defaults. If the caller passes
''(ornull), the plugin’s callback returns the same empty value when no subscription is found. Make sure your downstream code handles “no subscription” as an empty string, not as a missing key. - Always return a string. Returning
null,false, or omitting the return statement strips the chain. The default plugin callback assumes a string-typed contract.
Verification
This article was last verified against:
- Plugin:
mam-inapp-purchase-managerv2.0 - Source:
mam-inapp-purchase-manager.php(mam_iap_active_subscriber_product) - Order meta keys:
expiration_date,iap_expiration_date,iap_product
Re-verify whenever the order query in mam_iap_active_subscriber_product() changes status filters, the expiration_date meta key is renamed, the early-exit-on-first-match behavior changes, or the default callback starts honoring the $user_id argument.
Related articles
- Plugin overview: mam-inapp-purchase-manager
- Recipe: Create an IAP product
- Hook: mam_iap_purchase — sets the
expiration_datemeta this hook reads - Hook: mam_iap_require_iap — overrides the on/off paywall flag, which is a different decision
Metadata
| Field | Value |
|---|---|
| Article type | Hook Reference |
| Plugin slug | mam-inapp-purchase-manager |
| Applies to plugin version | 2.0+ |
| Category | Extending MAM Suite |
| Hook type | filter |
| Audience | PHP developer |
| Last verified | 2026-05-01 |
