refactor cart

This commit is contained in:
2025-12-15 23:07:50 +03:30
parent fea123f61e
commit 3e4a4eebe4
13 changed files with 494 additions and 414 deletions
+167
View File
@@ -0,0 +1,167 @@
import { HttpService } from "@nestjs/axios";
import { Inject, Injectable, InternalServerErrorException, Logger } from "@nestjs/common";
import { AxiosError } from "axios";
import { catchError, firstValueFrom, throwError } from "rxjs";
import { PaymentMessage } from "../../../common/enums/message.enum";
import { IZarinpalConfig } from "../../../configs/zarinpal.config";
import { ZARINPAL_CONFIG } from "../constants";
import { GatewayEnum } from "../enums/gateway.enum";
import {
IPaymentGateway,
IPaymentVerifyParams,
IProcessPaymentParams,
ZarinPalPGNewArgs,
ZarinPalPGNewRequestData,
ZarinPalPGVerifyData,
} from "../interfaces/IPayment";
@Injectable()
export class ZarinpalGateway implements IPaymentGateway {
private readonly IPG_TYPE = "payment";
private readonly logger = new Logger(ZarinpalGateway.name);
private readonly gatewayApiUrl: string;
private readonly requestHeader: Record<string, string> = { "Content-Type": "application/json", Accept: "application/json" };
constructor(
@Inject(ZARINPAL_CONFIG) private readonly config: IZarinpalConfig,
private readonly httpService: HttpService,
) {
this.gatewayApiUrl = `https://${this.IPG_TYPE}.zarinpal.com`;
}
async processPayment(processParams: IProcessPaymentParams) {
try {
const purchaseData: ZarinPalPGNewArgs = {
merchant_id: this.config.merchantId,
amount: processParams.amount,
callback_url: `${this.config.callBackUrl}/${GatewayEnum.ZARINPAL}`,
description: processParams.description,
currency: "IRT",
metadata: { email: processParams.email, mobile: processParams.mobile },
};
this.logger.log(`Processing payment request:`, {
merchant_id: this.config.merchantId,
amount: processParams.amount,
callback_url: `${this.config.callBackUrl}/${GatewayEnum.ZARINPAL}`,
description: processParams.description,
});
const { data } = await firstValueFrom(
this.httpService
.post<ZarinPalPGNewRequestData>(`${this.gatewayApiUrl}/pg/v4/payment/request.json`, purchaseData, {
headers: this.requestHeader,
})
.pipe(
catchError((err: AxiosError) => {
this.logger.error(`Payment request failed: ${err.message}`, err.stack);
return throwError(() => new InternalServerErrorException(PaymentMessage.ERROR_IN_PROCESS_PAYMENT));
}),
),
);
// Check for errors in response
if (data.errors && data.errors.length > 0) {
this.logger.error(`Zarinpal payment request error:`, data.errors);
throw new InternalServerErrorException(PaymentMessage.ERROR_IN_PROCESS_PAYMENT);
}
// Validate response data
if (!data.data?.authority) {
this.logger.error(`Invalid response from Zarinpal:`, data);
throw new InternalServerErrorException(PaymentMessage.ERROR_IN_PROCESS_PAYMENT);
}
this.logger.log(`Payment request successful - Authority: ${data.data.authority}`);
return {
redirectUrl: `${this.gatewayApiUrl}/pg/StartPay/${data.data.authority}`,
message: data.data.message,
reference: data.data.authority,
};
} catch (error) {
this.logger.error(`Payment processing error:`, error);
throw new InternalServerErrorException(PaymentMessage.ERROR_IN_PROCESS_PAYMENT);
}
}
async verifyPayment(verifyParams: IPaymentVerifyParams) {
const verifyData = {
merchant_id: this.config.merchantId,
amount: Number(verifyParams.amount),
authority: verifyParams.reference,
};
this.logger.log(`Verifying payment:`, {
merchant_id: this.config.merchantId,
amount: Number(verifyParams.amount),
authority: verifyParams.reference,
});
try {
const { data } = await firstValueFrom(
this.httpService
.post<ZarinPalPGVerifyData>(`${this.gatewayApiUrl}/pg/v4/payment/verify.json`, verifyData, {
headers: this.requestHeader,
})
.pipe(
catchError((err: AxiosError) => {
this.logger.error(`Verification request failed: ${err.message}`, err.stack);
// If we have error response data from Zarinpal, extract it
if (err.response?.data) {
const errorData = err.response.data as ZarinPalPGVerifyData;
this.logger.error(`Zarinpal error response:`, errorData);
// Return the Zarinpal error response if it has the expected structure
if (errorData.errors) {
return throwError(() => errorData);
}
}
return throwError(() => new InternalServerErrorException(PaymentMessage.ERROR_IN_VERIFY_PAYMENT));
}),
),
);
// Log the verification result
if (data.data) {
this.logger.log(`Verification response - Code: ${data.data.code}, RefID: ${data.data.ref_id || "N/A"}`);
} else if (data.errors && data.errors.length > 0) {
this.logger.warn(`Verification error - Code: ${data.errors[0].code}, Message: ${data.errors[0].message}`);
}
// Return the complete Zarinpal response - let the service handle the business logic
return {
code: data.data?.code || data.errors?.[0]?.code || -1,
message: data.data?.message || data.errors?.[0]?.message || "Unknown error",
ref_id: data.data?.ref_id || 0,
card_hash: data.data?.card_hash || "",
card_pan: data.data?.card_pan || "",
fee_type: data.data?.fee_type || "",
fee: data.data?.fee || 0,
};
} catch (error) {
// If it's a Zarinpal error response, extract the error code and return it
if (error && typeof error === "object" && "errors" in error) {
const zarinpalError = error as ZarinPalPGVerifyData;
const firstError = zarinpalError.errors[0];
this.logger.warn(`Zarinpal verification error - Code: ${firstError.code}, Message: ${firstError.message}`);
return {
code: firstError.code,
message: firstError.message,
ref_id: 0,
card_hash: "",
card_pan: "",
fee_type: "",
fee: 0,
};
}
this.logger.error(`Payment verification error:`, error);
throw new InternalServerErrorException(PaymentMessage.ERROR_IN_VERIFY_PAYMENT);
}
}
}