diff --git a/daras_ai_v2/bot_integration_widgets.py b/daras_ai_v2/bot_integration_widgets.py index 15d89b4d6..36cca09ec 100644 --- a/daras_ai_v2/bot_integration_widgets.py +++ b/daras_ai_v2/bot_integration_widgets.py @@ -426,6 +426,7 @@ def web_widget_config(bi: BotIntegration, user: AppUser | None): enablePhotoUpload=False, autoPlayResponses=True, enableAudioMessage=True, + enableConversations=True, branding=( dict(showPoweredByGooey=True) | bi.web_config_extras.get("branding", {}) @@ -442,6 +443,9 @@ def web_widget_config(bi: BotIntegration, user: AppUser | None): config["enablePhotoUpload"] = gui.checkbox( "Allow Photo Upload", value=config["enablePhotoUpload"] ) + config["enableConversations"] = gui.checkbox( + 'Show "New Chat"', value=config["enableConversations"] + ) with scol2: config["enableAudioMessage"] = gui.checkbox( "Enable Audio Message", value=config["enableAudioMessage"] diff --git a/daras_ai_v2/send_email.py b/daras_ai_v2/send_email.py index a9ff1934d..84741c2dc 100644 --- a/daras_ai_v2/send_email.py +++ b/daras_ai_v2/send_email.py @@ -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: diff --git a/daras_ai_v2/settings.py b/daras_ai_v2/settings.py index e1aca9769..f1a75de42 100644 --- a/daras_ai_v2/settings.py +++ b/daras_ai_v2/settings.py @@ -11,7 +11,6 @@ """ import os -import json from pathlib import Path import sentry_sdk @@ -265,6 +264,7 @@ ADMIN_EMAILS = config("ADMIN_EMAILS", cast=Csv(), default="") SUPPORT_EMAIL = "Gooey.AI Support " SALES_EMAIL = "Gooey.AI Sales " +PAYMENT_EMAIL = "Gooey.AI Payments " SEND_RUN_EMAIL_AFTER_SEC = config("SEND_RUN_EMAIL_AFTER_SEC", 5) DISALLOWED_TITLE_SLUGS = config("DISALLOWED_TITLE_SLUGS", cast=Csv(), default="") + [ diff --git a/payments/tasks.py b/payments/tasks.py index 252064541..4a602c3ba 100644 --- a/payments/tasks.py +++ b/payments/tasks.py @@ -39,6 +39,34 @@ 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, + subject: str, +): + 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=subject, + html_body=templates.get_template("auto_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 diff --git a/payments/webhooks.py b/payments/webhooks.py index 0b822cfe7..bff97390f 100644 --- a/payments/webhooks.py +++ b/payments/webhooks.py @@ -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: @@ -189,6 +192,28 @@ def handle_subscription_cancelled(cls, uid: str): external_id=None, ) + @classmethod + def handle_invoice_failed(cls, uid: str, data: dict): + 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"): + send_payment_failed_email_with_invoice.delay( + uid=uid, + invoice_url=data["hosted_invoice_url"], + dollar_amt=data["amount_due"] / 100, + subject="Payment failure on your Gooey.AI auto-recharge", + ) + elif data.get("subscription_details", {}): + send_payment_failed_email_with_invoice.delay( + uid=uid, + invoice_url=data["hosted_invoice_url"], + dollar_amt=data["amount_due"] / 100, + subject="Payment failure on your Gooey.AI subscription", + ) + def add_balance_for_payment( *, diff --git a/routers/root.py b/routers/root.py index b3a636207..e234a5443 100644 --- a/routers/root.py +++ b/routers/root.py @@ -761,7 +761,7 @@ class RecipeTabs(TabData, Enum): route=history_route, ) integrations = TabData( - title=f'Facebook, Whatsapp, Slack, Instagram Icons Integrations', + title=f'Facebook, Whatsapp, Slack, Instagram Icons Integrations', label="Integrations", route=integrations_route, ) diff --git a/routers/stripe.py b/routers/stripe.py index aa948ba98..7b1534ecc 100644 --- a/routers/stripe.py +++ b/routers/stripe.py @@ -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": diff --git a/templates/auto_payment_failed_email.html b/templates/auto_payment_failed_email.html new file mode 100644 index 000000000..6eaaba7d2 --- /dev/null +++ b/templates/auto_payment_failed_email.html @@ -0,0 +1,38 @@ + + + + + + + + + + + +
+ +

Hi {{ user.first_name() }},

+ +

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

+ +

+ Please make a payment on Gooey.AI for continued service or update + your payment method on your account. +

+ +

+ + + +

+ +

+ Cheers,
+ The Gooey.AI team +

+ +
+ + + +