# membership_product_apply
Configure existing membership products from one observed product-list page. This is configuration, not a purchase, payment, refund or settlement. The connected endpoint's schema determines available actions.
## Read the current page
```json
{"channelCode":"selected-channel-code","view":{"kind":"detail","membershipMid":"returned-membership-mid","products":{"page":1,"limit":15,"isDeactivated":true}}}
```
Use [memberships_get](publ_memberships_get.md). Select numeric IDs from this page
and copy `productPageRevision`, which is distinct from the membership revision.
Keep the observed page, limit and `isDeactivated` filter. Only the requested page
is covered; the bridge does not search other pages or both activation states.
## Prepare and apply one list action
```json
{
"requestId":"product-selling-001",
"channelCode":"selected-channel-code",
"membershipMid":"returned-membership-mid",
"sourcePage":{"page":1,"limit":15,"isDeactivated":true},
"expectedPageRevision":"0000000000000000000000000000000000000000000000000000000000000000",
"action":{"kind":"selling-state","subscriptionProductIds":[11,12],"isDeactivated":false},
"dryRun":true
}
```
Replace the sample revision and IDs with the observed values. `false` starts
selling; `true` suspends it. The source page above contains suspended products even
though the intended new state is selling. Up to 100 unique positive numeric IDs
are allowed, all on that page. Page sizes are 15/30/50/100. A dry run returns the
selected before/after states and target IDs without POST or a new task. Send the
same request with dryRun false to apply. Only IDs needing change enter one source
bulk POST. An already-matching selection is independently observed without a POST.
The exact selected-channel owner or MANAGE_MONETIZATION delegate is required.
The source list control requires a recognized monetizing plan; FREE_TRIAL and an
unknown/missing plan cannot proceed. ENTERPRISE_TRIAL permits only FREE_FINITE
products, including when several products are selected. This does not purchase a
plan or activate monetization, and it does not add an activation-key gate absent
from the source handler.
The bridge repeats the page, checks selected editor details and the parent
membership, and rechecks the context immediately before dispatch. It then reads
every selected ID independently and checks the requested selling state with the
observed identity, type, title, alias, price, period and purchase methods preserved.
Membership presentation/access must also remain unchanged. The observed projection
does not prove extension configuration, other pages, visitor access or checkout.
Missing editor purchase methods cannot be guessed from a UI default to authorize
a mutation. The operation does not create a product, change price/period, issue a
serial key or perform any fund movement.
## Inputs
| Field | Required here | Contract |
| --- | --- | --- |
| `membershipMid` | Yes | string; min length 1; max length 512; Copy membership.mid from the selected memberships_get detail, not a channel code, numeric membership ID or member/profile ID. |
| `action` | Yes | one of: `extension`, `selling-state`, `edit` (by `kind`) |
| `action.subscriptionProductId` | Yes | When `action.kind` is `extension`: integer; greater than 0 |
| `action.settings` | Yes | When `action.kind` is `extension`: object |
| `action.settings.isEnabled` | Yes | When `action.kind` is `extension`: boolean; Disable retains configured settings. First setup has no delete/reset-to-null operation and cannot return to its original unconfigured state. |
| `action.settings.name` | Yes | When `action.kind` is `extension`: string; min length 3; max length 20 |
| `action.settings.feeAmount` | Yes | When `action.kind` is `extension`: string; pattern `^(?:0\|[1-9]\d{0,7})(?:\.\d{1,2})?$`; Selling amount in the observed channel base currency, without separators. |
| `action.settings.originalFeeAmount` | No | When `action.kind` is `extension`: string; pattern `^(?:0\|[1-9]\d{0,7})(?:\.\d{1,2})?$` |
| `action.settings.extensionPeriodInDays` | Yes | When `action.kind` is `extension`: integer; minimum 1; maximum 365 |
| `action.settings.extensionLimit` | Yes | When `action.kind` is `extension`: integer; minimum 1; maximum 100 or null |
| `action.settings.purchaseMethodsType` | Yes | When `action.kind` is `extension`: `CASH_ONLY`, `CASH_AND_CP` |
| `action.settings.extendableBeforeExpiryDays` | Yes | When `action.kind` is `extension`: integer; minimum 1; maximum 365 or null |
| `action.subscriptionProductIds` | Yes | When `action.kind` is `selling-state`: array; items: integer; greater than 0; min items 1; max items 100 |
| `action.isDeactivated` | Yes | When `action.kind` is `selling-state`: boolean; false starts selling; true suspends selling. Only listed selected products are changed. |
| `action.subscriptionProductId` | Yes | When `action.kind` is `edit`: integer; greater than 0 |
| `action.patch` | Yes | When `action.kind` is `edit`: object |
| `action.patch.alias` | No | When `action.kind` is `edit`: string; min length 2; max length 20 |
| `action.patch.availablePurchaseMethodsType` | No | When `action.kind` is `edit`: `CASH_ONLY`, `CASH_AND_CP` |
| `confirmToken` | No | string; pattern `^ct[a-f\d]{32}$`; The confirmToken from this tool's preview result. Omit it to preview; repeat the same input with it to execute. |
| `irreversibleConfirmed` | No | `true`; true only after the person confirmed again, when the preview has requiresReconfirmation: the action cannot be undone. |
## Result, recovery and console
MEMBERSHIP_PRODUCTS_UPDATED confirms all requested selected states; UNCHANGED
means no POST was needed. `targetIds` identifies the dispatched subset; `products`
contains every selected detail. The source acknowledgement is a data:null HTTP200
envelope; exact acknowledgement validation is a bridge policy and still requires
fresh readback. A partial batch, malformed receipt, response loss or changed
preserved field remains unconfirmed, even if some rows changed.
Keep the same requestId and use task_status/task_resume with
taskType:"membership-products". Resume never sends a batch. Prepared work reports
MEMBERSHIP_PRODUCTS_NOT_DISPATCHED; only the identical original call may dispatch
unsent work after checks. Sent work is read-only on every replay, including after
an explicit rejection. Matching recovery returns MEMBERSHIP_PRODUCTS_RECONCILED.
Do not create a new request ID to bypass uncertainty. Completed records are historical.
One canonical console URL opens the same membership product list: the observed page/filter for a plan and page 1 of the requested state after completion. It does not select rows or prove those rows occur on page 1. Read the appropriate current page to continue. Restoration requires a fresh observed page/revision and a new explicit task; it is not an automatic rollback. Demo has no actual console link.
## Edit a product's displayed alias and purchase methods
Use the same current sourcePage and productPageRevision. Choose `action.kind:edit`,
one numeric subscriptionProductId from that page, and only the intended patch.
```json
{
"requestId":"product-editor-001",
"channelCode":"selected-channel-code",
"membershipMid":"returned-membership-mid",
"sourcePage":{"page":1,"limit":15,"isDeactivated":false},
"expectedPageRevision":"0000000000000000000000000000000000000000000000000000000000000000",
"action":{"kind":"edit","subscriptionProductId":11,"patch":{"alias":"Member offer","availablePurchaseMethodsType":"CASH_AND_CP"}},
"dryRun":true
}
```
Alias is trimmed, then checked against the original 4–20 UTF-16-unit bound; two supplementary-plane emoji occupy four units. Omitted alias stays unchanged. Only ONCE_FINITE and ONCE_INFINITE permit an explicit purchase-method selection. Omit it for other types, whose source controls are locked/hidden. CASH_AND_CP requires fresh valid Channel Point branding during both preparation and pre-dispatch checks. Missing/failed branding does not enable the option. This does not create point branding, charge points or move funds.
The source editor locks the product title, base price, type and period; these
cannot be supplied in patch. The workflow preserves their observed values in the
same full update body used by the source submission. The old API DTO names only
alias, while the active editor/saga spreads the full preserved fields and purchase
selection. Title/type/fee/finite period are composed from current state, never
caller replacements. A free product omits fee as the source does. Unknown editor
purchase methods are not silently replaced with CASH_ONLY; list/detail consistency
must be established before a change. The selling-state plan/trial gate is not
added to this editor, matching the source screen.
The tool validates the exact selected-ID receipt and independently reads its
requested alias/purchase methods plus preserved fields. Results and taskType remain
MEMBERSHIP_PRODUCTS_UPDATED/UNCHANGED/RECONCILED and membership-products. No-op
sends no PUT; dry run saves no new task. Sent editor work is never resent, even if
an invalid receipt or response loss occurs. A prepared task can be sent only by
the identical original apply call. Completed history remains historical.
The single destination is the selected product editor, not the product list.
Restoration requires fresh page/revision and a new explicit task. Receipt validation
and readback do not prove causal ownership, atomicity, visitor checkout or billing.
Product creation with price/period choices uses [membership_product_create](membership_product_create.md); extension pricing is supported below; source-locked existing prices are not an alternative route.
## Configure the product's extension offer
Select one ID from the same observed page. Use `action.kind:extension` and the
complete source wizard settings; this supports both first setup and editing.
```json
{
"requestId":"product-extension-001",
"channelCode":"selected-channel-code",
"membershipMid":"returned-membership-mid",
"sourcePage":{"page":1,"limit":15,"isDeactivated":false},
"expectedPageRevision":"0000000000000000000000000000000000000000000000000000000000000000",
"action":{"kind":"extension","subscriptionProductId":11,"settings":{
"isEnabled":true,"name":"Extra membership","feeAmount":"700",
"originalFeeAmount":"1000","extensionPeriodInDays":30,"extensionLimit":5,
"purchaseMethodsType":"CASH_ONLY","extendableBeforeExpiryDays":7
}},
"dryRun":true
}
```
Only ONCE_FINITE, COUPON_ONCE_FINITE and COUPON_FREE_FINITE expose this source
quick action. Name is trimmed and 3–20 UTF-16 units. Period and days before expiry
are integers 1–365. null days means immediate purchase. Limit is 1–100 or null for
unlimited. isEnabled false disables the offer while retaining complete settings;
it does not delete the extension record or undo an actual member's extension.
Amounts are decimal strings without grouping in the observed channel base
currency. The source extension selling-price policy accepts KRW 300–10,000,000
in 100-unit steps and USD 5–10,000 with up to two decimal places, with the exact
source per-channel exceptions. The optional original-price control uses a
separate legacy minimum map and no selling-price step validation. It must be at
least the selling price; omission composes the selling price as the original
price, matching the wizard. Supply base-currency amounts, not a display currency
conversion. Other base currencies are outside the current membership contract.
CASH_AND_CP requires fresh observed branding in both preparation checks. Source
extension controls have no extra selling-plan/trial gate; none is invented.
The full settings enter one POST set-extension. Its exact product receipt must
preserve the base product, then a fresh detail and the same list page must agree.
All extension fields must be present after saving, including disabled or null
limits/timing. Missing extensionFee before saving is unknown; explicit null means
unconfigured. Existing settings are never guessed from UI fallback defaults.
Equivalent decimal trailing zeros are normalized for extension-price comparison.
The base product's title, alias, price, duration, type, selling state and purchase
methods remain unchanged, as does the observed parent membership.
Result extensionSettings contains the confirmed configuration; existing
MEMBERSHIP_PRODUCTS_* codes and taskType membership-products apply. Unchanged
settings send no POST. A sent task only reads on resume, including after a lost
response or wrong receipt. A missing row on the original page remains unverified;
the bridge does not search other pages or resend. Prepared work needs the exact
original apply input. Completed tasks return history. Restoring prior configured
settings needs a fresh page/revision and a new requestId. First setup has no source
delete/reset API; disabling does not restore an unconfigured null record.
The single console URL and next read retain the observed page and filter, even
on later pages. The source modal has no deep link; open its extension quick action
on that product row. This configures an offer, not a member purchase or extension,
payment, refund, settlement, fund movement or service billing.