Skip to content

Commit

Permalink
Features/lamnv/ahamove integration (#68)
Browse files Browse the repository at this point in the history
* feat: get ahamove token, estimate fee, webhook and ws api

* fix: update migrations and update ahamove api

* fix: update script migrations

* fix: update on wheel api

* integration with momo

* fix: update integration with momo

* update momo webhook

* update configuration for momo

* fix: momo integration

* fix: momo integration

* minor modify

* fix: update momo invoice check and ahamove webhook

* adjust some logic

* fix comment from Tuan (#72)

* fix comment from Tuan

* change input from orderId into invoiceId

* hotfix

---------

Co-authored-by: lamnv <[email protected]>
Co-authored-by: NHT <[email protected]>

---------

Co-authored-by: lamnv <[email protected]>
Co-authored-by: NHT <[email protected]>
Co-authored-by: nfesta2023 <[email protected]>
  • Loading branch information
4 people authored Mar 14, 2024
1 parent 2add72d commit e3011b1
Show file tree
Hide file tree
Showing 30 changed files with 1,102 additions and 191 deletions.
12 changes: 11 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,14 @@ DB_NAME=
FEATURE_FLAG=

#AHAMOVE INTEGRATION
AHAMOVE_TOKEN=
AHAMOVE_TOKEN=

#MOMO INTEGRATION
MOMO_PARTNER_CODE=
MOMO_ACCESS_KEY=
MOMO_SECRETKEY=
MOMO_REDIRECT_HOST=
MOMO_REDIRECT_URL=
MOMO_REQUEST_TYPE=
MOMO_BASE_URL=
MOMO_MAX_RETRIES=
3 changes: 2 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 80
"printWidth": 80,
"endOfLine": "auto"
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@nestjs/typeorm": "^10.0.1",
"@nestjs/websockets": "^10.3.3",
"axios": "^1.6.2",
"axios-retry": "^4.0.0",
"flagsmith-nodejs": "^3.2.0",
"joi": "^17.12.1",
"mysql2": "^3.6.5",
Expand Down
4 changes: 4 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { CommonModule } from './feature/common/common.module';
import { CartModule } from './feature/cart/cart.module';
import { RatingAndReviewModule } from './feature/rating-and-review/rating-and-review.module';
import { AhamoveModule } from './dependency/ahamove/ahamove.module';
import { InvoiceStatusHistoryModule } from './feature/invoice-status-history/invoice-status-history.module';
import { MomoModule } from './dependency/momo/momo.module';
import { OrderModule } from './feature/order/order.module';
import { HealthCheckController } from './healthcheck/health-check.controller';

Expand Down Expand Up @@ -50,6 +52,8 @@ import { HealthCheckController } from './healthcheck/health-check.controller';
CartModule,
RatingAndReviewModule,
AhamoveModule,
InvoiceStatusHistoryModule,
MomoModule,
OrderModule,
],
controllers: [AppController, HealthCheckController],
Expand Down
11 changes: 11 additions & 0 deletions src/config/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ export default () => ({
password: process.env.DB_PASSWORD,
name: process.env.DB_NAME,
},
momo: {
partnerCode: process.env.MOMO_PARTNER_CODE || '',
accessKey: process.env.MOMO_ACCESS_KEY || '',
secretkey: process.env.MOMO_SECRETKEY || '',
redirectHost: process.env.MOMO_REDIRECT_HOST || 'https://api.2all.com.vn',
redirectUrl:
process.env.MOMO_REDIRECT_URL || 'https://www.2all.com.vn/order/detail',
requestType: process.env.MOMO_REQUEST_TYPE || 'captureWallet',
baseUrl: process.env.MOMO_BASE_URL || 'https://test-payment.momo.vn',
maximumRetry: process.env.MOMO_MAX_RETRIES || 1,
},
featureFlag: process.env.FEATURE_FLAG || '',
ahamoveToken: process.env.AHAMOVE_TOKEN,
planningDay: 7, // the restaurant will plan new cooking schedule every Saturday (last until the end of the day)
Expand Down
8 changes: 6 additions & 2 deletions src/database/migrations/1708402970486-TableAhamoveOrder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ export class TableAhamoveOrder1708402970486 implements MigrationInterface {
await queryRunner.query(
`CREATE TABLE \`Ahamove_Order\` (\`id\` varchar(36) NOT NULL, \`service_id\` varchar(255) NOT NULL, \`path\` json NOT NULL, \`requests\` json NOT NULL, \`payment_method\` varchar(255) NOT NULL, \`total_pay\` int NOT NULL, \`order_time\` int NOT NULL, \`promo_code\` varchar(255) NULL, \`remarks\` varchar(255) NOT NULL, \`admin_note\` varchar(255) NOT NULL, \`route_optimized\` tinyint NOT NULL, \`idle_until\` int NOT NULL, \`items\` json NOT NULL, \`package_detail\` json NOT NULL, \`group_service_id\` varchar(255) NULL, \`group_requests\` varchar(255) NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`,
);
await queryRunner.query(`ALTER TABLE \`Ahamove_Order\` ADD \`order_id\` varchar(255) NULL`);
await queryRunner.query(`ALTER TABLE \`Ahamove_Order\` ADD \`response\` json NOT NULL`);
await queryRunner.query(
`ALTER TABLE \`Ahamove_Order\` ADD \`order_id\` varchar(255) NULL`,
);
await queryRunner.query(
`ALTER TABLE \`Ahamove_Order\` ADD \`response\` json NOT NULL`,
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
Expand Down
16 changes: 16 additions & 0 deletions src/database/migrations/1709005940772-add-momo-transactions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class AddMomoTransactions1709005940772 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE \`MomoTransaction\` (\`id\` int NOT NULL AUTO_INCREMENT, \`partnerCode\` varchar(50) NOT NULL, \`requestId\` varchar(50) NOT NULL, \`amount\` decimal(10,2) NOT NULL, \`orderId\` varchar(50) NOT NULL, \`transId\` bigint NOT NULL, \`responseTime\` bigint NOT NULL, \`orderInfo\` varchar(255) NOT NULL, \`type\` varchar(10) NOT NULL, \`resultCode\` int NOT NULL, \`redirectUrl\` varchar(255) NOT NULL, \`ipnUrl\` varchar(255) NOT NULL, \`extraData\` text NOT NULL, \`requestType\` varchar(50) NOT NULL, \`signature\` varchar(255) NOT NULL, \`lang\` varchar(2) NOT NULL DEFAULT 'en', PRIMARY KEY (\`id\`)) ENGINE=InnoDB`,
);
await queryRunner.query(`ALTER TABLE \`MomoTransaction\`
ADD COLUMN \`payUrl\` TEXT NULL AFTER \`lang\`,
ADD COLUMN \`message\` TEXT NULL AFTER \`payUrl\`;`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE \`MomoTransaction\``);
}
}
8 changes: 7 additions & 1 deletion src/dependency/ahamove/ahamove.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@ import { AhamoveController } from './ahamove.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AhamoveOrderEntity } from 'src/entity/ahamove-order.entity';
import { AhamoveOrderHookEntity } from 'src/entity/ahamove-order-hook.entity';
import { InvoiceStatusHistoryModule } from 'src/feature/invoice-status-history/invoice-status-history.module';
import { OrderModule } from 'src/feature/order/order.module';

@Module({
imports: [HttpModule, ConfigModule, TypeOrmModule.forFeature([AhamoveOrderEntity, AhamoveOrderHookEntity]), OrderModule],
imports: [
HttpModule,
ConfigModule,
TypeOrmModule.forFeature([AhamoveOrderEntity, AhamoveOrderHookEntity]),
InvoiceStatusHistoryModule,
],
providers: [AhamoveService],
exports: [AhamoveService],
controllers: [AhamoveController],
Expand Down
93 changes: 72 additions & 21 deletions src/dependency/ahamove/ahamove.service.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import { HttpService } from '@nestjs/axios';
import { BadRequestException, Inject, Injectable, InternalServerErrorException, Logger, OnModuleInit } from '@nestjs/common';
import {
BadRequestException,
Inject,
Injectable,
InternalServerErrorException,
Logger,
OnModuleInit,
} from '@nestjs/common';
import { AxiosRequestConfig } from 'axios';
import { firstValueFrom, lastValueFrom } from 'rxjs';
import { Coordinate } from 'src/type';
import { FlagsmithService } from '../flagsmith/flagsmith.service';
import { ConfigService } from '@nestjs/config';
import axios from 'axios';
import { AhamoveOrder, PostAhaOrderRequest } from './dto/ahamove.dto';
import postAhaOrderRequestSchema, { coordinateListSchema } from './schema/ahamove.schema';
import postAhaOrderRequestSchema, {
coordinateListSchema,
} from './schema/ahamove.schema';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { AhamoveOrderEntity } from 'src/entity/ahamove-order.entity';
Expand All @@ -32,13 +41,18 @@ export class AhamoveService implements OnModuleInit {
private readonly httpService: HttpService,
@Inject('FLAGSMITH_SERVICE') private flagService: FlagsmithService,
private configService: ConfigService,
@InjectRepository(AhamoveOrderEntity) private ahamoveOrder: Repository<AhamoveOrderEntity>,
@InjectRepository(AhamoveOrderHookEntity) private ahamoveOrderHook: Repository<AhamoveOrderHookEntity>,
private readonly orderService: OrderService,
@InjectRepository(AhamoveOrderEntity)
private ahamoveOrder: Repository<AhamoveOrderEntity>,
@InjectRepository(AhamoveOrderHookEntity)
private ahamoveOrderHook: Repository<AhamoveOrderHookEntity>,
) {
this.AHA_MOVE_BASE_URL = configService.get('ahamove.baseUrl') || 'https://apistg.ahamove.com/api';
this.AHA_MOVE_API_KEY = configService.get('ahamove.apiKey') || '7bbc5c69e7237f267e97f81237a717c387f13bdb';
this.AHA_MOVE_USERNAME = configService.get('ahamove.username') || '2ALL Admin';
this.AHA_MOVE_BASE_URL =
configService.get('ahamove.baseUrl') || 'https://apistg.ahamove.com';
this.AHA_MOVE_API_KEY =
configService.get('ahamove.apiKey') ||
'7bbc5c69e7237f267e97f81237a717c387f13bdb';
this.AHA_MOVE_USERNAME =
configService.get('ahamove.username') || '2ALL Admin';
this.AHA_MOVE_MOBILE = configService.get('ahamove.mobile') || '84905005248';
}

Expand All @@ -48,14 +62,14 @@ export class AhamoveService implements OnModuleInit {

async getAhamoveAccessToken() {
let data = JSON.stringify({
mobile: '84905005248',
name: '2ALL Admin',
api_key: '7bbc5c69e7237f267e97f81237a717c387f13bdb',
mobile: this.AHA_MOVE_MOBILE,
name: this.AHA_MOVE_USERNAME,
api_key: this.AHA_MOVE_API_KEY,
});

let config = {
method: 'post',
url: `${this.AHA_MOVE_BASE_URL}/v3/partner/account/register`,
url: `${this.AHA_MOVE_BASE_URL}/api/v3/partner/account/register`,
headers: {
'Content-Type': 'application/json',
},
Expand All @@ -75,7 +89,10 @@ export class AhamoveService implements OnModuleInit {
});
}

async estimateTimeAndDistance(startingPoint: Coordinate, destination: Coordinate) {
async estimateTimeAndDistance(
startingPoint: Coordinate,
destination: Coordinate,
) {
try {
const data: any = JSON.stringify({
order_time: 0,
Expand Down Expand Up @@ -135,7 +152,9 @@ export class AhamoveService implements OnModuleInit {
const { error, value } = await coordinateListSchema.validate(coordinates);
if (error) {
this.logger.warn('Bad coordinates: ' + coordinates);
throw new BadRequestException(error?.details.map((x) => x.message).join(', '));
throw new BadRequestException(
error?.details.map((x) => x.message).join(', '),
);
}
const startingPoint = coordinates[0];
const destination = coordinates[1];
Expand Down Expand Up @@ -168,7 +187,7 @@ export class AhamoveService implements OnModuleInit {
const config: AxiosRequestConfig = {
method: 'post',
maxBodyLength: Infinity,
url: `${this.AHA_MOVE_BASE_URL}/v3/partner/order/estimate`,
url: `${this.AHA_MOVE_BASE_URL}/api/v3/partner/order/estimate`,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.AHA_MOVE_TOKEN}`,
Expand All @@ -179,7 +198,9 @@ export class AhamoveService implements OnModuleInit {
const response = await axios.request(config);
return response.data;
} catch (error) {
this.logger.error('Error occurred while call to get estimate: ' + JSON.stringify(error));
this.logger.error(
'Error occurred while call to get estimate: ' + JSON.stringify(error),
);
throw new InternalServerErrorException(error);
}
}
Expand All @@ -194,7 +215,7 @@ export class AhamoveService implements OnModuleInit {
let config = {
method: 'post',
maxBodyLength: Infinity,
url: `${this.AHA_MOVE_BASE_URL}/v3/partner/order/create`,
url: `${this.AHA_MOVE_BASE_URL}/api/v3/partner/order/create`,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.AHA_MOVE_TOKEN}`,
Expand All @@ -206,17 +227,24 @@ export class AhamoveService implements OnModuleInit {
// update order id and response from ahamove
orderRequest.order_id = data.order_id;
orderRequest.response = data;
const result = await this.ahamoveOrder.save(AhamoveMapper.fromDTOtoEntity(orderRequest));
const result = await this.ahamoveOrder.save(
AhamoveMapper.fromDTOtoEntity(orderRequest),
);
this.logger.verbose('created ahamove order', JSON.stringify(result));
return data;
} catch (error) {
this.logger.error('An error occurred while calling post ahamove order', JSON.stringify(error));
this.logger.error(
'An error occurred while calling post ahamove order',
JSON.stringify(error),
);
throw new InternalServerErrorException('An error occurred');
}
}

async getAhamoveOrderByOrderId(orderId: string): Promise<AhamoveOrderEntity> {
const result = await this.ahamoveOrder.findOne({ where: { order_id: orderId } });
const result = await this.ahamoveOrder.findOne({
where: { order_id: orderId },
});
return result;
}

Expand All @@ -225,7 +253,10 @@ export class AhamoveService implements OnModuleInit {
return result;
}

async #buildAhamoveRequest(order: PostAhaOrderRequest, service_type: string): Promise<AhamoveOrder> {
async #buildAhamoveRequest(
order: PostAhaOrderRequest,
service_type: string,
): Promise<AhamoveOrder> {
try {
await postAhaOrderRequestSchema.validate(order);
} catch (error) {
Expand Down Expand Up @@ -253,4 +284,24 @@ export class AhamoveService implements OnModuleInit {
}
return result;
}

async cancelAhamoveOrder(orderId, cancelReasonMessage) {
cancelReasonMessage =
cancelReasonMessage || 'supplier_sender_ask_to_return_the_package';
let config = {
method: 'get',
maxBodyLength: Infinity,
url: `${this.AHA_MOVE_BASE_URL}/v1/order/cancel?token=${this.AHA_MOVE_TOKEN}&order_id=${orderId}&comment=${cancelReasonMessage}`,
headers: {
'cache-control': 'no-cache',
},
};
try {
const result = await axios.request(config);
return result;
} catch (error) {
this.logger.error('An error occurred ', JSON.stringify(error));
throw new InternalServerErrorException(error?.message);
}
}
}
4 changes: 3 additions & 1 deletion src/dependency/ahamove/schema/ahamove.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,7 @@ const coordinateSchema = Joi.object({
long: Joi.number().required(),
});

export const coordinateListSchema = Joi.array().items(coordinateSchema).required();
export const coordinateListSchema = Joi.array()
.items(coordinateSchema)
.required();
export default postAhaOrderRequestSchema;
21 changes: 21 additions & 0 deletions src/dependency/momo/momo.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Controller, Logger } from '@nestjs/common';
import { MomoService } from './momo.service';
import { MessagePattern } from '@nestjs/microservices';
import { MomoRequestDTO } from './momo.dto';

@Controller('momo')
export class MomoController {
private readonly logger = new Logger(MomoController.name);

constructor(private readonly momoService: MomoService) {}

@MessagePattern({ cmd: 'create_momo_payment' })
async sendMomoPaymentRequest(payload: MomoRequestDTO) {
return this.momoService.sendMomoPaymentRequest(payload);
}

@MessagePattern({ cmd: 'momo_payment_ipn_callback' })
async handleMomoCallback(payload: any) {
return this.momoService.handleMoMoIpnCallBack(payload);
}
}
16 changes: 16 additions & 0 deletions src/dependency/momo/momo.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export interface MomoRequestDTO {
invoiceId: number;
}

export interface MomoRequest {
accessKey: string;
amount: string;
extraData: string;
ipnUrl: string;
orderId: string;
orderInfo: string;
partnerCode: string;
redirectUrl: string;
requestId: string;
requestType: string;
}
22 changes: 22 additions & 0 deletions src/dependency/momo/momo.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Module } from '@nestjs/common';
import { MomoService } from './momo.service';
import { MomoController } from './momo.controller';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { MomoTransaction } from 'src/entity/momo-transaction.entity';
import { InvoiceStatusHistoryModule } from 'src/feature/invoice-status-history/invoice-status-history.module';
import { Invoice } from 'src/entity/invoice.entity';
import { InvoiceStatusHistory } from 'src/entity/invoice-history-status.entity';
import { OrderModule } from 'src/feature/order/order.module';

@Module({
imports: [
ConfigModule,
TypeOrmModule.forFeature([MomoTransaction, Invoice, InvoiceStatusHistory]),
InvoiceStatusHistoryModule,
OrderModule,
],
providers: [MomoService],
controllers: [MomoController],
})
export class MomoModule {}
Loading

0 comments on commit e3011b1

Please sign in to comment.