intake: cold ?dot= visitors can finish + correct per-state CTA links

- Wizard Finish button: for visitors with no token/order (e.g. arriving via a
  campaign ?dot= link), create the compliance order from collected intake data
  and redirect to Stripe Checkout, instead of silently doing nothing.
- StateTrucking: Operating States no longer required; single-state/intrastate
  carriers can finish (relabeled 'Other Operating States (if any)').
- build_trucking_campaigns: per-state programs (weight_tax/emissions) now derive
  the CTA landing page from the deficiency flag's state suffix (e.g.
  state_weight_tax_OR -> OR), not the carrier base state, so a GA-based carrier
  flagged for OR weight-mile tax links to the OR page (not a mismatched one).
This commit is contained in:
justin 2026-06-02 12:56:03 -05:00
parent d420c49818
commit 53ae3ef870
3 changed files with 94 additions and 17 deletions

View file

@ -569,11 +569,61 @@ const STEP_LABELS: Record<string, string> = {
const API = (window as any).__PW_API || "";
if (!orderNumber && !token) {
// No order context — this is a standalone order, go to payment
// (shouldn't happen since payment step was removed for token orders)
nextBtn.disabled = false;
nextBtn.textContent = "Finish";
return;
// No order context yet (cold visitor, e.g. arrived via a campaign
// ?dot= link). Create the compliance order from the intake data we
// collected, then hand off to Stripe Checkout. Without this the Finish
// button would silently do nothing because there is no order to submit
// to and trucking services have no in-wizard payment step.
const d = state.intake_data || {};
const email = d.email || state.email || "";
const name = d.legal_name || d.entity_name || state.name || "";
if (!email || !name) {
alert("Please enter your business name and email before finishing.");
nextBtn.disabled = false;
nextBtn.textContent = "Finish";
return;
}
try {
const createResp = await fetch(`${API}/api/v1/compliance-orders`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
service_slug: slug,
customer_email: email,
customer_name: name,
customer_phone: d.phone || "",
intake_data: state.intake_data,
}),
});
if (!createResp.ok) {
const err = await createResp.json().catch(() => ({}));
throw new Error(err.error || `HTTP ${createResp.status}`);
}
const order = await createResp.json();
const newOrderNumber = order.order_number;
if (!newOrderNumber) throw new Error("Order created but no order number returned.");
// Kick off Stripe Checkout for the new order.
const checkoutResp = await fetch(`${API}/api/v1/checkout/create-session`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
order_id: newOrderNumber,
order_type: "compliance",
payment_method: "card",
}),
});
if (!checkoutResp.ok) throw new Error(`Checkout HTTP ${checkoutResp.status}`);
const { checkout_url } = await checkoutResp.json();
if (!checkout_url) throw new Error("No checkout URL returned.");
sessionStorage.removeItem(`pw-intake-${slug}`);
window.location.href = checkout_url;
return;
} catch (e: any) {
alert("Could not start checkout: " + (e.message || "please try again."));
nextBtn.disabled = false;
nextBtn.textContent = "Finish";
return;
}
}
try {

View file

@ -58,10 +58,10 @@
</select></label>
</div>
<div class="pw-row">
<label class="pw-field"><span>Operating States (besides base) <em>*</em></span>
<input type="text" id="st-operating-states" placeholder="e.g. CA, NV, AZ, OR (comma-separated)" /></label>
<label class="pw-field"><span>Other Operating States (if any)</span>
<input type="text" id="st-operating-states" placeholder="e.g. CA, NV, AZ, OR (comma-separated). Leave blank if you only run in your base state" /></label>
</div>
<p class="pw-field-help">After your order, we'll send a short follow-up form to collect each vehicle's VIN, plate, and registered weight for the apportioned/IFTA filing.</p>
<p class="pw-field-help">List any states besides your base state where you operate. Leave blank if you run intrastate only. After your order, we'll send a short follow-up form to collect each vehicle's VIN, plate, and registered weight for the apportioned/IFTA filing.</p>
</div>
<!-- ═══ CA MCP + CARB / Emissions ═══ -->
@ -256,7 +256,6 @@
}
// Section-specific required
const sectionRequired = {
"st-sec-irp-ifta": [["st-operating-states","Operating States"]],
"st-sec-intrastate": [["st-authority-type","Authority Type"]],
};
for (const [secId, fields] of Object.entries(sectionRequired)) {