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 proceedingOPERATION:
refundCreate— mutation Inputs:input.orderId,input.refundLineItems(from parameter or all refundable),input.notify,input.note: <reason>Expected output:refund.id,refund.totalRefundedSet,userErrorsOPERATION:
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
# 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
}
}
}
# 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
}
}
}
# 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:
{
"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.