refactor : payment
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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"),
|
||||
};
|
||||
|
||||
@@ -197,6 +197,7 @@ export const enum PaymentMessage {
|
||||
CartPaymentDesc = "خرید کالا:",
|
||||
PaymentNotBelongToUser = "پرداخت برای این کاربر نمیباشد",
|
||||
PaymentRequestFailed = "درخواست پرداخت موفقیت آمیز نبود",
|
||||
RefNumIsAlreadyUsed = "این شماره رسید قبلا برای تایید استفاده شده .",
|
||||
}
|
||||
|
||||
export const enum CouponMessage {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export enum GatewayProvider {
|
||||
Zarinpal = "Zarinpal",
|
||||
Asanpardakht = "asanPardakht",
|
||||
SEP = "SEP",
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 };
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user