bison-web-components 1.2.0 → 1.5.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.
@@ -90,6 +90,639 @@ const BOP_ICONS = {
90
90
  plus: '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg>',
91
91
  };
92
92
 
93
+ class BisonJibPayAPI {
94
+ constructor(baseURL, embeddableKey) {
95
+ if (!embeddableKey || typeof embeddableKey !== 'string' || !embeddableKey.trim()) {
96
+ throw new Error("Missing required 'x-embeddable-key' for BisonJibPayAPI");
97
+ }
98
+ this.baseURL = baseURL || "https://bison-jib-development.azurewebsites.net";
99
+ this.embeddableKey = embeddableKey;
100
+ }
101
+
102
+ /**
103
+ * Make authenticated API request
104
+ * @private
105
+ */
106
+ async request(endpoint, options = {}) {
107
+ if (!this.embeddableKey || !this.embeddableKey.trim()) {
108
+ throw new Error("Missing required 'x-embeddable-key' for BisonJibPayAPI request");
109
+ }
110
+
111
+ const url = `${this.baseURL}${endpoint}`;
112
+ const headers = {
113
+ "X-Embeddable-Key": this.embeddableKey,
114
+ ...options.headers,
115
+ };
116
+
117
+ // Don't add Content-Type for FormData
118
+ if (!(options.body instanceof FormData)) {
119
+ headers["Content-Type"] = "application/json";
120
+ }
121
+
122
+ try {
123
+ const response = await fetch(url, {
124
+ ...options,
125
+ headers,
126
+ });
127
+
128
+ const data = await response.json();
129
+
130
+ if (!response.ok) {
131
+ throw {
132
+ status: response.status,
133
+ data: data,
134
+ };
135
+ }
136
+
137
+ return data;
138
+ } catch (error) {
139
+ // Re-throw with structured error
140
+ if (error.status) throw error;
141
+ throw {
142
+ status: 500,
143
+ data: {
144
+ success: false,
145
+ message: "Network error occurred",
146
+ errors: [error.message],
147
+ },
148
+ };
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Validate operator email
154
+ *
155
+ * @param {string} email - Operator's email address
156
+ * @param {string} operatorId - Operator's ID
157
+ * @param {string|null} clientId - Optional client ID
158
+ */
159
+ async validateOperatorEmail(email, operatorId, clientId = null) {
160
+ const payload = { email, operatorId };
161
+ if (clientId) payload.clientId = clientId;
162
+ return this.request("/api/embeddable/validate/operator-email", {
163
+ method: "POST",
164
+ body: JSON.stringify(payload),
165
+ });
166
+ }
167
+
168
+ /**
169
+ * Validate user email
170
+ *
171
+ * Checks if a user email exists in the system.
172
+ *
173
+ * @param {string} email - User's email address
174
+ * @returns {Promise<{success: boolean, message: string, data: {exists: boolean, message: string}, errors: string[], timestamp: string, traceId: string}>}
175
+ *
176
+ * @example
177
+ * const api = new BisonJibPayAPI(baseURL, embeddableKey);
178
+ * const result = await api.validateUserEmail('user@example.com');
179
+ * if (result.data.exists) {
180
+ * console.log('User email exists');
181
+ * }
182
+ */
183
+ async validateUserEmail(email) {
184
+ return this.request("/api/embeddable/validate/user-email", {
185
+ method: "POST",
186
+ body: JSON.stringify({ email }),
187
+ });
188
+ }
189
+
190
+ /**
191
+ * Verify operator email
192
+ * Checks if an operator is registered/onboarded in the system
193
+ *
194
+ * @param {string} email - Operator's email address
195
+ * @param {string} operatorId - Operator's ID
196
+ * @param {string|null} clientId - Optional client ID
197
+ * @returns {Promise<{success: boolean, message: string, data?: any}>}
198
+ *
199
+ * @example
200
+ * const api = new BisonJibPayAPI(baseURL, embeddableKey);
201
+ * const result = await api.verifyOperator('operator@example.com', 'OP123456');
202
+ * if (result.success) {
203
+ * console.log('Operator is verified');
204
+ * }
205
+ */
206
+ async verifyOperator(email, operatorId, clientId = null) {
207
+ return this.validateOperatorEmail(email, operatorId, clientId);
208
+ }
209
+
210
+ /**
211
+ * Verify WIO email
212
+ * Checks if a WIO (Worker Independent Operator) has an account in the system
213
+ *
214
+ * @param {string} email - WIO's email address
215
+ * @returns {Promise<{success: boolean, message: string, data?: {moovAccountId: string}}>}
216
+ *
217
+ * @example
218
+ * const api = new BisonJibPayAPI(baseURL, embeddableKey);
219
+ * const result = await api.verifyWio('wio@example.com');
220
+ * if (result.success && result.data?.moovAccountId) {
221
+ * console.log('WIO is verified with account:', result.data.moovAccountId);
222
+ * }
223
+ */
224
+ async verifyWio(email) {
225
+ return this.getAccountByEmail(email);
226
+ }
227
+
228
+ /**
229
+ * Register operator
230
+ */
231
+ async registerOperator(formData) {
232
+ return this.request("/api/embeddable/operator-registration", {
233
+ method: "POST",
234
+ body: formData, // FormData object
235
+ });
236
+ }
237
+
238
+ /**
239
+ * Register WIO
240
+ */
241
+ async registerWIO(payload) {
242
+ return this.request("/api/embeddable/wio-registration", {
243
+ method: "POST",
244
+ body: payload, // FormData object
245
+ });
246
+ }
247
+
248
+ async getAccountByEmail(operatorEmail) {
249
+ const param = new URLSearchParams();
250
+ param.append("email", operatorEmail);
251
+
252
+ return this.request(`/api/embeddable/moov-account-id?${param.toString()}`, {
253
+ method: "GET",
254
+ });
255
+ }
256
+
257
+ async getAccountByOperatorId(operatorId) {
258
+ const param = new URLSearchParams();
259
+ param.append("operatorId", operatorId);
260
+
261
+ return this.request(`/api/embeddable/moov-account-id?${param.toString()}`, {
262
+ method: "GET",
263
+ });
264
+ }
265
+
266
+ async getAccountByClientId(clientId) {
267
+ const param = new URLSearchParams();
268
+ param.append("clientId", clientId);
269
+
270
+ return this.request(`/api/embeddable/moov-account-id?${param.toString()}`, {
271
+ method: "GET",
272
+ });
273
+ }
274
+
275
+ /**
276
+ * Generate Moov access token for operator
277
+ *
278
+ * This method calls the backend API to generate a Moov token for payment operations.
279
+ * The backend handles the secure communication with Moov's API.
280
+ *
281
+ * @param {string} operatorEmail - Operator's email address
282
+ * @param {string|null} moovAccountId - Optional Moov account ID
283
+ * @param {string|null} operatorId - Optional operator ID
284
+ * @param {string|null} clientId - Optional client ID
285
+ * @returns {Promise<{access_token: string, expires_in?: number, scope?: string}>}
286
+ *
287
+ * @example
288
+ * const api = new BisonJibPayAPI(baseURL, embeddableKey);
289
+ * const tokenData = await api.generateMoovToken('operator@example.com');
290
+ * console.log(tokenData.access_token);
291
+ */
292
+ async generateMoovToken(operatorEmail, moovAccountId = null, operatorId = null, clientId = null) {
293
+ console.log("CALLED GENERATE MOOV TOKEN");
294
+
295
+ // Use provided moovAccountId or fetch it if not provided
296
+ let accountId = moovAccountId;
297
+ if (!accountId) {
298
+ if (operatorEmail) {
299
+ const account = await this.getAccountByEmail(operatorEmail);
300
+ accountId = account.data.moovAccountId;
301
+ } else if (operatorId) {
302
+ const account = await this.getAccountByOperatorId(operatorId);
303
+ accountId = account.data.moovAccountId;
304
+ } else if (clientId) {
305
+ const account = await this.getAccountByClientId(clientId);
306
+ accountId = account.data.moovAccountId;
307
+ } else {
308
+ throw {
309
+ status: 400,
310
+ data: {
311
+ success: false,
312
+ message: "Email, operator ID, or client ID is required to generate a token",
313
+ errors: ["Missing operator identifier"],
314
+ },
315
+ };
316
+ }
317
+ }
318
+ console.log("MOOV ACCOUNT ID", accountId);
319
+ let accountScopes = [
320
+ "/accounts/{ACCOUNT_ID}/bank-accounts.read",
321
+ "/accounts/{ACCOUNT_ID}/bank-accounts.write",
322
+ "/accounts/{ACCOUNT_ID}/capabilities.read",
323
+ "/accounts/{ACCOUNT_ID}/capabilities.write",
324
+ "/accounts/{ACCOUNT_ID}/cards.read",
325
+ "/accounts/{ACCOUNT_ID}/cards.write",
326
+ "/accounts/{ACCOUNT_ID}/profile.read",
327
+ "/accounts/{ACCOUNT_ID}/profile.write",
328
+ "/accounts/{ACCOUNT_ID}/representatives.read",
329
+ "/accounts/{ACCOUNT_ID}/representatives.write",
330
+ ];
331
+
332
+ if (accountId) {
333
+ accountScopes = accountScopes.map((value) =>
334
+ value.replace("{ACCOUNT_ID}", accountId)
335
+ );
336
+ }
337
+
338
+ const tokenPayload = {
339
+ scopes: [
340
+ "/accounts.read",
341
+ "/accounts.write",
342
+ "/fed.read",
343
+ "/profile-enrichment.read",
344
+ ...accountScopes,
345
+ ],
346
+ };
347
+
348
+ if (operatorEmail) {
349
+ tokenPayload.email = operatorEmail;
350
+ }
351
+
352
+ if (operatorId) {
353
+ tokenPayload.operatorId = operatorId;
354
+ }
355
+
356
+ if (clientId) {
357
+ tokenPayload.clientId = clientId;
358
+ }
359
+
360
+ return this.request("/api/embeddable/moov-access-token", {
361
+ method: "POST",
362
+ body: JSON.stringify(tokenPayload),
363
+ });
364
+ }
365
+
366
+ /**
367
+ * Generate Plaid Link token for WIO
368
+ *
369
+ * This method calls the backend API to generate a Plaid Link token for bank account linking.
370
+ * The token is used to initialize Plaid Link in the Moov payment drop.
371
+ *
372
+ * @param {string} wioEmail - WIO's email address
373
+ * @returns {Promise<{link_token: string, expiration?: string}>}
374
+ *
375
+ * @example
376
+ * const api = new BisonJibPayAPI(baseURL, embeddableKey);
377
+ * const tokenData = await api.generatePlaidToken('wio@example.com');
378
+ * console.log(tokenData.link_token);
379
+ */
380
+ async generatePlaidToken(wioEmail) {
381
+ return this.request("/api/embeddable/plaid/link-token", {
382
+ method: "POST",
383
+ body: JSON.stringify({
384
+ clientName: wioEmail,
385
+ countryCodes: ["US"],
386
+ user: {
387
+ clientUserId: "wio-email",
388
+ legalName: "Wio User",
389
+ },
390
+ products: ["transactions"],
391
+ client_name: "Personal Finance App",
392
+ }),
393
+ });
394
+ }
395
+
396
+ /**
397
+ * Create Plaid processor token
398
+ *
399
+ * Exchanges a Plaid public token for a processor token that can be used with Moov.
400
+ * This is called during the Plaid Link flow after the user selects their bank account.
401
+ *
402
+ * @param {string} publicToken - Plaid public token from Link flow
403
+ * @param {string} bankAccountId - Selected bank account ID
404
+ * @returns {Promise<{processor_token: string, bank_account_id: string}>}
405
+ *
406
+ * @example
407
+ * const api = new BisonJibPayAPI(baseURL, embeddableKey);
408
+ * const result = await api.createProcessorToken(publicToken, accountId);
409
+ * console.log(result.processor_token);
410
+ */
411
+ async createProcessorToken(publicToken, bankAccountId) {
412
+ return this.request("/api/embeddable/plaid/processor-token", {
413
+ method: "POST",
414
+ body: JSON.stringify({
415
+ publicToken,
416
+ accountId: bankAccountId,
417
+ }),
418
+ });
419
+ }
420
+
421
+ async addPlaidAccountToMoov(publicToken, bankAccountId, moovAccountId) {
422
+ return this.request("/api/embeddable/plaid/add-to-moov", {
423
+ method: "POST",
424
+ body: JSON.stringify({
425
+ publicToken,
426
+ moovAccountId,
427
+ accountId: bankAccountId,
428
+ }),
429
+ });
430
+ }
431
+
432
+ /**
433
+ * Get payment methods by moovAccountId directly
434
+ *
435
+ * This method fetches all available payment methods for the given moovAccountId.
436
+ * Use this when you already have the moovAccountId cached to avoid extra API calls.
437
+ *
438
+ * @param {string} moovAccountId - The Moov account ID
439
+ * @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}>}
440
+ *
441
+ * @example
442
+ * const api = new BisonJibPayAPI(baseURL, embeddableKey);
443
+ * const paymentMethods = await api.getPaymentMethodsByAccountId('moov-account-id');
444
+ * console.log(paymentMethods.data); // Array of payment methods
445
+ */
446
+ async getPaymentMethodsByAccountId(moovAccountId) {
447
+ if (!moovAccountId) {
448
+ throw {
449
+ status: 400,
450
+ data: {
451
+ success: false,
452
+ message: "Moov account ID is required",
453
+ errors: ["moovAccountId parameter is missing"],
454
+ },
455
+ };
456
+ }
457
+
458
+ return this.request(`/api/embeddable/payment-methods/${moovAccountId}`, {
459
+ method: "GET",
460
+ });
461
+ }
462
+
463
+ /**
464
+ * Get payment methods for an operator by email
465
+ *
466
+ * This method first retrieves the operator's moovAccountId by email,
467
+ * then fetches all available payment methods for that account.
468
+ * Note: If you already have the moovAccountId, use getPaymentMethodsByAccountId() instead
469
+ * to avoid the extra API call.
470
+ *
471
+ * @param {string} operatorEmail - Operator's email address
472
+ * @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}>}
473
+ *
474
+ * @example
475
+ * const api = new BisonJibPayAPI(baseURL, embeddableKey);
476
+ * const paymentMethods = await api.getPaymentMethods('operator@example.com');
477
+ * console.log(paymentMethods.data); // Array of payment methods
478
+ */
479
+ async getPaymentMethods(operatorEmail) {
480
+ // First, get the account by email to retrieve moovAccountId
481
+ const account = await this.getAccountByEmail(operatorEmail);
482
+ const moovAccountId = account.data?.moovAccountId || account.moovAccountId;
483
+
484
+ if (!moovAccountId) {
485
+ throw {
486
+ status: 404,
487
+ data: {
488
+ success: false,
489
+ message: "Moov account ID not found for the given email",
490
+ errors: ["No moovAccountId associated with this operator"],
491
+ },
492
+ };
493
+ }
494
+
495
+ // Use the direct method to fetch payment methods
496
+ return this.getPaymentMethodsByAccountId(moovAccountId);
497
+ }
498
+
499
+ /**
500
+ * Delete a payment method by moovAccountId and paymentMethodId directly
501
+ *
502
+ * Use this when you already have the moovAccountId cached to avoid extra API calls.
503
+ *
504
+ * @param {string} moovAccountId - The Moov account ID
505
+ * @param {string} paymentMethodId - The ID of the payment method to delete
506
+ * @returns {Promise<{success: boolean, message: string, data: string, errors: string[], timestamp: string, traceId: string}>}
507
+ *
508
+ * @example
509
+ * const api = new BisonJibPayAPI(baseURL, embeddableKey);
510
+ * const result = await api.deletePaymentMethodByAccountId('moov-account-id', 'pm_123456');
511
+ * console.log(result.success); // true if deleted successfully
512
+ */
513
+ async deletePaymentMethodByAccountId(moovAccountId, paymentMethodId) {
514
+ if (!moovAccountId) {
515
+ throw {
516
+ status: 400,
517
+ data: {
518
+ success: false,
519
+ message: "Moov account ID is required",
520
+ errors: ["moovAccountId parameter is missing"],
521
+ },
522
+ };
523
+ }
524
+
525
+ if (!paymentMethodId) {
526
+ throw {
527
+ status: 400,
528
+ data: {
529
+ success: false,
530
+ message: "Payment method ID is required",
531
+ errors: ["paymentMethodId parameter is missing"],
532
+ },
533
+ };
534
+ }
535
+
536
+ return this.request(
537
+ `/api/embeddable/bank-account/${moovAccountId}/${paymentMethodId}`,
538
+ {
539
+ method: "DELETE",
540
+ }
541
+ );
542
+ }
543
+
544
+ /**
545
+ * Delete a payment method by ID
546
+ *
547
+ * This method first retrieves the operator's moovAccountId by email,
548
+ * then deletes the specified payment method.
549
+ * Note: If you already have the moovAccountId, use deletePaymentMethodByAccountId() instead
550
+ * to avoid the extra API call.
551
+ *
552
+ * @param {string} operatorEmail - Operator's email address
553
+ * @param {string} paymentMethodId - The ID of the payment method to delete
554
+ * @returns {Promise<{success: boolean, message: string, data: string, errors: string[], timestamp: string, traceId: string}>}
555
+ *
556
+ * @example
557
+ * const api = new BisonJibPayAPI(baseURL, embeddableKey);
558
+ * const result = await api.deletePaymentMethodById('operator@example.com', 'pm_123456');
559
+ * console.log(result.success); // true if deleted successfully
560
+ */
561
+ async deletePaymentMethodById(operatorEmail, paymentMethodId) {
562
+ // First, get the account by email to retrieve moovAccountId
563
+ const account = await this.getAccountByEmail(operatorEmail);
564
+ const moovAccountId = account.data?.moovAccountId || account.moovAccountId;
565
+
566
+ if (!moovAccountId) {
567
+ throw {
568
+ status: 404,
569
+ data: {
570
+ success: false,
571
+ message: "Moov account ID not found for the given email",
572
+ errors: ["No moovAccountId associated with this operator"],
573
+ },
574
+ };
575
+ }
576
+
577
+ // Use the direct method to delete payment method
578
+ return this.deletePaymentMethodByAccountId(moovAccountId, paymentMethodId);
579
+ }
580
+ /**
581
+ * Fetch underwriting history by moovAccountId
582
+ *
583
+ * This method retrieves the underwriting history for the given moovAccountId.
584
+ * Use this when you already have the moovAccountId cached.
585
+ *
586
+ * Response Codes:
587
+ * - 200: Success with data array (may be empty)
588
+ * - 400: Missing or invalid moovAccountId parameter
589
+ * - 401: Invalid or missing X-Embeddable-Key header
590
+ * - 404: Moov account with specified ID not found
591
+ * - 500: Server error while retrieving underwriting history
592
+ *
593
+ * @param {string} moovAccountId - The Moov account ID
594
+ * @returns {Promise<{success: boolean, message?: string, data: Array|null, errors: string[], timestamp?: string, traceId?: string}>}
595
+ *
596
+ * @example
597
+ * const api = new BisonJibPayAPI(baseURL, embeddableKey);
598
+ * const history = await api.fetchUnderwritingByAccountId('moov-account-id');
599
+ * console.log(history.data); // Array of underwriting history records
600
+ */
601
+ async fetchUnderwritingByAccountId(moovAccountId) {
602
+ if (!moovAccountId) {
603
+ throw {
604
+ status: 400,
605
+ data: {
606
+ success: false,
607
+ message: "Moov account ID is required",
608
+ errors: ["moovAccountId parameter is missing"],
609
+ },
610
+ };
611
+ }
612
+
613
+ return this.request(
614
+ `/api/embeddable/underwriting-history/${moovAccountId}`,
615
+ {
616
+ method: "GET",
617
+ }
618
+ );
619
+ }
620
+
621
+ /**
622
+ * Find operator from Enverus
623
+ *
624
+ * @param {string|number} [opOrgId] - Optional Operator Org ID
625
+ * @param {string} [orgNumber] - Optional Org Number
626
+ * @returns {Promise<any>}
627
+ */
628
+ async findOperatorFromEnverus(opOrgId = null, orgNumber = null) {
629
+ const params = new URLSearchParams();
630
+ if (opOrgId) params.append("opOrgId", opOrgId);
631
+ if (orgNumber) params.append("orgNumber", orgNumber);
632
+
633
+ const queryString = params.toString() ? `?${params.toString()}` : "";
634
+
635
+ return this.request(`/api/enverus/operators/lookup${queryString}`, {
636
+ method: "GET",
637
+ });
638
+ }
639
+
640
+ /**
641
+ * List all bank accounts for an operator
642
+ *
643
+ * @param {string} operatorId - The internal GUID of the operator
644
+ * @returns {Promise<any>}
645
+ */
646
+ async getOperatorBankAccounts(operatorId) {
647
+ if (!operatorId) {
648
+ throw {
649
+ status: 400,
650
+ data: {
651
+ success: false,
652
+ message: "Operator ID is required",
653
+ errors: ["operatorId parameter is missing"],
654
+ },
655
+ };
656
+ }
657
+
658
+ return this.request(`/api/operators/${operatorId}/bank-accounts`, {
659
+ method: "GET",
660
+ });
661
+ }
662
+
663
+ /**
664
+ * Add a new bank account for an operator
665
+ *
666
+ * @param {string} operatorId - The internal GUID of the operator
667
+ * @param {Object} bankAccountData - The bank account details to add
668
+ * @returns {Promise<any>}
669
+ */
670
+ async addOperatorBankAccount(operatorId, bankAccountData) {
671
+ if (!operatorId) {
672
+ throw {
673
+ status: 400,
674
+ data: {
675
+ success: false,
676
+ message: "Operator ID is required",
677
+ errors: ["operatorId parameter is missing"],
678
+ },
679
+ };
680
+ }
681
+
682
+ return this.request(`/api/operators/${operatorId}/bank-accounts`, {
683
+ method: "POST",
684
+ body: JSON.stringify(bankAccountData),
685
+ });
686
+ }
687
+
688
+ /**
689
+ * Delete/unlink a bank account for an operator
690
+ *
691
+ * @param {string} operatorId - The internal GUID of the operator
692
+ * @param {string} bankAccountId - The ID of the bank account to delete
693
+ * @returns {Promise<any>}
694
+ */
695
+ async deleteOperatorBankAccount(operatorId, bankAccountId) {
696
+ if (!operatorId || !bankAccountId) {
697
+ throw {
698
+ status: 400,
699
+ data: {
700
+ success: false,
701
+ message: "Operator ID and Bank Account ID are required",
702
+ errors: ["operatorId or bankAccountId parameter is missing"],
703
+ },
704
+ };
705
+ }
706
+
707
+ return this.request(`/api/operators/${operatorId}/bank-accounts/${bankAccountId}`, {
708
+ method: "DELETE",
709
+ });
710
+ }
711
+ }
712
+
713
+ // Export for ES6 modules (primary export method for modern bundlers)
714
+ export { BisonJibPayAPI };
715
+
716
+ // Make available globally for script tag usage
717
+ if (typeof window !== "undefined") {
718
+ window.BisonJibPayAPI = BisonJibPayAPI;
719
+ }
720
+
721
+ // Export for CommonJS (Node.js)
722
+ if (typeof module !== "undefined" && module.exports) {
723
+ module.exports = { BisonJibPayAPI };
724
+ }
725
+
93
726
  class BisonOperatorPayments extends HTMLElement {
94
727
  constructor() {
95
728
  super();
@@ -153,7 +786,7 @@ class BisonOperatorPayments extends HTMLElement {
153
786
  }
154
787
 
155
788
  static get observedAttributes() {
156
- return ["org-number", "op-org-id", "x-embeddable-key"];
789
+ return ["org-number", "op-org-id", "x-embeddable-key", "api-base-url"];
157
790
  }
158
791
 
159
792
  connectedCallback() {
@@ -202,13 +835,41 @@ class BisonOperatorPayments extends HTMLElement {
202
835
  }
203
836
 
204
837
  /** Resolves the API instance and syncs the current embeddable key. */
205
- _getApi() {
206
- const api =
207
- this._api || (typeof window !== "undefined" && window.__bisonApi);
208
- if (api) {
209
- api.embeddableKey = this._embeddableKey;
838
+ async _getApi() {
839
+ if (this._api) {
840
+ if (this._embeddableKey) this._api.embeddableKey = this._embeddableKey;
841
+ return this._api;
842
+ }
843
+ if (typeof window !== "undefined" && window.__bisonApi) {
844
+ if (this._embeddableKey) window.__bisonApi.embeddableKey = this._embeddableKey;
845
+ return window.__bisonApi;
846
+ }
847
+
848
+ // Try reading config from window object explicitly provided by consumer
849
+ let baseUrl = this.getAttribute("api-base-url") || "";
850
+ let globalKey = this._embeddableKey;
851
+ if (typeof window !== "undefined" && window.BISON_JIB_PAY_CONFIG) {
852
+ baseUrl = baseUrl || window.BISON_JIB_PAY_CONFIG.apiBaseURL || "";
853
+ if (!this._embeddableKey && window.BISON_JIB_PAY_CONFIG.embeddableKey) {
854
+ globalKey = window.BISON_JIB_PAY_CONFIG.embeddableKey;
855
+ this._embeddableKey = globalKey; // Sync to component state
856
+ }
857
+ }
858
+
859
+ try {
860
+ this._api = new BisonJibPayAPI(baseUrl, globalKey);
861
+
862
+ // Attach to window to prevent redundant instantiations across components
863
+ if (typeof window !== "undefined") {
864
+ window.BisonJibPayAPI = BisonJibPayAPI;
865
+ window.__bisonApi = this._api;
866
+ }
867
+
868
+ return this._api;
869
+ } catch (err) {
870
+ this._log("Failed to initialize BisonJibPayAPI:", err);
871
+ return null;
210
872
  }
211
- return api;
212
873
  }
213
874
 
214
875
  // ==================== OPERATOR LOOKUP (Enverus) ====================
@@ -219,6 +880,12 @@ class BisonOperatorPayments extends HTMLElement {
219
880
  * Priority: opOrgId > orgNumber
220
881
  */
221
882
  _evaluateOperatorAttributes() {
883
+ // Try to resolve key from global config if not already set via attribute
884
+ if (!this._embeddableKey && typeof window !== "undefined" && window.BISON_JIB_PAY_CONFIG?.embeddableKey) {
885
+ this._embeddableKey = window.BISON_JIB_PAY_CONFIG.embeddableKey;
886
+ this._disabledReason = null;
887
+ }
888
+
222
889
  // Gate: embeddable key must be present before any API calls
223
890
  if (!this._embeddableKey) {
224
891
  this._log("Embeddable key missing → skipping operator evaluation");
@@ -260,7 +927,7 @@ class BisonOperatorPayments extends HTMLElement {
260
927
  async _performOperatorLookup(opOrgId, orgNumber) {
261
928
  // Resolve API instance — consumers can set this._api externally,
262
929
  // or the component falls back to a global instance on window.
263
- const api = this._getApi();
930
+ const api = await this._getApi();
264
931
  if (!api || typeof api.findOperatorFromEnverus !== "function") {
265
932
  const msg =
266
933
  "No API instance available — set component._api or window.__bisonApi";
@@ -280,8 +947,12 @@ class BisonOperatorPayments extends HTMLElement {
280
947
  try {
281
948
  const result = await api.findOperatorFromEnverus(opOrgId, orgNumber);
282
949
  this._log("Operator lookup SUCCESS", result);
283
- this._operatorData = result;
284
- this._operatorId = result?.data?.operatorId || result?.operatorId || null;
950
+
951
+ // Handle both wrapped and unwrapped response
952
+ const data = result?.data || result;
953
+ this._operatorData = data;
954
+ this._operatorId = data?.operatorId || null;
955
+
285
956
  this._operatorLookupError = null;
286
957
  this._componentDisabled = false;
287
958
  this._dispatchLookupEvent({ status: "success", data: result });
@@ -334,6 +1005,8 @@ class BisonOperatorPayments extends HTMLElement {
334
1005
  const loaderSvg =
335
1006
  '<span class="bop-trigger-spinner"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg></span>';
336
1007
  const logoImg = `<img src="${BOP_BISON_LOGO}" alt="Bison" class="bop-trigger-logo">`;
1008
+
1009
+ // Check for company name in operatorData
337
1010
  const companyName = this._operatorData?.companyName || "";
338
1011
  if (this._isOperatorLookupPending) {
339
1012
  btn.innerHTML = `${loaderSvg} Initializing...`;
@@ -364,8 +1037,8 @@ class BisonOperatorPayments extends HTMLElement {
364
1037
  this._log(`Fetching bank accounts for operatorId: ${this._operatorId}`);
365
1038
  const response = await api.getOperatorBankAccounts(this._operatorId);
366
1039
 
367
- // The payload structure is expected to be inside response.data
368
- const accountsData = response?.data || [];
1040
+ // Higher robustness: handle both direct array or wrapped in response.data
1041
+ const accountsData = Array.isArray(response) ? response : (response?.data || []);
369
1042
 
370
1043
  // Map the DTO to the internal representation
371
1044
  this._accounts = accountsData.map((acc) => ({
@@ -652,7 +1325,7 @@ class BisonOperatorPayments extends HTMLElement {
652
1325
  this._isLoading = true;
653
1326
  this._renderContent();
654
1327
 
655
- const api = this._getApi();
1328
+ const api = await this._getApi();
656
1329
  if (!api || !this._operatorId) {
657
1330
  this._log("Error: Missing API instance or operator ID for unlinking.");
658
1331
  this._isLoading = false;
@@ -892,7 +1565,7 @@ class BisonOperatorPayments extends HTMLElement {
892
1565
  this._renderLinkModal();
893
1566
 
894
1567
  try {
895
- const api = this._getApi();
1568
+ const api = await this._getApi();
896
1569
  if (!api || !this._operatorId) {
897
1570
  throw new Error("Missing API instance or operator ID");
898
1571
  }
@@ -915,25 +1588,25 @@ class BisonOperatorPayments extends HTMLElement {
915
1588
  this._linkModalSubmitting = false;
916
1589
  this._removeLinkModalGuards();
917
1590
 
918
- // Store the new account as pending based on the response if available, or locally generated
919
- const newAcc = response?.data || {};
920
- const lastFour = newAcc.accountNumber
921
- ? newAcc.accountNumber.slice(-4)
1591
+ // Store the new account as pending - handle both direct object or wrapped in response.data
1592
+ const data = response?.data || response || {};
1593
+ const lastFour = data.accountNumber
1594
+ ? data.accountNumber.slice(-4)
922
1595
  : this._linkModalValues.accountNumber.trim().slice(-4);
923
1596
 
924
1597
  this._pendingLinkedAccount = {
925
- id: newAcc.id || `linked-${Date.now()}`,
1598
+ id: data.id || `linked-${Date.now()}`,
926
1599
  type:
927
- newAcc.accountType === 0
1600
+ data.accountType === 0
928
1601
  ? "Checking"
929
- : newAcc.accountType === 1
1602
+ : data.accountType === 1
930
1603
  ? "Savings"
931
- : payload.accountName || "Linked Account",
932
- bankName: newAcc.bankName || payload.bankName,
1604
+ : data.accountName || payload.accountName || "Linked Account",
1605
+ bankName: data.bankName || payload.bankName,
933
1606
  lastFour: lastFour.padStart(4, "0"),
934
1607
  balance: 0,
935
- isVerified: newAcc.isVerified || false,
936
- isDefault: newAcc.isDefault || false,
1608
+ isVerified: data.isVerified || false,
1609
+ isDefault: data.isDefault || false,
937
1610
  cannotUnlink: false,
938
1611
  };
939
1612
 
@@ -2127,3 +2800,6 @@ if (typeof module !== "undefined" && module.exports) {
2127
2800
  if (typeof window !== "undefined") {
2128
2801
  window.BisonOperatorPayments = BisonOperatorPayments;
2129
2802
  }
2803
+
2804
+ // Named export for ESM
2805
+ export { BisonOperatorPayments };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bison-web-components",
3
- "version": "1.2.0",
3
+ "version": "1.5.0",
4
4
  "description": "Bison JIB's suite of web components",
5
5
  "main": "component.js",
6
6
  "type": "module",