NobGit
public imalexnord read

Slot

Calendar-first scheduling powered by Cloudflare Workers and Google Calendar.

Languages

Repository composition by tracked source files.

TypeScript
TypeScript 79% CSS 19% SQL 2% HTML 0%
Create file Wiki Documentation
Clone
https://nobgit.com/user/imalexnord/slot.git
ssh://[email protected]:2222/user/imalexnord/slot.git
setup-stripe.mjs
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');