mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-03 21:43:21 +00:00 
			
		
		
		
	Instead of charging the customer using the attached payment method and then creating the invoice, we create an invoice and force an immediate payment for the invoice via the attached payment method.
		
			
				
	
	
		
			87 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			87 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import json
 | 
						|
import logging
 | 
						|
 | 
						|
import stripe
 | 
						|
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
 | 
						|
 | 
						|
from corporate.lib.stripe import STRIPE_API_VERSION
 | 
						|
from corporate.lib.stripe_event_handler import (
 | 
						|
    handle_checkout_session_completed_event,
 | 
						|
    handle_invoice_paid_event,
 | 
						|
)
 | 
						|
from corporate.models import Event, Invoice, Session
 | 
						|
from zproject.config import get_secret
 | 
						|
 | 
						|
billing_logger = logging.getLogger("corporate.stripe")
 | 
						|
 | 
						|
 | 
						|
@csrf_exempt
 | 
						|
def stripe_webhook(request: HttpRequest) -> HttpResponse:
 | 
						|
    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:
 | 
						|
            stripe_event = stripe.Webhook.construct_event(
 | 
						|
                request.body,
 | 
						|
                request.headers["Stripe-Signature"],
 | 
						|
                stripe_webhook_endpoint_secret,
 | 
						|
            )
 | 
						|
        except ValueError:
 | 
						|
            return HttpResponse(status=400)
 | 
						|
        except stripe.SignatureVerificationError:
 | 
						|
            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)
 | 
						|
 | 
						|
    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)
 | 
						|
 | 
						|
    if stripe_event.type not in [
 | 
						|
        "checkout.session.completed",
 | 
						|
        "invoice.paid",
 | 
						|
    ]:
 | 
						|
        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)
 | 
						|
    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)
 | 
						|
    # 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.
 | 
						|
    return HttpResponse(status=200)
 |