fix: the bug in the payment service

This commit is contained in:
Mahyargdz
2025-05-21 10:06:29 +03:30
parent 858f55bb0b
commit 9b3b5b41d7
10 changed files with 454 additions and 103 deletions
+2
View File
@@ -2,6 +2,7 @@ import { EntityManager } from "@mikro-orm/postgresql";
import { Seeder } from "@mikro-orm/seeder";
import { Logger } from "@nestjs/common";
import { PaymentGatewaySeeder } from "./payment-gateway.seeder";
import { RoleSeeder } from "./role.seeder";
export class IndexSeeder extends Seeder {
@@ -10,6 +11,7 @@ export class IndexSeeder extends Seeder {
async run(em: EntityManager): Promise<void> {
this.logger.log("Starting seeding process...");
await new RoleSeeder().run(em);
await new PaymentGatewaySeeder().run(em);
this.logger.log("Seeding process completed successfully.");
}
}
@@ -0,0 +1,34 @@
import { EntityManager, RequiredEntityData } from "@mikro-orm/postgresql";
import { Seeder } from "@mikro-orm/seeder";
import { Logger } from "@nestjs/common";
import { PaymentGateway } from "../../src/modules/payments/entities/payment-gateway.entity";
import { GatewayEnum } from "../../src/modules/payments/enums/gateway.enum";
export class PaymentGatewaySeeder extends Seeder {
private readonly logger = new Logger(PaymentGatewaySeeder.name);
async run(em: EntityManager): Promise<void> {
const paymentGateways: RequiredEntityData<PaymentGateway>[] = [
{
name: GatewayEnum.ZARINPAL,
nameFa: "زرین پال",
logoUrl: "https://storage.danakcorp.com/logo/fa-dark.png",
description: "درگاه پرداخت زرین پال",
},
];
for (const paymentGateway of paymentGateways) {
const existingPaymentGateway = await em.findOne(PaymentGateway, { name: paymentGateway.name });
if (!existingPaymentGateway) {
const createdPaymentGateway = em.create(PaymentGateway, paymentGateway);
await em.persistAndFlush(createdPaymentGateway);
this.logger.log(`[PaymentGatewaySeeder] Created payment gateway: ${createdPaymentGateway.name}`);
} else {
this.logger.log(`[PaymentGatewaySeeder] Payment gateway already exists: ${paymentGateway.name}`);
}
}
}
}
@@ -9,9 +9,7 @@ export enum DomainVerificationMethod {
export class UpdateBusinessDomainDto {
@IsNotEmpty()
@IsString()
@Matches(/^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/, {
message: "Domain must be a valid domain name (e.g., example.com)",
})
@Matches(/^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/, { message: "Domain must be a valid domain name (e.g., example.com)" })
domain: string;
}
@@ -1,10 +1,12 @@
import { Body, Controller, Get, Param, Post } from "@nestjs/common";
import { ApiOperation } from "@nestjs/swagger";
import { UpdateBusinessDomainDto, VerifyDomainDto } from "./DTO/business-domain.dto";
import { BusinessSlugParamDto } from "./DTO/business-slug-param.dto";
import { Business } from "./entities/business.entity";
import { BusinessesService } from "./services/businesses.service";
import { DomainVerificationService } from "./services/domain-verification.service";
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
import { BusinessDec } from "../../common/decorators/business.decorator";
@Controller("business")
@@ -15,37 +17,74 @@ export class BusinessesController {
) {}
@Get("slug/:slug")
@ApiOperation({ summary: "Get business by slug" })
async getBusinessBySlug(@Param() params: BusinessSlugParamDto) {
return this.businessesService.getBusinessBySlug(params.slug);
}
@Post()
// Domain management endpoints
@Post("domain")
@ApiOperation({ summary: "Update business domain" })
@AuthGuards()
async updateBusinessDomain(@BusinessDec() business: Business, @Body() domainDto: UpdateBusinessDomainDto) {
return this.domainVerificationService.updateBusinessDomain(business.id, domainDto);
}
@Get("status")
@Get("domain/status")
@ApiOperation({ summary: "Get business domain status" })
@AuthGuards()
async getDomainStatus(@BusinessDec() business: Business) {
return this.domainVerificationService.getDomainVerificationStatus(business.id);
}
@Post("verify")
@Post("domain/verify")
@ApiOperation({ summary: "Initiate domain verification" })
@AuthGuards()
async verifyDomain(@BusinessDec() business: Business, @Body() verifyDto: VerifyDomainDto) {
return this.domainVerificationService.initiateVerification(business.id, verifyDto);
}
@Post("verify/check")
@Post("domain/verify/check")
@ApiOperation({ summary: "Check domain verification status" })
@AuthGuards()
async checkVerification(@BusinessDec() business: Business) {
return this.domainVerificationService.verifyDomain(business.id);
}
@Post("admin/:businessId/verify")
// DNS-specific domain verification endpoints
@Post("domain/set-domain")
@ApiOperation({ summary: "Set domain and get verification instructions" })
@AuthGuards()
async setDomainAndGetInstructions(@BusinessDec() business: Business, @Body() domainDto: UpdateBusinessDomainDto) {
return this.domainVerificationService.setDomainAndGetVerificationInstructions(business.id, domainDto.domain);
}
@Get("domain/check-dns")
@ApiOperation({ summary: "Check DNS verification" })
@AuthGuards()
async checkDnsVerification(@BusinessDec() business: Business) {
return this.domainVerificationService.verifyDnsTxtRecord(business.id);
}
// Admin domain management endpoints
@Post("admin/:businessId/domain/verify")
@ApiOperation({ summary: "Admin initiate domain verification" })
@AuthGuards()
async adminVerifyDomain(@Param("businessId") businessId: string, @Body() verifyDto: VerifyDomainDto) {
return this.domainVerificationService.initiateVerification(businessId, verifyDto);
}
@Post("admin/:businessId/verify/check")
@Post("admin/:businessId/domain/verify/check")
@ApiOperation({ summary: "Admin check domain verification status" })
@AuthGuards()
async adminCheckVerification(@Param("businessId") businessId: string) {
return this.domainVerificationService.verifyDomain(businessId);
}
@Post("admin/:businessId/domain/check-dns")
@ApiOperation({ summary: "Admin check DNS verification" })
@AuthGuards()
async adminCheckDnsVerification(@Param("businessId") businessId: string) {
return this.domainVerificationService.verifyDnsTxtRecord(businessId);
}
}
@@ -27,8 +27,4 @@ export class BusinessesService {
return business;
}
async flushChanges() {
await this.businessRepository.getEntityManager().flush();
}
}
@@ -1,9 +1,11 @@
import * as crypto from "node:crypto";
import * as dns from "node:dns";
import crypto from "node:crypto";
import dns from "node:dns";
import { promisify } from "node:util";
import { EntityManager } from "@mikro-orm/postgresql";
import { BadRequestException, Injectable, Logger } from "@nestjs/common";
import axios from "axios";
import dayjs from "dayjs";
import { BusinessesService } from "./businesses.service";
import { BusinessMessage } from "../../../common/enums/message.enum";
@@ -13,8 +15,17 @@ import { DomainVerificationMethod, UpdateBusinessDomainDto, VerifyDomainDto } fr
export class DomainVerificationService {
private readonly logger = new Logger(DomainVerificationService.name);
private readonly resolveTxt = promisify(dns.resolveTxt);
private readonly resolveA = promisify(dns.resolve4);
private readonly resolveCname = promisify(dns.resolveCname);
constructor(private readonly businessesService: BusinessesService) {}
// The server IP or domain users should point their domain to
private readonly serverIP = "IP_OF_YOUR_SERVER"; // Replace with actual IP
private readonly serverDomain = "app.dzone.danakcorp.com"; // Replace with actual domain
constructor(
private readonly businessesService: BusinessesService,
private readonly em: EntityManager,
) {}
/**
* Update business domain
@@ -41,13 +52,13 @@ export class DomainVerificationService {
business.domainVerifiedAt = undefined;
business.domainVerificationMethod = undefined;
await this.businessesService.flushChanges();
await this.em.flush();
return {
message: BusinessMessage.DOMAIN_UPDATED,
domain: business.domain,
isVerified: business.isDomainVerified,
verificationMethods: this.getVerificationInstructions(business.domain, verificationToken),
dnsInstructions: this.getDomainSetupInstructions(business.domain, verificationToken),
};
}
@@ -73,12 +84,12 @@ export class DomainVerificationService {
// If no token exists, generate one
if (!business.domainVerificationToken) {
business.domainVerificationToken = this.generateVerificationToken();
await this.businessesService.flushChanges();
await this.em.flush();
}
// Update verification method
business.domainVerificationMethod = verifyDto.method;
await this.businessesService.flushChanges();
await this.em.flush();
return {
message: BusinessMessage.DOMAIN_VERIFICATION_INITIATED,
@@ -88,6 +99,44 @@ export class DomainVerificationService {
};
}
/**
* Set domain and get verification and connection instructions
*/
async setDomainAndGetVerificationInstructions(businessId: string, domain: string) {
const business = await this.businessesService.getBusinessById(businessId);
// Domain hasn't changed, check if it's already verified
if (business.domain === domain && business.isDomainVerified) {
return {
message: BusinessMessage.DOMAIN_ALREADY_VERIFIED,
domain: business.domain,
isVerified: business.isDomainVerified,
dnsInstructions: business.domainVerificationToken
? this.getDomainSetupInstructions(domain, business.domainVerificationToken)
: this.getDomainSetupInstructions(domain, ""),
};
}
// Generate verification token
const verificationToken = this.generateVerificationToken();
// Update the domain and verification data
business.domain = domain;
business.isDomainVerified = false;
business.domainVerificationToken = verificationToken;
business.domainVerifiedAt = undefined;
await this.em.flush();
// Return DNS verification and setup instructions
return {
message: BusinessMessage.DOMAIN_VERIFICATION_INITIATED,
domain: business.domain,
isVerified: false,
dnsInstructions: this.getDomainSetupInstructions(domain, verificationToken),
};
}
/**
* Check domain verification status
*/
@@ -112,7 +161,7 @@ export class DomainVerificationService {
switch (business.domainVerificationMethod) {
case DomainVerificationMethod.DNS_TXT:
isVerified = await this.verifyDnsTxtRecord(business.domain, business.domainVerificationToken);
isVerified = await this.checkDnsTxtRecord(business.domain, business.domainVerificationToken);
break;
case DomainVerificationMethod.FILE:
isVerified = await this.verifyFileMethod(business.domain, business.domainVerificationToken);
@@ -126,8 +175,8 @@ export class DomainVerificationService {
if (isVerified) {
business.isDomainVerified = true;
business.domainVerifiedAt = new Date();
await this.businessesService.flushChanges();
business.domainVerifiedAt = dayjs().toDate();
await this.em.flush();
return {
message: BusinessMessage.DOMAIN_VERIFICATION_SUCCESS,
@@ -150,6 +199,82 @@ export class DomainVerificationService {
}
}
/**
* Verify DNS TXT record and domain pointing
*/
async verifyDnsTxtRecord(businessId: string) {
const business = await this.businessesService.getBusinessById(businessId);
if (!business.domain || !business.domainVerificationToken) {
throw new BadRequestException(BusinessMessage.DOMAIN_INVALID);
}
if (business.isDomainVerified) {
const domainPointsToServer = await this.checkDomainPointsToServer(business.domain);
return {
message: BusinessMessage.DOMAIN_ALREADY_VERIFIED,
domain: business.domain,
isVerified: true,
verifiedAt: business.domainVerifiedAt,
domainPointsToServer,
serverConnectionStatus: domainPointsToServer
? "Your domain is properly set up and pointing to our servers"
: "Your domain is verified, but not properly pointing to our servers",
setupInstructions: !domainPointsToServer ? this.getDomainConnectionInstructions(business.domain) : null,
};
}
try {
// First check domain ownership verification via TXT record
const isVerified = await this.checkDnsTxtRecord(business.domain, business.domainVerificationToken);
// Then check if domain points to our server (A or CNAME record)
const domainPointsToServer = await this.checkDomainPointsToServer(business.domain);
// If TXT verification passes, mark the domain as verified regardless of A/CNAME
if (isVerified) {
// Update domain verification status
business.isDomainVerified = true;
business.domainVerifiedAt = dayjs().toDate();
await this.em.flush();
return {
message: BusinessMessage.DOMAIN_VERIFICATION_SUCCESS,
domain: business.domain,
isVerified: true,
verifiedAt: business.domainVerifiedAt,
domainPointsToServer,
serverConnectionStatus: domainPointsToServer
? "Your domain is properly set up and pointing to our servers"
: "Your domain is verified, but not properly pointing to our servers",
connectionInstructions: !domainPointsToServer ? this.getDomainConnectionInstructions(business.domain) : null,
};
} else {
return {
message: BusinessMessage.DOMAIN_VERIFICATION_FAILED,
domain: business.domain,
isVerified: false,
domainPointsToServer,
serverConnectionStatus: domainPointsToServer
? "Your domain is pointing to our servers, but ownership verification failed"
: "Both ownership verification and server connection failed",
setupInstructions: this.getDomainSetupInstructions(business.domain, business.domainVerificationToken || ""),
};
}
} catch (error: unknown) {
this.logger.error(`Domain verification failed: ${error instanceof Error ? error.message : String(error)}`);
return {
message: BusinessMessage.DOMAIN_VERIFICATION_FAILED,
domain: business.domain,
isVerified: false,
error: error instanceof Error ? error.message : String(error),
setupInstructions: this.getDomainSetupInstructions(business.domain, business.domainVerificationToken || ""),
};
}
}
/**
* Get domain verification status
*/
@@ -162,13 +287,139 @@ export class DomainVerificationService {
};
}
// Check if domain points to our server (A or CNAME record)
const domainPointsToServer = await this.checkDomainPointsToServer(business.domain);
return {
hasDomain: true,
domain: business.domain,
isVerified: business.isDomainVerified,
verificationMethod: business.domainVerificationMethod,
verifiedAt: business.domainVerifiedAt,
instructions: business.domainVerificationToken ? this.getVerificationInstructions(business.domain, business.domainVerificationToken) : null,
domainPointsToServer,
verificationStatus: business.isDomainVerified ? "Verified" : "Not Verified",
connectionStatus: domainPointsToServer ? "Connected" : "Not Connected",
fullSetupComplete: business.isDomainVerified && domainPointsToServer,
connectionInstructions: !domainPointsToServer ? this.getDomainConnectionInstructions(business.domain) : null,
setupInstructions: !business.isDomainVerified ? this.getDomainSetupInstructions(business.domain, business.domainVerificationToken || "") : null,
};
}
/**
* Verify domain using file method
*/
private async verifyFileMethod(domain: string, token: string): Promise<boolean> {
try {
// Try HTTPS first
try {
const response = await axios.get(`https://${domain}/.well-known/${token}.html`, {
timeout: 5000, // 5 seconds timeout
headers: {
"User-Agent": "Mozilla/5.0 (compatible; DzoneVerification/1.0; +https://dzone.danakcorp.com/domain-verification)",
},
});
if (response.status === 200 && response.data.toString().trim() === token.trim()) {
return true;
}
} catch (error) {
this.logger.debug(`HTTPS file verification failed, trying HTTP: ${error instanceof Error ? error.message : String(error)}`);
}
// Try HTTP if HTTPS fails
const httpResponse = await axios.get(`http://${domain}/.well-known/${token}.html`, {
timeout: 5000,
headers: {
"User-Agent": "Mozilla/5.0 (compatible; DzoneVerification/1.0; +https://dzone.danakcorp.com/domain-verification)",
},
});
if (httpResponse.status === 200 && httpResponse.data.toString().trim() === token.trim()) {
return true;
}
return false;
} catch (error: unknown) {
this.logger.error(`Error verifying file method: ${error instanceof Error ? error.message : String(error)}`);
return false;
}
}
/**
* Verify domain using meta tag method
*/
private async verifyMetaTagMethod(domain: string, token: string): Promise<boolean> {
try {
// Try HTTPS first
try {
const response = await axios.get(`https://${domain}`, {
timeout: 5000, // 5 seconds timeout
headers: {
"User-Agent": "Mozilla/5.0 (compatible; DzoneVerification/1.0; +https://dzone.danakcorp.com/domain-verification)",
},
});
if (response.status === 200) {
// Check for meta tag in the HTML
const content = response.data.toString();
const metaTagRegex = new RegExp(`<meta[^>]*name=["']dzone-verification["'][^>]*content=["']${token}["']`, "i");
if (metaTagRegex.test(content)) {
return true;
}
}
} catch (error) {
this.logger.debug(`HTTPS meta tag verification failed, trying HTTP: ${error instanceof Error ? error.message : String(error)}`);
}
// Try HTTP if HTTPS fails
const httpResponse = await axios.get(`http://${domain}`, {
timeout: 5000,
headers: {
"User-Agent": "Mozilla/5.0 (compatible; DzoneVerification/1.0; +https://dzone.danakcorp.com/domain-verification)",
},
});
if (httpResponse.status === 200) {
// Check for meta tag in the HTML
const content = httpResponse.data.toString();
const metaTagRegex = new RegExp(`<meta[^>]*name=["']dzone-verification["'][^>]*content=["']${token}["']`, "i");
return metaTagRegex.test(content);
}
return false;
} catch (error: unknown) {
this.logger.error(`Error verifying meta tag method: ${error instanceof Error ? error.message : String(error)}`);
return false;
}
}
/**
* Get domain verification and connection status
*/
async getDomainStatus(businessId: string) {
const business = await this.businessesService.getBusinessById(businessId);
if (!business.domain) {
return {
hasDomain: false,
message: "No domain set for this business",
};
}
const domainPointsToServer = await this.checkDomainPointsToServer(business.domain);
return {
hasDomain: true,
domain: business.domain,
isVerified: business.isDomainVerified,
verifiedAt: business.domainVerifiedAt,
domainPointsToServer,
verificationStatus: business.isDomainVerified ? "Verified" : "Not Verified",
connectionStatus: domainPointsToServer ? "Connected" : "Not Connected",
fullSetupComplete: business.isDomainVerified && domainPointsToServer,
setupInstructions:
!business.isDomainVerified || !domainPointsToServer
? this.getDomainSetupInstructions(business.domain, business.domainVerificationToken || "")
: null,
};
}
@@ -206,14 +457,86 @@ export class DomainVerificationService {
};
}
/**
* Get domain connection instructions
*/
private getDomainConnectionInstructions(_domain: string) {
// The domain parameter is used for contextual purposes in some implementations
// but not directly in this template
return {
title: "Point your domain to our servers",
description: "Choose ONE of these options to connect your domain:",
options: [
{
title: "Option A: Using A Record (Direct IP)",
steps: [
"1. Add an A record in your DNS settings",
"2. Set the host to @ or your subdomain (e.g., www)",
"3. Set the value to our server IP: " + this.serverIP,
"4. Set TTL to 3600 (1 hour) or the lowest available",
],
},
{
title: "Option B: Using CNAME Record (Recommended)",
steps: [
"1. Add a CNAME record in your DNS settings",
"2. Set the host to @ or your subdomain (e.g., www)",
"3. Set the value to: " + this.serverDomain,
"4. Set TTL to 3600 (1 hour) or the lowest available",
"Note: Some DNS providers don't allow CNAME for root domains (@)",
],
},
],
troubleshooting: [
"- Make sure you've added the records to the correct domain",
"- Some DNS providers require the @ symbol for root domains",
"- Check that you've copied the values exactly as shown",
"- DNS changes can take time to propagate (typically 15 minutes to 48 hours)",
],
};
}
/**
* Get comprehensive domain setup instructions
*/
private getDomainSetupInstructions(domain: string, token: string) {
return {
verificationInstructions: {
title: "Step 1: Verify domain ownership",
description: "Add this TXT record to prove you own the domain:",
host: "@", // Root domain
type: "TXT", // Record type
value: token, // Token value
ttl: "3600", // Time to live (1 hour)
},
connectionInstructions: this.getDomainConnectionInstructions(domain),
general: {
verificationEndpoint: `/business/domain/verify/check-dns`,
propagationNote: "DNS changes can take 15 minutes to 48 hours to propagate worldwide",
troubleshooting: [
"- Make sure you've added the records to the correct domain",
"- Some DNS providers require the @ symbol for root domains",
"- Check that you've copied the values exactly as shown",
"- Wait at least 15 minutes before checking verification status",
],
},
};
}
/**
* Verify domain using DNS TXT record
*/
private async verifyDnsTxtRecord(domain: string, token: string): Promise<boolean> {
private async checkDnsTxtRecord(domain: string, token: string): Promise<boolean> {
try {
this.logger.log(`Checking TXT records for domain: ${domain}`);
// Try to resolve TXT records
const txtRecords = await this.resolveTxt(domain);
// Flatten the results and check if any match our token
const records = txtRecords.flat();
this.logger.log(`Found TXT records: ${JSON.stringify(records)}`);
return records.some((record) => record === token);
} catch (error: unknown) {
this.logger.error(`Error verifying DNS TXT record: ${error instanceof Error ? error.message : String(error)}`);
@@ -222,89 +545,42 @@ export class DomainVerificationService {
}
/**
* Verify domain using file method
* Check if domain points to our server via A or CNAME record
*/
private async verifyFileMethod(domain: string, token: string): Promise<boolean> {
private async checkDomainPointsToServer(domain: string): Promise<boolean> {
try {
// Try HTTPS first
// Try CNAME first
try {
const response = await axios.get(`https://${domain}/.well-known/${token}.html`, {
timeout: 5000, // 5 seconds timeout
headers: {
"User-Agent": "Mozilla/5.0 (compatible; DzoneVerification/1.0; +https://dzone.com/domain-verification)",
},
});
const cnameRecords = await this.resolveCname(domain);
if (response.status === 200 && response.data.toString().trim() === token.trim()) {
this.logger.log(`Found CNAME records for ${domain}: ${JSON.stringify(cnameRecords)}`);
// Check if any CNAME points to our server domain
if (cnameRecords.some((record) => record === this.serverDomain)) {
return true;
}
} catch (error) {
this.logger.debug(`HTTPS file verification failed, trying HTTP: ${error instanceof Error ? error.message : String(error)}`);
} catch (_error) {
this.logger.debug(`No CNAME records found for ${domain}, trying A records`);
}
// Try HTTP if HTTPS fails
const httpResponse = await axios.get(`http://${domain}/.well-known/${token}.html`, {
timeout: 5000,
headers: {
"User-Agent": "Mozilla/5.0 (compatible; DzoneVerification/1.0; +https://dzone.com/domain-verification)",
},
});
if (httpResponse.status === 200 && httpResponse.data.toString().trim() === token.trim()) {
return true;
}
return false;
} catch (error: unknown) {
this.logger.error(`Error verifying file method: ${error instanceof Error ? error.message : String(error)}`);
return false;
}
}
/**
* Verify domain using meta tag method
*/
private async verifyMetaTagMethod(domain: string, token: string): Promise<boolean> {
try {
// Try HTTPS first
// Try A record
try {
const response = await axios.get(`https://${domain}`, {
timeout: 5000, // 5 seconds timeout
headers: {
"User-Agent": "Mozilla/5.0 (compatible; DzoneVerification/1.0; +https://dzone.com/domain-verification)",
},
});
const aRecords = await this.resolveA(domain);
if (response.status === 200) {
// Check for meta tag in the HTML
const content = response.data.toString();
const metaTagRegex = new RegExp(`<meta[^>]*name=["']dzone-verification["'][^>]*content=["']${token}["']`, "i");
if (metaTagRegex.test(content)) {
return true;
}
this.logger.log(`Found A records for ${domain}: ${JSON.stringify(aRecords)}`);
// Check if any A record points to our server IP
if (aRecords.some((record) => record === this.serverIP)) {
return true;
}
} catch (error) {
this.logger.debug(`HTTPS meta tag verification failed, trying HTTP: ${error instanceof Error ? error.message : String(error)}`);
}
// Try HTTP if HTTPS fails
const httpResponse = await axios.get(`http://${domain}`, {
timeout: 5000,
headers: {
"User-Agent": "Mozilla/5.0 (compatible; DzoneVerification/1.0; +https://dzone.com/domain-verification)",
},
});
if (httpResponse.status === 200) {
// Check for meta tag in the HTML
const content = httpResponse.data.toString();
const metaTagRegex = new RegExp(`<meta[^>]*name=["']dzone-verification["'][^>]*content=["']${token}["']`, "i");
return metaTagRegex.test(content);
} catch (_error) {
this.logger.debug(`No A records found for ${domain}`);
}
// If we get here, neither CNAME nor A records point to our server
return false;
} catch (error: unknown) {
this.logger.error(`Error verifying meta tag method: ${error instanceof Error ? error.message : String(error)}`);
this.logger.error(`Error checking domain DNS records: ${error instanceof Error ? error.message : String(error)}`);
return false;
}
}
@@ -5,12 +5,12 @@ import { PaymentMessage } from "../../../common/enums/message.enum";
export class CheckoutPaymentDto {
@IsNotEmpty({ message: PaymentMessage.GATEWAY_REQUIRED })
@IsUUID("4", { message: PaymentMessage.GATEWAY_ID_SHOULD_BE_UUID })
@IsUUID("7", { message: PaymentMessage.GATEWAY_ID_SHOULD_BE_UUID })
@ApiProperty({ description: "Gateway id to pay", example: "e6fdce2a-9f91-47c4-8561-48368fc275b5" })
gatewayId: string;
@IsNotEmpty({ message: PaymentMessage.INVOICE_ID_REQUIRED })
@IsUUID("4", { message: PaymentMessage.INVOICE_ID_SHOULD_BE_UUID })
@IsUUID("7", { message: PaymentMessage.INVOICE_ID_SHOULD_BE_UUID })
@ApiProperty({ description: "Invoice id to pay", example: "e6fdce2a-9f91-47c4-8561-48368fc275b5" })
invoiceId: string;
}
@@ -205,7 +205,7 @@ export class PaymentProcessor extends WorkerProcessor {
const verifyData = await gateway.verifyPayment({
reference: payment.reference,
amount: payment.amount,
merchantId: payment.business.zarinpalMerchantId || this.zarinpalMerchantId,
merchantId: payment.business.zarinpalMerchantId ?? this.zarinpalMerchantId,
});
const result = await this.paymentsService.processVerificationResult(verifyData, payment, em);
@@ -58,17 +58,17 @@ export class PaymentsService {
if (invoice.company.user.id !== user.id) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID_OR_NOT_BELONG_TO_USER);
const gatewayData = await this.processPayment(paymentGateway.name, {
amount: invoice.totalPrice.toNumber(),
amount: new Decimal(invoice.totalPrice).toNumber(),
description: PaymentMessage.CHECKOUT_INVOICE_MESSAGE.replace("[invoiceNumber]", invoice.numericId.toString()),
email: user.email,
mobile: user.phone,
callBackPath: invoice.business.slug,
merchantId: invoice.business.zarinpalMerchantId || this.zarinpalMerchantId,
merchantId: invoice.business.zarinpalMerchantId ?? this.zarinpalMerchantId,
});
const payment = await this.createGatewayPaymentForUser(
user,
invoice.totalPrice.toNumber(),
new Decimal(invoice.totalPrice).toNumber(),
gatewayData.reference,
paymentGateway.id,
invoice.business.id,
@@ -84,6 +84,7 @@ export class PaymentsService {
} catch (error) {
await em.rollback();
if (error instanceof HttpException) throw error;
this.logger.error("Payment checkout failed:", error);
throw new InternalServerErrorException(PaymentMessage.ERROR_IN_PAYMENT);
}
}
@@ -113,7 +114,7 @@ export class PaymentsService {
const verifyData = await paymentGateway.verifyPayment({
reference: queryDto.Authority,
amount: payment.amount,
merchantId: payment.business.zarinpalMerchantId || this.zarinpalMerchantId,
merchantId: payment.business.zarinpalMerchantId ?? this.zarinpalMerchantId,
});
return await this.handlePaymentVerificationWithResponse(verifyData, payment, rep, frontUrl, em);
@@ -289,8 +290,12 @@ export class PaymentsService {
//===============================================
private async getPaymentByReference(reference: string, em: EntityManager): Promise<Payment> {
const payment = await em.findOne(Payment, { reference }, { populate: ["user", "invoice", "business"], lockMode: LockMode.PESSIMISTIC_WRITE });
// First get the payment with a lock, but without joins
const payment = await em.findOne(Payment, { reference }, { lockMode: LockMode.PESSIMISTIC_WRITE });
if (!payment) throw new BadRequestException(PaymentMessage.PAYMENT_NOT_FOUND_WITH_REF);
// Then populate relations after acquiring the lock
await em.populate(payment, ["user", "invoice", "business"]);
return payment;
}
+2 -1
View File
@@ -4,11 +4,12 @@ import { ApiBody, ApiConsumes, ApiOperation, ApiTags } from "@nestjs/swagger";
import { UploadMultipleFileDto, UploadSingleFileDto } from "./DTO/upload-file.dto";
import { UploaderService } from "./uploader.service";
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
import { IFile } from "../utils/interfaces/IFile";
@ApiTags("Uploader")
@Controller("uploader")
// @AuthGuards()
@AuthGuards()
export class UploaderController {
constructor(private readonly uploaderService: UploaderService) {}