forked from Goutam/lynkeduppro-crm
feat(founder): payment→register flow + founder-only registration gate
- Next API routes: /api/register + /api/checkout (Stripe session) + /api/stripe-webhook (signature-verified; fulfils through be-crm), ported from the landing's serverless functions. Shared founder lead validation in src/lib/founder/lead.ts. - /api/founder/lookup + /api/founder/session proxy be-crm's public endpoints. - /thanks polls session_id → founder token → redirects to /portal/register?ft=<token>; falls back to the emailed link if the webhook is briefly delayed. - register-flow: reads ?ft=, validates + prefills (email locked, name, phone) from the paid founder record, passes founderToken to crm.account.register (claims token + provisions owner). Open registration is now blocked — only a paid founder (ft) or a team invitee may register; everyone else sees a 'Founder access required' gate.
This commit is contained in:
+39
-2
@@ -81,7 +81,8 @@
|
||||
<div class="step"><span class="n">3</span><span class="t"><b>Watch for launch updates.</b> As a Founding Member you'll hear first about new features and your rollout timeline.</span></div>
|
||||
</div>
|
||||
|
||||
<a class="cta" href="/">Close</a>
|
||||
<a class="cta" id="setupCta" href="/portal/register">Set up your account</a>
|
||||
<p class="fine" id="setupStatus" style="margin-top:10px;">Preparing your account…</p>
|
||||
|
||||
<p class="fine">
|
||||
Questions? Email <a href="mailto:support@lynkeduppro.com">support@lynkeduppro.com</a> and reference your order below.
|
||||
@@ -92,9 +93,10 @@
|
||||
<script>
|
||||
(function () {
|
||||
var q = new URLSearchParams(location.search);
|
||||
var demo = q.get("demo") === "1";
|
||||
// Demo mode ($0 no-card flow from page/19.html): reframe the page so nobody
|
||||
// thinks a real charge happened.
|
||||
if (q.get("demo") === "1") {
|
||||
if (demo) {
|
||||
document.getElementById("badge").textContent = "🎬 Demo complete";
|
||||
document.getElementById("headline").innerHTML = "That's the full flow — start to finish";
|
||||
document.getElementById("lead").textContent =
|
||||
@@ -104,6 +106,41 @@
|
||||
// Show the Stripe session id (from success_url) so support can look up the order.
|
||||
var id = q.get("session_id");
|
||||
if (id) document.getElementById("ref").textContent = "Order reference: " + id;
|
||||
|
||||
var status = document.getElementById("setupStatus");
|
||||
var cta = document.getElementById("setupCta");
|
||||
if (demo || !id) {
|
||||
// No real purchase to attribute — just offer the register page.
|
||||
if (status) status.textContent = "";
|
||||
return;
|
||||
}
|
||||
|
||||
// Poll be-crm (via our proxy) for the founder token minted by the Stripe
|
||||
// webhook, then redirect to the prefilled register page. The welcome email
|
||||
// carries the same link, so this is a convenience — nothing is lost if the
|
||||
// webhook is briefly delayed.
|
||||
var tries = 0, MAX = 15; // ~30s at 2s intervals
|
||||
function poll() {
|
||||
tries++;
|
||||
fetch("/api/founder/session?session_id=" + encodeURIComponent(id), { cache: "no-store" })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (d) {
|
||||
if (d && d.status === "ready" && d.token) {
|
||||
var url = "/portal/register?ft=" + encodeURIComponent(d.token);
|
||||
if (cta) cta.setAttribute("href", url);
|
||||
if (status) status.textContent = "Redirecting you to finish setup…";
|
||||
window.location.replace(url);
|
||||
return;
|
||||
}
|
||||
if (tries < MAX) { setTimeout(poll, 2000); return; }
|
||||
if (status) status.innerHTML = "We emailed you a secure link to finish setting up your account — check your inbox (and spam).";
|
||||
})
|
||||
.catch(function () {
|
||||
if (tries < MAX) { setTimeout(poll, 2000); return; }
|
||||
if (status) status.innerHTML = "We emailed you a secure link to finish setting up your account — check your inbox (and spam).";
|
||||
});
|
||||
}
|
||||
poll();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user