Phase 3-5: - API: POST /api/v1/fcc-carrier-registration (order creation with pricing) - API: GET /api/v1/fcc-carrier-registration/:id (status) - API: GET /api/v1/fcc-carrier-registration/state-fees (formation fees) - Checkout: fcc_carrier_registration order type with Stripe line items - Payment handler: dispatch worker + send confirmation email - Pipeline handler: 8-step CRTC-style pipeline (formation → CORES → 499 → DC Agent → State PUC → RMD/CPNI/CALEA/BDC → add-ons → final review) - Job server dispatch map entry - Service page CTA updated to link to order page Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
250 lines
9.2 KiB
TypeScript
250 lines
9.2 KiB
TypeScript
/**
|
|
* FCC Carrier / ISP Registration — order creation & status API.
|
|
*
|
|
* POST /api/v1/fcc-carrier-registration — create order
|
|
* GET /api/v1/fcc-carrier-registration/:id — get order status
|
|
* GET /api/v1/fcc-carrier-registration/state-fees — formation fees per state
|
|
*/
|
|
|
|
import { Router, type Request, type Response } from "express";
|
|
import { pool } from "../db.js";
|
|
import { randomBytes } from "crypto";
|
|
|
|
const router = Router();
|
|
|
|
const BASE_FEE_CENTS = 129900; // $1,299
|
|
const FORMATION_MARKUP_CENTS = 2500; // $25 filing service
|
|
const STIR_SHAKEN_FEE_CENTS = 49900;
|
|
const OCN_FEE_CENTS = 265000;
|
|
const STATE_PUC_FEE_CENTS = 39900; // per state
|
|
|
|
function generateOrderNumber(): string {
|
|
const year = new Date().getFullYear();
|
|
const id = randomBytes(4).toString("hex").toUpperCase();
|
|
return `FCR-${year}-${id}`;
|
|
}
|
|
|
|
// ── GET /api/v1/fcc-carrier-registration/state-fees ─────────────────────────
|
|
|
|
router.get("/api/v1/fcc-carrier-registration/state-fees", async (_req: Request, res: Response) => {
|
|
try {
|
|
const { rows } = await pool.query(
|
|
`SELECT state_code, state_name, llc_formation_fee, corp_formation_fee,
|
|
expedited_fee, typical_processing_days
|
|
FROM state_filing_fees ORDER BY state_name`,
|
|
);
|
|
res.json({ states: rows, markup_cents: FORMATION_MARKUP_CENTS });
|
|
} catch (err) {
|
|
console.error("[fcc-carrier-reg] state-fees error:", err);
|
|
res.status(500).json({ error: "Could not load state fees" });
|
|
}
|
|
});
|
|
|
|
// ── POST /api/v1/fcc-carrier-registration ───────────────────────────────────
|
|
|
|
router.post("/api/v1/fcc-carrier-registration", async (req: Request, res: Response) => {
|
|
try {
|
|
const {
|
|
customer_name, customer_email, customer_phone,
|
|
entity_source, entity_legal_name, ein, formation_state, entity_type, frn,
|
|
contact_name, contact_email, contact_phone, contact_title,
|
|
address_street, address_city, address_state, address_zip,
|
|
service_wizard, services,
|
|
engagement_accepted,
|
|
} = req.body ?? {};
|
|
|
|
// Validate required fields
|
|
if (!customer_email || !customer_name) {
|
|
res.status(400).json({ error: "customer_name and customer_email are required." });
|
|
return;
|
|
}
|
|
if (!entity_source || !["existing", "new_formation"].includes(entity_source)) {
|
|
res.status(400).json({ error: "entity_source must be 'existing' or 'new_formation'." });
|
|
return;
|
|
}
|
|
if (!contact_name || !contact_email) {
|
|
res.status(400).json({ error: "contact_name and contact_email are required." });
|
|
return;
|
|
}
|
|
|
|
// Determine included services from wizard + checklist
|
|
const svcList: string[] = Array.isArray(services) ? services : [];
|
|
const wizardData = service_wizard || {};
|
|
|
|
const includeFormation = entity_source === "new_formation";
|
|
const includeRmd = true; // always included in base
|
|
const includeCpni = true;
|
|
const includeCalea = true;
|
|
const includeBdc = true;
|
|
const includeDcAgent = true;
|
|
const includeStirShaken = svcList.includes("stir_shaken");
|
|
const includeOcn = svcList.includes("ocn");
|
|
const statePucStates = svcList.includes("state_puc") ? (wizardData.puc_states || []) : [];
|
|
|
|
// Calculate pricing
|
|
let formationFeeCents = 0;
|
|
let stateFeeCents = 0;
|
|
if (includeFormation && formation_state) {
|
|
// Look up state filing fee
|
|
const feeCol = (entity_type === "corporation") ? "corp_formation_fee" : "llc_formation_fee";
|
|
try {
|
|
const feeResult = await pool.query(
|
|
`SELECT ${feeCol} AS fee FROM state_filing_fees WHERE state_code = $1`,
|
|
[formation_state.toUpperCase()],
|
|
);
|
|
if (feeResult.rows.length > 0 && feeResult.rows[0].fee) {
|
|
stateFeeCents = Number(feeResult.rows[0].fee);
|
|
}
|
|
} catch {}
|
|
formationFeeCents = FORMATION_MARKUP_CENTS;
|
|
}
|
|
|
|
let addonFeeCents = 0;
|
|
if (includeStirShaken) addonFeeCents += STIR_SHAKEN_FEE_CENTS;
|
|
if (includeOcn) addonFeeCents += OCN_FEE_CENTS;
|
|
const pucFeeCents = statePucStates.length * STATE_PUC_FEE_CENTS;
|
|
|
|
const orderNumber = generateOrderNumber();
|
|
|
|
const result = await pool.query(
|
|
`INSERT INTO fcc_carrier_registrations (
|
|
order_number, customer_email, customer_name, customer_phone,
|
|
entity_source, entity_legal_name, entity_type, formation_state, ein, frn,
|
|
contact_name, contact_email, contact_phone, contact_title,
|
|
address_street, address_city, address_state, address_zip,
|
|
service_wizard,
|
|
include_formation, include_dc_agent, include_rmd, include_cpni,
|
|
include_calea, include_bdc, include_stir_shaken, include_ocn,
|
|
state_puc_states,
|
|
service_fee_cents, formation_fee_cents, state_fee_cents,
|
|
puc_fee_cents, addon_fee_cents,
|
|
engagement_accepted_at, engagement_accepted_ip
|
|
) VALUES (
|
|
$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,
|
|
$19::jsonb,$20,$21,$22,$23,$24,$25,$26,$27,$28::text[],
|
|
$29,$30,$31,$32,$33,$34,$35
|
|
) RETURNING *`,
|
|
[
|
|
orderNumber,
|
|
customer_email.toLowerCase().trim(),
|
|
customer_name.trim(),
|
|
customer_phone || null,
|
|
entity_source,
|
|
entity_legal_name || null,
|
|
entity_type || null,
|
|
formation_state ? formation_state.toUpperCase() : null,
|
|
ein || null,
|
|
frn || null,
|
|
contact_name.trim(),
|
|
contact_email.toLowerCase().trim(),
|
|
contact_phone || null,
|
|
contact_title || null,
|
|
address_street || null,
|
|
address_city || null,
|
|
address_state ? address_state.toUpperCase() : null,
|
|
address_zip || null,
|
|
JSON.stringify(wizardData),
|
|
includeFormation,
|
|
includeDcAgent,
|
|
includeRmd,
|
|
includeCpni,
|
|
includeCalea,
|
|
includeBdc,
|
|
includeStirShaken,
|
|
includeOcn,
|
|
statePucStates,
|
|
BASE_FEE_CENTS,
|
|
formationFeeCents,
|
|
stateFeeCents,
|
|
pucFeeCents,
|
|
addonFeeCents,
|
|
engagement_accepted ? new Date().toISOString() : null,
|
|
engagement_accepted ? (req.ip || req.headers["x-forwarded-for"] || null) : null,
|
|
],
|
|
);
|
|
|
|
const order = result.rows[0];
|
|
|
|
// If formation needed, create a formation_orders row
|
|
if (includeFormation && formation_state) {
|
|
try {
|
|
const formationOrderNumber = `FO-${new Date().getFullYear()}-${randomBytes(3).toString("hex").toUpperCase()}`;
|
|
await pool.query(
|
|
`INSERT INTO formation_orders (
|
|
order_number, customer_name, customer_email, customer_phone,
|
|
state_code, entity_type, entity_name,
|
|
principal_address, principal_city, principal_state, principal_zip,
|
|
service_fee_cents, state_fee_cents, payment_status
|
|
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,'paid')`,
|
|
[
|
|
formationOrderNumber,
|
|
customer_name.trim(),
|
|
customer_email.toLowerCase().trim(),
|
|
customer_phone || null,
|
|
formation_state.toUpperCase(),
|
|
entity_type || "llc",
|
|
entity_legal_name || null,
|
|
address_street || null,
|
|
address_city || null,
|
|
address_state ? address_state.toUpperCase() : null,
|
|
address_zip || null,
|
|
FORMATION_MARKUP_CENTS,
|
|
stateFeeCents,
|
|
],
|
|
);
|
|
// Link formation order to carrier registration
|
|
await pool.query(
|
|
`UPDATE fcc_carrier_registrations SET formation_order_number = $1 WHERE order_number = $2`,
|
|
[formationOrderNumber, orderNumber],
|
|
);
|
|
} catch (formErr) {
|
|
console.warn("[fcc-carrier-reg] Formation order creation failed (non-fatal):", formErr);
|
|
}
|
|
}
|
|
|
|
const totalCents = BASE_FEE_CENTS + formationFeeCents + stateFeeCents + pucFeeCents + addonFeeCents;
|
|
|
|
console.log(
|
|
`[fcc-carrier-reg] Created ${orderNumber}: ${entity_source} for ${customer_email} — $${(totalCents / 100).toFixed(2)}`,
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
order_number: orderNumber,
|
|
order_id: orderNumber,
|
|
order_type: "fcc_carrier_registration",
|
|
total_cents: totalCents,
|
|
pricing: {
|
|
base: BASE_FEE_CENTS,
|
|
formation: formationFeeCents + stateFeeCents,
|
|
addons: addonFeeCents,
|
|
puc: pucFeeCents,
|
|
total: totalCents,
|
|
},
|
|
});
|
|
} catch (err) {
|
|
console.error("[fcc-carrier-reg] Create error:", err);
|
|
res.status(500).json({ error: "Could not create order." });
|
|
}
|
|
});
|
|
|
|
// ── GET /api/v1/fcc-carrier-registration/:id ────────────────────────────────
|
|
|
|
router.get("/api/v1/fcc-carrier-registration/:id", async (req: Request, res: Response) => {
|
|
try {
|
|
const { rows } = await pool.query(
|
|
`SELECT * FROM fcc_carrier_registrations WHERE order_number = $1`,
|
|
[req.params.id],
|
|
);
|
|
if (rows.length === 0) {
|
|
res.status(404).json({ error: "Order not found" });
|
|
return;
|
|
}
|
|
res.json(rows[0]);
|
|
} catch (err) {
|
|
console.error("[fcc-carrier-reg] Get error:", err);
|
|
res.status(500).json({ error: "Could not load order." });
|
|
}
|
|
});
|
|
|
|
export default router;
|