blaaiz-nodejs-sdk 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -1
- package/index.d.ts +41 -3
- package/package.json +3 -3
- package/src/index.js +1 -1
- package/src/services/CustomerService.js +102 -3
package/README.md
CHANGED
|
@@ -94,6 +94,23 @@ const customers = await blaaiz.customers.list();
|
|
|
94
94
|
console.log('Customers:', customers.data);
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
+
You can also pass optional filters and opt-in pagination. Supported filters
|
|
98
|
+
are `email`, `id_number`, `registration_number`, `verification_status`, and
|
|
99
|
+
`type`. Set `paginate: true` to receive a paginated response that includes
|
|
100
|
+
`links` and `meta` (with `current_page`, `total`, etc.) alongside `data`.
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
const verified = await blaaiz.customers.list({
|
|
104
|
+
email: 'john@example.com',
|
|
105
|
+
verification_status: 'VERIFIED',
|
|
106
|
+
type: 'individual',
|
|
107
|
+
paginate: true
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
console.log('Page:', verified.data.meta.current_page);
|
|
111
|
+
console.log('Customers:', verified.data.data);
|
|
112
|
+
```
|
|
113
|
+
|
|
97
114
|
#### Update Customer
|
|
98
115
|
|
|
99
116
|
```javascript
|
|
@@ -128,7 +145,7 @@ const result = await blaaiz.customers.uploadFileComplete('customer-id', {
|
|
|
128
145
|
file: fileBuffer, // Buffer or Uint8Array
|
|
129
146
|
file_category: 'identity', // identity, proof_of_address, liveness_check
|
|
130
147
|
filename: 'passport.jpg', // Optional
|
|
131
|
-
|
|
148
|
+
content_type: 'image/jpeg' // Optional
|
|
132
149
|
});
|
|
133
150
|
|
|
134
151
|
// Option B: Upload from Base64 string
|
package/index.d.ts
CHANGED
|
@@ -35,7 +35,43 @@ export interface CustomerData {
|
|
|
35
35
|
export interface Customer extends CustomerData {
|
|
36
36
|
id: string;
|
|
37
37
|
business_id: string;
|
|
38
|
-
verification_status: 'PENDING' | 'VERIFIED' | 'REJECTED';
|
|
38
|
+
verification_status: 'PENDING' | 'PROCESSING' | 'VERIFIED' | 'REJECTED';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface CustomerListFilters {
|
|
42
|
+
email?: string;
|
|
43
|
+
id_number?: string;
|
|
44
|
+
registration_number?: string;
|
|
45
|
+
verification_status?: 'PENDING' | 'PROCESSING' | 'VERIFIED' | 'REJECTED';
|
|
46
|
+
type?: 'individual' | 'business';
|
|
47
|
+
paginate?: boolean;
|
|
48
|
+
page?: number;
|
|
49
|
+
per_page?: number;
|
|
50
|
+
[key: string]: string | number | boolean | undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface PaginationLinks {
|
|
54
|
+
first?: string | null;
|
|
55
|
+
last?: string | null;
|
|
56
|
+
prev?: string | null;
|
|
57
|
+
next?: string | null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface PaginationMeta {
|
|
61
|
+
current_page: number;
|
|
62
|
+
from?: number | null;
|
|
63
|
+
last_page?: number;
|
|
64
|
+
path?: string;
|
|
65
|
+
per_page?: number;
|
|
66
|
+
to?: number | null;
|
|
67
|
+
total: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface PaginatedCustomers {
|
|
71
|
+
data: Customer[];
|
|
72
|
+
links: PaginationLinks;
|
|
73
|
+
meta: PaginationMeta;
|
|
74
|
+
message?: string;
|
|
39
75
|
}
|
|
40
76
|
|
|
41
77
|
export interface CustomerKYCData {
|
|
@@ -52,6 +88,8 @@ export interface FileUploadOptions {
|
|
|
52
88
|
file: Buffer | Uint8Array | string;
|
|
53
89
|
file_category: 'identity' | 'proof_of_address' | 'liveness_check';
|
|
54
90
|
filename?: string;
|
|
91
|
+
content_type?: string;
|
|
92
|
+
/** @deprecated Use content_type instead */
|
|
55
93
|
contentType?: string;
|
|
56
94
|
}
|
|
57
95
|
|
|
@@ -242,7 +280,7 @@ export interface WebhookEvent {
|
|
|
242
280
|
export declare class CustomerService {
|
|
243
281
|
constructor(client: any);
|
|
244
282
|
create(customerData: CustomerData): Promise<BlaaizResponse<{ data: Customer }>>;
|
|
245
|
-
list(): Promise<BlaaizResponse<Customer[]>>;
|
|
283
|
+
list(filters?: CustomerListFilters): Promise<BlaaizResponse<Customer[] | PaginatedCustomers>>;
|
|
246
284
|
get(customerId: string): Promise<BlaaizResponse<Customer>>;
|
|
247
285
|
update(customerId: string, updateData: Partial<CustomerData>): Promise<BlaaizResponse<Customer>>;
|
|
248
286
|
addKYC(customerId: string, kycData: CustomerKYCData): Promise<BlaaizResponse<any>>;
|
|
@@ -331,7 +369,7 @@ export declare class Blaaiz {
|
|
|
331
369
|
|
|
332
370
|
testConnection(): Promise<boolean>;
|
|
333
371
|
|
|
334
|
-
|
|
372
|
+
createCompletePayout(payoutConfig: {
|
|
335
373
|
customerData?: CustomerData;
|
|
336
374
|
payoutData: PayoutData;
|
|
337
375
|
}): Promise<{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blaaiz-nodejs-sdk",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Official Node.js SDK for Blaaiz RaaS (Remittance as a Service) API",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
"license": "MIT",
|
|
41
41
|
"repository": {
|
|
42
42
|
"type": "git",
|
|
43
|
-
"url": "https://github.com/blaaiz
|
|
43
|
+
"url": "git+https://github.com/Blaaiz/blaaiz-nodejs-sdk.git"
|
|
44
44
|
},
|
|
45
45
|
"bugs": {
|
|
46
|
-
"url": "https://github.com/blaaiz
|
|
46
|
+
"url": "https://github.com/Blaaiz/blaaiz-nodejs-sdk/issues"
|
|
47
47
|
},
|
|
48
48
|
"homepage": "https://docs.business.blaaiz.com",
|
|
49
49
|
"engines": {
|
package/src/index.js
CHANGED
|
@@ -25,8 +25,15 @@ class CustomerService {
|
|
|
25
25
|
return this.client.makeRequest('POST', '/api/external/customer', customerData)
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
async list () {
|
|
29
|
-
|
|
28
|
+
async list (filters = {}) {
|
|
29
|
+
const params = new URLSearchParams()
|
|
30
|
+
for (const [key, value] of Object.entries(filters || {})) {
|
|
31
|
+
if (value === undefined || value === null) continue
|
|
32
|
+
params.append(key, typeof value === 'boolean' ? String(value) : value)
|
|
33
|
+
}
|
|
34
|
+
const query = params.toString()
|
|
35
|
+
const endpoint = query ? `/api/external/customer?${query}` : '/api/external/customer'
|
|
36
|
+
return this.client.makeRequest('GET', endpoint)
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
async get (customerId) {
|
|
@@ -84,7 +91,8 @@ class CustomerService {
|
|
|
84
91
|
}
|
|
85
92
|
|
|
86
93
|
const { file, file_category } = fileOptions // eslint-disable-line camelcase
|
|
87
|
-
let { filename
|
|
94
|
+
let { filename } = fileOptions
|
|
95
|
+
let contentType = fileOptions.content_type || fileOptions.contentType // eslint-disable-line camelcase
|
|
88
96
|
|
|
89
97
|
if (!file) {
|
|
90
98
|
throw new Error('File is required')
|
|
@@ -129,6 +137,15 @@ class CustomerService {
|
|
|
129
137
|
if (file.startsWith('data:')) {
|
|
130
138
|
// Handle data URL format: data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k=
|
|
131
139
|
const base64Data = file.split(',')[1]
|
|
140
|
+
if (!base64Data || !base64Data.trim()) {
|
|
141
|
+
throw new Error('Invalid data URL: no base64 data found after the comma')
|
|
142
|
+
}
|
|
143
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(base64Data.trim())) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
'The base64 portion of the data URL does not appear to be valid base64. ' +
|
|
146
|
+
'Ensure the string after the comma contains only valid base64 characters.'
|
|
147
|
+
)
|
|
148
|
+
}
|
|
132
149
|
fileBuffer = Buffer.from(base64Data, 'base64')
|
|
133
150
|
|
|
134
151
|
// Extract content type from data URL if not provided
|
|
@@ -154,6 +171,15 @@ class CustomerService {
|
|
|
154
171
|
}
|
|
155
172
|
} else {
|
|
156
173
|
// Handle plain base64 string
|
|
174
|
+
if (!file.trim()) {
|
|
175
|
+
throw new Error('The file string is empty')
|
|
176
|
+
}
|
|
177
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(file.trim())) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
'The file string does not appear to be valid base64. ' +
|
|
180
|
+
'If you meant to pass a file path, Buffer, or URL, use the appropriate format instead.'
|
|
181
|
+
)
|
|
182
|
+
}
|
|
157
183
|
fileBuffer = Buffer.from(file, 'base64')
|
|
158
184
|
}
|
|
159
185
|
} else {
|
|
@@ -161,6 +187,19 @@ class CustomerService {
|
|
|
161
187
|
fileBuffer = Buffer.from(file)
|
|
162
188
|
}
|
|
163
189
|
|
|
190
|
+
// Auto-detect content type if not provided
|
|
191
|
+
if (!contentType) {
|
|
192
|
+
contentType = this._detectContentTypeFromBytes(fileBuffer)
|
|
193
|
+
}
|
|
194
|
+
if (!contentType && filename) {
|
|
195
|
+
contentType = this._getContentTypeFromFilename(filename)
|
|
196
|
+
}
|
|
197
|
+
if (!contentType) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
'Could not determine file content type. Please provide a content_type (e.g., "image/jpeg", "image/png", "application/pdf") in fileOptions.'
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
164
203
|
await this._uploadToS3(presignedUrl, fileBuffer, contentType, filename)
|
|
165
204
|
|
|
166
205
|
// Map file category to the correct field name expected by Laravel API
|
|
@@ -353,6 +392,66 @@ class CustomerService {
|
|
|
353
392
|
})
|
|
354
393
|
}
|
|
355
394
|
|
|
395
|
+
_detectContentTypeFromBytes (buffer) {
|
|
396
|
+
if (!buffer || buffer.length < 4) return null
|
|
397
|
+
|
|
398
|
+
// JPEG: FF D8 FF
|
|
399
|
+
if (buffer[0] === 0xFF && buffer[1] === 0xD8 && buffer[2] === 0xFF) {
|
|
400
|
+
return 'image/jpeg'
|
|
401
|
+
}
|
|
402
|
+
// PNG: 89 50 4E 47
|
|
403
|
+
if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4E && buffer[3] === 0x47) {
|
|
404
|
+
return 'image/png'
|
|
405
|
+
}
|
|
406
|
+
// GIF: 47 49 46 38
|
|
407
|
+
if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x38) {
|
|
408
|
+
return 'image/gif'
|
|
409
|
+
}
|
|
410
|
+
// PDF: 25 50 44 46 (%PDF)
|
|
411
|
+
if (buffer[0] === 0x25 && buffer[1] === 0x50 && buffer[2] === 0x44 && buffer[3] === 0x46) {
|
|
412
|
+
return 'application/pdf'
|
|
413
|
+
}
|
|
414
|
+
// WEBP: starts with RIFF....WEBP
|
|
415
|
+
if (buffer.length >= 12 &&
|
|
416
|
+
buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46 &&
|
|
417
|
+
buffer[8] === 0x57 && buffer[9] === 0x45 && buffer[10] === 0x42 && buffer[11] === 0x50) {
|
|
418
|
+
return 'image/webp'
|
|
419
|
+
}
|
|
420
|
+
// BMP: 42 4D
|
|
421
|
+
if (buffer[0] === 0x42 && buffer[1] === 0x4D) {
|
|
422
|
+
return 'image/bmp'
|
|
423
|
+
}
|
|
424
|
+
// TIFF: 49 49 2A 00 (little-endian) or 4D 4D 00 2A (big-endian)
|
|
425
|
+
if ((buffer[0] === 0x49 && buffer[1] === 0x49 && buffer[2] === 0x2A && buffer[3] === 0x00) ||
|
|
426
|
+
(buffer[0] === 0x4D && buffer[1] === 0x4D && buffer[2] === 0x00 && buffer[3] === 0x2A)) {
|
|
427
|
+
return 'image/tiff'
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return null
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
_getContentTypeFromFilename (filename) {
|
|
434
|
+
if (!filename) return null
|
|
435
|
+
|
|
436
|
+
const ext = filename.toLowerCase().split('.').pop()
|
|
437
|
+
const extToMime = {
|
|
438
|
+
jpg: 'image/jpeg',
|
|
439
|
+
jpeg: 'image/jpeg',
|
|
440
|
+
png: 'image/png',
|
|
441
|
+
gif: 'image/gif',
|
|
442
|
+
webp: 'image/webp',
|
|
443
|
+
bmp: 'image/bmp',
|
|
444
|
+
tiff: 'image/tiff',
|
|
445
|
+
tif: 'image/tiff',
|
|
446
|
+
pdf: 'application/pdf',
|
|
447
|
+
txt: 'text/plain',
|
|
448
|
+
doc: 'application/msword',
|
|
449
|
+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return extToMime[ext] || null
|
|
453
|
+
}
|
|
454
|
+
|
|
356
455
|
_getExtensionFromContentType (contentType) {
|
|
357
456
|
const mimeToExt = {
|
|
358
457
|
'image/jpeg': '.jpg',
|