Fix 6 bugs from code review
Critical: - Single-order discount used wrong column names (discount_pct/discount_flat_cents → discount_type/discount_value). Discounts were silently $0. - Single-order discount skipped allowed_emails and expires_at checks - Free orders now set paid_at = NOW() High: - Discount usage now tracked in discount_usage table + current_uses incremented - Flat discount only replaces bundle when flat >= bundle (was always replacing) Minor: - Removed unused CDR profile fetch in EntityStep Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4125b0f09f
commit
d4c4ae003e
3 changed files with 49 additions and 16 deletions
|
|
@ -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],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<string, unknown>;
|
||||
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<string, unknown>;
|
||||
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)}`,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue