refactor(automations): detect manual payment via OrderTransaction.manualPaymentGateway
- Drop wireTransferGatewayNames from ShopSettings (new migration). - Replace string-matching with a GraphQL query against Order.transactions[].manualPaymentGateway, the first-class flag Shopify exposes for any merchant-defined manual payment method. - Both webhook handlers now fetch the order on the fly to classify it, removing the configurable gateway-names field from settings.
This commit is contained in:
@@ -4,32 +4,54 @@ import db from "../../db.server";
|
||||
import { generateInvoice } from "./generateInvoice.server";
|
||||
import { sendInvoiceEmail } from "./email.server";
|
||||
|
||||
const DEFAULT_WIRE_TRANSFER_NAMES = [
|
||||
"manual",
|
||||
"Überweisung",
|
||||
"Wire Transfer",
|
||||
"Bank Transfer",
|
||||
"Vorkasse",
|
||||
"Bank Deposit",
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns true when any of the order's `payment_gateway_names` matches one of
|
||||
* the configured wire-transfer gateway names (case-insensitive substring).
|
||||
* Returns true when the order has at least one transaction processed by a
|
||||
* Shopify "manual" payment gateway (wire transfer, cash on delivery,
|
||||
* money order, custom manual methods, …). This uses
|
||||
* `OrderTransaction.manualPaymentGateway`, the only first-class flag
|
||||
* Shopify exposes for distinguishing manual gateways from automated ones.
|
||||
*
|
||||
* Falls back to `false` on any GraphQL error so we don't block fulfilment
|
||||
* automations on transient API issues.
|
||||
*/
|
||||
export function isWireTransferOrder(
|
||||
paymentGatewayNames: readonly string[] | null | undefined,
|
||||
configured: string,
|
||||
): boolean {
|
||||
if (!paymentGatewayNames || paymentGatewayNames.length === 0) return false;
|
||||
const needles = (configured.trim() ? configured.split(",") : DEFAULT_WIRE_TRANSFER_NAMES)
|
||||
.map((s) => s.trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
if (needles.length === 0) return false;
|
||||
return paymentGatewayNames.some((name) => {
|
||||
const n = (name ?? "").toLowerCase();
|
||||
return needles.some((needle) => n.includes(needle));
|
||||
});
|
||||
export async function isManualPaymentOrder(
|
||||
admin: AdminApiContext,
|
||||
orderGid: string,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const res = await admin.graphql(
|
||||
`#graphql
|
||||
query OrderManualPaymentCheck($id: ID!) {
|
||||
order(id: $id) {
|
||||
transactions(first: 20) {
|
||||
kind
|
||||
status
|
||||
manualPaymentGateway
|
||||
}
|
||||
}
|
||||
}`,
|
||||
{ variables: { id: orderGid } },
|
||||
);
|
||||
const json = (await res.json()) as {
|
||||
data?: {
|
||||
order?: {
|
||||
transactions?: Array<{
|
||||
kind?: string;
|
||||
status?: string;
|
||||
manualPaymentGateway?: boolean;
|
||||
}>;
|
||||
} | null;
|
||||
};
|
||||
};
|
||||
const txs = json.data?.order?.transactions ?? [];
|
||||
// Any non-failed transaction processed by a manual gateway counts.
|
||||
return txs.some(
|
||||
(t) => t.manualPaymentGateway === true && t.status !== "FAILURE" && t.status !== "ERROR",
|
||||
);
|
||||
} catch (err) {
|
||||
console.warn(`isManualPaymentOrder query failed for ${orderGid}:`, err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export interface AutoEmailArgs {
|
||||
@@ -51,7 +73,8 @@ export async function generateAndEmailInvoice(args: AutoEmailArgs): Promise<{
|
||||
reason?: string;
|
||||
invoiceNumber?: string;
|
||||
}> {
|
||||
const orderGid = `gid://shopify/Order/${String(args.orderId).replace(/^.*\//, "")}`;
|
||||
const orderNumeric = String(args.orderId).replace(/^.*\//, "");
|
||||
const orderGid = `gid://shopify/Order/${orderNumeric}`;
|
||||
|
||||
const existing = await db.invoice.findFirst({
|
||||
where: {
|
||||
@@ -72,7 +95,7 @@ export async function generateAndEmailInvoice(args: AutoEmailArgs): Promise<{
|
||||
const generated = await generateInvoice({
|
||||
shopDomain: args.shopDomain,
|
||||
admin: args.admin,
|
||||
orderId: String(args.orderId),
|
||||
orderId: orderNumeric,
|
||||
});
|
||||
invoiceId = generated.invoiceId;
|
||||
invoiceNumber = generated.invoiceNumber;
|
||||
|
||||
Reference in New Issue
Block a user