๐ Stripe Integration with NestJS + TypeORM: Let's Make It Happen! ๐ณ๐ป
Sachith Wickramasekara
Posted on October 6, 2024
Hey there, dev community! ๐ Are you building a cool app and want to handle payments like a pro? Look no further! Today, weโre diving into Stripe integration with NestJS (using TypeORM ๐๏ธ), and Iโll walk you through the steps to get everything up and running smoothly ๐. Let's dive right in! ๐
Step 1: First Things First โ Stripe Keys ๐
Before we do anything, youโll need to grab your Stripe Public and Secret Keys from the Stripe Dashboard.
๐๏ธ These keys are important because:
Public Key: This is used on the frontend to securely send card details to Stripe.
Secret Key: This stays in the backend and allows us to perform actions like creating customers, payment intents, etc. (๐ฐ we don't want this to leak!)
How to connect them in NestJS? Well, letโs use .env to store these keys securely!
# In .env
STRIPE_PUBLIC_KEY=pk_test_123
STRIPE_API_KEY=sk_test_456
Next, you can inject this into your NestJS project using @nestjs/config. Weโll use this in our StripeModule. Keep reading! ๐
Step 2: Setting Up Stripe in NestJS โ๏ธ
Alright, letโs install what we need in our NestJS project!
npm install @nestjs/config stripe @types/stripe
Now, create a Stripe module. Hereโs the code to get that rolling:
@Module({
imports: [TypeOrmModule.forFeature([UserDetails])],
controllers: [StripeController],
providers: [
StripeService,
{
provide: 'STRIPE_API_KEY',
useFactory: async (configService: ConfigService) =>
configService.get('STRIPE_API_KEY'),
inject: [ConfigService],
},
],
exports: [StripeService],
})
export class StripeModule {}
We import the UserDetails entity because weโll be linking payments to users later! ๐
Step 3: The Flow of Payments ๐ณ
Stripe payments follow a simple but powerful flow. Here's a bird's eye view of what we'll be doing:
- Be a Stripe customer ๐งโ๐ผ
- Create a payment intent ๐ก
- Attach payment methods ๐ณ
- Confirm the payment intent ๐
Letโs break these steps down further! ๐๐
Step 4: Creating the Stripe Service ๐ ๏ธ
First, weโll initialize Stripe in our service by creating a stripe instance:
import Stripe from 'stripe';
@Injectable()
export class StripeService {
private readonly stripe: Stripe;
constructor(
@Inject('STRIPE_API_KEY') private readonly apiKey: string,
) {
this.stripe = new Stripe(this.apiKey, {
apiVersion: '2024-06-20',
});
}
}
Thatโs it! ๐ Now our service is ready to handle all Stripe-related operations!
Step 5: Be a Stripe Customer ๐ฅ
Before you can make any payments, you need to create a customer in Stripe! Here's a neat way to do it:
const stripeCustomer = await this.stripe.customers.create({
email: 'john.doe@example.com',
name: 'John Doe',
phone: '555-555-5555',
address: {
line1: '123 NestJS St.',
city: 'Codingville',
country: 'US',
},
metadata: {
userId: '12345', // Store anything you like here!
},
});
You can save the stripeCustomer.id to your database (in the UserDetails table or elsewhere). Donโt worry, you can always edit the details later! โ๏ธ
Step 6: Creating Payment Intents ๐ฏ
Now that you're a Stripe customer, letโs talk about creating a payment intent. The payment intent will tell Stripe how much to charge, which currency to use, and which payment methods are acceptable (weโll use 'card' in this case ๐ณ).
async createPaymentIntent(
amount: number,
currency: string,
): Promise<Stripe.PaymentIntent> {
try {
const paymentIntent = await this.stripe.paymentIntents.create({
amount,
currency,
payment_method_types: ['card'],
});
this.logger.log('Payment Intent created successfully');
return paymentIntent;
} catch (error) {
this.logger.error('Failed to create Payment Intent', error.stack);
throw new Error('Unable to create Payment Intent');
}
}
@Post('create-payment-intent')
async createPaymentIntent(
@Body('amount') amount: number,
@Body('currency') currency: string,
): Promise<{ paymentIntentId: string }> {
try {
const paymentIntent = await this.stripeService.createPaymentIntent(
amount,
currency,
);
this.logger.log('Payment Intent created successfully');
return { paymentIntentId: paymentIntent.id };
} catch (error) {
this.logger.error('Failed to create Payment Intent', error.stack);
throw new HttpException(
'Failed to create Payment Intent',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
Don't forget! After creating the intent, Stripe gives you a paymentIntent.id โ store this in local storage or your database for future use! ๐ฆ
Step 7: Adding Payment Methods ๐ณ
Once you have a payment intent, you'll need a payment method. This links the customerโs payment details (like a card) to their account.
async addingPaymentMethod(
userId: string,
paymentMethodId: string,
): Promise<Stripe.PaymentMethod> {
try {
const userDetails = await this.userDetailsRepository.findOne({
where: { userID: userId },
});
const paymentMethod = await this.stripe.paymentMethods.attach(
paymentMethodId,
{
customer: userDetails.stripeCustomerId,
},
);
return paymentMethod;
} catch (error) {
this.logger.error('Failed to add payment method', error.stack);
throw error;
}
}
@Post('add-payment-method')
async addPaymentMethod(
@AuthenticatedUser() user: any,
@Body('paymentMethodId') paymentMethodId: string,
): Promise<Stripe.PaymentMethod> {
try {
const paymentMethod = await this.stripeService.addPaymentMethod(
user.sub,
paymentMethodId,
);
this.logger.log('Payment method added successfully');
return paymentMethod;
} catch (error) {
this.logger.error('Failed to add payment method', error.stack);
throw new HttpException(
'Failed to add payment method',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
๐ Note: Youโll need the Stripe customer ID (from Step 5) to attach a payment method!
Step 8: Retrieving Payment Methods ๐
Want to see all payment methods a customer has? You can easily fetch them using the following code:
async retrievePaymentMethods(
userId: string,
): Promise<Stripe.PaymentMethod[]> {
try {
const userDetails = await this.userDetailsRepository.findOne({
where: { userID: userId },
});
const paymentMethods = await this.stripe.paymentMethods.list({
customer: userDetails.stripeCustomerId,
type: 'card',
});
return paymentMethods.data;
} catch (error) {
this.logger.error('Failed to fetch payment methods', error.stack);
throw error;
}
}
@Get('get-payment-methods')
async retrievePaymentMethods(
@AuthenticatedUser() user: any,
): Promise<Stripe.PaymentMethod[]> {
try {
const paymentMethods = await this.stripeService.retrievePaymentMethods(
user.sub,
);
this.logger.log('Payment methods fetched successfully');
return paymentMethods;
} catch (error) {
this.logger.error('Failed to fetch payment methods', error.stack);
throw new HttpException(
'Failed to fetch payment methods',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
Stripe will return a payment method ID for every card added. ๐ ๏ธ Use it to confirm payments!
Step 9: Confirming the Payment Intent โ
The final piece is to confirm the payment intent. To do this, you need both the paymentIntentId and the paymentMethodId.
async paymentConfirmation(
paymentIntentId: string,
paymentMethodId: string,
): Promise<Stripe.PaymentIntent> {
try {
const confirmedPaymentIntent = await this.stripe.paymentIntents.confirm(
paymentIntentId,
{
payment_method: paymentMethodId,
},
);
return confirmedPaymentIntent;
} catch (error) {
this.logger.error('Failed to confirm Payment Intent', error.stack);
throw new Error('Unable to confirm Payment Intent');
}
}
@Post('confirm-payment-intent')
async confirmPaymentIntent(
@Body('paymentIntentId') paymentIntentId: string,
@Body('paymentMethodId') paymentMethodId: string,
@AuthenticatedUser() user: any,
): Promise<Stripe.PaymentIntent> {
try {
const paymentIntent = await this.stripeService.confirmPaymentIntent(
paymentIntentId,
paymentMethodId,
user.sub,
);
this.logger.log('Payment Intent confirmed successfully');
return paymentIntent;
} catch (error) {
this.logger.error('Failed to confirm Payment Intent', error.stack);
throw new HttpException(
'Failed to confirm Payment Intent',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
Once the payment is confirmed, you can remove the paymentIntentId from local storage and redirect the user to a success screen! ๐
๐ฌ Wrapping It Up
And thatโs a wrap! ๐ Integrating Stripe with NestJS isnโt rocket science, but with this guide, youโll have a smooth ride from start to finish. ๐ You'll be able to handle payments like a boss, and your users will love the seamless experience!
Have fun coding, and happy transactions! ๐ธ
Posted on October 6, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.