Skip to content

Commit d187b71

Browse files
committed
add stripe webhook supabase edge fn, add plan upgrade modal, stripe checkout/portal handlers
1 parent 74debd0 commit d187b71

File tree

25 files changed

+1247
-584
lines changed

25 files changed

+1247
-584
lines changed

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"deno.enable": true,
3+
"deno.enablePaths": ["./supabase/functions"],
4+
"deno.importMap": "./supabase/functions/import_map.json"
5+
}

deno.lock

Lines changed: 101 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package-lock.json

Lines changed: 1 addition & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
"react-simple-code-editor": "^0.14.1",
7979
"recharts": "^3.0.2",
8080
"sonner": "^2.0.1",
81+
"stripe": "^18.3.0",
8182
"ts-morph": "^25.0.1",
8283
"tsd": "^0.32.0",
8384
"vivus": "^0.4.6",
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { COUPON_CODES, STRIPE_PRICE_IDS } from '@/constants/pricing';
2+
import { AgentsmithServices } from '@/lib/AgentsmithServices';
3+
import { createClient } from '@/lib/supabase/server';
4+
import { routes } from '@/utils/routes';
5+
import { NextRequest, NextResponse } from 'next/server';
6+
import Stripe from 'stripe';
7+
8+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
9+
10+
const handler = async (req: NextRequest) => {
11+
const { searchParams } = new URL(req.url);
12+
const organizationUuid = searchParams.get('organizationUuid');
13+
const stripePriceId = searchParams.get('stripePriceId');
14+
const seats = searchParams.get('seats');
15+
16+
const supabase = await createClient();
17+
const { services, logger } = new AgentsmithServices({ supabase });
18+
19+
if (!organizationUuid || !stripePriceId) {
20+
logger.error('Missing organizationUuid or stripePriceId, cannot create stripe checkout');
21+
22+
return NextResponse.redirect(
23+
new URL(
24+
routes.error('Missing organizationUuid or stripePriceId, cannot create stripe checkout'),
25+
req.url,
26+
),
27+
);
28+
}
29+
30+
const { authUser, agentsmithUser } = await services.users.initialize();
31+
32+
if (!authUser || !agentsmithUser) {
33+
logger.error('User not found, cannot create stripe checkout.');
34+
return NextResponse.redirect(
35+
new URL(routes.error('User not found, cannot create stripe checkout.'), req.url),
36+
);
37+
}
38+
39+
const organization = await services.organizations.getOrganizationData(organizationUuid);
40+
41+
if (!organization) {
42+
return NextResponse.redirect(
43+
new URL(routes.error('Organization not found, cannot create stripe checkout.'), req.url),
44+
);
45+
}
46+
47+
// validate the price id
48+
try {
49+
await stripe.prices.retrieve(stripePriceId);
50+
} catch (error) {
51+
logger.error(error, 'Error retrieving price, cannot create stripe checkout.');
52+
return NextResponse.redirect(
53+
new URL(routes.error('Invalid price id, cannot create stripe checkout.'), req.url),
54+
);
55+
}
56+
57+
const successUrl = new URL(routes.studio.organizationBillingSuccess(organizationUuid), req.url);
58+
59+
// should get this from url params via a pricing modal or something so it's more clear
60+
// quantity == number of seats
61+
const maxQuantity =
62+
stripePriceId === STRIPE_PRICE_IDS.HOBBY.MONTHLY ||
63+
stripePriceId === STRIPE_PRICE_IDS.HOBBY.YEARLY
64+
? 3
65+
: 99;
66+
67+
const metadata = {
68+
organizationUuid,
69+
agentsmithUserId: agentsmithUser.id,
70+
authUserId: authUser.id,
71+
};
72+
73+
const quantity = seats !== null && !isNaN(Number(seats)) ? Number(seats) : 1;
74+
75+
try {
76+
const session = await stripe.checkout.sessions.create({
77+
success_url: successUrl.toString(),
78+
line_items: [
79+
{
80+
price: stripePriceId,
81+
quantity,
82+
adjustable_quantity: {
83+
enabled: true,
84+
minimum: 1,
85+
maximum: maxQuantity,
86+
},
87+
},
88+
],
89+
mode: 'subscription',
90+
discounts: [{ coupon: COUPON_CODES.AGENTSMITH50 }],
91+
customer_email: organization.stripe_customer_id ? undefined : authUser.email,
92+
customer: organization.stripe_customer_id ?? undefined,
93+
metadata,
94+
subscription_data: {
95+
metadata,
96+
},
97+
});
98+
99+
if (!session.url) {
100+
logger.error(
101+
session,
102+
'Stripe checkout session url not found, cannot create stripe checkout.',
103+
);
104+
return NextResponse.redirect(
105+
new URL(
106+
routes.error('Stripe checkout session url not found, cannot create stripe checkout.'),
107+
req.url,
108+
),
109+
);
110+
}
111+
112+
return NextResponse.redirect(session.url);
113+
} catch (error) {
114+
logger.error(error, 'Error creating stripe checkout session, cannot create stripe checkout.');
115+
return NextResponse.redirect(
116+
new URL(
117+
routes.error('Error creating stripe checkout session, cannot create stripe checkout.'),
118+
req.url,
119+
),
120+
);
121+
}
122+
};
123+
124+
export { handler as GET };

0 commit comments

Comments
 (0)