Skip to main content

Client Module

The client module handles client management operations including client creation, instance management, organizational structure, PSP assignment, and Xendit integration for Indonesia clients.

Import​

const client = await brdzSDK.client;

Methods Overview​

MethodDescriptionAuth RequiredHTTP Endpoint
createClientWithAdminCreate client with admin user❌POST /clients/create_with_admin
getAllClientsGet all clients✅GET /clients
getMyInstancesGet my client instances✅GET /clients/my-instances
createInstanceCreate new instance✅POST /clients/create-instance
updateInstanceAliasUpdate instance alias✅PUT /clients/:instance_id/update-alias
deleteInstanceDelete instance✅DELETE /clients/clients/:instance_id

createClientWithAdmin​

Create a new client organization with an admin user. Automatically generates client code (CL-YYYYMM-XXXX format), assigns PSP based on country, creates admin user with secure password, sends welcome email, and creates Xendit customers for Indonesia clients.

Syntax​

const result = await client.createClientWithAdmin(clientData);

Parameters​

ParameterTypeRequiredDescription
clientDataobject✅Client creation data
clientData.emailstring✅Admin email address (must be unique)
clientData.client_aliasstring✅Client organization name
clientData.client_typestring✅Type of client organization (CORPORATE, STARTUP, etc.)
clientData.country_codestring✅Country code for PSP assignment (2-letter ISO)
clientData.phonestring✅Contact phone number with country code
clientData.client_statusstring❌Client status (default: "PENDING")

Returns​

Promise<{
message: string;
client: {
client_id: number;
client_code: string;
email: string;
client_alias: string;
client_type: string;
client_status: string;
ekyc_status: string;
country_code: string;
psp_id: number;
phone: string;
};
admin_user: {
user_id: number;
client_id: number;
username: string;
email: string;
phone: string;
role: string;
user_status: string;
ekyc_status: string;
};
ekyc_status: string;
xendit_integration?: {
psp_provider: string;
business_customer: {
status: string;
customer_id: string;
customer_type: string;
} | null;
admin_customer: {
status: string;
customer_id: string;
customer_type: string;
} | null;
};
}>

Example​

try {
// Create Indonesia client with Xendit integration
const newClient = await client.createClientWithAdmin({
email: 'admin@techcompany.com',
client_alias: 'Tech Company Ltd',
client_type: 'CORPORATE',
country_code: 'ID',
phone: '+628123456789',
client_status: 'PENDING'
});

console.log('Client Created:', newClient.client.client_code);
console.log('Admin User ID:', newClient.admin_user.user_id);
console.log('PSP ID:', newClient.client.psp_id);
console.log('Message:', newClient.message);

// Client code format: CL-YYYYMM-XXXX (auto-generated)
console.log('Generated Code:', newClient.client.client_code);

// Check Xendit integration for Indonesia clients
if (newClient.xendit_integration) {
console.log('PSP Provider:', newClient.xendit_integration.psp_provider);

if (newClient.xendit_integration.business_customer) {
console.log('Business Customer ID:', newClient.xendit_integration.business_customer.customer_id);
console.log('Customer Type:', newClient.xendit_integration.business_customer.customer_type);
}

if (newClient.xendit_integration.admin_customer) {
console.log('Admin Customer ID:', newClient.xendit_integration.admin_customer.customer_id);
console.log('Customer Type:', newClient.xendit_integration.admin_customer.customer_type);
}
} else {
console.log('No Xendit integration (non-Indonesia client)');
}

// Admin will receive email with login credentials
console.log('Admin Email Sent:', newClient.admin_user.email);

} catch (error) {
console.error('Client creation failed:', error.message);

if (error.message.includes('Email is already in use')) {
console.error('Email conflict - use different email');
} else if (error.message.includes('No PSP available')) {
console.error('PSP not configured for this country');
} else if (error.message.includes('No FIAT_PSP PSP available')) {
console.error('No FIAT PSP configured for this country');
}
}

