diff --git a/apps/api/src/db/payments/payments_products.py b/apps/api/src/db/payments/payments_products.py index 7eb5fbd3..56cdcc73 100644 --- a/apps/api/src/db/payments/payments_products.py +++ b/apps/api/src/db/payments/payments_products.py @@ -7,10 +7,15 @@ class PaymentProductTypeEnum(str, Enum): SUBSCRIPTION = "subscription" ONE_TIME = "one_time" +class PaymentPriceTypeEnum(str, Enum): + CUSTOMER_CHOICE = "customer_choice" + FIXED_PRICE = "fixed_price" + class PaymentsProductBase(SQLModel): name: str = "" description: Optional[str] = "" product_type: PaymentProductTypeEnum = PaymentProductTypeEnum.ONE_TIME + price_type: PaymentPriceTypeEnum = PaymentPriceTypeEnum.FIXED_PRICE benefits: str = "" amount: float = 0.0 currency: str = "USD" diff --git a/apps/api/src/services/payments/stripe.py b/apps/api/src/services/payments/stripe.py index 5be8c178..052d2618 100644 --- a/apps/api/src/services/payments/stripe.py +++ b/apps/api/src/services/payments/stripe.py @@ -1,7 +1,7 @@ from fastapi import HTTPException, Request from sqlmodel import Session import stripe -from src.db.payments.payments_products import PaymentProductTypeEnum, PaymentsProduct +from src.db.payments.payments_products import PaymentPriceTypeEnum, PaymentProductTypeEnum, PaymentsProduct from src.db.users import AnonymousUser, PublicUser from src.services.payments.payments import get_payments_config @@ -43,22 +43,23 @@ async def create_stripe_product( # Set the Stripe API key using the credentials stripe.api_key = creds.get('stripe_secret_key') - ## Create product - - # Interval or one time - if product_data.product_type == PaymentProductTypeEnum.SUBSCRIPTION: - interval = "month" + # Prepare default_price_data based on price_type + if product_data.price_type == PaymentPriceTypeEnum.CUSTOMER_CHOICE: + default_price_data = { + "currency": product_data.currency, + "custom_unit_amount": { + "enabled": True, + "minimum": int(product_data.amount * 100), # Convert to cents + } + } else: - interval = None - - # Prepare default_price_data - default_price_data = { - "currency": product_data.currency, - "unit_amount": int(product_data.amount * 100) # Convert to cents - } - - if interval: - default_price_data["recurring"] = {"interval": interval} + default_price_data = { + "currency": product_data.currency, + "unit_amount": int(product_data.amount * 100) # Convert to cents + } + + if product_data.product_type == PaymentProductTypeEnum.SUBSCRIPTION: + default_price_data["recurring"] = {"interval": "month"} product = stripe.Product.create( name=product_data.name, @@ -104,13 +105,22 @@ async def update_stripe_product( stripe.api_key = creds.get('stripe_secret_key') try: - - # Always create a new price - new_price_data = { - "currency": product_data.currency, - "unit_amount": int(product_data.amount * 100), # Convert to cents - "product": product_id, - } + # Create new price based on price_type + if product_data.price_type == PaymentPriceTypeEnum.CUSTOMER_CHOICE: + new_price_data = { + "currency": product_data.currency, + "product": product_id, + "custom_unit_amount": { + "enabled": True, + "minimum": int(product_data.amount * 100), # Convert to cents + } + } + else: + new_price_data = { + "currency": product_data.currency, + "unit_amount": int(product_data.amount * 100), # Convert to cents + "product": product_id, + } if product_data.product_type == PaymentProductTypeEnum.SUBSCRIPTION: new_price_data["recurring"] = {"interval": "month"} @@ -128,16 +138,12 @@ async def update_stripe_product( # Update the product in Stripe updated_product = stripe.Product.modify(product_id, **update_data) - # Archive all existing prices for the product existing_prices = stripe.Price.list(product=product_id, active=True) for price in existing_prices: if price.id != new_price.id: stripe.Price.modify(price.id, active=False) - - # Set the new price as the default price for the product - updated_product = stripe.Product.modify(product_id, default_price=new_price.id) return updated_product except stripe.StripeError as e: diff --git a/apps/web/components/Dashboard/Payments/SubComponents/CreateProductForm.tsx b/apps/web/components/Dashboard/Payments/SubComponents/CreateProductForm.tsx index 173297b1..22ef657e 100644 --- a/apps/web/components/Dashboard/Payments/SubComponents/CreateProductForm.tsx +++ b/apps/web/components/Dashboard/Payments/SubComponents/CreateProductForm.tsx @@ -16,16 +16,20 @@ import currencyCodes from 'currency-codes'; const validationSchema = Yup.object().shape({ name: Yup.string().required('Name is required'), description: Yup.string().required('Description is required'), - amount: Yup.number().min(0, 'Amount must be positive').required('Amount is required'), + amount: Yup.number() + .min(1, 'Amount must be greater than zero') + .required('Amount is required'), benefits: Yup.string(), currency: Yup.string().required('Currency is required'), product_type: Yup.string().oneOf(['one_time', 'subscription']).required('Product type is required'), + price_type: Yup.string().oneOf(['fixed_price', 'customer_choice']).required('Price type is required'), }); interface ProductFormValues { name: string; description: string; product_type: 'one_time' | 'subscription'; + price_type: 'fixed_price' | 'customer_choice'; benefits: string; amount: number; currency: string; @@ -48,6 +52,7 @@ const CreateProductForm: React.FC<{ onSuccess: () => void }> = ({ onSuccess }) = name: '', description: '', product_type: 'one_time', + price_type: 'fixed_price', benefits: '', amount: 0, currency: 'USD', @@ -92,11 +97,49 @@ const CreateProductForm: React.FC<{ onSuccess: () => void }> = ({ onSuccess }) = + +
+ + + +
+ +
+ + + +
- - + +
@@ -120,23 +163,6 @@ const CreateProductForm: React.FC<{ onSuccess: () => void }> = ({ onSuccess }) =
-
- - - -
-