mirror of
https://github.com/zulip/zulip.git
synced 2025-10-24 00:23:49 +00:00
182 lines
6.6 KiB
Python
182 lines
6.6 KiB
Python
import logging
|
|
from typing import Any, Callable, Dict, Union
|
|
|
|
import stripe
|
|
from django.conf import settings
|
|
|
|
from corporate.lib.stripe import (
|
|
BillingError,
|
|
UpgradeWithExistingPlanError,
|
|
ensure_realm_does_not_have_active_plan,
|
|
process_initial_upgrade,
|
|
update_or_create_stripe_customer,
|
|
)
|
|
from corporate.models import Event, PaymentIntent, Session
|
|
from zerver.models import get_active_user_profile_by_id_in_realm
|
|
|
|
billing_logger = logging.getLogger("corporate.stripe")
|
|
|
|
|
|
def error_handler(
|
|
func: Callable[[Any, Any], None],
|
|
) -> Callable[[Union[stripe.checkout.Session, stripe.PaymentIntent], Event], None]:
|
|
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()
|
|
|
|
return wrapper
|
|
|
|
|
|
@error_handler
|
|
def handle_checkout_session_completed_event(
|
|
stripe_session: stripe.checkout.Session, session: Session
|
|
) -> None:
|
|
session.status = Session.COMPLETED
|
|
session.save()
|
|
|
|
stripe_setup_intent = stripe.SetupIntent.retrieve(stripe_session.setup_intent)
|
|
assert session.customer.realm is not None
|
|
user_id = stripe_session.metadata.get("user_id")
|
|
assert user_id is not None
|
|
user = get_active_user_profile_by_id_in_realm(user_id, session.customer.realm)
|
|
payment_method = stripe_setup_intent.payment_method
|
|
|
|
if session.type in [
|
|
Session.UPGRADE_FROM_BILLING_PAGE,
|
|
Session.RETRY_UPGRADE_WITH_ANOTHER_PAYMENT_METHOD,
|
|
]:
|
|
ensure_realm_does_not_have_active_plan(user.realm)
|
|
update_or_create_stripe_customer(user, payment_method)
|
|
session.payment_intent.status = PaymentIntent.PROCESSING
|
|
session.payment_intent.last_payment_error = ()
|
|
session.payment_intent.save(update_fields=["status", "last_payment_error"])
|
|
try:
|
|
stripe.PaymentIntent.confirm(
|
|
session.payment_intent.stripe_payment_intent_id,
|
|
payment_method=payment_method,
|
|
off_session=True,
|
|
)
|
|
except stripe.error.CardError:
|
|
pass
|
|
elif session.type in [
|
|
Session.FREE_TRIAL_UPGRADE_FROM_BILLING_PAGE,
|
|
Session.FREE_TRIAL_UPGRADE_FROM_ONBOARDING_PAGE,
|
|
]:
|
|
ensure_realm_does_not_have_active_plan(user.realm)
|
|
update_or_create_stripe_customer(user, payment_method)
|
|
process_initial_upgrade(
|
|
user,
|
|
int(stripe_setup_intent.metadata["licenses"]),
|
|
stripe_setup_intent.metadata["license_management"] == "automatic",
|
|
int(stripe_setup_intent.metadata["billing_schedule"]),
|
|
charge_automatically=True,
|
|
free_trial=True,
|
|
)
|
|
elif session.type in [Session.CARD_UPDATE_FROM_BILLING_PAGE]:
|
|
update_or_create_stripe_customer(user, payment_method)
|
|
|
|
|
|
@error_handler
|
|
def handle_payment_intent_succeeded_event(
|
|
stripe_payment_intent: stripe.PaymentIntent, payment_intent: PaymentIntent
|
|
) -> None:
|
|
payment_intent.status = PaymentIntent.SUCCEEDED
|
|
payment_intent.save()
|
|
metadata: Dict[str, Any] = stripe_payment_intent.metadata
|
|
assert payment_intent.customer.realm is not None
|
|
user_id = metadata.get("user_id")
|
|
assert user_id is not None
|
|
user = get_active_user_profile_by_id_in_realm(user_id, payment_intent.customer.realm)
|
|
|
|
description = ""
|
|
for charge in stripe_payment_intent.charges:
|
|
description = f"Payment (Card ending in {charge.payment_method_details.card.last4})"
|
|
break
|
|
|
|
stripe.InvoiceItem.create(
|
|
amount=stripe_payment_intent.amount * -1,
|
|
currency="usd",
|
|
customer=stripe_payment_intent.customer,
|
|
description=description,
|
|
discountable=False,
|
|
)
|
|
try:
|
|
ensure_realm_does_not_have_active_plan(user.realm)
|
|
except UpgradeWithExistingPlanError as e:
|
|
stripe_invoice = stripe.Invoice.create(
|
|
auto_advance=True,
|
|
collection_method="charge_automatically",
|
|
customer=stripe_payment_intent.customer,
|
|
days_until_due=None,
|
|
statement_descriptor="Zulip Cloud Standard Credit",
|
|
)
|
|
stripe.Invoice.finalize_invoice(stripe_invoice)
|
|
raise e
|
|
|
|
process_initial_upgrade(
|
|
user,
|
|
int(metadata["licenses"]),
|
|
metadata["license_management"] == "automatic",
|
|
int(metadata["billing_schedule"]),
|
|
True,
|
|
False,
|
|
)
|
|
|
|
|
|
@error_handler
|
|
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"])
|