Features​

  • Automatic Client Code: Generates unique code in CL-YYYYMM-XXXX format
  • PSP Assignment: Country-based Payment Service Provider selection with fallback
  • Admin User Creation: Secure password generation with bcrypt hashing
  • Email Notification: Welcome email with login credentials sent automatically
  • eKYC Initialization: Sets up PENDING eKYC status for compliance
  • Xendit Integration: Auto-creates business and individual customers for Indonesia
  • Database Transaction: Rollback safety on any failure
  • Email Logging: Tracks email delivery status

PSP Assignment​

The system automatically assigns PSP based on country code:

  • Indonesia (ID): XENDIT (PSP ID: 11) with Xendit customer creation
  • Singapore (SG): PSP_Singapore (PSP ID: 2)
  • United States (US): PSP_USA (PSP ID: 4)
  • Other countries: Fallback to available FIAT_PSP

Xendit Integration (Indonesia Only)​

For country_code = 'ID', the system creates:

  1. Business Customer: For the client organization

    • Customer Type: BUSINESS
    • Links to client_id
    • Used for business operations
  2. Individual Customer: For the admin user

    • Customer Type: INDIVIDUAL
    • Links to user_id
    • Personal account for admin

HTTP Endpoint​

  • Method: POST
  • URL: https://api.brdz.link/api/clients/create_with_admin
  • Headers: No authentication required (public endpoint)

getAllClients​

Retrieve list of all clients in the system. Admin access required. Returns clients with PSP and contact information.

Syntax​

const result = await client.getAllClients(params);

Parameters​

ParameterTypeRequiredDescription
paramsobject❌Query parameters (optional)

Returns​

Promise<Array<{
client_id: number;
client_code: string;
email: string;
client_alias: string;
client_type: string;
client_status: string;
ekyc_status: string;
country_code: string;
psp_id: number;
phone: string;
created: string;
updated: string;
}>>

Example​

try {
const clients = await client.getAllClients();

console.log('Total Clients:', clients.length);

clients.forEach(client => {
console.log(`${client.client_alias} (${client.client_code})`);
console.log(` Status: ${client.client_status}`);
console.log(` eKYC: ${client.ekyc_status}`);
console.log(` Country: ${client.country_code}, PSP ID: ${client.psp_id}`);
console.log('---');
});

// Filter by status
const activeClients = clients.filter(c => c.client_status === 'ACTIVE');
const pendingClients = clients.filter(c => c.client_status === 'PENDING');
const indonesiaClients = clients.filter(c => c.country_code === 'ID');

console.log(`Active: ${activeClients.length}, Pending: ${pendingClients.length}`);
console.log(`Indonesia Clients: ${indonesiaClients.length} (with Xendit)`);

} catch (error) {
console.error('Failed to get clients:', error.message);
}

HTTP Endpoint​

  • Method: GET
  • URL: https://api.brdz.link/api/clients
  • Headers: Authorization: Bearer <JWT_TOKEN>, x-api-key required

getMyInstances​

Get instances belonging to the logged-in admin's client. Returns instance information with client details.

Syntax​

const result = await client.getMyInstances();

Parameters​

None.

Returns​

Promise<{
instances: Array<{
instance_id: number;
client_id: number;
instance_name: string;
client_alias: string | null;
instance_status: string;
ekyc_status: string;
client_code: string;
country_code: string;
}>;
}>

Example​

