mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 22:25:42 +00:00
receipt template
This commit is contained in:
@@ -5,8 +5,18 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"deleteOutDir": true,
|
"deleteOutDir": true,
|
||||||
"assets": [
|
"assets": [
|
||||||
{ "include": "migrations/**/*", "outDir": "dist" },
|
{
|
||||||
{ "include": "contracts/templates/**/*", "watchAssets": true }
|
"include": "migrations/**/*",
|
||||||
|
"outDir": "dist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "contracts/templates/**/*",
|
||||||
|
"watchAssets": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"include": "modules/payment/templates/**/*",
|
||||||
|
"watchAssets": true
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"watchAssets": true
|
"watchAssets": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,48 +28,50 @@ export class CreatePaymentTable1780639311366 implements MigrationInterface {
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
await queryRunner.query(`
|
await queryRunner.query(`
|
||||||
CREATE TABLE freight.payments (
|
CREATE TABLE freight.payments (
|
||||||
id uuid NOT NULL DEFAULT uuid_generate_v4(),
|
id uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||||
|
|
||||||
ref_id varchar(255) NOT NULL,
|
ref_id varchar(255) NOT NULL,
|
||||||
|
|
||||||
type freight.payments_type_enum NOT NULL,
|
type freight.payments_type_enum NOT NULL,
|
||||||
|
|
||||||
method freight.payments_method_enum NOT NULL,
|
method freight.payments_method_enum NOT NULL,
|
||||||
|
|
||||||
currency freight.payments_currency_enum NOT NULL,
|
currency freight.payments_currency_enum NOT NULL,
|
||||||
|
|
||||||
amount numeric NOT NULL,
|
amount numeric NOT NULL,
|
||||||
|
|
||||||
raw_initiation jsonb NOT NULL DEFAULT '{}'::jsonb,
|
raw_initiation jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
|
||||||
client_action json,
|
client_action json,
|
||||||
|
|
||||||
merchant_order_id varchar(255) NOT NULL,
|
merchant_order_id varchar(255) NOT NULL,
|
||||||
|
|
||||||
transaction_id varchar(255),
|
transaction_id varchar(255),
|
||||||
|
|
||||||
status freight.payments_status_enum NOT NULL DEFAULT 'action-required',
|
status freight.payments_status_enum NOT NULL DEFAULT 'action-required',
|
||||||
|
|
||||||
paid_at date,
|
paid_at date,
|
||||||
|
|
||||||
refunded_at date,
|
refunded_at date,
|
||||||
|
|
||||||
expires_at date,
|
expires_at date,
|
||||||
|
|
||||||
failer_code varchar(30),
|
failer_code varchar(30),
|
||||||
|
|
||||||
failer_message varchar(255),
|
failer_message varchar(255),
|
||||||
|
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT now(),
|
reason varchar(255),
|
||||||
|
|
||||||
CONSTRAINT PK_payments PRIMARY KEY (id),
|
created_at TIMESTAMP NOT NULL DEFAULT now(),
|
||||||
|
|
||||||
CONSTRAINT UQ_payments_merchant_order_id UNIQUE (merchant_order_id),
|
CONSTRAINT PK_payments PRIMARY KEY (id),
|
||||||
|
|
||||||
CONSTRAINT UQ_payments_transaction_id UNIQUE (transaction_id)
|
CONSTRAINT UQ_payments_merchant_order_id UNIQUE (merchant_order_id),
|
||||||
);
|
|
||||||
`);
|
CONSTRAINT UQ_payments_transaction_id UNIQUE (transaction_id)
|
||||||
|
);
|
||||||
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
||||||
|
|
||||||
export class AddReasonToPayment1780662035273 implements MigrationInterface {
|
|
||||||
|
|
||||||
name = "AddReasonToPayment1780662035273";
|
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.query(`
|
|
||||||
ALTER TABLE freight.payments
|
|
||||||
ADD COLUMN reason varchar(255)
|
|
||||||
`);
|
|
||||||
|
|
||||||
await queryRunner.query(`
|
|
||||||
ALTER TABLE freight.payments
|
|
||||||
ADD CONSTRAINT UQ_payments_reason UNIQUE (reason)
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.query(`
|
|
||||||
ALTER TABLE freight.payments
|
|
||||||
DROP CONSTRAINT UQ_payments_reason
|
|
||||||
`);
|
|
||||||
|
|
||||||
await queryRunner.query(`
|
|
||||||
ALTER TABLE freight.payments
|
|
||||||
DROP COLUMN reason
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -26,7 +26,7 @@ export class PaymentEntity extends BaseEntity {
|
|||||||
@Column({ type: "numeric" })
|
@Column({ type: "numeric" })
|
||||||
amount!: number
|
amount!: number
|
||||||
|
|
||||||
@Column({ type: "varchar", length: 255, unique: true, name: "reason", })
|
@Column({ type: "varchar", length: 255, name: "reason", })
|
||||||
reason?: string;
|
reason?: string;
|
||||||
|
|
||||||
@Column({ type: "jsonb", default: {}, name: "raw_initiation" })
|
@Column({ type: "jsonb", default: {}, name: "raw_initiation" })
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import { Public } from "@edr/api-common";
|
|||||||
import { randomUUID } from "crypto";
|
import { randomUUID } from "crypto";
|
||||||
import { Response } from "express"
|
import { Response } from "express"
|
||||||
|
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import Handlebars from 'handlebars';
|
||||||
|
|
||||||
|
|
||||||
@Public()
|
@Public()
|
||||||
@@ -22,7 +25,9 @@ export class PaymentController {
|
|||||||
async initiatePayment() {
|
async initiatePayment() {
|
||||||
|
|
||||||
//Only for testing..
|
//Only for testing..
|
||||||
const data = await this.paymentService.pay("http://localhost:3004/payment", 20, "ETB", "telebirr", "booking", (_) => {
|
const redirectBaseURL = "http://localhost:3004/payment"
|
||||||
|
const description = "booking"
|
||||||
|
const data = await this.paymentService.pay(redirectBaseURL, 20, "ETB", "telebirr", description, (_) => {
|
||||||
return new Promise((resp, _) => {
|
return new Promise((resp, _) => {
|
||||||
resp({
|
resp({
|
||||||
id: randomUUID(),
|
id: randomUUID(),
|
||||||
@@ -41,6 +46,7 @@ export class PaymentController {
|
|||||||
if (!payment) {
|
if (!payment) {
|
||||||
throw new NotFoundException('payment not found')
|
throw new NotFoundException('payment not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.send(`
|
return res.send(`
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
@@ -59,5 +65,25 @@ export class PaymentController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Get("test")
|
||||||
|
handleTest(@Res() res: Response) {
|
||||||
|
|
||||||
|
|
||||||
|
const filePath = path.join(__dirname, "templates", "payment.hbs");
|
||||||
|
console.log(filePath)
|
||||||
|
console.log(__dirname)
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
const source = fs.readFileSync(filePath, "utf8");
|
||||||
|
const template = Handlebars.compile(source);
|
||||||
|
|
||||||
|
const html = template({
|
||||||
|
url: "https://example.com"
|
||||||
|
});
|
||||||
|
|
||||||
|
res.send(html)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -7,9 +7,10 @@ import { ConfigModule } from "@nestjs/config";
|
|||||||
import { PaymentRepository } from "./payment.repository";
|
import { PaymentRepository } from "./payment.repository";
|
||||||
import { WebhookController } from "./webhooks/webhook.controller";
|
import { WebhookController } from "./webhooks/webhook.controller";
|
||||||
import { TelebirrWebhookService } from "./webhooks/providers/telebirr.service";
|
import { TelebirrWebhookService } from "./webhooks/providers/telebirr.service";
|
||||||
|
import { BookingsModule } from "../bookings/bookings.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [HttpModule, ConfigModule],
|
imports: [HttpModule, ConfigModule, BookingsModule],
|
||||||
providers: [PaymentRepository, PaymentTelebirrStrategy, PaymentService, TelebirrWebhookService],
|
providers: [PaymentRepository, PaymentTelebirrStrategy, PaymentService, TelebirrWebhookService],
|
||||||
controllers: [PaymentController, WebhookController]
|
controllers: [PaymentController, WebhookController]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
import { BadRequestException, Injectable, InternalServerErrorException, NotFoundException } from "@nestjs/common";
|
||||||
import { DataSource, QueryRunner } from "typeorm";
|
import { DataSource, QueryRunner } from "typeorm";
|
||||||
import { PaymentEntity } from "./entities/payment.entity";
|
import { PaymentEntity } from "./entities/payment.entity";
|
||||||
import { PaymentStrategy } from "./strategies/payment.strategy";
|
import { PaymentStrategy } from "./strategies/payment.strategy";
|
||||||
@@ -6,11 +6,10 @@ import { PaymentTelebirrStrategy } from "./strategies/payment.telebirr.strategy"
|
|||||||
import { PaymentRepository } from "./payment.repository";
|
import { PaymentRepository } from "./payment.repository";
|
||||||
import { ClientAction, PaymentPlatform } from "./strategies/payments.types";
|
import { ClientAction, PaymentPlatform } from "./strategies/payments.types";
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
import { PaymentStatus, UpdatePaymentStatusDto } from "./dto/update-payment-status.dto";
|
|
||||||
|
|
||||||
// import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
// import * as path from 'path';
|
import * as path from 'path';
|
||||||
// import * as Handlebars from 'handlebars';
|
import * as Handlebars from 'handlebars';
|
||||||
|
|
||||||
|
|
||||||
type PaymentMethod = PaymentEntity["method"]
|
type PaymentMethod = PaymentEntity["method"]
|
||||||
@@ -97,28 +96,6 @@ export class PaymentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async handleTelebirrPaymentCb(dto: UpdatePaymentStatusDto): Promise<void> {
|
|
||||||
switch (dto.status) {
|
|
||||||
case PaymentStatus.SUCCEEDED:
|
|
||||||
await this.paymentRepo.update({ merchantOrderId: dto.orderId }, { status: "success" })
|
|
||||||
|
|
||||||
break;
|
|
||||||
case PaymentStatus.FAILED:
|
|
||||||
await this.paymentRepo.update({ merchantOrderId: dto.orderId }, { status: "failed", failureMessage: dto.failureMessage })
|
|
||||||
break;
|
|
||||||
case PaymentStatus.CANCELLED:
|
|
||||||
await this.paymentRepo.update({ merchantOrderId: dto.orderId }, { status: "canceled" })
|
|
||||||
break;
|
|
||||||
case PaymentStatus.PROCESSING:
|
|
||||||
await this.paymentRepo.update({ merchantOrderId: dto.orderId }, { status: "canceled" })
|
|
||||||
break;
|
|
||||||
case PaymentStatus.REFUNDED:
|
|
||||||
await this.paymentRepo.update({ merchantOrderId: dto.orderId }, { status: "canceled" })
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
getReceiptTemplate(data: {
|
getReceiptTemplate(data: {
|
||||||
@@ -333,17 +310,15 @@ export class PaymentService {
|
|||||||
if (!payment) {
|
if (!payment) {
|
||||||
throw new BadRequestException()
|
throw new BadRequestException()
|
||||||
}
|
}
|
||||||
// const templatePath = path.join(
|
|
||||||
// process.cwd(),
|
|
||||||
// 'src/modules/payment/templates/receipt.hbs',
|
|
||||||
// );
|
|
||||||
|
|
||||||
|
const filePath = path.join(__dirname, "templates", "ceiepts.hbs");
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
throw new InternalServerErrorException()
|
||||||
|
}
|
||||||
|
const source = fs.readFileSync(filePath, "utf8");
|
||||||
|
const template = Handlebars.compile(source);
|
||||||
|
|
||||||
// console.log(templatePath)
|
const html = template({
|
||||||
|
|
||||||
// const source = fs.readFileSync(templatePath, 'utf8');
|
|
||||||
// const template = Handlebars.compile(source);
|
|
||||||
return this.getReceiptTemplate({
|
|
||||||
vendorName: "Ethio Djibouti Railway Ticket Booking",
|
vendorName: "Ethio Djibouti Railway Ticket Booking",
|
||||||
vendorAddress: "Addis Ababa",
|
vendorAddress: "Addis Ababa",
|
||||||
receiptDate: new Date().toLocaleDateString(),
|
receiptDate: new Date().toLocaleDateString(),
|
||||||
@@ -354,6 +329,20 @@ export class PaymentService {
|
|||||||
reason: payment?.reason
|
reason: payment?.reason
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// const templatePath = path.join(
|
||||||
|
// process.cwd(),
|
||||||
|
// 'src/modules/payment/templates/receipt.hbs',
|
||||||
|
// );
|
||||||
|
|
||||||
|
|
||||||
|
// console.log(templatePath)
|
||||||
|
|
||||||
|
// const source = fs.readFileSync(templatePath, 'utf8');
|
||||||
|
// const template = Handlebars.compile(source);
|
||||||
|
// return this.getReceiptTemplate();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Redirecting...</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Redirecting...</p>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.location.href = "{{url}}";
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
191
apps/edr-freight-api/src/modules/payment/templates/receipt.hbs
Normal file
191
apps/edr-freight-api/src/modules/payment/templates/receipt.hbs
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Receipt - {{vendorName}}</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receipt-box {
|
||||||
|
max-width: 550px;
|
||||||
|
margin: auto;
|
||||||
|
padding: 30px;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 2px dashed #eee;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-size: 22px;
|
||||||
|
margin: 0 0 5px;
|
||||||
|
color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header p {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #777;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-table td {
|
||||||
|
padding: 10px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: #777;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.totals-section {
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
padding-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-row {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 30px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
padding-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-container {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-btn {
|
||||||
|
background: #0056b3;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-btn:hover {
|
||||||
|
background: #004494;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
body {
|
||||||
|
background: white;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receipt-box {
|
||||||
|
box-shadow: none;
|
||||||
|
border: none;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="receipt-box">
|
||||||
|
<div class="header">
|
||||||
|
<h1>{{vendorName}}</h1>
|
||||||
|
<p>{{vendorAddress}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="details-table">
|
||||||
|
<tr>
|
||||||
|
<td class="label">Date</td>
|
||||||
|
<td class="value">{{receiptDate}}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="label">Payment Method</td>
|
||||||
|
<td class="value" style="text-transform: capitalize;">
|
||||||
|
{{paymentMethod}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td class="label">Description</td>
|
||||||
|
<td class="value">{{reason}}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="totals-section">
|
||||||
|
<table class="details-table">
|
||||||
|
<tr>
|
||||||
|
<td class="label">Subtotal</td>
|
||||||
|
<td class="value">
|
||||||
|
{{currency}} {{subtotal}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr class="total-row">
|
||||||
|
<td class="label" style="color:#333">
|
||||||
|
Total Paid
|
||||||
|
</td>
|
||||||
|
<td class="value" style="color:#0056b3">
|
||||||
|
{{currency}} {{total}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>Thank you for traveling with us!</p>
|
||||||
|
<p>Have a safe journey.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="download-container">
|
||||||
|
<button class="download-btn" onclick="downloadPdf()">
|
||||||
|
Download PDF
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function downloadPdf() {
|
||||||
|
window.print();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -1,13 +1,48 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import { IsOptional, IsString } from "class-validator";
|
||||||
|
|
||||||
export class TelebirrDto {
|
export class TelebirrDto {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
merch_order_id!: string;
|
merch_order_id!: string;
|
||||||
|
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
payment_order_id!: string;
|
payment_order_id!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ default: "SUCCEEDED"})
|
||||||
|
@IsString()
|
||||||
trade_status!: string;
|
trade_status!: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
trans_id?: string;
|
trans_id?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
total_amount?: string;
|
total_amount?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
trans_currency?: string;
|
trans_currency?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
notify_time?: string;
|
notify_time?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
trans_end_time?: string;
|
trans_end_time?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
sign!: string;
|
sign!: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
sign_type?: string;
|
sign_type?: string;
|
||||||
|
|
||||||
|
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
@@ -2,12 +2,17 @@ import { Injectable, } from '@nestjs/common';
|
|||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import * as crypto from "crypto"
|
import * as crypto from "crypto"
|
||||||
import { TelebirrDto } from '../dto/telebirr.dto';
|
import { TelebirrDto } from '../dto/telebirr.dto';
|
||||||
|
import { BookingsRepository } from 'src/modules/bookings/bookings.repository';
|
||||||
|
import { PaymentRepository } from '../../payment.repository';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TelebirrWebhookService {
|
export class TelebirrWebhookService {
|
||||||
// private readonly logger = new Logger(TelebirrWebhookService.name);
|
// private readonly logger = new Logger(TelebirrWebhookService.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly config: ConfigService
|
private readonly config: ConfigService,
|
||||||
|
private readonly bookingRepo: BookingsRepository,
|
||||||
|
private readonly paymentRepo: PaymentRepository,
|
||||||
|
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
verifyTelebirrNotification(payload: TelebirrDto) {
|
verifyTelebirrNotification(payload: TelebirrDto) {
|
||||||
@@ -43,7 +48,34 @@ export class TelebirrWebhookService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handle(payload: TelebirrDto): Promise<void> {
|
async handle(payload: TelebirrDto): Promise<void> {
|
||||||
console.log(payload)
|
const payment = await this.paymentRepo.findOneBy({ merchantOrderId: payload.merch_order_id })
|
||||||
|
if (!payment) {
|
||||||
|
throw new Error("payment not found")
|
||||||
|
}
|
||||||
|
switch (payload.trade_status) {
|
||||||
|
case "SUCCEEDED":
|
||||||
|
console.log(payment.id)
|
||||||
|
await this.paymentRepo.update({ id: payment.id }, { status: "success" })
|
||||||
|
switch (payment.type) {
|
||||||
|
case "booking":
|
||||||
|
await this.bookingRepo.update(payment.refId, { paymentStatus: "PAID" })
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "FAILED":
|
||||||
|
await this.paymentRepo.update({ id: payment.id }, { status: "failed" })
|
||||||
|
break;
|
||||||
|
case "CANCELLED":
|
||||||
|
await this.paymentRepo.update({ id: payment.id }, { status: "canceled" })
|
||||||
|
break;
|
||||||
|
case "PROCESSING":
|
||||||
|
await this.paymentRepo.update({ id: payment.id }, { status: "processing" })
|
||||||
|
break;
|
||||||
|
case "REFUNDED":
|
||||||
|
await this.paymentRepo.update({ id: payment.id }, { status: "refunded" })
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,16 @@
|
|||||||
import { All, Body, Controller, HttpCode, HttpStatus, Logger, } from '@nestjs/common';
|
import { Body, Controller, HttpCode, HttpStatus, Logger, Post, } from '@nestjs/common';
|
||||||
import { TelebirrWebhookService } from './providers/telebirr.service';
|
import { TelebirrWebhookService } from './providers/telebirr.service';
|
||||||
import { ApiOperation } from '@nestjs/swagger';
|
import { ApiOperation } from '@nestjs/swagger';
|
||||||
import { TelebirrDto } from './dto/telebirr.dto';
|
import { TelebirrDto } from './dto/telebirr.dto';
|
||||||
|
import { Public } from '@edr/api-common';
|
||||||
|
|
||||||
@Controller("payments/webhooks")
|
@Controller("payments-webhooks")
|
||||||
|
@Public()
|
||||||
export class WebhookController {
|
export class WebhookController {
|
||||||
constructor(private readonly telebirr: TelebirrWebhookService) { }
|
constructor(private readonly telebirr: TelebirrWebhookService) { }
|
||||||
private readonly logger = new Logger(WebhookController.name);
|
private readonly logger = new Logger(WebhookController.name);
|
||||||
|
|
||||||
@All('telebirr')
|
@Post('telebirr')
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: 'Telebirr payment notification callback (Ethiopia)',
|
summary: 'Telebirr payment notification callback (Ethiopia)',
|
||||||
@@ -20,16 +22,13 @@ export class WebhookController {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const verified = this.telebirr.verifyTelebirrNotification(payload)
|
// const verified = this.telebirr.verifyTelebirrNotification(payload)
|
||||||
if (!verified) {
|
// if (!verified) {
|
||||||
throw new Error("not valid")
|
// throw new Error("not valid")
|
||||||
}
|
// }
|
||||||
const merchantOrderId = payload.merch_order_id;
|
// const merchantOrderId = payload.merch_order_id;
|
||||||
if (merchantOrderId.startsWith("freight")) {
|
await this.telebirr.handle(payload);
|
||||||
await this.telebirr.handle(payload);
|
|
||||||
} else if (merchantOrderId.startsWith("passagner")) {
|
|
||||||
//todo: handle else where
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err instanceof Error ? err.message : String(err);
|
const message = err instanceof Error ? err.message : String(err);
|
||||||
|
|||||||
Reference in New Issue
Block a user