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>
242 lines
11 KiB
TypeScript
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;
|