Skip to content

Powered by Grav CMS + Helios Theme

Order Changes

You edit a placed order by staging a change and confirming it, never by recalculating it in place. This page is for a store administrator editing orders, taking returns, or deciding whether to turn the feature on.

Before you begin

  • Order changes need the order_versioning feature switched on. See Turn order versioning on.
  • Changing an order needs kahunacart.orders.manage.

Staged, then confirmed

A change is a proposal. Opening one writes a row in kahunacart_order_changes. Adding actions to it writes rows in kahunacart_order_change_actions. The order's lines, adjustments, totals and statuses stay exactly as they were until somebody confirms.

A change that is never confirmed changes nothing. You can build one up over several sessions, look at the previewed total, and walk away.

Confirming appends and never removes. It increments the order's version, copies every current line and adjustment into the new version, applies the actions to the copies, and leaves the previous version's rows untouched. Version 1 reads back unchanged however many edits follow.

Important

There is no Recalculate button that rewrites a placed order in place. Recalculating a paid order as though it were a cart is how line items get silently deleted from orders somebody has already paid for.

The action vocabulary

A change is built from typed verbs. Which verbs are legal depends on the kind of change.

Verb What it does
add_item A line the order did not have
write_off_item Take a line out of the current version. The original line stays readable at its own version.
reinstate_item Put a written-off line back, at the price it was bought under
update_quantity Change how many of a line there are
update_price Change what a line's units cost
add_adjustment A discount, a fee, a manual credit
remove_adjustment Take an adjustment out of the current version
update_shipping Change what postage costs
update_address Correct a billing or shipping address
update_tax Correct the tax
add_payment Record money taken outside KahunaCart for the difference
refund_payment Money going back, through the existing refund path
return_item Units of a line coming back

There is no delete_item. Removing a line is write_off_item, which means "do not carry this line into the new version". The line itself is never written to and never deleted.

Kind of change Verbs it may use
edit Everything except return_item
exchange Everything
claim Everything
return_request return_item, add_adjustment, refund_payment
return_receive return_item, write_off_item, add_adjustment, refund_payment

Edit a placed order

  1. Open the order in KahunaCart → Orders.
  2. Click Stage a change, pick Edit, and give a reason.
  3. Add actions. Each one updates the previewed totals beside the change. Nothing is written to the order yet.
  4. Read the preview. It runs the store's real pricing pipeline over the projected order, so the total shown is the total a confirm produces.
  5. Click Confirm change.

The order moves to the next version, its lines and adjustments are the new version's copies, and an order.changed entry appears on the order's history naming you.

To abandon the change instead, click Discard change. The change and its actions stay on the record so a withdrawn proposal can still be read.

In another currency

Every amount in a change is in the order's own currency, and the preview and the confirmed version are too. A yen order's change reads in whole yen and a euro order's in euro, and the admin reads a typed amount with the order's currency's decimal places: 1500 is 1500 yen, 12.50 is 12.50 euro, and an amount with more decimals than the currency has is refused before anything is sent.

On a store that sells in more than one currency an add_item that names a variant_id is priced for you, in the order's currency, from the product's amount there. The admin offers the catalogue in a list that shows each product's price in the order's currency, and a product with no price in it is greyed out with "Not sold in EUR". A product with no price in the order's currency cannot be added, and the server refuses it with 422 even when a price is typed, because a typed price would still be selling something nobody priced for that currency. A price you type for a product that does have one wins over the catalogue's, and a custom line (no variant_id) is priced by the amount you send. The snapshot kept on the line says which currency its price was in.

A store with one currency prices nothing for you, as before. The price is whatever the request carries.

Read an order at an earlier version

  1. Open the order.
  2. Choose a version from the version selector beside the order number.

The lines and adjustments shown are the ones the order carried at that version, including lines a later change wrote off. Historical versions are read-only.

GET /kahunacart/orders/{id}/versions/{v} answers the same thing.

Unlock a closed order

An order whose status is marked is_final (canceled, archived, or anything you flagged that way) needs unlocking before a change can be staged against it.

  1. Open the order.
  2. Click Unlock for editing and give a reason.

The unlock is never refused. It writes an order.unlocked entry on the order's history naming whoever pressed it. Adding a forgotten line to a closed order never means raising a second order; it costs one audited action that somebody owns.

