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
| Method | Description | Auth Required | HTTP Endpoint |
|---|---|---|---|
createClientWithAdmin | Create client with admin user | ❌ | POST /clients/create_with_admin |
getAllClients | Get all clients | ✅ | GET /clients |
getMyInstances | Get my client instances | ✅ | GET /clients/my-instances |
createInstance | Create new instance | ✅ | POST /clients/create-instance |
updateInstanceAlias | Update instance alias | ✅ | PUT /clients/:instance_id/update-alias |
deleteInstance | Delete 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
| Parameter | Type | Required | Description |
|---|---|---|---|
clientData | object | ✅ | Client creation data |
clientData.email | string | ✅ | Admin email address (must be unique) |
clientData.client_alias | string | ✅ | Client organization name |
clientData.client_type | string | ✅ | Type of client organization (CORPORATE, STARTUP, etc.) |
clientData.country_code | string | ✅ | Country code for PSP assignment (2-letter ISO) |
clientData.phone | string | ✅ | Contact phone number with country code |
clientData.client_status | string | ❌ | 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-XXXXformat - 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:
-
Business Customer: For the client organization
- Customer Type: BUSINESS
- Links to client_id
- Used for business operations
-
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
| Parameter | Type | Required | Description |
|---|---|---|---|
params | object | ❌ | 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-keyrequired
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-keyrequired
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
| Parameter | Type | Required | Description |
|---|---|---|---|
instanceData | object | ✅ | Instance creation data |
instanceData.instance_name | string | ✅ | 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-keyrequired
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
| Parameter | Type | Required | Description |
|---|---|---|---|
instanceId | string | ✅ | Instance ID to update |
aliasData | object | ✅ | Alias update data |
aliasData.alias | string | ✅ | 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-keyrequired
deleteInstance
Delete an instance owned by the admin's client. Permanent deletion that cannot be undone.
Syntax
const result = await client.deleteInstance(instanceId);
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
instanceId | string | ✅ | 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-keyrequired
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-XXXXwith 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
| Error | Description | Solution |
|---|---|---|
Email, Name, Type, Country and Mobile Number are required! | Missing required fields | Provide all mandatory parameters |
Email is already in use by another client. | Duplicate admin email | Use different email address |
No PSP available for the selected country. | Country not supported | Check PSP configuration or use supported country |
There is no FIAT_PSP PSP available for that country. | No FIAT PSP configured | Configure FIAT_PSP for country |
Access denied. The instance is not yours. | Instance ownership issue | Verify instance belongs to your client |
Client not found for this user. | User not linked to client | Complete client onboarding first |
Instance name is required | Missing instance name | Provide instance_name in request |
Client ID not found | User not associated with client | Ensure 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 automatically handles PSP assignment, admin setup, email notifications, and Xendit integration for Indonesia clients.
Instance deletion is permanent and cannot be undone. Always confirm before deleting production instances.
Indonesia clients (country_code: 'ID') automatically get both business and individual Xendit customer accounts created through PSP service integration.
Client codes follow CL-YYYYMM-XXXX format with auto-incrementing sequences per month for unique identification.