diff --git a/padelclub_backend/settings.py b/padelclub_backend/settings.py index 5520a72..5d8b334 100644 --- a/padelclub_backend/settings.py +++ b/padelclub_backend/settings.py @@ -72,6 +72,8 @@ TEMPLATES = [ 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', + 'shop.context_processors.stripe_context', + ], }, }, @@ -154,22 +156,37 @@ AUTHENTICATION_BACKENDS = [ CSRF_COOKIE_SECURE = True # if using HTTPS SESSION_COOKIE_SECURE = True # Si vous utilisez HTTPS -# Stripe Settings -STRIPE_PUBLISHABLE_KEY = 'pk_test_51R4LrTPEZkECCx484C2KbmRpcO2ZkZb0NoNi8QJB4X3E5JFu3bvLk4JZQmz9grKbk6O40z3xI8DawHrGyUY0fOT600VEKC9ran' # Replace with your actual key -STRIPE_SECRET_KEY = 'sk_test_51R4LrTPEZkECCx48PkSbEYarhts7J7XNYpS1mJgows5z5dcv38l0G2tImvhXCjzvMgUH9ML0vLMOEPeyUBtYVf5H00Qvz8t3rE' # Replace with your actual key -STRIPE_WEBHOOK_SECRET = 'whsec_cbaa9c0c7b24041136e063a7d60fb674ec0646b2c4b821512c41a27634d7b1ba' # Optional for later -STRIPE_CURRENCY = 'eur' # Set to your preferred currency -# STRIPE_PUBLISHABLE_KEY = os.environ.get('STRIPE_PUBLISHABLE_KEY', 'your_test_publishable_key') -# STRIPE_SECRET_KEY = os.environ.get('STRIPE_SECRET_KEY', 'your_test_secret_key') -# STRIPE_WEBHOOK_SECRET = os.environ.get('STRIPE_WEBHOOK_SECRET', 'your_test_webhook_secret') - +STRIPE_MODE = os.environ.get('STRIPE_MODE', 'test') # Default to test mode + +# Depending on the mode, use appropriate keys +if STRIPE_MODE == 'test': + STRIPE_PUBLISHABLE_KEY = os.environ.get( + 'STRIPE_TEST_PUBLISHABLE_KEY', + 'pk_test_51R4LrTPEZkECCx484C2KbmRpcO2ZkZb0NoNi8QJB4X3E5JFu3bvLk4JZQmz9grKbk6O40z3xI8DawHrGyUY0fOT600VEKC9ran' + ) + STRIPE_SECRET_KEY = os.environ.get( + 'STRIPE_TEST_SECRET_KEY', + 'sk_test_51R4LrTPEZkECCx48PkSbEYarhts7J7XNYpS1mJgows5z5dcv38l0G2tImvhXCjzvMgUH9ML0vLMOEPeyUBtYVf5H00Qvz8t3rE' + ) + STRIPE_WEBHOOK_SECRET = os.environ.get( + 'STRIPE_TEST_WEBHOOK_SECRET', + 'whsec_cbaa9c0c7b24041136e063a7d60fb674ec0646b2c4b821512c41a27634d7b1ba' + ) +else: + STRIPE_PUBLISHABLE_KEY = os.environ.get('STRIPE_LIVE_PUBLISHABLE_KEY', '') + STRIPE_SECRET_KEY = os.environ.get('STRIPE_LIVE_SECRET_KEY', '') + STRIPE_WEBHOOK_SECRET = os.environ.get('STRIPE_LIVE_WEBHOOK_SECRET', '') + +STRIPE_CURRENCY = 'eur' # Add managers who should receive internal emails -MANAGERS = [ +SHOP_MANAGERS = [ ('Razmig Sarkissian', 'razmig@padelclub.app'), + # ('Shop Admin', 'shop-admin@padelclub.app'), # ('Laurent Morvillier', 'laurent@padelclub.app'), # ('Xavier Rousset', 'xavier@padelclub.app'), ] SITE_URL = 'https://padelclub.app' +SHOP_SUPPORT_EMAIL = 'shop-support@padelclub.app' from .settings_local import * from .settings_app import * diff --git a/shop/context_processors.py b/shop/context_processors.py new file mode 100644 index 0000000..b55a424 --- /dev/null +++ b/shop/context_processors.py @@ -0,0 +1,10 @@ +from django.conf import settings + +def stripe_context(request): + """Add Stripe-related context variables to templates""" + stripe_mode = getattr(settings, 'STRIPE_MODE', 'test') + return { + 'STRIPE_PUBLISHABLE_KEY': settings.STRIPE_PUBLISHABLE_KEY, + 'STRIPE_MODE': stripe_mode, + 'STRIPE_IS_TEST_MODE': stripe_mode == 'test', + } diff --git a/shop/migrations/0017_order_stripe_mode.py b/shop/migrations/0017_order_stripe_mode.py new file mode 100644 index 0000000..8a018a9 --- /dev/null +++ b/shop/migrations/0017_order_stripe_mode.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.11 on 2025-03-21 08:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('shop', '0016_order_webhook_processed'), + ] + + operations = [ + migrations.AddField( + model_name='order', + name='stripe_mode', + field=models.CharField(choices=[('test', 'Test Mode'), ('live', 'Live Mode')], default='test', max_length=10), + ), + ] diff --git a/shop/models.py b/shop/models.py index e656844..e0cd3a2 100644 --- a/shop/models.py +++ b/shop/models.py @@ -80,6 +80,10 @@ class Order(models.Model): ('FAILED', 'Failed'), ]) webhook_processed = models.BooleanField(default=False) + stripe_mode = models.CharField(max_length=10, default='test', choices=[ + ('test', 'Test Mode'), + ('live', 'Live Mode'), + ]) def __str__(self): return f"Order #{self.id} - {self.status}" diff --git a/shop/signals.py b/shop/signals.py index 79c0b3e..4dd1091 100644 --- a/shop/signals.py +++ b/shop/signals.py @@ -115,7 +115,7 @@ Ceci est un message automatique. Merci de ne pas répondre. """ # Send internal email - recipient_list = [email for name, email in settings.MANAGERS] + recipient_list = [email for name, email in settings.SHOP_MANAGERS] if not recipient_list: recipient_list = [settings.DEFAULT_FROM_EMAIL] @@ -131,7 +131,7 @@ Ceci est un message automatique. Merci de ne pas répondre. if status == OrderStatus.PAID and customer_email and instance.payment_status == "PAID": # Generate customer-facing URLs shop_url = f"{settings.SITE_URL}/shop" - contact_email = "support@padelclub.app" + contact_email = f"{settings.SHOP_SUPPORT_EMAIL}" # Create a customer receipt email customer_subject = f"Confirmation de votre commande #{order_id} - PadelClub" diff --git a/shop/stripe_utils.py b/shop/stripe_utils.py index bf43eef..99fb354 100644 --- a/shop/stripe_utils.py +++ b/shop/stripe_utils.py @@ -1,47 +1,90 @@ import stripe from django.conf import settings +import logging -# Configure Stripe with your secret key -stripe.api_key = settings.STRIPE_SECRET_KEY +logger = logging.getLogger(__name__) -def create_payment_intent(amount, currency=settings.STRIPE_CURRENCY, metadata=None): - """ - Create a payment intent with Stripe +class StripeService: + """Service class to manage Stripe operations with mode awareness""" - Args: - amount: Amount in cents (e.g., 1000 for €10.00) - currency: Currency code (default: settings.STRIPE_CURRENCY) - metadata: Additional info to attach to the payment intent + def __init__(self): + # Determine if we're in test mode + self.mode = getattr(settings, 'STRIPE_MODE', 'test') + self.is_test_mode = self.mode == 'test' - Returns: - The created payment intent object - """ - intent = stripe.PaymentIntent.create( + # Get appropriate keys based on mode + self.api_key = settings.STRIPE_SECRET_KEY + self.publishable_key = settings.STRIPE_PUBLISHABLE_KEY + self.webhook_secret = settings.STRIPE_WEBHOOK_SECRET + self.currency = getattr(settings, 'STRIPE_CURRENCY', 'eur') + + # Configure Stripe library + stripe.api_key = self.api_key + + # Log initialization in debug mode + mode_str = "TEST" if self.is_test_mode else "LIVE" + logger.debug(f"Initialized StripeService in {mode_str} mode") + + def create_checkout_session(self, line_items, success_url, cancel_url, metadata=None): + """Create a Stripe Checkout Session for one-time payments""" + if self.is_test_mode: + logger.info(f"Creating checkout session in TEST mode with metadata: {metadata}") + + session = stripe.checkout.Session.create( + payment_method_types=['card'], + line_items=line_items, + mode='payment', + success_url=success_url, + cancel_url=cancel_url, + metadata=metadata or {}, + ) + return session + + def verify_webhook_signature(self, payload, signature): + """Verify webhook signature using mode-appropriate secret""" + try: + event = stripe.Webhook.construct_event( + payload, signature, self.webhook_secret + ) + return event + except stripe.error.SignatureVerificationError as e: + logger.error(f"Webhook signature verification failed: {str(e)}") + raise + + def construct_event_for_testing(self, payload, signature): + """Helper method to construct events during testing""" + return stripe.Webhook.construct_event( + payload, signature, self.webhook_secret + ) + + def get_checkout_session(self, session_id): + """Retrieve a checkout session by ID""" + return stripe.checkout.Session.retrieve(session_id) + + def get_payment_intent(self, payment_intent_id): + """Retrieve a payment intent by ID""" + return stripe.PaymentIntent.retrieve(payment_intent_id) + +# Create a singleton instance for import and use throughout the app +stripe_service = StripeService() + +# For backward compatibility, expose some functions directly +def create_payment_intent(amount, currency=None, metadata=None): + """Legacy function for backward compatibility""" + if currency is None: + currency = stripe_service.currency + + return stripe.PaymentIntent.create( amount=amount, currency=currency, metadata=metadata or {}, ) - return intent def create_checkout_session(line_items, success_url, cancel_url, metadata=None): - """ - Create a Stripe Checkout Session for one-time payments - - Args: - line_items: List of items to purchase - success_url: URL to redirect on successful payment - cancel_url: URL to redirect on canceled payment - metadata: Additional info to attach to the session - - Returns: - The created checkout session - """ - session = stripe.checkout.Session.create( - payment_method_types=['card'], + """Legacy function for backward compatibility""" + return stripe_service.create_checkout_session( line_items=line_items, - mode='payment', success_url=success_url, cancel_url=cancel_url, metadata=metadata or {}, ) - return session diff --git a/shop/templates/shop/cart.html b/shop/templates/shop/cart.html index 8db24d4..af2d6ec 100644 --- a/shop/templates/shop/cart.html +++ b/shop/templates/shop/cart.html @@ -16,6 +16,14 @@ Se connecter {% endif %} +{% if STRIPE_IS_TEST_MODE %} +
+ ⚠️ Test Mode: Stripe is currently in test mode. No real payments will be processed. +
+ Use test card: 4242 4242 4242 4242 with any future date and any CVC. +
+{% endif %} +

Votre panier

diff --git a/shop/templates/shop/payment.html b/shop/templates/shop/payment.html index 9156226..f22c0ee 100644 --- a/shop/templates/shop/payment.html +++ b/shop/templates/shop/payment.html @@ -5,6 +5,13 @@ {% block second_title %}La Boutique{% endblock %} {% block content %} +{% if STRIPE_IS_TEST_MODE %} +
+ ⚠️ Test Mode: Stripe is currently in test mode. No real payments will be processed. +
+ Use test card: 4242 4242 4242 4242 with any future date and any CVC. +
+{% endif %}