Purpose
Processes refunds and creates replacement orders without navigating the Shopify admin UI. This skill handles both the refund and the optional replacement draft order in a single workflow.
Prerequisites
- Authenticated Shopify CLI session:
shopify auth login --store <domain> - API scopes:
read_orders,write_orders
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| store | string | yes | — | Store domain (e.g., mystore.myshopify.com) |
| format | string | no | human | Output format: human or json |
| dry_run | bool | no | false | Preview operations without executing mutations |
| order_id | string | yes | — | GID of the order (e.g., gid://shopify/Order/12345) |
| refund_line_items | array | no | all refundable | Array of {line_item_id, quantity} to refund; if omitted, refunds all refundable quantities |
| reason | string | no | other | Refund reason: customer, fraud, inventory, declined, other |
| create_replacement | bool | no | false | If true, create a draft order with the same line items after refund |
| notify_customer | bool | no | true | Send refund notification email to customer |
Safety
⚠️ Steps 2 and 3 execute irreversible financial mutations.
refundCreatecannot be undone — once a refund is processed, the payment cannot be re-captured.draftOrderCreatecreates a new draft order that must be invoiced and paid separately. Run withdry_run: trueto verify the refund line items and amounts before committing. VerifyrefundableQuantityper line item from Step 1 before proceeding.
Workflow Steps
-
OPERATION:
order— query Inputs:id: <order_id>Expected output: Full order withdisplayFinancialStatus,lineItems(withrefundableQuantity),transactions,customer,shippingAddress; verify order is refundable before proceeding -
OPERATION:
refundCreate— mutation Inputs:input.orderId,input.refundLineItems(from parameter or all refundable),input.notify,input.note: <reason>Expected output:refund.id,refund.totalRefundedSet,userErrors -
OPERATION:
draftOrderCreate— mutation (only ifcreate_replacement: true) Inputs:input.lineItems(from original order line items),input.customerId,input.shippingAddress,input.note: "Replacement for order <name>"Expected output:draftOrder.id,draftOrder.name,draftOrder.invoiceUrl,userErrors
GraphQL Operations
graphql# order:query — validated against api_version 2025-01 query OrderForRefund($id: ID!) { order(id: $id) { id name displayFinancialStatus displayFulfillmentStatus totalPriceSet { shopMoney { amount currencyCode } } lineItems(first: 50) { edges { node { id title quantity refundableQuantity variant { id sku price } } } } transactions(first: 10) { id kind status amountSet { shopMoney { amount currencyCode } } gateway } refunds { id createdAt totalRefundedSet { shopMoney { amount currencyCode } } } customer { id defaultEmailAddress { emailAddress } firstName lastName } shippingAddress { address1 city province country zip } } }
graphql# refundCreate:mutation — validated against api_version 2025-01 mutation RefundCreate($input: RefundInput!) { refundCreate(input: $input) { refund { id createdAt totalRefundedSet { shopMoney { amount currencyCode } } } userErrors { field message } } }
graphql# draftOrderCreate:mutation — validated against api_version 2025-01 mutation DraftOrderCreate($input: DraftOrderInput!) { draftOrderCreate(input: $input) { draftOrder { id name invoiceUrl } userErrors { field message } } }
Session Tracking
Claude MUST emit the following output at each stage. This is mandatory.
On start, emit:
╔══════════════════════════════════════════════╗ ║ SKILL: refund-and-reorder ║ ║ Store: <store domain> ║ ║ Started: <YYYY-MM-DD HH:MM UTC> ║ ╚══════════════════════════════════════════════╝
After each step, emit:
[N/TOTAL] <QUERY|MUTATION> <OperationName> → Params: <brief summary of key inputs> → Result: <count or outcome>
If dry_run: true, prefix every mutation step with [DRY RUN] and do not execute it.
On completion, emit:
For format: human (default):
══════════════════════════════════════════════ OUTCOME SUMMARY Order: <name> Refund ID: <id> Amount refunded: <amount> <currency> Replacement draft: <draft order name or "none"> Errors: 0 Output: none ══════════════════════════════════════════════
For format: json, emit:
json{ "skill": "refund-and-reorder", "store": "<domain>", "started_at": "<ISO8601>", "completed_at": "<ISO8601>", "dry_run": false, "steps": [ { "step": 1, "operation": "OrderForRefund", "type": "query", "params_summary": "order <id>", "result_summary": "<status>", "skipped": false }, { "step": 2, "operation": "RefundCreate", "type": "mutation", "params_summary": "<n> line items, reason: <reason>", "result_summary": "refund <id>", "skipped": false }, { "step": 3, "operation": "DraftOrderCreate", "type": "mutation", "params_summary": "<n> line items, customer <id>", "result_summary": "draft <name>", "skipped": false } ], "outcome": { "order_name": "<name>", "refund_id": "<id>", "amount_refunded": "<amount>", "currency": "<currency>", "draft_order_name": "<name or null>", "draft_order_invoice_url": "<url or null>", "errors": 0, "output_file": null } }
Output Format
No CSV output. The session completion summary reports the refund ID and amount. If create_replacement: true, the draft order name and invoice URL are included in the output.
Error Handling
| Error | Cause | Recovery |
|---|---|---|
refundableQuantity is 0 | Line item already fully refunded | Check order refund history |
userErrors from refundCreate | Invalid refund amounts or order not refundable | Check displayFinancialStatus — must not be REFUNDED |
userErrors from draftOrderCreate | Invalid line items or customer | Verify product variants still exist |
| Order not found | Invalid order GID | Use order-lookup-and-summary skill to find the correct order ID |
Best Practices
- Always run
dry_run: truefirst — Step 2 is irreversible. VerifyrefundableQuantityper line item in Step 1 output before committing. - For partial refunds, specify
refund_line_itemsexplicitly — omitting it refunds all refundable items, which may not be intended. - The
create_replacementdraft order is not automatically invoiced or fulfilled — shareinvoiceUrlwith the customer for payment. - Use
notify_customer: falsefor internal corrections where the customer should not be alerted. - Check
displayFinancialStatusfrom Step 1 — if it isREFUNDED, there is nothing left to refund.


