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.
Files changed (2) hide show
  1. package/package.json +5 -4
  2. 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.4",
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
- "axios": "^1.6.0",
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"
@@ -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
- // Mock Stripe checkout sessions endpoint (SDK expects this path)
151
- this.app.post('/api/stripe/checkout-sessions', (req, res) => {
152
- const sessionId = 'cs_local_' + Date.now();
153
- res.json({
154
- success: true,
155
- data: {
156
- sessionId,
157
- url: `http://localhost:${this.port}/checkout/${sessionId}?success=${encodeURIComponent(req.body.success_url)}&cancel=${encodeURIComponent(req.body.cancel_url)}`,
158
- amount_total: req.body.amount || 999,
159
- currency: req.body.currency || 'usd',
160
- payment_status: 'unpaid',
161
- status: 'open'
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
- // Mock Stripe products endpoint
184
- this.app.get('/api/stripe/products', (req, res) => {
185
- res.json({
186
- success: true,
187
- data: [
188
- {
189
- id: 'prod_local_validate',
190
- name: 'Validate',
191
- description: 'Proof of concept validation',
192
- price: 500,
193
- currency: 'usd',
194
- active: true
195
- },
196
- {
197
- id: 'prod_local_prototype',
198
- name: 'Prototype',
199
- description: 'Build an MVP prototype',
200
- price: 2500,
201
- currency: 'usd',
202
- active: true
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
- // Mock webhook endpoint
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
- // In local mode, accept all webhooks
395
- const event = typeof req.body === 'string' ? JSON.parse(req.body) : req.body;
396
-
397
- if (!this.json) {
398
- console.log(chalk.blue(`⚡ Webhook received: ${event.type}`));
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
  }