bison-web-components 1.0.0

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/api.js ADDED
@@ -0,0 +1,659 @@
1
+ /**
2
+ * API Service for BisonJibPay Embeddable Endpoints
3
+ *
4
+ * This class provides a centralized way to interact with the BisonJibPay API.
5
+ * It handles authentication via embeddable keys and provides methods for
6
+ * common operations like operator validation, registration, and token generation.
7
+ *
8
+ * @class BisonJibPayAPI
9
+ * @author @kfajardo
10
+ * @version 1.0.0
11
+ *
12
+ * @example
13
+ * // Initialize the API
14
+ * const api = new BisonJibPayAPI(
15
+ * 'https://your-api.com',
16
+ * 'your-embeddable-key'
17
+ * );
18
+ *
19
+ * // Validate operator email
20
+ * const result = await api.validateOperatorEmail(
21
+ * 'operator@example.com',
22
+ * 'OP123456'
23
+ * );
24
+ *
25
+ * // Generate Moov token
26
+ * const token = await api.generateMoovToken('operator@example.com');
27
+ */
28
+ class BisonJibPayAPI {
29
+ constructor(baseURL, embeddableKey) {
30
+ if (!embeddableKey || typeof embeddableKey !== 'string' || !embeddableKey.trim()) {
31
+ throw new Error("Missing required 'x-embeddable-key' for BisonJibPayAPI");
32
+ }
33
+ this.baseURL = baseURL || "https://bison-jib-development.azurewebsites.net";
34
+ this.embeddableKey = embeddableKey;
35
+ }
36
+
37
+ /**
38
+ * Make authenticated API request
39
+ * @private
40
+ */
41
+ async request(endpoint, options = {}) {
42
+ if (!this.embeddableKey || !this.embeddableKey.trim()) {
43
+ throw new Error("Missing required 'x-embeddable-key' for BisonJibPayAPI request");
44
+ }
45
+
46
+ const url = `${this.baseURL}${endpoint}`;
47
+ const headers = {
48
+ "X-Embeddable-Key": this.embeddableKey,
49
+ ...options.headers,
50
+ };
51
+
52
+ // Don't add Content-Type for FormData
53
+ if (!(options.body instanceof FormData)) {
54
+ headers["Content-Type"] = "application/json";
55
+ }
56
+
57
+ try {
58
+ const response = await fetch(url, {
59
+ ...options,
60
+ headers,
61
+ });
62
+
63
+ const data = await response.json();
64
+
65
+ if (!response.ok) {
66
+ throw {
67
+ status: response.status,
68
+ data: data,
69
+ };
70
+ }
71
+
72
+ return data;
73
+ } catch (error) {
74
+ // Re-throw with structured error
75
+ if (error.status) throw error;
76
+ throw {
77
+ status: 500,
78
+ data: {
79
+ success: false,
80
+ message: "Network error occurred",
81
+ errors: [error.message],
82
+ },
83
+ };
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Validate operator email
89
+ *
90
+ * @param {string} email - Operator's email address
91
+ * @param {string} operatorId - Operator's ID
92
+ * @param {string|null} clientId - Optional client ID
93
+ */
94
+ async validateOperatorEmail(email, operatorId, clientId = null) {
95
+ const payload = { email, operatorId };
96
+ if (clientId) payload.clientId = clientId;
97
+ return this.request("/api/embeddable/validate/operator-email", {
98
+ method: "POST",
99
+ body: JSON.stringify(payload),
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Validate user email
105
+ *
106
+ * Checks if a user email exists in the system.
107
+ *
108
+ * @param {string} email - User's email address
109
+ * @returns {Promise<{success: boolean, message: string, data: {exists: boolean, message: string}, errors: string[], timestamp: string, traceId: string}>}
110
+ *
111
+ * @example
112
+ * const api = new BisonJibPayAPI(baseURL, embeddableKey);
113
+ * const result = await api.validateUserEmail('user@example.com');
114
+ * if (result.data.exists) {
115
+ * console.log('User email exists');
116
+ * }
117
+ */
118
+ async validateUserEmail(email) {
119
+ return this.request("/api/embeddable/validate/user-email", {
120
+ method: "POST",
121
+ body: JSON.stringify({ email }),
122
+ });
123
+ }
124
+
125
+ /**
126
+ * Verify operator email
127
+ * Checks if an operator is registered/onboarded in the system
128
+ *
129
+ * @param {string} email - Operator's email address
130
+ * @param {string} operatorId - Operator's ID
131
+ * @param {string|null} clientId - Optional client ID
132
+ * @returns {Promise<{success: boolean, message: string, data?: any}>}
133
+ *
134
+ * @example
135
+ * const api = new BisonJibPayAPI(baseURL, embeddableKey);
136
+ * const result = await api.verifyOperator('operator@example.com', 'OP123456');
137
+ * if (result.success) {
138
+ * console.log('Operator is verified');
139
+ * }
140
+ */
141
+ async verifyOperator(email, operatorId, clientId = null) {
142
+ return this.validateOperatorEmail(email, operatorId, clientId);
143
+ }
144
+
145
+ /**
146
+ * Verify WIO email
147
+ * Checks if a WIO (Worker Independent Operator) has an account in the system
148
+ *
149
+ * @param {string} email - WIO's email address
150
+ * @returns {Promise<{success: boolean, message: string, data?: {moovAccountId: string}}>}
151
+ *
152
+ * @example
153
+ * const api = new BisonJibPayAPI(baseURL, embeddableKey);
154
+ * const result = await api.verifyWio('wio@example.com');
155
+ * if (result.success && result.data?.moovAccountId) {
156
+ * console.log('WIO is verified with account:', result.data.moovAccountId);
157
+ * }
158
+ */
159
+ async verifyWio(email) {
160
+ return this.getAccountByEmail(email);
161
+ }
162
+
163
+ /**
164
+ * Register operator
165
+ */
166
+ async registerOperator(formData) {
167
+ return this.request("/api/embeddable/operator-registration", {
168
+ method: "POST",
169
+ body: formData, // FormData object
170
+ });
171
+ }
172
+
173
+ /**
174
+ * Register WIO
175
+ */
176
+ async registerWIO(payload) {
177
+ return this.request("/api/embeddable/wio-registration", {
178
+ method: "POST",
179
+ body: payload, // FormData object
180
+ });
181
+ }
182
+
183
+ async getAccountByEmail(operatorEmail) {
184
+ const param = new URLSearchParams();
185
+ param.append("email", operatorEmail);
186
+
187
+ return this.request(`/api/embeddable/moov-account-id?${param.toString()}`, {
188
+ method: "GET",
189
+ });
190
+ }
191
+
192
+ async getAccountByOperatorId(operatorId) {
193
+ const param = new URLSearchParams();
194
+ param.append("operatorId", operatorId);
195
+
196
+ return this.request(`/api/embeddable/moov-account-id?${param.toString()}`, {
197
+ method: "GET",
198
+ });
199
+ }
200
+
201
+ async getAccountByClientId(clientId) {
202
+ const param = new URLSearchParams();
203
+ param.append("clientId", clientId);
204
+
205
+ return this.request(`/api/embeddable/moov-account-id?${param.toString()}`, {
206
+ method: "GET",
207
+ });
208
+ }
209
+
210
+ /**
211
+ * Generate Moov access token for operator
212
+ *
213
+ * This method calls the backend API to generate a Moov token for payment operations.
214
+ * The backend handles the secure communication with Moov's API.
215
+ *
216
+ * @param {string} operatorEmail - Operator's email address
217
+ * @param {string|null} moovAccountId - Optional Moov account ID
218
+ * @param {string|null} operatorId - Optional operator ID
219
+ * @param {string|null} clientId - Optional client ID
220
+ * @returns {Promise<{access_token: string, expires_in?: number, scope?: string}>}
221
+ *
222
+ * @example
223
+ * const api = new BisonJibPayAPI(baseURL, embeddableKey);
224
+ * const tokenData = await api.generateMoovToken('operator@example.com');
225
+ * console.log(tokenData.access_token);
226
+ */
227
+ async generateMoovToken(operatorEmail, moovAccountId = null, operatorId = null, clientId = null) {
228
+ console.log("CALLED GENERATE MOOV TOKEN");
229
+
230
+ // Use provided moovAccountId or fetch it if not provided
231
+ let accountId = moovAccountId;
232
+ if (!accountId) {
233
+ if (operatorEmail) {
234
+ const account = await this.getAccountByEmail(operatorEmail);
235
+ accountId = account.data.moovAccountId;
236
+ } else if (operatorId) {
237
+ const account = await this.getAccountByOperatorId(operatorId);
238
+ accountId = account.data.moovAccountId;
239
+ } else if (clientId) {
240
+ const account = await this.getAccountByClientId(clientId);
241
+ accountId = account.data.moovAccountId;
242
+ } else {
243
+ throw {
244
+ status: 400,
245
+ data: {
246
+ success: false,
247
+ message: "Email, operator ID, or client ID is required to generate a token",
248
+ errors: ["Missing operator identifier"],
249
+ },
250
+ };
251
+ }
252
+ }
253
+ console.log("MOOV ACCOUNT ID", accountId);
254
+ let accountScopes = [
255
+ "/accounts/{ACCOUNT_ID}/bank-accounts.read",
256
+ "/accounts/{ACCOUNT_ID}/bank-accounts.write",
257
+ "/accounts/{ACCOUNT_ID}/capabilities.read",
258
+ "/accounts/{ACCOUNT_ID}/capabilities.write",
259
+ "/accounts/{ACCOUNT_ID}/cards.read",
260
+ "/accounts/{ACCOUNT_ID}/cards.write",
261
+ "/accounts/{ACCOUNT_ID}/profile.read",
262
+ "/accounts/{ACCOUNT_ID}/profile.write",
263
+ "/accounts/{ACCOUNT_ID}/representatives.read",
264
+ "/accounts/{ACCOUNT_ID}/representatives.write",
265
+ ];
266
+
267
+ if (accountId) {
268
+ accountScopes = accountScopes.map((value) =>
269
+ value.replace("{ACCOUNT_ID}", accountId)
270
+ );
271
+ }
272
+
273
+ const tokenPayload = {
274
+ scopes: [
275
+ "/accounts.read",
276
+ "/accounts.write",
277
+ "/fed.read",
278
+ "/profile-enrichment.read",
279
+ ...accountScopes,
280
+ ],
281
+ };
282
+
283
+ if (operatorEmail) {
284
+ tokenPayload.email = operatorEmail;
285
+ }
286
+
287
+ if (operatorId) {
288
+ tokenPayload.operatorId = operatorId;
289
+ }
290
+
291
+ if (clientId) {
292
+ tokenPayload.clientId = clientId;
293
+ }
294
+
295
+ return this.request("/api/embeddable/moov-access-token", {
296
+ method: "POST",
297
+ body: JSON.stringify(tokenPayload),
298
+ });
299
+ }
300
+
301
+ /**
302
+ * Generate Plaid Link token for WIO
303
+ *
304
+ * This method calls the backend API to generate a Plaid Link token for bank account linking.
305
+ * The token is used to initialize Plaid Link in the Moov payment drop.
306
+ *
307
+ * @param {string} wioEmail - WIO's email address
308
+ * @returns {Promise<{link_token: string, expiration?: string}>}
309
+ *
310
+ * @example
311
+ * const api = new BisonJibPayAPI(baseURL, embeddableKey);
312
+ * const tokenData = await api.generatePlaidToken('wio@example.com');
313
+ * console.log(tokenData.link_token);
314
+ */
315
+ async generatePlaidToken(wioEmail) {
316
+ return this.request("/api/embeddable/plaid/link-token", {
317
+ method: "POST",
318
+ body: JSON.stringify({
319
+ clientName: wioEmail,
320
+ countryCodes: ["US"],
321
+ user: {
322
+ clientUserId: "wio-email",
323
+ legalName: "Wio User",
324
+ },
325
+ products: ["transactions"],
326
+ client_name: "Personal Finance App",
327
+ }),
328
+ });
329
+ }
330
+
331
+ /**
332
+ * Create Plaid processor token
333
+ *
334
+ * Exchanges a Plaid public token for a processor token that can be used with Moov.
335
+ * This is called during the Plaid Link flow after the user selects their bank account.
336
+ *
337
+ * @param {string} publicToken - Plaid public token from Link flow
338
+ * @param {string} bankAccountId - Selected bank account ID
339
+ * @returns {Promise<{processor_token: string, bank_account_id: string}>}
340
+ *
341
+ * @example
342
+ * const api = new BisonJibPayAPI(baseURL, embeddableKey);
343
+ * const result = await api.createProcessorToken(publicToken, accountId);
344
+ * console.log(result.processor_token);
345
+ */
346
+ async createProcessorToken(publicToken, bankAccountId) {
347
+ return this.request("/api/embeddable/plaid/processor-token", {
348
+ method: "POST",
349
+ body: JSON.stringify({
350
+ publicToken,
351
+ accountId: bankAccountId,
352
+ }),
353
+ });
354
+ }
355
+
356
+ async addPlaidAccountToMoov(publicToken, bankAccountId, moovAccountId) {
357
+ return this.request("/api/embeddable/plaid/add-to-moov", {
358
+ method: "POST",
359
+ body: JSON.stringify({
360
+ publicToken,
361
+ moovAccountId,
362
+ accountId: bankAccountId,
363
+ }),
364
+ });
365
+ }
366
+
367
+ /**
368
+ * Get payment methods by moovAccountId directly
369
+ *
370
+ * This method fetches all available payment methods for the given moovAccountId.
371
+ * Use this when you already have the moovAccountId cached to avoid extra API calls.
372
+ *
373
+ * @param {string} moovAccountId - The Moov account ID
374
+ * @returns {Promise<{success: boolean, message: string, data: Array<{paymentMethodID: string, paymentMethodType: string, wallet?: object, bankAccount?: object, card?: object, applePay?: object}>, errors: string[], timestamp: string, traceId: string}>}
375
+ *
376
+ * @example
377
+ * const api = new BisonJibPayAPI(baseURL, embeddableKey);
378
+ * const paymentMethods = await api.getPaymentMethodsByAccountId('moov-account-id');
379
+ * console.log(paymentMethods.data); // Array of payment methods
380
+ */
381
+ async getPaymentMethodsByAccountId(moovAccountId) {
382
+ if (!moovAccountId) {
383
+ throw {
384
+ status: 400,
385
+ data: {
386
+ success: false,
387
+ message: "Moov account ID is required",
388
+ errors: ["moovAccountId parameter is missing"],
389
+ },
390
+ };
391
+ }
392
+
393
+ return this.request(`/api/embeddable/payment-methods/${moovAccountId}`, {
394
+ method: "GET",
395
+ });
396
+ }
397
+
398
+ /**
399
+ * Get payment methods for an operator by email
400
+ *
401
+ * This method first retrieves the operator's moovAccountId by email,
402
+ * then fetches all available payment methods for that account.
403
+ * Note: If you already have the moovAccountId, use getPaymentMethodsByAccountId() instead
404
+ * to avoid the extra API call.
405
+ *
406
+ * @param {string} operatorEmail - Operator's email address
407
+ * @returns {Promise<{success: boolean, message: string, data: Array<{paymentMethodID: string, paymentMethodType: string, wallet?: object, bankAccount?: object, card?: object, applePay?: object}>, errors: string[], timestamp: string, traceId: string}>}
408
+ *
409
+ * @example
410
+ * const api = new BisonJibPayAPI(baseURL, embeddableKey);
411
+ * const paymentMethods = await api.getPaymentMethods('operator@example.com');
412
+ * console.log(paymentMethods.data); // Array of payment methods
413
+ */
414
+ async getPaymentMethods(operatorEmail) {
415
+ // First, get the account by email to retrieve moovAccountId
416
+ const account = await this.getAccountByEmail(operatorEmail);
417
+ const moovAccountId = account.data?.moovAccountId || account.moovAccountId;
418
+
419
+ if (!moovAccountId) {
420
+ throw {
421
+ status: 404,
422
+ data: {
423
+ success: false,
424
+ message: "Moov account ID not found for the given email",
425
+ errors: ["No moovAccountId associated with this operator"],
426
+ },
427
+ };
428
+ }
429
+
430
+ // Use the direct method to fetch payment methods
431
+ return this.getPaymentMethodsByAccountId(moovAccountId);
432
+ }
433
+
434
+ /**
435
+ * Delete a payment method by moovAccountId and paymentMethodId directly
436
+ *
437
+ * Use this when you already have the moovAccountId cached to avoid extra API calls.
438
+ *
439
+ * @param {string} moovAccountId - The Moov account ID
440
+ * @param {string} paymentMethodId - The ID of the payment method to delete
441
+ * @returns {Promise<{success: boolean, message: string, data: string, errors: string[], timestamp: string, traceId: string}>}
442
+ *
443
+ * @example
444
+ * const api = new BisonJibPayAPI(baseURL, embeddableKey);
445
+ * const result = await api.deletePaymentMethodByAccountId('moov-account-id', 'pm_123456');
446
+ * console.log(result.success); // true if deleted successfully
447
+ */
448
+ async deletePaymentMethodByAccountId(moovAccountId, paymentMethodId) {
449
+ if (!moovAccountId) {
450
+ throw {
451
+ status: 400,
452
+ data: {
453
+ success: false,
454
+ message: "Moov account ID is required",
455
+ errors: ["moovAccountId parameter is missing"],
456
+ },
457
+ };
458
+ }
459
+
460
+ if (!paymentMethodId) {
461
+ throw {
462
+ status: 400,
463
+ data: {
464
+ success: false,
465
+ message: "Payment method ID is required",
466
+ errors: ["paymentMethodId parameter is missing"],
467
+ },
468
+ };
469
+ }
470
+
471
+ return this.request(
472
+ `/api/embeddable/bank-account/${moovAccountId}/${paymentMethodId}`,
473
+ {
474
+ method: "DELETE",
475
+ }
476
+ );
477
+ }
478
+
479
+ /**
480
+ * Delete a payment method by ID
481
+ *
482
+ * This method first retrieves the operator's moovAccountId by email,
483
+ * then deletes the specified payment method.
484
+ * Note: If you already have the moovAccountId, use deletePaymentMethodByAccountId() instead
485
+ * to avoid the extra API call.
486
+ *
487
+ * @param {string} operatorEmail - Operator's email address
488
+ * @param {string} paymentMethodId - The ID of the payment method to delete
489
+ * @returns {Promise<{success: boolean, message: string, data: string, errors: string[], timestamp: string, traceId: string}>}
490
+ *
491
+ * @example
492
+ * const api = new BisonJibPayAPI(baseURL, embeddableKey);
493
+ * const result = await api.deletePaymentMethodById('operator@example.com', 'pm_123456');
494
+ * console.log(result.success); // true if deleted successfully
495
+ */
496
+ async deletePaymentMethodById(operatorEmail, paymentMethodId) {
497
+ // First, get the account by email to retrieve moovAccountId
498
+ const account = await this.getAccountByEmail(operatorEmail);
499
+ const moovAccountId = account.data?.moovAccountId || account.moovAccountId;
500
+
501
+ if (!moovAccountId) {
502
+ throw {
503
+ status: 404,
504
+ data: {
505
+ success: false,
506
+ message: "Moov account ID not found for the given email",
507
+ errors: ["No moovAccountId associated with this operator"],
508
+ },
509
+ };
510
+ }
511
+
512
+ // Use the direct method to delete payment method
513
+ return this.deletePaymentMethodByAccountId(moovAccountId, paymentMethodId);
514
+ }
515
+ /**
516
+ * Fetch underwriting history by moovAccountId
517
+ *
518
+ * This method retrieves the underwriting history for the given moovAccountId.
519
+ * Use this when you already have the moovAccountId cached.
520
+ *
521
+ * Response Codes:
522
+ * - 200: Success with data array (may be empty)
523
+ * - 400: Missing or invalid moovAccountId parameter
524
+ * - 401: Invalid or missing X-Embeddable-Key header
525
+ * - 404: Moov account with specified ID not found
526
+ * - 500: Server error while retrieving underwriting history
527
+ *
528
+ * @param {string} moovAccountId - The Moov account ID
529
+ * @returns {Promise<{success: boolean, message?: string, data: Array|null, errors: string[], timestamp?: string, traceId?: string}>}
530
+ *
531
+ * @example
532
+ * const api = new BisonJibPayAPI(baseURL, embeddableKey);
533
+ * const history = await api.fetchUnderwritingByAccountId('moov-account-id');
534
+ * console.log(history.data); // Array of underwriting history records
535
+ */
536
+ async fetchUnderwritingByAccountId(moovAccountId) {
537
+ if (!moovAccountId) {
538
+ throw {
539
+ status: 400,
540
+ data: {
541
+ success: false,
542
+ message: "Moov account ID is required",
543
+ errors: ["moovAccountId parameter is missing"],
544
+ },
545
+ };
546
+ }
547
+
548
+ return this.request(
549
+ `/api/embeddable/underwriting-history/${moovAccountId}`,
550
+ {
551
+ method: "GET",
552
+ }
553
+ );
554
+ }
555
+
556
+ /**
557
+ * Find operator from Enverus
558
+ *
559
+ * @param {string|number} [opOrgId] - Optional Operator Org ID
560
+ * @param {string} [orgNumber] - Optional Org Number
561
+ * @returns {Promise<any>}
562
+ */
563
+ async findOperatorFromEnverus(opOrgId = null, orgNumber = null) {
564
+ const params = new URLSearchParams();
565
+ if (opOrgId) params.append("opOrgId", opOrgId);
566
+ if (orgNumber) params.append("orgNumber", orgNumber);
567
+
568
+ const queryString = params.toString() ? `?${params.toString()}` : "";
569
+
570
+ return this.request(`/api/enverus/operators/lookup${queryString}`, {
571
+ method: "GET",
572
+ });
573
+ }
574
+
575
+ /**
576
+ * List all bank accounts for an operator
577
+ *
578
+ * @param {string} operatorId - The internal GUID of the operator
579
+ * @returns {Promise<any>}
580
+ */
581
+ async getOperatorBankAccounts(operatorId) {
582
+ if (!operatorId) {
583
+ throw {
584
+ status: 400,
585
+ data: {
586
+ success: false,
587
+ message: "Operator ID is required",
588
+ errors: ["operatorId parameter is missing"],
589
+ },
590
+ };
591
+ }
592
+
593
+ return this.request(`/api/operators/${operatorId}/bank-accounts`, {
594
+ method: "GET",
595
+ });
596
+ }
597
+
598
+ /**
599
+ * Add a new bank account for an operator
600
+ *
601
+ * @param {string} operatorId - The internal GUID of the operator
602
+ * @param {Object} bankAccountData - The bank account details to add
603
+ * @returns {Promise<any>}
604
+ */
605
+ async addOperatorBankAccount(operatorId, bankAccountData) {
606
+ if (!operatorId) {
607
+ throw {
608
+ status: 400,
609
+ data: {
610
+ success: false,
611
+ message: "Operator ID is required",
612
+ errors: ["operatorId parameter is missing"],
613
+ },
614
+ };
615
+ }
616
+
617
+ return this.request(`/api/operators/${operatorId}/bank-accounts`, {
618
+ method: "POST",
619
+ body: JSON.stringify(bankAccountData),
620
+ });
621
+ }
622
+
623
+ /**
624
+ * Delete/unlink a bank account for an operator
625
+ *
626
+ * @param {string} operatorId - The internal GUID of the operator
627
+ * @param {string} bankAccountId - The ID of the bank account to delete
628
+ * @returns {Promise<any>}
629
+ */
630
+ async deleteOperatorBankAccount(operatorId, bankAccountId) {
631
+ if (!operatorId || !bankAccountId) {
632
+ throw {
633
+ status: 400,
634
+ data: {
635
+ success: false,
636
+ message: "Operator ID and Bank Account ID are required",
637
+ errors: ["operatorId or bankAccountId parameter is missing"],
638
+ },
639
+ };
640
+ }
641
+
642
+ return this.request(`/api/operators/${operatorId}/bank-accounts/${bankAccountId}`, {
643
+ method: "DELETE",
644
+ });
645
+ }
646
+ }
647
+
648
+ // Export for ES6 modules (primary export method for modern bundlers)
649
+ export { BisonJibPayAPI };
650
+
651
+ // Make available globally for script tag usage
652
+ if (typeof window !== "undefined") {
653
+ window.BisonJibPayAPI = BisonJibPayAPI;
654
+ }
655
+
656
+ // Export for CommonJS (Node.js)
657
+ if (typeof module !== "undefined" && module.exports) {
658
+ module.exports = { BisonJibPayAPI };
659
+ }