try {
const result = await client.getMyInstances();

console.log('My Instances:', result.instances.length);

if (result.instances.length > 0) {
const firstInstance = result.instances[0];
console.log('Client Code:', firstInstance.client_code);
console.log('Country:', firstInstance.country_code);
console.log('eKYC Status:', firstInstance.ekyc_status);

// Check if client can create more instances
if (firstInstance.ekyc_status === 'APPROVED') {
console.log('✅ Client is KYC approved - full functionality available');
} else {
console.log('⚠️ Client KYC pending - limited functionality');
}
}

result.instances.forEach(instance => {
console.log(`${instance.instance_name}: ${instance.instance_status}`);
console.log(` Alias: ${instance.client_alias || 'No alias set'}`);
console.log(` ID: ${instance.instance_id}`);
});

// Filter active instances
const activeInstances = result.instances.filter(i =>
i.instance_status === 'ACTIVE'
);

console.log('Active Instances:', activeInstances.length);

} catch (error) {
console.error('Failed to get instances:', error.message);

if (error.message.includes('Client not found')) {
console.error('User not associated with any client');
}
}

HTTP Endpoint​

  • Method: GET
  • URL: https://api.brdz.link/api/clients/my-instances
  • Headers: Authorization: Bearer <JWT_TOKEN>, x-api-key required

createInstance​

Create a new instance for the logged-in admin's client. Instance belongs to the client automatically.

Syntax​

const result = await client.createInstance(instanceData);

Parameters​

ParameterTypeRequiredDescription
instanceDataobject✅Instance creation data
instanceData.instance_namestring✅Name for the new instance (unique within client)

Returns​

Promise<{
message: string;
instance: {
instance_id: number;
client_id: number;
instance_name: string;
client_alias: string | null;
instance_status: string;
created_at: string;
updated_at: string;
};
}>

Example​

try {
const newInstance = await client.createInstance({
instance_name: 'Production API'
});

console.log('Instance Created:', newInstance.instance.instance_name);
console.log('Instance ID:', newInstance.instance.instance_id);
console.log('Client ID:', newInstance.instance.client_id);
console.log('Status:', newInstance.instance.instance_status);
console.log('Message:', newInstance.message);

// Instance belongs to the admin's client automatically
console.log('Created at:', newInstance.instance.created_at);

// Set alias if needed
if (!newInstance.instance.client_alias) {
console.log('💡 Consider setting an alias for better identification');
}

} catch (error) {
console.error('Instance creation failed:', error.message);

if (error.message.includes('Instance name is required')) {
console.error('Provide instance name in request');
} else if (error.message.includes('Client ID not found')) {
console.error('User not linked to any client');
}
}

HTTP Endpoint​

  • Method: POST
  • URL: https://api.brdz.link/api/clients/create-instance
  • Headers: Authorization: Bearer <JWT_TOKEN>, x-api-key required

updateInstanceAlias​

Update the display alias for an instance owned by the admin's client. Alias provides better identification.

Syntax​

const result = await client.updateInstanceAlias(instanceId, aliasData);

Parameters​

ParameterTypeRequiredDescription
instanceIdstring✅Instance ID to update
aliasDataobject✅Alias update data
aliasData.aliasstring✅New alias/display name

Returns​

Promise<{
message: string;
}>

Example​

try {
const result = await client.updateInstanceAlias('1', {
alias: 'Production Environment v2.0'
});

console.log('Update Result:', result.message);
console.log('✅ Alias updated successfully');

// Verify update by getting instances
const instances = await client.getMyInstances();
const updatedInstance = instances.instances.find(i => i.instance_id === 1);

if (updatedInstance) {
console.log('New Alias:', updatedInstance.client_alias);
}

} catch (error) {
console.error('Alias update failed:', error.message);

if (error.message.includes('Access denied')) {
console.error('Instance does not belong to your client');
} else if (error.message.includes('required')) {
console.error('Alias and Instance ID are required');
} else if (error.message.includes('Client not found')) {
console.error('User not associated with any client');
}
}

HTTP Endpoint​

  • Method: PUT
  • URL: https://api.brdz.link/api/clients/:instance_id/update-alias
  • Headers: Authorization: Bearer <JWT_TOKEN>, x-api-key required

deleteInstance​

Delete an instance owned by the admin's client. Permanent deletion that cannot be undone.

Syntax​

const result = await client.deleteInstance(instanceId);

Parameters​

