new-site/site/public/order/state-puc/index.html
justin 618fafe1d5 order: payment-first express checkout + fix dead Tawk chat widget
Conversion fix for the checkout drop-off (54 sessions reached an /order/ page
over 3 days, 0 advanced to payment). Root cause was friction, not a bug: every
order page dropped a cold email-click straight into a 28-field intake Wizard
before showing any payment option.

- New ExpressCheckout.astro: payment-first entry. Shows price + the minimal
  fields the API needs (prefilled from public records: ?dot= FMCSA census for
  trucking, ?npi= NPPES lookup for healthcare) + Continue to payment. Creates a
  single-service batch-of-one (POST /compliance-orders/batch, which does NOT
  gate Stripe on intake_data_validated) then create-session -> Stripe. Full
  intake is collected AFTER payment via the per-service 'Complete Your Intake
  Form' email the webhook already sends (links to /order/<slug>?order=CO-xxx,
  which re-enters the Wizard in paid-intake mode).

- New OrderFlow.astro: single source of truth replacing ~50 near-identical thin
  Wizard wrappers. Trucking + healthcare default to payment-first (express on
  top, marketing hero moved BELOW the CTA). Telecom + corporate keep Wizard-first
  (rich pre-payment FCC/499 intake, no public-records prefill). Paid-intake
  re-entry (?order=/?token=) always renders the full Wizard.

