mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
@@ -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") ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user