-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Refactor subscription backend logic #2426
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
Conversation
Someone is attempting to deploy a commit to the Onlook Team on Vercel. A member of the Team first needs to authorize it. |
// Here you can decide the logic for the carry-over. | ||
// Example, you may want to carry over 100% of the credits on the first carry-over, | ||
// and 50% of the credits on the next carry-overs. | ||
// const max = rate.carryOverTotal === 0 ? rate.left : rate.left * 0.50; | ||
const max = rate.left; | ||
|
||
// For now, we only carry over the credits on the first carry-over. | ||
// In the future, we may want to carry over the credits on the next carry-overs. | ||
if (rate.carryOverTotal === 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is where you can adjust the carry-over rules. Right now it is set up as:
- It only carries over once.
- 100% of the remaining credits carry over.
apps/web/client/src/server/api/routers/subscription/subscription.ts
Outdated
Show resolved
Hide resolved
timestamp: new Date(), | ||
// running a transaction helps with concurrency issues and ensures that | ||
// the usage is incremented atomically | ||
return db.transaction(async (tx) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens when increment is called on a free plan with no rateLimits? Seems like in this case there should be an upsert or some logic here to handle that? Seeing a 500 error when running with an existing user with no subscription
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@onlook/web-client dev: [TRPC] usage.increment took 327ms to execute
│ @onlook/web-client dev: ⨯ Error [TRPCError]: Rollback
│ @onlook/web-client dev: at <unknown> (src/server/api/routers/usage/index.ts:122:19)
│ @onlook/web-client dev: at async (src/server/api/trpc.ts:105:19)
│ @onlook/web-client dev: at async streamResponse (src/app/api/chat/route.ts:102:29)
│ @onlook/web-client dev: 120 | // if there are no credits left then rollback
│ @onlook/web-client dev: 121 | if (!limit?.left) {
│ @onlook/web-client dev: > 122 | tx.rollback();
│ @onlook/web-client dev: | ^
│ @onlook/web-client dev: 123 | return;
│ @onlook/web-client dev: 124 | }
│ @onlook/web-client dev: 125 | {
│ @onlook/web-client dev: code: 'INTERNAL_SERVER_ERROR',
│ @onlook/web-client dev: [cause]: Error [DrizzleError]: Rollback
│ @onlook/web-client dev: at <unknown> (src/server/api/routers/usage/index.ts:122:19)
│ @onlook/web-client dev: at async (src/server/api/trpc.ts:105:19)
│ @onlook/web-client dev: at async streamResponse (src/app/api/chat/route.ts:102:29)
│ @onlook/web-client dev: 120 | // if there are no credits left then rollback
│ @onlook/web-client dev: 121 | if (!limit?.left) {
│ @onlook/web-client dev: > 122 | tx.rollback();
│ @onlook/web-client dev: | ^
│ @onlook/web-client dev: 123 | return;
│ @onlook/web-client dev: 124 | }
│ @onlook/web-client dev: 125 | {
│ @onlook/web-client dev: cause: undefined
│ @onlook/web-client dev: }
│ @onlook/web-client dev: }
│ @onlook/web-client dev: ⨯ Error [TRPCError]: Rollback
│ @onlook/web-client dev: at <unknown> (src/server/api/routers/usage/index.ts:122:19)
│ @onlook/web-client dev: at async (src/server/api/trpc.ts:105:19)
│ @onlook/web-client dev: at async streamResponse (src/app/api/chat/route.ts:102:29)
│ @onlook/web-client dev: 120 | // if there are no credits left then rollback
│ @onlook/web-client dev: 121 | if (!limit?.left) {
│ @onlook/web-client dev: > 122 | tx.rollback();
│ @onlook/web-client dev: | ^
│ @onlook/web-client dev: 123 | return;
│ @onlook/web-client dev: 124 | }
│ @onlook/web-client dev: 125 | {
│ @onlook/web-client dev: code: 'INTERNAL_SERVER_ERROR',
│ @onlook/web-client dev: [cause]: Error [DrizzleError]: Rollback
│ @onlook/web-client dev: at <unknown> (src/server/api/routers/usage/index.ts:122:19)
│ @onlook/web-client dev: at async (src/server/api/trpc.ts:105:19)
│ @onlook/web-client dev: at async streamResponse (src/app/api/chat/route.ts:102:29)
│ @onlook/web-client dev: 120 | // if there are no credits left then rollback
│ @onlook/web-client dev: 121 | if (!limit?.left) {
│ @onlook/web-client dev: > 122 | tx.rollback();
│ @onlook/web-client dev: | ^
│ @onlook/web-client dev: 123 | return;
│ @onlook/web-client dev: 124 | }
│ @onlook/web-client dev: 125 | {
│ @onlook/web-client dev: cause: undefined
│ @onlook/web-client dev: }
│ @onlook/web-client dev: }
* Fixes both auth login buttons from showing loading animation when either one is actually loading
* Fix decimal input support, add VH units. * fix: preserve arrow key behavior
where: and(eq(subscriptions.userId, user.id), eq(subscriptions.status, SubscriptionStatus.ACTIVE)), | ||
}); | ||
if (!subscription) { | ||
return { rateLimitId: undefined, usageRecordId: undefined }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think in this case we need to check the records for the free user to make sure they don't go over their free tier limit. Would it make sense to create a rate-limit row? Or just use our custom logic and count the usage record?
This would also not count towards free usage limit because it's a no-op. Ideally we still want to make sure this works. You can simulate free user by editing the subscription table row and marking the subscription as cancelled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah, great catch
I didn't want to have rows for free users since they have daily rates, if you have a million users then each month would add 30 million rows in the table. On the other hand, your comment does highlight an issue with separating systems and for free users, it involves counting rows on a large table (though, now indexed) which is not optimal.
I was trying to keep it simple so the credits stored in the database were tied to the subscription's lifecycle. The alternative would be to inject two rate limits (one daily and one monthly) when a free user sends a message and then when a free user converts to paid, have the daily/monthly rate limits endedAt
in the past.
IMO, it's the sort of thing you want to stabilize so it doesn't have to be revisited until months/years from now. By that time, you may revisit how you monetize things and start from scratch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I see the problem with the usage calculation now. Especially when scaled up. Perhaps having a daily usage count system could make sense.
One last wrinkle here is that the daily usage counting is now only monthly limit counting when user doesn't have a subscription. Seems like this would not reset daily so free users will only have 5 free messages per month. Please let me know if this is accurate.
daily: { |
|
||
const insertRateLimit = async (tx: PgTransaction<any, any, any>, item: StripeItem) => { | ||
console.log(`Inserting rate limit for subscription ${item.subscription.id}`); | ||
// One month from the start date. NOT SURE IF THIS IS CORRECT. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@http-teapot should we use a better end date here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If stripeCurrentPeriodEnd
is available then I'd use that so you don't have to calculate 31/30 days based on month
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
Description
Related Issues
Type of Change
Testing
Screenshots (if applicable)
Additional Notes
Important
Refactor subscription backend to use Stripe webhooks and implement rate limiting based on subscription status, updating database schema and frontend accordingly.
route.ts
andstripe.ts
.route.ts
andusage/index.ts
.route.ts
.rateLimits
table inrate-limits.ts
to track usage limits per subscription.subscriptions
table insubscription.ts
to include period start and end timestamps.usageRecords
table inusage.ts
to include indexing for efficient querying.createCustomer
function infunctions.ts
to handle new customer creation.createCheckoutSession
to includestripeCustomerId
infunctions.ts
.functions.ts
.This description was created by
for bf4cdb4. You can customize this summary. It will automatically update as commits are pushed.