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 userPOST /clients/create_with_admin
getAllClientsGet all clientsGET /clients
getMyInstancesGet my client instancesGET /clients/my-instances
createInstanceCreate new instancePOST /clients/create-instance
updateInstanceAliasUpdate instance aliasPUT /clients/:instance_id/update-alias
deleteInstanceDelete instanceDELETE /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
clientDataobjectClient creation data
clientData.emailstringAdmin email address (must be unique)
clientData.client_aliasstringClient organization name
clientData.client_typestringType of client organization (CORPORATE, STARTUP, etc.)
clientData.country_codestringCountry code for PSP assignment (2-letter ISO)
clientData.phonestringContact phone number with country code
clientData.client_statusstringClient 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
paramsobjectQuery 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
instanceDataobjectInstance creation data
instanceData.instance_namestringName 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
instanceIdstringInstance ID to update
aliasDataobjectAlias update data
aliasData.aliasstringNew 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
instanceIdstringInstance 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.