Skip to content

Commit 64bd149

Browse files
refactor: create new payment service and remove stripe service
1 parent c8298f1 commit 64bd149

File tree

7 files changed

+140
-123
lines changed

7 files changed

+140
-123
lines changed

src/hooks/useCheckout.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { HAS_FREE_TRIAL } from "@/constants/HAS_FREE_TRIAL";
44
import { useToast } from "@/hooks/useToast";
55
import { supabase } from '@/libs/supabase/client';
66
import AuthService from '@/services/auth';
7-
import StripeService from '@/services/stripe';
7+
import PaymentService from '@/services/payment';
88

99
export const useCheckout = () => {
1010
const { addToast } = useToast();
@@ -35,7 +35,7 @@ export const useCheckout = () => {
3535
const sessionId = jsonResponse.id;
3636

3737
if (sessionId) {
38-
await StripeService.redirectToCheckout(sessionId);
38+
await PaymentService.redirectToCheckout(sessionId);
3939
} else {
4040
addToast({
4141
id: Date.now().toString(),

src/pages/api/payments/create-billing-portal.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { NextApiRequest, NextApiResponse } from 'next';
33
import { stripe } from '@/libs/stripe';
44
import { supabaseServerClient } from '@/libs/supabase/server';
55
import AuthService from '@/services/auth';
6-
import StripeService from '@/services/stripe';
6+
import PaymentService from '@/services/payment';
77
import SubscriptionService from '@/services/subscription';
88

99
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
@@ -22,7 +22,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
2222
const AuthServiceInstance = new AuthService(supabaseServerClient);
2323
const SubscriptionServiceInstance = new SubscriptionService(supabaseServerClient);
2424

25-
const StripeServiceInstance = new StripeService(stripe);
25+
const PaymentServiceInstance = new PaymentService(stripe);
2626

2727

2828
const user = await AuthServiceInstance.getUser(token);
@@ -37,13 +37,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
3737
if (!subscription) {
3838
return res.status(404).json({ error: 'No subscription found for user' });
3939
}
40-
const customerId = await StripeServiceInstance.getCustomerIdFromSubscription(subscription.stripe_subscription_id);
40+
const customerId = await PaymentServiceInstance.getCustomerIdFromSubscription(subscription.stripe_subscription_id);
4141

4242
if (!customerId) {
4343
return res.status(404).json({ error: 'No customerId found for subscription' });
4444
}
4545

46-
const portalSession = await StripeServiceInstance.createBillingPortalSession(customerId);
46+
const portalSession = await PaymentServiceInstance.createBillingPortalSession(customerId);
4747

4848
return res.status(200).json({ url: portalSession.url });
4949
} catch (error) {

src/pages/api/payments/create-checkout.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { NextApiRequest, NextApiResponse } from 'next';
22

33
import { stripe } from '@/libs/stripe';
4-
import StripeService from '@/services/stripe';
4+
import PaymentService from '@/services/payment';
55

66
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
77
if (req.method !== 'POST') {
@@ -17,8 +17,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
1717
const freeTrial = hasFreeTrial ? Number(hasFreeTrial.split('d')?.[0]) : 0;
1818

1919
try {
20-
const stripeServiceInstance = new StripeService(stripe);
21-
const session = await stripeServiceInstance.createCheckoutSession(
20+
const PaymentServiceInstance = new PaymentService(stripe);
21+
const session = await PaymentServiceInstance.createCheckoutSession(
2222
priceId,
2323
plan,
2424
userId,

src/pages/api/payments/get-plans.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { NextApiRequest, NextApiResponse } from 'next';
22
import Stripe from 'stripe';
33

44
import { stripe } from '@/libs/stripe';
5-
import StripeService from '@/services/stripe';
5+
import PaymentService from '@/services/payment';
66
import { loadTranslationsSSR } from '@/utils/loadTranslationsSSR';
77
import { InputData, transformPurchasePlansDTO } from '@/utils/transformPurchasePlansDTO';
88

@@ -20,8 +20,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
2020

2121

2222
try {
23-
const StripeServiceInstance = new StripeService(stripe);
24-
const prices = await StripeServiceInstance.listActivePrices();
23+
const PaymentServiceInstance = new PaymentService(stripe);
24+
const prices = await PaymentServiceInstance.listActivePrices();
2525

2626
const response = prices?.map((price) => {
2727
const product = price.product as Stripe.Product;

src/pages/api/webhooks.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { stripe } from '@/libs/stripe';
55
import { supabaseServerClient } from '@/libs/supabase/server';
66
import AuthService from '@/services/auth';
77
import EmailService from '@/services/email';
8-
import StripeService from '@/services/stripe';
8+
import PaymentService from '@/services/payment';
99
import SubscriptionService from '@/services/subscription';
1010

1111
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
@@ -27,7 +27,7 @@ async function getRawBody(req: NextApiRequest): Promise<string> {
2727

2828
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
2929
if (req.method === 'POST') {
30-
const StripeServiceInstance = new StripeService(stripe);
30+
const PaymentServiceInstance = new PaymentService(stripe);
3131
const AuthServiceInstance = new AuthService(supabaseServerClient);
3232
const SubscriptionServiceInstance = new SubscriptionService(supabaseServerClient);
3333
const EmailServiceInstance = new EmailService();
@@ -44,7 +44,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
4444
let event;
4545

4646
try {
47-
event = StripeServiceInstance.constructWebhookEvent(rawBody, sig, endpointSecret);
47+
event = PaymentServiceInstance.constructWebhookEvent(rawBody, sig, endpointSecret);
4848
} catch (err) {
4949
const errorMessage = err instanceof Error ? err.message : 'Erro desconhecido';
5050
console.error('Erro ao verificar a assinatura do webhook:', errorMessage);

src/services/payment.ts

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { loadStripe } from '@stripe/stripe-js';
2+
import Stripe from 'stripe';
3+
4+
import { calculateCurrencyAmount } from '@/utils/calculateCurrencyAmount';
5+
6+
export default class PaymentService {
7+
private stripe: Stripe;
8+
9+
constructor(stripe: Stripe) {
10+
this.stripe = stripe;
11+
}
12+
13+
async createCheckoutSession(
14+
priceId: string,
15+
plan: string,
16+
userId: string,
17+
origin: string,
18+
freeTrial?: number,
19+
currency?: string
20+
): Promise<Stripe.Checkout.Session> {
21+
const price = await this.getPrice(priceId);
22+
const recurringInterval = price.recurring?.interval;
23+
24+
if (!recurringInterval) {
25+
throw new Error('Recurring interval not found for this price');
26+
}
27+
28+
const sessionData = this.buildCheckoutSessionData(
29+
priceId,
30+
plan,
31+
userId,
32+
origin,
33+
recurringInterval,
34+
freeTrial,
35+
currency,
36+
price.unit_amount
37+
);
38+
39+
return await this.stripe.checkout.sessions.create(sessionData);
40+
}
41+
42+
async listActivePrices(): Promise<Stripe.Price[]> {
43+
const prices = await this.stripe.prices.list({ active: true, expand: ['data.product'] });
44+
return prices.data;
45+
}
46+
47+
constructWebhookEvent(rawBody: string | Buffer, sig: string | string[], secret: string): Stripe.Event {
48+
return this.stripe.webhooks.constructEvent(rawBody, sig, secret);
49+
}
50+
51+
async getCustomerIdFromSubscription(subscriptionId: string): Promise<string | null> {
52+
try {
53+
const subscription = await this.stripe.subscriptions.retrieve(subscriptionId);
54+
return subscription.customer as string;
55+
} catch (error) {
56+
console.error('Erro ao buscar assinatura:', error);
57+
return null;
58+
}
59+
}
60+
61+
async createBillingPortalSession(customerId: string): Promise<Stripe.BillingPortal.Session> {
62+
return await this.stripe.billingPortal.sessions.create({
63+
customer: customerId,
64+
return_url: `${process.env.NEXT_PUBLIC_PROJECT_URL}/dashboard/subscription`,
65+
});
66+
}
67+
68+
static redirectToCheckout(sessionId: string): void {
69+
loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!).then((clientStripe) => {
70+
clientStripe?.redirectToCheckout({ sessionId });
71+
});
72+
}
73+
74+
private async getPrice(priceId: string): Promise<Stripe.Price> {
75+
return this.stripe.prices.retrieve(priceId);
76+
}
77+
78+
private buildCheckoutSessionData(
79+
priceId: string,
80+
plan: string,
81+
userId: string,
82+
origin: string,
83+
recurringInterval: Stripe.Price.Recurring.Interval,
84+
freeTrial?: number,
85+
currency?: string,
86+
unitAmount?: number | null
87+
): Stripe.Checkout.SessionCreateParams {
88+
const sessionData: Stripe.Checkout.SessionCreateParams = {
89+
payment_method_types: ['card'],
90+
line_items: [this.buildLineItem(priceId, plan, recurringInterval, currency, unitAmount)],
91+
mode: 'subscription',
92+
success_url: `${origin}/payments?status=success`,
93+
cancel_url: `${origin}/payments?status=cancel`,
94+
metadata: { userId, plan },
95+
};
96+
97+
if (freeTrial) {
98+
sessionData.subscription_data = { trial_period_days: freeTrial };
99+
}
100+
101+
return sessionData;
102+
}
103+
104+
private buildLineItem(
105+
priceId: string,
106+
plan: string,
107+
recurringInterval: Stripe.Price.Recurring.Interval,
108+
currency?: string,
109+
unitAmount?: number | null
110+
): Stripe.Checkout.SessionCreateParams.LineItem {
111+
if (!currency || !unitAmount) {
112+
return { price: priceId, quantity: 1 };
113+
}
114+
115+
return {
116+
price_data: {
117+
currency,
118+
product_data: { name: plan },
119+
recurring: { interval: recurringInterval },
120+
unit_amount: calculateCurrencyAmount(String(unitAmount), currency),
121+
},
122+
quantity: 1,
123+
};
124+
}
125+
}

src/services/stripe.ts

Lines changed: 0 additions & 108 deletions
This file was deleted.

0 commit comments

Comments
 (0)