๐Ÿš€ Stripe Integration with NestJS + TypeORM: Let's Make It Happen! ๐Ÿ’ณ๐Ÿ’ป

Sachith Wickramasekara - Oct 6 - - Dev Community

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 {}

Enter fullscreen mode Exit fullscreen mode

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:

  1. Be a Stripe customer ๐Ÿง‘โ€๐Ÿ’ผ
  2. Create a payment intent ๐Ÿ’ก
  3. Attach payment methods ๐Ÿ’ณ
  4. 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',
    });
  }
}

Enter fullscreen mode Exit fullscreen mode

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!
  },
});

Enter fullscreen mode Exit fullscreen mode

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');
  }
}

Enter fullscreen mode Exit fullscreen mode
@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,
      );
    }
  }
Enter fullscreen mode Exit fullscreen mode

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;
    }
  }

Enter fullscreen mode Exit fullscreen mode
 @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,
      );
    }
  }
Enter fullscreen mode Exit fullscreen mode

๐Ÿ‘‰ 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;
    }
  }

Enter fullscreen mode Exit fullscreen mode
  @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,
      );
    }
  }
Enter fullscreen mode Exit fullscreen mode

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');
    }
  }

Enter fullscreen mode Exit fullscreen mode
  @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,
      );
    }
  }
Enter fullscreen mode Exit fullscreen mode

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! ๐Ÿ’ธ

.
Terabox Video Player