new-site/api/src/routes/bundles.ts
justin 118d24cc1a Rename 'IPES & ISP Registrations' to 'FCC Carrier / ISP Registration'
Updated across 61 static HTML files (nav links), bundles catalog,
service page title/description/heading, and llms.txt.
URL stays /services/telecom/ipes-isp (no redirect needed).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 08:06:47 -05:00

242 lines
11 KiB
TypeScript

/**
* Service bundle routes.
*
* Bundles give 20% off when purchasing all services in a category
* or a curated cross-category package.
*/
import { Router } from "express";
import { pool } from "../db.js";
import { submitLimiter } from "../middleware/rate-limit.js";
import { v4 as uuidv4 } from "uuid";
const router = Router();
// Service prices in cents (must match the current site pricing)
// discountable: true = our service fee (eligible for bundle discount)
// false = pass-through fee (state fees — NOT discountable)
const SERVICE_PRICES: Record<string, { price: number; name: string; quote: boolean; discountable: boolean }> = {
"flsa-audit": { price: 149900, name: "FLSA / Wage & Hour Audit", quote: false, discountable: true },
"contractor-classification": { price: 49900, name: "Contractor Classification Review", quote: false, discountable: true },
"handbook-review": { price: 99900, name: "Employee Handbook Review", quote: false, discountable: true },
"policy-development": { price: 0, name: "Workplace Policy Development", quote: true, discountable: true },
"ccpa-audit": { price: 249900, name: "CCPA/CPRA Compliance Audit", quote: false, discountable: true },
"privacy-policy": { price: 49900, name: "Privacy Policy Generation", quote: false, discountable: true },
"data-mapping": { price: 0, name: "Data Mapping & Inventory", quote: true, discountable: true },
"breach-response": { price: 199900, name: "Breach Response Planning", quote: false, discountable: true },
"consent-audit": { price: 129900, name: "SMS/Call Consent Audit", quote: false, discountable: true },
"dnc-compliance": { price: 79900, name: "DNC Compliance Review", quote: false, discountable: true },
"campaign-review": { price: 59900, name: "Campaign Compliance Review", quote: false, discountable: true },
"fcc-499a": { price: 79900, name: "FCC 499A Filing", quote: false, discountable: true },
"stir-shaken": { price: 0, name: "STIR/SHAKEN Implementation", quote: true, discountable: true },
"ipes-isp": { price: 129900, name: "FCC Carrier / ISP Registration", quote: false, discountable: true },
"database-management": { price: 49900, name: "Telecom Database Management", quote: false, discountable: true },
"state-puc": { price: 39900, name: "State PUC/PSC Filings", quote: false, discountable: true },
"formation": { price: 17900, name: "Business Formation (Basic)", quote: false, discountable: true },
"state-registration": { price: 24900, name: "State Registration", quote: false, discountable: true },
"annual-reports": { price: 9900, name: "Annual Report Filing", quote: false, discountable: true },
"registered-agent": { price: 9900, name: "Registered Agent Service", quote: false, discountable: false },
};
// These are NEVER discountable — pass-through costs and third-party fees
// State filing fees: calculated separately per state, added at order time
function calculateBundlePrice(services: string[], discountPct: number) {
let discountableTotal = 0; // our service fees — eligible for bundle discount
let nonDiscountableTotal = 0; // pass-through fees — never discounted
let hasQuoteItems = false;
const items: Array<{ slug: string; name: string; price: number; quote: boolean; discountable: boolean }> = [];
for (const slug of services) {
const svc = SERVICE_PRICES[slug];
if (!svc) continue;
items.push({ slug, name: svc.name, price: svc.price, quote: svc.quote, discountable: svc.discountable });
if (svc.quote) {
hasQuoteItems = true;
} else if (svc.discountable) {
discountableTotal += svc.price;
} else {
nonDiscountableTotal += svc.price;
}
}
const originalTotal = discountableTotal + nonDiscountableTotal;
const discountCents = Math.round((discountableTotal * discountPct) / 100);
const finalTotal = originalTotal - discountCents;
return { items, originalTotal, discountableTotal, nonDiscountableTotal, discountPct, discountCents, finalTotal, hasQuoteItems };
}
// GET /api/v1/bundles — List all active bundles with calculated pricing
router.get("/api/v1/bundles", async (_req, res) => {
try {
const result = await pool.query(
"SELECT * FROM service_bundles WHERE active = TRUE ORDER BY display_order, name",
);
const bundles = result.rows.map((b: any) => {
const calc = calculateBundlePrice(b.services, b.discount_pct);
return {
slug: b.slug,
name: b.name,
description: b.description,
category: b.category,
discount_pct: b.discount_pct,
services: calc.items,
original_total_cents: calc.originalTotal,
discount_cents: calc.discountCents,
final_total_cents: calc.finalTotal,
has_quote_items: calc.hasQuoteItems,
savings_text: `Save $${(calc.discountCents / 100).toFixed(0)} (${b.discount_pct}% off)`,
};
});
res.json({ bundles });
} catch (err) {
console.error("[bundles] List error:", err);
res.status(500).json({ error: "Could not load bundles." });
}
});
// GET /api/v1/bundles/:slug — Single bundle with pricing
router.get("/api/v1/bundles/:slug", async (req, res) => {
try {
const result = await pool.query(
"SELECT * FROM service_bundles WHERE slug = $1 AND active = TRUE",
[req.params.slug],
);
if (result.rows.length === 0) {
res.status(404).json({ error: "Bundle not found." });
return;
}
const b = result.rows[0];
const calc = calculateBundlePrice(b.services, b.discount_pct);
res.json({
bundle: {
slug: b.slug,
name: b.name,
description: b.description,
category: b.category,
discount_pct: b.discount_pct,
services: calc.items,
original_total_cents: calc.originalTotal,
discount_cents: calc.discountCents,
final_total_cents: calc.finalTotal,
has_quote_items: calc.hasQuoteItems,
},
});
} catch (err) {
console.error("[bundles] Get error:", err);
res.status(500).json({ error: "Could not load bundle." });
}
});
// POST /api/v1/bundles/order — Place a bundle order
router.post("/api/v1/bundles/order", submitLimiter, async (req, res) => {
try {
const {
bundle_slug, customer_name, customer_email, customer_phone,
customer_company, discount_code,
} = req.body ?? {};
if (!bundle_slug || !customer_name || !customer_email) {
res.status(400).json({ error: "Missing required fields: bundle_slug, customer_name, customer_email" });
return;
}
// Get the bundle
const bundleResult = await pool.query(
"SELECT * FROM service_bundles WHERE slug = $1 AND active = TRUE",
[bundle_slug],
);
if (bundleResult.rows.length === 0) {
res.status(404).json({ error: "Bundle not found." });
return;
}
const bundle = bundleResult.rows[0];
const calc = calculateBundlePrice(bundle.services, bundle.discount_pct);
// Discount code — only applies to discountable service fees (NOT state fees)
let discountCodeCents = 0;
if (discount_code) {
const dcResult = await pool.query("SELECT * FROM discount_codes WHERE code = $1 AND active = TRUE", [discount_code.toUpperCase()]);
if (dcResult.rows.length > 0) {
const dc = dcResult.rows[0];
// Apply discount code ONLY to the already-discounted service fee total (after bundle discount)
const discountableAfterBundle = calc.discountableTotal - calc.discountCents;
if (dc.discount_type === "percent") {
discountCodeCents = Math.round((discountableAfterBundle * dc.discount_value) / 100);
} else {
discountCodeCents = Math.min(dc.discount_value, discountableAfterBundle);
}
}
}
// Grand total: discounted service fees + non-discountable fees
const grandTotal = calc.finalTotal - discountCodeCents;
const year = new Date().getFullYear();
const short = uuidv4().split("-")[0]!.toUpperCase();
const orderNumber = `BDL-${year}-${short}`;
const result = await pool.query(
`INSERT INTO bundle_orders (
bundle_slug, order_number, customer_name, customer_email, customer_phone, customer_company,
original_total_cents, discount_pct, discount_cents, final_total_cents,
discount_code, discount_code_cents, status
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,'received')
RETURNING id, order_number`,
[
bundle_slug, orderNumber, customer_name, customer_email.toLowerCase().trim(),
customer_phone || null, customer_company || null,
calc.originalTotal, bundle.discount_pct, calc.discountCents, grandTotal,
discount_code ? discount_code.toUpperCase() : null, discountCodeCents,
],
);
// Create commission if this order used an agent's referral code
if (discount_code) {
try {
const { createCommission } = await import("./agents.js");
// Check if the discount code belongs to a sales agent
const agentCheck = await pool.query(
"SELECT sa.agent_code FROM sales_agents sa JOIN discount_codes dc ON sa.discount_code_id = dc.id WHERE dc.code = $1 AND sa.active = TRUE",
[discount_code.toUpperCase()],
);
if (agentCheck.rows.length > 0) {
await createCommission({
agentCode: agentCheck.rows[0].agent_code,
orderType: "bundle",
orderId: result.rows[0].id,
orderNumber: result.rows[0].order_number,
serviceSlug: bundle_slug,
customerName: customer_name,
customerEmail: customer_email,
orderAmountCents: grandTotal,
discountCents: discountCodeCents,
});
}
} catch (commErr) {
console.error("[bundles] Commission creation failed (non-blocking):", commErr);
}
}
res.status(201).json({
success: true,
order_number: result.rows[0].order_number,
bundle: bundle.name,
original_total: `$${(calc.originalTotal / 100).toFixed(2)}`,
bundle_discount: `- $${(calc.discountCents / 100).toFixed(2)} (${bundle.discount_pct}% bundle discount)`,
code_discount: discountCodeCents > 0 ? `- $${(discountCodeCents / 100).toFixed(2)}` : null,
total: `$${(grandTotal / 100).toFixed(2)}`,
message: "Bundle order received. We will begin processing within one business day.",
});
} catch (err) {
console.error("[bundles] Order error:", err);
res.status(500).json({ error: "Could not place bundle order." });
}
});
export default router;