refactor : payment

This commit is contained in:
2026-01-03 17:35:18 +03:30
parent 60f7b46779
commit a65ad8b5ff
15 changed files with 159 additions and 224 deletions
+1 -3
View File
@@ -67,9 +67,8 @@ import { FaqService } from "../modules/faq/faq.service";
import { FineService } from "../modules/fine/fine.service";
import { FineRepo, createFineRepo } from "../modules/fine/repository/fine.repository";
import { FineRuleRepo, createFineRuleRepo } from "../modules/fine/repository/fineRule.repository";
import { AsanPardakhtGateway } from "../modules/IPG/gateways/asanpardakht";
import { ZarinPalGateway } from "../modules/IPG/gateways/zarinpal";
import { SepGateway } from "../modules/IPG/gateways/sep";
import { ZarinPalGateway } from "../modules/IPG/gateways/zarinpal";
import { PaymentGateway } from "../modules/IPG/PaymentGateway";
import { JobService } from "../modules/job/job.service";
import { JobRepo, createJobRepo } from "../modules/job/repository/job.repo";
@@ -159,7 +158,6 @@ const containerModules = new AsyncContainerModule(async (bind) => {
// bind(IOCTYPES.PassportAuth).to(PassportAuth).inSingletonScope();
bind<PaymentGateway>(IOCTYPES.PaymentGateway).to(PaymentGateway).inSingletonScope();
bind<ZarinPalGateway>(IOCTYPES.ZarinPalGateway).to(ZarinPalGateway).inSingletonScope();
bind<AsanPardakhtGateway>(IOCTYPES.AsanPardakhtGateway).to(AsanPardakhtGateway).inSingletonScope();
bind<SepGateway>(IOCTYPES.SEPGateway).to(SepGateway).inSingletonScope();
// #region services
bind<CategoryService>(IOCTYPES.CategoryService).to(CategoryService).inSingletonScope();
-1
View File
@@ -139,7 +139,6 @@ export const IOCTYPES = {
// #endregion
Logger: Symbol.for("Logger"),
ZarinPalGateway: Symbol.for("ZarinPalGateway"),
AsanPardakhtGateway: Symbol.for("AsanPardakhtGateway"),
SEPGateway: Symbol.for("SEPGateway"),
PaymentGateway: Symbol.for("PaymentGateway"),
};
+1
View File
@@ -197,6 +197,7 @@ export const enum PaymentMessage {
CartPaymentDesc = "خرید کالا:",
PaymentNotBelongToUser = "پرداخت برای این کاربر نمی‌باشد",
PaymentRequestFailed = "درخواست پرداخت موفقیت آمیز نبود",
RefNumIsAlreadyUsed = "این شماره رسید قبلا برای تایید استفاده شده .",
}
export const enum CouponMessage {
-1
View File
@@ -1,6 +1,5 @@
export enum GatewayProvider {
Zarinpal = "Zarinpal",
Asanpardakht = "asanPardakht",
SEP = "SEP",
}
+28 -38
View File
@@ -1,59 +1,49 @@
import { inject, injectable } from "inversify";
import { AsanPardakhtGateway } from "./gateways/asanpardakht";
import { ZarinPalGateway } from "./gateways/zarinpal";
import { SepGateway } from "./gateways/sep";
import { IPaymentGateway, NewPaymentData, VerifyPaymentData } from "./interface/IPG";
import { ZarinPalGateway } from "./gateways/zarinpal";
import { IPaymentGateway, NewPaymentData, VerifyPaymentData, IGateway } from "./interface/IPG";
import { GatewayProvider } from "../../common/enums/payment.enum";
import { BadRequestError } from "../../core/app/app.errors";
import { IOCTYPES } from "../../IOC/ioc.types";
@injectable()
class PaymentGateway implements IPaymentGateway {
@inject(IOCTYPES.ZarinPalGateway) zarinPalGateway: ZarinPalGateway;
@inject(IOCTYPES.AsanPardakhtGateway) asanpardakhtGateway: AsanPardakhtGateway;
@inject(IOCTYPES.SEPGateway) sepGateway: SepGateway;
@inject(IOCTYPES.ZarinPalGateway) private zarinPalGateway: ZarinPalGateway;
@inject(IOCTYPES.SEPGateway) private sepGateway: SepGateway;
private readonly gateways: Map<GatewayProvider, IGateway> = new Map();
constructor() {
this.gateways.set(GatewayProvider.Zarinpal, this.zarinPalGateway);
this.gateways.set(GatewayProvider.SEP, this.sepGateway);
}
public async requestPayment(gatewayType: GatewayProvider, data: NewPaymentData): Promise<any> {
switch (gatewayType) {
case GatewayProvider.Zarinpal:
return await this.zarinPalGateway.processPayment(data.amount, data.description, data.email, data.mobile, data.callbackPath);
case GatewayProvider.Asanpardakht:
//TODO:fix this
return this.asanpardakhtGateway.token();
case GatewayProvider.SEP:
return await this.sepGateway.processPayment(data.amount, data.description, data.email, data.mobile, data.callbackPath);
default:
throw new BadRequestError(`Payment provider ${gatewayType} is not supported`);
}
const gateway = this.getGateway(gatewayType);
return await gateway.processPayment(data.amount, data.description, data.email, data.mobile, data.callbackPath);
}
public async verifyPayment(gatewayType: GatewayProvider, data: VerifyPaymentData): Promise<any> {
switch (gatewayType) {
case GatewayProvider.Zarinpal:
return await this.zarinPalGateway.verifyPayment(data.authority, data.amount);
case GatewayProvider.Asanpardakht:
//TODO:fix this
return this.asanpardakhtGateway.time();
case GatewayProvider.SEP:
return await this.sepGateway.verifyPayment(data.authority, data.amount);
default:
throw new BadRequestError(`Payment provider ${gatewayType} is not supported`);
}
const gateway = this.getGateway(gatewayType);
const verifyData = gatewayType === GatewayProvider.SEP
? { refNum: data.refNum, amount: data.amount }
: { authority: data.authority, amount: data.amount };
return await gateway.verifyPayment(verifyData);
}
public async retryPayment(gatewayType: GatewayProvider, authority: string) {
switch (gatewayType) {
case GatewayProvider.Zarinpal:
return await this.zarinPalGateway.retryPayment(authority);
case GatewayProvider.Asanpardakht:
//TODO:fix this
return this.asanpardakhtGateway.token();
case GatewayProvider.SEP:
return await this.sepGateway.retryPayment(authority);
default:
throw new BadRequestError(`Payment provider ${gatewayType} is not supported`);
const gateway = this.getGateway(gatewayType);
return await gateway.retryPayment(authority);
}
private getGateway(gatewayType: GatewayProvider): IGateway {
const gateway = this.gateways.get(gatewayType);
if (!gateway) {
throw new BadRequestError(`Payment provider ${gatewayType} is not supported`);
}
return gateway;
}
}
-141
View File
@@ -1,141 +0,0 @@
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
import { injectable } from "inversify";
@injectable()
class AsanPardakhtGateway {
private invoiceId: string | null = null;
private amount: number | null = null;
private callBackURL = `${process.env.SITE_URL}/verify/ap`;
private readonly GatewayAuth = {
user: process.env.ASANPARDAKHT_USER,
password: process.env.ASANPARDAKHT_PASS,
merchant_id: process.env.ASANPARDAKHT_MERCHANT_ID,
merchantConfigID: process.env.ASANPARDAKHT_MERCHANT_CONFIG_ID,
};
// API endpoint URLs
private readonly TokenURL = "v1/Token";
private readonly TimeURL = "v1/Time";
private readonly TranResultURL = "v1/TranResult";
private readonly CardHashURL = "v1/CardHash";
private readonly SettlementURL = "v1/Settlement";
private readonly VerifyURL = "v1/Verify";
private readonly CancelURL = "v1/Cancel";
private readonly ReverseURL = "v1/Reverse";
private readonly GatewayApiURL = "https://ipgrest.asanpardakht.ir";
setInvoiceId(id: string): this {
this.invoiceId = id;
return this;
}
setAmount(amount: number): this {
this.amount = amount;
return this;
}
async time(): Promise<{ code: number; content: any }> {
return await this.callAPI("GET", this.TimeURL);
}
async TranResult(): Promise<{ code: number; content: any }> {
const res = await this.callAPI(
"GET",
`${this.TranResultURL}?${new URLSearchParams({
merchantConfigurationId: this.GatewayAuth.merchantConfigID!,
localInvoiceId: this.invoiceId!,
}).toString()}`,
);
return { code: res.code, content: JSON.parse(res.content) };
}
async CardHash(): Promise<{ code: number; content: any }> {
return await this.callAPI(
"GET",
`${this.CardHashURL}?${new URLSearchParams({
merchantConfigurationId: this.GatewayAuth.merchantConfigID!,
localInvoiceId: this.invoiceId!,
}).toString()}`,
);
}
async token(): Promise<{ code: number; content: any }> {
if (this.callBackURL) {
this.callBackURL += this.callBackURL.includes("?") ? "&" : "?";
}
const currentDate = new Date().toISOString().split("T").join(" ").split(".")[0].replace(/[-:]/g, "");
return await this.callAPI("POST", this.TokenURL, {
serviceTypeId: 1,
merchantConfigurationId: this.GatewayAuth.merchantConfigID!,
localInvoiceId: this.invoiceId!,
amountInRials: this.amount!,
localDate: currentDate,
callbackURL: `${this.callBackURL}${new URLSearchParams({ invoice: this.invoiceId! }).toString()}`,
paymentId: 0,
additionalData: "",
});
}
async settlement(transId: string): Promise<{ code: number; content: any }> {
return await this.callAPI("POST", this.SettlementURL, {
merchantConfigurationId: this.GatewayAuth.merchantConfigID!,
payGateTranId: transId,
});
}
async verify(transId: string): Promise<{ code: number; content: any }> {
return await this.callAPI("POST", this.VerifyURL, {
merchantConfigurationId: this.GatewayAuth.merchantConfigID!,
payGateTranId: transId,
});
}
async reverse(transId: string): Promise<{ code: number; content: any }> {
return await this.callAPI("POST", this.ReverseURL, {
merchantConfigurationId: this.GatewayAuth.merchantConfigID!,
payGateTranId: transId,
});
}
async cancel(transId: string): Promise<{ code: number; content: any }> {
return await this.callAPI("POST", this.CancelURL, {
merchantConfigurationId: this.GatewayAuth.merchantConfigID!,
payGateTranId: transId,
});
}
/**
* Internal method to call the API.
* @param method - The HTTP method to use.
* @param endpoint - The API endpoint to call.
* @param data - Optional data to send with the request.
* @returns A promise resolving to the API response.
*/
private async callAPI(method: "GET" | "POST", endpoint: string, data: any = null): Promise<{ code: number; content: any }> {
const url = `${this.GatewayApiURL}/${endpoint}`;
const options: AxiosRequestConfig = {
method,
url,
headers: {
Accept: "application/json",
Usr: this.GatewayAuth.user || "",
Pwd: this.GatewayAuth.password || "",
"Content-Type": "application/json",
},
data: data ? JSON.stringify(data) : null,
};
try {
const response: AxiosResponse = await axios(options);
return { content: response.data, code: response.status };
} catch (error: any) {
if (error.response) {
return { content: error.response.data, code: error.response.status };
} else {
return { content: error.message, code: 500 };
}
}
}
}
export { AsanPardakhtGateway };
+30 -12
View File
@@ -3,38 +3,54 @@ import { injectable } from "inversify";
import { InternalError } from "../../../core/app/app.errors";
import { Logger } from "../../../core/logging/logger";
import { IGateway } from "../interface/IPG";
interface SepRequestPaymentResponse {
status: 1 | -1;
errorCode?: string;
errorDesc?: string;
token?: string;
}
interface SepVerifyPaymentResponse {
TransactionDetail: {
"RRN": string,
"RefNum": string,
"MaskedPan": string,
"HashedPan": string,
"TerminalNumber": number,
"OrginalAmount": number,
"AffectiveAmount": number,
"StraceDate": string,
"StraceNo": string,
},
"ResultCode": number,
"ResultDescription": string,
"Success": boolean
}
@injectable()
class SepGateway {
class SepGateway implements IGateway {
private logger = new Logger(SepGateway.name);
private gatewayApiUrl: string = process.env.SEP_HOST || "https://sep.shaparak.ir";
private callBackURL = `https://api.fajrtabloshop.com/payment/verify/SEP/`;
private TERMINAL_ID: string = process.env.SEP_TERMINAL_ID || "15364098";
private terminalNumber: string = process.env.SEP_TERMINAL_NUMBER || "15364098";
private requestHeader = { "Content-Type": "application/json", Accept: "application/json" };
async processPayment(amount: number, description: string, email: string, mobile: string, callBackPath: string) {
if (!this.gatewayApiUrl || !this.TERMINAL_ID) {
async processPayment(amount: number, _description: string, _email: string, _mobile: string, callBackPath: string) {
if (!this.gatewayApiUrl || !this.terminalNumber) {
throw new InternalError(["SEP gateway is not configured"]);
}
await this.checkOutgoingIp();
const requesturl = `${this.gatewayApiUrl}/onlinepg/onlinepg`;
console.log("SEP Gateway processPayment called", email, description);
try {
const purchaseData = {
Action: "Token",
TerminalId: this.TERMINAL_ID,
TerminalId: this.terminalNumber,
Amount: amount, // ریال
RedirectUrl: `${this.callBackURL}${callBackPath}`,
ResNum: Date.now().toString(),
CellNumber: mobile,
CellNumber: _mobile,
};
const response = await axios.post<SepRequestPaymentResponse>(requesturl, purchaseData, { headers: this.requestHeader });
@@ -58,22 +74,24 @@ class SepGateway {
async checkOutgoingIp() {
try {
const response = await axios.get("https://api.ipify.org?format=json");
console.log("Your Server's Public Outgoing IP is:", response.data.ip);
return response.data.ip;
} catch (error) {
console.error("Could not determine outgoing IP", error);
}
}
async verifyPayment(authority: string, amount: number) {
if (!this.gatewayApiUrl || !this.TERMINAL_ID) {
async verifyPayment(data: { authority?: string; refNum?: string; amount: number }) {
if (!this.gatewayApiUrl || !this.terminalNumber) {
throw new InternalError(["SEP gateway is not configured"]);
}
// https://sep.shaparak.ir/verifyTxnRandomSessionkey/ipg/VerifyTransaction
try {
const verifyData = { authority, amount, terminal_id: this.TERMINAL_ID };
const response = await axios.post(`${this.gatewayApiUrl}/payment/verify`, verifyData, { headers: this.requestHeader });
const verifyData = { RefNum: data.refNum, TerminalNumber: this.terminalNumber };
const response = await axios.post<SepVerifyPaymentResponse>(`${this.gatewayApiUrl}/verifyTxnRandomSessionkey/ipg/VerifyTransaction`,
verifyData,
{ headers: this.requestHeader }
);
return response.data;
} catch (error) {
this.logger.error("error in SEP payment verification", error);
+4 -3
View File
@@ -4,9 +4,10 @@ import { injectable } from "inversify";
import { ZarinPalPGNewArgs, ZarinPalPGNewRequestData, ZarinPalPGVerifyData } from "../../../common/types/paymentGateway.type";
import { InternalError } from "../../../core/app/app.errors";
import { Logger } from "../../../core/logging/logger";
import { IGateway } from "../interface/IPG";
@injectable()
class ZarinPalGateway {
class ZarinPalGateway implements IGateway {
private logger = new Logger(ZarinPalGateway.name);
private gatewayApiUrl: string = `https://${process.env.IPG_TYPE}.zarinpal.com/pg`; //or api subdomain
@@ -39,10 +40,10 @@ class ZarinPalGateway {
}
}
async verifyPayment(authority: string, amount: number) {
async verifyPayment(verifyParams: { authority?: string; refNum?: string; amount: number }) {
try {
//TODO:check if amount should be in toman or riyal
const verifyData = { authority, amount, merchant_id: this.MERCHANT_ID };
const verifyData = { authority: verifyParams.authority, amount: verifyParams.amount, merchant_id: this.MERCHANT_ID };
const response = await axios.post(`${this.gatewayApiUrl}/v4/payment/verify.json`, verifyData, { headers: this.requestHeader });
const { data } = response.data as ZarinPalPGVerifyData;
+8 -1
View File
@@ -23,6 +23,12 @@ export interface IPaymentGateway {
retryPayment(gatewayType: GatewayProvider, authority: string): Promise<any>;
}
export interface IGateway {
processPayment(amount: number, description: string, email: string, mobile: string, callBackPath: string): Promise<any>;
verifyPayment(data: { authority?: string; refNum?: string; amount: number }): Promise<any>;
retryPayment(authority: string): Promise<any>;
}
export interface NewPaymentData {
amount: number;
description: string;
@@ -32,6 +38,7 @@ export interface NewPaymentData {
}
export interface VerifyPaymentData {
authority: string;
amount: number;
authority?: string;
refNum?:string
}
@@ -0,0 +1,14 @@
export class VerifyOnlinePaymentDTO {
MID: string;
State: string;
Status: string;
RRN: string;
RefNum: string;
ResNum: string;
TerminalId: string;
TraceNo: string;
Amount: string;
Wage: string;
SecurePan: string;
HashedCardNumber: string;
}
@@ -7,7 +7,7 @@ export interface ICartPayment {
_id: Types.ObjectId;
user_id: Types.ObjectId;
authority: string;
transaction_id: number;
transaction_id: string | null;
payment_method: Types.ObjectId;
priceDetails: IPriceDetails;
totalPrice: number;
@@ -34,7 +34,7 @@ export interface IProductRequestPayment {
_id: Types.ObjectId;
seller_id: Types.ObjectId;
authority: string;
transaction_id: number;
transaction_id: string | null;
payment_method: Types.ObjectId;
priceDetails: IPRPriceDetails;
totalPrice: number;
+2 -2
View File
@@ -19,7 +19,7 @@ const CartPaymentSchema = new Schema<ICartPayment>(
{
user_id: { type: Schema.Types.ObjectId, ref: "User", required: true },
authority: { type: String, required: true },
transaction_id: { type: Number, default: null },
transaction_id: { type: String, default: null },
payment_method: { type: Schema.Types.ObjectId, ref: "PaymentMethod", required: true },
priceDetails: { type: PaymentPriceDetails, required: true },
totalPrice: { type: Number, required: true },
@@ -46,7 +46,7 @@ const PRPaymentSchema = new Schema<IProductRequestPayment>(
{
seller_id: { type: Schema.Types.ObjectId, ref: "Seller", required: true },
authority: { type: String, required: true },
transaction_id: { type: Number, default: null },
transaction_id: { type: String, default: null },
payment_method: { type: Schema.Types.ObjectId, ref: "PaymentMethod", required: true },
priceDetails: { type: PRPriceDetailSchema },
totalPrice: { type: Number, required: true },
+21
View File
@@ -6,6 +6,7 @@ import { PaymentService } from "./providers/payment.service";
import { HttpStatus } from "../../common";
import { CartCheckoutDTO } from "./DTO/cartCheckout.dto";
import { PRCheckoutDTO } from "./DTO/PRCheckout.dto";
import { VerifyOnlinePaymentDTO } from "./DTO/verifyOnlinePayment.dto";
import { PRPaymentService } from "./providers/PR-payment.service";
import { BaseController } from "../../common/base/controller";
import { ApiAuth, ApiModel, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from "../../common/decorator/swggerDocs";
@@ -104,6 +105,26 @@ class PaymentController extends BaseController {
// return this.response(data);
}
@ApiModel(VerifyOnlinePaymentDTO)
@ApiOperation("verify a payment for current session user for cartCheckout (method post for SEP)")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("gateway", "the name of api gateway", true)
@httpPost("/verify/:gateway/cart")
public async VerifyCartCheckoutPost(
@requestParam("gateway") gateway: string,
@response() res: Response,
@requestBody() dto: VerifyOnlinePaymentDTO,
) {
const verifyCallbackData = {
status: dto.Status,
refNum: dto.RefNum,
};
const data = await this.paymentService.verifyCartPayment(gateway as GatewayProvider, verifyCallbackData);
const html = verifyPaymentTemplate(data);
return res.send(html);
// return this.response(data);
}
@ApiOperation("verify a payment for current session seller for product request checkout")
@ApiResponse("successful", HttpStatus.Ok)
@ApiParam("gateway", "the name of api gateway", true)
@@ -110,13 +110,23 @@ export class PRPaymentService {
//##################################################################
//##################################################################
async verifyPRPayment(gateway: GatewayProvider, verifyCallbackData: { status: string; authority: string }) {
const { status, authority } = verifyCallbackData;
async verifyPRPayment(gateway: GatewayProvider, verifyCallbackData: { status: string; authority?: string; refNum?: string }) {
const { status, authority, refNum } = verifyCallbackData;
const session = await startSession();
session.startTransaction();
try {
const paymentData = await this.findPaymentByAuthority(authority, session);
// For SEP gateway, refNum is used as authority in database
const authorityToFind = gateway === GatewayProvider.SEP ? refNum : authority;
if (!authorityToFind) throw new BadRequestError(PaymentMessage.PaymentNotFound);
// For SEP gateway, check if refNum has already been used as transaction_id
if (gateway === GatewayProvider.SEP && refNum) {
const existingPayment = await this.PRPaymentRepo.model.findOne({ transaction_id: refNum }).session(session);
if (existingPayment) throw new BadRequestError(PaymentMessage.RefNumIsAlreadyUsed);
}
const paymentData = await this.findPaymentByAuthority(authorityToFind, session);
const productRequest = await this.findProductRequestByPaymentId(paymentData._id.toString(), session);
const PRId = productRequest._id.toString();
@@ -125,9 +135,12 @@ export class PRPaymentService {
return this.buildPaymentResponse(status, PaymentMessage.PaymentNotSuccessful, paymentData._id.toString(), PRId);
}
const verificationResult = await this.verifyPaymentWithGateway(gateway, authority, paymentData.totalPrice);
const verificationResult = await this.verifyPaymentWithGateway(gateway, authorityToFind, paymentData.totalPrice);
if (verificationResult.success) {
await this.handleSuccessPayment(paymentData._id.toString(), verificationResult.refId!, session);
// For SEP gateway, save refNum as transaction_id; for Zarinpal, save ref_id
const transactionId = gateway === GatewayProvider.SEP ? refNum : verificationResult.refId;
if (!transactionId) throw new BadRequestError(PaymentMessage.PaymentNotSuccessful);
await this.handleSuccessPayment(paymentData._id.toString(), transactionId, session);
return this.buildPaymentResponse(status, PaymentMessage.PaymentSuccessful, paymentData._id.toString(), PRId);
}
@@ -161,10 +174,10 @@ export class PRPaymentService {
}
/******************************/
private async handleSuccessPayment(paymentId: string, refId: string, session: ClientSession) {
private async handleSuccessPayment(paymentId: string, transactionId: string, session: ClientSession) {
await this.PRPaymentRepo.model.findByIdAndUpdate(
paymentId,
{ paymentStatus: PaymentStatus.Completed, transaction_id: refId },
{ paymentStatus: PaymentStatus.Completed, transaction_id: transactionId },
{ session },
);
await session.commitTransaction();
@@ -140,12 +140,11 @@ class PaymentService {
// extracted to handle payment creation
const { payment, paymentData } = await this.handlePaymentCreation(paymentMethod, price, user, description, session);
// create order and order items
const { order, orderItems } = await this.orderService.createOrder(user, payment, cartShipmentItems, session);
// clear cart and adjust stock
await this.finalizeOrder(user, cartShipmentItems, session);
// clear cart and adjust stock
await this.finalizeOrder(user, cartShipmentItems, session);
await session.commitTransaction();
await session.endSession();
return { paymentData, order, orderItems };
@@ -161,12 +160,22 @@ class PaymentService {
//##################################################################
//##################################################################
//verify callback of cart payment
async verifyCartPayment(gateway: GatewayProvider, verifyCallbackData: { authority: string; status: string }) {
const { status, authority } = verifyCallbackData;
async verifyCartPayment(gateway: GatewayProvider, verifyCallbackData: { status: string; authority?: string; refNum?: string }) {
const { status, authority, refNum } = verifyCallbackData;
const session = await startSession();
session.startTransaction();
try {
const paymentData = await this.cartPaymentRepo.model.findOne({ authority }).session(session);
// For SEP gateway, refNum is used as authority in database
const authorityToFind = gateway === GatewayProvider.SEP ? refNum : authority;
if (!authorityToFind) throw new BadRequestError(PaymentMessage.PaymentNotFound);
// For SEP gateway, check if refNum has already been used as transaction_id
if (gateway === GatewayProvider.SEP && refNum) {
const existingPayment = await this.cartPaymentRepo.model.findOne({ transaction_id: refNum }).session(session);
if (existingPayment) throw new BadRequestError(PaymentMessage.RefNumIsAlreadyUsed);
}
const paymentData = await this.cartPaymentRepo.model.findOne({ authority: authorityToFind }).session(session);
if (!paymentData) throw new BadRequestError(PaymentMessage.PaymentNotFound);
@@ -185,10 +194,16 @@ class PaymentService {
}
// Verify payment through gateway
const data = await this.paymentGateway.verifyPayment(gateway, { authority, amount: paymentData.totalPrice });
const verifyData =
gateway === GatewayProvider.SEP
? { refNum: authority, amount: paymentData.totalPrice }
: { authority, amount: paymentData.totalPrice };
const data = await this.paymentGateway.verifyPayment(gateway, verifyData);
if (data.code === 100 || data.code === 101) {
await this.handleSellerNotify(order._id, user, session);
await this.handleSuccessfulPayment(paymentData._id.toString(), order._id, data.ref_id, session);
// For SEP gateway, save refNum as transaction_id; for Zarinpal, save ref_id
const transactionId = gateway === GatewayProvider.SEP ? refNum : data.ref_id;
await this.handleSuccessfulPayment(paymentData._id.toString(), order._id, transactionId, session);
return this.buildPaymentResponse(status, PaymentMessage.PaymentSuccessful, paymentData._id.toString(), order._id);
}
@@ -279,8 +294,8 @@ class PaymentService {
}
//##################################################################
//##################################################################
private async handleSuccessfulPayment(paymentId: string, orderId: number, refId: number, session: ClientSession) {
await this.updatePaymentStatusAndReference(paymentId, refId, PaymentStatus.Completed, session);
private async handleSuccessfulPayment(paymentId: string, orderId: number, transactionId: string, session: ClientSession) {
await this.updatePaymentStatusAndReference(paymentId, transactionId, PaymentStatus.Completed, session);
await this.orderService.updateOrderStatus(orderId, OrdersStatus.process_by_seller, session);
@@ -385,8 +400,8 @@ class PaymentService {
}
//##################################################################
//##################################################################
private async updatePaymentStatusAndReference(paymentId: string, refId: number | null, status: PaymentStatus, session: ClientSession) {
return await this.cartPaymentRepo.model.findByIdAndUpdate(paymentId, { paymentStatus: status, transaction_id: refId }, { session });
private async updatePaymentStatusAndReference(paymentId: string, transactionId: string | null, status: PaymentStatus, session: ClientSession) {
return await this.cartPaymentRepo.model.findByIdAndUpdate(paymentId, { paymentStatus: status, transaction_id: transactionId }, { session });
}
}