Skip to content

Commit

Permalink
create ahamove request for momo processing (#83)
Browse files Browse the repository at this point in the history
Co-authored-by: NHT <[email protected]>
  • Loading branch information
nfesta2023 and hoangtuan910 authored Mar 20, 2024
1 parent 0de1d44 commit 5b43472
Show file tree
Hide file tree
Showing 10 changed files with 466 additions and 193 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

## Node version

v20.10.0
Expand All @@ -13,10 +12,12 @@ v20.10.0
$ yarn install
```

## Running the app
$ export BACKEND_ENV=dev
$ npm run typeorm migration:run -- -d ./src/migration.config.ts
## Migration

$ export BACKEND_ENV=dev
$ npm run typeorm migration:run -- -d ./src/migration.config.ts
$ npm run typeorm migration:revert -- -d ./src/migration.config.ts
$ npm run typeorm migration:generate ./src/database/migrations/XXXX -- -d ./src/migration.config.ts

```bash
# development
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class UpdateMomoTransactionTable1710911376008
implements MigrationInterface
{
name = 'UpdateMomoTransactionTable1710911376008';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE \`MomoTransaction\`
CHANGE COLUMN \`partnerCode\` \`partnerCode\` VARCHAR(50) NULL ,
CHANGE COLUMN \`requestId\` \`requestId\` VARCHAR(50) NULL ,
CHANGE COLUMN \`amount\` \`amount\` DECIMAL(10,2) NULL ,
CHANGE COLUMN \`orderId\` \`orderId\` VARCHAR(50) NULL ,
CHANGE COLUMN \`transId\` \`transId\` BIGINT NULL ,
CHANGE COLUMN \`responseTime\` \`responseTime\` BIGINT NULL ,
CHANGE COLUMN \`orderInfo\` \`orderInfo\` VARCHAR(255) NULL ,
CHANGE COLUMN \`type\` \`type\` VARCHAR(10) NULL ,
CHANGE COLUMN \`resultCode\` \`resultCode\` INT NULL ,
CHANGE COLUMN \`redirectUrl\` \`redirectUrl\` VARCHAR(255) NULL ,
CHANGE COLUMN \`ipnUrl\` \`ipnUrl\` VARCHAR(255) NULL ,
CHANGE COLUMN \`extraData\` \`extraData\` TEXT NULL ,
CHANGE COLUMN \`requestType\` \`requestType\` VARCHAR(50) NULL ,
CHANGE COLUMN \`signature\` \`signature\` VARCHAR(255) NULL ,
CHANGE COLUMN \`lang\` \`lang\` VARCHAR(2) NULL DEFAULT 'en' ;
`,
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DELETE FROM \`MomoTransaction\` where id > 0`);
await queryRunner.query(
`ALTER TABLE \`MomoTransaction\`
CHANGE COLUMN \`partnerCode\` \`partnerCode\` VARCHAR(50) NOT NULL ,
CHANGE COLUMN \`requestId\` \`requestId\` VARCHAR(50) NOT NULL ,
CHANGE COLUMN \`amount\` \`amount\` DECIMAL(10,2) NOT NULL ,
CHANGE COLUMN \`orderId\` \`orderId\` VARCHAR(50) NOT NULL ,
CHANGE COLUMN \`transId\` \`transId\` BIGINT NOT NULL ,
CHANGE COLUMN \`responseTime\` \`responseTime\` BIGINT NOT NULL ,
CHANGE COLUMN \`orderInfo\` \`orderInfo\` VARCHAR(255) NOT NULL ,
CHANGE COLUMN \`type\` \`type\` VARCHAR(10) NOT NULL ,
CHANGE COLUMN \`resultCode\` \`resultCode\` INT NOT NULL ,
CHANGE COLUMN \`redirectUrl\` \`redirectUrl\` VARCHAR(255) NOT NULL ,
CHANGE COLUMN \`ipnUrl\` \`ipnUrl\` VARCHAR(255) NOT NULL ,
CHANGE COLUMN \`extraData\` \`extraData\` TEXT NOT NULL ,
CHANGE COLUMN \`requestType\` \`requestType\` VARCHAR(50) NOT NULL ,
CHANGE COLUMN \`signature\` \`signature\` VARCHAR(255) NOT NULL ,
CHANGE COLUMN \`lang\` \`lang\` VARCHAR(2) NOT NULL DEFAULT 'en' ;
`,
);
}
}
4 changes: 3 additions & 1 deletion src/dependency/momo/momo.controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Controller, Logger } from '@nestjs/common';
import { Controller, Logger, UseFilters } from '@nestjs/common';
import { MomoService } from './momo.service';
import { MessagePattern } from '@nestjs/microservices';
import { MomoRequestDTO } from './momo.dto';
import { CustomRpcExceptionFilter } from 'src/filters/custom-rpc-exception.filter';

