Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pricing improvements and bug fixes #379

Merged
merged 31 commits into from
Jul 14, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
fefbbf4
Don't make use of transaction.atomic in handle_subscription_updated
nikochiko Jun 6, 2024
e35d78f
paypal.Subscription.cancel: ignore when sub is already cancelled / ex…
nikochiko Jun 6, 2024
46e34b1
Fix paypal next invoice time when it is None
nikochiko Jun 6, 2024
965b040
Payment processing page: run paypal subscription update in thread
nikochiko Jun 7, 2024
c42d12d
Merge branch 'pricing-v2' of github.com:GooeyAI/gooey-server into pri…
nikochiko Jun 7, 2024
375e11e
Merge branch 'master' into pricing-v2
nikochiko Jun 7, 2024
8fea44b
Merge branch 'master' into pricing-v2-improvements
nikochiko Jun 9, 2024
43c250f
sentry_sdk: upgrade to 1.45, add loguru extra to capture logged info
nikochiko Jun 9, 2024
61e5da4
Refactor auto-recharge functionality with exceptions
nikochiko Jun 10, 2024
558505c
Refactor payments webhook handling code into payments/webhooks.py
nikochiko Jun 10, 2024
cdcc8ac
Add BasePage.run_with_auto_recharge
nikochiko Jun 10, 2024
c42ffae
recipe runner task: s/page.run/page.run_with_auto_recharge
nikochiko Jun 10, 2024
d231447
api: don't auto-recharge before the run (we do that afterwards)
nikochiko Jun 10, 2024
5f48995
Remove subscription-change logic from routers/account.py
nikochiko Jun 10, 2024
7f227db
billing_page: fix same-key bug in buttons, refactor for clarity
nikochiko Jun 10, 2024
b12dc0c
Fix Subscription model: allow null-values for auto-recharge config in DB
nikochiko Jun 10, 2024
83cb1cf
Add missing migration for blank=True on AppUser.subscription
nikochiko Jun 10, 2024
45a3019
stripe auto invoice: use default setting for AUTO_RECHARGE_COOLDOWN w…
nikochiko Jun 10, 2024
156a363
Remove unnecessary cosmetic changes
nikochiko Jun 10, 2024
bcbb02f
Fix bug: proration behavior
nikochiko Jun 11, 2024
c971f7a
Merge branch 'master' into pricing-v2-improvements
nikochiko Jun 14, 2024
fa6d9e5
Merge remote-tracking branch 'origin/master' into pricing-v2-improvem…
devxpy Jul 11, 2024
b5bc34b
rename gui_runner -> runner_task
devxpy Jul 11, 2024
4dbd145
fix tests
devxpy Jul 12, 2024
9b012e1
record extra data about transactions
devxpy Jul 14, 2024
9ede7d8
fix: record the correct txn id for paypal
devxpy Jul 14, 2024
1edae2a
re-enable one time payment buttons
devxpy Jul 14, 2024
2de5fc5
fix: dont charge the user when downgrading the subscription
devxpy Jul 14, 2024
6bb0ac2
refactor
devxpy Jul 14, 2024
b9df617
allow saved payment options in stripe addon checkout
devxpy Jul 14, 2024
e6b9955
remove network calls inside transaction.atomic()
devxpy Jul 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions app_users/migrations/0017_alter_appuser_subscription.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Generated by Django 4.2.7 on 2024-06-10 09:21

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('payments', '0004_alter_subscription_auto_recharge_balance_threshold'),
('app_users', '0016_appuser_disable_rate_limits'),
]

operations = [
migrations.AlterField(
model_name='appuser',
name='subscription',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='user', to='payments.subscription'),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this migration is not from a model change in another commit.

the addition is blank=True

),
]
17 changes: 5 additions & 12 deletions celeryapp/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,14 @@
from celeryapp.celeryconfig import app
from daras_ai.image_input import truncate_text_words
from daras_ai_v2 import settings
from daras_ai_v2.auto_recharge import auto_recharge_user
from daras_ai_v2.base import StateKeys, BasePage
from daras_ai_v2.exceptions import UserError
from daras_ai_v2.redis_cache import redis_lock
from daras_ai_v2.send_email import send_email_via_postmark, send_low_balance_email
from daras_ai_v2.settings import templates
from gooey_ui.pubsub import realtime_push
from gooey_ui.state import set_query_params
from gooeysite.bg_db_conn import db_middleware, next_db_safe
from payments.tasks import run_auto_recharge_gracefully


@app.task
Expand Down Expand Up @@ -97,13 +96,13 @@ def save(done=False):
page.dump_state_to_sr(st.session_state | output, sr)

try:
gen = page.run(st.session_state)
gen = page.run_with_auto_recharge(st.session_state)
save()
while True:
# record time
start_time = time()
try:
# advance the generator (to further progress of run())
# advance the generator (to further progress of run_with_auto_recharge())
yield_val = next_db_safe(gen)
# increment total time taken after every iteration
run_time += time() - start_time
Expand Down Expand Up @@ -143,7 +142,9 @@ def save(done=False):
save(done=True)
if not is_api_call:
send_email_on_completion(page, sr)

