Short answer
Treat cancellations, refunds, and returns as three separate events. A cancellation stops fulfillment and releases stock; a refund is a financial document (usually an ERP credit note) that may or may not involve goods; a return moves goods back into stock after inspection. Decide which system initiates each one, map refund lines and amounts exactly, make credit notes idempotent by refund ID, and reconcile refund totals daily.
Refunds and cancellations are where integration logic meets accounting rules. Getting them wrong does not just produce an operational annoyance — it produces revenue figures, VAT returns, and stock values that do not match reality. This guide separates the events, explains the decisions you must make, and shows how to implement them safely.
Separate three different events
| Event | What happens | Typical ERP document | Stock effect |
|---|---|---|---|
| Cancellation | Order will not be fulfilled (fully or partly) | Cancel or close the sales order | Release reservation |
| Refund | Money returned to the customer | Credit note (credit memo) | None by itself |
| Return | Goods come back | Return receipt / return order | Increase stock after inspection |
These often occur together — a customer returns an item and receives a refund — but they happen at different times and in different systems. Modeling them as one event is the root of most refund integration problems.
Decide where each event starts
A sensible default for direct-to-consumer stores:
- Cancellations before shipment start in the ecommerce platform (customer service cancels the order). The integration cancels the ERP order if it has not been released to the warehouse; otherwise it raises a review item so the warehouse can stop the shipment.
- Refunds start in the ecommerce platform, because the payment was captured there and refunds must return through the same payment provider. The integration creates a credit note in the ERP.
- Returns start in the warehouse or a returns platform. The ERP books the receipt after inspection, and stock flows back to the store through the normal inventory sync.
B2B and invoice-based businesses often reverse this: credit notes start in the ERP and the store is only informed. Whatever you choose, write it down per event type.
Implementing cancellations
On a cancellation event (for Shopify, the orders/cancelled webhook):
- Fetch the order and confirm it is cancelled (check
cancelledAtand the cancellation reason). - Find the ERP order by external reference.
- If the ERP order is not yet released for picking, cancel it through the ERP’s normal process so reservations are released.
- If it is released or shipped, do not auto-cancel. Create a review item for operations.
- Record the outcome against the ecommerce order.
If the order never reached the ERP (still queued or failed), mark the pending export job as superseded so it is not created afterward.
Implementing refunds
Map the refund precisely
A refund can contain several components. In Shopify, a refund record includes refunded line items with quantities and amounts, and transactions that record the money returned. Your credit note needs to reflect each component:
- Refunded line items → credit note lines with the same article, quantity, and net amount.
- Refunded shipping → a credit line on your shipping service item.
- Order-level adjustments (goodwill credits, price adjustments without returned items) → a dedicated adjustment service item.
- Taxes → copy the refunded tax amounts rather than recalculating, so the credit note matches what the customer received.
- Restock flag → informational. Do not adjust ERP stock from the refund; let the return receipt do that.
function mapRefundToCreditNote(refund: PlatformRefund, erpOrder: ErpOrder): CreditNoteDraft {
const lines = refund.lineItems.map((r) => ({
article: xref.articleForSku(r.sku),
quantity: r.quantity,
netAmount: r.subtotal, // amount excluding tax, as refunded
taxAmount: r.totalTax,
}));
if (refund.shippingAmount > 0) lines.push(shippingCreditLine(refund.shippingAmount));
if (refund.adjustmentAmount !== 0) lines.push(adjustmentLine(refund.adjustmentAmount));
return {
externalRef: `refund:${refund.id}`, // unique, makes creation idempotent
originalOrder: erpOrder.number,
currency: refund.currency,
lines,
};
}
Validate totals
The credit note total must equal the amount actually refunded. Compare against the sum of the refund’s successful transactions. If they differ beyond a rounding tolerance, route to review — do not post a credit note that disagrees with the payment provider.
Make it idempotent
Use the platform’s refund ID as the credit note’s unique external reference. Refunds are typically created once and not edited, which makes this straightforward.
Implementing returns
Returns are mainly an ERP and warehouse process:
- The return is registered (in the store, a returns app, or the ERP).
- Goods arrive; the warehouse inspects them.
- The ERP books sellable units into stock and damaged units into a quarantine or write-off location.
- Inventory sync publishes the new sellable quantity to the store.
If the store also tracks returns (Shopify has return objects, for example), decide whether the integration updates their status from the ERP. Keep the restock decision with the warehouse.
Edge cases to decide up front
- Partial cancellation of some lines: update the ERP order lines, or cancel and re-create? Most ERPs handle line cancellation; use it.
- Refund before the order reached the ERP: hold the refund until the order export succeeds, or post both together.
- Refund larger than the original order line (goodwill): post the excess as an adjustment line.
- Multiple partial refunds on one order: each is its own credit note referencing the original.
- Exchanges: model as a return plus a new order, unless your ERP has a native exchange document.
- Chargebacks and disputes: these come from the payment provider, not the store’s refund flow. Handle them in finance processes, not the order integration.
Security considerations
- Refund and credit note creation moves money in your books. Restrict which credentials can create credit notes and log every one with the source refund ID.
- If the ERP can trigger refunds on the platform, require an approval step and set amount limits.
Testing
- Full cancellation before export, after export, and after release to the warehouse.
- Partial refund of one line; refund of shipping only; goodwill adjustment with no items.
- Two partial refunds on the same order.
- Same refund webhook delivered twice.
- Refund for an order whose export is still failing.
- Return received damaged, with no restock.
Common mistakes
- Treating refund and return as the same event and restocking on refund.
- Recalculating tax on credit notes.
- Auto-cancelling ERP orders that have already shipped.
- Using the order ID as the credit note reference, so a second partial refund collides.
- No reconciliation of refund totals between platform, payment provider, and ERP.
Summary
Model cancellations, refunds, and returns as separate events with clear starting systems. Cancel ERP orders only before warehouse release, map refunds to credit notes line by line using the refund ID as a unique reference, restock only after inspection, and reconcile refund totals daily. Clear separation makes these flows predictable for operations and auditable for finance.
Frequently asked questions
Is a cancellation the same as a refund?
No. A cancellation is an operational decision not to fulfill. A refund returns money. An order can be cancelled with or without a refund, and refunded partially without being cancelled.
Should refunds be initiated in the ERP or in the ecommerce platform?
Customer-facing refunds are usually initiated in the ecommerce platform because the payment was captured there and the refund must go back through the same payment provider. The ERP then records the credit note. Initiating refunds in the ERP requires the integration to call the platform's refund API and is harder to get right.
How do I handle a refund that includes shipping?
Map the refunded shipping amount to a separate credit note line using your shipping service item, so revenue and shipping income stay correctly separated.
When should returned stock become available again?
After inspection, not when the refund is issued. Many returns arrive damaged or incomplete. Restock based on the warehouse's receipt and inspection in the ERP, then let the normal inventory sync update the store.
Sources and further reading
Platform-specific statements on this page were checked against these sources on . Vendor APIs change; confirm details in the current documentation before you build.
- [1] Refund object — Shopify
- [2] orderCancel mutation — Shopify
- [3] Manage inventory quantities and states (restock reason) — Shopify
- [4] WooCommerce REST API — order refunds — WooCommerce
Related reading
- Guides How to Synchronize Shopify Orders with an ERP How to export Shopify orders to an ERP reliably — choosing the trigger, mapping lines, taxes, discounts and payments, handling edits, and avoiding duplicates.
- Guides How to Prevent Duplicate Orders in an ERP Integration Why ecommerce integrations create duplicate ERP orders — duplicate webhooks, retries after timeouts, races — and the layered defenses that stop them.
- Guides How to Synchronize Inventory Between Shopify and an ERP Keep Shopify stock levels aligned with your ERP — choosing which quantity to publish, mapping locations, using compare-and-swap updates, and reconciling drift.
- Guides How to Synchronize Shipment Tracking Between Shopify and an ERP Send shipments and tracking numbers from an ERP or WMS to Shopify using fulfillment orders — partial shipments, multiple packages, carriers, and customer notifications.
- Concepts What Is Data Mapping in Integrations? What data mapping means in ecommerce-ERP integrations — field, value, and structural mapping — with examples, common pitfalls, and how to document and test mappings.