first version
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user