Add ZarinPal e-payment and related checkout/search fixes.
Business settings can store ZarinPal merchant credentials, and the payment registry can initiate and verify ZarinPal alongside Mellat. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
c76c3967bb
commit
b991a60cbc
@@ -35,6 +35,56 @@ type UserListRow = {
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* ILIKE patterns so local Iranian input (09…) matches stored E.164 (+98…).
|
||||
* `09024300` must match `+989024300340`; short fragments like `4300` still match.
|
||||
*/
|
||||
function cellNumberLikePatterns(input: string): string[] {
|
||||
const trimmed = input.trim();
|
||||
if (!trimmed) return [];
|
||||
|
||||
const patterns = new Set<string>();
|
||||
patterns.add(`%${trimmed}%`);
|
||||
|
||||
const digits = trimmed.replace(/\D/g, '');
|
||||
if (!digits) return [...patterns];
|
||||
|
||||
patterns.add(`%${digits}%`);
|
||||
|
||||
let rest = digits;
|
||||
if (rest.startsWith('00')) rest = rest.slice(2);
|
||||
|
||||
if (rest.startsWith('98')) {
|
||||
patterns.add(`%+${rest}%`);
|
||||
const national = rest.slice(2);
|
||||
if (national) {
|
||||
patterns.add(`%0${national}%`);
|
||||
patterns.add(`%${national}%`);
|
||||
}
|
||||
} else if (rest.startsWith('0')) {
|
||||
const national = rest.slice(1);
|
||||
if (national) {
|
||||
patterns.add(`%+98${national}%`);
|
||||
patterns.add(`%98${national}%`);
|
||||
}
|
||||
} else if (rest.startsWith('9')) {
|
||||
patterns.add(`%+98${rest}%`);
|
||||
patterns.add(`%98${rest}%`);
|
||||
patterns.add(`%0${rest}%`);
|
||||
}
|
||||
|
||||
return [...patterns];
|
||||
}
|
||||
|
||||
function cellNumberIlikeFilter(input: string | undefined): Prisma.Sql {
|
||||
const patterns = cellNumberLikePatterns(input ?? '');
|
||||
if (!patterns.length) return Prisma.empty;
|
||||
return Prisma.sql`AND (${Prisma.join(
|
||||
patterns.map((pattern) => Prisma.sql`u.cell_number ILIKE ${pattern}`),
|
||||
' OR ',
|
||||
)})`;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
constructor(
|
||||
@@ -111,7 +161,6 @@ export class UsersService {
|
||||
const skip = (page - 1) * pageSize;
|
||||
|
||||
const nameLike = query.name?.trim() ? `%${query.name.trim()}%` : null;
|
||||
const cellLike = query.cellNumber?.trim() ? `%${query.cellNumber.trim()}%` : null;
|
||||
const roleSlug = query.role?.trim() || null;
|
||||
const businessId = query.businessId ?? null;
|
||||
const membership = query.membership ?? null;
|
||||
@@ -125,7 +174,7 @@ export class UsersService {
|
||||
OR (COALESCE(u.first_name, '') || ' ' || COALESCE(u.last_name, '')) ILIKE ${nameLike}
|
||||
)
|
||||
` : Prisma.empty}
|
||||
${cellLike ? Prisma.sql`AND u.cell_number ILIKE ${cellLike}` : Prisma.empty}
|
||||
${cellNumberIlikeFilter(query.cellNumber)}
|
||||
${roleSlug ? Prisma.sql`
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
@@ -346,6 +395,11 @@ export class UsersService {
|
||||
const q = query.q.trim();
|
||||
const limit = Math.min(Math.max(query.limit ?? 20, 1), 50);
|
||||
const like = `%${q}%`;
|
||||
const cellPatterns = cellNumberLikePatterns(q);
|
||||
const cellMatch = Prisma.join(
|
||||
cellPatterns.map((pattern) => Prisma.sql`u.cell_number ILIKE ${pattern}`),
|
||||
' OR ',
|
||||
);
|
||||
|
||||
const items = await this.prisma.$queryRaw<
|
||||
{
|
||||
@@ -365,7 +419,7 @@ export class UsersService {
|
||||
FROM users u
|
||||
WHERE u.is_active = TRUE
|
||||
AND (
|
||||
u.cell_number ILIKE ${like}
|
||||
${cellMatch}
|
||||
OR u.first_name ILIKE ${like}
|
||||
OR u.last_name ILIKE ${like}
|
||||
OR u.email ILIKE ${like}
|
||||
|
||||
Reference in New Issue
Block a user