From 5b4347268fd92d3b8e639026529696cc225c9117 Mon Sep 17 00:00:00 2001 From: nfesta2023 <142601504+nfesta2023@users.noreply.github.com> Date: Wed, 20 Mar 2024 20:34:25 +0700 Subject: [PATCH] create ahamove request for momo processing (#83) Co-authored-by: NHT --- README.md | 9 +- ...710911376008-UpdateMomoTransactionTable.ts | 52 +++ src/dependency/momo/momo.controller.ts | 4 +- src/dependency/momo/momo.service.ts | 178 ++++----- src/dto/create-momo-payment-request.dto.ts | 3 + src/dto/create-momo-payment-response.dto.ts | 5 + src/entity/momo-transaction.entity.ts | 2 +- src/entity/order.entity.ts | 2 +- .../invoice-status-history.service.ts | 31 +- src/feature/order/order.service.ts | 373 +++++++++++++----- 10 files changed, 466 insertions(+), 193 deletions(-) create mode 100644 src/database/migrations/1710911376008-UpdateMomoTransactionTable.ts create mode 100644 src/dto/create-momo-payment-request.dto.ts create mode 100644 src/dto/create-momo-payment-response.dto.ts diff --git a/README.md b/README.md index 9909f9f..11f98fd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - ## Node version v20.10.0 @@ -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 diff --git a/src/database/migrations/1710911376008-UpdateMomoTransactionTable.ts b/src/database/migrations/1710911376008-UpdateMomoTransactionTable.ts new file mode 100644 index 0000000..9f22d8e --- /dev/null +++ b/src/database/migrations/1710911376008-UpdateMomoTransactionTable.ts @@ -0,0 +1,52 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateMomoTransactionTable1710911376008 + implements MigrationInterface +{ + name = 'UpdateMomoTransactionTable1710911376008'; + + public async up(queryRunner: QueryRunner): Promise { + 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 { + 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' ; + `, + ); + } +} diff --git a/src/dependency/momo/momo.controller.ts b/src/dependency/momo/momo.controller.ts index 0bf3b50..69f769d 100644 --- a/src/dependency/momo/momo.controller.ts +++ b/src/dependency/momo/momo.controller.ts @@ -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 { @@ -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); } diff --git a/src/dependency/momo/momo.service.ts b/src/dependency/momo/momo.service.ts index df18fc5..8a2dca2 100644 --- a/src/dependency/momo/momo.service.ts +++ b/src/dependency/momo/momo.service.ts @@ -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 { @@ -54,30 +56,32 @@ export class MomoService { this.ipnUrl = `${this.redirectHost}/momo/momo-ipn-callback`; } - async sendMomoPaymentRequest(request: MomoRequestDTO) { + async sendMomoPaymentRequest( + request: CreateMomoPaymentRequest, + ): Promise { 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 @@ -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, + }); } } diff --git a/src/dto/create-momo-payment-request.dto.ts b/src/dto/create-momo-payment-request.dto.ts new file mode 100644 index 0000000..24628f8 --- /dev/null +++ b/src/dto/create-momo-payment-request.dto.ts @@ -0,0 +1,3 @@ +export class CreateMomoPaymentRequest { + invoiceId: number; +} diff --git a/src/dto/create-momo-payment-response.dto.ts b/src/dto/create-momo-payment-response.dto.ts new file mode 100644 index 0000000..36f2094 --- /dev/null +++ b/src/dto/create-momo-payment-response.dto.ts @@ -0,0 +1,5 @@ +export class CreateMomoPaymentResponse { + invoiceId: number; + amount: number; + payUrl: string; +} diff --git a/src/entity/momo-transaction.entity.ts b/src/entity/momo-transaction.entity.ts index a0bde86..376820d 100644 --- a/src/entity/momo-transaction.entity.ts +++ b/src/entity/momo-transaction.entity.ts @@ -12,7 +12,7 @@ export class MomoTransaction { requestId: string; @Column('decimal', { precision: 10, scale: 2 }) - amount: number; + amount: string; @Column({ length: 50 }) orderId: string; diff --git a/src/entity/order.entity.ts b/src/entity/order.entity.ts index 275e3bf..52a05d4 100644 --- a/src/entity/order.entity.ts +++ b/src/entity/order.entity.ts @@ -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; diff --git a/src/feature/invoice-status-history/invoice-status-history.service.ts b/src/feature/invoice-status-history/invoice-status-history.service.ts index df3d85b..37fb182 100644 --- a/src/feature/invoice-status-history/invoice-status-history.service.ts +++ b/src/feature/invoice-status-history/invoice-status-history.service.ts @@ -3,14 +3,15 @@ import { InternalServerErrorException, Logger, } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm'; import { InvoiceStatusHistory } from 'src/entity/invoice-history-status.entity'; import { Invoice } from 'src/entity/invoice.entity'; import { Order } from 'src/entity/order.entity'; import { InvoiceHistoryStatusEnum, OrderStatus } from 'src/enum'; -import { Repository } from 'typeorm'; +import { EntityManager, Repository } from 'typeorm'; import { v4 as uuidv4 } from 'uuid'; import { OrderService } from '../order/order.service'; +import { FALSE } from 'src/constant'; @Injectable() export class InvoiceStatusHistoryService { @@ -20,6 +21,7 @@ export class InvoiceStatusHistoryService { @InjectRepository(InvoiceStatusHistory) private invoiceHistoryStatusRepo: Repository, private readonly orderService: OrderService, + @InjectEntityManager() private entityManager: EntityManager, ) {} async updateInvoiceHistoryFromMomoWebhook(webhookData): Promise { @@ -33,7 +35,6 @@ export class InvoiceStatusHistoryService { const currentInvoice = await this.invoiceRepo.findOne({ where: { payment_order_id: requestId }, }); - console.log('????', currentInvoice); if (currentInvoice) { const updateData = this.convertMomoResultCode( @@ -55,6 +56,30 @@ export class InvoiceStatusHistoryService { await this.orderService.cancelOrder(currentInvoice.order_id, { isMomo: true, }); + } else if (updateData.status_id === InvoiceHistoryStatusEnum.PAID) { + //Create Ahamove Request if the order is the normal order + const order = await this.entityManager + .createQueryBuilder(Order, 'order') + .where('order.order_id = :order_id', { + order_id: currentInvoice.order_id, + }) + .getOne(); + if (!order) { + this.logger.error(`Order ${currentInvoice.order_id} not existed`); + throw new InternalServerErrorException(); + } + if (order.is_preorder == FALSE) { + const deliveryOrderId = + await this.orderService.createDeliveryRequest(order); + await this.entityManager + .createQueryBuilder() + .update(Order) + .set({ + delivery_order_id: deliveryOrderId, + }) + .where('order_id = :order_id', { order_id: order.order_id }) + .execute(); + } } return { message: 'Order updated successfully' }; } diff --git a/src/feature/order/order.service.ts b/src/feature/order/order.service.ts index ee0f17f..7a38bdb 100644 --- a/src/feature/order/order.service.ts +++ b/src/feature/order/order.service.ts @@ -16,7 +16,6 @@ import { PaymentList, } from 'src/enum'; import { EntityManager, Repository } from 'typeorm'; -import { GetApplicationFeeResponse } from './dto/get-application-fee-response.dto'; import { AhamoveService } from 'src/dependency/ahamove/ahamove.service'; import { PaymentOption } from 'src/entity/payment-option.entity'; import { OrderStatusLog } from 'src/entity/order-status-log.entity'; @@ -50,6 +49,7 @@ import { RestaurantExt } from 'src/entity/restaurant-ext.entity'; import { Packaging } from 'src/entity/packaging.entity'; import { GetDeliveryFeeResonse } from './dto/get-delivery-fee-response.dto'; import { VND } from 'src/constant/unit.constant'; +import { FALSE, TRUE } from 'src/constant'; @Injectable() export class OrderService { @@ -284,6 +284,7 @@ export class OrderService { } finally { // UPDATE ORDER STATUS const orderStatusLog = new OrderStatusLog(); + orderStatusLog.order_id = order_id; orderStatusLog.logged_at = new Date().getTime(); orderStatusLog.order_status_id = OrderStatus.CANCELLED; orderStatusLog.note = 'momo payment has been failed'; @@ -755,111 +756,42 @@ export class OrderService { } //set is_preorder or not - let isPreorder = false; + let isPreorder = FALSE; const isToday = this.commonService.isToday( expected_arrival_time, restaurant.utc_time_zone, ); if (!isToday) { - isPreorder = true; + isPreorder = TRUE; } else if (isToday && havingAdvancedCustomization) { - isPreorder = true; + isPreorder = TRUE; } //Create the request to delivery service let deliveryOrderId = null; - if (isPreorder == false && paymentMethod.name == PaymentList.COD) { - const restaurantAddressString = restaurantAddress.address_line - ? `${restaurantAddress.address_line}, ${restaurantAddress.ward}, ${restaurantAddress.city}, ${restaurantAddress.country}` - : `${restaurantAddress.ward}, ${restaurantAddress.city}, ${restaurantAddress.country}`; - const customerAddressString = address.address_line - ? `${address.address_line}, ${address.ward}, ${address.city}, ${address.country}` - : `${address.ward}, ${address.city}, ${address.country}`; - const restaurantExt = await this.entityManager - .createQueryBuilder(RestaurantExt, 'ext') - .where('ext.restaurant_id = :restaurant_id', { restaurant_id }) - .andWhere('ext.ISO_language_code = :lang', { lang: 'vie' }) - .getOne(); - const deliveryTime = deliveryEstimation.duration * 1000; - const deliverBufferTime = - this.configService.get('deliverBufferTime') * 60 * 1000; - const orderTime = - (expected_arrival_time - deliveryTime - deliverBufferTime) / 1000; - const averageOtherFee = (orderTotal - orderSubTotal) / orderQuantitySum; - const skuWIthMenuItems = await this.entityManager - .createQueryBuilder(SKU, 'sku') - .leftJoinAndSelect('sku.menu_item', 'menuItem') - .leftJoinAndSelect('menuItem.menuItemExt', 'ext') - .where('sku.sku_id IN (:...skuList)', { skuList }) - .getMany(); - const deliveryItems = []; - orderItems.forEach((i) => { - const sku = skuWIthMenuItems.find( - (skuWIthMenuItem) => skuWIthMenuItem.sku_id == i.sku_id, - ); - const name = sku.menu_item.menuItemExt.find( - (ext) => ext.ISO_language_code == 'vie', - ).short_name; - deliveryItems.push({ - _id: i.sku_id.toString(), - num: i.qty_ordered, - name: `${name} - ${i.portion_customization} - ${i.advanced_taste_customization} - ${i.basic_taste_customization}`, - price: i.price + averageOtherFee, - }); - }); - const ahamoveOrderRequest: PostAhaOrderRequest = { - startingPoint: { - address: restaurantAddressString, - lat: Number(restaurantAddress.latitude), - lng: Number(restaurantAddress.longitude), - name: restaurantExt.name, - mobile: restaurant.phone_number, - cod: 0, - formatted_address: restaurantAddressString, - short_address: restaurantAddressString, - address_code: null, - remarks: 'KHÔNG ỨNG TIỀN', - item_value: 0, - // require_pod?: boolean; // Optional property - }, - destination: { - address: customerAddressString, - lat: Number(address.latitude), - lng: Number(address.longitude), - name: customer.name, - mobile: customer.phone_number, - cod: orderTotal, - formatted_address: customerAddressString, - short_address: customerAddressString, - address_code: null, - remarks: 'KHÔNG ỨNG TIỀN', - item_value: 0, - require_pod: true, - }, - paymentMethod: 'BALANCE', - totalPay: 0, - orderTime: orderTime, - promoCode: null, - remarks: driver_note, - adminNote: '', - routeOptimized: false, - idleUntil: orderTime, - items: deliveryItems, - packageDetails: [], - groupServiceId: null, - groupRequests: null, - serviceType: null, + if (isPreorder == FALSE && paymentMethod.name == PaymentList.COD) { + const customerAddress: Address = { + address_id: undefined, + created_at: undefined, + ...address, }; - try { - deliveryOrderId = ( - await this.ahamoveService.postAhamoveOrder(ahamoveOrderRequest) - ).order_id; - } catch (error) { - throw new CustomRpcException( - 114, - 'There are some errors to request the delivery service', - ); - } + + deliveryOrderId = await this.createDeliveryRequest( + undefined, + restaurant_id, + restaurantAddress, + customerAddress, + deliveryEstimation, + expected_arrival_time, + orderTotal, + orderSubTotal, + orderQuantitySum, + skuList, + orderItems, + restaurant, + customer, + driver_note, + ); } //insert database (with transaction) @@ -1408,4 +1340,255 @@ export class OrderService { distance_km: deliveryEstimation?.distance, }; } + + async createDeliveryRequest( + order: Order = undefined, + restaurant_id: number = undefined, + restaurant_address: Address = undefined, + customer_address: Address = undefined, + delivery_estimation: any = undefined, + expected_arrival_time: number = undefined, + order_total: number = undefined, + order_sub_total: number = undefined, + order_quantity_sum: number = undefined, + sku_list: number[] = undefined, + order_items: OrderSKU[] = undefined, + _restaurant: Restaurant = undefined, + _customer: Customer = undefined, + driver_note: string = undefined, + ): Promise { + //Create the request to delivery service + let deliveryOrderId: string = undefined; + + //Restaurant Info + let restaurant: Restaurant = undefined; + if (_restaurant) { + restaurant = _restaurant; + } else { + restaurant = await this.commonService.getRestaurantById( + order.restaurant_id, + ); + if (!restaurant) { + this.logger.error('Restaurant doesnot exist'); + throw new Error('Restaurant doesnot exist'); + } + } + + const restaurantId = restaurant_id + ? restaurant_id + : restaurant.restaurant_id; + + //Restaurant Address + let restaurantAddress: Address = undefined; + if (restaurant_address) { + restaurantAddress = restaurant_address; + } else { + restaurantAddress = await this.entityManager + .createQueryBuilder(Address, 'address') + .where('address.address_id = :address_id', { + address_id: restaurant.address_id, + }) + .getOne(); + if (!restaurantAddress) { + this.logger.error('Restaurant address doesnot exist'); + throw new Error('Restaurant address doesnot exist'); + } + } + + //Customer Address + let customerAddress: Address = undefined; + if (customer_address) { + customerAddress = customer_address; + } else { + customerAddress = await this.entityManager + .createQueryBuilder(Address, 'address') + .where('address.address_id = :address_id', { + address_id: order.address_id, + }) + .getOne(); + if (!customerAddress) { + this.logger.error('Customer address doesnot exist'); + throw new Error('Customer address doesnot exist'); + } + } + + //Delivery Estimation + let deliveryEstimation: any = undefined; + if (delivery_estimation) { + deliveryEstimation = delivery_estimation; + } else { + deliveryEstimation = ( + await this.ahamoveService.estimatePrice([ + { + lat: restaurantAddress.latitude, + long: restaurantAddress.longitude, + }, + { + lat: customerAddress.latitude, + long: customerAddress.longitude, + }, + ]) + )[0].data; + if (!deliveryEstimation) { + this.logger.error('Cannot get delivery estimation'); + throw new Error('Cannot get delivery estimation'); + } + } + + const expectedArrivalTime: number = expected_arrival_time + ? expected_arrival_time + : order.expected_arrival_time; + + const orderTotal: number = order_total ? order_total : order.order_total; + + const orderSubTotal: number = order_sub_total + ? order_sub_total + : order.order_total - + order.delivery_fee - + order.packaging_fee - + order.cutlery_fee - + order.app_fee + + order.coupon_value_from_platform + + order.coupon_value_from_restaurant; + + //Order Items + let orderItems: OrderSKU[] = undefined; + if (order_items) { + orderItems = order_items; + } else { + orderItems = await this.entityManager + .createQueryBuilder(OrderSKU, 'item') + .where('item.order_id = :order_id', { order_id: order.order_id }) + .getMany(); + if (!orderItems || orderItems.length <= 0) { + this.logger.error('Cannot found order items'); + throw new Error('Cannot found order items'); + } + } + + let skuList: number[] = undefined; + if (sku_list) { + skuList = sku_list; + } else { + skuList = [...new Set(orderItems.map((item) => item.sku_id))]; + } + + const orderQuantitySum: number = order_quantity_sum + ? order_quantity_sum + : orderItems + .map((i) => i.qty_ordered) + .reduce((sum, val) => (sum += val), 0); + + //Customer Info + let customer: Customer = undefined; + if (_customer) { + customer = _customer; + } else { + customer = await this.entityManager + .createQueryBuilder(Customer, 'customer') + .where('customer.customer_id = :customer_id', { + customer_id: order.customer_id, + }) + .getOne(); + if (!customer) { + throw new Error('Customer is not found'); + } + } + + const driverNote: string = driver_note ? driver_note : order.driver_note; + + const restaurantAddressString = restaurantAddress.address_line + ? `${restaurantAddress.address_line}, ${restaurantAddress.ward}, ${restaurantAddress.city}, ${restaurantAddress.country}` + : `${restaurantAddress.ward}, ${restaurantAddress.city}, ${restaurantAddress.country}`; + const customerAddressString = customerAddress.address_line + ? `${customerAddress.address_line}, ${customerAddress.ward}, ${customerAddress.city}, ${customerAddress.country}` + : `${customerAddress.ward}, ${customerAddress.city}, ${customerAddress.country}`; + const restaurantExt = await this.entityManager + .createQueryBuilder(RestaurantExt, 'ext') + .where('ext.restaurant_id = :restaurantId', { restaurantId }) + .andWhere('ext.ISO_language_code = :lang', { lang: 'vie' }) + .getOne(); + const deliveryTime = deliveryEstimation.duration * 1000; + const deliverBufferTime = + this.configService.get('deliverBufferTime') * 60 * 1000; + const orderTime = + (expectedArrivalTime - deliveryTime - deliverBufferTime) / 1000; + const averageOtherFee = (orderTotal - orderSubTotal) / orderQuantitySum; + const skuWIthMenuItems = await this.entityManager + .createQueryBuilder(SKU, 'sku') + .leftJoinAndSelect('sku.menu_item', 'menuItem') + .leftJoinAndSelect('menuItem.menuItemExt', 'ext') + .where('sku.sku_id IN (:...skuList)', { skuList }) + .getMany(); + const deliveryItems = []; + orderItems.forEach((i) => { + const sku = skuWIthMenuItems.find( + (skuWIthMenuItem) => skuWIthMenuItem.sku_id == i.sku_id, + ); + const name = sku.menu_item.menuItemExt.find( + (ext) => ext.ISO_language_code == 'vie', + ).short_name; + deliveryItems.push({ + _id: i.sku_id.toString(), + num: i.qty_ordered, + name: `${name} - ${i.portion_customization} - ${i.advanced_taste_customization} - ${i.basic_taste_customization}`, + price: i.price + averageOtherFee, + }); + }); + const ahamoveOrderRequest: PostAhaOrderRequest = { + startingPoint: { + address: restaurantAddressString, + lat: Number(restaurantAddress.latitude), + lng: Number(restaurantAddress.longitude), + name: restaurantExt.name, + mobile: restaurant.phone_number, + cod: 0, + formatted_address: restaurantAddressString, + short_address: restaurantAddressString, + address_code: null, + remarks: 'KHÔNG ỨNG TIỀN', + item_value: 0, + // require_pod?: boolean; // Optional property + }, + destination: { + address: customerAddressString, + lat: Number(customerAddress.latitude), + lng: Number(customerAddress.longitude), + name: customer.name, + mobile: customer.phone_number, + cod: orderTotal, + formatted_address: customerAddressString, + short_address: customerAddressString, + address_code: null, + remarks: driverNote, + item_value: 0, + require_pod: true, + }, + paymentMethod: 'BALANCE', + totalPay: 0, + orderTime: orderTime, + promoCode: null, + remarks: driverNote, + adminNote: '', + routeOptimized: false, + idleUntil: orderTime, + items: deliveryItems, + packageDetails: [], + groupServiceId: null, + groupRequests: null, + serviceType: null, + }; + try { + deliveryOrderId = ( + await this.ahamoveService.postAhamoveOrder(ahamoveOrderRequest) + ).order_id; + } catch (error) { + throw new CustomRpcException( + 114, + 'There are some errors to request the delivery service', + ); + } + + return deliveryOrderId; + } }