Compare commits

..

57 Commits

Author SHA1 Message Date
will.anderson 876f2afe27 Merge pull request 'dev → stage: free checkout Stripe fix + copy updates' (#124) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 6m51s
2026-05-12 00:16:26 +00:00
will.anderson d12f0375f8 Merge pull request 'dev → stage: force full build after registry cleanup' (#122) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 7m23s
2026-05-11 23:56:57 +00:00
will.anderson 6c2f423548 Merge pull request 'dev → stage: trigger rebuild' (#120) from dev into stage 2026-05-11 23:46:43 +00:00
will.anderson c6fb24498a Merge pull request 'ci: trigger workflow via dist/ change' (#118) from ci/trigger-rebuild into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 42s
2026-05-11 23:27:43 +00:00
will.anderson 3e230e52e5 ci: touch dist/ to trigger workflow rebuild 2026-05-11 18:27:31 -05:00
will.anderson f06850eb1a Merge pull request 'ci: re-trigger #3' (#117) from ci/trigger-rebuild into stage 2026-05-11 23:25:53 +00:00
will.anderson 0a4d454765 ci: re-trigger #3 2026-05-11 18:25:41 -05:00
will.anderson 7bc2a8e8f6 Merge pull request 'ci: re-trigger build after runner restart' (#116) from ci/trigger-rebuild into stage 2026-05-11 22:59:51 +00:00
will.anderson c4c30f1b33 ci: re-trigger after runner restart 2026-05-11 17:59:40 -05:00
will.anderson ae1a87de98 Merge pull request 'ci: rebuild after registry cleanup' (#115) from ci/trigger-rebuild into stage 2026-05-11 22:34:23 +00:00
will.anderson f5cbc15b43 Merge pull request 'dev → stage: fix HTML escaping in headings and button' (#114) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 8m5s
2026-05-11 22:27:28 +00:00
will.anderson e148e6987d Merge pull request 'Force full El rebuild — strip CGI content' (#111) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 7m44s
2026-05-11 21:43:20 +00:00
will.anderson 9554430b7e Merge pull request 'Also skip El rebuild for workflow-only changes' (#108) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 8m23s
2026-05-11 20:47:04 +00:00
will.anderson 9685a42c7d Merge pull request 'Skip El rebuild for migration/script/test-only changes' (#106) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 11m48s
2026-05-11 20:45:32 +00:00
will.anderson 9650dad951 Merge pull request 'Update CORS test: no-Origin requests are allowed' (#104) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 8m44s
2026-05-11 20:22:39 +00:00
will.anderson c3aec8947a Merge pull request 'Fix stage source guard: fetch origin/dev before ancestry check' (#102) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 9m28s
2026-05-11 19:09:52 +00:00
will.anderson 441d6d7cb5 Fix: idempotent migration policy creation
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 12s
2026-05-11 18:56:50 +00:00
will.anderson de9bf25437 Merge pull request 'dev → stage: fix CI migration heredoc YAML parse error' (#98) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 23s
Merge PR #98 from dev into stage
2026-05-11 18:34:15 +00:00
will.anderson a59fdf4baa Merge pull request 'dev → stage: fix supabase-config null-origin CORS bug' (#96) from dev into stage
Merge PR #96 from dev into stage
2026-05-11 18:31:00 +00:00
will.anderson ae633d3f71 Merge pull request 'dev → stage: wire Supabase migrations into CI/CD' (#94) from dev into stage
Merge PR #94 from dev into stage
2026-05-11 18:22:17 +00:00
will.anderson 43b5286fd5 Merge pull request 'dev → stage: pricing buttons, API keys, enterprise contacts' (#92) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 7m55s
Merge dev into stage
2026-05-11 18:06:02 +00:00
will.anderson 04641ed1a3 Merge pull request 'Stage: CI fixes, pricing buttons, API key provisioning' (#90) from fix/stage-ci-paths into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 10s
2026-05-11 17:49:37 +00:00
will.anderson f4a202e220 Merge pull request 'dev → stage: CSP unsafe-eval fix' (#89) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 7m59s
Merge dev into stage
2026-05-11 16:45:12 +00:00
will.anderson 3482e7e0f5 Merge pull request 'dev → stage: remove --obfuscate (CSP/eval fix)' (#87) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 2m31s
2026-05-11 16:11:58 +00:00
will.anderson beee0f99a7 Merge pull request 'Stage: fix Stripe CDN mock + free-plan waitForLoadState sync' (#85) from dev into stage
Stage: fix Stripe CDN mock + free-plan waitForLoadState sync
2026-05-11 15:36:35 +00:00
will.anderson 4b70e8c186 Merge pull request 'Fix Stripe CDN mock override and free-plan sync guards in E2E tests' (#83) from fix/stage-ci-paths into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 11s
Fix Stripe CDN mock override and free-plan sync guards in E2E tests
2026-05-11 14:55:22 +00:00
will.anderson f9a5f93070 Merge pull request 'Stage: fix CI JS corruption from obfuscator stdout + flaky test guards' (#82) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 20m46s
Stage: fix CI JS corruption + flaky test guards
2026-05-11 14:16:48 +00:00
will.anderson 8e2deab5cb Merge pull request 'Stage: free plan age verification + soul demo personalization' (#80) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 23m41s
Stage: free plan age verification + soul demo personalization
2026-05-11 07:05:52 +00:00
will.anderson ddeca2250e Merge pull request 'dev → stage: CI paths + comprehensive checkout tests' (#78) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 20m10s
2026-05-11 06:21:21 +00:00
will.anderson d228701828 Merge pull request 'dev → stage: comprehensive checkout + Stripe tests' (#76) from dev into stage
Merge: dev into stage — comprehensive checkout + Stripe tests
2026-05-11 06:19:33 +00:00
will.anderson 41f27e83aa Merge pull request 'test: full Playwright + API test suite for stage' (#74) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 6m43s
Merge dev into stage
2026-05-11 05:29:33 +00:00
will.anderson 533436e2c2 Merge pull request 'security: pentest fixes — deploy to stage' (#70) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m32s
security: pentest fixes — deploy to stage
2026-05-11 04:57:20 +00:00
will.anderson aeea037e6f Merge pull request 'feat: auth-gate demo chat + budget circuit breaker' (#68) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m53s
feat: auth-gate demo chat + budget circuit breaker
2026-05-11 04:45:56 +00:00
will.anderson 41bad94368 Merge pull request 'feat: scale fixes — max-instances, asset caching, shared rate limits, global cap' (#66) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 4m15s
feat: scale fixes — max-instances, asset caching, shared rate limits, global cap
2026-05-11 03:12:54 +00:00
will.anderson 3020b4e902 Merge pull request 'feat: extract soul-demo into standalone Cloud Run service' (#64) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 4m5s
feat: extract soul-demo into standalone Cloud Run service
2026-05-11 02:09:27 +00:00
will.anderson e82425a829 Merge pull request 'deploy: fix HAVE_CURL verification' (#62) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m14s
2026-05-11 01:07:49 +00:00
will.anderson c4cdb31529 Merge pull request 'deploy: fix HAVE_CURL — enable chat proxy to soul-demo' (#60) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 57s
deploy: fix HAVE_CURL — enable chat proxy to soul-demo
2026-05-11 01:03:41 +00:00
will.anderson a1c0cc090d Merge pull request 'Deploy: replace k3s with direct soul-demo watchdog' (#58) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 2m54s
Merge dev into stage
2026-05-11 00:47:16 +00:00
will.anderson 7df96a2273 Merge pull request 'Deploy: fix envelope truncation' (#56) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m50s
2026-05-11 00:23:38 +00:00
will.anderson d3b890b739 Merge pull request 'Deploy: fix JS served as JSON envelope' (#54) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m11s
Deploy: fix JS served as JSON envelope
2026-05-10 22:34:57 +00:00
will.anderson 3f069eeb79 Merge pull request 'Fix checkout auth (dev → stage)' (#52) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m14s
Fix checkout auth (dev → stage)
2026-05-10 22:01:17 +00:00
will.anderson 8676751ed6 Merge pull request 'Fix http handler registration (dev → stage)' (#50) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m40s
Merge PR #50: Fix http handler registration (dev → stage)
2026-05-10 18:37:16 +00:00
will.anderson a4f5312069 Merge pull request 'Fix GLIBC_2.38 mismatch: switch base image to ubuntu:24.04' (#48) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m37s
Fix GLIBC_2.38 mismatch: switch base image to ubuntu:24.04
2026-05-10 18:02:14 +00:00
will.anderson c76e5a19eb Merge pull request 'Non-blocking entrypoint + k3s --flannel-iface fix' (#46) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 4m8s
Non-blocking entrypoint + k3s flannel-iface fix
2026-05-10 17:55:12 +00:00
will.anderson 58b7b32cdd Single-stage Dockerfile.stage: pre-download k3s on host runner
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 12s
2026-05-10 16:27:07 +00:00
will.anderson 0fdabcce86 Merge pull request 'promote: dev → stage' (#42) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 4m46s
promote: dev → stage
2026-05-10 15:57:36 +00:00
will.anderson 79de47de2c Merge pull request 'promote: dev → stage' (#40) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 10m59s
2026-05-10 02:26:35 +00:00
will.anderson 45963154d9 Merge pull request 'promote: dev → stage' (#35) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 14m42s
2026-05-10 01:32:52 +00:00
will.anderson aabaa2ffb0 Merge pull request 'promote: dev → stage' (#33) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 15m11s
2026-05-10 01:07:20 +00:00
will.anderson d5dcb08ec6 Merge pull request 'promote: dev → stage (soul-demo image tar fix)' (#31) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 24s
2026-05-10 01:01:01 +00:00
will.anderson 20a36eeb9e Merge pull request 'promote: dev → stage' (#29) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 3m11s
2026-05-10 00:34:47 +00:00
will.anderson 32a179c24a Merge pull request 'promote: dev → stage' (#27) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 37s
2026-05-10 00:12:47 +00:00
will.anderson 6bc026de19 Merge pull request 'promote: dev → stage' (#25) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 21s
2026-05-09 23:44:30 +00:00
will.anderson 0ae526b72e Merge pull request 'promote: dev → stage' (#23) from dev into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 22s
2026-05-09 23:35:56 +00:00
will.anderson 8221aef605 promote: dev → stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 37s
2026-05-09 18:34:59 +00:00
will.anderson f8487c43a0 Merge branch 'dev' into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 14m16s
2026-05-09 17:41:09 +00:00
will.anderson 36b99dd9e2 Merge branch 'dev' into stage
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Failing after 6s
2026-05-09 17:32:23 +00:00
5 changed files with 34 additions and 103 deletions
+2 -63
View File
@@ -77,23 +77,6 @@ static _Thread_local int _tl_arena_active = 0;
* Allows serving PNGs and other binary files without strlen truncation. */ * Allows serving PNGs and other binary files without strlen truncation. */
static _Thread_local size_t _tl_fs_read_len = 0; static _Thread_local size_t _tl_fs_read_len = 0;
/* Binary body side-channel for http_response().
*
* http_response() normally JSON-encodes the body via jb_emit_escaped(), which
* stops at the first null byte (C-string semantics). Binary files like PNGs
* contain null bytes as early as byte 8 (IHDR chunk length), causing truncation.
*
* When _tl_fs_read_len > 0 at the time http_response() is called, we skip
* JSON-encoding and instead:
* 1. malloc-copy the raw bytes here
* 2. write the sentinel string "__el_binary__" into the envelope body field
* 3. In http_send_response(), detect the sentinel and use these raw bytes
*
* Thread-local so each worker thread has independent storage.
* Lifecycle: set by http_response(), consumed (and freed) by http_send_response(). */
static _Thread_local char* _tl_binary_body = NULL;
static _Thread_local size_t _tl_binary_size = 0;
static void el_arena_track(char* p) { static void el_arena_track(char* p) {
if (!_tl_arena_active || !p) return; if (!_tl_arena_active || !p) return;
if (_tl_arena.count >= _tl_arena.cap) { if (_tl_arena.count >= _tl_arena.cap) {
@@ -1553,22 +1536,10 @@ static void http_send_response(int fd, const char* body) {
} }
const char* eff_body = is_envelope ? env_body : body; const char* eff_body = is_envelope ? env_body : body;
int binary_side_channel = 0;
/* Binary side-channel: if the envelope body is the sentinel "__el_binary__",
* http_response() stored the real bytes in _tl_binary_body/_tl_binary_size.
* Substitute them here so http_send_all() sends the correct binary payload. */
if (is_envelope && env_body && strcmp(env_body, "__el_binary__") == 0
&& _tl_binary_body && _tl_binary_size > 0) {
eff_body = _tl_binary_body;
binary_side_channel = 1;
}
/* Use the real byte count from fs_read if available (handles binary files /* Use the real byte count from fs_read if available (handles binary files
* with embedded null bytes PNG, WOFF2, etc.). Fall back to strlen for * with embedded null bytes PNG, WOFF2, etc.). Fall back to strlen for
* normal text/JSON responses where _tl_fs_read_len is 0. */ * normal text/JSON responses where _tl_fs_read_len is 0. */
size_t blen = binary_side_channel ? _tl_binary_size size_t blen = (_tl_fs_read_len > 0) ? _tl_fs_read_len : strlen(eff_body);
: (_tl_fs_read_len > 0) ? _tl_fs_read_len : strlen(eff_body);
_tl_fs_read_len = 0; /* consume — one-shot per response */ _tl_fs_read_len = 0; /* consume — one-shot per response */
int head_only = _tl_http_head_only; int head_only = _tl_http_head_only;
@@ -1616,13 +1587,6 @@ static void http_send_response(int fd, const char* body) {
if (env_parsed_root) el_release(env_parsed_root); if (env_parsed_root) el_release(env_parsed_root);
free(env_body); free(env_body);
free(hdrs.buf); free(hdrs.buf);
/* Release binary side-channel if it was used (or left over from an error). */
if (_tl_binary_body) {
free(_tl_binary_body);
_tl_binary_body = NULL;
_tl_binary_size = 0;
}
} }
typedef struct { typedef struct {
@@ -1997,14 +1961,6 @@ el_val_t http_response(el_val_t status, el_val_t headers_json, el_val_t body) {
const char* b = EL_CSTR(body); const char* b = EL_CSTR(body);
if (!b) b = ""; if (!b) b = "";
/* Capture binary length BEFORE clearing _tl_fs_read_len.
* If the body came from fs_read(), _tl_fs_read_len holds the real byte
* count. jb_emit_escaped() stops at the first NUL byte, so we cannot
* JSON-encode binary data directly. Instead we copy it to a thread-local
* side-channel and write the sentinel "__el_binary__" into the envelope.
* http_send_response() detects the sentinel and uses the side-channel. */
size_t binary_len = _tl_fs_read_len;
/* Clear the fs_read binary-length hint: the envelope we're about to build /* Clear the fs_read binary-length hint: the envelope we're about to build
* is a fresh JSON string, not the raw file bytes. Without this reset, * is a fresh JSON string, not the raw file bytes. Without this reset,
* http_worker would use the stale _tl_fs_read_len (= original file size) * http_worker would use the stale _tl_fs_read_len (= original file size)
@@ -2012,18 +1968,6 @@ el_val_t http_response(el_val_t status, el_val_t headers_json, el_val_t body) {
* http_send_response and http_parse_envelope. */ * http_send_response and http_parse_envelope. */
_tl_fs_read_len = 0; _tl_fs_read_len = 0;
if (binary_len > 0) {
/* Binary body path: store raw bytes in thread-local, emit sentinel. */
free(_tl_binary_body); /* discard any stale binary from a prior error path */
_tl_binary_body = malloc(binary_len);
if (_tl_binary_body) {
memcpy(_tl_binary_body, b, binary_len);
_tl_binary_size = binary_len;
} else {
_tl_binary_size = 0; /* malloc failed — fall through to empty body */
}
}
JsonBuf out; jb_init(&out); JsonBuf out; jb_init(&out);
jb_puts(&out, EL_HTTP_RESPONSE_TAG); /* {"el_http_response":1 */ jb_puts(&out, EL_HTTP_RESPONSE_TAG); /* {"el_http_response":1 */
jb_puts(&out, ",\"status\":"); jb_puts(&out, ",\"status\":");
@@ -2033,12 +1977,7 @@ el_val_t http_response(el_val_t status, el_val_t headers_json, el_val_t body) {
jb_puts(&out, ",\"headers\":"); jb_puts(&out, ",\"headers\":");
jb_puts(&out, hj); jb_puts(&out, hj);
jb_puts(&out, ",\"body\":"); jb_puts(&out, ",\"body\":");
if (binary_len > 0 && _tl_binary_body) { jb_emit_escaped(&out, b);
/* Sentinel: http_send_response() will substitute the real bytes. */
jb_puts(&out, "\"__el_binary__\"");
} else {
jb_emit_escaped(&out, b);
}
jb_putc(&out, '}'); jb_putc(&out, '}');
return el_wrap_str(out.buf); return el_wrap_str(out.buf);
} }
+1 -25
View File
@@ -345,31 +345,7 @@ fn checkout_page(plan: String, pub_key: String) -> String {
} }
fn checkout_style_html() -> String { fn checkout_style_html() -> String {
let css: String = ".checkout-shell { let css: String = ".checkout-plan-name {
max-width: 980px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
align-items: start;
}
.checkout-summary {
position: sticky;
top: 2rem;
}
.checkout-form-wrap {
min-width: 0;
}
@media (max-width: 860px) {
.checkout-shell {
grid-template-columns: 1fr;
gap: 2.5rem;
}
.checkout-summary {
position: static;
}
}
.checkout-plan-name {
font-family: var(--head); font-family: var(--head);
font-size: clamp(1.5rem, 3vw, 2rem); font-size: clamp(1.5rem, 3vw, 2rem);
font-weight: 600; font-weight: 600;
+17 -3
View File
@@ -1,6 +1,20 @@
// checkout-free.el -- RETIRED. Free plan now uses the standard Stripe // checkout-free.el -- Free plan: show success panel after auth completes.
// payment flow (checkout-stripe.el) with a $0 PaymentIntent for age // Watches the auth-badge element; when it becomes visible, hides the auth
// verification. This file is no longer compiled or loaded. // section and shows the free-success panel. No card required for free tier.
// Compiled with: elc --target=js --bundle --minify --obfuscate
fn main() -> Void { fn main() -> Void {
native_js("(function() {
var success = document.getElementById('free-success');
var auth = document.getElementById('auth-section');
if (!success) return;
var timer = setInterval(function() {
var badge = document.getElementById('auth-badge');
if (badge && badge.offsetParent !== null) {
if (auth) auth.style.display = 'none';
success.style.display = '';
clearInterval(timer);
}
}, 150);
})()")
} }
+1 -1
View File
@@ -101,7 +101,7 @@ fn main() -> Void {
if (submitLabel) { if (submitLabel) {
submitLabel.textContent = window._neuronMode === 'setup' submitLabel.textContent = window._neuronMode === 'setup'
? 'Save my card - no charge today →' ? 'Save my card - no charge today →'
: PLAN === 'free' ? 'Verify age & get started →' : 'Complete purchase →'; : 'Complete purchase →';
} }
waitForStripe(function() { waitForStripe(function() {
if (!stripe) stripe = Stripe(STRIPE_PK); if (!stripe) stripe = Stripe(STRIPE_PK);
+13 -11
View File
@@ -697,21 +697,23 @@ fn handle_request_inner(method: String, path: String, headers: Map, body: String
} }
} }
// Free tier: $0 PaymentIntent for age verification (18+ requirement). // Free tier: creates a SetupIntent for age verification (18+ requirement).
// Verifies card is valid. No charge, no capture. // No charge but the user must provide a valid payment method.
// Note: setup_future_usage cannot be used with amount=0.
if str_eq(plan, "free") { if str_eq(plan, "free") {
let free_pi_body: String = "amount=0" let free_si_body: String = "automatic_payment_methods[enabled]=true"
+ "&currency=usd" + "&usage=off_session"
+ "&payment_method_types[]=card"
+ "&metadata[plan]=free" + "&metadata[plan]=free"
+ "&metadata[purpose]=age_verification" + "&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_si_body = if !str_eq(pi_cus_id, "") { free_si_body + "&customer=" + pi_cus_id } else { free_si_body }
let free_pi_resp: String = http_post_form_auth( let free_si_resp: String = http_post_form_auth(
"https://api.stripe.com/v1/payment_intents", "https://api.stripe.com/v1/setup_intents",
free_pi_body, free_si_body,
auth_header) auth_header)
return free_pi_resp if str_starts_with(free_si_resp, "{") {
let inner: String = str_slice(free_si_resp, 1, str_len(free_si_resp))
return "{\"setup_mode\":true,\"plan\":\"free\"," + inner
}
return free_si_resp
} }
// Setup-mode path: save payment method, do not charge. Only valid // Setup-mode path: save payment method, do not charge. Only valid