Skip to content

Commit

Permalink
feat: send notification when payment of auto-recharge/susbcription in…
Browse files Browse the repository at this point in the history
…voice fails
  • Loading branch information
nikochiko committed Aug 28, 2024
1 parent 8e8bff3 commit bd6e730
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 3 deletions.
2 changes: 1 addition & 1 deletion daras_ai_v2/send_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def send_email_via_postmark(
html_body: str = "",
text_body: str = "",
message_stream: typing.Literal[
"outbound", "gooey-ai-workflows", "announcements"
"outbound", "gooey-ai-workflows", "announcements", "billing"
] = "outbound",
):
if is_running_pytest:
Expand Down
2 changes: 1 addition & 1 deletion daras_ai_v2/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"""

import os
import json
from pathlib import Path

import sentry_sdk
Expand Down Expand Up @@ -265,6 +264,7 @@
ADMIN_EMAILS = config("ADMIN_EMAILS", cast=Csv(), default="")
SUPPORT_EMAIL = "Gooey.AI Support <[email protected]>"
SALES_EMAIL = "Gooey.AI Sales <[email protected]>"
PAYMENT_EMAIL = "Gooey.AI Payments <[email protected]>"
SEND_RUN_EMAIL_AFTER_SEC = config("SEND_RUN_EMAIL_AFTER_SEC", 5)

DISALLOWED_TITLE_SLUGS = config("DISALLOWED_TITLE_SLUGS", cast=Csv(), default="") + [
Expand Down
33 changes: 33 additions & 0 deletions payments/tasks.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from typing import Literal

import stripe
from django.utils import timezone
from loguru import logger

Expand Down Expand Up @@ -39,6 +42,36 @@ def send_monthly_spending_notification_email(user_id: int):
user.subscription.save(update_fields=["monthly_spending_notification_sent_at"])


@app.task
def send_payment_failed_email_with_invoice(
uid: str,
invoice_url: str,
dollar_amt: float,
kind: Literal["subscription", "auto recharge"],
):
from routers.account import account_route

user = AppUser.objects.get(uid=uid)
if not user.email:
logger.error(f"User doesn't have an email: {user=}")
return

send_email_via_postmark(
from_address=settings.PAYMENT_EMAIL,
to_address=user.email,
subject=f"Payment failure on your Gooey.AI {kind}",
html_body=templates.get_template(
"off_session_payment_failed_email.html"
).render(
user=user,
dollar_amt=f"{dollar_amt:.2f}",
invoice_url=invoice_url,
account_url=get_app_route_url(account_route),
),
message_stream="billing",
)


def send_monthly_budget_reached_email(user: AppUser):
from routers.account import account_route

Expand Down
35 changes: 34 additions & 1 deletion payments/webhooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
from daras_ai_v2 import paypal
from .models import Subscription
from .plans import PricingPlan
from .tasks import send_monthly_spending_notification_email
from .tasks import (
send_monthly_spending_notification_email,
send_payment_failed_email_with_invoice,
)


class PaypalWebhookHandler:
Expand Down Expand Up @@ -189,6 +192,36 @@ def handle_subscription_cancelled(cls, uid: str):
external_id=None,
)

@classmethod
def handle_invoice_failed(cls, uid: str, data: dict):
logger.info(f"Invoice failed: {data}")

if stripe.Charge.list(payment_intent=data["payment_intent"], limit=1).has_more:
# we must have already sent an invoice for this to the user. so we should just ignore this event
logger.info("Charge already exists for this payment intent")
return

if data.get("metadata", {}).get("auto_recharge"):
logger.info("auto recharge failed... sending invoice email")
send_payment_failed_email_with_invoice.delay(
uid=uid,
invoice_url=data["hosted_invoice_url"],
dollar_amt=data["amount_due"] / 100,
kind="auto recharge",
)
elif data.get("subscription_details", {}):
print("subscription failed")
send_payment_failed_email_with_invoice.delay(
uid=uid,
invoice_url=data["hosted_invoice_url"],
dollar_amt=data["amount_due"] / 100,
kind="subscription",
)
else:
print("not auto recharge or subscription")
print(f"{data.get('metadata')=}")
return


def add_balance_for_payment(
*,
Expand Down
2 changes: 2 additions & 0 deletions routers/stripe.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ def webhook_received(request: Request, payload: bytes = fastapi_request_body):
match event["type"]:
case "invoice.paid":
StripeWebhookHandler.handle_invoice_paid(uid, data)
case "invoice.payment_failed":
StripeWebhookHandler.handle_invoice_failed(uid, data)
case "checkout.session.completed":
StripeWebhookHandler.handle_checkout_session_completed(uid, data)
case "customer.subscription.created" | "customer.subscription.updated":
Expand Down
22 changes: 22 additions & 0 deletions templates/base_email.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>{% block title %}{% endblock title %}</title>

{% block head %}{% endblock head %}

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
</head>

<body class="d-flex flex-column min-vh-100">
<div class="flex-fill">
{% block content %}{% endblock content %}
</div>

</body>

</html>
25 changes: 25 additions & 0 deletions templates/off_session_payment_failed_email.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{% extends 'base_email.html' %}

{% block title %}Payment failed{% endblock title %}

{% block content %}
<p>Hi {{ user.first_name() }},</p>

<p>We attempted to process your payment for ${{ dollar_amt }} but your payment method was declined.</p>

<p>
Please <a href="{{ invoice_url }}">make a payment</a> on Gooey.AI for continued service or update
your payment method on <a href="{{ account_url }}">your account</a>.
</p>

<p>
<a href="{{ invoice_url }}">
<button class="btn btn-dark text-light">Make Payment</button>
</a>
</p>

<p>
Cheers,<br />
The Gooey.AI team
</p>
{% endblock content %}

0 comments on commit bd6e730

Please sign in to comment.