Refunds

Refunds you didn't see: catching cancellations on already-delivered orders

You wake up. Coffee. Open your eBay messages. There's a polite "where's my refund?" from a buyer for an order your sheet shows as Delivered three days ago. You check AliExpress — the AE order is also Delivered. You check your bank statement — yep, AE refunded the buyer six days ago. You missed it. Your sheet is wrong.

This happens because of a specific gap in AliExpress's tracking data: the tracking status doesn't get updated when a refund happens. The order stays "Delivered" forever. The refund lives in a completely different part of the API response, and most tracking tools never look at it.

Where refunds actually live in the AE response

If you query aliexpress.trade.ds.order.get for a delivered-but-refunded order, the structure you'll get back looks something like this:

  • order_status: FINISH (looks fine)
  • logistics_status: BUYER_ACCEPT_GOODS (looks fine)
  • gmt_refund_payment_finish: a timestampthis is the signal
  • child_order_list[].end_reason: sometimes contains SECURITY_CANCELED, RISK, or BUYER_CANCEL
  • issue_status: sometimes contains IN_ISSUE if a dispute is open

Any one of those five signals being present means there's a refund or dispute on the order — even if the tracking status still says Delivered. Your refund-detection logic needs to check all five, not just the obvious ones.

The four refund states worth distinguishing

Once you're parsing all those fields, you'll quickly discover refunds aren't binary. There are at least four states worth tracking separately:

  1. Refunded — money has been returned to the buyer. gmt_refund_payment_finish is populated.
  2. Refund Processing — AE has approved the refund but the money hasn't moved yet. Look for FUND_PROCESSING in the status fields.
  3. Cancelled — order ended before shipment. end_reason contains a cancel-flavoured value (CANCEL, SECURITY, RISK, CLOSED, TIMEOUT).
  4. Dispute / Issue — open buyer dispute that may or may not resolve in your favour. issue_status contains IN_ISSUE or IN_FROZEN.

The "end_reason" trap

The single biggest gotcha: AliExpress sets child_order_list[].end_reason for successfully delivered orders too. If you treat "any non-empty end_reason" as "cancelled", you'll mark every delivered order as cancelled. Filter for specific cancel-flavoured strings only.

Watch out for these end_reason values that mean "successfully completed", not "cancelled":

  • BUYER_ACCEPT_GOODS
  • AUTO_FINISH
  • PRODUCT_NORMAL_FINISH

The "false cancellation on delivered order" pattern

The most confusing pattern we've seen: AE returns order_status = FINISH, logistics_status = BUYER_ACCEPT_GOODS, but child_order_list[0].end_reason = CANCELED. The order is delivered. There's no refund. The "CANCELED" end reason is legacy data from a dispute that was resolved in the seller's favour.

The fix is an early-exit check: if order_status is FINISH and logistics_status indicates buyer receipt and there's no gmt_refund timestamp, the order is delivered. Trust those two parent fields over the child end_reason.

Surface it in your sheet

Internally you want all four refund states (or more). For the spreadsheet column, we recommend collapsing to binary Yes / No — that's what dropshippers actually act on. "Was this order refunded? Yes or no." If the answer is Yes, the seller checks the row and decides what to do. The internal four-state value is still used to decide whether to override the Tracking Status column to "Cancelled & Refunded", but the buyer-facing column stays simple.

How often to recheck

Refunds can happen weeks after delivery, especially on disputes. Recheck the Refund column whenever the row is touched, even on orders that have been Delivered for a while. The marginal cost is one extra AE API call per order per run. The avoided cost — finding out about a refund from an eBay buyer message — is much higher.


Let the fetcher do this More guides