fix: update order-fulfilled and order-cancelled webhooks to forward to N8N

- Update GraphQL fragments to include languageCode and all necessary fields
- Modify order-fulfilled.ts to forward to N8N instead of sending directly
- Modify order-cancelled.ts to forward to N8N instead of sending directly
- Regenerate GraphQL types with full order data
- Enable multi-language emails for shipped and cancelled orders
This commit is contained in:
Unchained
2026-03-28 13:58:29 +02:00
parent 2065b24d7a
commit 1089f03ee3
7 changed files with 1080 additions and 116 deletions
+176 -20
View File
File diff suppressed because one or more lines are too long
+78 -1
View File
@@ -2,12 +2,89 @@ fragment OrderCancelledWebhookPayload on OrderCancelled {
order { order {
id id
number number
created
status
userEmail userEmail
languageCode
channel {
slug
}
user { user {
email email
firstName firstName
lastName lastName
} }
status shippingAddress {
firstName
lastName
streetAddress1
streetAddress2
city
postalCode
country {
country
}
phone
}
billingAddress {
firstName
lastName
streetAddress1
streetAddress2
city
postalCode
country {
country
}
phone
}
lines {
id
quantity
unitPrice {
gross {
amount
currency
}
}
totalPrice {
gross {
amount
currency
}
}
variant {
name
sku
product {
name
media {
url
}
}
}
}
subtotal {
gross {
amount
currency
}
}
shippingPrice {
gross {
amount
currency
}
}
total {
gross {
amount
currency
}
}
metadata {
key
value
}
} }
} }
+4
View File
@@ -82,5 +82,9 @@ fragment OrderCreatedWebhookPayload on OrderCreated {
currency currency
} }
} }
metadata {
key
value
}
} }
} }
+79 -1
View File
@@ -2,17 +2,95 @@ fragment OrderFulfilledWebhookPayload on OrderFulfilled {
order { order {
id id
number number
created
status
userEmail userEmail
languageCode
channel {
slug
}
user { user {
email email
firstName firstName
lastName lastName
} }
status shippingAddress {
firstName
lastName
streetAddress1
streetAddress2
city
postalCode
country {
country
}
phone
}
billingAddress {
firstName
lastName
streetAddress1
streetAddress2
city
postalCode
country {
country
}
phone
}
lines {
id
quantity
unitPrice {
gross {
amount
currency
}
}
totalPrice {
gross {
amount
currency
}
}
variant {
name
sku
product {
name
media {
url
}
}
}
}
subtotal {
gross {
amount
currency
}
}
shippingPrice {
gross {
amount
currency
}
}
total {
gross {
amount
currency
}
}
metadata {
key
value
}
fulfillments { fulfillments {
id id
status status
created created
trackingNumber
} }
} }
} }
+697 -25
View File
File diff suppressed because it is too large Load Diff
+20 -32
View File
@@ -4,7 +4,9 @@ import {
OrderCancelledWebhookPayloadFragment, OrderCancelledWebhookPayloadFragment,
} from "@/generated/graphql"; } from "@/generated/graphql";
import { saleorApp } from "@/saleor-app"; import { saleorApp } from "@/saleor-app";
import { sendOrderCancelledEmail, formatPrice } from "@/lib/resend";
// N8N webhook URL for Order Cancelled
const N8N_WEBHOOK_URL = "https://n8n.nodecrew.me/webhook/saleor-order-cancelled";
export const orderCancelledWebhook = new SaleorAsyncWebhook<OrderCancelledWebhookPayloadFragment>({ export const orderCancelledWebhook = new SaleorAsyncWebhook<OrderCancelledWebhookPayloadFragment>({
name: "Order Cancelled in Saleor", name: "Order Cancelled in Saleor",
@@ -15,43 +17,29 @@ export const orderCancelledWebhook = new SaleorAsyncWebhook<OrderCancelledWebhoo
}); });
export default orderCancelledWebhook.createHandler(async (req, res, ctx) => { export default orderCancelledWebhook.createHandler(async (req, res, ctx) => {
const { payload, event, baseUrl, authData } = ctx; const { payload, event, authData } = ctx;
const order = payload.order;
if (!order) { console.log(`Order cancelled: ${payload.order?.number} for ${payload.order?.userEmail}`);
console.error("No order data in webhook payload"); console.log(`Forwarding to N8N: ${N8N_WEBHOOK_URL}`);
return res.status(200).end();
}
console.log(`Order ${order.number} cancelled for customer: ${order.userEmail}`);
const items = ((order as any).lines || []).map((line: any) => ({
id: line.id,
name: line.variant?.product?.name || "Unknown Product",
quantity: line.quantity,
price: formatPrice(line.totalPrice?.gross?.amount || 0, line.totalPrice?.gross?.currency || "USD"),
}));
const orderData = {
orderId: order.id,
orderNumber: order.number || "Unknown",
customerName: (order as any).shippingAddress?.firstName
? `${(order as any).shippingAddress.firstName} ${(order as any).shippingAddress.lastName || ""}`.trim()
: order.userEmail?.split("@")[0] || "Customer",
items,
total: formatPrice((order as any).total?.gross?.amount || 0, (order as any).total?.gross?.currency || "USD"),
};
try { try {
if (order.userEmail) { // Forward to N8N
await sendOrderCancelledEmail({ const response = await fetch(N8N_WEBHOOK_URL, {
to: order.userEmail, method: "POST",
orderData, headers: {
"Content-Type": "application/json",
"x-saleor-event": "order.cancelled",
},
body: JSON.stringify(payload),
}); });
console.log(`Customer notification sent for cancelled order ${order.number}`);
if (!response.ok) {
console.error(`N8N returned ${response.status}: ${await response.text()}`);
} else {
console.log(`Successfully forwarded to N8N: ${response.status}`);
} }
} catch (error) { } catch (error) {
console.error("Failed to send email:", error); console.error(`Failed to forward to N8N:`, error);
} }
return res.status(200).end(); return res.status(200).end();
+20 -31
View File
@@ -4,7 +4,9 @@ import {
OrderFulfilledWebhookPayloadFragment, OrderFulfilledWebhookPayloadFragment,
} from "@/generated/graphql"; } from "@/generated/graphql";
import { saleorApp } from "@/saleor-app"; import { saleorApp } from "@/saleor-app";
import { sendOrderShippedEmail, formatPrice } from "@/lib/resend";
// N8N webhook URL for Order Fulfilled/Shipped
const N8N_WEBHOOK_URL = "https://n8n.nodecrew.me/webhook/saleor-order-shipped";
export const orderFulfilledWebhook = new SaleorAsyncWebhook<OrderFulfilledWebhookPayloadFragment>({ export const orderFulfilledWebhook = new SaleorAsyncWebhook<OrderFulfilledWebhookPayloadFragment>({
name: "Order Fulfilled in Saleor", name: "Order Fulfilled in Saleor",
@@ -15,42 +17,29 @@ export const orderFulfilledWebhook = new SaleorAsyncWebhook<OrderFulfilledWebhoo
}); });
export default orderFulfilledWebhook.createHandler(async (req, res, ctx) => { export default orderFulfilledWebhook.createHandler(async (req, res, ctx) => {
const { payload, event, baseUrl, authData } = ctx; const { payload, event, authData } = ctx;
const order = payload.order;
if (!order) { console.log(`Order fulfilled: ${payload.order?.number} for ${payload.order?.userEmail}`);
console.error("No order data in webhook payload"); console.log(`Forwarding to N8N: ${N8N_WEBHOOK_URL}`);
return res.status(200).end();
}
console.log(`Order ${order.number} fulfilled for customer: ${order.userEmail}`);
const items = ((order as any).lines || []).map((line: any) => ({
id: line.id,
name: line.variant?.product?.name || "Unknown Product",
quantity: line.quantity,
price: formatPrice(line.totalPrice?.gross?.amount || 0, line.totalPrice?.gross?.currency || "USD"),
}));
const orderData = {
orderId: order.id,
orderNumber: order.number || "Unknown",
customerName: (order as any).shippingAddress?.firstName
? `${(order as any).shippingAddress.firstName} ${(order as any).shippingAddress.lastName || ""}`.trim()
: order.userEmail?.split("@")[0] || "Customer",
items,
};
try { try {
if (order.userEmail) { // Forward to N8N
await sendOrderShippedEmail({ const response = await fetch(N8N_WEBHOOK_URL, {
to: order.userEmail, method: "POST",
orderData, headers: {
"Content-Type": "application/json",
"x-saleor-event": "order.fulfilled",
},
body: JSON.stringify(payload),
}); });
console.log(`Customer notification sent for fulfilled order ${order.number}`);
if (!response.ok) {
console.error(`N8N returned ${response.status}: ${await response.text()}`);
} else {
console.log(`Successfully forwarded to N8N: ${response.status}`);
} }
} catch (error) { } catch (error) {
console.error("Failed to send email:", error); console.error(`Failed to forward to N8N:`, error);
} }
return res.status(200).end(); return res.status(200).end();