feat: forward order webhooks to N8N instead of sending emails directly

- Modified order-notifications.ts to forward webhooks to N8N
- Updated webhook handlers for ORDER_CONFIRMED, ORDER_FULFILLED, ORDER_CANCELLED
- Removed direct email sending via Resend
- Webhooks now forward to https://n8n.nodecrew.me/webhook/saleor-{event}
- Each event type gets forwarded with appropriate x-saleor-event header
This commit is contained in:
Unchained
2026-03-27 15:13:52 +02:00
parent bd8d941b1c
commit a2601a3d3c
2 changed files with 53 additions and 86 deletions
-2
View File
@@ -7,7 +7,6 @@ import {
orderConfirmedWebhook, orderConfirmedWebhook,
orderFulfilledWebhook, orderFulfilledWebhook,
orderCancelledWebhook, orderCancelledWebhook,
orderFullyPaidWebhook,
} from "./webhooks/order-notifications"; } from "./webhooks/order-notifications";
/** /**
@@ -37,7 +36,6 @@ export default createManifestHandler({
orderConfirmedWebhook.getWebhookManifest(apiBaseURL), orderConfirmedWebhook.getWebhookManifest(apiBaseURL),
orderFulfilledWebhook.getWebhookManifest(apiBaseURL), orderFulfilledWebhook.getWebhookManifest(apiBaseURL),
orderCancelledWebhook.getWebhookManifest(apiBaseURL), orderCancelledWebhook.getWebhookManifest(apiBaseURL),
orderFullyPaidWebhook.getWebhookManifest(apiBaseURL),
], ],
extensions: [], extensions: [],
author: "ManoonOils", author: "ManoonOils",
+53 -84
View File
@@ -6,24 +6,14 @@ import {
OrderFulfilledWebhookPayloadFragment, OrderFulfilledWebhookPayloadFragment,
OrderCancelledSubscriptionDocument, OrderCancelledSubscriptionDocument,
OrderCancelledWebhookPayloadFragment, OrderCancelledWebhookPayloadFragment,
OrderFullyPaidSubscriptionDocument,
OrderFullyPaidWebhookPayloadFragment,
} from "@/generated/graphql"; } from "@/generated/graphql";
import { saleorApp } from "@/saleor-app"; import { saleorApp } from "@/saleor-app";
import {
sendOrderConfirmationEmail,
sendOrderShippedEmail,
sendOrderCancelledEmail,
sendOrderPaidEmail,
sendAdminNotification,
} from "@/lib/resend";
const FROM_EMAIL = process.env.FROM_EMAIL || "support@mail.manoonoils.com"; // N8N webhook URLs - these will forward Saleor events to N8N
const FROM_NAME = process.env.FROM_NAME || "ManoonOils"; const N8N_BASE_URL = "https://n8n.nodecrew.me/webhook";
const SITE_URL = process.env.SITE_URL || "https://dev.manoonoils.com";
export const orderConfirmedWebhook = new SaleorAsyncWebhook<OrderConfirmedWebhookPayloadFragment>({ export const orderConfirmedWebhook = new SaleorAsyncWebhook<OrderConfirmedWebhookPayloadFragment>({
name: "Order Confirmed", name: "Order Confirmed - N8N",
webhookPath: "api/webhooks/order-confirmed", webhookPath: "api/webhooks/order-confirmed",
event: "ORDER_CONFIRMED", event: "ORDER_CONFIRMED",
apl: saleorApp.apl, apl: saleorApp.apl,
@@ -31,7 +21,7 @@ export const orderConfirmedWebhook = new SaleorAsyncWebhook<OrderConfirmedWebhoo
}); });
export const orderFulfilledWebhook = new SaleorAsyncWebhook<OrderFulfilledWebhookPayloadFragment>({ export const orderFulfilledWebhook = new SaleorAsyncWebhook<OrderFulfilledWebhookPayloadFragment>({
name: "Order Fulfilled", name: "Order Fulfilled - N8N",
webhookPath: "api/webhooks/order-fulfilled", webhookPath: "api/webhooks/order-fulfilled",
event: "ORDER_FULFILLED", event: "ORDER_FULFILLED",
apl: saleorApp.apl, apl: saleorApp.apl,
@@ -39,101 +29,80 @@ export const orderFulfilledWebhook = new SaleorAsyncWebhook<OrderFulfilledWebhoo
}); });
export const orderCancelledWebhook = new SaleorAsyncWebhook<OrderCancelledWebhookPayloadFragment>({ export const orderCancelledWebhook = new SaleorAsyncWebhook<OrderCancelledWebhookPayloadFragment>({
name: "Order Cancelled", name: "Order Cancelled - N8N",
webhookPath: "api/webhooks/order-cancelled", webhookPath: "api/webhooks/order-cancelled",
event: "ORDER_CANCELLED", event: "ORDER_CANCELLED",
apl: saleorApp.apl, apl: saleorApp.apl,
query: OrderCancelledSubscriptionDocument, query: OrderCancelledSubscriptionDocument,
}); });
export const orderFullyPaidWebhook = new SaleorAsyncWebhook<OrderFullyPaidWebhookPayloadFragment>({ // Forward webhook payload to N8N
name: "Order Fully Paid", async function forwardToN8N(eventType: string, payload: any) {
webhookPath: "api/webhooks/order-fully-paid", const n8nUrl = `${N8N_BASE_URL}/saleor-${eventType}`;
event: "ORDER_FULLY_PAID",
apl: saleorApp.apl, console.log(`Forwarding ${eventType} to N8N: ${n8nUrl}`);
query: OrderFullyPaidSubscriptionDocument,
}); try {
const response = await fetch(n8nUrl, {
function getTrackingInfo(order: any) { method: "POST",
const trackingMeta = order.metadata?.find((m: any) => m.key === "trackingNumber"); headers: {
const trackingUrlMeta = order.metadata?.find((m: any) => m.key === "trackingUrl"); "Content-Type": "application/json",
return { "x-saleor-event": eventType,
trackingNumber: trackingMeta?.value, },
trackingUrl: trackingUrlMeta?.value, body: JSON.stringify(payload),
}; });
}
if (!response.ok) {
function getCancellationReason(order: any) { console.error(`N8N returned ${response.status}: ${await response.text()}`);
const reasonMeta = order.metadata?.find((m: any) => m.key === "cancellationReason"); throw new Error(`N8N returned ${response.status}`);
return reasonMeta?.value; }
console.log(`Successfully forwarded ${eventType} to N8N`);
return true;
} catch (error) {
console.error(`Failed to forward ${eventType} to N8N:`, error);
throw error;
}
} }
export const orderConfirmedHandler = orderConfirmedWebhook.createHandler(async (req, res, ctx) => { export const orderConfirmedHandler = orderConfirmedWebhook.createHandler(async (req, res, ctx) => {
const { payload } = ctx; const { payload } = ctx;
console.log(`Order confirmed: ${payload.order?.id}`); console.log(`Order confirmed received: ${payload.order?.id}`);
if (payload.order) { try {
try { await forwardToN8N("order.created", payload);
await sendOrderConfirmationEmail(payload.order); return res.status(200).end();
await sendAdminNotification(payload.order, "ORDER_CONFIRMED"); } catch (error) {
} catch (error) { console.error("Failed to forward order confirmed:", error);
console.error("Failed to send confirmation email:", error); return res.status(500).end();
}
} }
return res.status(200).end();
}); });
export const orderFulfilledHandler = orderFulfilledWebhook.createHandler(async (req, res, ctx) => { export const orderFulfilledHandler = orderFulfilledWebhook.createHandler(async (req, res, ctx) => {
const { payload } = ctx; const { payload } = ctx;
console.log(`Order fulfilled: ${payload.order?.id}`); console.log(`Order fulfilled received: ${payload.order?.id}`);
if (payload.order) { try {
const { trackingNumber, trackingUrl } = getTrackingInfo(payload.order); await forwardToN8N("order.fulfilled", payload);
try { return res.status(200).end();
await sendOrderShippedEmail(payload.order, trackingNumber, trackingUrl); } catch (error) {
await sendAdminNotification(payload.order, "ORDER_FULFILLED"); console.error("Failed to forward order fulfilled:", error);
} catch (error) { return res.status(500).end();
console.error("Failed to send shipping email:", error);
}
} }
return res.status(200).end();
}); });
export const orderCancelledHandler = orderCancelledWebhook.createHandler(async (req, res, ctx) => { export const orderCancelledHandler = orderCancelledWebhook.createHandler(async (req, res, ctx) => {
const { payload } = ctx; const { payload } = ctx;
console.log(`Order cancelled: ${payload.order?.id}`); console.log(`Order cancelled received: ${payload.order?.id}`);
if (payload.order) { try {
const reason = getCancellationReason(payload.order); await forwardToN8N("order.cancelled", payload);
try { return res.status(200).end();
await sendOrderCancelledEmail(payload.order, reason); } catch (error) {
await sendAdminNotification(payload.order, "ORDER_CANCELLED"); console.error("Failed to forward order cancelled:", error);
} catch (error) { return res.status(500).end();
console.error("Failed to send cancellation email:", error);
}
} }
return res.status(200).end();
}); });
export const orderFullyPaidHandler = orderFullyPaidWebhook.createHandler(async (req, res, ctx) => {
const { payload } = ctx;
console.log(`Order fully paid: ${payload.order?.id}`);
if (payload.order) {
try {
await sendOrderPaidEmail(payload.order);
await sendAdminNotification(payload.order, "ORDER_FULLY_PAID");
} catch (error) {
console.error("Failed to send payment email:", error);
}
}
return res.status(200).end();
});