import { spawnSync } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; import { stdin as input, stdout as output } from 'node:process'; import { createInterface } from 'node:readline/promises'; const root = process.cwd(); const workerConfigPath = path.join(root, 'wrangler.jsonc'); const wranglerCli = path.join(root, 'node_modules', 'wrangler', 'bin', 'wrangler.js'); function fail(message) { console.error(`\nStripe setup failed: ${message}`); process.exit(1); } function ensureProjectRoot() { if (!fs.existsSync(workerConfigPath)) { fail('Run npm run setup:stripe from the Slot project root.'); } if (!fs.existsSync(wranglerCli)) { fail('Dependencies are missing. Run npm install, then try again.'); } } function wrangler(args, options = {}) { const { allowFailure = false, capture = false } = options; const result = spawnSync(process.execPath, [wranglerCli, ...args], { cwd: root, shell: false, stdio: capture ? ['ignore', 'pipe', 'pipe'] : 'inherit', encoding: capture ? 'utf8' : undefined, env: { ...process.env, NO_COLOR: process.env.NO_COLOR ?? '1' }, }); if (result.error) { if (allowFailure) return result; throw result.error; } if ((result.status ?? 1) !== 0 && !allowFailure) { process.exit(result.status ?? 1); } return result; } async function promptForStripe() { const rl = createInterface({ input, output }); try { console.log('\nStripe configuration'); console.log('Use test-mode values while testing. Do not paste these secrets into chat.\n'); const stripeSecretKey = (await rl.question('Stripe Secret Key (sk_test_... or sk_live_...): ')).trim(); const stripeWebhookSecret = (await rl.question('Stripe Webhook Signing Secret (whsec_...): ')).trim(); if (!/^sk_(test|live)_/.test(stripeSecretKey)) { throw new Error('Stripe Secret Key must begin with sk_test_ or sk_live_.'); } if (!/^whsec_/.test(stripeWebhookSecret)) { throw new Error('Stripe Webhook Signing Secret must begin with whsec_.'); } return { stripeSecretKey, stripeWebhookSecret }; } finally { rl.close(); } } function uploadStripeSecrets(answers) { const secretFile = path.join(root, `.slot-stripe-secrets-${process.pid}.json`); try { fs.writeFileSync(secretFile, JSON.stringify({ STRIPE_SECRET_KEY: answers.stripeSecretKey, STRIPE_WEBHOOK_SECRET: answers.stripeWebhookSecret, }), { mode: 0o600 }); wrangler(['secret', 'bulk', secretFile]); } finally { fs.rmSync(secretFile, { force: true }); } } function listSecretNames() { const result = wrangler(['secret', 'list', '--format', 'json'], { allowFailure: true, capture: true }); if ((result.status ?? 1) !== 0) return new Set(); try { const parsed = JSON.parse(`${result.stdout ?? ''}`.trim() || '[]'); return new Set(Array.isArray(parsed) ? parsed.map((item) => item.name).filter(Boolean) : []); } catch { return new Set(); } } ensureProjectRoot(); console.log('\nSlot Stripe setup\n================='); console.log('This only adds/updates Stripe Worker secrets. It does not touch D1, Google OAuth, or application code.\n'); if ((wrangler(['whoami'], { allowFailure: true, capture: true }).status ?? 1) !== 0) { console.log('Opening Cloudflare login...'); wrangler(['login']); } const answers = await promptForStripe(); uploadStripeSecrets(answers); const names = listSecretNames(); const missing = ['STRIPE_SECRET_KEY', 'STRIPE_WEBHOOK_SECRET'].filter((name) => !names.has(name)); if (missing.length) { fail(`Could not verify Worker secrets: ${missing.join(', ')}`); } console.log('\nDone. Stripe secrets are configured on the existing Worker.'); console.log('Next, set the per-minute rate and enable required payment in Slot Admin -> Settings -> Stripe payments.\n');