Fix 6 bugs found in compliance and checkout flows
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) <noreply@anthropic.com>
This commit is contained in:
parent
28d82912f7
commit
a7d7fee154
4 changed files with 32 additions and 9 deletions
|
|
@ -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],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<string, unknown>[] = [];
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue