feat(SPG-423) Midtrans Integration
parent
403a75c835
commit
2551521539
|
@ -1,5 +1,6 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { EventBus } from '@nestjs/cqrs';
|
import { EventBus } from '@nestjs/cqrs';
|
||||||
|
import { mappingMidtransTransaction } from '../../domain/usecases/helpers/mapping-transaction.helper';
|
||||||
const midtransClient = require('midtrans-client');
|
const midtransClient = require('midtrans-client');
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -19,20 +20,7 @@ export class MidtransService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(body): Promise<any> {
|
async create(body): Promise<any> {
|
||||||
return await this.midtransInstance.createTransaction({
|
const data = mappingMidtransTransaction(body);
|
||||||
transaction_details: {
|
return await this.midtransInstance.createTransaction(data);
|
||||||
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',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
|
@ -2,19 +2,16 @@ import { ConfigModule } from '@nestjs/config';
|
||||||
import { CqrsModule } from '@nestjs/cqrs';
|
import { CqrsModule } from '@nestjs/cqrs';
|
||||||
import { MidtransController } from './infrastructure/midtrans.controller';
|
import { MidtransController } from './infrastructure/midtrans.controller';
|
||||||
import { MidtransService } from './data/services/midtrans.service';
|
import { MidtransService } from './data/services/midtrans.service';
|
||||||
import { Module } from '@nestjs/common';
|
import { Global, Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Global()
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot(),
|
ConfigModule.forRoot(),
|
||||||
// TypeOrmModule.forFeature(
|
|
||||||
// [
|
|
||||||
// ],
|
|
||||||
// CONNECTION_NAME.DEFAULT,
|
|
||||||
// ),
|
|
||||||
CqrsModule,
|
CqrsModule,
|
||||||
],
|
],
|
||||||
controllers: [MidtransController],
|
controllers: [MidtransController],
|
||||||
providers: [MidtransService],
|
providers: [MidtransService],
|
||||||
|
exports: [MidtransService],
|
||||||
})
|
})
|
||||||
export class MidtransModule {}
|
export class MidtransModule {}
|
||||||
|
|
|
@ -125,6 +125,12 @@ export class TransactionModel
|
||||||
@Column('varchar', { name: 'payment_code_reference', nullable: true })
|
@Column('varchar', { name: 'payment_code_reference', nullable: true })
|
||||||
payment_code_reference: string;
|
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 })
|
@Column('date', { name: 'payment_date', nullable: true })
|
||||||
payment_date: Date;
|
payment_date: Date;
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,8 @@ export interface TransactionEntity extends BaseStatusEntity {
|
||||||
payment_type_method_qr: string;
|
payment_type_method_qr: string;
|
||||||
payment_card_information: string;
|
payment_card_information: string;
|
||||||
payment_code_reference: string;
|
payment_code_reference: string;
|
||||||
|
payment_midtrans_token: string;
|
||||||
|
payment_midtrans_url: string;
|
||||||
payment_date: Date;
|
payment_date: Date;
|
||||||
|
|
||||||
// calculation data
|
// calculation data
|
||||||
|
|
|
@ -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<MidtransCallbackEvent>
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,9 +13,12 @@ import { TransactionModel } from '../../../data/models/transaction.model';
|
||||||
import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event';
|
import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event';
|
||||||
import { STATUS } from 'src/core/strings/constants/base.constants';
|
import { STATUS } from 'src/core/strings/constants/base.constants';
|
||||||
import { generateInvoiceCodeHelper } from './helpers/generate-invoice-code.helper';
|
import { generateInvoiceCodeHelper } from './helpers/generate-invoice-code.helper';
|
||||||
|
import { TransactionPaymentType } from '../../../constants';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ConfirmTransactionManager extends BaseUpdateStatusManager<TransactionEntity> {
|
export class ConfirmTransactionManager extends BaseUpdateStatusManager<TransactionEntity> {
|
||||||
|
protected relations = ['items'];
|
||||||
|
|
||||||
getResult(): string {
|
getResult(): string {
|
||||||
return `Success active data ${this.result.invoice_code}`;
|
return `Success active data ${this.result.invoice_code}`;
|
||||||
}
|
}
|
||||||
|
@ -33,6 +36,25 @@ export class ConfirmTransactionManager extends BaseUpdateStatusManager<Transacti
|
||||||
|
|
||||||
async beforeProcess(): Promise<void> {
|
async beforeProcess(): Promise<void> {
|
||||||
const freeTransaction = this.data.payment_total < 1;
|
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, {
|
Object.assign(this.data, {
|
||||||
invoice_code: await generateInvoiceCodeHelper(this.dataService),
|
invoice_code: await generateInvoiceCodeHelper(this.dataService),
|
||||||
status: freeTransaction ? STATUS.ACTIVE : STATUS.PENDING,
|
status: freeTransaction ? STATUS.ACTIVE : STATUS.PENDING,
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { CancelTransactionManager } from './managers/cancel-transaction.manager'
|
||||||
import { BatchCancelTransactionManager } from './managers/batch-cancel-transaction.manager';
|
import { BatchCancelTransactionManager } from './managers/batch-cancel-transaction.manager';
|
||||||
import { ConfirmDataTransactionManager } from './managers/confirm-data-transaction.manager';
|
import { ConfirmDataTransactionManager } from './managers/confirm-data-transaction.manager';
|
||||||
import { BatchConfirmDataTransactionManager } from './managers/batch-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()
|
@Injectable()
|
||||||
export class TransactionDataOrchestrator {
|
export class TransactionDataOrchestrator {
|
||||||
|
@ -29,6 +30,7 @@ export class TransactionDataOrchestrator {
|
||||||
private cancelManager: CancelTransactionManager,
|
private cancelManager: CancelTransactionManager,
|
||||||
private batchCancelManager: BatchCancelTransactionManager,
|
private batchCancelManager: BatchCancelTransactionManager,
|
||||||
private serviceData: TransactionDataService,
|
private serviceData: TransactionDataService,
|
||||||
|
private midtransService: MidtransService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async create(data): Promise<TransactionEntity> {
|
async create(data): Promise<TransactionEntity> {
|
||||||
|
@ -81,7 +83,11 @@ export class TransactionDataOrchestrator {
|
||||||
|
|
||||||
async confirm(dataId): Promise<string> {
|
async confirm(dataId): Promise<string> {
|
||||||
this.confirmManager.setData(dataId, STATUS.ACTIVE);
|
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();
|
await this.confirmManager.execute();
|
||||||
return this.confirmManager.getResult();
|
return this.confirmManager.getResult();
|
||||||
}
|
}
|
||||||
|
@ -91,6 +97,7 @@ export class TransactionDataOrchestrator {
|
||||||
this.batchConfirmManager.setService(
|
this.batchConfirmManager.setService(
|
||||||
this.serviceData,
|
this.serviceData,
|
||||||
TABLE_NAME.TRANSACTION,
|
TABLE_NAME.TRANSACTION,
|
||||||
|
this.midtransService,
|
||||||
);
|
);
|
||||||
await this.batchConfirmManager.execute();
|
await this.batchConfirmManager.execute();
|
||||||
return this.batchConfirmManager.getResult();
|
return this.batchConfirmManager.getResult();
|
||||||
|
|
|
@ -31,6 +31,7 @@ import { SalesPriceFormulaModel } from '../sales-price-formula/data/models/sales
|
||||||
import { TaxModel } from '../tax/data/models/tax.model';
|
import { TaxModel } from '../tax/data/models/tax.model';
|
||||||
import { SettledTransactionHandler } from './domain/usecases/handlers/settled-transaction.handler';
|
import { SettledTransactionHandler } from './domain/usecases/handlers/settled-transaction.handler';
|
||||||
import { RefundUpdatedHandler } from './domain/usecases/handlers/refund-update.handler';
|
import { RefundUpdatedHandler } from './domain/usecases/handlers/refund-update.handler';
|
||||||
|
import { MidtransCallbackHandler } from './domain/usecases/handlers/midtrans-transaction-callback.handler';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -51,6 +52,7 @@ import { RefundUpdatedHandler } from './domain/usecases/handlers/refund-update.h
|
||||||
providers: [
|
providers: [
|
||||||
RefundUpdatedHandler,
|
RefundUpdatedHandler,
|
||||||
PosTransactionHandler,
|
PosTransactionHandler,
|
||||||
|
MidtransCallbackHandler,
|
||||||
SettledTransactionHandler,
|
SettledTransactionHandler,
|
||||||
|
|
||||||
IndexTransactionManager,
|
IndexTransactionManager,
|
||||||
|
|
Loading…
Reference in New Issue