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
|
|
@ -205,10 +205,10 @@ export async function sendOrderConfirmationEmail(params: OrderConfirmationParams
|
|||
</td></tr>
|
||||
</table>
|
||||
|
||||
<p style="margin:0 0 16px;font-size:13px;color:#6b7280;line-height:1.5;">
|
||||
${(order_type === "compliance" || order_type === "compliance_batch") ? `<p style="margin:0 0 16px;font-size:13px;color:#6b7280;line-height:1.5;">
|
||||
FCC compliance fees are tax deductible as ordinary business expenses under IRC § 162.
|
||||
A formal receipt will be sent separately.
|
||||
</p>
|
||||
</p>` : ""}
|
||||
|
||||
<h2 style="margin:0 0 12px;font-size:16px;font-weight:700;color:#111827;">What happens next</h2>
|
||||
${stepsHtml}
|
||||
|
|
@ -222,7 +222,7 @@ export async function sendOrderConfirmationEmail(params: OrderConfirmationParams
|
|||
|
||||
<p style="margin:0;font-size:14px;color:#6b7280;">
|
||||
Questions? Reply to this email or reach us at
|
||||
<a href="mailto:support@performancewest.net" style="color:#1e3a5f;">support@performancewest.net</a>
|
||||
<a href="mailto:info@performancewest.net" style="color:#1e3a5f;">info@performancewest.net</a>
|
||||
or call <a href="tel:18884110383" style="color:#1e3a5f;">1-888-411-0383</a>.
|
||||
</p>
|
||||
`;
|
||||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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…
Reference in a new issue