DEV Community

Jayvee Ramos
Jayvee Ramos

Posted on

12. Payment Processing with Stripe API

Introduction

In today's tutorial, we will guide you through creating a microservice for processing payments in your reservation system using the Stripe API. This microservice will handle the task of billing customers when they book reservations, and we'll take you through each step to set it up.

Let's get started!


Step 1: Creating a New NestJS App

To kick things off, we'll use the Nest CLI to generate a new app. Open your terminal and run the following command:

nest generate app payments

Enter fullscreen mode Exit fullscreen mode

This command creates a new NestJS application named "payments" for handling payments in your reservation system. It includes a default controller and a basic payments module.


Step 2: Docker Configuration

Next, we'll set up Docker for this microservice. I will not explain this code because we already tackled it in our previous tutorials

touch apps/payments/Dockerfile
Enter fullscreen mode Exit fullscreen mode

it will create a Dockerfile inside the payments folder now populate it with this content:

# Use Node Alpine as the base image for development
FROM node:alpine as development

WORKDIR /usr/src/app

COPY package.json package-lock.json ./
RUN npm install -g && npm install

COPY . .

RUN npm run build

# Create a production image
FROM node:alpine as production

ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}

WORKDIR /usr/src/app

COPY package.json package-lock.json ./
RUN npm install --only=production

COPY --from=development /usr/src/app/dist ./dist
COPY --from=development /usr/src/app/package.json ./package.json

CMD [ "node", "dist/apps/payments/main" ]

Enter fullscreen mode Exit fullscreen mode

What we did was just Copy the Docker file from your reservations app and place it in the payments app folder. In the Docker file, we changed the startup command to point to the dist/apps/payments folder.


Update docker-compose.yaml - Now, update your Docker Compose file to include the payments service. Make sure to specify a unique port (e.g., 3003) and expose it for the payments microservice.

Open your docker-compose file and update it with the following content to include payment service:

services:
  reservations:
    build:
      context: .
      dockerfile: apps/reservations/Dockerfile
      target: development
    command: npm run start:dev reservations
    env_file:
      - ./apps/reservations/.env
    ports:
      - '3000:3000'
    volumes:
      - .:/usr/src/app
  auth:
    build:
      context: .
      dockerfile: apps/auth/Dockerfile
      target: development
    command: npm run start:dev auth
    env_file:
      - apps/auth/.env
    ports:
      - '3001:3001'
    volumes:
      - .:/usr/src/app
  payments:
    build:
      context: .
      dockerfile: apps/payments/Dockerfile
      target: development
    command: npm run start:dev payments
    env_file:
      - apps/payments/.env
    ports:
      - '3003:3003'
    volumes:
      - .:/usr/src/app
  mongo:
    image: mongo

Enter fullscreen mode Exit fullscreen mode

Step 3: Configuring the Microservice

In the payments folder, open the main.ts file and turn this app into a standalone microservice. Update it with the following content:

import { NestFactory } from '@nestjs/core';
import { PaymentsModule } from './payments.module';
import { Logger } from 'nestjs-pino';
import { Transport } from '@nestjs/microservices';
import { ConfigService } from '@nestjs/config';

async function bootstrap() {
  const app = await NestFactory.create(PaymentsModule);
  const configService = app.get(ConfigService);
  app.connectMicroservice({
    transport: Transport.TCP,
    options: {
      host: '0.0.0.0',
      port: configService.get('PORT'),
    },
  });

  app.useLogger(app.get(Logger));

  await app.startAllMicroservices();
}
bootstrap();


Enter fullscreen mode Exit fullscreen mode

We just configure the transport as "transport.TCP" and set the port using the config service. But since we are using a PORT environment variable which does not yet exist in the payments service we should now create a .env file and specify the PORT variables

touch apps/payments/.env
Enter fullscreen mode Exit fullscreen mode

populate it with the following code:

PORT=3003
Enter fullscreen mode Exit fullscreen mode

Then on your config module in the payments module (payments.module.ts) set it up as we did for the auth service. This allows you to access and validate the PORT environment variable as follows:

import { Module } from '@nestjs/common';
import { PaymentsController } from './payments.controller';
import { PaymentsService } from './payments.service';
import * as Joi from 'joi';
import { ConfigModule } from '@nestjs/config';
import { LoggerModule } from '@app/common';

