Skip to content

Commit ffaf659

Browse files
committed
refactor: ♻️ streamline Stripe webhook handling and improve billing form loading state management
1 parent 2e5af4e commit ffaf659

File tree

4 files changed

+63
-39
lines changed

4 files changed

+63
-39
lines changed

.cursor/mcp.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"mcpServers": {
3+
"postgres": {
4+
"command": "npx",
5+
"args": [
6+
"-y",
7+
"@modelcontextprotocol/server-postgres",
8+
"postgres://postgres:postgres@localhost:5432/localdb"
9+
]
10+
}
11+
}
12+
}

src/app/api/stripe/route.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ export async function GET(req: NextRequest) {
3737
cancel_url: billingUrl,
3838
payment_method_types: ["card"],
3939
mode: "subscription",
40-
billing_address_collection: "auto",
4140
customer_email: user.email!,
4241
line_items: [
4342
{

src/app/api/webhooks/stripe/route.ts

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ export async function POST(req: NextRequest) {
1111
const signature = headers.get("Stripe-Signature") as string;
1212

1313
let event: Stripe.Event | undefined = undefined;
14-
1514
try {
1615
event = stripe.webhooks.constructEvent(
1716
body,
@@ -28,9 +27,10 @@ export async function POST(req: NextRequest) {
2827
| Stripe.Checkout.Session
2928
| Stripe.Invoice;
3029

31-
console.log({ event, eventData });
32-
33-
if (event?.type === "checkout.session.completed") {
30+
if (
31+
event?.type === "checkout.session.completed" ||
32+
event?.type === "checkout.session.async_payment_succeeded"
33+
) {
3434
const session = eventData as Stripe.Checkout.Session;
3535
// Retrieve the subscription details from Stripe.
3636
const subscription = await stripe.subscriptions.retrieve(
@@ -40,36 +40,49 @@ export async function POST(req: NextRequest) {
4040
// Update the user stripe into in our database.
4141
// Since this is the initial subscription, we need to update
4242
// the subscription id and customer id.
43-
await prisma.user.update({
44-
where: {
45-
id: session?.metadata?.userId,
46-
},
47-
data: {
48-
stripeSubscriptionId: subscription.id,
49-
stripeCustomerId: subscription.customer as string,
50-
stripePriceId: subscription.items.data[0].price.id,
51-
stripeCurrentPeriodEnd: new Date(subscription.ended_at! * 1000),
52-
},
53-
});
54-
}
55-
56-
if (event?.type === "invoice.payment_succeeded") {
43+
try {
44+
await prisma.user.update({
45+
where: {
46+
id: session?.metadata?.userId,
47+
},
48+
data: {
49+
stripeSubscriptionId: subscription.id,
50+
stripeCustomerId: subscription.customer as string,
51+
stripePriceId: subscription.items.data[0].price.id,
52+
stripeCurrentPeriodEnd: new Date(
53+
subscription.items.data[0].current_period_end! * 1000
54+
),
55+
},
56+
});
57+
} catch (error) {
58+
console.error(error);
59+
}
60+
} else if (event?.type === "invoice.payment_succeeded") {
5761
const invoice = eventData as Stripe.Invoice;
62+
63+
const subscriptionId = invoice.parent?.subscription_details?.subscription;
64+
5865
// Retrieve the subscription details from Stripe.
5966
const subscription = await stripe.subscriptions.retrieve(
60-
invoice.parent?.subscription_details?.subscription as string
67+
subscriptionId as string
6168
);
6269

6370
// Update the price id and set the new period end.
64-
await prisma.user.update({
65-
where: {
66-
stripeSubscriptionId: subscription.id,
67-
},
68-
data: {
69-
stripePriceId: subscription.items.data[0].price.id,
70-
stripeCurrentPeriodEnd: new Date(subscription.ended_at! * 1000),
71-
},
72-
});
71+
try {
72+
await prisma.user.update({
73+
where: {
74+
stripeSubscriptionId: subscription.id,
75+
},
76+
data: {
77+
stripePriceId: subscription.items.data[0].price.id,
78+
stripeCurrentPeriodEnd: new Date(
79+
subscription.items.data[0].current_period_end! * 1000
80+
),
81+
},
82+
});
83+
} catch (error) {
84+
console.error(error);
85+
}
7386
}
7487

7588
return new Response(null, { status: 200 });

src/components/billing-form.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ export function BillingForm({
2525
className,
2626
...props
2727
}: BillingFormProps) {
28-
const [isLoading, setIsLoading] = useState<boolean>(false);
29-
console.log(subscriptionPlan);
28+
const [isLoading, setIsLoading] = useState(false);
3029

3130
async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
3231
event.preventDefault();
33-
setIsLoading(!isLoading);
32+
33+
setIsLoading(true);
3434

3535
// Get a Stripe session URL.
3636
const response = await fetch("/api/stripe");
@@ -46,10 +46,12 @@ export function BillingForm({
4646
// Redirect to the Stripe session.
4747
// This could be a checkout page for initial upgrade.
4848
// Or portal to manage existing subscription.
49-
const session = await response.json();
50-
if (session) {
51-
window.location.href = session.url;
49+
const data = await response.json();
50+
if (data.url) {
51+
window.location.href = data.url;
5252
}
53+
54+
setIsLoading(false);
5355
}
5456

5557
return (
@@ -67,10 +69,8 @@ export function BillingForm({
6769
{subscriptionPlan.description}
6870
</CardContent>
6971
<CardFooter className="flex flex-col items-start space-y-2 md:flex-row md:justify-between md:space-x-0">
70-
<Button type="submit" disabled={isLoading}>
71-
{isLoading && (
72-
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
73-
)}
72+
<Button type="submit" role="link" disabled={isLoading}>
73+
{isLoading && <Icons.spinner className="h-4 w-4 animate-spin" />}
7474
{subscriptionPlan.isPro ? "Manage Subscription" : "Upgrade to PRO"}
7575
</Button>
7676
{subscriptionPlan.isPro ? (

0 commit comments

Comments
 (0)