You’re right! NO_RESTOCK is ambiguous on its own. It doesn’t tell you whether it was a cancellation without restock or just a standalone refund.
The missing piece here is the line item’s fulfillmentStatus. I tested this approach against my test store with 4 different scenarios and it works reliably:
query OrderRefundClassification($orderId: ID!) {
order(id: $orderId) {
id
cancelledAt
cancelReason
refunds {
id
createdAt
return {
id
}
refundLineItems(first: 50) {
nodes {
lineItem {
id
title
fulfillmentStatus # This is the key field
}
quantity
restockType
}
}
}
}
}
Your classification logic would be:
-
Return:
refund.returnis not null, ORrestockType = RETURN -
Cancellation:
restockType = CANCEL, OR (restockType = NO_RESTOCKANDlineItem.fulfillmentStatusis unfulfilled) -
Standalone refund:
restockType = NO_RESTOCKANDlineItem.fulfillmentStatusis fulfilled
I validated this with actual refunds:
-
CANCEL+ unfulfilled → cancellation with restock -
NO_RESTOCK+ unfulfilled → cancellation without restock (your ambiguous case) -
RETURN+ fulfilled → return with restock -
NO_RESTOCK+ fulfilled → standalone refund
Cancellations happen on unfulfilled items, so NO_RESTOCK + unfulfilled = cancellation without restock. If it’s NO_RESTOCK + fulfilled, it’s a standalone refund.
This came up in another thread where someone noted the API doesn’t have full parity with what the admin UI can do for cancellations without restock - the admin uses an internal removal field that’s not exposed publicly. But the fulfillmentStatus approach works for classification.
Let me know if that covers your scenario or if there’s more I can do to help!