Skip to content

Refunds & cancellations

When an order is cancelled or refunded, Assemblified can either break the assembled units back into raws (return components to inventory) or keep them assembled (return them to the pre-assembled shelf). The choice is controlled by the Keep Assembled on ReturnKeep Assembled on ReturnA toggle on BOMs and sub-assemblies that controls cancel/refund behavior. When on, returned units restore the entity's pre-assembled shelf instead of breaking back into raws. Cascades through nested sub-assemblies — traversal halts at any node with the flag set. Read more → setting — and it cascades through nested sub-assemblies in a non-obvious way.

This page explains the cascade rule with worked examples.

  • The Keep Assembled on Return setting
  • The three-branch behavior table
  • Partial refunds and the netting rule
  • Full cancellations
  • The kill-switches (when to disable cancel/refund processing globally)

Each BOM has a Keep Assembled on Return toggle in the Assemble Settings card.

  • On. Cancelled or refunded units go back into this BOM’s pre-assembled stock instead of being broken back down into raw materials.
  • Off. Cancelled or refunded units are broken back down into raw materials, while nested sub-assemblies can still keep their own assembled stock if flagged.
  • Default: Off.

Sub-assemblies have an independent flag with the same name. The interaction between them is what makes this surface tricky.

For a BOM B containing a sub-assembly SA, where each entity has its own flag:

Parent BOM flagSA flagWhat happens on cancel/refund of N units
On(any)Nothing decomposes. BOM B’s pre-assembled shelf gets +N. Raws stay inside the assembled units. SA’s stock unchanged.
OffOnSA stays assembled, parent’s direct raws return. SA’s pre-assembled shelf gets +N×qty (where qty is the SA’s quantity multiplier in the parent). The parent BOM’s direct raw materials (not those inside the SA) return to inventory.
OffOffAll raws return. Both the parent’s direct raws and the SA’s nested raws come back to inventory. The recursion continues into nested sub-assemblies unless one of them has the flag.

The rule, in plain English: traversal halts at any node with the flag set. A BOM with the flag on stops everything. A BOM without the flag traverses; if it hits a sub-assembly with the flag, it stops there.

Set up:

  • BOM B contains 3 × Raw R1 directly + 1 × Sub-assembly SA.
  • SA contains 5 × Raw R2 + 2 × Raw R3.

Customer orders 4 units of B. Then cancels.

ConfigurationWhat restores
B flag on, SA flag (any)B’s pre-assembled +4. R1, R2, R3 unchanged. SA’s pre-assembled unchanged.
B flag off, SA flag onR1 +12 (4 × 3). SA’s pre-assembled +4 (4 × 1). R2, R3 unchanged.
B flag off, SA flag offR1 +12. R2 +20 (4 × 1 × 5). R3 +8 (4 × 1 × 2). Pre-assembled unchanged.

This is tested in the e2e suite (07-sub-assembly-keep-assembled.spec.md).

Refunds and cancellations are processed independently. Each refund:

  • Walks the cascade rule for the refunded quantity.
  • Logs a row to refundHistory[] for that order.
  • Restores raws or pre-assembled per the cascade.

If you refund 3 units of a 10-unit order, then refund 2 more, you’ve now refunded 5 units total. Each was processed when it happened.

When a customer fully cancels an order after one or more partial refunds, Assemblified nets the prior refunds against the original order quantity:

  • Original order: 10 units.
  • Refund 1: 3 units (already restored at refund time).
  • Cancel: line item shows 10 units.
  • Net to restore on cancel: 10 − 3 = 7.

Without this netting, the 3 already-refunded units would be double-counted. With it, the math is clean: the customer’s order goes from 10 → 0 in inventory effects.

Worked into the cascade: if Keep Assembled is on, the BOM gets +7 to its shelf (not +10). If off, raws restore for 7 units (not 10).

There are two shop-level settings that disable cancel and refund processing globally:

  • Disable cancel handler. Order cancellations are still received as webhooks but produce no inventory side-effects.
  • Disable refund handler. Same, but for refunds.

Both are settings on the user_settings level (admin-only). They’re meant for incidents:

  • A buggy webhook payload that’s corrupting state.
  • A flood of refunds in an unexpected pattern.
  • A planned data migration where you want webhooks to stop affecting state.

After re-enabling, you’ll need to manually reconcile any orders that occurred during the kill-switch window.

If you have the Logistified integration (Enhanced subscription), the demand side always sees the full raw-material requirement on cancel/refund, regardless of the Keep Assembled flags. The reasoning: demand tracking is in absolute terms — the customer’s order isn’t going to be fulfilled, so the demand should drop. Whether the physical inventory comes back is a separate decision (the cascade rule).

So a cancel with Keep Assembled on:

  • Physical inventory: unchanged (units stay assembled).
  • BOM pre-assembled shelf: +N.
  • Logistified demand: -full raw requirement (as if the units would have been built from scratch).

This usually behaves correctly. The asymmetry only matters when reconciling cross-system reports.

  • Cancel without a prior create log. If the original execution log is missing (old order, deduped webhook, BOM was inactive at the time), cancellation can produce a +N to pre-assembled with no offsetting -N. The shelf clamps at zero, but you’ll see “free” inventory appear. Audit your missing-log warnings.
  • Setting the parent flag on after a cancel happened with it off. The flag is read at execution time. A cancel processed with the flag off applies that branch of the cascade; flipping the flag later doesn’t retroactively change anything.
  • Per-component locations matter on restore too. Raw materials that were consumed from a specific location restore to that location.
  • Refund webhook subscription. Shopify only fires ORDERS_UPDATED (Assemblified registers this one topic). The disambiguator detects refund vs cancel from payload content. So there’s no separate “refunds” event stream — they all flow through the same pipeline.