ParameterTypeRequiredDescription
instanceIdstring✅Instance ID to delete

Returns​

Promise<{
message: string;
}>

Example​

try {
// Confirm deletion before proceeding
const confirmDelete = confirm('Are you sure you want to delete this instance? This cannot be undone.');

if (!confirmDelete) {
console.log('Deletion cancelled by user');
return;
}

const result = await client.deleteInstance('1');

console.log('Deletion Result:', result.message);
console.log('Instance permanently deleted');

// Refresh instance list
const updatedInstances = await client.getMyInstances();
console.log('Remaining instances:', updatedInstances.instances.length);

} catch (error) {
console.error('Instance deletion failed:', error.message);

if (error.message.includes('not found')) {
console.error('Instance not found or access denied');
} else if (error.message.includes('not linked')) {
console.error('User not associated with any client');
}
}

HTTP Endpoint​

  • Method: DELETE
  • URL: https://api.brdz.link/api/clients/clients/:instance_id
  • Headers: Authorization: Bearer <JWT_TOKEN>, x-api-key required

Complete Client Management Example​

Enterprise Client Setup with Xendit​

class EnterpriseClientManager {
constructor(sdk) {
this.sdk = sdk;
}

async createOrganization(orgData) {
const client = await this.sdk.client;

try {
const result = await client.createClientWithAdmin({
email: orgData.adminEmail,
client_alias: orgData.name,
client_type: orgData.type,
country_code: orgData.country,
phone: orgData.phone
});

console.log('Organization created:', result.client.client_code);
console.log('PSP assigned:', result.client.psp_id);

// Handle Xendit integration for Indonesia
const hasXenditIntegration = !!result.xendit_integration;

return {
client: result.client,
admin: result.admin_user,
needsKyc: result.ekyc_status === 'PENDING',
xenditIntegration: {
available: hasXenditIntegration,
businessCustomer: result.xendit_integration?.business_customer?.customer_id,
adminCustomer: result.xendit_integration?.admin_customer?.customer_id,
provider: result.xendit_integration?.psp_provider
}
};
} catch (error) {
throw new Error(`Organization creation failed: ${error.message}`);
}
}

async setupMultiEnvironment() {
const client = await this.sdk.client;
const environments = [
{ name: 'Production API', alias: 'Production Environment' },
{ name: 'Staging API', alias: 'Staging Environment' },
{ name: 'Development API', alias: 'Development Environment' },
{ name: 'Testing API', alias: 'QA Testing Environment' }
];

const results = [];

for (const env of environments) {
try {
// Create instance
const instance = await client.createInstance({
instance_name: env.name
});

// Set alias
const aliasUpdate = await client.updateInstanceAlias(
instance.instance.instance_id.toString(),
{ alias: env.alias }
);

results.push({
name: env.name,
alias: env.alias,
success: true,
instanceId: instance.instance.instance_id,
data: instance
});

} catch (error) {
results.push({
name: env.name,
success: false,
error: error.message
});
}
}

return results;
}

async getOrganizationDashboard() {
const client = await this.sdk.client;

try {
const instances = await client.getMyInstances();

if (instances.instances.length === 0) {
return {
error: 'No instances found',
suggestion: 'Create your first instance'
};
}

const clientInfo = instances.instances[0];

const dashboard = {
organization: {
clientCode: clientInfo.client_code,
country: clientInfo.country_code,
kycStatus: clientInfo.ekyc_status,
hasXenditIntegration: clientInfo.country_code === 'ID'
},
instances: {
total: instances.instances.length,
active: instances.instances.filter(i => i.instance_status === 'ACTIVE').length,
withAlias: instances.instances.filter(i => i.client_alias).length,
list: instances.instances.map(i => ({
id: i.instance_id,
name: i.instance_name,
alias: i.client_alias,
status: i.instance_status
}))
},
capabilities: {
canCreateInstances: clientInfo.ekyc_status === 'APPROVED',
canManageInstances: true,
xenditPayments: clientInfo.country_code === 'ID'
}
};

return dashboard;
} catch (error) {
throw new Error(`Failed to get dashboard: ${error.message}`);
}
}

async manageInstanceLifecycle(action, instanceId, data = {}) {
const client = await this.sdk.client;

try {
switch (action) {
case 'create':
const newInstance = await client.createInstance(data);
console.log(`✅ Created instance: ${newInstance.instance.instance_name}`);
return newInstance;

case 'updateAlias':
const aliasResult = await client.updateInstanceAlias(instanceId, data);
console.log(`✅ Updated alias for instance ${instanceId}`);
return aliasResult;

case 'delete':
const deleteResult = await client.deleteInstance(instanceId);
console.log(`✅ Deleted instance ${instanceId}`);
return deleteResult;

default:
throw new Error('Invalid action. Use: create, updateAlias, delete');
}
} catch (error) {
throw new Error(`Instance ${action} failed: ${error.message}`);
}
}

async getClientsByAdmin() {
const client = await this.sdk.client;

try {
const allClients = await client.getAllClients();

const analysis = {
total: allClients.length,
byStatus: {
active: allClients.filter(c => c.client_status === 'ACTIVE').length,
pending: allClients.filter(c => c.client_status === 'PENDING').length,
inactive: allClients.filter(c => c.client_status === 'INACTIVE').length
},
byKyc: {
approved: allClients.filter(c => c.ekyc_status === 'APPROVED').length,
pending: allClients.filter(c => c.ekyc_status === 'PENDING').length,
rejected: allClients.filter(c => c.ekyc_status === 'REJECTED').length
},
byCountry: {},
xenditEnabled: allClients.filter(c => c.country_code === 'ID').length
};

// Count by country
allClients.forEach(client => {
analysis.byCountry[client.country_code] =
(analysis.byCountry[client.country_code] || 0) + 1;
});

return {
clients: allClients,
analysis
};
} catch (error) {
throw new Error(`Failed to get clients analysis: ${error.message}`);
}
}
}

