spaps 0.3.10 → 0.4.0

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 +1 -1
  2. package/src/local-server.js +325 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spaps",
3
- "version": "0.3.10",
3
+ "version": "0.4.0",
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": {
@@ -523,6 +523,331 @@ class LocalServer {
523
523
  }
524
524
  });
525
525
 
526
+ // Enhanced Stripe Product Management - Full CRUD
527
+
528
+ // GET /api/stripe/products - List all products with filtering
529
+ this.app.get('/api/stripe/products', async (req, res) => {
530
+ try {
531
+ if (USE_REAL_STRIPE) {
532
+ const { active, category, limit = '100' } = req.query;
533
+
534
+ const products = await stripe.products.list({
535
+ active: active === 'true' ? true : active === 'false' ? false : undefined,
536
+ limit: parseInt(limit),
537
+ expand: ['data.default_price']
538
+ });
539
+
540
+ // Filter out non-SPAPS managed products if needed
541
+ let filteredProducts = products.data;
542
+ if (category === 'spaps') {
543
+ filteredProducts = products.data.filter(p =>
544
+ p.metadata && p.metadata.spaps_managed === 'true'
545
+ );
546
+ }
547
+
548
+ res.json({
549
+ success: true,
550
+ data: {
551
+ products: filteredProducts.map(product => ({
552
+ id: product.id,
553
+ name: product.name,
554
+ description: product.description,
555
+ images: product.images,
556
+ active: product.active,
557
+ metadata: product.metadata,
558
+ default_price: product.default_price ? {
559
+ id: product.default_price.id,
560
+ unit_amount: product.default_price.unit_amount,
561
+ currency: product.default_price.currency,
562
+ recurring: product.default_price.recurring
563
+ } : null,
564
+ created: product.created,
565
+ updated: product.updated
566
+ })),
567
+ has_more: products.has_more,
568
+ total_count: filteredProducts.length
569
+ }
570
+ });
571
+ } else {
572
+ // Mock response
573
+ const localProducts = this.adminManager.listProducts();
574
+ res.json({
575
+ success: true,
576
+ data: {
577
+ products: localProducts,
578
+ has_more: false,
579
+ total_count: localProducts.length
580
+ }
581
+ });
582
+ }
583
+ } catch (error) {
584
+ console.error('List products error:', error);
585
+ res.status(500).json({
586
+ success: false,
587
+ error: {
588
+ code: 'LIST_PRODUCTS_ERROR',
589
+ message: error.message || 'Failed to list products'
590
+ }
591
+ });
592
+ }
593
+ });
594
+
595
+ // POST /api/stripe/products - Create new product
596
+ this.app.post('/api/stripe/products', async (req, res) => {
597
+ try {
598
+ const { name, description, images, metadata = {}, active = true } = req.body;
599
+
600
+ if (!name) {
601
+ return res.status(400).json({
602
+ success: false,
603
+ error: { message: 'Product name is required' }
604
+ });
605
+ }
606
+
607
+ if (USE_REAL_STRIPE) {
608
+ // Create product in Stripe
609
+ const stripeProduct = await stripe.products.create({
610
+ name,
611
+ description,
612
+ images,
613
+ active,
614
+ metadata: {
615
+ ...metadata,
616
+ spaps_managed: 'true',
617
+ created_by: 'spaps_cli'
618
+ }
619
+ });
620
+
621
+ res.json({
622
+ success: true,
623
+ data: {
624
+ product: {
625
+ id: stripeProduct.id,
626
+ name: stripeProduct.name,
627
+ description: stripeProduct.description,
628
+ images: stripeProduct.images,
629
+ active: stripeProduct.active,
630
+ metadata: stripeProduct.metadata,
631
+ created: stripeProduct.created
632
+ }
633
+ }
634
+ });
635
+ } else {
636
+ // Create locally
637
+ const product = this.adminManager.createProduct({
638
+ name,
639
+ description,
640
+ price: 0, // Will need to set price separately
641
+ currency: 'usd'
642
+ });
643
+
644
+ res.json({
645
+ success: true,
646
+ data: { product }
647
+ });
648
+ }
649
+ } catch (error) {
650
+ console.error('Create product error:', error);
651
+ res.status(500).json({
652
+ success: false,
653
+ error: {
654
+ code: 'CREATE_PRODUCT_ERROR',
655
+ message: error.message || 'Failed to create product'
656
+ }
657
+ });
658
+ }
659
+ });
660
+
661
+ // PUT /api/stripe/products/:productId - Update product
662
+ this.app.put('/api/stripe/products/:productId', async (req, res) => {
663
+ try {
664
+ const { productId } = req.params;
665
+ const { name, description, images, metadata, active } = req.body;
666
+
667
+ if (USE_REAL_STRIPE) {
668
+ // Update in Stripe
669
+ const updateData = {};
670
+ if (name !== undefined) updateData.name = name;
671
+ if (description !== undefined) updateData.description = description;
672
+ if (images !== undefined) updateData.images = images;
673
+ if (metadata !== undefined) updateData.metadata = metadata;
674
+ if (active !== undefined) updateData.active = active;
675
+
676
+ const stripeProduct = await stripe.products.update(productId, updateData);
677
+
678
+ res.json({
679
+ success: true,
680
+ data: {
681
+ product: {
682
+ id: stripeProduct.id,
683
+ name: stripeProduct.name,
684
+ description: stripeProduct.description,
685
+ images: stripeProduct.images,
686
+ active: stripeProduct.active,
687
+ metadata: stripeProduct.metadata,
688
+ updated: stripeProduct.updated
689
+ }
690
+ }
691
+ });
692
+ } else {
693
+ // Update locally
694
+ const product = this.adminManager.updateProduct(productId, {
695
+ name,
696
+ description,
697
+ active
698
+ });
699
+
700
+ res.json({
701
+ success: true,
702
+ data: { product }
703
+ });
704
+ }
705
+ } catch (error) {
706
+ console.error('Update product error:', error);
707
+ res.status(500).json({
708
+ success: false,
709
+ error: {
710
+ code: 'UPDATE_PRODUCT_ERROR',
711
+ message: error.message || 'Failed to update product'
712
+ }
713
+ });
714
+ }
715
+ });
716
+
717
+ // DELETE /api/stripe/products/:productId - Archive product
718
+ this.app.delete('/api/stripe/products/:productId', async (req, res) => {
719
+ try {
720
+ const { productId } = req.params;
721
+
722
+ if (USE_REAL_STRIPE) {
723
+ // Archive in Stripe (can't truly delete)
724
+ const stripeProduct = await stripe.products.update(productId, {
725
+ active: false
726
+ });
727
+
728
+ res.json({
729
+ success: true,
730
+ message: 'Product archived successfully',
731
+ data: {
732
+ product: {
733
+ id: stripeProduct.id,
734
+ active: stripeProduct.active,
735
+ updated: stripeProduct.updated
736
+ }
737
+ }
738
+ });
739
+ } else {
740
+ // Soft delete locally
741
+ const result = this.adminManager.deleteProduct(productId);
742
+
743
+ res.json({
744
+ success: true,
745
+ message: 'Product deleted successfully',
746
+ data: result
747
+ });
748
+ }
749
+ } catch (error) {
750
+ console.error('Delete product error:', error);
751
+ res.status(500).json({
752
+ success: false,
753
+ error: {
754
+ code: 'DELETE_PRODUCT_ERROR',
755
+ message: error.message || 'Failed to delete product'
756
+ }
757
+ });
758
+ }
759
+ });
760
+
761
+ // POST /api/stripe/prices - Create price for product
762
+ this.app.post('/api/stripe/prices', async (req, res) => {
763
+ try {
764
+ const {
765
+ product_id,
766
+ unit_amount,
767
+ currency = 'usd',
768
+ recurring = null,
769
+ nickname,
770
+ metadata = {},
771
+ active = true
772
+ } = req.body;
773
+
774
+ if (!product_id || !unit_amount) {
775
+ return res.status(400).json({
776
+ success: false,
777
+ error: { message: 'Product ID and unit amount are required' }
778
+ });
779
+ }
780
+
781
+ if (USE_REAL_STRIPE) {
782
+ // Create price in Stripe
783
+ const priceData = {
784
+ product: product_id,
785
+ unit_amount: parseInt(unit_amount),
786
+ currency,
787
+ metadata: {
788
+ ...metadata,
789
+ spaps_managed: 'true'
790
+ }
791
+ };
792
+
793
+ if (recurring) {
794
+ priceData.recurring = {
795
+ interval: recurring.interval || 'month',
796
+ interval_count: recurring.interval_count || 1
797
+ };
798
+ }
799
+
800
+ if (nickname) priceData.nickname = nickname;
801
+
802
+ const stripePrice = await stripe.prices.create(priceData);
803
+
804
+ res.json({
805
+ success: true,
806
+ data: {
807
+ price: {
808
+ id: stripePrice.id,
809
+ product: stripePrice.product,
810
+ unit_amount: stripePrice.unit_amount,
811
+ currency: stripePrice.currency,
812
+ type: stripePrice.type,
813
+ recurring: stripePrice.recurring,
814
+ nickname: stripePrice.nickname,
815
+ active: stripePrice.active,
816
+ created: stripePrice.created
817
+ }
818
+ }
819
+ });
820
+ } else {
821
+ // Mock price creation
822
+ res.json({
823
+ success: true,
824
+ data: {
825
+ price: {
826
+ id: `price_local_${Date.now()}`,
827
+ product: product_id,
828
+ unit_amount,
829
+ currency,
830
+ type: recurring ? 'recurring' : 'one_time',
831
+ recurring,
832
+ nickname,
833
+ active,
834
+ created: Math.floor(Date.now() / 1000)
835
+ }
836
+ }
837
+ });
838
+ }
839
+ } catch (error) {
840
+ console.error('Create price error:', error);
841
+ res.status(500).json({
842
+ success: false,
843
+ error: {
844
+ code: 'CREATE_PRICE_ERROR',
845
+ message: error.message || 'Failed to create price'
846
+ }
847
+ });
848
+ }
849
+ });
850
+
526
851
  // Mock usage endpoints
527
852
  this.app.get('/api/usage/balance', (req, res) => {
528
853
  res.json({