spaps 0.3.4 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -4
- package/src/local-server.js +201 -46
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spaps",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "Sweet Potato Authentication & Payment Service CLI - Zero-config local development and project scaffolding",
|
|
5
5
|
"main": "bin/spaps.js",
|
|
6
6
|
"bin": {
|
|
@@ -37,13 +37,14 @@
|
|
|
37
37
|
},
|
|
38
38
|
"homepage": "https://sweetpotato.dev",
|
|
39
39
|
"dependencies": {
|
|
40
|
+
"axios": "^1.6.0",
|
|
40
41
|
"chalk": "^4.1.2",
|
|
41
42
|
"commander": "^11.1.0",
|
|
43
|
+
"cors": "^2.8.5",
|
|
44
|
+
"express": "^4.18.2",
|
|
42
45
|
"ora": "^5.4.1",
|
|
43
46
|
"prompts": "^2.4.2",
|
|
44
|
-
"
|
|
45
|
-
"express": "^4.18.2",
|
|
46
|
-
"cors": "^2.8.5"
|
|
47
|
+
"stripe": "^18.5.0"
|
|
47
48
|
},
|
|
48
49
|
"engines": {
|
|
49
50
|
"node": ">=16.0.0"
|
package/src/local-server.js
CHANGED
|
@@ -12,6 +12,11 @@ const { generateDocsHTML } = require('./docs-html');
|
|
|
12
12
|
const StripeLocalManager = require('./stripe-local');
|
|
13
13
|
const LocalAdminManager = require('./admin-local');
|
|
14
14
|
|
|
15
|
+
// Stripe configuration for test mode
|
|
16
|
+
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY || 'sk_test_51S1WOy2HT0E1dOewiHvzt7T96PDwjocSDDUuc2ur569AVA5fDj4UpNM66lujrda1tTYrgooG0Z1dNFZfwEZuZdcA00nuVLJW67');
|
|
17
|
+
const STRIPE_PUBLISHABLE_KEY = process.env.STRIPE_PUBLISHABLE_KEY || 'pk_test_51S1WOy2HT0E1dOewb2EkxZIaPkz7v3zMM9VxuBoxgNILYMmS85I4zrAWTkevyUQcaWlWUoC2NYnB8X5ZKd5e7Ifc005IzIW6H2';
|
|
18
|
+
const USE_REAL_STRIPE = process.env.USE_REAL_STRIPE !== 'false'; // Default to true
|
|
19
|
+
|
|
15
20
|
class LocalServer {
|
|
16
21
|
constructor(options = {}) {
|
|
17
22
|
this.port = options.port || process.env.PORT || 3456;
|
|
@@ -147,20 +152,74 @@ class LocalServer {
|
|
|
147
152
|
});
|
|
148
153
|
});
|
|
149
154
|
|
|
150
|
-
//
|
|
151
|
-
this.app.post('/api/stripe/checkout-sessions', (req, res) => {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
155
|
+
// Stripe checkout sessions endpoint - REAL or MOCK based on config
|
|
156
|
+
this.app.post('/api/stripe/checkout-sessions', async (req, res) => {
|
|
157
|
+
try {
|
|
158
|
+
if (USE_REAL_STRIPE) {
|
|
159
|
+
// Real Stripe checkout session
|
|
160
|
+
const { product_name, amount, currency = 'usd', success_url, cancel_url, price_id } = req.body;
|
|
161
|
+
|
|
162
|
+
let lineItems;
|
|
163
|
+
if (price_id) {
|
|
164
|
+
// Use existing price
|
|
165
|
+
lineItems = [{ price: price_id, quantity: 1 }];
|
|
166
|
+
} else {
|
|
167
|
+
// Create price on the fly
|
|
168
|
+
lineItems = [{
|
|
169
|
+
price_data: {
|
|
170
|
+
currency,
|
|
171
|
+
product_data: { name: product_name || 'Product' },
|
|
172
|
+
unit_amount: amount || 999
|
|
173
|
+
},
|
|
174
|
+
quantity: 1
|
|
175
|
+
}];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const session = await stripe.checkout.sessions.create({
|
|
179
|
+
mode: 'payment',
|
|
180
|
+
line_items: lineItems,
|
|
181
|
+
success_url,
|
|
182
|
+
cancel_url,
|
|
183
|
+
automatic_tax: { enabled: false },
|
|
184
|
+
customer_creation: 'always'
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
res.json({
|
|
188
|
+
success: true,
|
|
189
|
+
data: {
|
|
190
|
+
sessionId: session.id,
|
|
191
|
+
url: session.url,
|
|
192
|
+
amount_total: session.amount_total,
|
|
193
|
+
currency: session.currency,
|
|
194
|
+
payment_status: session.payment_status,
|
|
195
|
+
status: session.status
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
} else {
|
|
199
|
+
// Mock response (fallback)
|
|
200
|
+
const sessionId = 'cs_local_' + Date.now();
|
|
201
|
+
res.json({
|
|
202
|
+
success: true,
|
|
203
|
+
data: {
|
|
204
|
+
sessionId,
|
|
205
|
+
url: `http://localhost:${this.port}/checkout/${sessionId}?success=${encodeURIComponent(req.body.success_url)}&cancel=${encodeURIComponent(req.body.cancel_url)}`,
|
|
206
|
+
amount_total: req.body.amount || 999,
|
|
207
|
+
currency: req.body.currency || 'usd',
|
|
208
|
+
payment_status: 'unpaid',
|
|
209
|
+
status: 'open'
|
|
210
|
+
}
|
|
211
|
+
});
|
|
162
212
|
}
|
|
163
|
-
})
|
|
213
|
+
} catch (error) {
|
|
214
|
+
console.error('Stripe checkout error:', error);
|
|
215
|
+
res.status(500).json({
|
|
216
|
+
success: false,
|
|
217
|
+
error: {
|
|
218
|
+
code: 'CHECKOUT_ERROR',
|
|
219
|
+
message: error.message || 'Failed to create checkout session'
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
164
223
|
});
|
|
165
224
|
|
|
166
225
|
// Mock Stripe endpoints (legacy)
|
|
@@ -180,29 +239,77 @@ class LocalServer {
|
|
|
180
239
|
});
|
|
181
240
|
});
|
|
182
241
|
|
|
183
|
-
//
|
|
184
|
-
this.app.get('/api/stripe/products', (req, res) => {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
{
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
242
|
+
// Stripe products endpoint - REAL or MOCK based on config
|
|
243
|
+
this.app.get('/api/stripe/products', async (req, res) => {
|
|
244
|
+
try {
|
|
245
|
+
if (USE_REAL_STRIPE) {
|
|
246
|
+
// Fetch real Stripe products
|
|
247
|
+
const products = await stripe.products.list({
|
|
248
|
+
active: req.query.active !== undefined ? req.query.active === 'true' : undefined,
|
|
249
|
+
limit: req.query.limit ? parseInt(req.query.limit) : 10
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Get prices for each product
|
|
253
|
+
const productsWithPrices = await Promise.all(
|
|
254
|
+
products.data.map(async (product) => {
|
|
255
|
+
const prices = await stripe.prices.list({
|
|
256
|
+
product: product.id,
|
|
257
|
+
active: true,
|
|
258
|
+
limit: 1
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const defaultPrice = prices.data[0];
|
|
262
|
+
return {
|
|
263
|
+
id: product.id,
|
|
264
|
+
name: product.name,
|
|
265
|
+
description: product.description,
|
|
266
|
+
price: defaultPrice ? defaultPrice.unit_amount : 0,
|
|
267
|
+
currency: defaultPrice ? defaultPrice.currency : 'usd',
|
|
268
|
+
price_id: defaultPrice ? defaultPrice.id : null,
|
|
269
|
+
active: product.active,
|
|
270
|
+
metadata: product.metadata
|
|
271
|
+
};
|
|
272
|
+
})
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
res.json({
|
|
276
|
+
success: true,
|
|
277
|
+
data: productsWithPrices
|
|
278
|
+
});
|
|
279
|
+
} else {
|
|
280
|
+
// Mock response (fallback)
|
|
281
|
+
res.json({
|
|
282
|
+
success: true,
|
|
283
|
+
data: [
|
|
284
|
+
{
|
|
285
|
+
id: 'prod_local_validate',
|
|
286
|
+
name: 'Validate',
|
|
287
|
+
description: 'Proof of concept validation',
|
|
288
|
+
price: 500,
|
|
289
|
+
currency: 'usd',
|
|
290
|
+
active: true
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
id: 'prod_local_prototype',
|
|
294
|
+
name: 'Prototype',
|
|
295
|
+
description: 'Build an MVP prototype',
|
|
296
|
+
price: 2500,
|
|
297
|
+
currency: 'usd',
|
|
298
|
+
active: true
|
|
299
|
+
}
|
|
300
|
+
]
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
} catch (error) {
|
|
304
|
+
console.error('Stripe products error:', error);
|
|
305
|
+
res.status(500).json({
|
|
306
|
+
success: false,
|
|
307
|
+
error: {
|
|
308
|
+
code: 'PRODUCTS_ERROR',
|
|
309
|
+
message: error.message || 'Failed to fetch products'
|
|
203
310
|
}
|
|
204
|
-
|
|
205
|
-
}
|
|
311
|
+
});
|
|
312
|
+
}
|
|
206
313
|
});
|
|
207
314
|
|
|
208
315
|
// Mock auth nonce endpoint
|
|
@@ -389,19 +496,62 @@ class LocalServer {
|
|
|
389
496
|
`);
|
|
390
497
|
});
|
|
391
498
|
|
|
392
|
-
//
|
|
499
|
+
// Stripe webhook endpoint - REAL or MOCK based on config
|
|
393
500
|
this.app.post('/api/stripe/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
501
|
+
try {
|
|
502
|
+
let event;
|
|
503
|
+
|
|
504
|
+
if (USE_REAL_STRIPE) {
|
|
505
|
+
// Real Stripe webhook verification
|
|
506
|
+
const sig = req.headers['stripe-signature'];
|
|
507
|
+
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
|
|
508
|
+
|
|
509
|
+
if (webhookSecret && sig) {
|
|
510
|
+
try {
|
|
511
|
+
event = stripe.webhooks.constructEvent(req.body, sig, webhookSecret);
|
|
512
|
+
} catch (err) {
|
|
513
|
+
console.error('Webhook signature verification failed:', err.message);
|
|
514
|
+
return res.status(400).send(`Webhook Error: ${err.message}`);
|
|
515
|
+
}
|
|
516
|
+
} else {
|
|
517
|
+
// For local development without webhook secret
|
|
518
|
+
event = JSON.parse(req.body.toString());
|
|
519
|
+
}
|
|
520
|
+
} else {
|
|
521
|
+
// Mock mode - accept all webhooks
|
|
522
|
+
event = typeof req.body === 'string' ? JSON.parse(req.body) : req.body;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (!this.json) {
|
|
526
|
+
console.log(chalk.blue(`⚡ Webhook received: ${event.type}`));
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Handle the event
|
|
530
|
+
switch (event.type) {
|
|
531
|
+
case 'checkout.session.completed':
|
|
532
|
+
const session = event.data.object;
|
|
533
|
+
console.log(chalk.green(`✅ Payment successful: ${session.id}`));
|
|
534
|
+
break;
|
|
535
|
+
case 'payment_intent.succeeded':
|
|
536
|
+
const paymentIntent = event.data.object;
|
|
537
|
+
console.log(chalk.green(`💰 Payment intent succeeded: ${paymentIntent.id}`));
|
|
538
|
+
break;
|
|
539
|
+
case 'customer.subscription.created':
|
|
540
|
+
const subscription = event.data.object;
|
|
541
|
+
console.log(chalk.green(`📋 Subscription created: ${subscription.id}`));
|
|
542
|
+
break;
|
|
543
|
+
default:
|
|
544
|
+
console.log(chalk.yellow(`🔔 Unhandled event type: ${event.type}`));
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Store for testing
|
|
548
|
+
this.lastWebhookEvent = event;
|
|
549
|
+
|
|
550
|
+
res.json({ received: true });
|
|
551
|
+
} catch (error) {
|
|
552
|
+
console.error('Webhook processing error:', error);
|
|
553
|
+
res.status(500).json({ error: error.message });
|
|
399
554
|
}
|
|
400
|
-
|
|
401
|
-
// Store for testing
|
|
402
|
-
this.lastWebhookEvent = event;
|
|
403
|
-
|
|
404
|
-
res.json({ received: true });
|
|
405
555
|
});
|
|
406
556
|
|
|
407
557
|
// Webhook testing UI
|
|
@@ -787,6 +937,11 @@ class LocalServer {
|
|
|
787
937
|
console.log(chalk.yellow('🍠 SPAPS Local Development Server'));
|
|
788
938
|
console.log(chalk.green(`✨ Running at: http://localhost:${this.port}`));
|
|
789
939
|
console.log(chalk.blue(`📝 Documentation: http://localhost:${this.port}/docs`));
|
|
940
|
+
if (USE_REAL_STRIPE) {
|
|
941
|
+
console.log(chalk.magenta('💳 Stripe: Real test mode (live API calls)'));
|
|
942
|
+
} else {
|
|
943
|
+
console.log(chalk.gray('💳 Stripe: Mock mode (simulated responses)'));
|
|
944
|
+
}
|
|
790
945
|
console.log(chalk.dim(' Press Ctrl+C to stop'));
|
|
791
946
|
console.log();
|
|
792
947
|
}
|