mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 00:52:50 +00:00
170 lines
6.0 KiB
TypeScript
170 lines
6.0 KiB
TypeScript
import {
|
|
BadRequestException,
|
|
forwardRef,
|
|
Inject,
|
|
Injectable,
|
|
InternalServerErrorException,
|
|
NotFoundException,
|
|
} from "@nestjs/common";
|
|
import { DataSource } from "typeorm";
|
|
import { PaymentEntity } from "./entities/payment.entity";
|
|
import { PaymentRepository } from "./payment.repository";
|
|
|
|
import * as fs from "fs";
|
|
import * as path from "path";
|
|
import * as Handlebars from "handlebars";
|
|
import { ConfigService } from "@nestjs/config";
|
|
// import { SchedulingStatus } from "@edr/types";
|
|
import { Booking } from "../bookings/entities/booking.entity";
|
|
|
|
import {
|
|
ClientAction,
|
|
createMerchantOrderId,
|
|
ProviderPaymentStatus,
|
|
TelebirrProvider,
|
|
} from "@edr/payment-providers";
|
|
import { ProviderInitiationInput } from "@edr/types"
|
|
import { InitiateResponseDto, PaymentPlatformDto } from "./payments.dto";
|
|
import { BookingBatchService } from "../train-scheduling/booking-batch.service";
|
|
|
|
const DEFAULT_CURRENCY = "ETB";
|
|
|
|
@Injectable()
|
|
export class PaymentService {
|
|
constructor(
|
|
private readonly configService: ConfigService,
|
|
private readonly datasource: DataSource,
|
|
private readonly paymentRepo: PaymentRepository,
|
|
private readonly telebirrProvider: TelebirrProvider,
|
|
@Inject(forwardRef(() => BookingBatchService))
|
|
private readonly bookingBatchService: BookingBatchService,
|
|
) { }
|
|
|
|
async initBookingTelebirr(
|
|
bookingId: string,
|
|
platform: PaymentPlatformDto,
|
|
): Promise<{ redirectUrl: string }> {
|
|
// const booking = await this.datasource.getRepository(Booking).findOneBy({ id: bookingId });
|
|
// if (!booking) throw new NotFoundException("Booking not found");
|
|
|
|
// const booking = new Booking()
|
|
// booking.totalAmount = 20
|
|
// booking.id = randomUUID
|
|
const amount = 20
|
|
const merchantOrderId = createMerchantOrderId();
|
|
const redirectBase = this.configService.get<string>("TELEBIRR_SUCCESS_BOOKING_REDIRECT_BASE_URL");
|
|
const redirectUrl = `${redirectBase}/${merchantOrderId}`;
|
|
const amountMinor = Math.round(Number(amount) * 100);
|
|
|
|
const input: ProviderInitiationInput = {
|
|
merchantOrderId,
|
|
orderRef: bookingId,
|
|
amountMinor,
|
|
currency: DEFAULT_CURRENCY,
|
|
platform: platform || "web",
|
|
redirectUrl,
|
|
};
|
|
|
|
const result = await this.telebirrProvider.initiate(input);
|
|
|
|
const payment = await this.paymentRepo.create({
|
|
amount: amount,
|
|
currency: DEFAULT_CURRENCY,
|
|
method: "telebirr",
|
|
refId: bookingId,
|
|
type: "booking",
|
|
merchantOrderId,
|
|
rawInitiation: result.rawInitiation,
|
|
clientAction: result.clientAction as Record<string, unknown>,
|
|
expiresAt: result.expiresAt,
|
|
reason: `Payment for booking`,
|
|
});
|
|
|
|
return {
|
|
redirectUrl: `${this.configService.get<string>("TELEBIRR_REDIRECT_BASE_URL")}/${payment.merchantOrderId}`
|
|
}
|
|
}
|
|
|
|
|
|
async getActivePaymentByOrderIdAndMethod(orderId: string, method: PaymentEntity["method"]): Promise<PaymentEntity | null> {
|
|
return this.paymentRepo.getActivePaymentByOrderIdAndMethod(orderId, method)
|
|
}
|
|
|
|
|
|
async genReceiptHtml(orderId: string) {
|
|
const payment = await this.paymentRepo.findOneBy({
|
|
merchantOrderId: orderId,
|
|
status: "success"
|
|
})
|
|
if (!payment) {
|
|
throw new BadRequestException()
|
|
}
|
|
|
|
const filePath = path.join(__dirname, "templates", "receipt.hbs");
|
|
if (!fs.existsSync(filePath)) {
|
|
throw new InternalServerErrorException()
|
|
}
|
|
const source = fs.readFileSync(filePath, "utf8");
|
|
const template = Handlebars.compile(source);
|
|
|
|
const html = template({
|
|
vendorName: "Ethio Djibouti Railway Ticket Booking",
|
|
vendorAddress: "Addis Ababa",
|
|
receiptDate: payment.paidAt,
|
|
paymentMethod: payment?.method,
|
|
subtotal: payment?.amount.toString(),
|
|
total: payment?.amount.toString(),
|
|
currency: payment?.currency,
|
|
reason: payment?.reason
|
|
});
|
|
|
|
return html;
|
|
}
|
|
|
|
async checkStatusAndUpdate(orderId: string) {
|
|
const resp = await this.paymentRepo.findOneBy({ merchantOrderId: orderId })
|
|
if (!resp) {
|
|
throw new NotFoundException("order id not found")
|
|
}
|
|
const result = await this.telebirrProvider.queryStatus(resp.merchantOrderId)
|
|
|
|
if (result.status === ProviderPaymentStatus.SUCCEEDED) {
|
|
await this.datasource.transaction(async (mg) => {
|
|
await mg.update(PaymentEntity, { id: resp.id }, { status: "success" })
|
|
await mg.update(Booking, { id: resp.refId }, { paymentStatus: "PAID" })
|
|
})
|
|
if (resp.type === "booking") {
|
|
await this.bookingBatchService.ensurePaidBookingAllocated(resp.refId)
|
|
}
|
|
}
|
|
return {
|
|
status: result.status
|
|
}
|
|
}
|
|
|
|
findBookingById(id: string) {
|
|
return this.paymentRepo.findOneBy({ refId: id, type: "booking" })
|
|
}
|
|
|
|
formatIntentResponse(intent: PaymentEntity): InitiateResponseDto {
|
|
const clientAction =
|
|
intent.clientAction && typeof intent.clientAction === "object"
|
|
? (intent.clientAction as unknown as ClientAction)
|
|
: undefined;
|
|
const statusMap: Record<string, ProviderPaymentStatus> = {
|
|
"action-required": ProviderPaymentStatus.REQUIRES_ACTION,
|
|
"processing": ProviderPaymentStatus.PROCESSING,
|
|
"success": ProviderPaymentStatus.SUCCEEDED,
|
|
"failed": ProviderPaymentStatus.FAILED,
|
|
"canceled": ProviderPaymentStatus.CANCELLED,
|
|
"refunded": ProviderPaymentStatus.CANCELLED,
|
|
};
|
|
return {
|
|
intentId: intent.id,
|
|
status: statusMap[intent.status] ?? ProviderPaymentStatus.PROCESSING,
|
|
clientAction,
|
|
merchantOrderId: intent.merchantOrderId ?? undefined,
|
|
};
|
|
}
|
|
}
|