13. Integrating and Validating Payment Services in a Reservation System
Published: 2023-11-07 03:28:27

## Introduction:

In this tutorial, we will walk you through the process of integrating a payment service with a reservation service. This integration will enable your application to bill users for reservations. We will also cover the validation of input data to ensure data consistency and security. 

---

## Step 1: Define Payment Service

Define `PAYMENTS_SERVICE`  in your **libs/common/src/constants/service.ts**

`service.ts`

```
export const AUTH_SERVICE = 'auth';
export const PAYMENTS_SERVICE = 'payments';
```

---

## Step 2: Modify Reservation Module

To enable the reservation service to communicate with the payment service, we first need to set up the payment service as a client. In your `reservations module`, inject the payment service using an injection token:

```
import { Module } from '@nestjs/common';
import { ReservationsService } from './reservations.service';
import { ReservationsController } from './reservations.controller';
import {
  AUTH_SERVICE,
  DatabaseModule,
  LoggerModule,
  PAYMENTS_SERVICE,
} from '@app/common';
import { ReservationsRepository } from './reservations.repository';
import {
  ReservationDocument,
  ReservationSchema,
} from './entities/reservation.entity';
import { ConfigModule, ConfigService } from '@nestjs/config';
import * as Joi from 'joi';
import { ClientsModule, Transport } from '@nestjs/microservices';

@Module({
  imports: [
    DatabaseModule,
    DatabaseModule.forFeature([
      { name: ReservationDocument.name, schema: ReservationSchema },
    ]),
    LoggerModule,
    ConfigModule.forRoot({
      isGlobal: true,
      validationSchema: Joi.object({
        MONGODB_URI: Joi.string().required(),
        PORT: Joi.number().required(),
        AUTH_HOST: Joi.string().required(),
        AUTH_PORT: Joi.number().required(),
      }),
    }),
    ClientsModule.registerAsync([
      {
        name: AUTH_SERVICE,
        useFactory: (configService: ConfigService) => ({
          transport: Transport.TCP,
          options: {
            host: configService.get('AUTH_HOST'),
            port: configService.get('AUTH_PORT'),
          },
        }),
        inject: [ConfigService],
      },
      {
        name: PAYMENTS_SERVICE,
        useFactory: (configService: ConfigService) => ({
          transport: Transport.TCP,
          options: {
            host: configService.get('PAYMENTS_HOST'),
            port: configService.get('PAYMENTS_PORT'),
          },
        }),
        inject: [ConfigService],
      },
    ]),
  ],
  controllers: [ReservationsController],
  providers: [ReservationsService, ReservationsRepository],
})
export class ReservationsModule {}

```

---

## Step 3: Configure Environment Variables

Since we are using two new environment variables, `PAYMENTS_HOST` and `PAYMENTS_PORT`, which do not yet exist in our .env file, let's go ahead and define them. Please open your payments .env file and modify its content with the following code.

```
MONGODB_URI=mongodb://mongo:27017/reservation
PORT=3000
AUTH_HOST=auth
AUTH_PORT=3002
PAYMENTS_HOST=payments
PAYMENTS_PORT=3003
```

---

## Step 4: Inject the Payment Service

Inject the payment service using NestJS's @Inject decorator and call it as a client proxy for microservices. Ensure that the import statement is correct. 
update your `reservations.service.ts` with this content:

```
import { Inject, Injectable } from '@nestjs/common';

import { ReservationsRepository } from './reservations.repository';
import { CreateReservationDto } from './dto/create-reservation.dto';
import { UpdateReservationDto } from './dto/update-reservation.dto';
import { PAYMENTS_SERVICE } from '@app/common';
import { ClientProxy } from '@nestjs/microservices';

@Injectable()
export class ReservationsService {
  @Inject(PAYMENTS_SERVICE) payment_service: ClientProxy;
  constructor(
    private readonly reservationsRepository: ReservationsRepository,
  ) {}

  async create(createReservationDto: CreateReservationDto, userId: string) {
    return this.reservationsRepository.create({
      ...createReservationDto,
      timestamp: new Date(),
      userId,
    });
  }

  async findAll() {
    return this.reservationsRepository.find({});
  }

  async findOne(_id: string) {
    return this.reservationsRepository.findOne({ _id });
  }

  async update(_id: string, updateReservationDto: UpdateReservationDto) {
    return this.reservationsRepository.findOneAndUpdate(
      { _id },
      { $set: updateReservationDto },
    );
  }

  async remove(_id: string) {
    return this.reservationsRepository.findOneAndDelete({ _id });
  }
}

```

This step allows you to use the payment service for billing users when creating reservations.

---