- Rewrote all 50 /order/*.astro pages to use OrderFlow (foreign-qualification
  keeps its multi-state toggle via slotted content).

- Fixed the dead Tawk.to live-chat widget site-wide: the snippet set an invalid
  crossorigin='*' attribute, forcing the browser into anonymous CORS mode and
  blocking the script (0 chat requests fired anywhere). Removed it to match
  Tawk's official snippet (footer partial + 73 static public/*.html files).

Verified: build clean; express on top with hero below; ?dot=/?npi= prefill;
paid-intake re-entry swaps to Wizard; telecom stays wizard-first; batch-of-one
-> live Stripe URL; both POST endpoints allow the prod origin via CORS.
2026-06-25 11:32:48 -05:00

532 lines
45 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="State PUC/PSC registration for VoIP, broadband, and CLEC providers. $399/state plus state filing fees. Bond procurement coordinated.">
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<title>State PUC/PSC Registration — Performance West Inc.</title>
<script>
window.__PW_API = (function() {
var h = window.location.hostname;
if (h === "localhost" || h === "127.0.0.1") return "http://" + h + ":3001";
if (h === "dev.performancewest.net") return "https://api.dev.performancewest.net";
return "https://api.performancewest.net";
})();
</script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Inter',system-ui,sans-serif;color:#1f2937;background:#f9fafb;line-height:1.6}
a{color:#1e3a5f;text-decoration:none}
.wrap{max-width:780px;margin:0 auto;padding:2rem 1.25rem 4rem}
h1{font-size:1.65rem;font-weight:700;color:#111827;margin-bottom:.25rem}
.subtitle{font-size:.9rem;color:#6b7280;margin-bottom:1.5rem}
.card{background:#fff;border:1px solid #e5e7eb;border-radius:12px;padding:1.25rem;margin-bottom:1rem}
.card h2{font-size:1.1rem;font-weight:700;color:#1e3a5f;margin-bottom:.75rem}
.label{display:block;font-size:.82rem;font-weight:600;color:#374151;margin-bottom:.3rem}
select,input[type=text]{width:100%;padding:.5rem .7rem;border:1px solid #d1d5db;border-radius:8px;font-size:.88rem;background:#fff}
select:focus,input:focus{outline:none;border-color:#1e3a5f;box-shadow:0 0 0 2px rgba(30,58,95,.15)}
.btn{display:inline-flex;align-items:center;justify-content:center;gap:.4rem;padding:.65rem 1.5rem;border-radius:8px;font-weight:600;font-size:.92rem;cursor:pointer;border:none;transition:all .15s}
.btn-primary{background:#1e3a5f;color:#fff}.btn-primary:hover{background:#162e4d}
.btn-primary:disabled{opacity:.5;cursor:not-allowed}
.state-grid{display:grid;grid-template-columns:1fr;gap:.5rem;max-height:500px;overflow-y:auto;padding:.5rem 0}
@media(min-width:640px){.state-grid{grid-template-columns:1fr 1fr}}
.state-row{display:flex;align-items:center;gap:.6rem;padding:.55rem .7rem;border:1px solid #e5e7eb;border-radius:8px;cursor:pointer;transition:all .15s}
.state-row:hover{border-color:#1e3a5f;background:#f0f4f8}
.state-row.selected{border-color:#1e3a5f;background:#eff6ff}
.state-row.exempt{opacity:.55;cursor:default;background:#f9fafb}
.state-row input[type=checkbox]{accent-color:#1e3a5f;width:16px;height:16px;cursor:pointer}
.state-name{font-weight:600;font-size:.88rem;flex:1}
.state-meta{font-size:.75rem;color:#6b7280;text-align:right}
.state-meta .fee{color:#059669;font-weight:600}
.state-meta .bond{color:#d97706;font-size:.7rem}
.state-meta .exempt-tag{color:#9ca3af;font-style:italic}
.summary-box{position:sticky;bottom:0;background:#fff;border-top:2px solid #1e3a5f;padding:1rem 1.25rem;margin:-1.25rem;margin-top:1rem;border-radius:0 0 12px 12px}
.summary-row{display:flex;justify-content:space-between;font-size:.88rem;padding:.15rem 0}
.summary-row.total{font-weight:700;font-size:1.05rem;border-top:1px solid #e5e7eb;padding-top:.5rem;margin-top:.35rem}
.filter-bar{display:flex;gap:.5rem;flex-wrap:wrap;margin-bottom:.75rem}
.filter-btn{padding:.3rem .7rem;border:1px solid #d1d5db;border-radius:6px;font-size:.78rem;background:#fff;cursor:pointer;font-weight:500}
.filter-btn.active{background:#1e3a5f;color:#fff;border-color:#1e3a5f}
.hidden{display:none}
.info-banner{padding:.6rem .9rem;background:#ecfdf5;border-left:3px solid #059669;border-radius:0 6px 6px 0;font-size:.85rem;color:#065f46;margin-bottom:1.25rem}
.bond-banner{padding:.5rem .8rem;background:#fffbeb;border-left:3px solid #d97706;border-radius:0 6px 6px 0;font-size:.82rem;color:#92400e;margin-top:.75rem}
.step-dots{display:flex;gap:.75rem;margin-bottom:1.5rem}
.step-dot{width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:.72rem;font-weight:700;background:#e5e7eb;color:#6b7280}
.step-dot.active{background:#1e3a5f;color:#fff}
.step-dot.done{background:#22c55e;color:#fff}
.entity-form input{margin-bottom:.75rem}
</style>
<script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script><script defer src="/js/pw-analytics.js"></script></head>
<body>
<div class="wrap">
<h1>State PUC/PSC Registration</h1>
<p class="subtitle">Register your VoIP, broadband, or CLEC service with state Public Utility Commissions. $399/state plus state filing fees.</p>
<div class="info-banner">
<strong>Tax deductible</strong> &mdash; State PUC registration fees are deductible as ordinary business expenses under IRC &sect; 162.
</div>
<div class="step-dots">
<div class="step-dot active" id="dot-1">1</div>
<div class="step-dot" id="dot-2">2</div>
<div class="step-dot" id="dot-3">3</div>
</div>
<!-- Step 1: Provider Info -->
<div id="step-1" class="card">
<h2>Provider Information</h2>
<div class="entity-form">
<label class="label">Legal Entity Name</label>
<input type="text" id="entity-name" placeholder="Acme Telecom LLC">
<label class="label">FRN (FCC Registration Number)</label>
<input type="text" id="frn" placeholder="0012345678">
<label class="label">Registration Type</label>
<select id="reg-type">
<option value="voip">VoIP / IPES Provider</option>
<option value="broadband">Broadband / ISP Provider</option>
<option value="clec">CLEC (Competitive Local Exchange Carrier)</option>
<option value="bundle">Bundle (VoIP + CLEC)</option>
</select>
<label class="label" style="margin-top:.5rem">Provider Classification</label>
<select id="provider-type">
<option value="">-- Select --</option>
<option value="facilities_based">Facilities-Based (own network infrastructure)</option>
<option value="reseller">Reseller (resell another carrier's service)</option>
<option value="over_the_top">Over-the-Top VoIP (internet-based, no PSTN interconnection)</option>
<option value="hybrid">Hybrid (own some infrastructure, resell some)</option>
</select>
<p style="font-size:.75rem;color:#6b7280;margin-top:.25rem">
Provider type affects bond requirements and registration category in many states. Resellers and OTT VoIP providers often have lower or no bond requirements.
</p>
</div>
<div style="margin-top:1rem;text-align:right">
<button class="btn btn-primary" id="btn-next-1">Select States &rarr;</button>
</div>
</div>
<!-- Step 2: State Selection -->
<div id="step-2" class="card hidden">
<h2>Select States</h2>
<p style="font-size:.85rem;color:#6b7280;margin-bottom:.75rem">
Check the states where you need PUC registration. Exempt states (no registration required) are grayed out.
</p>
<div class="filter-bar">
<button class="filter-btn active" data-filter="all">All States</button>
<button class="filter-btn" data-filter="required">Registration Required</button>
<button class="filter-btn" data-filter="bond">Bond Required</button>
<button class="filter-btn" data-filter="exempt">Exempt / No Reg</button>
</div>
<div style="margin-bottom:.5rem">
<label style="font-size:.82rem;cursor:pointer">
<input type="checkbox" id="select-all-required" style="accent-color:#1e3a5f"> Select all states requiring registration
</label>
</div>
<div class="state-grid" id="state-grid">
<div style="text-align:center;padding:2rem;color:#9ca3af;grid-column:1/-1">Loading state data...</div>
</div>
<div id="bond-banner" class="bond-banner hidden">
<strong>Bond required</strong> in <span id="bond-count">0</span> selected state(s). We coordinate surety bond procurement with our bonding partners. Bond amounts shown are typical ranges &mdash; exact amounts depend on your provider size and type.
</div>
<div class="summary-box" id="summary-box" style="display:none">
<div class="summary-row"><span>Selected states</span><span id="sum-states">0</span></div>
<div class="summary-row"><span>Service fees ($399/state)</span><span id="sum-service">$0</span></div>
<div class="summary-row"><span>State filing fees</span><span id="sum-state-fees">$0</span></div>
<div class="summary-row total"><span>Total</span><span id="sum-total">$0</span></div>
<div style="font-size:.72rem;color:#9ca3af;margin-top:.35rem">
Bond amounts not included in total &mdash; coordinated separately.
</div>
<div style="margin-top:.75rem;display:flex;gap:.5rem;justify-content:flex-end">
<button class="btn" style="background:#e5e7eb;color:#374151" id="btn-back-2">&larr; Back</button>
<button class="btn btn-primary" id="btn-next-2" disabled>Review &amp; Pay &rarr;</button>
</div>
</div>
</div>
<!-- Step 3: Review & Pay -->
<div id="step-3" class="card hidden">
<h2>Review &amp; Checkout</h2>
<div id="review-content"></div>
<div style="margin-top:1.25rem;border-top:1px solid #e5e7eb;padding-top:1rem">
<h3 style="font-size:.95rem;font-weight:600;margin-bottom:.75rem">Contact Information</h3>
<label class="label">Full Name</label>
<input type="text" id="customer-name" placeholder="John Smith" style="margin-bottom:.5rem">
<label class="label">Email Address</label>
<input type="text" id="customer-email" placeholder="john@example.com" style="margin-bottom:.5rem">
<label class="label">Phone (optional)</label>
<input type="text" id="customer-phone" placeholder="(555) 123-4567" style="margin-bottom:.5rem">
<label class="label">Discount Code (optional)</label>
<input type="text" id="promo-code" placeholder="SAVE20" style="margin-bottom:.75rem">
</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>
</label>
<div style="margin-top:1rem;display:flex;gap:.5rem;justify-content:flex-end">
<button class="btn" style="background:#e5e7eb;color:#374151" id="btn-back-3">&larr; Back</button>
<button class="btn btn-primary" id="btn-pay" disabled>Continue to Payment</button>
</div>
<p id="checkout-error" style="color:#dc2626;font-size:.82rem;margin-top:.5rem;display:none"></p>
</div>
</div>
<script>
(function() {
const API = window.__PW_API;
let allStates = [];
let selected = new Set();
// Load state data
fetch(API + '/api/v1/puc/requirements/all')
.then(r => r.json())
.then(data => {
allStates = data.states || [];
renderGrid(allStates);
})
.catch(err => {
document.getElementById('state-grid').innerHTML =
'<div style="color:#dc2626;padding:1rem;grid-column:1/-1">Failed to load state data. Please refresh.</div>';
});
// Get the relevant fee for a state based on registration type
function getStateFee(s, regType) {
if (regType === 'broadband') return s.broadband_fee_cents || 0;
if (regType === 'clec') return s.clec_fee_cents || 0;
if (regType === 'bundle') return (s.voip_fee_cents || 0) + (s.clec_fee_cents || 0);
return s.voip_fee_cents || 0;
}
function getStateBond(s, regType) {
if (regType === 'clec' || regType === 'bundle') {
return { required: s.clec_bond_required || s.voip_bond_required, cents: Math.max(s.clec_bond_cents || 0, s.voip_bond_cents || 0) };
}
if (regType === 'broadband') return { required: false, cents: 0 };
return { required: s.voip_bond_required, cents: s.voip_bond_cents || 0 };
}
function renderGrid(states, filter) {
const grid = document.getElementById('state-grid');
grid.innerHTML = '';
const regType = document.getElementById('reg-type').value;
let filtered = states;
if (filter === 'required') filtered = states.filter(s => isRequired(s, regType));
else if (filter === 'bond') filtered = states.filter(s => getStateBond(s, regType).required && isRequired(s, regType));
else if (filter === 'exempt') filtered = states.filter(s => !isRequired(s, regType));
filtered.forEach(s => {
const req = isRequired(s, regType);
const fee = getStateFee(s, regType);
const bond = getStateBond(s, regType);
const row = document.createElement('label');
row.className = 'state-row' + (req ? '' : ' exempt') + (selected.has(s.state_code) ? ' selected' : '');
row.innerHTML = `
<input type="checkbox" value="${s.state_code}" ${req ? '' : 'disabled'} ${selected.has(s.state_code) ? 'checked' : ''}>
<span class="state-name">${s.state_name} (${s.state_code})</span>
<span class="state-meta">
${req ? `<span class="fee">$${(fee / 100).toLocaleString()}</span> fee` : '<span class="exempt-tag">No registration required</span>'}
${req && bond.required ? '<br><span class="bond">Bond: $' + (bond.cents / 100).toLocaleString() + '</span>' : ''}
</span>
`;
const cb = row.querySelector('input');
cb.addEventListener('change', () => {
if (cb.checked) { selected.add(s.state_code); row.classList.add('selected'); }
else { selected.delete(s.state_code); row.classList.remove('selected'); }
updateSummary();
});
grid.appendChild(row);
});
}
function isRequired(s, regType) {
if (regType === 'voip' || regType === 'bundle') return s.voip_required;
if (regType === 'broadband') return s.broadband_required;
if (regType === 'clec') return s.clec_required;
return s.voip_required;
}
function updateSummary() {
const box = document.getElementById('summary-box');
const regType = document.getElementById('reg-type').value;
if (selected.size === 0) {
box.style.display = 'none';
document.getElementById('btn-next-2').disabled = true;
document.getElementById('bond-banner').classList.add('hidden');
return;
}
box.style.display = 'block';
document.getElementById('btn-next-2').disabled = false;
let serviceFees = 0, stateFees = 0, bondTotal = 0, bondCount = 0;
selected.forEach(code => {
const s = allStates.find(st => st.state_code === code);
if (!s) return;
serviceFees += 39900;
stateFees += getStateFee(s, regType);
const bond = getStateBond(s, regType);
if (bond.required) { bondTotal += bond.cents; bondCount++; }
});
document.getElementById('sum-states').textContent = selected.size;
document.getElementById('sum-service').textContent = '$' + (serviceFees / 100).toLocaleString();
document.getElementById('sum-state-fees').textContent = '$' + (stateFees / 100).toLocaleString();
document.getElementById('sum-total').textContent = '$' + ((serviceFees + stateFees) / 100).toLocaleString();
if (bondCount > 0) {
document.getElementById('bond-banner').classList.remove('hidden');
document.getElementById('bond-count').textContent = bondCount;
} else {
document.getElementById('bond-banner').classList.add('hidden');
}
}
// Filter buttons
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
renderGrid(allStates, btn.dataset.filter);
});
});
// Select all required
document.getElementById('select-all-required').addEventListener('change', function() {
const regType = document.getElementById('reg-type').value;
if (this.checked) {
allStates.filter(s => isRequired(s, regType)).forEach(s => selected.add(s.state_code));
} else {
selected.clear();
}
renderGrid(allStates, document.querySelector('.filter-btn.active')?.dataset?.filter);
updateSummary();
});
// Re-render when reg type changes
document.getElementById('reg-type').addEventListener('change', () => {
selected.clear();
document.getElementById('select-all-required').checked = false;
renderGrid(allStates, document.querySelector('.filter-btn.active')?.dataset?.filter);
updateSummary();
});
// Step navigation
function showStep(n) {
[1,2,3].forEach(i => {
document.getElementById('step-' + i).classList.toggle('hidden', i !== n);
const dot = document.getElementById('dot-' + i);
dot.classList.remove('active','done');
if (i < n) dot.classList.add('done');
if (i === n) dot.classList.add('active');
});
}
document.getElementById('btn-next-1').addEventListener('click', () => {
const name = document.getElementById('entity-name').value.trim();
if (!name) { alert('Please enter your legal entity name.'); return; }
showStep(2);
// Re-render grid in case reg type changed
renderGrid(allStates);
updateSummary();
});
document.getElementById('btn-back-2').addEventListener('click', () => showStep(1));
document.getElementById('btn-next-2').addEventListener('click', () => {
showStep(3);
buildReview();
});
document.getElementById('btn-back-3').addEventListener('click', () => showStep(2));
function buildReview() {
const name = document.getElementById('entity-name').value.trim();
const frn = document.getElementById('frn').value.trim();
const regType = document.getElementById('reg-type').value;
const provType = document.getElementById('provider-type').value;
let html = `
<div style="margin-bottom:1rem">
<div style="font-size:.85rem;color:#6b7280">Entity</div>
<div style="font-weight:600">${esc(name)}</div>
${frn ? '<div style="font-size:.85rem;color:#6b7280">FRN: ' + esc(frn) + '</div>' : ''}
<div style="font-size:.85rem;color:#6b7280">Type: ${regType.toUpperCase()}${provType ? ' (' + provType.replace(/_/g,' ') + ')' : ''}</div>
</div>
<table style="width:100%;border-collapse:collapse;font-size:.85rem">
<thead><tr style="border-bottom:2px solid #e5e7eb">
<th style="text-align:left;padding:.4rem .5rem;font-weight:600">State</th>
<th style="text-align:right;padding:.4rem .5rem;font-weight:600">Filing Fee</th>
<th style="text-align:right;padding:.4rem .5rem;font-weight:600">Service Fee</th>
<th style="text-align:right;padding:.4rem .5rem;font-weight:600">Bond</th>
</tr></thead><tbody>
`;
let serviceTot = 0, stateTot = 0, bondTot = 0, bondStates = 0;
Array.from(selected).sort().forEach(code => {
const s = allStates.find(st => st.state_code === code);
if (!s) return;
const sf = getStateFee(s, regType);
const bond = getStateBond(s, regType);
const bondAmt = bond.required ? bond.cents : 0;
serviceTot += 39900;
stateTot += sf;
bondTot += bondAmt;
if (bond.required) bondStates++;
html += `<tr style="border-bottom:1px solid #f3f4f6">
<td style="padding:.35rem .5rem">${s.state_name} (${code})</td>
<td style="text-align:right;padding:.35rem .5rem">$${(sf/100).toLocaleString()}</td>
<td style="text-align:right;padding:.35rem .5rem">$399</td>
<td style="text-align:right;padding:.35rem .5rem;color:${bondAmt?'#d97706':'#9ca3af'}">${bondAmt ? '$'+(bondAmt/100).toLocaleString() : '---'}</td>
</tr>`;
});
html += `</tbody><tfoot>
<tr style="border-top:2px solid #e5e7eb;font-weight:700">
<td style="padding:.5rem">Total (${selected.size} states)</td>
<td style="text-align:right;padding:.5rem">$${(stateTot/100).toLocaleString()}</td>
<td style="text-align:right;padding:.5rem">$${(serviceTot/100).toLocaleString()}</td>
<td style="text-align:right;padding:.5rem;color:#d97706">${bondTot ? '$'+(bondTot/100).toLocaleString() : '---'}</td>
</tr>
</tfoot></table>
<div style="margin-top:.75rem;font-size:.92rem;font-weight:700;text-align:right">
Grand total: $${((serviceTot+stateTot)/100).toLocaleString()}
</div>
${bondTot > 0 ? '<div class="bond-banner" style="margin-top:.75rem"><strong>Bond total: $'+(bondTot/100).toLocaleString()+'</strong> across '+bondStates+' state(s). Bond procurement is coordinated separately after checkout.</div>' : ''}
`;
document.getElementById('review-content').innerHTML = html;
document.getElementById('btn-pay').disabled = false;
}
// Checkout — 2-step: create compliance order, then create Stripe session
document.getElementById('btn-pay').addEventListener('click', async () => {
const btn = document.getElementById('btn-pay');
const errEl = document.getElementById('checkout-error');
errEl.style.display = 'none';
if (!document.getElementById('engage-check').checked) {
errEl.textContent = 'Please accept the authorization terms to continue.';
errEl.style.display = 'block';
return;
}
const customerName = document.getElementById('customer-name').value.trim();
const customerEmail = document.getElementById('customer-email').value.trim();
if (!customerName || !customerEmail) {
errEl.textContent = 'Please enter your name and email address.';
errEl.style.display = 'block';
return;
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(customerEmail)) {
errEl.textContent = 'Please enter a valid email address.';
errEl.style.display = 'block';
return;
}
btn.disabled = true;
btn.textContent = 'Creating order...';
// Normalize provider_type: empty string -> null (DB CHECK constraint rejects "")
const provType = document.getElementById('provider-type').value || null;
const orderBody = {
service_slug: 'state-puc',
customer_name: customerName,
customer_email: customerEmail,
customer_phone: document.getElementById('customer-phone').value.trim() || undefined,
discount_code: document.getElementById('promo-code').value.trim() || undefined,
intake_data: {
entity_legal_name: document.getElementById('entity-name').value.trim(),
frn: document.getElementById('frn').value.trim(),
registration_type: document.getElementById('reg-type').value,
provider_type: provType,
target_states: Array.from(selected),
},
};
try {
// Step 1: Create compliance order
const orderResp = await fetch(API + '/api/v1/compliance-orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(orderBody),
});
const orderData = await orderResp.json();
if (!orderResp.ok) throw new Error(orderData.error || 'Order creation failed');
btn.textContent = 'Redirecting to payment...';
// Step 2: Create Stripe checkout session
const sessionResp = await fetch(API + '/api/v1/checkout/create-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
order_id: orderData.id,
order_type: 'compliance',
payment_method: 'card',
}),
});
const sessionData = await sessionResp.json();
if (!sessionResp.ok) throw new Error(sessionData.error || 'Checkout failed');
if (sessionData.checkout_url) {
window.location.href = sessionData.checkout_url;
} else {
throw new Error('No checkout URL returned');
}
} catch (err) {
errEl.textContent = err.message || 'Something went wrong. Please try again.';
errEl.style.display = 'block';
btn.disabled = false;
btn.textContent = 'Continue to Payment';
}
});
function esc(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
})();
</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";s0.parentNode.insertBefore(s1,s0);})();</script>
<script defer src="/js/pw-cookie-consent.js"></script><!-- Floating help button --><button type="button" id="support-fab" aria-label="Open support" class="fixed bottom-6 left-6 z-[9999] w-14 h-14 rounded-full bg-pw-700 text-white shadow-lg hover:bg-pw-800 transition-all hover:scale-105 flex items-center justify-center"> <svg id="support-fab-icon-open" class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path stroke-linecap="round" stroke-linejoin="round" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path> </svg> <svg id="support-fab-icon-close" class="w-6 h-6 hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"></path> </svg> </button> <!-- Slide-out panel --> <div id="support-panel" class="fixed bottom-24 left-6 z-[9998] w-[380px] max-w-[calc(100vw-2rem)] bg-white rounded-xl shadow-2xl border border-gray-200 transform translate-y-4 opacity-0 pointer-events-none transition-all duration-200 ease-out"> <div class="px-5 py-4 border-b border-gray-100 bg-gray-50 rounded-t-xl"> <h3 class="text-base font-semibold text-gray-900">How can we help?</h3> <p class="text-xs text-gray-500 mt-0.5">Choose a category and tell us what you need.</p> </div> <!-- Category selector (step 1) --> <div id="support-step-categories" class="p-4 space-y-2"> <button type="button" data-category="question" class="support-cat-btn w-full text-left px-4 py-3 rounded-lg border border-gray-200 hover:border-pw-300 hover:bg-pw-50 transition-colors group"> <div class="flex items-center gap-3"> <span class="flex-shrink-0 w-8 h-8 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center"> <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> </span> <div> <span class="text-sm font-medium text-gray-900 group-hover:text-pw-700">Ask a Question</span> <span class="block text-xs text-gray-500">About our compliance services or process</span> </div> </div> </button> <button type="button" data-category="support" class="support-cat-btn w-full text-left px-4 py-3 rounded-lg border border-gray-200 hover:border-pw-300 hover:bg-pw-50 transition-colors group"> <div class="flex items-center gap-3"> <span class="flex-shrink-0 w-8 h-8 rounded-full bg-green-100 text-green-600 flex items-center justify-center"> <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M18.364 5.636l-3.536 3.536m0 5.656l3.536 3.536M9.172 9.172L5.636 5.636m3.536 9.192l-3.536 3.536M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-5 0a4 4 0 11-8 0 4 4 0 018 0z"></path></svg> </span> <div> <span class="text-sm font-medium text-gray-900 group-hover:text-pw-700">Get Support</span> <span class="block text-xs text-gray-500">Help with an ongoing engagement</span> </div> </div> </button> <button type="button" data-category="issue" class="support-cat-btn w-full text-left px-4 py-3 rounded-lg border border-gray-200 hover:border-pw-300 hover:bg-pw-50 transition-colors group"> <div class="flex items-center gap-3"> <span class="flex-shrink-0 w-8 h-8 rounded-full bg-red-100 text-red-600 flex items-center justify-center"> <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg> </span> <div> <span class="text-sm font-medium text-gray-900 group-hover:text-pw-700">Report an Issue</span> <span class="block text-xs text-gray-500">Something isn't right with a deliverable</span> </div> </div> </button> <button type="button" data-category="service_request" class="support-cat-btn w-full text-left px-4 py-3 rounded-lg border border-gray-200 hover:border-pw-300 hover:bg-pw-50 transition-colors group"> <div class="flex items-center gap-3"> <span class="flex-shrink-0 w-8 h-8 rounded-full bg-purple-100 text-purple-600 flex items-center justify-center"> <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg> </span> <div> <span class="text-sm font-medium text-gray-900 group-hover:text-pw-700">Request a Service</span> <span class="block text-xs text-gray-500">Start a new compliance engagement</span> </div> </div> </button> <button type="button" data-category="quote" class="support-cat-btn w-full text-left px-4 py-3 rounded-lg border border-gray-200 hover:border-pw-300 hover:bg-pw-50 transition-colors group"> <div class="flex items-center gap-3"> <span class="flex-shrink-0 w-8 h-8 rounded-full bg-amber-100 text-amber-600 flex items-center justify-center"> <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> </span> <div> <span class="text-sm font-medium text-gray-900 group-hover:text-pw-700">Request a Quote</span> <span class="block text-xs text-gray-500">Get pricing for a complex engagement</span> </div> </div> </button> <!-- Sign-in link (hidden when logged in) --> <div id="support-signin-link" class="pt-2 border-t border-gray-100 mt-2"> <button type="button" id="support-signin-btn" class="w-full flex items-center justify-center gap-2 px-4 py-2 text-xs text-gray-500 hover:text-pw-700 transition-colors"> <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path></svg>
Already a client? <span class="font-medium text-pw-600">Sign in</span> </button> </div> </div> <!-- Ticket form — Report an Issue + guest fallback (step 2a) --> <form id="support-step-form" class="hidden p-4"> <button type="button" id="support-back-btn" class="inline-flex items-center gap-1 text-xs text-gray-500 hover:text-gray-700 mb-3"> <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"></path></svg>
Back
</button> <div id="support-category-badge" class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium bg-pw-100 text-pw-700 mb-3"></div> <div class="space-y-3"> <div> <label for="support-name" class="block text-xs font-medium text-gray-700 mb-1">Name <span class="text-gray-400">(optional)</span></label> <input type="text" id="support-name" name="name" placeholder="Your name" maxlength="100" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow"> </div> <div> <label for="support-email" class="block text-xs font-medium text-gray-700 mb-1">Email <span class="text-gray-400">(for follow-up)</span></label> <input type="email" id="support-email" name="email" placeholder="you@company.com" maxlength="200" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow"> </div> <div> <label for="support-subject" class="block text-xs font-medium text-gray-700 mb-1">Subject <span class="text-red-400">*</span></label> <input type="text" id="support-subject" name="subject" required minlength="3" maxlength="200" placeholder="Brief summary" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow"> </div> <div> <label for="support-message" class="block text-xs font-medium text-gray-700 mb-1">Message <span class="text-red-400">*</span></label> <textarea id="support-message" name="message" required minlength="10" maxlength="5000" rows="4" placeholder="Describe your question, issue, or request..." class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow resize-y"></textarea> <p class="text-[10px] text-gray-400 mt-0.5 text-right"><span id="support-char-count">0</span> / 5000</p> </div> <button type="submit" id="support-submit-btn" class="w-full py-2.5 px-4 bg-pw-700 text-white text-sm font-medium rounded-lg hover:bg-pw-800 transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
Submit
</button> </div> </form> <!-- Opportunity form — Request a Service / Request a Quote (step 2b) --> <form id="support-step-opportunity" class="hidden p-4"> <button type="button" id="opportunity-back-btn" class="inline-flex items-center gap-1 text-xs text-gray-500 hover:text-gray-700 mb-3"> <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"></path></svg>
Back
</button> <div id="opportunity-category-badge" class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium bg-pw-100 text-pw-700 mb-3"></div> <div class="space-y-3"> <div> <label for="opp-name" class="block text-xs font-medium text-gray-700 mb-1">Name <span class="text-red-400">*</span></label> <input type="text" id="opp-name" name="name" required minlength="2" maxlength="100" placeholder="Your name" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow"> </div> <div> <label for="opp-email" class="block text-xs font-medium text-gray-700 mb-1">Email <span class="text-red-400">*</span></label> <input type="email" id="opp-email" name="email" required placeholder="you@company.com" maxlength="200" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow"> </div> <div> <label for="opp-company" class="block text-xs font-medium text-gray-700 mb-1">Company <span class="text-gray-400">(optional)</span></label> <input type="text" id="opp-company" name="company" placeholder="Company name" maxlength="200" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow"> </div> <div> <label for="opp-phone" class="block text-xs font-medium text-gray-700 mb-1">Phone <span class="text-gray-400">(optional)</span></label> <input type="tel" id="opp-phone" name="phone" placeholder="(555) 555-1234" maxlength="30" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow"> </div> <div> <label for="opp-service" class="block text-xs font-medium text-gray-700 mb-1">Service interest <span class="text-red-400">*</span></label> <select id="opp-service" name="service_slug" required class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow bg-white"> <option value="" disabled selected>Select a service...</option> <option value="canada-crtc">Canada CRTC Carrier Package</option> <option value="llc-formation">LLC Formation</option> <option value="corporation-formation">Corporation Formation</option> <option value="registered-agent">Registered Agent</option> <option value="compliance-audit">Compliance Audit</option> <option value="other">Other / Not sure</option> </select> </div> <div> <label for="opp-details" class="block text-xs font-medium text-gray-700 mb-1">Details <span class="text-gray-400">(optional)</span></label> <textarea id="opp-details" name="details" maxlength="5000" rows="3" placeholder="Tell us about your project or what you need..." class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow resize-y"></textarea> </div> <button type="submit" id="opp-submit-btn" class="w-full py-2.5 px-4 bg-pw-700 text-white text-sm font-medium rounded-lg hover:bg-pw-800 transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
Submit Request
</button> </div> </form> <!-- Sign-in prompt — Ask a Question / Get Support when not logged in (step 2c) --> <div id="support-step-signin" class="hidden p-6"> <button type="button" id="signin-back-btn" class="inline-flex items-center gap-1 text-xs text-gray-500 hover:text-gray-700 mb-4"> <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"></path></svg>
Back
</button> <div class="text-center"> <div class="w-12 h-12 mx-auto mb-3 rounded-full bg-blue-100 flex items-center justify-center"> <svg class="w-6 h-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path></svg> </div> <h4 class="text-sm font-semibold text-gray-900 mb-1">Sign in for support</h4> <p class="text-xs text-gray-500 mb-5">Sign in to access your client portal where you can view your orders, submit support requests, and manage your services.</p> <button type="button" id="signin-auth-btn" class="w-full py-2.5 px-4 bg-pw-700 text-white text-sm font-medium rounded-lg hover:bg-pw-800 transition-colors mb-3">
Sign In
</button> <button type="button" id="signin-guest-btn" class="text-xs text-gray-500 hover:text-gray-700 transition-colors">
Continue as guest &rarr;
</button> </div> </div> <!-- Success state (step 3) --> <div id="support-step-success" class="hidden p-6 text-center"> <div class="w-12 h-12 mx-auto mb-3 rounded-full bg-green-100 flex items-center justify-center"> <svg class="w-6 h-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"></path></svg> </div> <h4 class="text-sm font-semibold text-gray-900 mb-1">Request received</h4> <p id="support-success-message" class="text-xs text-gray-500 mb-4">We'll get back to you within one business day.</p> <p id="support-ticket-id" class="text-xs text-gray-400 mb-4 hidden">Ref: <span></span></p> <button type="button" id="support-new-ticket-btn" class="text-xs text-pw-600 hover:text-pw-700 font-medium">Submit another request</button> </div> </div> <!-- Modal backdrop --><div id="auth-modal-backdrop" class="fixed inset-0 z-[200] bg-black/50 backdrop-blur-sm hidden items-center justify-center p-4" role="dialog" aria-modal="true" aria-labelledby="auth-modal-title"> <div id="auth-modal" class="relative w-full max-w-sm bg-white rounded-2xl shadow-2xl overflow-hidden"> <!-- Close --> <button id="auth-modal-close" type="button" aria-label="Close" class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 transition-colors z-10"> <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"></path> </svg> </button> <!-- Tab bar --> <div id="auth-tabs" class="flex border-b border-gray-100"> <button type="button" id="auth-tab-login" class="auth-tab flex-1 py-4 text-sm font-semibold text-pw-700 border-b-2 border-pw-600 transition-colors">
Sign In
</button> <button type="button" id="auth-tab-register" class="auth-tab flex-1 py-4 text-sm font-semibold text-gray-400 border-b-2 border-transparent hover:text-gray-600 transition-colors">
Create Account
</button> </div> <div class="px-6 py-6"> <!-- ── Login form ──────────────────────────────────────────────────── --> <form id="auth-login-form" class="space-y-4" novalidate> <div> <label for="auth-login-email" class="block text-sm font-medium text-gray-700 mb-1">Email</label> <input type="email" id="auth-login-email" autocomplete="email" required class="w-full rounded-lg border border-gray-300 px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-pw-500 focus:border-pw-500 transition-colors" placeholder="you@company.com"> </div> <div> <div class="flex items-center justify-between mb-1"> <label for="auth-login-password" class="text-sm font-medium text-gray-700">Password</label> <button type="button" id="auth-forgot-link" class="text-xs text-pw-600 hover:text-pw-800 underline underline-offset-2">
Forgot password?
</button> </div> <input type="password" id="auth-login-password" autocomplete="current-password" required class="w-full rounded-lg border border-gray-300 px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-pw-500 focus:border-pw-500 transition-colors" placeholder="••••••••"> </div> <p id="auth-login-err" class="hidden text-xs text-red-600 font-medium"></p> <button type="submit" id="auth-login-btn" class="w-full py-2.5 rounded-lg bg-pw-700 text-white text-sm font-semibold hover:bg-pw-800 transition-colors disabled:opacity-50">
Sign In
</button> </form> <!-- ── Register form ──────────────────────────────────────────────── --> <form id="auth-register-form" class="space-y-4 hidden" novalidate> <div> <label for="auth-reg-name" class="block text-sm font-medium text-gray-700 mb-1">Your Name</label> <input type="text" id="auth-reg-name" autocomplete="name" class="w-full rounded-lg border border-gray-300 px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-pw-500 focus:border-pw-500 transition-colors" placeholder="Full name"> </div> <div> <label for="auth-reg-email" class="block text-sm font-medium text-gray-700 mb-1">Email</label> <input type="email" id="auth-reg-email" autocomplete="email" required class="w-full rounded-lg border border-gray-300 px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-pw-500 focus:border-pw-500 transition-colors" placeholder="you@company.com"> </div> <div> <label for="auth-reg-password" class="block text-sm font-medium text-gray-700 mb-1">Password</label> <input type="password" id="auth-reg-password" autocomplete="new-password" required class="w-full rounded-lg border border-gray-300 px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-pw-500 focus:border-pw-500 transition-colors" placeholder="At least 8 characters"> </div> <p id="auth-reg-err" class="hidden text-xs text-red-600 font-medium"></p> <button type="submit" id="auth-reg-btn" class="w-full py-2.5 rounded-lg bg-pw-700 text-white text-sm font-semibold hover:bg-pw-800 transition-colors disabled:opacity-50">
Create Account
</button> </form> <!-- ── Forgot password form ────────────────────────────────────────── --> <div id="auth-forgot-form" class="hidden"> <button type="button" id="auth-back-to-login" class="flex items-center gap-1 text-xs text-gray-500 hover:text-gray-700 mb-4"> <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18"></path></svg>
Back to sign in
</button> <h3 class="text-base font-semibold text-gray-900 mb-1">Reset your password</h3> <p class="text-sm text-gray-500 mb-4">Enter your email and we'll send you a reset link.</p> <form id="auth-forgot-email-form" class="space-y-4" novalidate> <div> <label for="auth-forgot-email" class="block text-sm font-medium text-gray-700 mb-1">Email</label> <input type="email" id="auth-forgot-email" autocomplete="email" required class="w-full rounded-lg border border-gray-300 px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-pw-500 focus:border-pw-500 transition-colors" placeholder="you@company.com"> </div> <p id="auth-forgot-err" class="hidden text-xs text-red-600 font-medium"></p> <p id="auth-forgot-ok" class="hidden text-xs text-green-700 font-medium"></p> <button type="submit" id="auth-forgot-btn" class="w-full py-2.5 rounded-lg bg-pw-700 text-white text-sm font-semibold hover:bg-pw-800 transition-colors disabled:opacity-50">
Send reset link
</button> </form> </div> </div> </div> </div>
<script type="module" src="/_astro/hoisted.yFz1BYXO.js"></script>
</body>
</html>