diff --git a/api/src/routes/compliance-orders.ts b/api/src/routes/compliance-orders.ts index da830eb..70fc2ff 100644 --- a/api/src/routes/compliance-orders.ts +++ b/api/src/routes/compliance-orders.ts @@ -975,25 +975,35 @@ router.post("/api/v1/compliance-orders/batch", async (req, res) => { const govFeeTotal = services.reduce((sum, s) => sum + (COMPLIANCE_SERVICES[s].gov_fee_cents || 0), 0); const subtotal = discountableTotal + nonDiscountableTotal + govFeeTotal; - // Bundle discount applies only to discountable services - const bundleDiscountPct = discountableServices.filter(s => COMPLIANCE_SERVICES[s].price_cents > 0).length >= 2 ? 15 : 0; - const bundleDiscountCents = Math.round(discountableTotal * bundleDiscountPct / 100); + // Bundle discount applies only to discountable services (2+ services = 15%) + let bundleDiscountPct = discountableServices.filter(s => COMPLIANCE_SERVICES[s].price_cents > 0).length >= 2 ? 15 : 0; + let bundleDiscountCents = Math.round(discountableTotal * bundleDiscountPct / 100); - // Referral/promo code discount also applies only to discountable services + // Referral/promo code discount — replaces bundle discount if promo is >= bundle % let promoDiscountCents = 0; if (discount_code) { try { const disc = await pool.query( - `SELECT discount_pct, discount_flat_cents FROM discount_codes WHERE code = $1 AND active = true`, + `SELECT discount_type, discount_value FROM discount_codes 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; - const discountableAfterBundle = discountableTotal - bundleDiscountCents; - if ((d.discount_pct as number) > 0) { - promoDiscountCents = Math.round(discountableAfterBundle * (d.discount_pct as number) / 100); - } else if ((d.discount_flat_cents as number) > 0) { - promoDiscountCents = Math.min(d.discount_flat_cents as number, discountableAfterBundle); + const discType = d.discount_type as string; + const discValue = d.discount_value as number; + if (discType === "percent" && discValue > 0) { + // If promo % >= bundle %, replace bundle discount entirely (don't stack) + if (discValue >= bundleDiscountPct) { + bundleDiscountPct = 0; + bundleDiscountCents = 0; + promoDiscountCents = Math.round(discountableTotal * discValue / 100); + } else { + // Smaller promo stacks on top of bundle + const afterBundle = discountableTotal - bundleDiscountCents; + promoDiscountCents = Math.round(afterBundle * discValue / 100); + } + } else if (discType === "flat" && discValue > 0) { + promoDiscountCents = Math.min(discValue, discountableTotal - bundleDiscountCents); } } } catch { /* discount_codes table may not exist */ } diff --git a/site/public/order/fcc-compliance/index.html b/site/public/order/fcc-compliance/index.html index e715bf0..43e3041 100644 --- a/site/public/order/fcc-compliance/index.html +++ b/site/public/order/fcc-compliance/index.html @@ -182,6 +182,7 @@ function usd(c){return "$"+(c/100).toLocaleString("en-US",{minimumFractionDigits var params=new URLSearchParams(window.location.search); var rawKeys=(params.get("services")||"").split(",").map(function(s){return s.trim()}).filter(Boolean); var frn=params.get("frn")||""; +var promoFromUrl=params.get("code")||params.get("promo")||""; var slugs=[]; var seen={}; rawKeys.forEach(function(k){var s=CHECK_TO_SLUG[k]||k; if(SLUG_META[s]&&!seen[s]){slugs.push(s);seen[s]=1}}); if(slugs.indexOf("fcc-499a")>=0&&slugs.indexOf("fcc-499a-499q")>=0) slugs.splice(slugs.indexOf("fcc-499a"),1); @@ -326,15 +327,19 @@ function renderServices() { var svcTotal=discountableTotal+nonDiscountableTotal; var subtotal=svcTotal+govTotal; var discountableSlugs = selectedSlugs.filter(function(s){ var m=SLUG_META[s]||{}; return m.price > 0 && !m.nodiscount; }); - var hasDisc=discountableSlugs.length>=2; + var hasPromo=!!promoFromUrl; + var hasDisc=!hasPromo && discountableSlugs.length>=2; var disc=hasDisc?Math.round(discountableTotal*0.15):0; + var promoDisc=hasPromo?Math.round(discountableTotal*0.25):0; var summary=document.createElement("div"); summary.className="mt-4 p-4 bg-gray-50 border border-gray-200 rounded-lg"; var rows='
'+selectedSlugs.length+' service'+(selectedSlugs.length>1?'s':'')+''+usd(svcTotal)+'
'; if(govTotal>0) rows+='
Government filing fees (passthrough)'+usd(govTotal)+'
'; - if(hasDisc) rows+='
Bundle discount (15%)-'+usd(disc)+'
'; - rows+='
Total'+usd(subtotal-disc)+'
'; + if(hasPromo) rows+='
Memorial Day discount (25%) — code '+promoFromUrl.toUpperCase()+'-'+usd(promoDisc)+'
'; + else if(hasDisc) rows+='
Bundle discount (15%)-'+usd(disc)+'
'; + var totalDisc=hasPromo?promoDisc:disc; + rows+='
Total'+usd(subtotal-totalDisc)+'
'; summary.innerHTML=rows; listEl.appendChild(summary); @@ -397,6 +402,9 @@ function renderServices() { ''; listEl.appendChild(actions); + // Pre-fill promo code from URL + if(promoFromUrl){var promoEl=document.getElementById("pw-promo");if(promoEl){promoEl.value=promoFromUrl.toUpperCase();promoEl.readOnly=true;promoEl.style.background="#f0fdf4";promoEl.style.borderColor="#86efac";}} + // Batch checkout handler document.getElementById("pw-batch-form").addEventListener("submit", function(e){ e.preventDefault();