From a7d7fee15455748f781c574b5ddafb89a1406c0e Mon Sep 17 00:00:00 2001
From: justin
Date: Mon, 27 Apr 2026 09:56:12 -0500
Subject: [PATCH] Fix 6 bugs found in compliance and checkout flows
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
1. CRITICAL: Add compliance_batch to stripe session tableMap —
session IDs weren't being stored for batch orders
2. CRITICAL: Fix batch orders using order_number instead of batch_id
when storing stripe_session_id
3. MAJOR: Tax deductibility note only shows for compliance orders,
not CRTC/formation/bundles
4. MAJOR: Identity verification fallback changed from localhost:4321
to performancewest.net with warning log
5. MEDIUM: Fix discount rounding — last service absorbs remainder
to prevent cent loss across batch orders
6. LOW: Validate at least one paid service in batch orders
7. Standardize support email to info@performancewest.net everywhere
Co-Authored-By: Claude Opus 4.6 (1M context)
---
api/src/email.ts | 8 ++++----
api/src/routes/checkout.ts | 5 ++++-
api/src/routes/compliance-orders.ts | 24 +++++++++++++++++++++---
api/src/routes/identity.ts | 4 +++-
4 files changed, 32 insertions(+), 9 deletions(-)
diff --git a/api/src/email.ts b/api/src/email.ts
index a2ebd35..23625f6 100644
--- a/api/src/email.ts
+++ b/api/src/email.ts
@@ -205,10 +205,10 @@ export async function sendOrderConfirmationEmail(params: OrderConfirmationParams
-
+ ${(order_type === "compliance" || order_type === "compliance_batch") ? `
FCC compliance fees are tax deductible as ordinary business expenses under IRC § 162.
A formal receipt will be sent separately.
-
+
` : ""}
What happens next
${stepsHtml}
@@ -222,7 +222,7 @@ export async function sendOrderConfirmationEmail(params: OrderConfirmationParams
Questions? Reply to this email or reach us at
- support@performancewest.net
+ info@performancewest.net
or call 1-888-411-0383.
`;
@@ -243,7 +243,7 @@ export async function sendOrderConfirmationEmail(params: OrderConfirmationParams
`What happens next:`,
...nextSteps.map((s, i) => `${i + 1}. ${s}`),
``,
- `Questions? Email support@performancewest.net or call 1-888-411-0383.`,
+ `Questions? Email info@performancewest.net or call 1-888-411-0383.`,
``,
`Performance West Inc.`,
].join("\n"),
diff --git a/api/src/routes/checkout.ts b/api/src/routes/checkout.ts
index d11f4e0..913d67b 100644
--- a/api/src/routes/checkout.ts
+++ b/api/src/routes/checkout.ts
@@ -989,16 +989,19 @@ router.post("/api/v1/checkout/create-session", async (req, res) => {
formation: "formation_orders",
bundle: "bundle_orders",
compliance: "compliance_orders",
+ compliance_batch: "compliance_orders",
};
const table = tableMap[order_type];
if (table) {
+ // For batch orders, update by batch_id; otherwise by order_number
+ const whereCol = order_type === "compliance_batch" ? "batch_id" : "order_number";
await pool.query(
`UPDATE ${table}
SET stripe_session_id = $1,
payment_method = $2,
surcharge_pct = $3,
surcharge_cents = $4
- WHERE order_number = $5`,
+ WHERE ${whereCol} = $5`,
[session.id, payment_method, surcharge_pct, surcharge_cents, order_id],
);
}
diff --git a/api/src/routes/compliance-orders.ts b/api/src/routes/compliance-orders.ts
index 0ed73af..80f0722 100644
--- a/api/src/routes/compliance-orders.ts
+++ b/api/src/routes/compliance-orders.ts
@@ -925,6 +925,13 @@ router.post("/api/v1/compliance-orders/batch", async (req, res) => {
return;
}
+ // At least one paid service required
+ const hasPaidService = services.some(s => COMPLIANCE_SERVICES[s].price_cents > 0);
+ if (!hasPaidService) {
+ res.status(400).json({ error: "At least one paid service is required." });
+ return;
+ }
+
// Split services into discountable vs non-discountable (e.g., RA services)
const discountableServices = services.filter(s => COMPLIANCE_SERVICES[s].discountable);
const nonDiscountableServices = services.filter(s => !COMPLIANCE_SERVICES[s].discountable);
@@ -965,13 +972,24 @@ router.post("/api/v1/compliance-orders/batch", async (req, res) => {
try {
const orders: Record[] = [];
+ let discountDistributed = 0;
+ const discountableCount = discountableServices.length;
+ let discountableIdx = 0;
for (const slug of services) {
const svc = COMPLIANCE_SERVICES[slug];
// Distribute discount proportionally — only across discountable services
- const svcDiscount = svc.discountable
- ? Math.round(totalDiscountCents * svc.price_cents / (discountableTotal || 1))
- : 0;
+ // Last discountable service absorbs rounding remainder
+ let svcDiscount = 0;
+ if (svc.discountable && totalDiscountCents > 0) {
+ discountableIdx++;
+ if (discountableIdx === discountableCount) {
+ svcDiscount = totalDiscountCents - discountDistributed;
+ } else {
+ svcDiscount = Math.round(totalDiscountCents * svc.price_cents / (discountableTotal || 1));
+ }
+ discountDistributed += svcDiscount;
+ }
const orderNumber = generateOrderNumber();
const result = await pool.query(
diff --git a/api/src/routes/identity.ts b/api/src/routes/identity.ts
index e838add..48ab2c2 100644
--- a/api/src/routes/identity.ts
+++ b/api/src/routes/identity.ts
@@ -46,7 +46,9 @@ const STRIPE_IDENTITY_WEBHOOK_SECRET =
(process.env.NODE_ENV !== "production" && process.env.STRIPE_TEST_IDENTITY_WEBHOOK_SECRET?.trim()) ||
process.env.STRIPE_IDENTITY_WEBHOOK_SECRET ||
"";
-const DOMAIN = process.env.DOMAIN ? `https://${process.env.DOMAIN}` : "http://localhost:4321";
+const DOMAIN = process.env.DOMAIN
+ ? `https://${process.env.DOMAIN}`
+ : (() => { console.error("[identity] WARNING: DOMAIN env var not set — identity verification return URLs will fail"); return "https://performancewest.net"; })();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const stripe = STRIPE_SECRET_KEY ? new Stripe(STRIPE_SECRET_KEY, { apiVersion: "2026-03-25.dahlia" as any }) : null;