Orange Collar Media
Orange Collar Media
DOCS
OCMLABS/MODULE-EXTRA-FEES · OPTIONAL FEES

Optional fees the shopper opts into at checkout, with REST and GraphQL selection

MAGENTO 2.4.7 - 2.4.8 PHP 8.3+ LUMA + HYVÄ COMPOSER

A fee with Is Optional = Yes is offered instead of imposed. It appears in the checkout order summary as an unchecked line with a checkbox and its computed amount; checking it adds the fee and updates the total immediately, unchecking removes it the same way. Nothing waits for Place Order, and there is no default-on behavior - a shopper who never touches the checkbox is never charged.

Checkout order summary with an unchecked optional add-on fee checkbox

Targeting rules still apply

Is Optional adds a choice on top of the normal rules, it does not bypass them. A fee is offered only when it is active, inside its schedule window, scoped to the current store view, and its conditions match the cart. An optional fee whose conditions stop matching (the qualifying item is removed, say) silently disappears from the offer list, and a stale selection is ignored by the totals collector and pruned on the next selection write.

Where the checkbox lives

The selection checkbox exists in the standard Luma/Knockout checkout order summary only. On the cart page a shopper sees already-selected optional fees as read-only lines, the same as any other fee - selections are made and changed in checkout. On Hyva storefronts the checkbox works wherever the Luma-checkout fallback serves the checkout flow; there is no Hyva-native selection UI.

Selection persistence and cart merge

The selection is stored on the quote (quote.ocmlabs_selected_fee_ids, a JSON array of fee IDs), so it survives page reloads within the checkout session. When a guest logs in mid-checkout and their cart merges into the customer’s account cart, the guest’s selections carry over automatically - as long as the customer’s own cart had no selections of its own, in which case the existing selection is kept as-is.

REST selection endpoints

Headless and custom storefronts drive the same behavior over REST:

MethodRoutePurpose
GET/V1/carts/mine/ocmlabs-extra-feesList optional fees currently available to the authenticated customer’s cart, each with its computed amount and selected state
PUT/V1/carts/mine/ocmlabs-extra-feesReplace the selection
GET/V1/guest-carts/:cartId/ocmlabs-extra-feesSame, for a masked guest cart ID
PUT/V1/guest-carts/:cartId/ocmlabs-extra-feesSame, for a masked guest cart ID

The PUT body wraps the IDs in a selection object; an empty array deselects everything:

{"selection": {"fee_ids": [3, 7]}}

Posting a fee ID that is not currently available returns an error naming the offending IDs rather than silently dropping them.

GraphQL

The same surface exists in GraphQL: Cart.ocmlabs_available_optional_fees lists offerable fees with fee_id, label, amount, and selected; the ocmlabsSetSelectedFees mutation ({ cart_id, fee_ids }) replaces the selection and returns the cart with totals recollected; Cart.ocmlabs_extra_fees shows every applied fee including selected optional ones. See Architecture for the full schema.