public
imalexnord
read
Slot
Calendar-first scheduling powered by Cloudflare Workers and Google Calendar.
Languages
Repository composition by tracked source files.
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
Trace
scripts/setup-stripe.mjs
Trace helps you understand code history line by line. See who changed each line, when it changed, and which commit introduced it.
Author
Date
Commit
Line
Code
1
import { spawnSync } from 'node:child_process';
2
import fs from 'node:fs';
3
import path from 'node:path';
4
import { stdin as input, stdout as output } from 'node:process';
5
import { createInterface } from 'node:readline/promises';
7
const root = process.cwd();
8
const workerConfigPath = path.join(root, 'wrangler.jsonc');
9
const wranglerCli = path.join(root, 'node_modules', 'wrangler', 'bin', 'wrangler.js');
11
function fail(message) {
12
console.error(`\nStripe setup failed: ${message}`);
13
process.exit(1);
14
}
16
function ensureProjectRoot() {
17
if (!fs.existsSync(workerConfigPath)) {
18
fail('Run npm run setup:stripe from the Slot project root.');
19
}
20
if (!fs.existsSync(wranglerCli)) {
21
fail('Dependencies are missing. Run npm install, then try again.');
22
}
23
}
25
function wrangler(args, options = {}) {
26
const { allowFailure = false, capture = false } = options;
27
const result = spawnSync(process.execPath, [wranglerCli, ...args], {
28
cwd: root,
29
shell: false,
30
stdio: capture ? ['ignore', 'pipe', 'pipe'] : 'inherit',
31
encoding: capture ? 'utf8' : undefined,
32
env: { ...process.env, NO_COLOR: process.env.NO_COLOR ?? '1' },
33
});
35
if (result.error) {
36
if (allowFailure) return result;
37
throw result.error;
38
}
39
if ((result.status ?? 1) !== 0 && !allowFailure) {
40
process.exit(result.status ?? 1);
41
}
42
return result;
43
}
45
async function promptForStripe() {
46
const rl = createInterface({ input, output });
47
try {
48
console.log('\nStripe configuration');
49
console.log('Use test-mode values while testing. Do not paste these secrets into chat.\n');
50
const stripeSecretKey = (await rl.question('Stripe Secret Key (sk_test_... or sk_live_...): ')).trim();
51
const stripeWebhookSecret = (await rl.question('Stripe Webhook Signing Secret (whsec_...): ')).trim();
53
if (!/^sk_(test|live)_/.test(stripeSecretKey)) {
54
throw new Error('Stripe Secret Key must begin with sk_test_ or sk_live_.');
55
}
56
if (!/^whsec_/.test(stripeWebhookSecret)) {
57
throw new Error('Stripe Webhook Signing Secret must begin with whsec_.');
58
}
60
return { stripeSecretKey, stripeWebhookSecret };
61
} finally {
62
rl.close();
63
}
64
}
66
function uploadStripeSecrets(answers) {
67
const secretFile = path.join(root, `.slot-stripe-secrets-${process.pid}.json`);
68
try {
69
fs.writeFileSync(secretFile, JSON.stringify({
70
STRIPE_SECRET_KEY: answers.stripeSecretKey,
71
STRIPE_WEBHOOK_SECRET: answers.stripeWebhookSecret,
72
}), { mode: 0o600 });
74
wrangler(['secret', 'bulk', secretFile]);
75
} finally {
76
fs.rmSync(secretFile, { force: true });
77
}
78
}
80
function listSecretNames() {
81
const result = wrangler(['secret', 'list', '--format', 'json'], { allowFailure: true, capture: true });
82
if ((result.status ?? 1) !== 0) return new Set();
83
try {
84
const parsed = JSON.parse(`${result.stdout ?? ''}`.trim() || '[]');
85
return new Set(Array.isArray(parsed) ? parsed.map((item) => item.name).filter(Boolean) : []);
86
} catch {
87
return new Set();
88
}
89
}
91
ensureProjectRoot();
93
console.log('\nSlot Stripe setup\n=================');
94
console.log('This only adds/updates Stripe Worker secrets. It does not touch D1, Google OAuth, or application code.\n');
96
if ((wrangler(['whoami'], { allowFailure: true, capture: true }).status ?? 1) !== 0) {
97
console.log('Opening Cloudflare login...');
98
wrangler(['login']);
99
}
101
const answers = await promptForStripe();
102
uploadStripeSecrets(answers);
104
const names = listSecretNames();
105
const missing = ['STRIPE_SECRET_KEY', 'STRIPE_WEBHOOK_SECRET'].filter((name) => !names.has(name));
106
if (missing.length) {
107
fail(`Could not verify Worker secrets: ${missing.join(', ')}`);
108
}
110
console.log('\nDone. Stripe secrets are configured on the existing Worker.');
111
console.log('Next, set the per-minute rate and enable required payment in Slot Admin -> Settings -> Stripe payments.\n');