import type { ActionFunctionArgs } from "react-router"; import { authenticate } from "../shopify.server"; import db from "../db.server"; import { generateInvoice } from "../services/invoice/generateInvoice.server"; import { sendInvoiceEmail } from "../services/invoice/email.server"; /** * Flow action endpoint: "Send invoice email to customer". * * Generates the invoice if missing, then sends it via the shop's configured * SMTP. Marks the invoice as `sent`, locking it from in-place regeneration. * * Expected payload properties: * - order_id (required) * - recipient_email_override (optional) */ export const action = async ({ request }: ActionFunctionArgs) => { const { session, admin, payload } = await authenticate.flow(request); const orderId = extractOrderId(payload); if (!orderId) { return Response.json( { ok: false, error: "Missing 'order_id' in Flow payload properties." }, { status: 400 }, ); } const recipientOverride = extractProp(payload, "recipient_email_override"); try { // Make sure an invoice exists. const orderGid = orderId.startsWith("gid://") ? orderId : `gid://shopify/Order/${orderId}`; let invoice = await db.invoice.findFirst({ where: { shopDomain: session.shop, orderId: orderGid, kind: "invoice", cancelledAt: null, }, orderBy: [{ version: "desc" }, { createdAt: "desc" }], }); if (!invoice) { const generated = await generateInvoice({ shopDomain: session.shop, admin, orderId, }); invoice = await db.invoice.findUnique({ where: { id: generated.invoiceId } }); } if (!invoice) throw new Error("Failed to materialise an invoice for this order."); const result = await sendInvoiceEmail({ shopDomain: session.shop, invoiceId: invoice.id, toAddress: recipientOverride || undefined, }); if (!result.ok) { return Response.json( { ok: false, error: result.errorMessage ?? "Email send failed." }, { status: 422 }, ); } return { ok: true, invoiceNumber: invoice.invoiceNumber, toAddress: result.toAddress }; } catch (err) { const message = err instanceof Error ? err.message : String(err); console.error("flow.send-invoice-email failed:", err); return Response.json({ ok: false, error: message }, { status: 422 }); } }; function extractOrderId(payload: unknown): string | null { if (!payload || typeof payload !== "object") return null; const props = (payload as { properties?: Record }).properties; if (!props || typeof props !== "object") return null; const raw = props["order_id"]; if (typeof raw === "string" && raw.length > 0) return raw; if (typeof raw === "number") return String(raw); return null; } function extractProp(payload: unknown, key: string): string | null { if (!payload || typeof payload !== "object") return null; const props = (payload as { properties?: Record }).properties; if (!props || typeof props !== "object") return null; const raw = props[key]; if (typeof raw === "string" && raw.length > 0) return raw; return null; }