diff --git a/api/src/routes/checkout.ts b/api/src/routes/checkout.ts index 58005dd..87c24d5 100644 --- a/api/src/routes/checkout.ts +++ b/api/src/routes/checkout.ts @@ -924,7 +924,7 @@ router.post("/api/v1/checkout/create-session", async (req, res) => { if (zt) { const whereCol = order_type === "compliance_batch" ? "batch_id" : "order_number"; await pool.query( - `UPDATE ${zt} SET payment_status = 'paid', payment_method = 'free', surcharge_cents = 0, surcharge_pct = 0 WHERE ${whereCol} = $1`, + `UPDATE ${zt} SET payment_status = 'paid', payment_method = 'free', paid_at = NOW(), surcharge_cents = 0, surcharge_pct = 0 WHERE ${whereCol} = $1`, [order_id], ); } diff --git a/api/src/routes/compliance-orders.ts b/api/src/routes/compliance-orders.ts index 9aa91be..3a1af8a 100644 --- a/api/src/routes/compliance-orders.ts +++ b/api/src/routes/compliance-orders.ts @@ -803,19 +803,30 @@ router.post("/api/v1/compliance-orders", async (req, res) => { if (discount_code) { try { const disc = await pool.query( - `SELECT discount_pct, discount_flat_cents, active + `SELECT discount_type, discount_value, allowed_emails FROM discount_codes - WHERE code = $1 AND active = true`, + WHERE code = $1 AND active = true AND (expires_at IS NULL OR expires_at > now())`, [discount_code.toUpperCase().trim()], ); if (disc.rows.length > 0) { const d = disc.rows[0] as Record; - if ((d.discount_pct as number) > 0) { - discount_cents = Math.round( - (resolved_fee_cents * (d.discount_pct as number)) / 100, - ); - } else if ((d.discount_flat_cents as number) > 0) { - discount_cents = d.discount_flat_cents as number; + // Check allowed_emails restriction + const allowedEmails = d.allowed_emails as string[] | null; + if (allowedEmails && allowedEmails.length > 0) { + const ce = (customer_email || "").toLowerCase().trim(); + if (!allowedEmails.map((e: string) => e.toLowerCase()).includes(ce)) { + disc.rows.length = 0; // reject silently + } + } + } + if (disc.rows.length > 0) { + const d = disc.rows[0] as Record; + const discType = d.discount_type as string; + const discValue = d.discount_value as number; + if (discType === "percent" && discValue > 0) { + discount_cents = Math.round((resolved_fee_cents * discValue) / 100); + } else if (discType === "flat" && discValue > 0) { + discount_cents = Math.min(discValue, resolved_fee_cents); } } } catch { @@ -1014,10 +1025,16 @@ router.post("/api/v1/compliance-orders/batch", async (req, res) => { promoDiscountCents = Math.round(afterBundle * discValue / 100); } } else if (discType === "flat" && discValue > 0) { - // Flat discount replaces bundle discount (don't stack) - bundleDiscountPct = 0; - bundleDiscountCents = 0; - promoDiscountCents = Math.min(discValue, discountableTotal); + // Flat discount: use whichever is larger (flat promo or bundle %) + const flatAmt = Math.min(discValue, discountableTotal); + if (flatAmt >= bundleDiscountCents) { + bundleDiscountPct = 0; + bundleDiscountCents = 0; + promoDiscountCents = flatAmt; + } else { + // Bundle discount is better — keep it, skip flat promo + promoDiscountCents = 0; + } } } } catch { /* discount_codes table may not exist */ } @@ -1080,6 +1097,24 @@ router.post("/api/v1/compliance-orders/batch", async (req, res) => { orders.push(result.rows[0]); } + // Record discount usage + increment counter + if (discount_code && totalDiscountCents > 0) { + try { + await pool.query( + `INSERT INTO discount_usage (discount_code_id, code, order_type, order_id, customer_email, discount_amount, ip_address) + SELECT id, code, 'compliance_batch', $2, $3, $4, $5 + FROM discount_codes WHERE code = $1`, + [discount_code.toUpperCase().trim(), batchId, customer_email.toLowerCase().trim(), totalDiscountCents, req.ip || req.headers["x-forwarded-for"] || null], + ); + await pool.query( + `UPDATE discount_codes SET current_uses = current_uses + 1, updated_at = now() WHERE code = $1`, + [discount_code.toUpperCase().trim()], + ); + } catch (usageErr) { + console.warn("[compliance-orders] Discount usage tracking failed (non-fatal):", usageErr); + } + } + console.log( `[compliance-orders] Batch ${batchId}: ${services.length} orders for ${customer_email} — $${(totalCents / 100).toFixed(2)}`, ); diff --git a/site/src/components/intake/steps/EntityStep.astro b/site/src/components/intake/steps/EntityStep.astro index 5058503..82586bc 100644 --- a/site/src/components/intake/steps/EntityStep.astro +++ b/site/src/components/intake/steps/EntityStep.astro @@ -338,9 +338,7 @@ entity.contact_name = d.rmd.contact_name; } intoInputs(entity); - // Also try loading from our DB in case we have a richer record - const dbResp = await fetch(`${API}/api/v1/cdr/profile/by-entity/${frn}`).catch(() => null); - // Look up by FRN in telecom_entities + // Look up by FRN in telecom_entities (richer record) try { const teResp = await fetch(`${API}/api/v1/entities/telecom?frn=${frn}`); if (teResp.ok) {