mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2026-03-10 15:11:46 +00:00
refactor: remove address fields from customer entity and related services
- Removed address-related fields from Customer entity, DTOs, and services to streamline KYC process. - Updated KYC initiation and customer update logic to default to Saudi Arabia for country and use fixed address values. - Added migration to drop address columns from the database.
This commit is contained in:
@ -42,13 +42,11 @@ export class CardService {
|
||||
throw new BadRequestException('CUSTOMER.ALREADY_HAS_CARD');
|
||||
}
|
||||
|
||||
// Validate required address fields for card creation
|
||||
// Validate required fields for card creation
|
||||
const missingFields = [];
|
||||
if (!customer.country) missingFields.push('country');
|
||||
if (!customer.city) missingFields.push('city');
|
||||
if (!customer.region) missingFields.push('region');
|
||||
if (!customer.nationalId) missingFields.push('nationalId');
|
||||
if (!customer.dateOfBirth) missingFields.push('dateOfBirth');
|
||||
if (!customer.nationalIdExpiry) missingFields.push('nationalIdExpiry');
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
throw new BadRequestException(
|
||||
|
||||
@ -93,24 +93,22 @@ export class NeoLeapService {
|
||||
incomeSource: dto.incomeSource,
|
||||
jobCategory: dto.jobCategory,
|
||||
incomeRange: dto.incomeRange,
|
||||
// Map collected address fields to Neoleap's format
|
||||
// Use default address values for Neoleap KYC
|
||||
address: {
|
||||
national: {
|
||||
buildingNumber: dto.building || '',
|
||||
buildingNumber: '1',
|
||||
additionalNumber: '',
|
||||
street: dto.street || '',
|
||||
streetEn: dto.street || '',
|
||||
city: dto.city || '',
|
||||
cityEn: dto.city || '',
|
||||
street: 'King Fahd Road',
|
||||
streetEn: 'King Fahd Road',
|
||||
city: 'Riyadh',
|
||||
cityEn: 'Riyadh',
|
||||
zipcode: '',
|
||||
unitNumber: '',
|
||||
district: dto.neighborhood || '',
|
||||
districtEn: dto.neighborhood || '',
|
||||
district: 'Al Olaya',
|
||||
districtEn: 'Al Olaya',
|
||||
},
|
||||
general: {
|
||||
address: [dto.building, dto.street, dto.neighborhood, dto.city, dto.region]
|
||||
.filter(Boolean)
|
||||
.join(', '),
|
||||
address: '1, King Fahd Road, Al Olaya, Riyadh, Riyadh',
|
||||
website: '',
|
||||
email: dto.email || '',
|
||||
telephone1: dto.mobileNumber || '',
|
||||
@ -203,14 +201,14 @@ export class NeoLeapService {
|
||||
Title: customer.gender === Gender.MALE ? 'Mr' : 'Ms',
|
||||
Gender: customer.gender === Gender.MALE ? 'M' : 'F',
|
||||
LocalizedDateOfBirth: moment(customer.dateOfBirth).format('YYYY-MM-DD'),
|
||||
Nationality: CountriesNumericISO[customer.countryOfResidence],
|
||||
Nationality: CountriesNumericISO[customer.countryOfResidence || 'SA'],
|
||||
},
|
||||
ApplicationAddress: {
|
||||
City: customer.city,
|
||||
Country: CountriesNumericISO[customer.country],
|
||||
Region: customer.region,
|
||||
AddressLine1: `${customer.street} ${customer.building}`,
|
||||
AddressLine2: customer.neighborhood,
|
||||
City: 'Riyadh',
|
||||
Country: CountriesNumericISO['SA'],
|
||||
Region: 'Riyadh',
|
||||
AddressLine1: 'King Fahd Road 1',
|
||||
AddressLine2: 'Al Olaya',
|
||||
AddressRole: 0,
|
||||
Email: customer.user.email,
|
||||
Phone1: customer.user.phoneNumber,
|
||||
@ -279,14 +277,14 @@ export class NeoLeapService {
|
||||
Title: parent.gender === Gender.MALE ? 'Mr' : 'Ms',
|
||||
Gender: parent.gender === Gender.MALE ? 'M' : 'F',
|
||||
LocalizedDateOfBirth: moment(parent.dateOfBirth).format('YYYY-MM-DD'),
|
||||
Nationality: CountriesNumericISO[parent.countryOfResidence],
|
||||
Nationality: CountriesNumericISO[parent.countryOfResidence || 'SA'],
|
||||
},
|
||||
ApplicationAddress: {
|
||||
City: parent.city,
|
||||
Country: CountriesNumericISO[parent.country],
|
||||
Region: parent.region,
|
||||
AddressLine1: `${parent.street} ${parent.building}`,
|
||||
AddressLine2: parent.neighborhood,
|
||||
City: 'Riyadh',
|
||||
Country: CountriesNumericISO['SA'],
|
||||
Region: 'Riyadh',
|
||||
AddressLine1: 'King Fahd Road 1',
|
||||
AddressLine2: 'Al Olaya',
|
||||
AddressRole: 0,
|
||||
Email: child.user.email,
|
||||
Phone1: child.user.phoneNumber,
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsDateString, IsEmail, IsEnum, IsOptional, IsString, Matches } from 'class-validator';
|
||||
import { i18nValidationMessage as i18n } from 'nestjs-i18n';
|
||||
import { CountryIso } from '~/common/enums';
|
||||
import { IncomeRange, IncomeSource, JobCategory, JobSector, PoiType } from '~/customer/enums';
|
||||
import { Gender, IncomeRange, IncomeSource, JobCategory, JobSector, PoiType } from '~/customer/enums';
|
||||
|
||||
export class InitiateKycRequestDto {
|
||||
@ApiProperty({ example: '2586234623', description: 'Saudi National ID or Iqama number' })
|
||||
@ -26,6 +25,14 @@ export class InitiateKycRequestDto {
|
||||
@IsDateString({}, { message: i18n('validation.IsDateString', { path: 'general', property: 'customer.dateOfBirth' }) })
|
||||
dateOfBirth!: string;
|
||||
|
||||
@ApiProperty({ example: '2030-12-31', format: 'date', description: 'National ID expiry date' })
|
||||
@IsDateString({}, { message: i18n('validation.IsDateString', { path: 'general', property: 'customer.nationalIdExpiry' }) })
|
||||
nationalIdExpiry!: string;
|
||||
|
||||
@ApiProperty({ enum: Gender, example: Gender.MALE })
|
||||
@IsEnum(Gender, { message: i18n('validation.IsEnum', { path: 'general', property: 'customer.gender' }) })
|
||||
gender!: Gender;
|
||||
|
||||
@ApiProperty({ enum: JobSector, example: JobSector.PRIVATE_SECTOR })
|
||||
@IsEnum(JobSector, { message: i18n('validation.IsEnum', { path: 'general', property: 'customer.jobSector' }) })
|
||||
jobSector!: JobSector;
|
||||
@ -45,37 +52,4 @@ export class InitiateKycRequestDto {
|
||||
@ApiProperty({ enum: IncomeRange, example: IncomeRange.RANGE_10000_20000 })
|
||||
@IsEnum(IncomeRange, { message: i18n('validation.IsEnum', { path: 'general', property: 'customer.incomeRange' }) })
|
||||
incomeRange!: IncomeRange;
|
||||
|
||||
// Address fields (optional - can be provided here or during registration)
|
||||
@ApiProperty({ example: 'SA', description: 'Country code', required: false })
|
||||
@IsEnum(CountryIso, {
|
||||
message: i18n('validation.IsEnum', { path: 'general', property: 'customer.country' }),
|
||||
})
|
||||
@IsOptional()
|
||||
country?: CountryIso;
|
||||
|
||||
@ApiProperty({ example: 'Riyadh', description: 'Region/Province', required: false })
|
||||
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'customer.region' }) })
|
||||
@IsOptional()
|
||||
region?: string;
|
||||
|
||||
@ApiProperty({ example: 'Riyadh', description: 'City', required: false })
|
||||
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'customer.city' }) })
|
||||
@IsOptional()
|
||||
city?: string;
|
||||
|
||||
@ApiProperty({ example: 'Al Olaya', description: 'Neighborhood/District', required: false })
|
||||
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'customer.neighborhood' }) })
|
||||
@IsOptional()
|
||||
neighborhood?: string;
|
||||
|
||||
@ApiProperty({ example: 'King Fahd Road', description: 'Street name', required: false })
|
||||
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'customer.street' }) })
|
||||
@IsOptional()
|
||||
street?: string;
|
||||
|
||||
@ApiProperty({ example: '123', description: 'Building number', required: false })
|
||||
@IsString({ message: i18n('validation.IsString', { path: 'general', property: 'customer.building' }) })
|
||||
@IsOptional()
|
||||
building?: string;
|
||||
}
|
||||
|
||||
@ -49,24 +49,6 @@ export class CustomerResponseDto {
|
||||
@ApiProperty({ example: 12345 })
|
||||
waitingNumber!: number;
|
||||
|
||||
@ApiProperty({ example: 'SA' })
|
||||
country!: string | null;
|
||||
|
||||
@ApiProperty({ example: 'Riyadh' })
|
||||
region!: string | null;
|
||||
|
||||
@ApiProperty({ example: 'Riyadh City' })
|
||||
city!: string | null;
|
||||
|
||||
@ApiProperty({ example: 'Al-Masif' })
|
||||
neighborhood!: string | null;
|
||||
|
||||
@ApiProperty({ example: 'King Fahd Road' })
|
||||
street!: string | null;
|
||||
|
||||
@ApiProperty({ example: '123' })
|
||||
building!: string | null;
|
||||
|
||||
@ApiPropertyOptional({ type: DocumentMetaResponseDto })
|
||||
profilePicture!: DocumentMetaResponseDto | null;
|
||||
|
||||
@ -86,11 +68,5 @@ export class CustomerResponseDto {
|
||||
this.isJunior = customer.isJunior;
|
||||
this.isGuardian = customer.isGuardian;
|
||||
this.waitingNumber = customer.applicationNumber;
|
||||
this.country = customer.country;
|
||||
this.region = customer.region;
|
||||
this.city = customer.city;
|
||||
this.neighborhood = customer.neighborhood;
|
||||
this.street = customer.street;
|
||||
this.building = customer.building;
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,24 +68,6 @@ export class Customer extends BaseEntity {
|
||||
@Column('varchar', { name: 'user_id' })
|
||||
userId!: string;
|
||||
|
||||
@Column('varchar', { name: 'country', length: 255, nullable: true })
|
||||
country!: CountryIso;
|
||||
|
||||
@Column('varchar', { name: 'region', length: 255, nullable: true })
|
||||
region!: string;
|
||||
|
||||
@Column('varchar', { name: 'city', length: 255, nullable: true })
|
||||
city!: string;
|
||||
|
||||
@Column('varchar', { name: 'neighborhood', length: 255, nullable: true })
|
||||
neighborhood!: string;
|
||||
|
||||
@Column('varchar', { name: 'street', length: 255, nullable: true })
|
||||
street!: string;
|
||||
|
||||
@Column('varchar', { name: 'building', length: 255, nullable: true })
|
||||
building!: string;
|
||||
|
||||
// KYC-specific fields
|
||||
@Column('varchar', { length: 255, nullable: true, name: 'neoleap_external_customer_id' })
|
||||
neoleapExternalCustomerId!: string | null;
|
||||
|
||||
@ -30,13 +30,6 @@ export class CustomerRepository {
|
||||
dateOfBirth: body.dateOfBirth,
|
||||
countryOfResidence: body.countryOfResidence,
|
||||
gender: body.gender,
|
||||
// Address fields
|
||||
country: body.country,
|
||||
region: body.region,
|
||||
city: body.city,
|
||||
neighborhood: body.neighborhood,
|
||||
street: body.street,
|
||||
building: body.building,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@ -73,10 +73,13 @@ export class CustomerService {
|
||||
throw new ConflictException('KYC verification already in progress for this National ID');
|
||||
}
|
||||
|
||||
// Update customer with KYC data (including address if provided)
|
||||
// Update customer with KYC data
|
||||
await this.customerRepository.updateCustomer(customerId, {
|
||||
nationalId: body.poiNumber,
|
||||
dateOfBirth: new Date(body.dateOfBirth),
|
||||
nationalIdExpiry: new Date(body.nationalIdExpiry),
|
||||
gender: body.gender,
|
||||
countryOfResidence: CountryIso.SAUDI_ARABIA, // Always default to Saudi Arabia
|
||||
mobileNumber: body.mobileNumber,
|
||||
jobSector: body.jobSector,
|
||||
employer: body.employer,
|
||||
@ -84,13 +87,6 @@ export class CustomerService {
|
||||
jobCategory: body.jobCategory,
|
||||
incomeRange: body.incomeRange,
|
||||
kycStatus: KycStatus.PENDING,
|
||||
// Address fields (only update if provided)
|
||||
...(body.country && { country: body.country }),
|
||||
...(body.region && { region: body.region }),
|
||||
...(body.city && { city: body.city }),
|
||||
...(body.neighborhood && { neighborhood: body.neighborhood }),
|
||||
...(body.street && { street: body.street }),
|
||||
...(body.building && { building: body.building }),
|
||||
});
|
||||
|
||||
// Call Neoleap KYC API
|
||||
@ -184,12 +180,6 @@ export class CustomerService {
|
||||
nationalId: '1089055972',
|
||||
nationalIdExpiry: moment('2031-09-17').toDate(),
|
||||
countryOfResidence: CountryIso.SAUDI_ARABIA,
|
||||
country: CountryIso.SAUDI_ARABIA,
|
||||
region: 'Mecca',
|
||||
city: 'AT Taif',
|
||||
neighborhood: 'Al Faisaliah',
|
||||
street: 'Al Faisaliah Street',
|
||||
building: '4',
|
||||
});
|
||||
|
||||
await User.update(userId, {
|
||||
|
||||
26
src/db/migrations/1765975126402-RemoveAddressColumns.ts
Normal file
26
src/db/migrations/1765975126402-RemoveAddressColumns.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class RemoveAddressColumns1765975126402 implements MigrationInterface {
|
||||
name = 'RemoveAddressColumns1765975126402';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
// Drop address columns from customers table
|
||||
await queryRunner.query(`ALTER TABLE "customers" DROP COLUMN IF EXISTS "country"`);
|
||||
await queryRunner.query(`ALTER TABLE "customers" DROP COLUMN IF EXISTS "region"`);
|
||||
await queryRunner.query(`ALTER TABLE "customers" DROP COLUMN IF EXISTS "city"`);
|
||||
await queryRunner.query(`ALTER TABLE "customers" DROP COLUMN IF EXISTS "neighborhood"`);
|
||||
await queryRunner.query(`ALTER TABLE "customers" DROP COLUMN IF EXISTS "street"`);
|
||||
await queryRunner.query(`ALTER TABLE "customers" DROP COLUMN IF EXISTS "building"`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
// Re-add address columns in case of rollback
|
||||
await queryRunner.query(`ALTER TABLE "customers" ADD "country" varchar(255)`);
|
||||
await queryRunner.query(`ALTER TABLE "customers" ADD "region" varchar(255)`);
|
||||
await queryRunner.query(`ALTER TABLE "customers" ADD "city" varchar(255)`);
|
||||
await queryRunner.query(`ALTER TABLE "customers" ADD "neighborhood" varchar(255)`);
|
||||
await queryRunner.query(`ALTER TABLE "customers" ADD "street" varchar(255)`);
|
||||
await queryRunner.query(`ALTER TABLE "customers" ADD "building" varchar(255)`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,4 +8,5 @@ export * from './1760869651296-AddMerchantInfoToTransactions';
|
||||
export * from './1761032305682-AddUniqueConstraintToUserEmail';
|
||||
export * from './1765804942393-AddKycFieldsAndTransactions';
|
||||
export * from './1765877128065-AddNationalIdToKycTransactions';
|
||||
export * from './1765891028260-RemoveOldCustomerColumns';
|
||||
export * from './1765891028260-RemoveOldCustomerColumns';
|
||||
export * from './1765975126402-RemoveAddressColumns';
|
||||
@ -65,13 +65,6 @@ export class UserService {
|
||||
firstName: body.firstName,
|
||||
lastName: body.lastName,
|
||||
countryOfResidence: body.countryOfResidence,
|
||||
// Address fields (optional during registration)
|
||||
country: body.country,
|
||||
region: body.region,
|
||||
city: body.city,
|
||||
neighborhood: body.neighborhood,
|
||||
street: body.street,
|
||||
building: body.building,
|
||||
}),
|
||||
this.userRepository.update(userId, {
|
||||
isPhoneVerified: true,
|
||||
|
||||
Reference in New Issue
Block a user