From 69f348d48b84920391184662580071d6812b5a33 Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Wed, 13 May 2026 12:12:10 -0500 Subject: [PATCH] Fix free plan checkout: use SetupIntent instead of $0 PaymentIntent Stripe rejects amount=0 PaymentIntents. Free plan age verification should use a SetupIntent (no charge, saves payment method). The JS already handles setup_mode:true by calling stripe.confirmSetup instead of confirmPayment. Mirrors the existing professional-later SetupIntent path. --- src/main.el | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/main.el b/src/main.el index 7f0596e..7dc52f9 100644 --- a/src/main.el +++ b/src/main.el @@ -699,21 +699,24 @@ fn handle_request_inner(method: String, path: String, headers: Map, body: String } } - // Free tier: $0 PaymentIntent for age verification (18+ requirement). - // Verifies card is valid. No charge, no capture. - // Note: setup_future_usage cannot be used with amount=0. + // Free tier: SetupIntent for age verification (18+ requirement). + // Verifies card is valid and saves it. No charge, no capture. + // $0 PaymentIntents are rejected by Stripe; SetupIntent is the correct tool. if str_eq(plan, "free") { - let free_pi_body: String = "amount=0" - + "¤cy=usd" - + "&payment_method_types[]=card" + let si_body: String = "automatic_payment_methods[enabled]=true" + + "&usage=off_session" + "&metadata[plan]=free" + "&metadata[purpose]=age_verification" - let free_pi_body = if !str_eq(pi_cus_id, "") { free_pi_body + "&customer=" + pi_cus_id } else { free_pi_body } - let free_pi_resp: String = http_post_form_auth( - "https://api.stripe.com/v1/payment_intents", - free_pi_body, + let si_body = if !str_eq(pi_cus_id, "") { si_body + "&customer=" + pi_cus_id } else { si_body } + let si_resp: String = http_post_form_auth( + "https://api.stripe.com/v1/setup_intents", + si_body, auth_header) - return free_pi_resp + if str_starts_with(si_resp, "{") { + let inner: String = str_slice(si_resp, 1, str_len(si_resp)) + return "{\"setup_mode\":true,\"plan\":\"free\"," + inner + } + return si_resp } // Setup-mode path: save payment method, do not charge. Only valid -- 2.52.0