@oapiex/sdk-kit 0.1.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/LICENSE +15 -0
- package/README.md +1 -0
- package/dist/contracts.d.cts +2 -0
- package/dist/contracts.d.ts +2 -0
- package/dist/index-BYtse_Mh.d.cts +107 -0
- package/dist/index-sMTTpEJS.d.ts +107 -0
- package/dist/index.cjs +802 -0
- package/dist/index.d.cts +527 -0
- package/dist/index.d.ts +528 -0
- package/dist/index.js +756 -0
- package/package.json +74 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,802 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
require("dotenv/config");
|
|
25
|
+
let axios = require("axios");
|
|
26
|
+
axios = __toESM(axios);
|
|
27
|
+
let path = require("path");
|
|
28
|
+
path = __toESM(path);
|
|
29
|
+
let crypto = require("crypto");
|
|
30
|
+
crypto = __toESM(crypto);
|
|
31
|
+
|
|
32
|
+
//#region src/utilities/global.ts
|
|
33
|
+
String.prototype.toKebabCase = function() {
|
|
34
|
+
return this.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
35
|
+
};
|
|
36
|
+
String.prototype.toCamelCase = function() {
|
|
37
|
+
return this.replace(/[-_ ]+([a-zA-Z0-9])/g, (_, c) => c.toUpperCase()).replace(/^[A-Z]/, (c) => c.toLowerCase());
|
|
38
|
+
};
|
|
39
|
+
String.prototype.toPascalCase = function() {
|
|
40
|
+
return this.replace(/(^\w|[-_ ]+\w)/g, (match) => match.replace(/[-_ ]+/, "").toUpperCase());
|
|
41
|
+
};
|
|
42
|
+
String.prototype.toSnakeCase = function() {
|
|
43
|
+
return this.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toLowerCase();
|
|
44
|
+
};
|
|
45
|
+
String.prototype.toSlug = function(separator = "-") {
|
|
46
|
+
return this.toSnakeCase().replace(/[/__:]+/g, "_").replace(/_/g, separator);
|
|
47
|
+
};
|
|
48
|
+
String.prototype.toTitleCase = function() {
|
|
49
|
+
return this.toLowerCase().replace(/(^|\s)\w/g, (match) => match.toUpperCase());
|
|
50
|
+
};
|
|
51
|
+
String.prototype.toCleanCase = function() {
|
|
52
|
+
return this.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\s+/g, " ").split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ").trim().replace(/\b\w{1,3}\b/g, (match) => match.toUpperCase());
|
|
53
|
+
};
|
|
54
|
+
String.prototype.truncate = function(n, suffix = "…") {
|
|
55
|
+
return this.length > n ? this.slice(0, n - 1) + suffix : this.toString();
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/utilities/helpers.ts
|
|
60
|
+
/**
|
|
61
|
+
* Takes a value like the one prodvided in a destructuring assignment where for instance
|
|
62
|
+
* a single value might have been provided as opposed to an array of values and
|
|
63
|
+
* normalizes it.
|
|
64
|
+
*
|
|
65
|
+
* Examples:
|
|
66
|
+
* Definition:
|
|
67
|
+
* (...value: string[]) => normalizeValue(value)
|
|
68
|
+
*
|
|
69
|
+
* Usage:
|
|
70
|
+
* normalizeValue('singleValue') // returns ['singleValue']
|
|
71
|
+
* normalizeValue('value1', 'value2') // returns ['value1', 'value2']
|
|
72
|
+
* normalizeValue(['value1', 'value2']) // returns ['value1', 'value2']
|
|
73
|
+
*
|
|
74
|
+
* @param value
|
|
75
|
+
* @returns
|
|
76
|
+
*/
|
|
77
|
+
const normalizeValue = (value) => {
|
|
78
|
+
if (Array.isArray(value)) return value;
|
|
79
|
+
return [value];
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Builds a full url based on endpoint provided
|
|
83
|
+
*
|
|
84
|
+
* @param baseUrl
|
|
85
|
+
* @param endpoint
|
|
86
|
+
*
|
|
87
|
+
* @returns
|
|
88
|
+
*/
|
|
89
|
+
const buildUrl = (baseUrl, ...endpoint) => {
|
|
90
|
+
return (baseUrl + path.default.normalize(path.default.join(...normalizeValue(endpoint)))).replace(/([^:]\/)\/+/g, "$1");
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/Builder.ts
|
|
95
|
+
var Builder = class {
|
|
96
|
+
static baseUrls = {
|
|
97
|
+
live: "https://api.flutterwave.com/v4/",
|
|
98
|
+
sandbox: "https://developersandbox-api.flutterwave.com/"
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Flutterwave Environment
|
|
102
|
+
*/
|
|
103
|
+
static environment;
|
|
104
|
+
constructor() {}
|
|
105
|
+
/**
|
|
106
|
+
* Sets the environment for the builder
|
|
107
|
+
*
|
|
108
|
+
* @param env
|
|
109
|
+
*/
|
|
110
|
+
static setEnvironment(env) {
|
|
111
|
+
this.environment = env;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Gets the base url based on environment
|
|
115
|
+
*
|
|
116
|
+
* @returns
|
|
117
|
+
*/
|
|
118
|
+
static baseUrl() {
|
|
119
|
+
if ((process.env.ENVIRONMENT || this.environment || "sandbox") === "live") return this.baseUrls.live;
|
|
120
|
+
return this.baseUrls.sandbox;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Builds a full url based on endpoint provided
|
|
124
|
+
*
|
|
125
|
+
* @param endpoint
|
|
126
|
+
* @returns
|
|
127
|
+
*/
|
|
128
|
+
static buildUrl(...endpoint) {
|
|
129
|
+
return buildUrl(this.baseUrl(), ...endpoint);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Builds parameters for query or path
|
|
133
|
+
*
|
|
134
|
+
* @param params
|
|
135
|
+
* @param type
|
|
136
|
+
* @returns
|
|
137
|
+
*/
|
|
138
|
+
static buildParams(params, type = "path") {
|
|
139
|
+
let built = "";
|
|
140
|
+
if (type === "path") built = Object.values(params).join("/");
|
|
141
|
+
else {
|
|
142
|
+
const queryParams = [];
|
|
143
|
+
for (const [key, value] of Object.entries(params)) queryParams.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
|
|
144
|
+
built = queryParams.join("&");
|
|
145
|
+
}
|
|
146
|
+
return built;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Assigns parameters to url {placeholders} based on type
|
|
150
|
+
*
|
|
151
|
+
* @param url
|
|
152
|
+
* @param params
|
|
153
|
+
* @param type
|
|
154
|
+
*
|
|
155
|
+
* @returns
|
|
156
|
+
*/
|
|
157
|
+
static assignParamsToUrl(url, params, type = "path") {
|
|
158
|
+
let builtUrl = url;
|
|
159
|
+
if (type === "path") for (const [key, value] of Object.entries(params)) {
|
|
160
|
+
builtUrl = builtUrl.replace(`{${key}}`, encodeURIComponent(String(value)));
|
|
161
|
+
builtUrl = builtUrl.replace(`:${key}`, encodeURIComponent(String(value)));
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
if (Object.keys(params).length === 0) return builtUrl;
|
|
165
|
+
const queryString = this.buildParams(params, "query");
|
|
166
|
+
const separator = builtUrl.includes("?") ? "&" : "?";
|
|
167
|
+
builtUrl += `${separator}${queryString}`;
|
|
168
|
+
}
|
|
169
|
+
return builtUrl;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Builds the target url by assigning both path and query parameters
|
|
173
|
+
*
|
|
174
|
+
* @param path
|
|
175
|
+
* @param params
|
|
176
|
+
* @param queryParams
|
|
177
|
+
* @returns
|
|
178
|
+
*/
|
|
179
|
+
static buildTargetUrl(path$2, params = {}, queryParams = {}) {
|
|
180
|
+
const url = this.buildUrl(path$2);
|
|
181
|
+
let builtUrl = this.assignParamsToUrl(url, params, "path");
|
|
182
|
+
builtUrl = this.assignParamsToUrl(builtUrl, queryParams, "query");
|
|
183
|
+
return builtUrl;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Encrypts specified keys in the input object and returns a new object with
|
|
187
|
+
* encrypted values.
|
|
188
|
+
*
|
|
189
|
+
* @param input The input object containing the data to be encrypted.
|
|
190
|
+
* @param keysToEncrypt The keys in the input object that should be encrypted.
|
|
191
|
+
* @param outputMapping A mapping of input keys to output keys for the encrypted values.
|
|
192
|
+
* @returns A new object with the specified keys encrypted.
|
|
193
|
+
*/
|
|
194
|
+
static async encryptDetails(input, keysToEncrypt = [], outputMapping = {}) {
|
|
195
|
+
const nonce = crypto.default.randomBytes(12).toString("base64").slice(0, 12);
|
|
196
|
+
const encryptableKeys = keysToEncrypt.length > 0 ? keysToEncrypt : Object.keys(input);
|
|
197
|
+
const encrypted = Object.fromEntries(Object.entries(input).map(([key, value]) => {
|
|
198
|
+
if (encryptableKeys.includes(key) && typeof value === "string") return [outputMapping?.[key] || key, this.encryptAES(value, process.env.ENCRYPTION_KEY, nonce)];
|
|
199
|
+
return [key, value];
|
|
200
|
+
}));
|
|
201
|
+
for (const key of encryptableKeys) delete input[key];
|
|
202
|
+
return encrypted;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Encrypts data using AES-GCM encryption
|
|
206
|
+
* @param data
|
|
207
|
+
* @param token
|
|
208
|
+
* @param nonce
|
|
209
|
+
* @returns
|
|
210
|
+
*/
|
|
211
|
+
static async encryptAES(data, token, nonce) {
|
|
212
|
+
if (nonce.length !== 12) throw new Error("Nonce must be exactly 12 characters long");
|
|
213
|
+
const cryptoSubtle = globalThis.crypto?.subtle || crypto.default.webcrypto?.subtle;
|
|
214
|
+
if (!cryptoSubtle) throw new Error("Crypto API is not available in this environment.");
|
|
215
|
+
const decodedKeyBytes = Uint8Array.from(atob(token), (c) => c.charCodeAt(0));
|
|
216
|
+
const key = await cryptoSubtle.importKey("raw", decodedKeyBytes, { name: "AES-GCM" }, false, ["encrypt"]);
|
|
217
|
+
const iv = new TextEncoder().encode(nonce);
|
|
218
|
+
const encryptedData = await cryptoSubtle.encrypt({
|
|
219
|
+
name: "AES-GCM",
|
|
220
|
+
iv
|
|
221
|
+
}, key, new TextEncoder().encode(data));
|
|
222
|
+
return btoa(String.fromCharCode(...new Uint8Array(encryptedData)));
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
//#endregion
|
|
227
|
+
//#region src/Exceptions/BadRequestException.ts
|
|
228
|
+
var BadRequestException = class extends Error {
|
|
229
|
+
statusCode = 400;
|
|
230
|
+
type = "INVALID_REQUEST";
|
|
231
|
+
code = "400";
|
|
232
|
+
data;
|
|
233
|
+
constructor(data) {
|
|
234
|
+
super(data?.error.message ?? data?.message ?? "Bad request. The server could not understand the request due to invalid syntax.");
|
|
235
|
+
if (data?.error.code) this.type = data.error.type;
|
|
236
|
+
if (data?.error.code) this.code = data.error.code;
|
|
237
|
+
this.data = data ?? {
|
|
238
|
+
status: "failed",
|
|
239
|
+
error: {
|
|
240
|
+
type: this.type,
|
|
241
|
+
code: this.code,
|
|
242
|
+
message: this.message,
|
|
243
|
+
validation_errors: []
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
this.name = "BadRequestException";
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
//#endregion
|
|
251
|
+
//#region src/Exceptions/ForbiddenRequestException.ts
|
|
252
|
+
var ForbiddenRequestException = class extends Error {
|
|
253
|
+
statusCode = 403;
|
|
254
|
+
type = "FORBIDDEN";
|
|
255
|
+
code = "403";
|
|
256
|
+
data;
|
|
257
|
+
constructor(data) {
|
|
258
|
+
super(data?.error.message ?? data?.message ?? "Forbidden request. You do not have permission to access this resource.");
|
|
259
|
+
if (data?.error.code) this.type = data.error.type;
|
|
260
|
+
if (data?.error.code) this.code = data.error.code;
|
|
261
|
+
this.data = data ?? {
|
|
262
|
+
status: "failed",
|
|
263
|
+
error: {
|
|
264
|
+
type: this.type,
|
|
265
|
+
code: this.code,
|
|
266
|
+
message: this.message
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
this.name = "ForbiddenRequestException";
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region src/Exceptions/UnauthorizedRequestException.ts
|
|
275
|
+
var UnauthorizedRequestException = class extends Error {
|
|
276
|
+
statusCode = 401;
|
|
277
|
+
type = "UNAUTHORIZED";
|
|
278
|
+
code = "401";
|
|
279
|
+
data;
|
|
280
|
+
constructor(data) {
|
|
281
|
+
super(data?.error.message ?? data?.message ?? "Forbidden request. You do not have permission to access this resource.");
|
|
282
|
+
if (data?.error.code) this.type = data.error.type;
|
|
283
|
+
if (data?.error.code) this.code = data.error.code;
|
|
284
|
+
this.data = data ?? {
|
|
285
|
+
status: "failed",
|
|
286
|
+
error: {
|
|
287
|
+
type: this.type,
|
|
288
|
+
code: this.code,
|
|
289
|
+
message: this.message
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
this.name = "UnauthorizedRequestException";
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
//#endregion
|
|
297
|
+
//#region src/Exceptions/HttpException.ts
|
|
298
|
+
var HttpException = class HttpException extends Error {
|
|
299
|
+
statusCode = 500;
|
|
300
|
+
parent;
|
|
301
|
+
constructor(data, statusCode, parent) {
|
|
302
|
+
super(data.message);
|
|
303
|
+
this.data = data;
|
|
304
|
+
this.name = "HttpException";
|
|
305
|
+
this.parent = parent;
|
|
306
|
+
if (statusCode) this.statusCode = statusCode;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Create an exception from status code
|
|
310
|
+
*
|
|
311
|
+
* @param code
|
|
312
|
+
* @param data
|
|
313
|
+
*/
|
|
314
|
+
static fromCode(code, data, parent) {
|
|
315
|
+
switch (code) {
|
|
316
|
+
case 400: return new BadRequestException(data);
|
|
317
|
+
case 401: return new UnauthorizedRequestException(data);
|
|
318
|
+
case 403: return new ForbiddenRequestException(data);
|
|
319
|
+
default: return new HttpException(data, code, parent);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
//#endregion
|
|
325
|
+
//#region src/Http.ts
|
|
326
|
+
var Http = class Http {
|
|
327
|
+
/**
|
|
328
|
+
* Bearer token
|
|
329
|
+
*/
|
|
330
|
+
static bearerToken;
|
|
331
|
+
/**
|
|
332
|
+
* Debug level
|
|
333
|
+
*/
|
|
334
|
+
static debugLevel = 0;
|
|
335
|
+
static apiInstance;
|
|
336
|
+
/**
|
|
337
|
+
* Creates an instance of Http.
|
|
338
|
+
*
|
|
339
|
+
* @param method
|
|
340
|
+
* @param url
|
|
341
|
+
* @param headers
|
|
342
|
+
* @param body
|
|
343
|
+
*/
|
|
344
|
+
constructor(headers = {}, body) {
|
|
345
|
+
this.headers = headers;
|
|
346
|
+
this.body = body;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Set the debug level
|
|
350
|
+
*
|
|
351
|
+
* @param debug
|
|
352
|
+
*/
|
|
353
|
+
static setDebugLevel(level = 0) {
|
|
354
|
+
this.debugLevel = level ?? 0;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Set the API instance
|
|
358
|
+
*
|
|
359
|
+
* @param api
|
|
360
|
+
*/
|
|
361
|
+
static setApiInstance(api) {
|
|
362
|
+
this.apiInstance = api;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Set the bearer token
|
|
366
|
+
*
|
|
367
|
+
* @param token
|
|
368
|
+
*/
|
|
369
|
+
static setBearerToken(token) {
|
|
370
|
+
this.bearerToken = token;
|
|
371
|
+
}
|
|
372
|
+
setDefaultHeaders(defaults) {
|
|
373
|
+
this.headers = {
|
|
374
|
+
...defaults,
|
|
375
|
+
...this.headers
|
|
376
|
+
};
|
|
377
|
+
if (Http.bearerToken) this.headers.Authorization = `Bearer ${Http.bearerToken}`;
|
|
378
|
+
}
|
|
379
|
+
getHeaders() {
|
|
380
|
+
return this.headers;
|
|
381
|
+
}
|
|
382
|
+
getBody() {
|
|
383
|
+
return this.body;
|
|
384
|
+
}
|
|
385
|
+
axiosApi() {
|
|
386
|
+
this.setDefaultHeaders({
|
|
387
|
+
"Accept": "application/json",
|
|
388
|
+
"Content-Type": "application/json"
|
|
389
|
+
});
|
|
390
|
+
const instance = axios.default.create({
|
|
391
|
+
baseURL: Builder.baseUrl(),
|
|
392
|
+
headers: this.getHeaders()
|
|
393
|
+
});
|
|
394
|
+
if (Http.debugLevel > 0) {
|
|
395
|
+
instance.interceptors.request.use((request) => {
|
|
396
|
+
console.log("Starting Request", JSON.stringify({
|
|
397
|
+
url: request.url,
|
|
398
|
+
method: request.method,
|
|
399
|
+
headers: Http.debugLevel < 3 ? Object.fromEntries(Object.entries(request.headers || {}).filter(([key]) => key.toLowerCase() !== "authorization")) : request.headers,
|
|
400
|
+
params: request.params,
|
|
401
|
+
data: request.data
|
|
402
|
+
}, null, 2));
|
|
403
|
+
return request;
|
|
404
|
+
}, (error) => {
|
|
405
|
+
console.log("Request Error:", JSON.stringify(error, null, 2));
|
|
406
|
+
return Promise.reject(error);
|
|
407
|
+
});
|
|
408
|
+
instance.interceptors.response.use((response) => {
|
|
409
|
+
console.log("Response:", JSON.stringify({
|
|
410
|
+
status: response.status,
|
|
411
|
+
data: Http.debugLevel < 3 ? Object.fromEntries(Object.entries(response.data || {}).filter(([key]) => key.toLowerCase() !== "access_token")) : response.data
|
|
412
|
+
}, null, 2));
|
|
413
|
+
return response;
|
|
414
|
+
}, (error) => {
|
|
415
|
+
console.log("Response Error:", JSON.stringify({
|
|
416
|
+
status: error.response?.status,
|
|
417
|
+
data: Object.fromEntries(Object.entries(error.response?.data || {}).filter(([key]) => key.toLowerCase() !== "access_token"))
|
|
418
|
+
}, null, 2));
|
|
419
|
+
return Promise.reject(error);
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
return instance;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Makes a GET request
|
|
426
|
+
*
|
|
427
|
+
* @param url
|
|
428
|
+
* @param headers
|
|
429
|
+
* @param params
|
|
430
|
+
* @returns
|
|
431
|
+
*/
|
|
432
|
+
static async get(url, params, headers = {}) {
|
|
433
|
+
return this.send(url, "GET", void 0, headers, params);
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
*
|
|
437
|
+
*
|
|
438
|
+
* @param url
|
|
439
|
+
* @param headers
|
|
440
|
+
* @param params
|
|
441
|
+
* @returns
|
|
442
|
+
*/
|
|
443
|
+
static async send(url, method, body, headers = {}, params) {
|
|
444
|
+
try {
|
|
445
|
+
const { data } = await new Http(headers).axiosApi()({
|
|
446
|
+
url,
|
|
447
|
+
method,
|
|
448
|
+
data: body,
|
|
449
|
+
params
|
|
450
|
+
});
|
|
451
|
+
return {
|
|
452
|
+
success: true,
|
|
453
|
+
message: data.message || "Request successful",
|
|
454
|
+
data: data.data ?? data,
|
|
455
|
+
meta: data.meta
|
|
456
|
+
};
|
|
457
|
+
} catch (e) {
|
|
458
|
+
const error = e.response?.data ?? {};
|
|
459
|
+
throw this.exception(e.response?.status ?? 500, error || e, e);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Create an HttpException from status and error
|
|
464
|
+
*
|
|
465
|
+
* @param status
|
|
466
|
+
* @param error
|
|
467
|
+
* @returns
|
|
468
|
+
*/
|
|
469
|
+
static exception(status, error, originalError) {
|
|
470
|
+
const exception = HttpException.fromCode(status, {
|
|
471
|
+
success: false,
|
|
472
|
+
message: `Request failed: ${error.error?.message || "An error occurred"}`,
|
|
473
|
+
status: "failed",
|
|
474
|
+
data: void 0,
|
|
475
|
+
meta: {},
|
|
476
|
+
error: {
|
|
477
|
+
type: ((typeof error.error === "string" ? error.error : error.error?.type) ?? "UNKNOWN_ERROR").toUpperCase(),
|
|
478
|
+
code: error.error?.code ?? "000000",
|
|
479
|
+
message: error.error?.message ?? error.error_description ?? error.message,
|
|
480
|
+
validation_errors: error.error?.validation_errors ?? []
|
|
481
|
+
}
|
|
482
|
+
}, originalError);
|
|
483
|
+
if (this.apiInstance) this.apiInstance.setLastException(exception);
|
|
484
|
+
return exception;
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
//#endregion
|
|
489
|
+
//#region src/Apis/BaseApi.ts
|
|
490
|
+
var BaseApi = class {
|
|
491
|
+
/**
|
|
492
|
+
* Flutterwave instance
|
|
493
|
+
*/
|
|
494
|
+
#core;
|
|
495
|
+
lastException;
|
|
496
|
+
/**
|
|
497
|
+
* Create a BaseApi instance
|
|
498
|
+
*
|
|
499
|
+
* @param coreInstance Core instance
|
|
500
|
+
*/
|
|
501
|
+
constructor(coreInstance) {
|
|
502
|
+
this.#core = coreInstance;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Get the owning core instance for SDK-specific API bootstrapping.
|
|
506
|
+
*/
|
|
507
|
+
get core() {
|
|
508
|
+
return this.#core;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Hook for SDK-specific API registration.
|
|
512
|
+
*/
|
|
513
|
+
boot() {}
|
|
514
|
+
/**
|
|
515
|
+
* Set access validator function
|
|
516
|
+
*
|
|
517
|
+
* @param validator
|
|
518
|
+
*/
|
|
519
|
+
setAccessValidator(validator) {
|
|
520
|
+
this.#core.setAccessValidator(validator);
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Get the last exception
|
|
524
|
+
*
|
|
525
|
+
* @returns
|
|
526
|
+
*/
|
|
527
|
+
getLastException() {
|
|
528
|
+
return this.lastException;
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Set the last exception
|
|
532
|
+
*
|
|
533
|
+
* @param exception
|
|
534
|
+
*/
|
|
535
|
+
setLastException(exception) {
|
|
536
|
+
this.lastException = exception;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Initialize BaseApi and its sub-APIs
|
|
540
|
+
*
|
|
541
|
+
* @param coreInstance Core instance
|
|
542
|
+
* @returns
|
|
543
|
+
*/
|
|
544
|
+
static initialize(coreInstance) {
|
|
545
|
+
Http.setDebugLevel(coreInstance.debugLevel);
|
|
546
|
+
const baseApi = new this(coreInstance);
|
|
547
|
+
Http.setApiInstance(baseApi);
|
|
548
|
+
baseApi.boot();
|
|
549
|
+
return baseApi;
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
//#endregion
|
|
554
|
+
//#region src/RuntimeSdk.ts
|
|
555
|
+
const createRuntimeApi = (core, bundle) => {
|
|
556
|
+
const api = BaseApi.initialize(core);
|
|
557
|
+
for (const group of bundle.manifest.groups) {
|
|
558
|
+
const namespace = {};
|
|
559
|
+
for (const operation of group.operations) namespace[operation.methodName] = async (...args) => {
|
|
560
|
+
await core.validateAccess();
|
|
561
|
+
const [pathParams, queryParams, body, headers] = normalizeRuntimeArguments(operation, args);
|
|
562
|
+
validateRequiredArguments(operation.pathParams, pathParams, "path");
|
|
563
|
+
validateRequiredArguments(operation.queryParams, queryParams, "query");
|
|
564
|
+
validateRequiredArguments(operation.headerParams, headers, "header");
|
|
565
|
+
if (operation.hasBody && operation.bodyRequired && body == null) throw new Error(`Missing required request body for ${operation.method} ${operation.path}`);
|
|
566
|
+
const { data } = await Http.send(core.builder.buildTargetUrl(operation.path, pathParams, queryParams), operation.method, body ?? {}, headers);
|
|
567
|
+
return data;
|
|
568
|
+
};
|
|
569
|
+
api[group.propertyName] = namespace;
|
|
570
|
+
}
|
|
571
|
+
return api;
|
|
572
|
+
};
|
|
573
|
+
const createSdk = (bundle, options) => {
|
|
574
|
+
return new Core(options).useDocument(bundle);
|
|
575
|
+
};
|
|
576
|
+
const normalizeRuntimeArguments = (operation, args) => {
|
|
577
|
+
let cursor = 0;
|
|
578
|
+
return [
|
|
579
|
+
operation.pathParams.length > 0 ? toRecord(args[cursor++]) : {},
|
|
580
|
+
operation.queryParams.length > 0 ? toRecord(args[cursor++]) : {},
|
|
581
|
+
operation.hasBody ? args[cursor++] : void 0,
|
|
582
|
+
operation.headerParams.length > 0 ? toRecord(args[cursor++]) : {}
|
|
583
|
+
];
|
|
584
|
+
};
|
|
585
|
+
const validateRequiredArguments = (parameters, values, location) => {
|
|
586
|
+
for (const parameter of parameters) {
|
|
587
|
+
if (!parameter.required) continue;
|
|
588
|
+
if (values[parameter.name] === void 0 && values[parameter.accessor] === void 0) throw new Error(`Missing required ${location} parameter: ${parameter.name}`);
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
const toRecord = (value) => {
|
|
592
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) return value;
|
|
593
|
+
return {};
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
//#endregion
|
|
597
|
+
//#region src/Core.ts
|
|
598
|
+
var Core = class {
|
|
599
|
+
static apiClass = BaseApi;
|
|
600
|
+
debugLevel = 0;
|
|
601
|
+
/**
|
|
602
|
+
* Client ID
|
|
603
|
+
*/
|
|
604
|
+
clientId;
|
|
605
|
+
/**
|
|
606
|
+
* Client Secret
|
|
607
|
+
*/
|
|
608
|
+
clientSecret;
|
|
609
|
+
/**
|
|
610
|
+
* Flutterwave Environment
|
|
611
|
+
*/
|
|
612
|
+
environment = "live";
|
|
613
|
+
accessValidator;
|
|
614
|
+
/**
|
|
615
|
+
* API Instance
|
|
616
|
+
*/
|
|
617
|
+
api;
|
|
618
|
+
/**
|
|
619
|
+
* Builder Instance
|
|
620
|
+
*/
|
|
621
|
+
builder = Builder;
|
|
622
|
+
constructor(clientId, clientSecret, encryptionKey, env) {
|
|
623
|
+
if (typeof clientId === "object") {
|
|
624
|
+
this.clientId = clientId.clientId;
|
|
625
|
+
this.clientSecret = clientId.clientSecret;
|
|
626
|
+
this.environment = clientId.environment ?? "live";
|
|
627
|
+
} else {
|
|
628
|
+
this.clientId = clientId ?? process.env.CLIENT_ID ?? "";
|
|
629
|
+
this.clientSecret = clientSecret ?? process.env.CLIENT_SECRET ?? "";
|
|
630
|
+
this.environment = env ?? process.env.ENVIRONMENT ?? "live";
|
|
631
|
+
}
|
|
632
|
+
if (!this.clientId || !this.clientSecret) throw new Error("Client ID and Client Secret are required to initialize Flutterwave instance");
|
|
633
|
+
this.builder.setEnvironment(this.environment);
|
|
634
|
+
this.api = this.createApi();
|
|
635
|
+
}
|
|
636
|
+
createApi() {
|
|
637
|
+
return (this.constructor.apiClass ?? BaseApi).initialize(this);
|
|
638
|
+
}
|
|
639
|
+
init(clientId, clientSecret, encryptionKey, env) {
|
|
640
|
+
return new this.constructor(clientId, clientSecret, encryptionKey, env);
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Set the debug level
|
|
644
|
+
*
|
|
645
|
+
* @param level
|
|
646
|
+
* @returns
|
|
647
|
+
*/
|
|
648
|
+
debug(level = 0) {
|
|
649
|
+
this.debugLevel = level;
|
|
650
|
+
Http.setDebugLevel(level);
|
|
651
|
+
return this;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Get the current environment
|
|
655
|
+
*
|
|
656
|
+
* @returns
|
|
657
|
+
*/
|
|
658
|
+
getEnvironment() {
|
|
659
|
+
return this.environment;
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Set access validator function
|
|
663
|
+
*
|
|
664
|
+
* @param validator Function to validate access
|
|
665
|
+
*/
|
|
666
|
+
setAccessValidator(validator) {
|
|
667
|
+
this.accessValidator = validator;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Validates access using the provided access validator function
|
|
671
|
+
*
|
|
672
|
+
* @throws Error if validation fails
|
|
673
|
+
*/
|
|
674
|
+
async validateAccess() {
|
|
675
|
+
const check = this.accessValidator ? await this.accessValidator(this) : true;
|
|
676
|
+
if (check !== true) throw new Error(typeof check === "string" ? check : "Access validation failed");
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Use a manifest bundle to create the API instance
|
|
680
|
+
*
|
|
681
|
+
* @param bundle
|
|
682
|
+
* @returns
|
|
683
|
+
*/
|
|
684
|
+
useDocument(bundle) {
|
|
685
|
+
this.api = createRuntimeApi(this, bundle);
|
|
686
|
+
return this;
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Use a manifest bundle to create the API instance (alias for useDocument).
|
|
690
|
+
*
|
|
691
|
+
* @param bundle
|
|
692
|
+
* @returns
|
|
693
|
+
*/
|
|
694
|
+
useSdk(bundle) {
|
|
695
|
+
return this.useDocument(bundle);
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
//#endregion
|
|
700
|
+
//#region src/utilities/Manager.ts
|
|
701
|
+
const defaultConfig = {
|
|
702
|
+
environment: "sandbox",
|
|
703
|
+
urls: {
|
|
704
|
+
live: "",
|
|
705
|
+
sandbox: ""
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
let globalConfig = defaultConfig;
|
|
709
|
+
const defineConfig = (config) => {
|
|
710
|
+
const userConfig = {
|
|
711
|
+
...defaultConfig,
|
|
712
|
+
...config,
|
|
713
|
+
urls: {
|
|
714
|
+
...defaultConfig.urls,
|
|
715
|
+
...config.urls
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
globalConfig = userConfig;
|
|
719
|
+
return userConfig;
|
|
720
|
+
};
|
|
721
|
+
|
|
722
|
+
//#endregion
|
|
723
|
+
//#region src/utilities/WebhookValidator.ts
|
|
724
|
+
var WebhookValidator = class {
|
|
725
|
+
algorithm = "sha256";
|
|
726
|
+
encoding = "base64";
|
|
727
|
+
/**
|
|
728
|
+
* @param secretHash
|
|
729
|
+
* @param options
|
|
730
|
+
*/
|
|
731
|
+
constructor(secretHash, options) {
|
|
732
|
+
this.secretHash = secretHash;
|
|
733
|
+
this.algorithm = options?.hashAlgorithm || "sha256";
|
|
734
|
+
this.encoding = options?.encoding || "base64";
|
|
735
|
+
if (!this.secretHash || this.secretHash.length === 0) this.secretHash = process.env.SECRET_HASH;
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Validate webhook signature
|
|
739
|
+
* @param rawBody - Raw request body as string
|
|
740
|
+
* @param signature - Signature from request header
|
|
741
|
+
* @returns {boolean} - True if signature is valid
|
|
742
|
+
*/
|
|
743
|
+
validate(rawBody, signature) {
|
|
744
|
+
if (!this.secretHash) throw new Error("Secret hash is required for validation");
|
|
745
|
+
return crypto.default.createHmac(this.algorithm, this.secretHash).update(rawBody).digest(this.encoding) === signature;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Generate signature for testing/verification
|
|
749
|
+
* @param rawBody - Raw request body as string
|
|
750
|
+
* @returns {string} - Generated signature
|
|
751
|
+
*/
|
|
752
|
+
generateSignature(rawBody) {
|
|
753
|
+
if (!this.secretHash) throw new Error("Secret hash is required to generate signature");
|
|
754
|
+
return crypto.default.createHmac(this.algorithm, this.secretHash).update(rawBody).digest(this.encoding);
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Async version of validate (for large payloads)
|
|
758
|
+
*
|
|
759
|
+
* @param rawBody
|
|
760
|
+
* @param signature
|
|
761
|
+
* @returns {Promise<boolean>}
|
|
762
|
+
*/
|
|
763
|
+
validateAsync(rawBody, signature) {
|
|
764
|
+
if (!this.secretHash) throw new Error("Secret hash is required for validation");
|
|
765
|
+
const hmac = crypto.default.createHmac(this.algorithm, this.secretHash);
|
|
766
|
+
hmac.update(rawBody);
|
|
767
|
+
return hmac.digest(this.encoding) === signature;
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Get current configuration
|
|
771
|
+
*/
|
|
772
|
+
getConfig() {
|
|
773
|
+
return {
|
|
774
|
+
algorithm: this.algorithm,
|
|
775
|
+
encoding: this.encoding,
|
|
776
|
+
secretHashLength: this.secretHash?.length
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
|
|
781
|
+
//#endregion
|
|
782
|
+
exports.BadRequestException = BadRequestException;
|
|
783
|
+
exports.BaseApi = BaseApi;
|
|
784
|
+
exports.Builder = Builder;
|
|
785
|
+
exports.Core = Core;
|
|
786
|
+
exports.ForbiddenRequestException = ForbiddenRequestException;
|
|
787
|
+
exports.Http = Http;
|
|
788
|
+
exports.HttpException = HttpException;
|
|
789
|
+
exports.UnauthorizedRequestException = UnauthorizedRequestException;
|
|
790
|
+
exports.WebhookValidator = WebhookValidator;
|
|
791
|
+
exports.buildUrl = buildUrl;
|
|
792
|
+
exports.createRuntimeApi = createRuntimeApi;
|
|
793
|
+
exports.createSdk = createSdk;
|
|
794
|
+
exports.defaultConfig = defaultConfig;
|
|
795
|
+
exports.defineConfig = defineConfig;
|
|
796
|
+
Object.defineProperty(exports, 'globalConfig', {
|
|
797
|
+
enumerable: true,
|
|
798
|
+
get: function () {
|
|
799
|
+
return globalConfig;
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
exports.normalizeValue = normalizeValue;
|