blaaiz-nodejs-sdk 1.2.0 → 1.3.1
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 +37 -0
- package/index.d.ts +7 -1
- package/package.json +1 -1
- package/src/client.js +153 -7
package/README.md
CHANGED
|
@@ -29,6 +29,43 @@ const isConnected = await blaaiz.testConnection();
|
|
|
29
29
|
console.log('API Connected:', isConnected);
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
## Authentication
|
|
33
|
+
|
|
34
|
+
The SDK supports two authentication methods.
|
|
35
|
+
|
|
36
|
+
### OAuth 2.0 client credentials (recommended for new integrations)
|
|
37
|
+
|
|
38
|
+
Provide a `client_id` and `client_secret` and the SDK handles the OAuth
|
|
39
|
+
`client_credentials` flow for you: it fetches a bearer token from
|
|
40
|
+
`/oauth/token`, caches it in memory, and refreshes it automatically shortly
|
|
41
|
+
before it expires.
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
const { Blaaiz } = require('blaaiz-nodejs-sdk');
|
|
45
|
+
|
|
46
|
+
const blaaiz = new Blaaiz({
|
|
47
|
+
client_id: process.env.BLAAIZ_CLIENT_ID,
|
|
48
|
+
client_secret: process.env.BLAAIZ_CLIENT_SECRET,
|
|
49
|
+
// oauth_scope: 'wallet:read payout:create', // Optional: defaults to the full scope set
|
|
50
|
+
// baseURL: 'https://api.blaaiz.com', // Optional: defaults to dev environment
|
|
51
|
+
// timeout: 30000 // Optional: request timeout in milliseconds
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
When `oauth_scope` is omitted the SDK requests the full set of supported scopes.
|
|
56
|
+
|
|
57
|
+
### API key (legacy)
|
|
58
|
+
|
|
59
|
+
API keys are still supported for existing integrations.
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
const blaaiz = new Blaaiz('your-api-key-here');
|
|
63
|
+
// or, using the options object:
|
|
64
|
+
const blaaiz2 = new Blaaiz({ api_key: process.env.BLAAIZ_API_KEY });
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
When both OAuth credentials and an API key are configured, OAuth is used.
|
|
68
|
+
|
|
32
69
|
## Features
|
|
33
70
|
|
|
34
71
|
- **Customer Management**: Create, update, and manage customers with KYC verification
|
package/index.d.ts
CHANGED
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
export interface BlaaizOptions {
|
|
6
6
|
baseURL?: string;
|
|
7
|
+
base_url?: string;
|
|
7
8
|
timeout?: number;
|
|
9
|
+
api_key?: string;
|
|
10
|
+
client_id?: string;
|
|
11
|
+
client_secret?: string;
|
|
12
|
+
oauth_scope?: string;
|
|
8
13
|
}
|
|
9
14
|
|
|
10
15
|
export interface BlaaizResponse<T = any> {
|
|
@@ -367,7 +372,8 @@ export declare class Blaaiz {
|
|
|
367
372
|
public webhooks: WebhookService;
|
|
368
373
|
|
|
369
374
|
constructor(apiKey: string, options?: BlaaizOptions);
|
|
370
|
-
|
|
375
|
+
constructor(options: BlaaizOptions);
|
|
376
|
+
|
|
371
377
|
testConnection(): Promise<boolean>;
|
|
372
378
|
|
|
373
379
|
createCompletePayout(payoutConfig: {
|
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -1,26 +1,170 @@
|
|
|
1
1
|
const https = require('https')
|
|
2
2
|
const http = require('http')
|
|
3
|
-
const { URL } = require('url')
|
|
3
|
+
const { URL, URLSearchParams } = require('url')
|
|
4
4
|
const BlaaizError = require('./error')
|
|
5
5
|
|
|
6
|
+
const ALL_SCOPES = [
|
|
7
|
+
'wallet:read', 'currency:read', 'bank:read', 'customer:read', 'customer:write',
|
|
8
|
+
'beneficiary:read', 'virtual-account:read', 'virtual-account:create', 'virtual-account:close',
|
|
9
|
+
'collection:create', 'collection:crypto:create', 'collection:interac:accept',
|
|
10
|
+
'payout:create', 'swap:create', 'transaction:read', 'fees:read', 'file:upload',
|
|
11
|
+
'webhook:read', 'webhook:write', 'webhook:replay', 'rates:read'
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
function isPresent (value) {
|
|
15
|
+
return typeof value === 'string' && value !== '' && value !== '0'
|
|
16
|
+
}
|
|
17
|
+
|
|
6
18
|
class BlaaizAPIClient {
|
|
7
19
|
constructor (apiKey, options = {}) {
|
|
8
|
-
if (
|
|
9
|
-
|
|
20
|
+
if (apiKey && typeof apiKey === 'object') {
|
|
21
|
+
options = apiKey
|
|
22
|
+
apiKey = undefined
|
|
10
23
|
}
|
|
11
24
|
|
|
12
|
-
this.
|
|
13
|
-
this.
|
|
25
|
+
this.clientId = options.client_id || ''
|
|
26
|
+
this.clientSecret = options.client_secret || ''
|
|
27
|
+
this.oauthScope = (options.oauth_scope === undefined || options.oauth_scope === null)
|
|
28
|
+
? ALL_SCOPES.join(' ')
|
|
29
|
+
: options.oauth_scope
|
|
30
|
+
this.apiKey = apiKey || options.api_key || ''
|
|
31
|
+
this.baseURL = options.baseURL || options.base_url || 'https://api-dev.blaaiz.com'
|
|
14
32
|
this.timeout = options.timeout || 30000
|
|
33
|
+
|
|
34
|
+
this.useOAuth = isPresent(this.clientId) && isPresent(this.clientSecret)
|
|
35
|
+
|
|
36
|
+
if (!this.useOAuth && !isPresent(this.apiKey)) {
|
|
37
|
+
throw new BlaaizError(
|
|
38
|
+
'Authentication required: provide either client_id and client_secret for OAuth, or an API key for legacy authentication'
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.accessToken = null
|
|
43
|
+
this.tokenExpiresAt = null
|
|
44
|
+
|
|
15
45
|
this.defaultHeaders = {
|
|
16
|
-
'x-blaaiz-api-key': this.apiKey,
|
|
17
46
|
Accept: 'application/json',
|
|
18
47
|
'Content-Type': 'application/json',
|
|
19
48
|
'User-Agent': 'Blaaiz-NodeJS-SDK/1.0.0'
|
|
20
49
|
}
|
|
21
50
|
}
|
|
22
51
|
|
|
52
|
+
async getOAuthToken () {
|
|
53
|
+
const now = Math.floor(Date.now() / 1000)
|
|
54
|
+
if (this.accessToken && this.tokenExpiresAt && now < this.tokenExpiresAt) {
|
|
55
|
+
return this.accessToken
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const body = new URLSearchParams({
|
|
59
|
+
grant_type: 'client_credentials',
|
|
60
|
+
client_id: this.clientId,
|
|
61
|
+
client_secret: this.clientSecret,
|
|
62
|
+
scope: this.oauthScope
|
|
63
|
+
}).toString()
|
|
64
|
+
|
|
65
|
+
let response
|
|
66
|
+
try {
|
|
67
|
+
response = await this._requestToken(body)
|
|
68
|
+
} catch (error) {
|
|
69
|
+
throw new BlaaizError(
|
|
70
|
+
`OAuth token request failed: ${error.message}`,
|
|
71
|
+
null,
|
|
72
|
+
'OAUTH_ERROR'
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (response.status < 200 || response.status >= 300) {
|
|
77
|
+
let errorData = null
|
|
78
|
+
try {
|
|
79
|
+
errorData = JSON.parse(response.body)
|
|
80
|
+
} catch (error) {
|
|
81
|
+
errorData = null
|
|
82
|
+
}
|
|
83
|
+
throw new BlaaizError(
|
|
84
|
+
(errorData && (errorData.error_description || errorData.message)) ||
|
|
85
|
+
`OAuth token request failed: HTTP ${response.status}`,
|
|
86
|
+
response.status,
|
|
87
|
+
(errorData && errorData.error) || 'OAUTH_ERROR'
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let parsed = null
|
|
92
|
+
try {
|
|
93
|
+
parsed = JSON.parse(response.body)
|
|
94
|
+
} catch (error) {
|
|
95
|
+
parsed = null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!parsed || !parsed.access_token) {
|
|
99
|
+
throw new BlaaizError(
|
|
100
|
+
'Failed to parse OAuth token response',
|
|
101
|
+
response.status,
|
|
102
|
+
'OAUTH_PARSE_ERROR'
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let expiresIn = Number(parsed.expires_in)
|
|
107
|
+
if (!Number.isFinite(expiresIn)) expiresIn = 900
|
|
108
|
+
this.accessToken = parsed.access_token
|
|
109
|
+
this.tokenExpiresAt = Math.floor(Date.now() / 1000) + expiresIn - 60
|
|
110
|
+
|
|
111
|
+
return this.accessToken
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async getAuthHeaders () {
|
|
115
|
+
if (this.useOAuth) {
|
|
116
|
+
const token = await this.getOAuthToken()
|
|
117
|
+
return { Authorization: `Bearer ${token}` }
|
|
118
|
+
}
|
|
119
|
+
return { 'x-blaaiz-api-key': this.apiKey }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
_requestToken (body) {
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
const url = new URL('/oauth/token', this.baseURL)
|
|
125
|
+
const options = {
|
|
126
|
+
hostname: url.hostname,
|
|
127
|
+
port: url.port || (url.protocol === 'https:' ? 443 : 80),
|
|
128
|
+
path: url.pathname + url.search,
|
|
129
|
+
method: 'POST',
|
|
130
|
+
headers: {
|
|
131
|
+
...this.defaultHeaders,
|
|
132
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
133
|
+
'Content-Length': Buffer.byteLength(body)
|
|
134
|
+
},
|
|
135
|
+
timeout: this.timeout
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const client = url.protocol === 'https:' ? https : http
|
|
139
|
+
const req = client.request(options, (res) => {
|
|
140
|
+
let responseData = ''
|
|
141
|
+
|
|
142
|
+
res.on('data', (chunk) => {
|
|
143
|
+
responseData += chunk
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
res.on('end', () => {
|
|
147
|
+
resolve({ status: res.statusCode, body: responseData })
|
|
148
|
+
})
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
req.on('error', (error) => {
|
|
152
|
+
reject(error)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
req.on('timeout', () => {
|
|
156
|
+
req.destroy()
|
|
157
|
+
reject(new Error('OAuth token request timeout'))
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
req.write(body)
|
|
161
|
+
req.end()
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
23
165
|
async makeRequest (method, endpoint, data = null, headers = {}) {
|
|
166
|
+
const authHeaders = await this.getAuthHeaders()
|
|
167
|
+
|
|
24
168
|
return new Promise((resolve, reject) => {
|
|
25
169
|
const url = new URL(endpoint, this.baseURL)
|
|
26
170
|
const options = {
|
|
@@ -28,7 +172,7 @@ class BlaaizAPIClient {
|
|
|
28
172
|
port: url.port || (url.protocol === 'https:' ? 443 : 80),
|
|
29
173
|
path: url.pathname + url.search,
|
|
30
174
|
method: method.toUpperCase(),
|
|
31
|
-
headers: { ...this.defaultHeaders, ...headers },
|
|
175
|
+
headers: { ...this.defaultHeaders, ...authHeaders, ...headers },
|
|
32
176
|
timeout: this.timeout
|
|
33
177
|
}
|
|
34
178
|
|
|
@@ -93,4 +237,6 @@ class BlaaizAPIClient {
|
|
|
93
237
|
}
|
|
94
238
|
}
|
|
95
239
|
|
|
240
|
+
BlaaizAPIClient.ALL_SCOPES = ALL_SCOPES
|
|
241
|
+
|
|
96
242
|
module.exports = BlaaizAPIClient
|