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.
- package/package.json +1 -1
- package/src/local-server.js +325 -0
package/package.json
CHANGED
package/src/local-server.js
CHANGED
|
@@ -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({
|