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:
- 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.
- 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.
Top comments (0)