1 | |
2 |
|
3 | |
4 | * Refund Order |
5 | * 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. |
6 | */ |
7 | export async function main( |
8 | auth: RT.Shopify, |
9 | order_id: string, |
10 | line_items: |
11 | | { |
12 | line_item_id: string |
13 | quantity: number |
14 | restock_type?: "RETURN" | "CANCEL" | "NO_RESTOCK" |
15 | location_id?: string |
16 | }[] |
17 | | undefined, |
18 | full_shipping_refund: boolean | undefined, |
19 | shipping_amount: number | undefined, |
20 | note: string | undefined, |
21 | notify: boolean | undefined |
22 | ) { |
23 | const orderId = order_id.startsWith("gid://") |
24 | ? order_id |
25 | : `gid://shopify/Order/${order_id}` |
26 | const gid = (value: string, type: string) => |
27 | value.startsWith("gid://") ? value : `gid://shopify/${type}/${value}` |
28 |
|
29 | const endpoint = `https://${auth.store_name}.myshopify.com/admin/api/2026-10/graphql.json` |
30 | const headers = { |
31 | "X-Shopify-Access-Token": auth.token, |
32 | "Content-Type": "application/json", |
33 | Accept: "application/json", |
34 | } |
35 | const refundLineItems = line_items?.length |
36 | ? line_items.map((item) => ({ |
37 | lineItemId: gid(item.line_item_id, "LineItem"), |
38 | quantity: item.quantity, |
39 | restockType: item.restock_type || "NO_RESTOCK", |
40 | locationId: item.location_id |
41 | ? gid(item.location_id, "Location") |
42 | : undefined, |
43 | })) |
44 | : undefined |
45 | const shipping = full_shipping_refund |
46 | ? { fullRefund: true } |
47 | : shipping_amount |
48 | ? { amount: shipping_amount } |
49 | : undefined |
50 |
|
51 | |
52 | const suggestedResponse = await fetch(endpoint, { |
53 | method: "POST", |
54 | headers, |
55 | body: JSON.stringify({ |
56 | 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 } } } } }`, |
57 | variables: { |
58 | id: orderId, |
59 | refundLineItems, |
60 | shippingAmount: shipping?.amount, |
61 | refundShipping: shipping?.fullRefund, |
62 | }, |
63 | }), |
64 | }) |
65 | if (!suggestedResponse.ok) { |
66 | throw new Error( |
67 | `${suggestedResponse.status} ${await suggestedResponse.text()}` |
68 | ) |
69 | } |
70 | const suggested = await suggestedResponse.json() |
71 | if (suggested.errors) { |
72 | throw new Error(JSON.stringify(suggested.errors)) |
73 | } |
74 | const transactions = |
75 | suggested.data.order.suggestedRefund.suggestedTransactions.map( |
76 | (t: any) => ({ |
77 | orderId, |
78 | parentId: t.parentTransaction?.id, |
79 | gateway: t.gateway, |
80 | kind: "REFUND", |
81 | amount: t.amountSet.shopMoney.amount, |
82 | }) |
83 | ) |
84 |
|
85 | const response = await fetch(endpoint, { |
86 | method: "POST", |
87 | headers, |
88 | body: JSON.stringify({ |
89 | 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 } } }`, |
90 | variables: { |
91 | input: { |
92 | orderId, |
93 | refundLineItems, |
94 | shipping, |
95 | transactions, |
96 | note: note || undefined, |
97 | notify: notify || false, |
98 | }, |
99 | |
100 | idempotencyKey: crypto.randomUUID(), |
101 | }, |
102 | }), |
103 | }) |
104 |
|
105 | if (!response.ok) { |
106 | throw new Error(`${response.status} ${await response.text()}`) |
107 | } |
108 |
|
109 | const { data, errors } = await response.json() |
110 | if (errors) { |
111 | throw new Error(JSON.stringify(errors)) |
112 | } |
113 |
|
114 | if (data.refundCreate.userErrors.length) { |
115 | throw new Error(JSON.stringify(data.refundCreate.userErrors)) |
116 | } |
117 |
|
118 | return data.refundCreate |
119 | } |
120 |
|