def handle_payment_intent_payment_failed_event( stripe_payment_intent: stripe.PaymentIntent, payment_intent: Event) -> None: payment_intent.status = PaymentIntent.get_status_integer_from_status_text( stripe_payment_intent.status) billing_logger.info( "Stripe payment intent failed: %s %s %s %s", payment_intent.customer.realm.string_id, stripe_payment_intent.last_payment_error.get("type"), stripe_payment_intent.last_payment_error.get("code"), stripe_payment_intent.last_payment_error.get("param"), ) payment_intent.last_payment_error = { "description": stripe_payment_intent.last_payment_error.get("type"), } payment_intent.last_payment_error[ "message"] = stripe_payment_intent.last_payment_error.get("message") payment_intent.save(update_fields=["status", "last_payment_error"])
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.META.get("HTTP_STRIPE_SIGNATURE"), stripe_webhook_endpoint_secret, ) except ValueError: return HttpResponse(status=400) except stripe.error.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", "payment_intent.succeeded", "payment_intent.payment_failed", ]: 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 == "payment_intent.succeeded": stripe_payment_intent = stripe_event.data.object assert isinstance(stripe_payment_intent, stripe.PaymentIntent) try: payment_intent = PaymentIntent.objects.get( stripe_payment_intent_id=stripe_payment_intent.id) except PaymentIntent.DoesNotExist: # PaymentIntent that was not manually created from the billing system. # Could be an Invoice getting paid which is is not an event we are interested in. return HttpResponse(status=200) event.content_type = ContentType.objects.get_for_model(PaymentIntent) event.object_id = payment_intent.id event.save() handle_payment_intent_succeeded_event(stripe_payment_intent, event) elif stripe_event.type == "payment_intent.payment_failed": stripe_payment_intent = stripe_event.data.object try: assert isinstance(stripe_payment_intent, stripe.PaymentIntent) payment_intent = PaymentIntent.objects.get( stripe_payment_intent_id=stripe_payment_intent.id) except PaymentIntent.DoesNotExist: # PaymentIntent that was not manually created from the billing system. # Could be an Invoice getting paid which is is not an event we are interested in. return HttpResponse(status=200) event.content_type = ContentType.objects.get_for_model(PaymentIntent) event.object_id = payment_intent.id event.save() handle_payment_intent_payment_failed_event(stripe_payment_intent, event) return HttpResponse(status=200)
def wrapper(stripe_object: Union[stripe.checkout.Session, stripe.PaymentIntent], event: Event) -> None: event.status = Event.EVENT_HANDLER_STARTED event.save(update_fields=["status"]) try: func(stripe_object, event.content_object) except BillingError as e: billing_logger.warning( "BillingError in %s event handler: %s. stripe_object_id=%s, customer_id=%s metadata=%s", event.type, e.error_description, stripe_object.id, stripe_object.customer, stripe_object.metadata, ) event.status = Event.EVENT_HANDLER_FAILED event.handler_error = { "message": e.msg, "description": e.error_description, } event.save(update_fields=["status", "handler_error"]) except Exception: billing_logger.exception( "Uncaught exception in %s event handler:", event.type, stack_info=True, ) event.status = Event.EVENT_HANDLER_FAILED event.handler_error = { "description": f"uncaught exception in {event.type} event handler", "message": BillingError.CONTACT_SUPPORT.format( email=settings.ZULIP_ADMINISTRATOR), } event.save(update_fields=["status", "handler_error"]) else: event.status = Event.EVENT_HANDLER_SUCCEEDED event.save()