Purpose
Fires when the in-app purchase product reported by the app changes — a user upgrades, downgrades, or switches products. Sibling plugins (typically mam-inapp-purchase-manager) update entitlements, gate features, or dispatch notifications.
The action fires during the phone-data build (in mam-main’s JSON manager) when the request’s iap_product differs from the stored iap_product usermeta, just before the new value is persisted.
Signature
do_action(
'mam_main_iap_subscription_has_changed',
int $user_id,
string $previous_product_id
);
| Parameter | Type | Description |
|---|---|---|
$user_id |
int | The user whose subscription changed |
$previous_product_id |
string | The iap_product usermeta value before the change (the old product id) |
The action fires only when the incoming iap_product request value differs from the stored iap_product usermeta. Read the new product id from the request / from usermeta after the pipeline persists it (get_user_meta( $user_id, 'iap_product', true )).
Example: persist new entitlement state
add_action( 'mam_main_iap_subscription_has_changed',
function ( int $user_id, string $previous_product_id ) {
// The new product id has been written to usermeta by the pipeline.
$new_product_id = get_user_meta( $user_id, 'iap_product', true );
update_user_meta( $user_id, "iap_changed_at", current_time( 'mysql' ) );
do_action( 'mam_notification_send_message', array(
'message_type' => 'mam-my-plugin-iap_changed',
'recipient_id' => $user_id,
'replacements' => array(
'previous_product' => $previous_product_id,
'new_product' => $new_product_id,
),
) );
},
10, 2
);
Pipeline integration
mam-main’s own JSON manager (main-json-manager.php) processes the app’s reported iap_product on each phone-data build. When the request’s iap_product differs from the stored iap_product usermeta, it fires this action, then persists the new value. Subscribers see the change in (near) real-time.
Gotchas
- Fires per-phone-data-request. A user with a long-running session may not see the action fire until they next launch the app or the cursor is invalidated.
- No retry. If your subscriber’s persistence fails, the action doesn’t fire again unless the state changes again.
product_idshapes vary by store. Apple uses reverse-DNS (com.example.subscription_monthly); Google uses arbitrary strings. Don’t assume a format.- The action carries the old product id only. It fires on any change of the reported
iap_product(upgrade, downgrade, switch) — it does not carry a state enum. Read the new product id from usermeta. - No transactional guarantee. Two simultaneous phone-data requests for the same user could both detect the same shift and both fire the action — guard your subscriber against double-application.
Related articles
- Phone data pipeline phases
- Hook: mam_get_phone_data_before_send
- Hook: mam_notification_send_message
Metadata
| Field | Value |
|---|---|
| Article type | Hook Reference |
| Plugin slug | mam-main |
| Applies to plugin version | 2.1.11+ |
| Hook type | action |
| Audience | PHP developer |
| Last verified | 2026-05-02 |
