add : PaymentCode And Customer Card
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsBoolean, IsInt, IsObject, IsOptional, IsString, MaxLength, Min } from 'class-validator';
|
||||
import { IsBoolean, IsInt, IsOptional, IsString, MaxLength, Min } from 'class-validator';
|
||||
|
||||
export class UpdatePaymentMethodSettingDto {
|
||||
@ApiPropertyOptional()
|
||||
@@ -36,10 +36,127 @@ export class UpdatePaymentMethodSettingDto {
|
||||
instructions?: string | null;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Gateway-specific config. Example: terminalId, merchantId, callbackUrl.',
|
||||
type: Object,
|
||||
description: 'آدرس callback یا return URL درگاه آنلاین.',
|
||||
example: 'https://admin.example.com/payment/callback/zarinpal',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
config?: Record<string, unknown>;
|
||||
@IsString()
|
||||
@MaxLength(500)
|
||||
callbackUrl?: string | null;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'کد مرچنت زرینپال',
|
||||
example: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(120)
|
||||
zarinpalMerchantId?: string | null;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'شماره ترمینال سامان',
|
||||
example: '1234567',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(120)
|
||||
samanTerminalId?: string | null;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'شماره ترمینال بهپرداخت ملت',
|
||||
example: '1234567',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(120)
|
||||
mellatTerminalId?: string | null;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'نام کاربری سرویس بهپرداخت ملت',
|
||||
example: 'bp_user',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(120)
|
||||
mellatUsername?: string | null;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'رمز عبور سرویس بهپرداخت ملت',
|
||||
example: 'secret-password',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(255)
|
||||
mellatPassword?: string | null;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'کد پذیرنده پاسارگاد',
|
||||
example: '12345678',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(120)
|
||||
pasargadMerchantCode?: string | null;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'کد ترمینال پاسارگاد',
|
||||
example: '1234567',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(120)
|
||||
pasargadTerminalCode?: string | null;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'محتوای PEM یا certificate متنی پاسارگاد',
|
||||
example: '-----BEGIN CERTIFICATE----- ...',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
pasargadCertificatePem?: string | null;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'نام بانک برای نمایش در ثبت فیش بانکی',
|
||||
example: 'بانک ملت',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(120)
|
||||
bankName?: string | null;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'نام صاحب حساب برای فیش بانکی',
|
||||
example: 'شرکت پارس شاپ',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(160)
|
||||
accountHolderName?: string | null;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'شماره حساب برای فیش بانکی',
|
||||
example: '1234567890',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(80)
|
||||
accountNumber?: string | null;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'شماره کارت برای فیش بانکی',
|
||||
example: '6037991234567890',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(30)
|
||||
cardNumber?: string | null;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'شماره شبا برای فیش بانکی',
|
||||
example: 'IR820540102680020817909002',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(40)
|
||||
shebaNumber?: string | null;
|
||||
}
|
||||
|
||||
@@ -44,8 +44,47 @@ export class PaymentMethodSetting {
|
||||
@Column({ type: 'text', nullable: true })
|
||||
instructions?: string | null;
|
||||
|
||||
@Column({ type: 'jsonb', default: {} })
|
||||
config: Record<string, unknown>;
|
||||
@Column({ name: 'callback_url', type: 'varchar', length: 500, nullable: true })
|
||||
callbackUrl?: string | null;
|
||||
|
||||
@Column({ name: 'zarinpal_merchant_id', type: 'varchar', length: 120, nullable: true })
|
||||
zarinpalMerchantId?: string | null;
|
||||
|
||||
@Column({ name: 'saman_terminal_id', type: 'varchar', length: 120, nullable: true })
|
||||
samanTerminalId?: string | null;
|
||||
|
||||
@Column({ name: 'mellat_terminal_id', type: 'varchar', length: 120, nullable: true })
|
||||
mellatTerminalId?: string | null;
|
||||
|
||||
@Column({ name: 'mellat_username', type: 'varchar', length: 120, nullable: true })
|
||||
mellatUsername?: string | null;
|
||||
|
||||
@Column({ name: 'mellat_password', type: 'varchar', length: 255, nullable: true })
|
||||
mellatPassword?: string | null;
|
||||
|
||||
@Column({ name: 'pasargad_merchant_code', type: 'varchar', length: 120, nullable: true })
|
||||
pasargadMerchantCode?: string | null;
|
||||
|
||||
@Column({ name: 'pasargad_terminal_code', type: 'varchar', length: 120, nullable: true })
|
||||
pasargadTerminalCode?: string | null;
|
||||
|
||||
@Column({ name: 'pasargad_certificate_pem', type: 'text', nullable: true })
|
||||
pasargadCertificatePem?: string | null;
|
||||
|
||||
@Column({ name: 'bank_name', type: 'varchar', length: 120, nullable: true })
|
||||
bankName?: string | null;
|
||||
|
||||
@Column({ name: 'account_holder_name', type: 'varchar', length: 160, nullable: true })
|
||||
accountHolderName?: string | null;
|
||||
|
||||
@Column({ name: 'account_number', type: 'varchar', length: 80, nullable: true })
|
||||
accountNumber?: string | null;
|
||||
|
||||
@Column({ name: 'card_number', type: 'varchar', length: 30, nullable: true })
|
||||
cardNumber?: string | null;
|
||||
|
||||
@Column({ name: 'sheba_number', type: 'varchar', length: 40, nullable: true })
|
||||
shebaNumber?: string | null;
|
||||
|
||||
@CreateDateColumn({ name: 'created_at' })
|
||||
createdAt: Date;
|
||||
|
||||
@@ -11,13 +11,13 @@ import { UserOrder } from '../users/entities/user-order.entity';
|
||||
import { OrderStatus } from '../users/enums/order-status.enum';
|
||||
import { PaymentStatus } from '../users/enums/payment-status.enum';
|
||||
import { UserRole } from '../users/enums/user-role.enum';
|
||||
import { PaymentMethodSetting } from './entities/payment-method-setting.entity';
|
||||
import { PaymentMethodCode } from './enums/payment-method-code.enum';
|
||||
import { PaymentMethodType } from './enums/payment-method-type.enum';
|
||||
import { ReviewBankSlipDto } from './dto/review-bank-slip.dto';
|
||||
import { SelectOrderPaymentMethodDto } from './dto/select-order-payment-method.dto';
|
||||
import { SubmitBankSlipDto } from './dto/submit-bank-slip.dto';
|
||||
import { UpdatePaymentMethodSettingDto } from './dto/update-payment-method-setting.dto';
|
||||
import { PaymentMethodSetting } from './entities/payment-method-setting.entity';
|
||||
import { PaymentMethodCode } from './enums/payment-method-code.enum';
|
||||
import { PaymentMethodType } from './enums/payment-method-type.enum';
|
||||
|
||||
@Injectable()
|
||||
export class PaymentsService {
|
||||
@@ -32,7 +32,20 @@ export class PaymentsService {
|
||||
| 'displayOrder'
|
||||
| 'description'
|
||||
| 'instructions'
|
||||
| 'config'
|
||||
| 'callbackUrl'
|
||||
| 'zarinpalMerchantId'
|
||||
| 'samanTerminalId'
|
||||
| 'mellatTerminalId'
|
||||
| 'mellatUsername'
|
||||
| 'mellatPassword'
|
||||
| 'pasargadMerchantCode'
|
||||
| 'pasargadTerminalCode'
|
||||
| 'pasargadCertificatePem'
|
||||
| 'bankName'
|
||||
| 'accountHolderName'
|
||||
| 'accountNumber'
|
||||
| 'cardNumber'
|
||||
| 'shebaNumber'
|
||||
>
|
||||
> = [
|
||||
{
|
||||
@@ -44,7 +57,20 @@ export class PaymentsService {
|
||||
displayOrder: 1,
|
||||
description: 'پرداخت آنلاین از طریق زرینپال',
|
||||
instructions: null,
|
||||
config: {},
|
||||
callbackUrl: null,
|
||||
zarinpalMerchantId: null,
|
||||
samanTerminalId: null,
|
||||
mellatTerminalId: null,
|
||||
mellatUsername: null,
|
||||
mellatPassword: null,
|
||||
pasargadMerchantCode: null,
|
||||
pasargadTerminalCode: null,
|
||||
pasargadCertificatePem: null,
|
||||
bankName: null,
|
||||
accountHolderName: null,
|
||||
accountNumber: null,
|
||||
cardNumber: null,
|
||||
shebaNumber: null,
|
||||
},
|
||||
{
|
||||
code: PaymentMethodCode.SAMAN,
|
||||
@@ -55,7 +81,20 @@ export class PaymentsService {
|
||||
displayOrder: 2,
|
||||
description: 'پرداخت آنلاین از طریق بانک سامان',
|
||||
instructions: null,
|
||||
config: {},
|
||||
callbackUrl: null,
|
||||
zarinpalMerchantId: null,
|
||||
samanTerminalId: null,
|
||||
mellatTerminalId: null,
|
||||
mellatUsername: null,
|
||||
mellatPassword: null,
|
||||
pasargadMerchantCode: null,
|
||||
pasargadTerminalCode: null,
|
||||
pasargadCertificatePem: null,
|
||||
bankName: null,
|
||||
accountHolderName: null,
|
||||
accountNumber: null,
|
||||
cardNumber: null,
|
||||
shebaNumber: null,
|
||||
},
|
||||
{
|
||||
code: PaymentMethodCode.MELLAT,
|
||||
@@ -66,7 +105,20 @@ export class PaymentsService {
|
||||
displayOrder: 3,
|
||||
description: 'پرداخت آنلاین از طریق بانک ملت',
|
||||
instructions: null,
|
||||
config: {},
|
||||
callbackUrl: null,
|
||||
zarinpalMerchantId: null,
|
||||
samanTerminalId: null,
|
||||
mellatTerminalId: null,
|
||||
mellatUsername: null,
|
||||
mellatPassword: null,
|
||||
pasargadMerchantCode: null,
|
||||
pasargadTerminalCode: null,
|
||||
pasargadCertificatePem: null,
|
||||
bankName: null,
|
||||
accountHolderName: null,
|
||||
accountNumber: null,
|
||||
cardNumber: null,
|
||||
shebaNumber: null,
|
||||
},
|
||||
{
|
||||
code: PaymentMethodCode.PASARGAD,
|
||||
@@ -77,7 +129,20 @@ export class PaymentsService {
|
||||
displayOrder: 4,
|
||||
description: 'پرداخت آنلاین از طریق بانک پاسارگاد',
|
||||
instructions: null,
|
||||
config: {},
|
||||
callbackUrl: null,
|
||||
zarinpalMerchantId: null,
|
||||
samanTerminalId: null,
|
||||
mellatTerminalId: null,
|
||||
mellatUsername: null,
|
||||
mellatPassword: null,
|
||||
pasargadMerchantCode: null,
|
||||
pasargadTerminalCode: null,
|
||||
pasargadCertificatePem: null,
|
||||
bankName: null,
|
||||
accountHolderName: null,
|
||||
accountNumber: null,
|
||||
cardNumber: null,
|
||||
shebaNumber: null,
|
||||
},
|
||||
{
|
||||
code: PaymentMethodCode.BANK_SLIP,
|
||||
@@ -89,7 +154,20 @@ export class PaymentsService {
|
||||
description: 'ثبت فیش بانکی و بررسی توسط ادمین',
|
||||
instructions:
|
||||
'پس از واریز، تصویر فیش و شماره پیگیری را ثبت کنید. سفارش در وضعیت منتظر تایید پرداخت قرار میگیرد.',
|
||||
config: {},
|
||||
callbackUrl: null,
|
||||
zarinpalMerchantId: null,
|
||||
samanTerminalId: null,
|
||||
mellatTerminalId: null,
|
||||
mellatUsername: null,
|
||||
mellatPassword: null,
|
||||
pasargadMerchantCode: null,
|
||||
pasargadTerminalCode: null,
|
||||
pasargadCertificatePem: null,
|
||||
bankName: null,
|
||||
accountHolderName: null,
|
||||
accountNumber: null,
|
||||
cardNumber: null,
|
||||
shebaNumber: null,
|
||||
},
|
||||
{
|
||||
code: PaymentMethodCode.CASH_ON_DELIVERY,
|
||||
@@ -100,7 +178,20 @@ export class PaymentsService {
|
||||
displayOrder: 6,
|
||||
description: 'پرداخت هنگام تحویل کالا',
|
||||
instructions: 'سفارش ثبت میشود و پرداخت هنگام تحویل انجام خواهد شد.',
|
||||
config: {},
|
||||
callbackUrl: null,
|
||||
zarinpalMerchantId: null,
|
||||
samanTerminalId: null,
|
||||
mellatTerminalId: null,
|
||||
mellatUsername: null,
|
||||
mellatPassword: null,
|
||||
pasargadMerchantCode: null,
|
||||
pasargadTerminalCode: null,
|
||||
pasargadCertificatePem: null,
|
||||
bankName: null,
|
||||
accountHolderName: null,
|
||||
accountNumber: null,
|
||||
cardNumber: null,
|
||||
shebaNumber: null,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -133,11 +224,43 @@ export class PaymentsService {
|
||||
...(dto.title !== undefined ? { title: dto.title } : {}),
|
||||
...(dto.description !== undefined ? { description: dto.description } : {}),
|
||||
...(dto.instructions !== undefined ? { instructions: dto.instructions } : {}),
|
||||
...(dto.config !== undefined
|
||||
? { config: { ...(method.config ?? {}), ...dto.config } }
|
||||
...(dto.callbackUrl !== undefined ? { callbackUrl: dto.callbackUrl } : {}),
|
||||
...(dto.zarinpalMerchantId !== undefined
|
||||
? { zarinpalMerchantId: dto.zarinpalMerchantId }
|
||||
: {}),
|
||||
...(dto.samanTerminalId !== undefined
|
||||
? { samanTerminalId: dto.samanTerminalId }
|
||||
: {}),
|
||||
...(dto.mellatTerminalId !== undefined
|
||||
? { mellatTerminalId: dto.mellatTerminalId }
|
||||
: {}),
|
||||
...(dto.mellatUsername !== undefined
|
||||
? { mellatUsername: dto.mellatUsername }
|
||||
: {}),
|
||||
...(dto.mellatPassword !== undefined
|
||||
? { mellatPassword: dto.mellatPassword }
|
||||
: {}),
|
||||
...(dto.pasargadMerchantCode !== undefined
|
||||
? { pasargadMerchantCode: dto.pasargadMerchantCode }
|
||||
: {}),
|
||||
...(dto.pasargadTerminalCode !== undefined
|
||||
? { pasargadTerminalCode: dto.pasargadTerminalCode }
|
||||
: {}),
|
||||
...(dto.pasargadCertificatePem !== undefined
|
||||
? { pasargadCertificatePem: dto.pasargadCertificatePem }
|
||||
: {}),
|
||||
...(dto.bankName !== undefined ? { bankName: dto.bankName } : {}),
|
||||
...(dto.accountHolderName !== undefined
|
||||
? { accountHolderName: dto.accountHolderName }
|
||||
: {}),
|
||||
...(dto.accountNumber !== undefined
|
||||
? { accountNumber: dto.accountNumber }
|
||||
: {}),
|
||||
...(dto.cardNumber !== undefined ? { cardNumber: dto.cardNumber } : {}),
|
||||
...(dto.shebaNumber !== undefined ? { shebaNumber: dto.shebaNumber } : {}),
|
||||
});
|
||||
|
||||
this.validateMethodCredentials(method);
|
||||
return this.paymentMethodSettingsRepository.save(method);
|
||||
}
|
||||
|
||||
@@ -185,6 +308,7 @@ export class PaymentsService {
|
||||
paymentUrl: this.buildMockGatewayUrl(savedOrder, method),
|
||||
sandboxEnabled: method.isSandboxEnabled,
|
||||
gateway: method.code,
|
||||
callbackUrl: method.callbackUrl ?? null,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -203,6 +327,7 @@ export class PaymentsService {
|
||||
action: {
|
||||
type: 'upload_bank_slip',
|
||||
instructions: method.instructions,
|
||||
bankAccount: this.serializeBankSlipInfo(method),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -241,7 +366,10 @@ export class PaymentsService {
|
||||
}
|
||||
|
||||
if (file) {
|
||||
const upload = await this.storageService.uploadPublicFile(file, 'payments/bank-slips');
|
||||
const upload = await this.storageService.uploadPublicFile(
|
||||
file,
|
||||
'payments/bank-slips',
|
||||
);
|
||||
if (order.bankSlipImageUrl && order.bankSlipImageUrl !== upload.url) {
|
||||
await this.storageService.deletePublicFileByUrl(order.bankSlipImageUrl);
|
||||
}
|
||||
@@ -418,9 +546,80 @@ export class PaymentsService {
|
||||
instructions: method.instructions,
|
||||
isSandboxEnabled: method.isSandboxEnabled,
|
||||
displayOrder: method.displayOrder,
|
||||
...(method.type === PaymentMethodType.BANK_SLIP
|
||||
? {
|
||||
bankAccount: this.serializeBankSlipInfo(method),
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
private serializeBankSlipInfo(method: PaymentMethodSetting) {
|
||||
return {
|
||||
bankName: method.bankName ?? null,
|
||||
accountHolderName: method.accountHolderName ?? null,
|
||||
accountNumber: method.accountNumber ?? null,
|
||||
cardNumber: method.cardNumber ?? null,
|
||||
shebaNumber: method.shebaNumber ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
private validateMethodCredentials(method: PaymentMethodSetting) {
|
||||
if (!method.isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (method.code) {
|
||||
case PaymentMethodCode.ZARINPAL:
|
||||
this.ensureRequiredFields(method, ['zarinpalMerchantId', 'callbackUrl']);
|
||||
break;
|
||||
case PaymentMethodCode.SAMAN:
|
||||
this.ensureRequiredFields(method, ['samanTerminalId', 'callbackUrl']);
|
||||
break;
|
||||
case PaymentMethodCode.MELLAT:
|
||||
this.ensureRequiredFields(method, [
|
||||
'mellatTerminalId',
|
||||
'mellatUsername',
|
||||
'mellatPassword',
|
||||
'callbackUrl',
|
||||
]);
|
||||
break;
|
||||
case PaymentMethodCode.PASARGAD:
|
||||
this.ensureRequiredFields(method, [
|
||||
'pasargadMerchantCode',
|
||||
'pasargadTerminalCode',
|
||||
'pasargadCertificatePem',
|
||||
'callbackUrl',
|
||||
]);
|
||||
break;
|
||||
case PaymentMethodCode.BANK_SLIP:
|
||||
this.ensureRequiredFields(method, [
|
||||
'bankName',
|
||||
'accountHolderName',
|
||||
'accountNumber',
|
||||
]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private ensureRequiredFields(
|
||||
method: PaymentMethodSetting,
|
||||
fields: Array<keyof PaymentMethodSetting>,
|
||||
) {
|
||||
const missingFields = fields.filter((field) => {
|
||||
const value = method[field];
|
||||
return value === null || value === undefined || value === '';
|
||||
});
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
throw new BadRequestException(
|
||||
`Missing required fields for ${method.code}: ${missingFields.join(', ')}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private mapOrderPayment(order: UserOrder) {
|
||||
return {
|
||||
id: order.id,
|
||||
@@ -441,10 +640,7 @@ export class PaymentsService {
|
||||
};
|
||||
}
|
||||
|
||||
private buildMockGatewayUrl(
|
||||
order: UserOrder,
|
||||
method: PaymentMethodSetting,
|
||||
) {
|
||||
private buildMockGatewayUrl(order: UserOrder, method: PaymentMethodSetting) {
|
||||
const mode = method.isSandboxEnabled ? 'sandbox' : 'production';
|
||||
return `https://payments.mock.local/${method.code}/${mode}/pay?orderId=${order.id}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user