@Module({
  imports: [
    LoggerModule,
    ConfigModule.forRoot({
      isGlobal: true,
      validationSchema: Joi.object({
        PORT: Joi.number().required(),
        STRIPE_SECRET_KEY: Joi.string().required(), //we will set up this on step 4
      }),
    }),
  ],
  controllers: [PaymentsController],
  providers: [PaymentsService],
})
export class PaymentsModule {}


Enter fullscreen mode Exit fullscreen mode

Step 4: Setting up the Stripe API

Before you can start processing payments, you need to set up a Stripe account. Head to Stripe's website, sign up for an account, and create an account within your dashboard.

In your Stripe dashboard, you will find your publishable key and secret key. We will use the secret key for API calls, so make sure to set it as an environment variable in your .env file.

update your payments .env file and include your secret_key which you got from your stripe dashboard:

PORT=3003
STRIPE_SECRET_KEY=sk_test_51NZp96JyPkGHZlunZYLEUwARyNBuxHbJ3tVFat6XtJxSxLwhJgIxMdeC4wtt42WkrWCM3H20yRVeMkc9SW38Uprt00xPMCJIDR
Enter fullscreen mode Exit fullscreen mode

Make sure to use your own secret key, the secret key I provided is deactivated

Next, install the Node.js Stripe API client library with the following command:

npm install stripe

Enter fullscreen mode Exit fullscreen mode

Step 5: Initializing the Stripe Client

let's create a DTO (Data Transfer Object) first

mkdir apps/payments/src/dto
touch apps/payments/src/dto/create-charge.dto.ts
Enter fullscreen mode Exit fullscreen mode

it will create a new directory named dto inside your payments folder then inside dto it will create a file named create-charge.dto.ts

populate the create-charge.dto.ts with this content:

import Stripe from 'stripe';

export class CreateChargeDto {
  card: Stripe.PaymentMethodCreateParams.Card1;
  amount: number;
}

Enter fullscreen mode Exit fullscreen mode

We can now use this CreatechargeDto to our controller and service

Open your payments.service.ts and populate it with the following content:

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import Stripe from 'stripe';
import { CreateChargeDto } from './dto/create-charge.dto';

@Injectable()
export class PaymentsService {
  private readonly stripe = new Stripe(
    this.configService.get('STRIPE_SECRET_KEY'),
    {
      apiVersion: '2023-10-16',
    },
  );
  constructor(private readonly configService: ConfigService) {}

  async createCharge({ amount }: CreateChargeDto) {
    const paymentIntent = await this.stripe.paymentIntents.create({
      amount: amount * 100,
      automatic_payment_methods: {
        enabled: true,
        allow_redirects: 'never',
      },
      confirm: true,
      payment_method: 'pm_card_visa',
      currency: 'usd',
    });

    return paymentIntent;
  }
}

Enter fullscreen mode Exit fullscreen mode

In the payments service, we created a private read-only variable to initialize the Stripe client using your secret key from the config service. and we set up the API version as well.

We also create a payment charge. using the Payment Intents API provided by Stripe for secure payment processing. We created a new payment method and then charged the user.

  1. We created a payment intent with the payment method and charge amount.
  2. We set the confirm the payment intent to true to charge the user immediately.
  3. We add an async method called** createCharge** to handle payment charges. Define the parameters, such as the card details and the amount to charge.

Step 7: Exposing the Payment Service

To make the payment service accessible to other microservices, set up a new async route in the payments controller decorated with the @MessagePattern decorator. Extract the payload and define a dedicated DTO for the data being passed to the create charge method.

Open your payment.controller.ts then update its content with this code:

import { Controller, UsePipes, ValidationPipe } from '@nestjs/common';
import { PaymentsService } from './payments.service';
import { MessagePattern, Payload } from '@nestjs/microservices';
import { CreateChargeDto } from '@app/common';

@Controller()
export class PaymentsController {
  constructor(private readonly paymentsService: PaymentsService) {}

  @MessagePattern('create_charge')
  @UsePipes(new ValidationPipe())
  async createcharge(@Payload() data: CreateChargeDto) {
    return this.paymentsService.createCharge(data);
  }
}

Enter fullscreen mode Exit fullscreen mode

We use NestJS's built-in validation pipes to validate the incoming metadata on the payload.


Conclusion

Congratulations! You've successfully created a microservice for processing payments using the Stripe API. This service can be integrated into your reservation system to handle payment processing securely. With the right configuration and integration, your application is ready to bill customers for their reservations.

In the next lessons, we will integrate and test it with our reservations.

Top comments (0)