Merge pull request #125 from Tria-plc/freigh/fix/dmoney

fix
This commit is contained in:
Eyob T.
2026-06-09 16:48:13 +03:00
committed by GitHub

View File

@@ -1,6 +1,6 @@
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { HttpService } from '@nestjs/axios';
import { Injectable, Logger } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { HttpService } from "@nestjs/axios";
import {
PaymentProvider,
ProviderInitiationInput,
@@ -8,10 +8,10 @@ import {
ProviderStatus,
ProviderPaymentStatus,
ProviderMethod,
} from '@edr/types';
import { AxiosError, AxiosRequestConfig } from 'axios';
import { firstValueFrom } from 'rxjs';
import * as crypto from 'node:crypto';
} from "@edr/types";
import { AxiosError, AxiosRequestConfig } from "axios";
import { firstValueFrom } from "rxjs";
import * as crypto from "node:crypto";
interface DMoneyAuthResponse {
token: string;
@@ -56,9 +56,11 @@ export class DMoneyProvider implements PaymentProvider {
constructor(
private readonly config: ConfigService,
private readonly http: HttpService,
) {}
) { }
async initiate(input: ProviderInitiationInput): Promise<ProviderInitiationResult> {
async initiate(
input: ProviderInitiationInput,
): Promise<ProviderInitiationResult> {
const token = await this.getFabricToken();
const amount = (input.amountMinor / 100).toFixed(2);
const timestamp = new Date().toISOString();
@@ -95,8 +97,11 @@ export class DMoneyProvider implements PaymentProvider {
return {
providerOrderId: response.orderId,
clientAction: response.checkoutUrl
? { type: 'REDIRECT', url: response.checkoutUrl }
: { type: 'REDIRECT', url: `${this.baseUrl}/checkout/${response.orderId}` },
? { type: "REDIRECT", url: response.checkoutUrl }
: {
type: "REDIRECT",
url: `${this.baseUrl}/checkout/${response.orderId}`,
},
expiresAt,
rawInitiation: {
request: this.sanitize(requestBody),
@@ -106,7 +111,7 @@ export class DMoneyProvider implements PaymentProvider {
}
async queryStatus(merchantOrderId: string): Promise<ProviderStatus> {
const token = await this.authenticate();
const token = await this.getFabricToken();
const timestamp = new Date().toISOString();
const signature = this.signRequest({
merchantId: this.merchantId,
@@ -130,14 +135,15 @@ export class DMoneyProvider implements PaymentProvider {
return {
status: mapped,
providerTxnId: response.transactionId,
failureCode: mapped === ProviderPaymentStatus.FAILED ? response.status : undefined,
failureCode:
mapped === ProviderPaymentStatus.FAILED ? response.status : undefined,
rawResponse: response as unknown as Record<string, unknown>,
};
}
verifyWebhookSignature(payload: Record<string, unknown>): boolean {
const { signature, ...data } = payload;
if (!signature || typeof signature !== 'string') return false;
if (!signature || typeof signature !== "string") return false;
const expectedSignature = this.signRequest(data);
return crypto.timingSafeEqual(
@@ -152,17 +158,17 @@ export class DMoneyProvider implements PaymentProvider {
private mapStatus(status: string): ProviderPaymentStatus {
switch (status?.toUpperCase()) {
case 'SUCCESS':
case 'COMPLETED':
case "SUCCESS":
case "COMPLETED":
return ProviderPaymentStatus.SUCCEEDED;
case 'FAILED':
case 'REJECTED':
case 'EXPIRED':
case 'CANCELLED':
case "FAILED":
case "REJECTED":
case "EXPIRED":
case "CANCELLED":
return ProviderPaymentStatus.FAILED;
case 'PENDING':
case "PENDING":
return ProviderPaymentStatus.REQUIRES_ACTION;
case 'PROCESSING':
case "PROCESSING":
return ProviderPaymentStatus.PROCESSING;
default:
return ProviderPaymentStatus.PROCESSING;
@@ -178,7 +184,9 @@ export class DMoneyProvider implements PaymentProvider {
);
if (!response.token) {
throw new Error(`DMoney authentication failed: ${JSON.stringify(response)}`);
throw new Error(
`DMoney authentication failed: ${JSON.stringify(response)}`,
);
}
return response.token;
@@ -186,22 +194,24 @@ export class DMoneyProvider implements PaymentProvider {
private signRequest(data: Record<string, unknown>): string {
const sortedKeys = Object.keys(data).sort();
const signString = sortedKeys
.map((key) => `${key}=${data[key]}`)
.join('&');
const signString = sortedKeys.map((key) => `${key}=${data[key]}`).join("&");
return crypto
.createHmac('sha256', this.secretKey)
.createHmac("sha256", this.secretKey)
.update(signString)
.digest('hex');
.digest("hex");
}
private async postJson<T>(url: string, body: unknown, token?: string): Promise<T> {
private async postJson<T>(
url: string,
body: unknown,
token?: string,
): Promise<T> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
"Content-Type": "application/json",
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
headers["Authorization"] = `Bearer ${token}`;
}
const config: AxiosRequestConfig = {
@@ -212,7 +222,9 @@ export class DMoneyProvider implements PaymentProvider {
const started = Date.now();
try {
const res = await firstValueFrom(this.http.post<T>(url, body, config));
this.logger.debug(`DMoney POST ${url} status=${res.status} latency=${Date.now() - started}ms`);
this.logger.debug(
`DMoney POST ${url} status=${res.status} latency=${Date.now() - started}ms`,
);
return res.data;
} catch (err) {
if (err instanceof AxiosError) {
@@ -220,7 +232,9 @@ export class DMoneyProvider implements PaymentProvider {
`DMoney POST ${url} failed: status=${err.response?.status} body=${JSON.stringify(err.response?.data)}`,
);
} else {
this.logger.error(`DMoney POST ${url} threw: ${err instanceof Error ? err.message : err}`);
this.logger.error(
`DMoney POST ${url} threw: ${err instanceof Error ? err.message : err}`,
);
}
throw err;
}
@@ -232,21 +246,21 @@ export class DMoneyProvider implements PaymentProvider {
}
private get baseUrl(): string {
return this.config.get<string>('dmoney.baseUrl') ?? '';
return this.config.get<string>("dmoney.baseUrl") ?? "";
}
private get merchantId(): string {
return this.config.get<string>('dmoney.merchantId') ?? '';
return this.config.get<string>("dmoney.merchantId") ?? "";
}
private get appSecret(): string {
return this.config.get<string>('dmoney.appSecret') ?? '';
return this.config.get<string>("dmoney.appSecret") ?? "";
}
private get secretKey(): string {
return this.config.get<string>('dmoney.secretKey') ?? '';
return this.config.get<string>("dmoney.secretKey") ?? "";
}
private get notifyUrl(): string {
return this.config.get<string>('dmoney.notifyUrl') ?? '';
return this.config.get<string>("dmoney.notifyUrl") ?? "";
}
private get returnUrl(): string {
return this.config.get<string>('dmoney.returnUrl') ?? '';
return this.config.get<string>("dmoney.returnUrl") ?? "";
}
}