run_low_balance_email_check(uid)
run_auto_recharge_gracefully(uid)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is run in the same thread, at the end of a task. not in a background task



def err_msg_for_exc(e: Exception):
Expand Down Expand Up @@ -253,11 +254,3 @@ def send_integration_attempt_email(*, user_id: int, platform: Platform, run_url:
subject=f"{user.display_name} Attempted to Connect to {platform.label}",
html_body=html_body,
)


@app.task
def auto_recharge(*, user_id: int):
redis_lock_key = f"gooey/auto_recharge/{user_id}"
with redis_lock(redis_lock_key):
user = AppUser.objects.get(id=user_id)
auto_recharge_user(user)
64 changes: 0 additions & 64 deletions daras_ai_v2/auto_recharge.py

This file was deleted.

44 changes: 37 additions & 7 deletions daras_ai_v2/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from time import sleep
from types import SimpleNamespace

from loguru import logger
import sentry_sdk
from django.db.models import Sum
from django.utils import timezone
Expand All @@ -24,6 +25,7 @@
)
from starlette.requests import Request

from daras_ai_v2.exceptions import UserError
import gooey_ui as st
from app_users.models import AppUser, AppUserTransaction
from bots.models import (
Expand All @@ -37,7 +39,6 @@
from daras_ai.text_format import format_number_with_suffix
from daras_ai_v2 import settings, urls
from daras_ai_v2.api_examples_widget import api_example_generator
from daras_ai_v2.auto_recharge import user_should_auto_recharge
from daras_ai_v2.breadcrumbs import render_breadcrumbs, get_title_breadcrumbs
from daras_ai_v2.copy_to_clipboard_button_widget import (
copy_to_clipboard_button,
Expand Down Expand Up @@ -71,6 +72,11 @@
from gooey_ui.components.pills import pill
from gooey_ui.pubsub import realtime_pull
from routers.account import AccountTabs
from payments.auto_recharge import (
AutoRechargeException,
auto_recharge_user,
should_attempt_auto_recharge,
)
from routers.root import RecipeTabs

DEFAULT_META_IMG = (
Expand Down Expand Up @@ -1355,6 +1361,29 @@ def _render_help(self):
def render_usage_guide(self):
raise NotImplementedError

def run_with_auto_recharge(self, state: dict) -> typing.Iterator[str | None]:
if not self.check_credits() and should_attempt_auto_recharge(self.request.user):
yield "Low balance detected. Recharging..."
try:
auto_recharge_user(uid=self.request.user.uid)
except AutoRechargeException as e:
# raise this error only if another auto-recharge
# procedure didn't complete successfully
self.request.user.refresh_from_db()
if not self.check_credits():
raise UserError(str(e)) from e
else:
self.request.user.refresh_from_db()

if not self.check_credits():
example_id, run_id, uid = extract_query_params(gooey_get_query_params())
error_msg = self.generate_credit_error_message(
example_id=example_id, run_id=run_id, uid=uid
)
raise UserError(error_msg)

yield from self.run(state)

def run(self, state: dict) -> typing.Iterator[str | None]:
# initialize request and response
request = self.RequestModel.parse_obj(state)
Expand Down Expand Up @@ -1505,19 +1534,17 @@ def estimate_run_duration(self) -> int | None:
pass

def on_submit(self):
from celeryapp.tasks import auto_recharge

try:
example_id, run_id, uid = self.create_new_run(enable_rate_limits=True)
except RateLimitExceeded as e:
st.session_state[StateKeys.run_status] = None
st.session_state[StateKeys.error_msg] = e.detail.get("error", "")
return

if user_should_auto_recharge(self.request.user):
auto_recharge.delay(user_id=self.request.user.id)

if settings.CREDITS_TO_DEDUCT_PER_RUN and not self.check_credits():
if not self.check_credits() and not should_attempt_auto_recharge(
self.request.user
):
# insufficient balance for this run and auto-recharge isn't setup
st.session_state[StateKeys.run_status] = None
st.session_state[StateKeys.error_msg] = self.generate_credit_error_message(
example_id, run_id, uid
Expand Down Expand Up @@ -1978,6 +2005,9 @@ def run_as_api_tab(self):
manage_api_keys(self.request.user)

def check_credits(self) -> bool:
if not settings.CREDITS_TO_DEDUCT_PER_RUN:
return True

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check_credits() was always being used with this check.

i felt more convenient to include that check here so we could call check_credits() only whenever we wanted.

assert self.request, "request must be set to check credits"
assert self.request.user, "request.user must be set to check credits"
return self.request.user.balance >= self.get_price_roundoff(st.session_state)
Expand Down
Loading