//native
/**
* Refund Order
* Refund line items and/or shipping on an order. The amount is computed from the chosen items (plus shipping) and refunded to the original payment method. restock_type is RETURN (back to stock at location_id), CANCEL (unfulfilled items) or NO_RESTOCK.
*/
export async function main(
auth: RT.Shopify,
order_id: string,
line_items:
| {
line_item_id: string
quantity: number
restock_type?: "RETURN" | "CANCEL" | "NO_RESTOCK"
location_id?: string
}[]
| undefined,
full_shipping_refund: boolean | undefined,
shipping_amount: number | undefined,
note: string | undefined,
notify: boolean | undefined
) {
const orderId = order_id.startsWith("gid://")
? order_id
: `gid://shopify/Order/${order_id}`
const gid = (value: string, type: string) =>
value.startsWith("gid://") ? value : `gid://shopify/${type}/${value}`
const endpoint = `https://${auth.store_name}.myshopify.com/admin/api/2026-10/graphql.json`
const headers = {
"X-Shopify-Access-Token": auth.token,
"Content-Type": "application/json",
Accept: "application/json",
}
const refundLineItems = line_items?.length
? line_items.map((item) => ({
lineItemId: gid(item.line_item_id, "LineItem"),
quantity: item.quantity,
restockType: item.restock_type || "NO_RESTOCK",
locationId: item.location_id
? gid(item.location_id, "Location")
: undefined,
}))
: undefined
const shipping = full_shipping_refund
? { fullRefund: true }
: shipping_amount
? { amount: shipping_amount }
: undefined
// Let Shopify compute the refundable amount and the parent transactions to refund against.
const suggestedResponse = await fetch(endpoint, {
method: "POST",
headers,
body: JSON.stringify({
query: `query ($id: ID!, $refundLineItems: [RefundLineItemInput!], $shippingAmount: Money, $refundShipping: Boolean) { order(id: $id) { suggestedRefund(refundLineItems: $refundLineItems, shippingAmount: $shippingAmount, refundShipping: $refundShipping) { suggestedTransactions { gateway kind amountSet { shopMoney { amount } } parentTransaction { id } } } } }`,
variables: {
id: orderId,
refundLineItems,
shippingAmount: shipping?.amount,
refundShipping: shipping?.fullRefund,
},
}),
})
if (!suggestedResponse.ok) {
throw new Error(
`${suggestedResponse.status} ${await suggestedResponse.text()}`
)
}
const suggested = await suggestedResponse.json()
if (suggested.errors) {
throw new Error(JSON.stringify(suggested.errors))
}
const transactions =
suggested.data.order.suggestedRefund.suggestedTransactions.map(
(t: any) => ({
orderId,
parentId: t.parentTransaction?.id,
gateway: t.gateway,
kind: "REFUND",
amount: t.amountSet.shopMoney.amount,
})
)
const response = await fetch(endpoint, {
method: "POST",
headers,
body: JSON.stringify({
query: `mutation ($input: RefundInput!, $idempotencyKey: String!) { refundCreate(input: $input) @idempotent(key: $idempotencyKey) { refund { id legacyResourceId createdAt note totalRefundedSet { shopMoney { amount currencyCode } } } order { id name displayFinancialStatus totalRefundedSet { shopMoney { amount currencyCode } } } userErrors { field message } } }`,
variables: {
input: {
orderId,
refundLineItems,
shipping,
transactions,
note: note || undefined,
notify: notify || false,
},
// Required on refundCreate since 2026-04; a fresh key per run (no automatic retries here).
idempotencyKey: crypto.randomUUID(),
},
}),
})
if (!response.ok) {
throw new Error(`${response.status} ${await response.text()}`)
}
const { data, errors } = await response.json()
if (errors) {
throw new Error(JSON.stringify(errors))
}
if (data.refundCreate.userErrors.length) {
throw new Error(JSON.stringify(data.refundCreate.userErrors))
}
return data.refundCreate
}
Submitted by hugo989 3 hours ago