From 2551521539daa7bc0289c8540fdf092a453e32c4 Mon Sep 17 00:00:00 2001 From: Aswin Ashar Abdullah Date: Thu, 18 Jul 2024 12:42:47 +0700 Subject: [PATCH] feat(SPG-423) Midtrans Integration --- .../data/services/midtrans.service.ts | 18 ++------ .../helpers/mapping-transaction.helper.ts | 31 +++++++++++++ .../configuration/midtrans/midtrans.module.ts | 9 ++-- .../data/models/transaction.model.ts | 6 +++ .../domain/entities/transaction.entity.ts | 2 + .../midtrans-transaction-callback.handler.ts | 44 +++++++++++++++++++ .../managers/confirm-transaction.manager.ts | 22 ++++++++++ .../usecases/transaction-data.orchestrator.ts | 9 +++- .../transaction/transaction.module.ts | 2 + 9 files changed, 121 insertions(+), 22 deletions(-) create mode 100644 src/modules/configuration/midtrans/domain/usecases/helpers/mapping-transaction.helper.ts create mode 100644 src/modules/transaction/transaction/domain/usecases/handlers/midtrans-transaction-callback.handler.ts diff --git a/src/modules/configuration/midtrans/data/services/midtrans.service.ts b/src/modules/configuration/midtrans/data/services/midtrans.service.ts index 568cf19..5a3d658 100644 --- a/src/modules/configuration/midtrans/data/services/midtrans.service.ts +++ b/src/modules/configuration/midtrans/data/services/midtrans.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; import { EventBus } from '@nestjs/cqrs'; +import { mappingMidtransTransaction } from '../../domain/usecases/helpers/mapping-transaction.helper'; const midtransClient = require('midtrans-client'); @Injectable() @@ -19,20 +20,7 @@ export class MidtransService { } async create(body): Promise { - return await this.midtransInstance.createTransaction({ - transaction_details: { - order_id: '123', - gross_amount: 10000, - }, - credit_card: { - secure: true, - }, - customer_details: { - first_name: 'budd', - last_name: 'pratama', - email: 'budi.pra@mail.com', - phone: '0822111111', - }, - }); + const data = mappingMidtransTransaction(body); + return await this.midtransInstance.createTransaction(data); } } diff --git a/src/modules/configuration/midtrans/domain/usecases/helpers/mapping-transaction.helper.ts b/src/modules/configuration/midtrans/domain/usecases/helpers/mapping-transaction.helper.ts new file mode 100644 index 0000000..0e6cb4c --- /dev/null +++ b/src/modules/configuration/midtrans/domain/usecases/helpers/mapping-transaction.helper.ts @@ -0,0 +1,31 @@ +export function mappingMidtransTransaction(transaction) { + const item_details = transaction.items?.map((item) => { + return { + quantity: Number(item.qty), + price: Number(item.total_price), + name: item.item_name, + category: item.item_category_name, + }; + }); + + if (transaction.payment_discount_total) { + item_details.push({ + quantity: 1, + price: -Number(transaction.payment_discount_total), + name: 'discount', + }); + } + + return { + transaction_details: { + order_id: transaction.id, + gross_amount: Number(transaction.payment_total), + }, + item_details: item_details, + customer_details: { + first_name: transaction.customer_name, + email: transaction.customer_email, + phone: transaction.customer_phone, + }, + }; +} diff --git a/src/modules/configuration/midtrans/midtrans.module.ts b/src/modules/configuration/midtrans/midtrans.module.ts index ae5abf1..38d22f5 100644 --- a/src/modules/configuration/midtrans/midtrans.module.ts +++ b/src/modules/configuration/midtrans/midtrans.module.ts @@ -2,19 +2,16 @@ import { ConfigModule } from '@nestjs/config'; import { CqrsModule } from '@nestjs/cqrs'; import { MidtransController } from './infrastructure/midtrans.controller'; import { MidtransService } from './data/services/midtrans.service'; -import { Module } from '@nestjs/common'; +import { Global, Module } from '@nestjs/common'; +@Global() @Module({ imports: [ ConfigModule.forRoot(), - // TypeOrmModule.forFeature( - // [ - // ], - // CONNECTION_NAME.DEFAULT, - // ), CqrsModule, ], controllers: [MidtransController], providers: [MidtransService], + exports: [MidtransService], }) export class MidtransModule {} diff --git a/src/modules/transaction/transaction/data/models/transaction.model.ts b/src/modules/transaction/transaction/data/models/transaction.model.ts index 7ca536f..786623f 100644 --- a/src/modules/transaction/transaction/data/models/transaction.model.ts +++ b/src/modules/transaction/transaction/data/models/transaction.model.ts @@ -125,6 +125,12 @@ export class TransactionModel @Column('varchar', { name: 'payment_code_reference', nullable: true }) payment_code_reference: string; + @Column('varchar', { name: 'payment_midtrans_token', nullable: true }) + payment_midtrans_token: string; + + @Column('varchar', { name: 'payment_midtrans_url', nullable: true }) + payment_midtrans_url: string; + @Column('date', { name: 'payment_date', nullable: true }) payment_date: Date; diff --git a/src/modules/transaction/transaction/domain/entities/transaction.entity.ts b/src/modules/transaction/transaction/domain/entities/transaction.entity.ts index c340dbc..09f1f54 100644 --- a/src/modules/transaction/transaction/domain/entities/transaction.entity.ts +++ b/src/modules/transaction/transaction/domain/entities/transaction.entity.ts @@ -47,6 +47,8 @@ export interface TransactionEntity extends BaseStatusEntity { payment_type_method_qr: string; payment_card_information: string; payment_code_reference: string; + payment_midtrans_token: string; + payment_midtrans_url: string; payment_date: Date; // calculation data diff --git a/src/modules/transaction/transaction/domain/usecases/handlers/midtrans-transaction-callback.handler.ts b/src/modules/transaction/transaction/domain/usecases/handlers/midtrans-transaction-callback.handler.ts new file mode 100644 index 0000000..ec0d46b --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/handlers/midtrans-transaction-callback.handler.ts @@ -0,0 +1,44 @@ +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { MidtransCallbackEvent } from 'src/modules/configuration/midtrans/domain/entities/midtrans-callback.event'; +import { TransactionDataService } from '../../../data/services/transaction-data.service'; +import { TransactionModel } from '../../../data/models/transaction.model'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +@EventsHandler(MidtransCallbackEvent) +export class MidtransCallbackHandler + implements IEventHandler +{ + constructor(private dataService: TransactionDataService) {} + + async handle(event: MidtransCallbackEvent) { + const data_id = event.data.id; + const data = event.data.data; + const transaction = await this.dataService.getOneByOptions({ + where: { + id: data_id, + }, + }); + + if (['capture', 'settlement'].includes(data.transaction_status)) { + Object.assign(transaction, { + status: STATUS.SETTLED, + settlement_date: new Date(data.settlement_time), + payment_code_reference: data.approval_code, + }); + } else if (['pending'].includes(data['transaction_status'])) { + Object.assign(transaction, { + status: STATUS.PENDING, + }); + } else { + Object.assign(transaction, { + status: STATUS.EXPIRED, + }); + } + + const queryRunner = this.dataService + .getRepository() + .manager.connection.createQueryRunner(); + + await this.dataService.create(queryRunner, TransactionModel, transaction); + } +} diff --git a/src/modules/transaction/transaction/domain/usecases/managers/confirm-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/confirm-transaction.manager.ts index b2da650..a53c28a 100644 --- a/src/modules/transaction/transaction/domain/usecases/managers/confirm-transaction.manager.ts +++ b/src/modules/transaction/transaction/domain/usecases/managers/confirm-transaction.manager.ts @@ -13,9 +13,12 @@ import { TransactionModel } from '../../../data/models/transaction.model'; import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event'; import { STATUS } from 'src/core/strings/constants/base.constants'; import { generateInvoiceCodeHelper } from './helpers/generate-invoice-code.helper'; +import { TransactionPaymentType } from '../../../constants'; @Injectable() export class ConfirmTransactionManager extends BaseUpdateStatusManager { + protected relations = ['items']; + getResult(): string { return `Success active data ${this.result.invoice_code}`; } @@ -33,6 +36,25 @@ export class ConfirmTransactionManager extends BaseUpdateStatusManager { const freeTransaction = this.data.payment_total < 1; + + if (this.data.payment_type == TransactionPaymentType.MIDTRANS) { + try { + const { token, redirect_url } = await this.dataServiceFirstOpt.create( + this.oldData, + ); + Object.assign(this.data, { + payment_midtrans_token: token, + payment_midtrans_url: redirect_url, + }); + } catch (error) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! this transaction already created, please check your email to continue payment`, + error: 'Unprocessable Entity', + }); + } + } + Object.assign(this.data, { invoice_code: await generateInvoiceCodeHelper(this.dataService), status: freeTransaction ? STATUS.ACTIVE : STATUS.PENDING, diff --git a/src/modules/transaction/transaction/domain/usecases/transaction-data.orchestrator.ts b/src/modules/transaction/transaction/domain/usecases/transaction-data.orchestrator.ts index 477fd1e..3aa32a8 100644 --- a/src/modules/transaction/transaction/domain/usecases/transaction-data.orchestrator.ts +++ b/src/modules/transaction/transaction/domain/usecases/transaction-data.orchestrator.ts @@ -14,6 +14,7 @@ import { CancelTransactionManager } from './managers/cancel-transaction.manager' import { BatchCancelTransactionManager } from './managers/batch-cancel-transaction.manager'; import { ConfirmDataTransactionManager } from './managers/confirm-data-transaction.manager'; import { BatchConfirmDataTransactionManager } from './managers/batch-confirm-data-transaction.manager'; +import { MidtransService } from 'src/modules/configuration/midtrans/data/services/midtrans.service'; @Injectable() export class TransactionDataOrchestrator { @@ -29,6 +30,7 @@ export class TransactionDataOrchestrator { private cancelManager: CancelTransactionManager, private batchCancelManager: BatchCancelTransactionManager, private serviceData: TransactionDataService, + private midtransService: MidtransService, ) {} async create(data): Promise { @@ -81,7 +83,11 @@ export class TransactionDataOrchestrator { async confirm(dataId): Promise { this.confirmManager.setData(dataId, STATUS.ACTIVE); - this.confirmManager.setService(this.serviceData, TABLE_NAME.TRANSACTION); + this.confirmManager.setService( + this.serviceData, + TABLE_NAME.TRANSACTION, + this.midtransService, + ); await this.confirmManager.execute(); return this.confirmManager.getResult(); } @@ -91,6 +97,7 @@ export class TransactionDataOrchestrator { this.batchConfirmManager.setService( this.serviceData, TABLE_NAME.TRANSACTION, + this.midtransService, ); await this.batchConfirmManager.execute(); return this.batchConfirmManager.getResult(); diff --git a/src/modules/transaction/transaction/transaction.module.ts b/src/modules/transaction/transaction/transaction.module.ts index 1ce5612..a626078 100644 --- a/src/modules/transaction/transaction/transaction.module.ts +++ b/src/modules/transaction/transaction/transaction.module.ts @@ -31,6 +31,7 @@ import { SalesPriceFormulaModel } from '../sales-price-formula/data/models/sales import { TaxModel } from '../tax/data/models/tax.model'; import { SettledTransactionHandler } from './domain/usecases/handlers/settled-transaction.handler'; import { RefundUpdatedHandler } from './domain/usecases/handlers/refund-update.handler'; +import { MidtransCallbackHandler } from './domain/usecases/handlers/midtrans-transaction-callback.handler'; @Module({ imports: [ @@ -51,6 +52,7 @@ import { RefundUpdatedHandler } from './domain/usecases/handlers/refund-update.h providers: [ RefundUpdatedHandler, PosTransactionHandler, + MidtransCallbackHandler, SettledTransactionHandler, IndexTransactionManager,