// Usage Example
const enterpriseManager = new EnterpriseClientManager(brdzSDK);

// Complete organization setup
async function setupNewEnterprise() {
try {
// Create organization with Xendit integration
const org = await enterpriseManager.createOrganization({
adminEmail: 'admin@newenterprise.com',
name: 'New Enterprise Corp',
type: 'CORPORATE',
country: 'ID', // Indonesia for Xendit integration
phone: '+628123456789'
});

console.log('Organization Setup Complete:');
console.log('- Client Code:', org.client.client_code);
console.log('- Admin User:', org.admin.username);
console.log('- Needs KYC:', org.needsKyc);
console.log('- Xendit Available:', org.xenditIntegration.available);

if (org.xenditIntegration.available) {
console.log('- Business Customer:', org.xenditIntegration.businessCustomer);
console.log('- Admin Customer:', org.xenditIntegration.adminCustomer);
}

// Setup multi-environment instances
const environments = await enterpriseManager.setupMultiEnvironment();
console.log('Environment Setup:', environments);

// Get dashboard overview
const dashboard = await enterpriseManager.getOrganizationDashboard();
console.log('Dashboard:', dashboard);

return { org, environments, dashboard };

} catch (error) {
console.error('Enterprise setup failed:', error.message);
throw error;
}
}

// Run setup
setupNewEnterprise()
.then(result => {
console.log('✅ Enterprise setup successful');
console.log('Total instances created:', result.environments.filter(e => e.success).length);
})
.catch(error => {
console.error('❌ Enterprise setup failed:', error.message);
});

Admin Dashboard for Multiple Clients​

