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.
This commit is contained in:
justin 2026-06-25 11:32:48 -05:00
parent ae68edbc58
commit 618fafe1d5
126 changed files with 766 additions and 1518 deletions

View file

@ -1,41 +1,14 @@
---
import Base from "../../layouts/Base.astro";
import VerticalOrderHeader from "../../components/VerticalOrderHeader.astro";
import Wizard from "../../components/intake/Wizard.astro";
import TaxDeductibilityNotice from "../../components/TaxDeductibilityNotice.astro";
import { INTAKE_MANIFEST, SERVICE_META, formatUSD } from "../../lib/intake_manifest";
import OrderFlow from "../../components/intake/OrderFlow.astro";
import { SERVICE_META } from "../../lib/intake_manifest";
const slug = "cpni-certification";
const steps = INTAKE_MANIFEST[slug];
const meta = SERVICE_META[slug];
const title = meta ? `Order ${meta.name}` : "Order";
const description = "47 CFR § 64.2009 annual CPNI certification filed at FCC ECFS docket 06-36. Due March 1.";
---
<Base title={title} description={description}>
<main>
<section class="pw-order-intro">
<h1>{meta?.name}</h1>
<p class="pw-desc">{description}</p>
</section>
<VerticalOrderHeader vertical="telecom" />
<Wizard service_slug={slug} steps={steps ?? ["entity", "review", "payment"]} title={meta?.name ?? slug} />
</main>
<script>
// Always hide price + tax on intake pages — if the user is here, they've
// already paid via the order page or batch checkout.
const price = document.getElementById("pw-price");
const tax = document.getElementById("pw-tax-notice");
if (price) price.style.display = "none";
if (tax) tax.style.display = "none";
</script>
<OrderFlow slug={slug} description={description} fallbackSteps={["entity", "review", "payment"]} />
</Base>
<style>
main { max-width: 900px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
.pw-order-intro { margin-bottom: 1.5rem; }
.pw-order-intro h1 { margin: 0 0 0.25rem; color: var(--pw-navy, #1a2744); }
.pw-price { font-size: 1.5rem; font-weight: 700; color: var(--pw-green, #059669); margin: 0 0 0.75rem; }
.pw-desc { color: var(--pw-muted, #64748b); max-width: 48rem; }
</style>