Add referral/discount code to FCC carrier page + REF-JAYK05 agent

Frontend (order/fcc-carrier-registration):
- Add a referral/discount code box on the review step that validates
  against /api/v1/discount/:code and shows the discount line + adjusted
  total. Discount applies to service fee + add-ons, never state filing fees.
- Prefill + auto-apply from ?code= / ?ref= query param (referral links).

Backend (fcc-carrier-registration route):
- Accept discount_code, validate it, store discount_code/discount_cents,
  and subtract from the total. Checkout already reads discount_cents to
  apply the Stripe coupon.
- Create a pending commission when the code belongs to an active sales agent.

Commission fix (agents.createCommission):
- Percent-type agents now earn commission_pct on ALL order types. Previously
  canada_crtc/formation/bundle used flat defaults and ignored percent agents.

Agent: created sales agent Jay Kordic (The Horizon Group) with custom code
REF-JAYK05 -> client gets 5% off discountable services, agent earns 15%.
Idempotent setup script in scripts/create_agent_jaykordic.cjs.
This commit is contained in:
justin 2026-06-02 14:31:22 -05:00
parent 1584a6692b
commit 53857574d3
4 changed files with 293 additions and 8 deletions

View file

@ -37,19 +37,23 @@ export async function createCommission(params: {
// Calculate commission amount
let commissionCents = agent.commission_default_cents || 30000; // $300 default
const overrides = agent.commission_overrides || {};
// Check for service-specific override
// Precedence:
// 1. Explicit per-service override (always wins, flat cents)
// 2. Percent-based agents earn commission_pct of the order on EVERY order type
// 3. Otherwise fall back to per-type flat defaults
if (params.serviceSlug && overrides[params.serviceSlug]) {
commissionCents = overrides[params.serviceSlug];
} else if (agent.commission_type === "percent") {
// Percent agents (e.g. referral partners on a flat % deal) get the same
// percentage regardless of order type. order_amount_cents is the total paid.
commissionCents = Math.round((params.orderAmountCents * (agent.commission_pct || 10)) / 100);
} else if (params.orderType === "canada_crtc") {
commissionCents = overrides["canada-crtc"] || 30000;
} else if (params.orderType === "formation") {
commissionCents = overrides["formation"] || 5000;
} else if (params.orderType === "bundle") {
commissionCents = overrides["bundle"] || 10000;
} else if (agent.commission_type === "percent") {
// For compliance services, use percentage
commissionCents = Math.round((params.orderAmountCents * (agent.commission_pct || 10)) / 100);
}
await pool.query(

View file

@ -51,6 +51,7 @@ router.post("/api/v1/fcc-carrier-registration", async (req: Request, res: Respon
address_street, address_city, address_state, address_zip,
service_wizard, services,
engagement_accepted,
discount_code,
} = req.body ?? {};
// Validate required fields
@ -104,6 +105,53 @@ router.post("/api/v1/fcc-carrier-registration", async (req: Request, res: Respon
if (includeOcn) addonFeeCents += OCN_FEE_CENTS;
const pucFeeCents = statePucStates.length * STATE_PUC_FEE_CENTS;
// ── Discount / referral code ──────────────────────────────────────────────
// Discounts apply to the Performance West service fee only. State filing fees
// (passed through at cost) are never discountable, matching the CRTC flow.
let discountCents = 0;
let normalizedDiscountCode: string | null = null;
if (discount_code && typeof discount_code === "string" && discount_code.trim().length >= 2) {
const code = discount_code.toUpperCase().trim();
try {
const dcResult = await pool.query(
"SELECT * FROM discount_codes WHERE code = $1",
[code],
);
if (dcResult.rows.length > 0) {
const dc = dcResult.rows[0];
const now = new Date();
const active = dc.active === true;
const notExpired = !dc.expires_at || new Date(dc.expires_at) >= now;
const started = !dc.starts_at || new Date(dc.starts_at) <= now;
const underGlobalLimit = dc.max_uses === null || dc.current_uses < dc.max_uses;
// Scope check: allow codes scoped to this service (or unscoped)
let inScope = true;
if (dc.applies_to) {
const allowed = String(dc.applies_to).split(",").map((s: string) => s.trim().toLowerCase());
inScope = allowed.includes("fcc_carrier_registration") || allowed.includes("all");
}
// Email allowlist check
let emailOk = true;
if (dc.allowed_emails && dc.allowed_emails.length > 0) {
const allowed = dc.allowed_emails.map((e: string) => e.toLowerCase());
emailOk = allowed.includes(customer_email.toLowerCase().trim());
}
if (active && notExpired && started && underGlobalLimit && inScope && emailOk) {
// Discountable base = service fee + add-ons (not state filing fees).
const discountable = BASE_FEE_CENTS + addonFeeCents;
if (dc.discount_type === "percent") {
discountCents = Math.round((discountable * dc.discount_value) / 100);
} else {
discountCents = Math.min(dc.discount_value, discountable);
}
normalizedDiscountCode = code;
}
}
} catch (dcErr) {
console.warn("[fcc-carrier-reg] Discount lookup failed (non-fatal):", dcErr);
}
}
const orderNumber = generateOrderNumber();
const result = await pool.query(
@ -118,11 +166,12 @@ router.post("/api/v1/fcc-carrier-registration", async (req: Request, res: Respon
state_puc_states,
service_fee_cents, formation_fee_cents, state_fee_cents,
puc_fee_cents, addon_fee_cents,
discount_code, discount_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
$29,$30,$31,$32,$33,$34,$35,$36,$37
) RETURNING *`,
[
orderNumber,
@ -158,6 +207,8 @@ router.post("/api/v1/fcc-carrier-registration", async (req: Request, res: Respon
stateFeeCents,
pucFeeCents,
addonFeeCents,
normalizedDiscountCode,
discountCents,
engagement_accepted ? new Date().toISOString() : null,
engagement_accepted ? (req.ip || req.headers["x-forwarded-for"] || null) : null,
],
@ -202,10 +253,38 @@ router.post("/api/v1/fcc-carrier-registration", async (req: Request, res: Respon
}
}
const totalCents = BASE_FEE_CENTS + formationFeeCents + stateFeeCents + pucFeeCents + addonFeeCents;
const subtotalCents = BASE_FEE_CENTS + formationFeeCents + stateFeeCents + pucFeeCents + addonFeeCents;
const totalCents = Math.max(0, subtotalCents - discountCents);
// If this order used a sales agent's referral code, record a pending commission.
if (normalizedDiscountCode) {
try {
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",
[normalizedDiscountCode],
);
if (agentCheck.rows.length > 0) {
const { createCommission } = await import("./agents.js");
await createCommission({
agentCode: agentCheck.rows[0].agent_code,
orderType: "fcc_carrier_registration",
orderId: order.id,
orderNumber: orderNumber,
serviceSlug: "fcc-carrier-registration",
customerName: customer_name.trim(),
customerEmail: customer_email.toLowerCase().trim(),
orderAmountCents: totalCents,
discountCents: discountCents,
});
}
} catch (commErr) {
console.warn("[fcc-carrier-reg] Commission creation failed (non-fatal):", commErr);
}
}
console.log(
`[fcc-carrier-reg] Created ${orderNumber}: ${entity_source} for ${customer_email}$${(totalCents / 100).toFixed(2)}`,
`[fcc-carrier-reg] Created ${orderNumber}: ${entity_source} for ${customer_email}$${(totalCents / 100).toFixed(2)}` +
(discountCents > 0 ? ` (discount ${normalizedDiscountCode} -$${(discountCents / 100).toFixed(2)})` : ""),
);
res.json({
@ -219,6 +298,9 @@ router.post("/api/v1/fcc-carrier-registration", async (req: Request, res: Respon
formation: formationFeeCents + stateFeeCents,
addons: addonFeeCents,
puc: pucFeeCents,
subtotal: subtotalCents,
discount_code: normalizedDiscountCode,
discount_cents: discountCents,
total: totalCents,
},
});

View file

@ -0,0 +1,105 @@
#!/usr/bin/env node
/**
* Create sales agent "Jay Kordic" with custom referral code JAYK05.
* - Client discount: 5% off all discountable service fees (discount_type='percent', value=5)
* - Agent commission: 15% (commission_type='percent', commission_pct=15)
*
* Idempotent: re-running updates the existing rows instead of duplicating.
*
* Usage (from api/ so it loads api/.env):
* cd api && node ../scripts/create_agent_jaykordic.cjs
*/
const fs = require("fs");
const path = require("path");
const { Client } = require("pg");
// Load DATABASE_URL from api/.env if not already in env
function loadEnv() {
if (process.env.DATABASE_URL) return;
const envPath = path.resolve(__dirname, "../api/.env");
if (!fs.existsSync(envPath)) return;
for (const line of fs.readFileSync(envPath, "utf8").split("\n")) {
const m = line.match(/^DATABASE_URL=(.*)$/);
if (m) { process.env.DATABASE_URL = m[1].trim(); break; }
}
}
const CODE = "REF-JAYK05";
const AGENT_NAME = "Jay Kordic";
const AGENT_COMPANY = "The Horizon Group";
const AGENT_EMAIL = "jay.kordic@performancewest.net"; // placeholder; update with real payout email
const CLIENT_DISCOUNT_PCT = 5;
const COMMISSION_PCT = 15;
(async () => {
loadEnv();
if (!process.env.DATABASE_URL) {
console.error("DATABASE_URL not found. Run from api/ or export DATABASE_URL.");
process.exit(1);
}
const c = new Client({ connectionString: process.env.DATABASE_URL });
await c.connect();
try {
await c.query("BEGIN");
// 1) Upsert the discount code (5% off, partner attribution to Jay Kordic)
const dc = await c.query(
`INSERT INTO discount_codes (code, description, discount_type, discount_value, referral_partner, referral_email, referral_pct, active)
VALUES ($1, $2, 'percent', $3, $4, $5, $6, TRUE)
ON CONFLICT (code) DO UPDATE SET
description = EXCLUDED.description,
discount_type = EXCLUDED.discount_type,
discount_value = EXCLUDED.discount_value,
referral_partner = EXCLUDED.referral_partner,
referral_email = EXCLUDED.referral_email,
referral_pct = EXCLUDED.referral_pct,
active = TRUE,
updated_at = now()
RETURNING id`,
[CODE, `Sales agent: ${AGENT_NAME} (${AGENT_COMPANY})`, CLIENT_DISCOUNT_PCT, AGENT_NAME, AGENT_EMAIL, COMMISSION_PCT],
);
const discountCodeId = dc.rows[0].id;
// 2) Upsert the sales agent (commission paid as percent of order)
const existing = await c.query("SELECT id FROM sales_agents WHERE email = $1 OR agent_code = $2", [AGENT_EMAIL, CODE]);
if (existing.rows.length > 0) {
await c.query(
`UPDATE sales_agents SET
agent_code = $1, discount_code_id = $2, name = $3, company = $4,
commission_type = 'percent', commission_pct = $5,
active = TRUE, updated_at = now()
WHERE id = $6`,
[CODE, discountCodeId, AGENT_NAME, AGENT_COMPANY, COMMISSION_PCT, existing.rows[0].id],
);
console.log(`Updated existing agent id=${existing.rows[0].id}`);
} else {
const ag = await c.query(
`INSERT INTO sales_agents (agent_code, discount_code_id, name, email, company, commission_type, commission_pct, active, onboarded_at)
VALUES ($1, $2, $3, $4, $5, 'percent', $6, TRUE, now())
RETURNING id`,
[CODE, discountCodeId, AGENT_NAME, AGENT_EMAIL, AGENT_COMPANY, COMMISSION_PCT],
);
console.log(`Created agent id=${ag.rows[0].id}`);
}
await c.query("COMMIT");
// Verify
const v = await c.query(
`SELECT d.code, d.discount_type, d.discount_value, d.referral_partner, d.referral_email, d.referral_pct, d.active AS code_active,
s.agent_code, s.name, s.commission_type, s.commission_pct, s.active AS agent_active
FROM discount_codes d
LEFT JOIN sales_agents s ON s.discount_code_id = d.id
WHERE d.code = $1`,
[CODE],
);
console.log("\nResult:");
console.dir(v.rows[0], { depth: null });
} catch (e) {
await c.query("ROLLBACK").catch(() => {});
console.error("ERROR:", e.message);
process.exitCode = 1;
} finally {
await c.end();
}
})();

View file

@ -350,6 +350,16 @@ select:focus,input:focus{outline:none;border-color:#1e3a5f;box-shadow:0 0 0 2px
<h2>Review & Checkout</h2>
<div id="review-content"></div>
<!-- Referral / discount code -->
<div style="margin-top:.75rem;padding:.75rem;border:1px dashed #d1d5db;border-radius:8px">
<label class="label" for="discount-code" style="margin-top:0">Referral or discount code</label>
<div style="display:flex;gap:.5rem">
<input type="text" id="discount-code" placeholder="e.g. REF-JAYK05" maxlength="40" style="flex:1;text-transform:uppercase">
<button type="button" class="btn btn-back" id="btn-apply-discount" style="white-space:nowrap">Apply</button>
</div>
<p id="discount-status" style="font-size:.8rem;margin-top:.4rem;display:none"></p>
</div>
<label style="display:flex;align-items:flex-start;gap:.5rem;padding:.65rem;margin-top:.75rem;border:1px solid #e5e7eb;border-radius:8px;cursor:pointer;font-size:.75rem;color:#6b7280;line-height:1.5">
<input type="checkbox" id="engage-check" required style="margin-top:2px;accent-color:#1e3a5f">
<span>I authorize Performance West Inc. to prepare and submit regulatory filings on my behalf as described above. I understand Performance West provides compliance consulting services, not legal advice or legal representation. I confirm the information I provide is accurate to the best of my knowledge. <a href="/terms" target="_blank" style="color:#1e3a5f;text-decoration:underline">Terms of Service</a></span>
@ -419,6 +429,8 @@ select:focus,input:focus{outline:none;border-color:#1e3a5f;box-shadow:0 0 0 2px
pucStates: [],
// Pricing
baseFee: 129900, formationFee: 0, stateFee: 0, pucFee: 0, addonFee: 0,
// Discount / referral
discountCode: '', discountCents: 0,
};
// ── Step navigation ──
@ -790,10 +802,58 @@ select:focus,input:focus{outline:none;border-color:#1e3a5f;box-shadow:0 0 0 2px
wizard.addrZip = document.getElementById('addr-zip').value.trim();
buildReview();
showStep(5);
// Auto-validate a prefilled referral code (e.g. from ?code= link) on first arrival
var dcInput = document.getElementById('discount-code');
if (dcInput && dcInput.value.trim() && !wizard.appliedDiscount) {
document.getElementById('btn-apply-discount').click();
}
});
document.getElementById('btn-back-4').addEventListener('click', function() { showStep(3); });
document.getElementById('btn-back-5').addEventListener('click', function() { showStep(4); });
// ── Apply referral / discount code ──
document.getElementById('btn-apply-discount').addEventListener('click', async function() {
var btn = this;
var input = document.getElementById('discount-code');
var statusEl = document.getElementById('discount-status');
var code = input.value.trim().toUpperCase();
statusEl.style.display = 'block';
if (!code) {
// Clearing the code
wizard.appliedDiscount = null; wizard.discountCode = ''; wizard.discountCents = 0;
statusEl.style.color = '#6b7280'; statusEl.textContent = 'No code applied.';
buildReview();
return;
}
btn.disabled = true; var prev = btn.textContent; btn.textContent = 'Checking...';
statusEl.style.color = '#6b7280'; statusEl.textContent = 'Validating code...';
try {
var email = wizard.contactEmail || document.getElementById('contact-email').value.trim();
var url = API + '/api/v1/discount/' + encodeURIComponent(code) +
'?service=fcc_carrier_registration' + (email ? '&email=' + encodeURIComponent(email) : '');
var resp = await fetch(url);
var data = await resp.json();
if (!resp.ok || !data.valid) {
wizard.appliedDiscount = null; wizard.discountCode = ''; wizard.discountCents = 0;
statusEl.style.color = '#dc2626';
statusEl.textContent = data.error || 'Code is not valid.';
buildReview();
return;
}
wizard.appliedDiscount = { discount_type: data.discount_type, discount_value: data.discount_value };
wizard.discountCode = data.code || code;
buildReview(); // recomputes wizard.discountCents against discountable base
statusEl.style.color = '#059669';
var label = data.discount_type === 'percent' ? (data.discount_value + '% off service fees') : ('$' + (data.discount_value / 100).toLocaleString() + ' off service fees');
statusEl.textContent = 'Applied ' + wizard.discountCode + ' \u2014 ' + label + (wizard.discountCents > 0 ? ' (\u2212$' + (wizard.discountCents / 100).toLocaleString() + ')' : '') + '.';
} catch (err) {
statusEl.style.color = '#dc2626';
statusEl.textContent = 'Could not validate code. Please try again.';
} finally {
btn.disabled = false; btn.textContent = prev;
}
});
// ── Build review ──
function buildReview() {
var services = [];
@ -819,11 +879,29 @@ select:focus,input:focus{outline:none;border-color:#1e3a5f;box-shadow:0 0 0 2px
}
var total = 0;
var discountableBase = 0; // service fee + add-ons; excludes formation/state filing fees
items.forEach(function(item) {
total += item.price;
var isFormation = item.name.indexOf('Business Formation') === 0;
if (!isFormation) discountableBase += item.price;
html += '<tr style="border-bottom:1px solid #f3f4f6"><td style="padding:.35rem">' + item.name + '</td>';
html += '<td style="text-align:right;padding:.35rem">' + (item.price ? '$' + (item.price / 100).toLocaleString() : 'Included') + '</td></tr>';
});
// Recompute discount against the current discountable base (in case services changed)
if (wizard.appliedDiscount) {
var d = wizard.appliedDiscount;
wizard.discountCents = d.discount_type === 'percent'
? Math.round((discountableBase * d.discount_value) / 100)
: Math.min(d.discount_value, discountableBase);
} else {
wizard.discountCents = 0;
}
if (wizard.discountCents > 0) {
html += '<tr style="color:#059669"><td style="padding:.35rem">Discount (' + wizard.discountCode + ')</td>';
html += '<td style="text-align:right;padding:.35rem">-$' + (wizard.discountCents / 100).toLocaleString() + '</td></tr>';
total -= wizard.discountCents;
}
html += '<tr style="border-top:2px solid #e5e7eb;font-weight:700"><td style="padding:.5rem">Total</td><td style="text-align:right;padding:.5rem">$' + (total / 100).toLocaleString() + '</td></tr>';
html += '</table>';
@ -880,6 +958,7 @@ select:focus,input:focus{outline:none;border-color:#1e3a5f;box-shadow:0 0 0 2px
puc_states: wizard.pucStates,
},
services: services,
discount_code: wizard.discountCode || '',
engagement_accepted: true,
}),
});
@ -954,6 +1033,21 @@ select:focus,input:focus{outline:none;border-color:#1e3a5f;box-shadow:0 0 0 2px
}).catch(function() {});
}, 500);
});
// ── Prefill referral code from ?code= / ?ref= query param ──
(function() {
try {
var qs = new URLSearchParams(window.location.search);
var refCode = (qs.get('code') || qs.get('ref') || '').trim().toUpperCase();
if (refCode) {
var input = document.getElementById('discount-code');
if (input) {
input.value = refCode;
wizard.discountCode = refCode; // captured even before the user reaches step 5
}
}
} catch (e) {}
})();
})();
</script>
<!-- Tawk.to Live Chat --><script>var Tawk_API=Tawk_API||{}, Tawk_LoadStart=new Date();(function(){var s1=document.createElement("script"),s0=document.getElementsByTagName("script")[0];s1.async=true;s1.src="https://embed.tawk.to/69d5a9ca0d1c3f1c37998081/1jll9ufph";s1.charset="UTF-8";s1.setAttribute("crossorigin","*");s0.parentNode.insertBefore(s1,s0);})();</script>