checkout: re-add link-customer + redirect to /account, fix success/account routes

Restores the /api/link-customer endpoint that was lost in the stash. It
runs right before stripe.confirmPayment() and:
  - searches Stripe for an existing Customer by email
  - creates one if missing (URL-encodes + and @ so Gmail aliases work)
  - attaches the Customer to the live PaymentIntent
  - upserts the Supabase waitlist row with stripe_customer_id + plan

Stripe locks customer on a Charge once set, so the webhook handler is
the second-line backstop for any race where confirmPayment beats the
link call.

Also: change return_url from /marketplace/success to /account?welcome=1
so buyers land where they need to be, and switch /marketplace/success
and /account from str_eq to str_starts_with so Stripe-appended query
strings (?payment_intent=...&redirect_status=succeeded) don't 404.
This commit is contained in:
Will Anderson
2026-05-01 23:34:52 -05:00
parent 702888d3aa
commit d6731f7834
2 changed files with 79 additions and 51 deletions
+18 -1
View File
@@ -703,6 +703,9 @@ fn checkout_page(plan: String, pub_key: String) -> String {
return;
}
// Capture the PI id so we can attach a Customer at submit time
window._neuronPiId = data.id || (data.client_secret ? data.client_secret.split('_secret_')[0] : '');
waitForStripe(function() {
stripe = Stripe(STRIPE_PK);
@@ -815,10 +818,24 @@ fn checkout_page(plan: String, pub_key: String) -> String {
setLoading(true);
document.getElementById('payment-message').style.display = 'none';
// Link a Stripe Customer to this PaymentIntent and to the Supabase waitlist row
// (so the buyer shows up as a named customer, not Guest, and the account page
// can find their plan via stripe_customer_id). Non-blocking - if it fails, the
// webhook still links them server-side after payment_intent.succeeded fires.
if (window._neuronPiId) {
try {
await fetch('/api/link-customer', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pi_id: window._neuronPiId, email: email, name: name, plan: PLAN })
});
} catch(e) { /* non-blocking */ }
}
var result = await stripe.confirmPayment({
elements: elements,
confirmParams: {
return_url: window.location.origin + '/marketplace/success',
return_url: window.location.origin + '/account?welcome=1',
payment_method_data: {
billing_details: { name: name, email: email }
},