From 0546c181833b933bd19d4769bb53710dacf5a2cf Mon Sep 17 00:00:00 2001 From: Nathan Chapman Date: Sun, 22 Jan 2023 14:16:06 -0700 Subject: [PATCH] Add Stripe webhook signature verification --- nginx/prod.conf | 2 +- ptcoffee/settings.py | 1 + storefront/cart.py | 1 + storefront/views.py | 103 +++++++++---------------------------------- 4 files changed, 25 insertions(+), 82 deletions(-) diff --git a/nginx/prod.conf b/nginx/prod.conf index 749911a..0757e7c 100644 --- a/nginx/prod.conf +++ b/nginx/prod.conf @@ -17,7 +17,7 @@ server { server { listen 443 ssl http2; listen [::]:443 ssl http2; - server_name ptcoffee.com; + server_name ptcoffee.com www.ptcoffee.com; # SSL ssl_certificate /etc/letsencrypt/live/ptcoffee.com/fullchain.pem; diff --git a/ptcoffee/settings.py b/ptcoffee/settings.py index 1c1c3f2..af8c72b 100644 --- a/ptcoffee/settings.py +++ b/ptcoffee/settings.py @@ -272,6 +272,7 @@ FACEBOOK_PIXEL_ID = env('FACEBOOK_PIXEL_ID', '') # https://stripe.com/docs STRIPE_API_KEY = env('STRIPE_API_KEY') +STRIPE_WEBHOOK_SECRET = env('STRIPE_WEBHOOK_SECRET', None) # PayPal diff --git a/storefront/cart.py b/storefront/cart.py index f54f13c..8616b8c 100644 --- a/storefront/cart.py +++ b/storefront/cart.py @@ -271,6 +271,7 @@ class Cart: validation.result['RateV4Response']['Package']['Postage'] ) except KeyError: + logger.warning(validation.result) raise USPSPostageError( 'Could not retrieve postage.' ) diff --git a/storefront/views.py b/storefront/views.py index e75d084..3c43e7b 100644 --- a/storefront/views.py +++ b/storefront/views.py @@ -721,108 +721,49 @@ class SubscriptionDoneView(TemplateView): def stripe_webhook(request): # You can use webhooks to receive information about asynchronous payment events. # For more about our webhook events check out https://stripe.com/docs/webhooks. - webhook_secret = None - request_data = json.loads(request.body) + endpoint_secret = settings.STRIPE_WEBHOOK_SECRET + payload = request.body + sig_header = request.META['HTTP_STRIPE_SIGNATURE'] + event = None - if webhook_secret: - # Retrieve the event by verifying the signature using the raw body - # and secret if webhook signing is configured. - signature = request.headers.get('stripe-signature') - try: - event = stripe.Webhook.construct_event( - payload=request.data, sig_header=signature, secret=webhook_secret) - data = event['data'] - except Exception as e: - return e - # Get the type of webhook event sent - used to check the status - # of PaymentIntents. - event_type = event['type'] - else: - data = request_data['data'] - event_type = request_data['type'] + try: + event = stripe.Webhook.construct_event( + payload, sig_header, endpoint_secret + ) + except ValueError as e: + # Invalid payload + logger.warning('Stripe Webhook: Invalid payload') + return JsonResponse({'status': 'ERROR: Invalid payload'}) + except stripe.error.SignatureVerificationError as e: + # Invalid signature + logger.warning('Stripe Webhook: Invalid signature') + return JsonResponse({'status': 'ERROR: Invalid signature'}) - data_object = data['object'] - - # logger.warning('\n') - # logger.warning(event_type.upper() + ':\n') - # logger.warning(data) - # logger.warning('\n') - - if event_type == 'checkout.session.completed': - # Payment is successful and the subscription is created. - # You should provision the subscription and save the customer ID to your database. - pass - - if event_type == 'customer.subscription.created': + if event.type == 'customer.subscription.created': try: subscription = Subscription.objects.get( - pk=data_object['metadata'].get('subscription_pk') + pk=event.data.object['metadata'].get('subscription_pk') ) except Subscription.DoesNotExist: logger.warning('Subscription does not exist') raise else: - subscription.stripe_id = data_object['id'] + subscription.stripe_id = event.data.object['id'] subscription.is_active = True subscription.save() - if event_type == 'invoice.paid': + if event.type == 'invoice.paid': # Continue to provision the subscription as payments continue to be made. # Store the status in your database and check when a user accesses your service. # This approach helps you avoid hitting rate limits. try: subscription = Subscription.objects.get( - stripe_id=data_object['subscription'] + stripe_id=event.data.object['subscription'] ) except Subscription.DoesNotExist: logger.warning('Subscription does not exist') raise else: - subscription.create_order(data_object) - - if event_type == 'invoice.payment_failed': - # The payment failed or the customer does not have a valid payment method. - # The subscription becomes past_due. Notify your customer and send them to the - # customer portal to update their payment information. - pass - - if event_type == 'invoice.created': - # Add shipping cost as an item on the invoice - # shipping_cost = get_shipping_cost( - # self.object.total_weight, - # self.request.user.default_shipping_address.postal_code - # ) * 100 - # stripe.InvoiceItem.create( - # customer=data_object['customer'], - # subscription=data_object['subscription'], - # description='Shipping', - # unit_amount=1234, - # currency=settings.DEFAULT_CURRENCY.lower() - # ) - pass - - # # if event_type == 'checkout.session.completed': - - # if event_type == 'invoice.paid': - # # Used to provision services after the trial has ended. - # # The status of the invoice will show up as paid. Store the status in your - # # database to reference when a user accesses your service to avoid hitting rate - # # limits. - # messages.success(request, 'Paid') - # logger.warning(data) - - # if event_type == 'invoice.payment_failed': - # # If the payment fails or the customer does not have a valid payment method, - # # an invoice.payment_failed event is sent, the subscription becomes past_due. - # # Use this webhook to notify your user that their payment has - # # failed and to retrieve new card details. - # messages.warning(request, 'Payment failed') - # logger.warning(data) - - # if event_type == 'customer.subscription.deleted': - # # handle subscription canceled automatically based - # # upon your subscription settings. Or if the user cancels it. - # messages.error(request, 'Deleted') - # logger.warning(data) + subscription.create_order(event.data.object) return JsonResponse({'status': 'success'})