fix(checkout): batch surcharge 5x over-count + ERPNext SO missing discount
Mitchell's batch CB-95BA6C90: Stripe correctly charged $450.88 ($437.75 net + $13.13 surcharge), but the DB + Telegram showed $503.40 with a $65.65 surcharge. Two bugs: 1) On Stripe session creation, the per-row surcharge UPDATE wrote the FULL batch surcharge ($13.13) to EVERY row via WHERE batch_id, so anything summing the per-row field (the Telegram order notification) over-counted Nx (5 x $13.13 = $65.65). Now the single surcharge is split across the rows so they sum to the true total. Stripe was always charged correctly (one surcharge line item). 2) ensureComplianceSalesOrder built the ERPNext SO from full line-item prices but applied NO discount, so the SO grand total over-stated what the customer paid. Now applies the promo/bundle discount via apply_discount_on=Grand Total + discount_amount on both the primary and fallback SO create.
This commit is contained in:
parent
058d4d426a
commit
6827aafdbc
1 changed files with 39 additions and 10 deletions
|
|
@ -308,10 +308,12 @@ export async function ensureComplianceSalesOrder(
|
||||||
const { COMPLIANCE_SERVICES } = await import("./compliance-orders.js");
|
const { COMPLIANCE_SERVICES } = await import("./compliance-orders.js");
|
||||||
const surchargePct = Number(first.surcharge_pct || 0);
|
const surchargePct = Number(first.surcharge_pct || 0);
|
||||||
let surchargeCents = 0;
|
let surchargeCents = 0;
|
||||||
|
let discountCents = 0;
|
||||||
|
|
||||||
const lineItems = rows.map((o: Record<string, any>) => {
|
const lineItems = rows.map((o: Record<string, any>) => {
|
||||||
const info = COMPLIANCE_SERVICES[(o.service_slug as string) || ""];
|
const info = COMPLIANCE_SERVICES[(o.service_slug as string) || ""];
|
||||||
surchargeCents += Number(o.surcharge_cents || 0);
|
surchargeCents += Number(o.surcharge_cents || 0);
|
||||||
|
discountCents += Number(o.discount_cents || 0);
|
||||||
const items: Array<{ item_code: string; description: string; qty: number; rate: number }> = [{
|
const items: Array<{ item_code: string; description: string; qty: number; rate: number }> = [{
|
||||||
item_code: info?.erpnext_item || "COMPLIANCE-SERVICE",
|
item_code: info?.erpnext_item || "COMPLIANCE-SERVICE",
|
||||||
description: (o.service_name as string) || info?.name || "Compliance Service",
|
description: (o.service_name as string) || info?.name || "Compliance Service",
|
||||||
|
|
@ -348,6 +350,9 @@ export async function ensureComplianceSalesOrder(
|
||||||
custom_surcharge_pct: surchargePct,
|
custom_surcharge_pct: surchargePct,
|
||||||
workflow_state: "Received",
|
workflow_state: "Received",
|
||||||
items: lineItems,
|
items: lineItems,
|
||||||
|
// Reflect the promo/bundle discount so the SO grand total matches what the
|
||||||
|
// customer actually paid (line items are full price; discount applied here).
|
||||||
|
...(discountCents > 0 ? { apply_discount_on: "Grand Total", discount_amount: toDollars(discountCents) } : {}),
|
||||||
}).catch(async (e: unknown) => {
|
}).catch(async (e: unknown) => {
|
||||||
// Resilience: if a service's ERPNext Item is missing, the SO would 404.
|
// Resilience: if a service's ERPNext Item is missing, the SO would 404.
|
||||||
// Retry once with every line item remapped to the generic COMPLIANCE-SERVICE
|
// Retry once with every line item remapped to the generic COMPLIANCE-SERVICE
|
||||||
|
|
@ -366,6 +371,7 @@ export async function ensureComplianceSalesOrder(
|
||||||
custom_surcharge_pct: surchargePct,
|
custom_surcharge_pct: surchargePct,
|
||||||
workflow_state: "Received",
|
workflow_state: "Received",
|
||||||
items: fallback,
|
items: fallback,
|
||||||
|
...(discountCents > 0 ? { apply_discount_on: "Grand Total", discount_amount: toDollars(discountCents) } : {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
|
|
@ -1512,17 +1518,40 @@ router.post("/api/v1/checkout/create-session", async (req, res) => {
|
||||||
};
|
};
|
||||||
const table = tableMap[order_type];
|
const table = tableMap[order_type];
|
||||||
if (table) {
|
if (table) {
|
||||||
// For batch orders, update by batch_id; otherwise by order_number
|
// For batch orders, update by batch_id; otherwise by order_number.
|
||||||
const whereCol = order_type === "compliance_batch" ? "batch_id" : "order_number";
|
const whereCol = order_type === "compliance_batch" ? "batch_id" : "order_number";
|
||||||
await pool.query(
|
if (order_type === "compliance_batch") {
|
||||||
`UPDATE ${table}
|
// A batch has ONE surcharge for the whole order, but it is stored per
|
||||||
SET stripe_session_id = $1,
|
// row. Writing the full surcharge_cents to every row makes anything that
|
||||||
payment_method = $2,
|
// SUMS the per-row field (e.g. the Telegram order notification) over-
|
||||||
surcharge_pct = $3,
|
// count by Nx. Split the single surcharge across the rows so the per-row
|
||||||
surcharge_cents = $4
|
// values sum to the true total (remainder on the first row).
|
||||||
WHERE ${whereCol} = $5`,
|
const { rows: brows } = await pool.query(
|
||||||
[session.id, payment_method, surcharge_pct, surcharge_cents, order_id],
|
`SELECT order_number FROM ${table} WHERE batch_id = $1 ORDER BY created_at`,
|
||||||
);
|
[order_id],
|
||||||
|
);
|
||||||
|
const n = brows.length || 1;
|
||||||
|
const per = Math.floor(surcharge_cents / n);
|
||||||
|
const remainder = surcharge_cents - per * n;
|
||||||
|
for (let i = 0; i < brows.length; i++) {
|
||||||
|
const rowSurcharge = per + (i === 0 ? remainder : 0);
|
||||||
|
await pool.query(
|
||||||
|
`UPDATE ${table}
|
||||||
|
SET stripe_session_id = $1, payment_method = $2,
|
||||||
|
surcharge_pct = $3, surcharge_cents = $4
|
||||||
|
WHERE order_number = $5`,
|
||||||
|
[session.id, payment_method, surcharge_pct, rowSurcharge, brows[i].order_number],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await pool.query(
|
||||||
|
`UPDATE ${table}
|
||||||
|
SET stripe_session_id = $1, payment_method = $2,
|
||||||
|
surcharge_pct = $3, surcharge_cents = $4
|
||||||
|
WHERE ${whereCol} = $5`,
|
||||||
|
[session.id, payment_method, surcharge_pct, surcharge_cents, order_id],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[checkout] Stripe session ${session.id} created for ${order_type} ${order_id}`);
|
console.log(`[checkout] Stripe session ${session.id} created for ${order_type} ${order_id}`);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue