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:
Gerhard Scheikl
2026-05-09 20:31:31 +02:00
parent 0800d1160b
commit 93aec2f368
6 changed files with 71 additions and 58 deletions
+4 -9
View File
@@ -177,7 +177,6 @@ export const action = async ({ request }: ActionFunctionArgs) => {
emailBodyHtmlEn: str("emailBodyHtmlEn"),
autoEmailOnWireTransferPlaced: bool("autoEmailOnWireTransferPlaced"),
autoEmailOnFulfilledNonWireTransfer: bool("autoEmailOnFulfilledNonWireTransfer"),
wireTransferGatewayNames: str("wireTransferGatewayNames"),
};
await db.shopSettings.upsert({
@@ -422,8 +421,10 @@ export default function SettingsRoute() {
Flow required (Flow is gated to Plus stores for custom apps).
When an automation fires, the invoice is generated (if it doesn't
already exist) and emailed to the customer using the SMTP and
email-template settings above. A copy is recorded in the email
log; failures are logged server-side.
email-template settings above. "Wire-transfer" is detected via
Shopify's <code>OrderTransaction.manualPaymentGateway</code> flag,
so any merchant-defined manual payment method (Überweisung, Cash
on Delivery, Money Order, ) qualifies.
</s-paragraph>
<Toggle
label='Auto-email the invoice when a wire-transfer order is placed (so the customer gets the bank details + GiroCode immediately).'
@@ -435,12 +436,6 @@ export default function SettingsRoute() {
name="autoEmailOnFulfilledNonWireTransfer"
checked={settings.autoEmailOnFulfilledNonWireTransfer}
/>
<Field
label="Wire-transfer payment gateway names (comma-separated, case-insensitive substring match)"
name="wireTransferGatewayNames"
defaultValue={settings.wireTransferGatewayNames}
helpText='Used to classify which orders count as "wire transfer". Leave empty to use the default: manual, Überweisung, Wire Transfer, Bank Transfer, Vorkasse, Bank Deposit.'
/>
</s-stack>
</s-section>
+8 -11
View File
@@ -3,14 +3,14 @@ import { authenticate } from "../shopify.server";
import db from "../db.server";
import {
generateAndEmailInvoice,
isWireTransferOrder,
isManualPaymentOrder,
} from "../services/invoice/automations.server";
/**
* orders/create — Automation 1: when a wire-transfer 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).
* 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);
@@ -24,12 +24,9 @@ export const action = async ({ request }: ActionFunctionArgs) => {
const orderId = payload?.id;
if (orderId == null) return new Response();
const gateways: string[] = Array.isArray(payload?.payment_gateway_names)
? payload.payment_gateway_names
: [];
if (!isWireTransferOrder(gateways, settings.wireTransferGatewayNames)) {
return new Response();
}
const orderGid = `gid://shopify/Order/${String(orderId).replace(/^.*\//, "")}`;
const isManual = await isManualPaymentOrder(admin, orderGid);
if (!isManual) return new Response();
try {
const result = await generateAndEmailInvoice({
+6 -8
View File
@@ -3,13 +3,13 @@ import { authenticate } from "../shopify.server";
import db from "../db.server";
import {
generateAndEmailInvoice,
isWireTransferOrder,
isManualPaymentOrder,
} from "../services/invoice/automations.server";
/**
* orders/fulfilled — Automation 2: when an order is fulfilled and is NOT a
* wire-transfer order (e.g. paid by card), automatically email the invoice
* to the customer. Wire-transfer orders are intentionally skipped here
* wire-transfer (manual-payment-gateway) order, automatically email the
* invoice to the customer. Manual-gateway orders are intentionally skipped
* because Automation 1 already emailed them at order-create time.
*/
export const action = async ({ request }: ActionFunctionArgs) => {
@@ -27,11 +27,9 @@ export const action = async ({ request }: ActionFunctionArgs) => {
const orderId = payload?.id;
if (orderId == null) return new Response();
const gateways: string[] = Array.isArray(payload?.payment_gateway_names)
? payload.payment_gateway_names
: [];
if (isWireTransferOrder(gateways, settings.wireTransferGatewayNames)) {
// Wire-transfer order — handled by Automation 1, skip here.
const orderGid = `gid://shopify/Order/${String(orderId).replace(/^.*\//, "")}`;
if (await isManualPaymentOrder(admin, orderGid)) {
// Manual / wire-transfer order — handled by Automation 1, skip here.
return new Response();
}