Returns

Returns are core. A return is a change of type return_request or return_receive.

return_request records what the customer says is coming back. It moves no stock and no money.

return_receive records what actually turned up. Each return_item action carries two quantities.

Field Meaning
received_qty How many units turned up
damaged_qty How many of those are damaged

Damaged units do not go back into stock. Receiving 2 of 3 with 1 damaged puts exactly 1 unit back on the shelf. The restock goes through the same atomic stock path every refund restock uses, and it only touches variants that manage stock.

Refunding the return uses the existing refund path unchanged: the same allocator, the same per-line quantities, the same provider call. See Refund an order.

Note

The customer-facing side — the return form on the order page, approval, return numbers and labels — is Returns. It records a return_request change when you approve a return and a return_receive change when you check its parcel in. Those two are recorded in place: a change made only of return_item actions edits no line, so it writes no new version of the order, every line keeps its id, and it works whether or not order versioning is on and without unlocking a closed order. OrderChangeService::recordReturn() is that one-step door, for an add-on that receives goods some other way.

A return holds the order's lines. While a return on the order is open, approved or received, confirming any other change is refused with a message naming the return ("Return R1042-1 is still active on this order…"), because a confirmed change writes a new version and gives every line a new id, and the return and the refund that follows it name the ids the customer sent back. The change stays staged: confirm it once the return is refunded, declined or cancelled. The return's own in-place records are not affected.

Turn order versioning on

Order changes are off until you turn them on. KahunaCart refuses to turn them on while an installed add-on has not declared that it understands versioned orders.

  1. Go to KahunaCart → Settings → Features.
  2. Read the blocking list beside Order versioning. It names any installed KahunaCart add-on that has not declared support.
  3. Update or switch off the plugins it names.
  4. Click Turn on.

A store with no add-ons has nothing blocking it and turns it on in one click. Switching a feature off is never refused.

Warning

An add-on that reads kahunacart_order_items or kahunacart_adjustments directly must filter on superseded_by_version IS NULL or it reads an edited order's lines twice. Everything that goes through OrderRepository already does. This is what declaring compatibility with order_versioning asserts.

An add-on declares support on onKahunaCartDeclareFeatureCompatibility:

PHP
public function onKahunaCartDeclareFeatureCompatibility(Event $event): void
{
    $event['features']->declare('kahunacart-licenses', 'Licenses', [
        'order_versioning',
        'order_status_registry',
    ], '1.2.0');
}

An add-on that declares nothing blocks every feature. An add-on written before a feature existed cannot have declared for it, and silence is not treated as consent.

Configuration

Config key Default What it does
features.order_versioning false Whether a fresh store starts with staged order changes available. Your choice on the Features screen is stored in the database and wins over this.

Events

Event Payload When
onKahunaCartOrderChangeStaged order_id, change_id, change, action A change was opened, or an action was added to one. Nothing has been written to the order.
onKahunaCartOrderChangeConfirmed order_id, change_id, change, version_from, version_to, order, restocked A change was applied to a new version
onKahunaCartDeclareFeatureCompatibility features, codes Declare which core features your add-on understands

API and MCP tools

Route Tool Permission
GET /kahunacart/orders/{id}/changes list_order_changes kahunacart.orders.view
POST /kahunacart/orders/{id}/changes create_order_change kahunacart.orders.manage
POST /kahunacart/orders/{id}/changes/{cid}/actions add_order_change_action kahunacart.orders.manage
POST /kahunacart/orders/{id}/changes/{cid}/confirm confirm_order_change kahunacart.orders.manage
POST /kahunacart/orders/{id}/changes/{cid}/cancel cancel_order_change kahunacart.orders.manage
GET /kahunacart/orders/{id}/versions/{v} get_order_version kahunacart.orders.view
POST /kahunacart/orders/{id}/unlock unlock_order kahunacart.orders.manage
GET /kahunacart/features list_features kahunacart.orders.view
POST /kahunacart/features/{code}/enable set_feature kahunacart.orders.manage

Confirming is idempotent. A repeated confirm_order_change answers with applied: false, the version is not bumped twice, and no unit is restocked twice.

Confirmation locks and reads the order again on each transaction attempt. If another confirmation changes the order while a failed attempt is retried, your change stays staged. Review it and try again.