Includes: API (Express/TypeScript), Astro site, Python workers, document generators, FCC compliance tools, Canada CRTC formation, Ansible infrastructure, and deployment scripts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
326 lines
12 KiB
TypeScript
326 lines
12 KiB
TypeScript
import { Router } from "express";
|
|
import { pool } from "../db.js";
|
|
import { submitLimiter } from "../middleware/rate-limit.js";
|
|
import { v4 as uuidv4 } from "uuid";
|
|
import { createFormationOrder } from "../erpnext-client.js";
|
|
|
|
const router = Router();
|
|
|
|
// GET /api/v1/states — Return all states with fees for the order form
|
|
router.get("/api/v1/states", async (_req, res) => {
|
|
try {
|
|
const result = await pool.query(
|
|
`SELECT state_code, state_name, llc_formation_fee, corp_formation_fee,
|
|
llc_annual_fee, llc_annual_period, corp_annual_fee, corp_annual_period,
|
|
expedited_fee, expedited_label, publication_required, publication_est_cost,
|
|
franchise_tax_required, franchise_tax_min, franchise_tax_notes,
|
|
business_license_required, business_license_fee,
|
|
portal_name, online_filing_available, typical_processing_days, notes
|
|
FROM state_filing_fees ORDER BY state_name`,
|
|
);
|
|
res.json({ states: result.rows });
|
|
} catch (err) {
|
|
console.error("[formations] Error fetching states:", err);
|
|
res.status(500).json({ error: "Could not load state data." });
|
|
}
|
|
});
|
|
|
|
// POST /api/v1/formations — Create a formation order
|
|
router.post("/api/v1/formations", submitLimiter, async (req, res) => {
|
|
try {
|
|
const {
|
|
customer_name,
|
|
customer_email,
|
|
customer_phone,
|
|
customer_company,
|
|
state_code,
|
|
entity_type,
|
|
entity_name,
|
|
entity_name_alt,
|
|
management_type,
|
|
purpose,
|
|
principal_address,
|
|
principal_city,
|
|
principal_state,
|
|
principal_zip,
|
|
mailing_address,
|
|
mailing_city,
|
|
mailing_state,
|
|
mailing_zip,
|
|
members,
|
|
include_ra_service,
|
|
include_ein,
|
|
include_operating_agreement,
|
|
expedited,
|
|
state_fee_cents,
|
|
service_fee_cents,
|
|
expedited_fee_cents,
|
|
total_cents,
|
|
discount_code,
|
|
} = req.body ?? {};
|
|
|
|
// Validation
|
|
if (
|
|
!customer_name ||
|
|
!customer_email ||
|
|
!state_code ||
|
|
!entity_type ||
|
|
!entity_name
|
|
) {
|
|
res.status(400).json({
|
|
error:
|
|
"Missing required fields: name, email, state, entity type, entity name",
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (!["llc", "corporation", "s_corp"].includes(entity_type)) {
|
|
res
|
|
.status(400)
|
|
.json({ error: "Entity type must be: llc, corporation, or s_corp" });
|
|
return;
|
|
}
|
|
|
|
if (
|
|
!customer_email ||
|
|
typeof customer_email !== "string" ||
|
|
!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(customer_email)
|
|
) {
|
|
res.status(400).json({ error: "Valid email address is required." });
|
|
return;
|
|
}
|
|
|
|
// --- Discount code validation ---
|
|
let discountCents = 0;
|
|
let validatedDiscountCode: string | null = null;
|
|
let discountCodeId: number | null = null;
|
|
let referralPayout = 0;
|
|
|
|
if (discount_code && typeof discount_code === "string" && discount_code.trim()) {
|
|
const dc = discount_code.toUpperCase().trim();
|
|
const dcResult = await pool.query("SELECT * FROM discount_codes WHERE code = $1", [dc]);
|
|
|
|
if (dcResult.rows.length > 0) {
|
|
const row = dcResult.rows[0];
|
|
const now = new Date();
|
|
const isActive = row.active;
|
|
const notExpired = !row.expires_at || new Date(row.expires_at) > now;
|
|
const hasStarted = new Date(row.starts_at) <= now;
|
|
const underLimit = row.max_uses === null || row.current_uses < row.max_uses;
|
|
|
|
// Check per-email limit
|
|
let emailOk = true;
|
|
if (customer_email && row.max_uses_per_email > 0) {
|
|
const eu = await pool.query(
|
|
"SELECT COUNT(*) as cnt FROM discount_usage WHERE code = $1 AND customer_email = $2",
|
|
[dc, customer_email.toLowerCase().trim()],
|
|
);
|
|
emailOk = parseInt(eu.rows[0]?.cnt || "0", 10) < row.max_uses_per_email;
|
|
}
|
|
|
|
// Check service scope
|
|
let scopeOk = true;
|
|
if (row.applies_to) {
|
|
const allowed = row.applies_to.split(",").map((s: string) => s.trim().toLowerCase());
|
|
scopeOk = allowed.includes("formation");
|
|
}
|
|
|
|
if (isActive && notExpired && hasStarted && underLimit && emailOk && scopeOk) {
|
|
validatedDiscountCode = dc;
|
|
discountCodeId = row.id;
|
|
|
|
// Discount applies ONLY to our service fee — never to state filing fees,
|
|
// expedited fees, or attorney review fees. Those are pass-through costs.
|
|
const serviceFee = service_fee_cents || 17900;
|
|
if (row.discount_type === "percent") {
|
|
discountCents = Math.round((serviceFee * row.discount_value) / 100);
|
|
} else {
|
|
discountCents = Math.min(row.discount_value, serviceFee);
|
|
}
|
|
|
|
// Referral payout based on service fee only (not state fees)
|
|
if (row.referral_pct > 0) {
|
|
referralPayout = Math.round((serviceFee * row.referral_pct) / 100);
|
|
}
|
|
}
|
|
}
|
|
// Silently ignore invalid codes — don't block the order
|
|
}
|
|
|
|
const finalServiceFee = (service_fee_cents || 17900) - discountCents;
|
|
const finalTotal = (total_cents || 0) - discountCents;
|
|
|
|
const year = new Date().getFullYear();
|
|
const short = uuidv4().split("-")[0]!.toUpperCase();
|
|
const orderNumber = `PW-${year}-${short}`;
|
|
|
|
const principalFull = principal_address
|
|
? `${principal_address}, ${principal_city}, ${principal_state} ${principal_zip}`
|
|
: null;
|
|
const mailingFull = mailing_address
|
|
? `${mailing_address}, ${mailing_city}, ${mailing_state} ${mailing_zip}`
|
|
: null;
|
|
|
|
const result = await pool.query(
|
|
`INSERT INTO formation_orders (
|
|
order_number, customer_name, customer_email, customer_phone, customer_company,
|
|
state_code, entity_type, entity_name, entity_name_alt,
|
|
management_type, principal_address, mailing_address,
|
|
members_json, include_ra_service, include_ein, include_operating_agreement,
|
|
expedited, state_fee_cents, service_fee_cents, expedited_fee_cents, total_cents,
|
|
status
|
|
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,'received')
|
|
RETURNING id, order_number`,
|
|
[
|
|
orderNumber,
|
|
customer_name.trim(),
|
|
customer_email.toLowerCase().trim(),
|
|
customer_phone || null,
|
|
customer_company || null,
|
|
state_code.toUpperCase(),
|
|
entity_type,
|
|
entity_name.trim(),
|
|
entity_name_alt || null,
|
|
management_type || "member_managed",
|
|
principalFull,
|
|
mailingFull,
|
|
JSON.stringify(members || []),
|
|
include_ra_service !== false,
|
|
include_ein || false,
|
|
include_operating_agreement || false,
|
|
expedited || false,
|
|
state_fee_cents || 0,
|
|
finalServiceFee,
|
|
expedited_fee_cents || 0,
|
|
finalTotal,
|
|
],
|
|
);
|
|
|
|
const orderId = result.rows[0].id;
|
|
|
|
// Push to ERPNext as source of truth — non-blocking, don't fail the response
|
|
const ENTITY_TYPE_MAP: Record<string, "LLC" | "Corporation" | "S-Corp"> = {
|
|
llc: "LLC",
|
|
corporation: "Corporation",
|
|
s_corp: "S-Corp",
|
|
};
|
|
const MGMT_TYPE_MAP: Record<string, "Member Managed" | "Manager Managed"> = {
|
|
member_managed: "Member Managed",
|
|
manager_managed: "Manager Managed",
|
|
};
|
|
|
|
try {
|
|
await createFormationOrder({
|
|
order_number: orderNumber,
|
|
customer: customer_name.trim(),
|
|
customer_email: customer_email.toLowerCase().trim(),
|
|
customer_phone: customer_phone || undefined,
|
|
state_code: state_code.toUpperCase(),
|
|
entity_type: ENTITY_TYPE_MAP[entity_type] || "LLC",
|
|
entity_name: entity_name.trim(),
|
|
entity_name_alt: entity_name_alt || undefined,
|
|
management_type: MGMT_TYPE_MAP[management_type || "member_managed"] || "Member Managed",
|
|
purpose: purpose || undefined,
|
|
principal_address: principalFull || undefined,
|
|
mailing_address: mailingFull || undefined,
|
|
members: members || [],
|
|
include_ra_service: include_ra_service !== false,
|
|
include_ein: include_ein || false,
|
|
include_operating_agreement: include_operating_agreement || false,
|
|
expedited: expedited || false,
|
|
state_fee_cents: state_fee_cents || 0,
|
|
service_fee_cents: finalServiceFee,
|
|
discount_code: validatedDiscountCode || undefined,
|
|
discount_cents: discountCents > 0 ? discountCents : undefined,
|
|
total_cents: finalTotal,
|
|
});
|
|
} catch (erpErr) {
|
|
console.error("[formations] ERPNext createFormationOrder failed (non-fatal):", erpErr);
|
|
}
|
|
|
|
// Record discount usage
|
|
if (validatedDiscountCode && discountCodeId) {
|
|
const ip = (req as any).clientIp || req.ip || "";
|
|
await pool.query(
|
|
`INSERT INTO discount_usage (discount_code_id, code, order_type, order_id, customer_email, discount_amount, referral_payout, ip_address)
|
|
VALUES ($1, $2, 'formation', $3, $4, $5, $6, $7)`,
|
|
[discountCodeId, validatedDiscountCode, orderId, customer_email.toLowerCase().trim(), discountCents, referralPayout, ip],
|
|
);
|
|
// Increment usage counter
|
|
await pool.query(
|
|
"UPDATE discount_codes SET current_uses = current_uses + 1, updated_at = now() WHERE id = $1",
|
|
[discountCodeId],
|
|
);
|
|
}
|
|
|
|
// 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: "formation",
|
|
orderId: result.rows[0].id,
|
|
orderNumber: result.rows[0].order_number,
|
|
serviceSlug: "formation",
|
|
customerName: customer_name,
|
|
customerEmail: customer_email,
|
|
orderAmountCents: finalTotal,
|
|
discountCents: discountCents,
|
|
});
|
|
}
|
|
} catch (commErr) {
|
|
console.error("[formations] Commission creation failed (non-blocking):", commErr);
|
|
}
|
|
}
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
order_number: result.rows[0].order_number,
|
|
message: "Formation order received. We will begin processing within one business day.",
|
|
discount_applied: discountCents > 0 ? {
|
|
code: validatedDiscountCode,
|
|
discount_cents: discountCents,
|
|
service_fee_after_discount: finalServiceFee,
|
|
} : null,
|
|
});
|
|
} catch (err) {
|
|
console.error("[formations] Error:", err);
|
|
res
|
|
.status(500)
|
|
.json({ error: "Could not place your order. Please try again." });
|
|
}
|
|
});
|
|
|
|
// GET /api/v1/formations/:orderNumber — Check order status
|
|
router.get("/api/v1/formations/:orderNumber", async (req, res) => {
|
|
try {
|
|
const { orderNumber } = req.params;
|
|
const result = await pool.query(
|
|
`SELECT order_number, state_code, entity_type, entity_name, status,
|
|
state_filing_number, filed_at, delivered_at, created_at
|
|
FROM formation_orders WHERE order_number = $1`,
|
|
[orderNumber],
|
|
);
|
|
|
|
if (result.rows.length === 0) {
|
|
res.status(404).json({ error: "Order not found" });
|
|
return;
|
|
}
|
|
|
|
res.json({ order: result.rows[0] });
|
|
} catch (err) {
|
|
console.error("[formations] Error fetching order:", err);
|
|
res
|
|
.status(500)
|
|
.json({ error: "Could not retrieve order. Please try again." });
|
|
}
|
|
});
|
|
|
|
export default router;
|