2021-08-29 15:33:29 +02:00
import json
import logging
from django . conf import settings
from django . contrib . contenttypes . models import ContentType
from django . http import HttpRequest , HttpResponse
from django . views . decorators . csrf import csrf_exempt
2024-02-10 07:47:32 +01:00
from corporate . models import Event , Invoice , Session
2021-08-29 15:33:29 +02:00
from zproject . config import get_secret
billing_logger = logging . getLogger ( " corporate.stripe " )
@csrf_exempt
def stripe_webhook ( request : HttpRequest ) - > HttpResponse :
2024-09-24 23:27:28 +02:00
import stripe
from corporate . lib . stripe import STRIPE_API_VERSION
from corporate . lib . stripe_event_handler import (
handle_checkout_session_completed_event ,
handle_invoice_paid_event ,
)
2021-08-29 15:33:29 +02:00
stripe_webhook_endpoint_secret = get_secret ( " stripe_webhook_endpoint_secret " , " " )
if (
stripe_webhook_endpoint_secret and not settings . TEST_SUITE
) : # nocoverage: We can't verify the signature in test suite since we fetch the events
# from Stripe events API and manually post to the webhook endpoint.
try :
2024-01-29 00:32:21 +01:00
stripe_event = stripe . Webhook . construct_event (
2021-08-29 15:33:29 +02:00
request . body ,
2022-07-06 21:49:29 +02:00
request . headers [ " Stripe-Signature " ] ,
2021-08-29 15:33:29 +02:00
stripe_webhook_endpoint_secret ,
)
except ValueError :
return HttpResponse ( status = 400 )
2024-01-29 00:32:21 +01:00
except stripe . SignatureVerificationError :
2021-08-29 15:33:29 +02:00
return HttpResponse ( status = 400 )
else :
assert not settings . PRODUCTION
try :
stripe_event = stripe . Event . construct_from ( json . loads ( request . body ) , stripe . api_key )
except Exception :
return HttpResponse ( status = 400 )
2021-09-07 17:53:27 +02:00
if stripe_event . api_version != STRIPE_API_VERSION :
error_message = f " Mismatch between billing system Stripe API version( { STRIPE_API_VERSION } ) and Stripe webhook event API version( { stripe_event . api_version } ). "
billing_logger . error ( error_message )
return HttpResponse ( status = 400 )
2021-08-29 15:33:29 +02:00
if stripe_event . type not in [
" checkout.session.completed " ,
2024-02-02 13:04:41 +01:00
" invoice.paid " ,
2021-08-29 15:33:29 +02:00
] :
return HttpResponse ( status = 200 )
if Event . objects . filter ( stripe_event_id = stripe_event . id ) . exists ( ) :
return HttpResponse ( status = 200 )
event = Event ( stripe_event_id = stripe_event . id , type = stripe_event . type )
if stripe_event . type == " checkout.session.completed " :
stripe_session = stripe_event . data . object
assert isinstance ( stripe_session , stripe . checkout . Session )
try :
session = Session . objects . get ( stripe_session_id = stripe_session . id )
except Session . DoesNotExist :
return HttpResponse ( status = 200 )
event . content_type = ContentType . objects . get_for_model ( Session )
event . object_id = session . id
event . save ( )
handle_checkout_session_completed_event ( stripe_session , event )
2024-02-02 13:04:41 +01:00
elif stripe_event . type == " invoice.paid " :
stripe_invoice = stripe_event . data . object
assert isinstance ( stripe_invoice , stripe . Invoice )
try :
invoice = Invoice . objects . get ( stripe_invoice_id = stripe_invoice . id )
except Invoice . DoesNotExist :
return HttpResponse ( status = 200 )
event . content_type = ContentType . objects . get_for_model ( Invoice )
event . object_id = invoice . id
event . save ( )
handle_invoice_paid_event ( stripe_invoice , event )
2023-11-18 11:29:04 +01:00
# We don't need to process failed payments via webhooks since we directly charge users
# when they click on "Purchase" button and immediately provide feedback for failed payments.
# If the feedback is not immediate, our event_status handler checks for payment status and informs the user.
2021-08-29 15:33:29 +02:00
return HttpResponse ( status = 200 )