// Admin dashboard for managing multiple clients
async function loadAdminClientDashboard() {
const client = await brdzSDK.client;

try {
const [allClients, myInstances] = await Promise.all([
client.getAllClients(),
client.getMyInstances()
]);

return {
systemOverview: {
totalClients: allClients.length,
activeClients: allClients.filter(c => c.client_status === 'ACTIVE').length,
pendingClients: allClients.filter(c => c.client_status === 'PENDING').length,
xenditClients: allClients.filter(c => c.country_code === 'ID').length,
approvedKyc: allClients.filter(c => c.ekyc_status === 'APPROVED').length
},
myOrganization: {
instances: myInstances.instances,
activeInstances: myInstances.instances.filter(i =>
i.instance_status === 'ACTIVE'
).length,
clientCode: myInstances.instances[0]?.client_code,
kycStatus: myInstances.instances[0]?.ekyc_status,
isIndonesia: myInstances.instances[0]?.country_code === 'ID'
},
clientAnalysis: analyzeClientDistribution(allClients)
};
} catch (error) {
throw new Error(`Dashboard load failed: ${error.message}`);
}
}

function analyzeClientDistribution(clients) {
const analysis = {
byCountry: {},
byType: {},
byPsp: {},
recentClients: clients
.sort((a, b) => new Date(b.created) - new Date(a.created))
.slice(0, 5)
};

clients.forEach(client => {
// By country
analysis.byCountry[client.country_code] =
(analysis.byCountry[client.country_code] || 0) + 1;

// By type
analysis.byType[client.client_type] =
(analysis.byType[client.client_type] || 0) + 1;

// By PSP
analysis.byPsp[client.psp_id] =
(analysis.byPsp[client.psp_id] || 0) + 1;
});

return analysis;
}

Client Features​

Automated Client Setup​

  • Client Code Generation: Auto-generated format CL-YYYYMM-XXXX with monthly sequences
  • PSP Assignment: Country-based payment service provider selection with fallback system
  • Admin User Creation: Secure 10-character password with crypto-random generation
  • Email Notifications: HTML welcome email with login credentials and frontend URL
  • eKYC Initialization: Compliance setup with PENDING status for workflow integration

Xendit Integration (Indonesia)​

  • Business Customer: Automatic creation for client organization operations
  • Individual Customer: Automatic creation for admin user personal account
  • PSP Integration: Seamless integration via pspService for customer management
  • Error Handling: Non-blocking failures ensure client creation continues
  • Status Tracking: Customer status tracked in local xendit_customers table

Instance Management​

  • Multi-Environment Support: Production, staging, development, testing instances
  • Alias System: Optional display names for better organization and identification
  • Access Control: Client-specific instance ownership with database validation
  • Status Tracking: Active/inactive instance management with lifecycle support

Security & Compliance​

  • Role-based Access: Admin-only operations with authMiddleware protection
  • Client Isolation: Users can only access their own organization's resources
  • Transaction Safety: Database BEGIN/COMMIT/ROLLBACK for data consistency
  • Audit Trail: Email logging and activity tracking for compliance
  • Password Security: bcrypt hashing with secure random generation

Error Handling​

Common Errors and Solutions​

ErrorDescriptionSolution
Email, Name, Type, Country and Mobile Number are required!Missing required fieldsProvide all mandatory parameters
Email is already in use by another client.Duplicate admin emailUse different email address
No PSP available for the selected country.Country not supportedCheck PSP configuration or use supported country
There is no FIAT_PSP PSP available for that country.No FIAT PSP configuredConfigure FIAT_PSP for country
Access denied. The instance is not yours.Instance ownership issueVerify instance belongs to your client
Client not found for this user.User not linked to clientComplete client onboarding first
Instance name is requiredMissing instance nameProvide instance_name in request
Client ID not foundUser not associated with clientEnsure user has valid client association

Comprehensive Error Handling Pattern​

