75 lines
2.9 KiB
TypeScript
75 lines
2.9 KiB
TypeScript
import type { ActionFunctionArgs } from "react-router";
|
|
import { authenticate } from "../shopify.server";
|
|
import db from "../db.server";
|
|
import {
|
|
generateAndEmailInvoice,
|
|
isManualPaymentOrder,
|
|
} from "../services/invoice/automations.server";
|
|
import { reserveWebhook } from "../services/webhooks/dedupe.server";
|
|
import { runWebhookInBackground } from "../services/webhooks/background.server";
|
|
|
|
/**
|
|
* orders/create — Automation 1: when a wire-transfer (manual-payment-gateway)
|
|
* order is placed, immediately generate and email the invoice (which includes
|
|
* the bank details + GiroCode) so the customer can pay. Other orders are
|
|
* ignored here; they're handled by orders/fulfilled (Automation 2).
|
|
*/
|
|
export const action = async ({ request }: ActionFunctionArgs) => {
|
|
const { shop, topic, payload, session, admin } = await authenticate.webhook(request);
|
|
console.log(`Received ${topic} webhook for ${shop}`);
|
|
|
|
// Reserve this delivery (status="processing"). `null` => already
|
|
// done/in-flight, so short-circuit. The reservation is committed only after
|
|
// the background work succeeds, and released on failure so Shopify's retry
|
|
// re-runs it (prevents the silent invoice loss we'd get if we recorded the
|
|
// id as processed before the slow PDF/email work).
|
|
const reservation = await reserveWebhook(request, shop, topic);
|
|
if (!reservation) return new Response();
|
|
|
|
if (!session || !admin) {
|
|
// App uninstalled before the webhook drained — nothing to do.
|
|
await reservation.commit();
|
|
return new Response();
|
|
}
|
|
|
|
const orderId = payload?.id;
|
|
if (orderId == null) {
|
|
await reservation.commit();
|
|
return new Response();
|
|
}
|
|
|
|
const customerLocale =
|
|
typeof payload?.customer_locale === "string" ? payload.customer_locale : undefined;
|
|
|
|
// Respond 200 immediately and run the (slow) PDF + email work in the
|
|
// background — keeps us well under Shopify's ~5s ack timeout. The queue
|
|
// commits the reservation on success and releases it on failure.
|
|
runWebhookInBackground(
|
|
`${topic} order=${orderId} shop=${shop}`,
|
|
async () => {
|
|
const settings = await db.shopSettings.findUnique({ where: { shopDomain: shop } });
|
|
if (!settings?.autoEmailOnWireTransferPlaced) return;
|
|
|
|
const orderGid = `gid://shopify/Order/${String(orderId).replace(/^.*\//, "")}`;
|
|
if (!(await isManualPaymentOrder(admin, orderGid))) return;
|
|
|
|
const result = await generateAndEmailInvoice({
|
|
shopDomain: shop,
|
|
admin,
|
|
orderId,
|
|
customerLocale,
|
|
});
|
|
if (!result.ok) {
|
|
// Throw so the reservation is released and Shopify retries — don't
|
|
// swallow the failure (which would leave the invoice unsent forever).
|
|
throw new Error(
|
|
`auto-email (wire-transfer placed) failed for order ${orderId} on ${shop}: ${result.reason}`,
|
|
);
|
|
}
|
|
},
|
|
reservation,
|
|
);
|
|
|
|
return new Response();
|
|
};
|