Skip to content

Commit 72698d1

Browse files
committed
Fix Stripe checkout errors and widen admin claims layout
1 parent 707b1fd commit 72698d1

4 files changed

Lines changed: 109 additions & 45 deletions

File tree

Lines changed: 63 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
'use strict';
22

3-
const Stripe = require('../../lib/stripe-client');
3+
const createStripeClient = require('../../lib/stripe-client');
44
const db = require('../../lib/db');
55
const { requireAdminAuth } = require('./_auth');
66

7+
function asServiceUnavailable(res, status, error) {
8+
return res.status(503).json({ ok: false, status, error });
9+
}
10+
11+
function asConflict(res, status, error) {
12+
return res.status(409).json({ ok: false, status, error });
13+
}
14+
715
module.exports = async function handler(req, res) {
816
res.setHeader('Content-Type', 'application/json; charset=utf-8');
917
res.setHeader('Cache-Control', 'no-store');
@@ -14,25 +22,35 @@ module.exports = async function handler(req, res) {
1422
}
1523
if (!requireAdminAuth(req, res)) return;
1624

17-
if (!process.env.STRIPE_SECRET_KEY) {
18-
return res.status(503).json({ ok: false, status: 'STRIPE_NOT_CONFIGURED' });
19-
}
20-
2125
const body = req.body || {};
2226
const claimId = typeof body.claimId === 'string' ? body.claimId.trim() : '';
2327
if (!claimId) return res.status(400).json({ ok: false, status: 'INVALID_CLAIM_ID' });
2428

29+
let stripe;
30+
try {
31+
stripe = createStripeClient(process.env.STRIPE_SECRET_KEY);
32+
} catch (error) {
33+
if (error?.code === 'STRIPE_NOT_CONFIGURED') {
34+
return asServiceUnavailable(res, 'STRIPE_NOT_CONFIGURED', 'Stripe secret key is not configured.');
35+
}
36+
if (error?.code === 'STRIPE_SECRET_KEY_INVALID') {
37+
return asServiceUnavailable(res, 'STRIPE_SECRET_KEY_INVALID', error.message);
38+
}
39+
console.error('ADMIN_CREATE_CHECKOUT_STRIPE_INIT_FAILED', { message: error?.message, code: error?.code, claimId });
40+
return asServiceUnavailable(res, 'STRIPE_NOT_CONFIGURED', 'Stripe secret key is not configured.');
41+
}
42+
2543
try {
2644
const claims = db.normalizeRows(await db.query('select * from claim_requests where claim_id = $1 limit 1', [claimId]));
27-
if (!claims.length) return res.status(404).json({ ok: false, status: 'CLAIM_NOT_FOUND' });
45+
if (!claims.length) return res.status(404).json({ ok: false, status: 'CLAIM_NOT_FOUND', error: 'Claim not found.' });
2846

2947
const claim = claims[0];
3048
if (claim.status === 'paid' || claim.payment_status === 'paid') {
31-
return res.status(409).json({ ok: false, status: 'PAYMENT_ALREADY_COMPLETED' });
49+
return asConflict(res, 'PAYMENT_ALREADY_COMPLETED', 'Payment is already completed for this claim.');
3250
}
3351

3452
if (!['cards_published', 'payment_pending'].includes(claim.status)) {
35-
return res.status(409).json({ ok: false, status: 'CLAIM_NOT_READY_FOR_PAYMENT' });
53+
return asConflict(res, 'CLAIM_NOT_READY_FOR_PAYMENT', 'Claim must be cards_published before creating checkout.');
3654
}
3755

3856
if (claim.status === 'payment_pending' && claim.stripe_checkout_session_id) {
@@ -45,38 +63,53 @@ module.exports = async function handler(req, res) {
4563
});
4664
}
4765

48-
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
49-
const priceCents = Number.parseInt(process.env.STRIPE_FOUNDING_PRICE_CENTS || '2000', 10) || 2000;
5066
const siteUrl = process.env.COMMANDLAYER_SITE_URL || 'https://www.commandlayer.org';
5167
const successUrl = `${siteUrl}/claim/status.html?claimId=${encodeURIComponent(claimId)}&payment=success`;
5268
const cancelUrl = `${siteUrl}/claim/status.html?claimId=${encodeURIComponent(claimId)}&payment=cancelled`;
5369

54-
const session = await stripe.checkout.sessions.create({
55-
mode: 'payment',
56-
success_url: successUrl,
57-
cancel_url: cancelUrl,
58-
line_items: [{
59-
quantity: 1,
60-
price_data: {
61-
currency: 'usd',
62-
unit_amount: priceCents,
63-
product_data: { name: 'Founding Activation' }
70+
let session;
71+
try {
72+
session = await stripe.checkout.sessions.create({
73+
mode: 'payment',
74+
success_url: successUrl,
75+
cancel_url: cancelUrl,
76+
line_items: [{
77+
quantity: 1,
78+
price_data: {
79+
currency: 'usd',
80+
unit_amount: 2000,
81+
product_data: {
82+
name: 'CommandLayer Founding Activation',
83+
description: '10 Trust Verification agent namespaces'
84+
}
85+
}
86+
}],
87+
metadata: {
88+
claimId,
89+
tenant: claim.tenant || '',
90+
packId: claim.pack_id || '',
91+
product: 'founding_activation'
6492
}
65-
}],
66-
metadata: {
67-
claimId,
68-
tenant: claim.tenant || '',
69-
packId: claim.pack_id || '',
70-
product: 'founding_activation'
93+
});
94+
} catch (error) {
95+
console.error('ADMIN_CREATE_CHECKOUT_SESSION_FAILED', { message: error?.message, code: error?.code, claimId });
96+
const payload = {
97+
ok: false,
98+
status: 'CHECKOUT_SESSION_CREATE_FAILED',
99+
error: 'Unable to create Stripe checkout session.'
100+
};
101+
if (process.env.NODE_ENV !== 'production') {
102+
payload.debug = { message: error?.message || 'Unknown Stripe error', code: error?.code || null };
71103
}
72-
});
104+
return res.status(502).json(payload);
105+
}
73106

74107
await db.query(
75108
`insert into claim_payments (claim_id, provider, stripe_checkout_session_id, amount_cents, currency, status, metadata_json)
76109
values ($1, 'stripe', $2, $3, 'usd', 'pending', $4::jsonb)
77110
on conflict (stripe_checkout_session_id)
78111
do update set status = excluded.status, metadata_json = excluded.metadata_json, updated_at = now()`,
79-
[claimId, session.id, priceCents, JSON.stringify({ checkoutUrl: session.url || null })]
112+
[claimId, session.id, 2000, JSON.stringify({ checkoutUrl: session.url || null })]
80113
);
81114

82115
const fromStatus = claim.status;
@@ -88,7 +121,7 @@ module.exports = async function handler(req, res) {
88121
payment_currency = 'usd',
89122
stripe_checkout_session_id = $3
90123
where claim_id = $1`,
91-
[claimId, priceCents, session.id]
124+
[claimId, 2000, session.id]
92125
);
93126

94127
await db.query(
@@ -107,7 +140,7 @@ module.exports = async function handler(req, res) {
107140

108141
return res.status(200).json({ ok: true, status: 'CHECKOUT_SESSION_CREATED', claimId, checkoutUrl: session.url || null, sessionId: session.id });
109142
} catch (error) {
110-
console.error('ADMIN_CREATE_CHECKOUT_SESSION_FAILED', { message: error.message, code: error.code });
143+
console.error('ADMIN_CREATE_CHECKOUT_SESSION_UNEXPECTED', { message: error?.message, code: error?.code, claimId });
111144
return res.status(500).json({ ok: false, status: 'ADMIN_CREATE_CHECKOUT_SESSION_FAILED', error: 'Failed to create checkout session.' });
112145
}
113146
};

lib/stripe-client.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,20 @@
11
'use strict';
2-
module.exports = require('stripe');
2+
3+
const Stripe = require('stripe');
4+
5+
function createStripeError(code, message) {
6+
const error = new Error(message);
7+
error.code = code;
8+
return error;
9+
}
10+
11+
module.exports = function createStripeClient(secretKey, options = {}) {
12+
const key = typeof secretKey === 'string' ? secretKey.trim() : '';
13+
if (!key) {
14+
throw createStripeError('STRIPE_NOT_CONFIGURED', 'Stripe secret key is not configured.');
15+
}
16+
if (key.startsWith('pk_')) {
17+
throw createStripeError('STRIPE_SECRET_KEY_INVALID', 'Stripe secret key must be a server secret key (sk_*), not a publishable key.');
18+
}
19+
return new Stripe(key, options);
20+
};

0 commit comments

Comments
 (0)