try {
const result = await client.createClientWithAdmin(clientData);

// Handle success with Xendit integration
if (result.xendit_integration) {
console.log('✅ Client created with Xendit integration');
handleXenditIntegration(result.xendit_integration);
} else {
console.log('✅ Client created without Xendit integration');
}

} catch (error) {
// Handle specific errors
if (error.message.includes('Email is already in use')) {
showEmailConflictError('This email is already registered. Please use a different email address.');

} else if (error.message.includes('No PSP available')) {
showPspConfigurationError('Payment service provider not available for this country. Please contact support.');

} else if (error.message.includes('No FIAT_PSP PSP available')) {
showPspTypeError('FIAT payment service provider not configured for this country.');

} else if (error.message.includes('required')) {
showValidationError('Please fill in all required fields: Email, Name, Type, Country, and Phone.');

} else if (error.message.includes('Access denied')) {
showAccessDeniedError('You do not have permission to access this resource.');

} else if (error.message.includes('Client not found')) {
showClientNotFoundError('Your account is not associated with any client organization.');

} else if (error.message.includes('Failed to create Client')) {
showDatabaseError('Server error occurred. Please try again or contact support.');

} else {
// Handle generic errors
showGenericError(`Operation failed: ${error.message}`);
}
}

// Helper functions for specific error handling
function handleXenditIntegration(xenditInfo) {
if (xenditInfo.business_customer?.status === 'success') {
console.log('✅ Business Xendit customer created');
}

if (xenditInfo.admin_customer?.status === 'success') {
console.log('✅ Admin Xendit customer created');
}

// Store customer IDs for future reference
localStorage.setItem('xendit_business_customer', xenditInfo.business_customer?.customer_id);
localStorage.setItem('xendit_admin_customer', xenditInfo.admin_customer?.customer_id);
}

Integration Notes​

Client Code Generation Logic​

// Format: CL-YYYYMM-XXXX
// Example: CL-202401-0001, CL-202401-0002, etc.

const generateClientCode = async () => {
const yearMonth = new Date().toISOString().slice(0, 7).replace('-', '');
const prefix = `CL-${yearMonth}-`;

// Get max sequence for current month
const result = await db.query(
`SELECT MAX(SUBSTRING(client_code FROM 11 FOR 4))::INTEGER AS max_seq
FROM clients WHERE client_code LIKE $1`,
[`${prefix}%`]
);

const nextSeq = (result.rows[0]?.max_seq || 0) + 1;
const paddedSeq = String(nextSeq).padStart(4, '0');
return `${prefix}${paddedSeq}`;
};

PSP Selection Algorithm​

// Primary PSP selection by country
const selectedPsp = await pspService.getPspByCountry(country_code);

// Fallback PSP if primary selection fails
if (!selectedPsp?.psp_id) {
const fallbackPsp = await db.query(`
SELECT psp_id FROM psp_providers
WHERE country_code = $1 AND psp_type = 'FIAT_PSP'
ORDER BY priority ASC, created_at ASC
LIMIT 1
`, [country_code]);

psp_id = fallbackPsp.rows[0].psp_id;
}

Xendit Customer Creation Flow​

// For Indonesia clients (country_code === 'ID')
if (country_code === 'ID' && selectedPsp.psp_name === 'XENDIT') {

// 1. Create business customer for client organization
const clientPspData = {
client_id: newClient.client_id,
email: email,
client_alias: client_alias,
client_type: client_type,
phone: phone
};

const businessCustomer = await pspService.getPspUser(clientPspData, country_code);

// 2. Create individual customer for admin user
const adminPspData = {
user_id: adminUser.user_id,
email: email,
username: adminUser.username,
phone: phone
};

const individualCustomer = await pspService.getPspUser(adminPspData, country_code);
}

Client Creation

Client creation automatically handles PSP assignment, admin setup, email notifications, and Xendit integration for Indonesia clients.

Instance Deletion

Instance deletion is permanent and cannot be undone. Always confirm before deleting production instances.

Xendit Integration

Indonesia clients (country_code: 'ID') automatically get both business and individual Xendit customer accounts created through PSP service integration.

Client Code Format

Client codes follow CL-YYYYMM-XXXX format with auto-incrementing sequences per month for unique identification.