1349450b140a7dd6b7983d9a4800b8375533041a
The Professional plan's "Hold until product launches" radio was wired
into the markup but ignored by both /api/payment-intent and the JS
submit handler. Buyers who picked it would still get charged
immediately because the server always created a PaymentIntent and
the client always called stripe.confirmPayment.
Fix:
* /api/payment-intent reads body.timing. When plan=professional and
timing=later, it creates a SetupIntent (usage=off_session) instead
of a PaymentIntent and returns {setup_mode:true, client_secret:...}.
Founding stays unconditional (lifetime, charge now).
* checkout JS now reads the radio (currentTiming()), passes timing
to the server, and re-fetches a new client_secret on radio change
so the buyer's choice is honored even if they toggle after first
mount.
* window._neuronMode tracks 'payment' vs 'setup'. The submit handler
branches: stripe.confirmSetup for save-card, stripe.confirmPayment
for charge-now. The submit button label updates to "Save my card -
no charge today" when in setup mode so the buyer sees the
intent before they hit submit.
* /api/link-customer receives timing + mode so the server can
differentiate at attach time.
A future webhook on setup_intent.succeeded will create the actual
Subscription with trial_end at launch (Q3 2026 / 2026-09-01) - that
piece is queued via metadata[hold_until]=launch on the SetupIntent.
For now, the saved payment method sits in Stripe untouched.
The point: a buyer who picks "Hold until launch" is NOT charged. The
flow has to be airtight - no surprise charges.
Description
Neuron marketing site - El-native server
9.7 MiB
Languages
Emacs Lisp
47.1%
C
34.7%
HTML
7.5%
TypeScript
5.4%
JavaScript
2.4%
Other
2.9%