> For the complete documentation index, see [llms.txt](https://docs.peakcommerce.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.peakcommerce.com/product/journeys-and-pages/journey-steps/commerce-actions.md).

# Commerce Actions

**Commerce actions** are the operations a journey step performs when a customer advances — the work behind the screen. A single step can run a fraud check, create or update an account, create an order, convert a lead, capture a payment method, record the order in PeakCommerce, and more. Each step runs its actions **in order** the moment the customer moves forward.

Open them in the journey builder: **Content → Journeys → Edit → Steps**, expand a step, and look under **Commerce actions**.

{% embed url="<https://youtu.be/JRjwXKeIa5c>" %}

## Anatomy of an action

Each action has:

* **Target** — where it runs:
  * **Internal** — runs inside PeakCommerce with no external integration attached (`integrationId` stays null). These are PeakCommerce's own writes — for example **create organization**, **update contact**, and **record order**.
  * **Integration** — calls a connected system (Zuora, Salesforce, SEON, …). You pick the **Integration** and the **Operation** it performs.
* **Operation** — the specific operation to run. Common operations include:
  * `create_order` — place a new order / subscription. Has an [Honor order grouping](#honor-order-grouping) option.
  * `update_order` — amend an existing subscription (used by **change-plan** steps; targets an account + subscription).
  * `cancel_order` — cancel a subscription via the Orders API, so the cancellation is recorded as an order that can carry custom fields mapped from context (for example the captured cancellation reason → `CancellationReason__c`).
  * `apply_retention_offer` — apply a retention **Offer** to an active subscription; the committing action behind retention save discounts (see [below](#apply-a-retention-offer)).
  * `update_account`, `fraud_check` (integration actions), and the internal `create organization`, `update contact`, and `record_order`.
* **Execution mode**:
  * **Blocking** — must finish before the next action starts. Use when a later action depends on the result (you can't create an order before the account exists).
  * **Async** — dispatched in turn without holding up the next action. Use for fire-and-forget side effects.
* **Order · Condition** — the action's position in the run order, plus an optional **condition** that decides whether it runs (for example *always*, or only when a given field is present).
* **Field mapping** — how the operation's inputs are filled (see below).

## Run order

Actions run **top to bottom**. Drag the handle to reorder them. Because **blocking** actions must complete before the next begins, order matters — a typical checkout step runs fraud check → create account → create order → … Async actions are dispatched in turn and don't hold up the sequence.

## Field mapping <a href="#field-mapping" id="field-mapping"></a>

Each action maps **source values** into the operation's **destination fields**:

* **Source — where the value comes from:**
  * **Context** — a value produced earlier in the journey (a previous step's output variable).
  * **Literal** — a fixed value you type.
  * **Dynamic / Expression** — a computed value.
  * **Object / JSON** — a structured value.
* **Destination** — the operation field the value fills.

For example, the internal **record order** action maps `totalRecurringCents → netMrrCents`, `email → customerEmail`, and a literal `journey → source`. The builder validates these routes, so the operation's required destination fields must be supplied before you can save the action.

{% hint style="info" %}
**A literal is changed here; a context value is changed where it is produced.** If a field is mapped from **Context**, editing the action will not change it — find the earlier step that produces that variable. If it is a **Literal**, this is the only place it is set.

This matters most for identifiers you paste in from another system. A literal Salesforce `OwnerId` or `RecordTypeId` copied from a **sandbox** org will not exist in production, and the write fails with `INVALID_CROSS_REFERENCE_KEY`. Salesforce record ownership in particular is settable in two unrelated places — see [Record owner means two different things](https://gitlab.com/peakcommerce/peak-help-docs/-/tree/main/integrations/salesforce-integration/connecting-salesforce-with-a-connected-app.md#record-owner-two-places).
{% endhint %}

## Required actions (locked) <a href="#required-actions" id="required-actions"></a>

Some actions are **required by the step's role** and appear **locked** with a 🔒 **Required** badge. You can **drag to reorder** them, but you **can't remove** them.

Required actions guarantee that PeakCommerce always performs its own internal writes — creating the organization, updating the contact, and recording the order — so your PeakCommerce records stay correct and complete **regardless of what the external billing system does or returns**.

"Required" is a posture the step's **role** assigns: when you give a step a role (for example **Payment**), the role brings its mandated set of internal actions with it, already configured and locked. See [Step Roles](/product/journeys-and-pages/journey-steps/journey-step-types.md).

## Internal and integration actions — why both

PeakCommerce models commerce **natively** and also syncs to your connected systems of record. The **integration** actions push to whatever billing, CRM, and risk systems you've connected — for example creating the subscription in your billing system (such as Zuora or Stripe), creating the account in your CRM (such as Salesforce), or running a fraud check (such as SEON). The **internal** actions make sure PeakCommerce's own order, contact, and organization records are written authoritatively. Together they keep PeakCommerce and your downstream systems in lockstep — and let the journey keep working even while a given integration is still being deepened.

## Apply a retention offer <a href="#apply-a-retention-offer" id="apply-a-retention-offer"></a>

`apply_retention_offer` is the operation that turns an accepted retention offer into an actual discount on the customer's subscription — the **committing** action of a retention journey. It runs against your billing integration and typically maps three inputs: the **offer** (from the Offers catalog), the **subscription** to discount, and the **account** the grant is recorded against.

What matters is what it does *around* the billing write:

* **The offer is the source of truth.** The action resolves the Offer's definition — discount type/value, duration, and its pinned discount instrument (a catalog rate plan) — so the terms live in one place. Change the offer, and every journey using it changes with it. The catalog must be synced for the instrument to resolve.
* **Live conflict / stacking gate.** Immediately before applying, the action reads the subscription's *current* discounts from the billing system and checks them against the offer's conflict rules. This catches discounts added by any other channel (finance, CPQ, mobile) between the moment the offer was shown and the moment it was accepted. The gate **fails closed**: if the subscription can't be read, nothing is applied.
* **Per-customer usage caps.** The account mapping is mandatory because every confirmed grant is written to the offer's **usage ledger** — that ledger is what enforces **Max Uses Per Customer** and **Cooldown** on future grants. No account, no apply.
* **Trial-aware activation.** On a trial subscription the discount can defer to the paid start; the action's result carries the activation date so the confirmation page can say when the discount begins.

If any guard fails — offer not found, instrument unresolved, conflict blocked, billing rejection — the action fails with the specific reason, and the failure is visible in the action trace like any other (see [Failure routing](#failure-routing)).

Offer definitions, caps, conflict scopes, and instruments are managed under **Commerce → Offers** — see [Offers & Discounts](/product/using-peakcommerce/products-and-bundles/offers-and-discounts.md).

## Save and assign a payment method <a href="#payment-methods" id="payment-methods"></a>

Saving a card and deciding which card pays are two separate actions, so a journey can do one without the other:

* **`add_payment_method` saves the card.** It attaches the payment method your payment form created (the Zuora hosted page or Stripe's card form) to the customer's account in your billing system. It does not change which card is charged. On Zuora, map **Payment Method ID** to the id the hosted page returned.
* **`assign_payment_method` decides which card pays.** Map the account, the saved **Payment Method ID**, and **Assign To**:
  * **account** makes it the card for the whole account. Every subscription that doesn't have its own card uses it.
  * **subscription** makes it the card for the one **Subscription ID** you map, and leaves the account's card and every other subscription alone. This works only where your billing system supports it, checked the same way as [Settings → Payment Method Updates](/product/settings/organization/payment-method-updates.md). Today that is Stripe. Where it isn't supported, or can't be checked right now, the action fails and says so. It never updates the whole account instead.

Both actions only change an account the journey is allowed to change: the customer's own, or one the journey just created. A refusal from your billing system fails the action with that system's reason, including Zuora answers that report success at the HTTP level but `success: false` in the body.

## Add or edit an action

* **Add action** — at the bottom of a step's action list. Choose the target, operation, and execution mode, then map the fields.
* **Edit action** — opens the action editor for an existing action.
* Required (locked) actions can be reordered and re-mapped, but not deleted.

Click **Done**, then **Publish** the journey to make your changes live.

## Test an integration action

Field mappings usually fail at the worst possible moment — on a live customer's order. The **Test integration** panel lets you catch those failures in the editor instead: it resolves your mappings into the exact payload the operation would send and validates it, **without creating anything** in the connected system.

Open the action editor for a saved integration action and expand the collapsible **Test integration** panel at the bottom ("resolve the payload & catch bad picklist values — no order required"):

1. **Sample context (JSON)** — the pretend journey context the test resolves context-sourced mappings against. Click **Reseed from mappings** to generate a starter object with one entry per context mapping, then edit the values to match a realistic session. Literal mappings don't need context — they are **validated regardless** of what you put here.
2. **Run test** — resolves the **current (unsaved) mappings**, so you can iterate without saving between attempts.
3. Review the results:
   * **Resolved payload** — the full payload the operation would send, so you can see exactly what each destination field receives.
   * **Validation warnings** — values that would be rejected by the CRM's **restricted picklists**. Each warning names the **field**, the offending **value**, and the **allowed values**. For example, with Salesforce a literal `Abandoned` mapped into a restricted lead-source field is flagged before any order ever runs. Only *restricted* picklists are enforced — unrestricted picklists accept arbitrary values, so validating them would produce false positives.
   * A green confirmation when no violations are detected.

The test is read-only end to end: no order, lead, or record is created or modified in the integration.

## Honor order grouping (create order) <a href="#honor-order-grouping" id="honor-order-grouping"></a>

The Zuora **create order** action has one option of its own, shown below **Order · Condition** in the action editor:

| Field                    | Type     | Default | What it does                                                                                                                                                                                                                   |
| ------------------------ | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Honor order grouping** | Checkbox | Off     | **On:** lines set to **Its own order line** in the product set are each booked as their own order action (an **Add Product**) in this order. **Off:** every line is booked in one order action, whatever the product set says. |

What to know:

* **It is still one order and one subscription.** Grouping changes how the order is recorded, not how it is invoiced.
* **Publish after changing it.** The checkbox is part of the published journey. The product set's [Order grouping](/product/using-peakcommerce/products-and-bundles/product-sets.md#line-booking-settings) values are read live when checkout starts, so changing those doesn't need a publish.
* **Lines must come from a product set.** A create-order action whose rate plans are mapped by hand, with no product set behind the checkout, books everything in one order action even with the box checked.
* **Per-line start dates need activation offsets.** Each line's **Effective Date Timing** is applied only when the action also has activation offsets turned on. There is no switch for that in the editor today; your PeakCommerce team turns it on. Without it, every line starts on the order date.
* **Available on Zuora**, the integration that places orders today.

For how the order is built, with a worked example, see [Guide: Booking a Line as Its Own Order Action](https://docs.peakcommerce.com/guides/feature-guides/billing-setup/guide-booking-a-line-as-its-own-order-action).

## Failure routing <a href="#failure-routing" id="failure-routing"></a>

By default, a failing **blocking** action **halts the step**: the visitor stays where they are and sees the action's failure message. That's the right default for anything a later action depends on. But some journeys want a softer landing — for example routing the visitor to a "we'll follow up" page when a non-essential write fails, instead of dead-ending them. Failure routing gives you that, per action and per step:

* **Per action — "Continue on failure".** Each blocking action (in its editor) has a **Continue on failure** checkbox. When checked, a failure of *this* action does not halt the step — the advance completes and routing follows the step's failure target (below). The exception is `fraud_check`: a fraud block is a security decision and **always halts** — the checkbox isn't offered.
* **Per step — the failure target.** The step's **Branching** panel has an **"On commerce-action failure (optional)"** selector: **Halt the step (default)** or **Go to #N \<step>**. It only takes effect when the failing action has Continue-on-failure enabled — without that opt-in, a failed action always halts, whatever the selector says.

Two things to know:

* **Failures stay visible.** Continue-on-failure changes where the *visitor* goes, never what gets recorded. The failure is still written to the action trace and surfaced in API Call Stats, exactly as if it had halted — routing around a failure never hides it.
* **The failure target is a real branch.** Journey validation treats the on-failure target as a genuine edge in the flow graph: it keeps a dedicated failure page counted as reachable, and it gets the same broken-target checks as any other route — deleting the target step flags the configuration instead of leaving a dangling path.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.peakcommerce.com/product/journeys-and-pages/journey-steps/commerce-actions.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
