Files
linumiq-invoice/app/routes/api.flow.send-invoice-email.tsx
Gerhard Scheikl 5b2aa5d62b first version
2026-04-28 21:56:11 +02:00

91 lines
3.1 KiB
TypeScript

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<string, unknown> }).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<string, unknown> }).properties;
if (!props || typeof props !== "object") return null;
const raw = props[key];
if (typeof raw === "string" && raw.length > 0) return raw;
return null;
}