@Controller('momo')
export class MomoController {
Expand All @@ -10,6 +11,7 @@ export class MomoController {
constructor(private readonly momoService: MomoService) {}

@MessagePattern({ cmd: 'create_momo_payment' })
@UseFilters(new CustomRpcExceptionFilter())
async sendMomoPaymentRequest(payload: MomoRequestDTO) {
return this.momoService.sendMomoPaymentRequest(payload);
}
Expand Down
178 changes: 90 additions & 88 deletions src/dependency/momo/momo.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,21 @@ import {
Logger,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import axios, { AxiosError } from 'axios';
import axios, { AxiosError, AxiosResponse } from 'axios';
import { MomoTransaction } from 'src/entity/momo-transaction.entity';
import { Repository } from 'typeorm';
import { MomoRequestDTO } from './momo.dto';
const crypto = require('crypto');
import axiosRetry from 'axios-retry';
import { InvoiceStatusHistory } from 'src/entity/invoice-history-status.entity';
import { v4 as uuidv4 } from 'uuid';
import { InvoiceHistoryStatusEnum, OrderStatus } from 'src/enum';
import { InvoiceHistoryStatusEnum } from 'src/enum';
import { Invoice } from 'src/entity/invoice.entity';
import { OrderService } from 'src/feature/order/order.service';
import { InvoiceStatusHistoryService } from 'src/feature/invoice-status-history/invoice-status-history.service';
import { ConfigService } from '@nestjs/config';
import { CustomRpcException } from 'src/exceptions/custom-rpc.exception';
import { CreateMomoPaymentResponse } from 'src/dto/create-momo-payment-response.dto';
import { CreateMomoPaymentRequest } from 'src/dto/create-momo-payment-request.dto';

@Injectable()
export class MomoService {
Expand Down Expand Up @@ -54,30 +56,32 @@ export class MomoService {
this.ipnUrl = `${this.redirectHost}/momo/momo-ipn-callback`;
}

async sendMomoPaymentRequest(request: MomoRequestDTO) {
async sendMomoPaymentRequest(
request: CreateMomoPaymentRequest,
): Promise<CreateMomoPaymentResponse> {
const currentInvoice = await this.invoiceRepo.findOne({
where: { invoice_id: request.invoiceId },
});
if (!currentInvoice) {
throw new InternalServerErrorException('Invoice not found');
// throw new InternalServerErrorException('Invoice not found');
throw new CustomRpcException(2, 'Invoice is not found');
}
const requestId = uuidv4();
const orderId = requestId;
const momoRedirectUrl = `${this.redirectUrl}/${currentInvoice.order_id}`;
const momoSignatureObj = {
partnerCode: this.partnerCode,
accessKey: this.accessKey,
requestId: requestId,
amount: currentInvoice.total_amount,
orderId: orderId,
orderInfo:
currentInvoice.description ||
`payment for invoice id ${request.invoiceId}`,
redirectUrl: this.redirectUrl,
orderInfo: `Thanh toán cho đơn hàng 2ALL ${currentInvoice.order_id}`,
redirectUrl: momoRedirectUrl,
ipnUrl: this.ipnUrl,
extraData: '',
requestType: this.requestType,
};
console.log(momoSignatureObj);
// console.log(momoSignatureObj);

const rawSignature = this.createSignature(momoSignatureObj);
const signature = crypto
Expand Down Expand Up @@ -126,103 +130,101 @@ export class MomoService {

if (
latestInvoiceStatus &&
latestInvoiceStatus.status_id === 'NEW' &&
latestInvoiceStatus.status_id === InvoiceHistoryStatusEnum.STARTED &&
!currentInvoice.payment_order_id
) {
this.logger.log(
'currentInvoice for momo payment order: ',
JSON.stringify(currentInvoice),
);
return axiosInstance
.request(options)
.then(async (response) => {
const momoOrderResult = response.data;
if (
momoOrderResult.resultCode === 0 ||
momoOrderResult.resultCode === 9000
) {
const momoResult = {
...momoOrderResult,
requestId: requestId,
partnerCode: this.partnerCode,
extraData: currentInvoice.description,
ipnUrl: this.ipnUrl,
orderId: orderId,
orderInfo: currentInvoice.description,
redirectUrl: this.redirectUrl,
requestType: this.requestType,
signature: signature,
type: 'request',
lang: 'en',
};
await this.momoRepo.save(momoResult);
if (momoResult.resultCode === 0) {
// const isPaymentOrderIdExist =
// !currentInvoice.payment_order_id ||
// currentInvoice.payment_order_id == ''
// ? false
// : true;

// Update field payment_order_id of table Invoice with requestId
await this.invoiceRepo.update(currentInvoice.invoice_id, {
payment_order_id: requestId,
});

//Insert a record into table 'Invoice_Status_History'
const momoInvoiceStatusHistory = new InvoiceStatusHistory();
momoInvoiceStatusHistory.status_id =
InvoiceHistoryStatusEnum.PENDING;
momoInvoiceStatusHistory.status_history_id = uuidv4();
momoInvoiceStatusHistory.invoice_id = currentInvoice.invoice_id;
// if (!isPaymentOrderIdExist) {
momoInvoiceStatusHistory.note = `momo request ${momoResult.requestId} for payment`;
// } else {
// momoInvoiceStatusHistory.note = `update new momo request ${momoResult.requestId} for payment`;
// }
await this.orderStatusHistoryRepo.insert(
momoInvoiceStatusHistory,
);
}
}
// return momoOrderResult;
return {
invoiceId: currentInvoice.invoice_id,
amount: momoOrderResult.amount,
payUrl: momoOrderResult.payUrl,
};
})
.catch(async (error: AxiosError) => {
this.logger.error(
'An error occurred when create momo request',
JSON.stringify(error.response?.data),
);
await this.orderService.cancelOrder(currentInvoice.order_id, {
isMomo: true,
});
throw new InternalServerErrorException();
let response: AxiosResponse;
try {
response = await axiosInstance.request(options);
} catch (error) {
console.log('error', error);
this.logger.error(
'An error occurred when create momo request',
JSON.stringify(error.response?.data),
);
//Cancel Order
await this.orderService.cancelOrder(currentInvoice.order_id, {
isMomo: true,
});
//Cancel Invoice
const momoInvoiceStatusHistory = new InvoiceStatusHistory();
momoInvoiceStatusHistory.invoice_id = currentInvoice.invoice_id;
momoInvoiceStatusHistory.status_id = InvoiceHistoryStatusEnum.CANCELLED;
momoInvoiceStatusHistory.note = 'Failed to call momo api';
momoInvoiceStatusHistory.status_history_id = uuidv4();
await this.invoiceHistoryStatusRepo.save(momoInvoiceStatusHistory);
// throw new InternalServerErrorException();
throw new CustomRpcException(201, error.response?.data);
}
const momoOrderResult = response.data;
this.logger.debug('momoOrderResult: ', momoOrderResult);
if (
momoOrderResult.resultCode === 0 ||
momoOrderResult.resultCode === 9000
) {
const momoResult = {
...momoOrderResult,
requestId: requestId,
partnerCode: this.partnerCode,
extraData: currentInvoice.description,
ipnUrl: this.ipnUrl,
orderId: orderId,
orderInfo: `Thanh toán cho đơn hàng 2ALL ${currentInvoice.order_id}`,
redirectUrl: momoRedirectUrl,
requestType: this.requestType,
signature: signature,
type: 'request',
lang: 'en',
};
await this.momoRepo.save(momoResult);
if (momoResult.resultCode === 0) {
// Update field payment_order_id of table Invoice with requestId
await this.invoiceRepo.update(currentInvoice.invoice_id, {
payment_order_id: requestId,
});

//Insert a record into table 'Invoice_Status_History'
const momoInvoiceStatusHistory = new InvoiceStatusHistory();
momoInvoiceStatusHistory.status_id = InvoiceHistoryStatusEnum.PENDING;
momoInvoiceStatusHistory.status_history_id = uuidv4();
momoInvoiceStatusHistory.invoice_id = currentInvoice.invoice_id;
momoInvoiceStatusHistory.note = `momo request ${momoResult.requestId} for payment`;
await this.orderStatusHistoryRepo.insert(momoInvoiceStatusHistory);
}
}
this.logger.debug('end usecase new invoice');
// return momoOrderResult;
return {
invoiceId: currentInvoice.invoice_id,
amount: momoOrderResult.amount,
payUrl: momoOrderResult.payUrl,
};
} else if (
latestInvoiceStatus &&
latestInvoiceStatus.status_id === 'PENDING' &&
latestInvoiceStatus.status_id === InvoiceHistoryStatusEnum.PENDING &&
currentInvoice.payment_order_id
) {
const currentMomoTransaction = await this.momoRepo.findOne({
where: { requestId: currentInvoice.payment_order_id, type: 'request' },
});
return {
// orderId: currentMomoTransaction?.orderId,
// requestId: currentMomoTransaction?.requestId,
invoiceId: currentInvoice.invoice_id,
amount: currentMomoTransaction.amount,
// responseTime: currentMomoTransaction.responseTime,
// message: currentMomoTransaction.message,
// resultCode: currentMomoTransaction.resultCode,
amount: Number(currentMomoTransaction.amount),
payUrl: currentMomoTransaction.payUrl,
};
} else {
throw new InternalServerErrorException(
'cannot create momo payment order with the invoice',
);
// throw new InternalServerErrorException(
// 'cannot create momo payment order with the invoice',
// );
throw new CustomRpcException(200, {
message: 'cannot create momo payment with the invoice',
invoice_status: latestInvoiceStatus.status_id,
payment_order_id: currentInvoice.payment_order_id,
});
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/dto/create-momo-payment-request.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export class CreateMomoPaymentRequest {
invoiceId: number;
}
5 changes: 5 additions & 0 deletions src/dto/create-momo-payment-response.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class CreateMomoPaymentResponse {
invoiceId: number;
amount: number;
payUrl: string;
}
2 changes: 1 addition & 1 deletion src/entity/momo-transaction.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export class MomoTransaction {
requestId: string;

@Column('decimal', { precision: 10, scale: 2 })
amount: number;
amount: string;

@Column({ length: 50 })
orderId: string;
Expand Down
2 changes: 1 addition & 1 deletion src/entity/order.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class Order {
currency: number;

@Column({ type: 'tinyint', nullable: false, default: '0', unique: false })
is_preorder: boolean;
is_preorder: number;

@Column({ type: 'bigint', nullable: true, unique: false })
expected_arrival_time: number;
Expand Down
Loading

0 comments on commit 5b43472

Please sign in to comment.