## Step 5: Creating Card DTO

Create a separate CardDTO to validate the credit card details. We will do it inside our **libs/common/src/dto** folder to allow us to use the **CardDTO** anywhere in our apps to avoid duplication:

```
touch libs/common/src/dto/card.dto.ts
touch libs/common/src/dto/create-charge.dto.ts
```

populate your `card.dto.ts` with the following content:

```
import { IsCreditCard, IsNotEmpty, IsNumber, IsString } from 'class-validator';

export class CardDTO {
  @IsString()
  @IsNotEmpty()
  cvc: string;

  @IsNumber()
  exp_month: number;

  @IsNumber()
  exp_year: number;

  @IsCreditCard()
  number: string;
}

```

populate your `create-charge.dto.ts` with the following content:

```
import { CardDTO } from './card.dto';
import { Type } from 'class-transformer';
import {
  IsDefined,
  IsNotEmpty,
  ValidateNested,
  IsNumber,
} from 'class-validator';

export class CreateChargeDto {
  @Type(() => CardDTO)
  @IsDefined()
  @IsNotEmpty()
  @ValidateNested()
  card: CardDTO;

  @IsNumber()
  amount: number;
}


```

Make sure to update the **libs/common/src/dto/index.ts** and export the newly created **CardDTO** and **CreateChargeDto**  

```
export * from './user.dto';
export * from './card.dto';
export * from './create-charge.dto';
```

>Please remember that we have already created a 'create-charge.dto' in our payments app. We need to delete the existing one and replace it with the newly created 'create-charge.dto' located inside our common folder. This change is necessary because we have not applied any validation to the 'create-charge.dto' in our payments app.

update your `paymens.controller.ts` with the following content to use our newly created **create-charge.dto.ts** in our @app/common:

```
import { Controller } 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')
  async createcharge(@Payload() data: CreateChargeDto) {
    return this.paymentsService.createCharge(data);
  }
}

```
---
## Step 6: Updating the Create Reservation DTO

To facilitate billing for reservations, update the Create Reservation DTO to include a card property.

In your reservation app inside the dto folder, Open your `create-reservation.dto.ts` file and update with this content:

```
import { CardDTO } from '@app/common';
import { Type } from 'class-transformer';
import {
  IsDate,
  IsDefined,
  IsNotEmpty,
  IsNumber,
  IsString,
  ValidateNested,
} from 'class-validator';

export class CreateReservationDto {
  @IsDate()
  @Type(() => Date)
  startDate: Date;

  @IsDate()
  @Type(() => Date)
  endDate: Date;

  @IsString()
  @IsNotEmpty()
  placeId: string;

  @IsString()
  @IsNotEmpty()
  invoiceId: string;

  @Type(() => CardDTO)
  @IsDefined()
  @IsNotEmpty()
  @ValidateNested()
  card: CardDTO;

  @IsNumber()
  amount: number;
}

```

---
Step 7: Test Your DTO

Test Scenario: Reservation Creation with Validation
Request Details:

Method: POST
Endpoint: http://localhost:3000/reservations
Test Steps:

1. Invalid Card Information missing `cvc`

Send a POST request with the following payload:

```
{
  "startDate": "2023-11-01",
  "endDate": "2023-11-05",
  "placeId": "1232",
  "invoiceId": "67890",
  "charge": {
   "card": {
    "exp_month": 12,     
    "exp_year": 2025,      
    "number": "4242 4242 4242 4242"  
  },
  "amount": 99.99
  }
}

```
Check the response for validation errors, especially for the "cvc" field.

2. Valid Card Information

Send a POST request with the following payload:

```
{
  "startDate": "2023-11-01",
  "endDate": "2023-11-05",
  "placeId": "1232",
  "invoiceId": "67890",
  "charge": {
   "card": {
    "cvc": "123",
    "exp_month": 12,     
    "exp_year": 2025,      
    "number": "4242 4242 4242 4242"  
  },
  "amount": 99.99
  }
}

```
Check the response for a successful reservation creation.

**Expected Outcomes:**

In Step 1, you should receive a validation error response indicating that the "cvc" field is incorrect.

In Step 2, you should receive a successful reservation creation response.

This scenario will help you verify if the validation is working correctly and if the reservation creation is functioning as expected.

---

## Conclusion:

You've successfully integrated and validated the payment service in your reservation system. With robust schema validation and proper data exchange, you can ensure a reliable and secure booking process. This tutorial provides a foundation for building more complex systems and enhancing the capabilities of your microservices. Happy coding!

In the next lesson, we will connect it with our Stripe to persist our payments and make a few modifications to our payment service. This will be our last lesson in payment integration.