@funnelfox/billing 0.6.7 → 0.7.1-beta.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/dist/chunk-index.cjs.js +283 -43
- package/dist/chunk-index.cjs2.js +5 -4
- package/dist/chunk-index.cjs3.js +106 -21
- package/dist/chunk-index.es.js +283 -43
- package/dist/chunk-index.es2.js +5 -4
- package/dist/chunk-index.es3.js +106 -21
- package/dist/funnelfox-billing.js +394 -68
- package/dist/funnelfox-billing.min.js +1 -1
- package/package.json +2 -2
- package/src/types.d.ts +28 -2
package/dist/chunk-index.es.js
CHANGED
|
@@ -148,46 +148,64 @@ class NetworkError extends FunnefoxSDKError {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
/**
|
|
151
|
-
* @fileoverview
|
|
152
|
-
* Loads Primer script and CSS from CDN independently of bundler
|
|
151
|
+
* @fileoverview Generic script and stylesheet loader utility to reduce bundle size
|
|
153
152
|
*/
|
|
154
|
-
const PRIMER_CDN_BASE = 'https://sdk.primer.io/web';
|
|
155
|
-
const DEFAULT_VERSION = '2.57.3';
|
|
156
|
-
// Integrity hashes for specific versions (for SRI security)
|
|
157
|
-
const INTEGRITY_HASHES = {
|
|
158
|
-
'2.57.3': {
|
|
159
|
-
js: 'sha384-xq2SWkYvTlKOMpuXQUXq1QI3eZN7JiqQ3Sc72U9wY1IE30MW3HkwQWg/1n6BTMz4',
|
|
160
|
-
},
|
|
161
|
-
};
|
|
162
|
-
let loadingPromise = null;
|
|
163
|
-
let isLoaded = false;
|
|
164
153
|
/**
|
|
165
|
-
*
|
|
154
|
+
* Dynamically loads an external script into the document.
|
|
155
|
+
* Checks if script already exists before loading to prevent duplicates.
|
|
156
|
+
*
|
|
157
|
+
* @param options - Script configuration options
|
|
158
|
+
* @returns Promise that resolves when script is loaded or rejects on error
|
|
166
159
|
*/
|
|
167
|
-
function
|
|
160
|
+
function loadScript$1(options) {
|
|
161
|
+
const { id, src, async = true, type = 'text/javascript', attributes = {}, integrity, crossOrigin, appendTo = 'body', } = options;
|
|
168
162
|
return new Promise((resolve, reject) => {
|
|
169
|
-
// Check if script already exists
|
|
170
|
-
|
|
163
|
+
// Check if script already exists (by ID or src)
|
|
164
|
+
let existingScript = null;
|
|
165
|
+
if (id) {
|
|
166
|
+
existingScript = document.getElementById(id);
|
|
167
|
+
}
|
|
168
|
+
if (!existingScript) {
|
|
169
|
+
existingScript = document.querySelector(`script[src="${src}"]`);
|
|
170
|
+
}
|
|
171
171
|
if (existingScript) {
|
|
172
172
|
resolve(existingScript);
|
|
173
173
|
return;
|
|
174
174
|
}
|
|
175
175
|
const script = document.createElement('script');
|
|
176
|
+
if (id) {
|
|
177
|
+
script.id = id;
|
|
178
|
+
}
|
|
179
|
+
script.type = type;
|
|
176
180
|
script.src = src;
|
|
177
|
-
|
|
178
|
-
|
|
181
|
+
if (async) {
|
|
182
|
+
script.async = true;
|
|
183
|
+
}
|
|
179
184
|
if (integrity) {
|
|
180
185
|
script.integrity = integrity;
|
|
181
186
|
}
|
|
187
|
+
if (crossOrigin) {
|
|
188
|
+
script.crossOrigin = crossOrigin;
|
|
189
|
+
}
|
|
190
|
+
// Set additional attributes
|
|
191
|
+
Object.entries(attributes).forEach(([key, value]) => {
|
|
192
|
+
script.setAttribute(key, value);
|
|
193
|
+
});
|
|
182
194
|
script.onload = () => resolve(script);
|
|
183
|
-
script.onerror = () => reject(new Error(`Failed to load
|
|
184
|
-
document.head.
|
|
195
|
+
script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
|
|
196
|
+
const target = appendTo === 'head' ? document.head : document.body;
|
|
197
|
+
target.appendChild(script);
|
|
185
198
|
});
|
|
186
199
|
}
|
|
187
200
|
/**
|
|
188
|
-
*
|
|
201
|
+
* Dynamically loads an external stylesheet into the document head.
|
|
202
|
+
* Checks if stylesheet already exists before loading to prevent duplicates.
|
|
203
|
+
*
|
|
204
|
+
* @param options - Stylesheet configuration options
|
|
205
|
+
* @returns Promise that resolves when stylesheet is loaded or rejects on error
|
|
189
206
|
*/
|
|
190
|
-
function
|
|
207
|
+
function loadStylesheet(options) {
|
|
208
|
+
const { href, integrity, crossOrigin } = options;
|
|
191
209
|
return new Promise((resolve, reject) => {
|
|
192
210
|
// Check if stylesheet already exists
|
|
193
211
|
const existingLink = document.querySelector(`link[href="${href}"]`);
|
|
@@ -198,15 +216,32 @@ function injectCSS(href, integrity) {
|
|
|
198
216
|
const link = document.createElement('link');
|
|
199
217
|
link.rel = 'stylesheet';
|
|
200
218
|
link.href = href;
|
|
201
|
-
link.crossOrigin = 'anonymous';
|
|
202
219
|
if (integrity) {
|
|
203
220
|
link.integrity = integrity;
|
|
204
221
|
}
|
|
222
|
+
if (crossOrigin) {
|
|
223
|
+
link.crossOrigin = crossOrigin;
|
|
224
|
+
}
|
|
205
225
|
link.onload = () => resolve(link);
|
|
206
|
-
link.onerror = () => reject(new Error(`Failed to load
|
|
226
|
+
link.onerror = () => reject(new Error(`Failed to load stylesheet: ${href}`));
|
|
207
227
|
document.head.appendChild(link);
|
|
208
228
|
});
|
|
209
229
|
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* @fileoverview Dynamic loader for Primer SDK
|
|
233
|
+
* Loads Primer script and CSS from CDN independently of bundler
|
|
234
|
+
*/
|
|
235
|
+
const PRIMER_CDN_BASE = 'https://sdk.primer.io/web';
|
|
236
|
+
const DEFAULT_VERSION = '2.57.3';
|
|
237
|
+
// Integrity hashes for specific versions (for SRI security)
|
|
238
|
+
const INTEGRITY_HASHES = {
|
|
239
|
+
'2.57.3': {
|
|
240
|
+
js: 'sha384-xq2SWkYvTlKOMpuXQUXq1QI3eZN7JiqQ3Sc72U9wY1IE30MW3HkwQWg/1n6BTMz4',
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
let loadingPromise = null;
|
|
244
|
+
let isLoaded = false;
|
|
210
245
|
/**
|
|
211
246
|
* Waits for window.Primer to be available
|
|
212
247
|
*/
|
|
@@ -258,8 +293,17 @@ async function loadPrimerSDK(version) {
|
|
|
258
293
|
try {
|
|
259
294
|
// Load CSS and JS in parallel
|
|
260
295
|
await Promise.all([
|
|
261
|
-
|
|
262
|
-
|
|
296
|
+
loadStylesheet({
|
|
297
|
+
href: cssUrl,
|
|
298
|
+
integrity: hashes?.css,
|
|
299
|
+
crossOrigin: 'anonymous',
|
|
300
|
+
}),
|
|
301
|
+
loadScript$1({
|
|
302
|
+
src: jsUrl,
|
|
303
|
+
integrity: hashes?.js,
|
|
304
|
+
crossOrigin: 'anonymous',
|
|
305
|
+
appendTo: 'head',
|
|
306
|
+
}),
|
|
263
307
|
]);
|
|
264
308
|
// Wait for Primer to be available on window
|
|
265
309
|
await waitForPrimer();
|
|
@@ -302,6 +346,28 @@ function generateId(prefix = '') {
|
|
|
302
346
|
const random = Math.random().toString(36).substr(2, 5);
|
|
303
347
|
return `${prefix}${timestamp}_${random}`;
|
|
304
348
|
}
|
|
349
|
+
/**
|
|
350
|
+
* Generates a UUID v4 compliant string (RFC 4122).
|
|
351
|
+
* Meets Airwallex requirements:
|
|
352
|
+
* - Maximum 128 characters (UUID is 36 chars)
|
|
353
|
+
* - Only contains: a-z, A-Z, 0-9, underscore, hyphen
|
|
354
|
+
* - No prefix + timestamp pattern
|
|
355
|
+
* - Not a short series of numbers
|
|
356
|
+
*
|
|
357
|
+
* @returns UUID v4 string (e.g., "a3bb189e-8bf9-3888-9912-ace4e6543002")
|
|
358
|
+
*/
|
|
359
|
+
function generateUUID() {
|
|
360
|
+
// Use crypto.randomUUID if available (modern browsers)
|
|
361
|
+
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
362
|
+
return crypto.randomUUID();
|
|
363
|
+
}
|
|
364
|
+
// Fallback: manual UUID v4 generation
|
|
365
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
|
366
|
+
const r = (Math.random() * 16) | 0;
|
|
367
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
368
|
+
return v.toString(16);
|
|
369
|
+
});
|
|
370
|
+
}
|
|
305
371
|
function sleep(ms) {
|
|
306
372
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
307
373
|
}
|
|
@@ -420,7 +486,7 @@ var PaymentMethod;
|
|
|
420
486
|
/**
|
|
421
487
|
* @fileoverview Constants for Funnefox SDK
|
|
422
488
|
*/
|
|
423
|
-
const SDK_VERSION = '0.
|
|
489
|
+
const SDK_VERSION = '0.7.1-beta.0';
|
|
424
490
|
const DEFAULTS = {
|
|
425
491
|
BASE_URL: 'https://billing.funnelfox.com',
|
|
426
492
|
REGION: 'default',
|
|
@@ -671,6 +737,8 @@ class PrimerWrapper {
|
|
|
671
737
|
onMethodRenderError: options.onMethodRenderError,
|
|
672
738
|
onMethodRender: options.onMethodRender,
|
|
673
739
|
onCardInputValueChange: options.onCardInputValueChange,
|
|
740
|
+
isCardholderNameRequired: options.isCardholderNameRequired,
|
|
741
|
+
isPostalCodeRequired: options.isPostalCodeRequired,
|
|
674
742
|
});
|
|
675
743
|
this.paymentMethodsInterfaces.push(cardInterface);
|
|
676
744
|
return cardInterface;
|
|
@@ -689,7 +757,7 @@ class PrimerWrapper {
|
|
|
689
757
|
throw new PrimerError('Failed to initialize Primer checkout', error);
|
|
690
758
|
}
|
|
691
759
|
}
|
|
692
|
-
async renderCardCheckoutWithElements(elements, { onSubmit, onInputChange, onCardInputValueChange, onMethodRenderError, onMethodRender, }) {
|
|
760
|
+
async renderCardCheckoutWithElements(elements, { onSubmit, onInputChange, onCardInputValueChange, isCardholderNameRequired, isPostalCodeRequired, onMethodRenderError, onMethodRender, }) {
|
|
693
761
|
try {
|
|
694
762
|
if (!this.currentHeadless) {
|
|
695
763
|
throw new PrimerError('Headless checkout not found');
|
|
@@ -705,8 +773,10 @@ class PrimerWrapper {
|
|
|
705
773
|
if (!pmManager)
|
|
706
774
|
return false;
|
|
707
775
|
const { valid, validationErrors } = await pmManager.validate();
|
|
708
|
-
const cardHolderError =
|
|
709
|
-
|
|
776
|
+
const cardHolderError = isCardholderNameRequired?.()
|
|
777
|
+
? validationErrors.find(v => v.name === 'cardholderName')?.message
|
|
778
|
+
: null;
|
|
779
|
+
dispatchError('cardholderName', cardHolderError);
|
|
710
780
|
let emailError = null;
|
|
711
781
|
if (hasEmail) {
|
|
712
782
|
const emailAddress = elements.emailAddress?.value?.trim();
|
|
@@ -715,11 +785,19 @@ class PrimerWrapper {
|
|
|
715
785
|
: null;
|
|
716
786
|
dispatchError('emailAddress', emailError);
|
|
717
787
|
}
|
|
718
|
-
|
|
788
|
+
const postalCodeError = getPostalCodeError();
|
|
789
|
+
dispatchError('postalCode', postalCodeError);
|
|
790
|
+
return valid && !emailError && !cardHolderError && !postalCodeError;
|
|
719
791
|
};
|
|
720
792
|
const dispatchError = (inputName, error) => {
|
|
721
793
|
onInputChange(inputName, error);
|
|
722
794
|
};
|
|
795
|
+
const getPostalCodeError = () => {
|
|
796
|
+
const postalCode = elements.postalCode?.value?.trim();
|
|
797
|
+
return isPostalCodeRequired?.() && !postalCode
|
|
798
|
+
? 'Please enter a postal code'
|
|
799
|
+
: null;
|
|
800
|
+
};
|
|
723
801
|
const onHostedInputChange = (name) => (event) => {
|
|
724
802
|
const input = event;
|
|
725
803
|
if (input.submitted) {
|
|
@@ -742,7 +820,22 @@ class PrimerWrapper {
|
|
|
742
820
|
};
|
|
743
821
|
elements.emailAddress.addEventListener('input', emailAddressOnChange);
|
|
744
822
|
}
|
|
823
|
+
const countrySelectorOnChange = (e) => {
|
|
824
|
+
const countryCode = e.target.value.trim();
|
|
825
|
+
onCardInputValueChange?.('countryCode', countryCode);
|
|
826
|
+
if (!isPostalCodeRequired?.()) {
|
|
827
|
+
dispatchError('postalCode', null);
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
const postalCodeOnChange = (e) => {
|
|
831
|
+
const postalCode = e.target.value.trim();
|
|
832
|
+
onCardInputValueChange?.('postalCode', postalCode);
|
|
833
|
+
dispatchError('postalCode', getPostalCodeError());
|
|
834
|
+
};
|
|
745
835
|
elements.cardholderName?.addEventListener('input', cardHolderOnChange);
|
|
836
|
+
elements.emailAddress?.addEventListener('input', emailAddressOnChange);
|
|
837
|
+
elements.countrySelector?.addEventListener('change', countrySelectorOnChange);
|
|
838
|
+
elements.postalCode?.addEventListener('input', postalCodeOnChange);
|
|
746
839
|
cardNumberInput.addEventListener('change', onHostedInputChange('cardNumber'));
|
|
747
840
|
expiryInput.addEventListener('change', onHostedInputChange('expiryDate'));
|
|
748
841
|
cvvInput.addEventListener('change', onHostedInputChange('cvv'));
|
|
@@ -785,6 +878,8 @@ class PrimerWrapper {
|
|
|
785
878
|
pmManager.removeHostedInputs();
|
|
786
879
|
elements.cardholderName?.removeEventListener('input', cardHolderOnChange);
|
|
787
880
|
elements.emailAddress?.removeEventListener('input', emailAddressOnChange);
|
|
881
|
+
elements.countrySelector?.removeEventListener('change', countrySelectorOnChange);
|
|
882
|
+
elements.postalCode?.removeEventListener('input', postalCodeOnChange);
|
|
788
883
|
elements.button?.removeEventListener('click', onSubmitHandler);
|
|
789
884
|
};
|
|
790
885
|
this.destroyCallbacks.push(onDestroy);
|
|
@@ -803,6 +898,12 @@ class PrimerWrapper {
|
|
|
803
898
|
if (elements.emailAddress) {
|
|
804
899
|
elements.emailAddress.disabled = disabled;
|
|
805
900
|
}
|
|
901
|
+
if (elements.countrySelector) {
|
|
902
|
+
elements.countrySelector.disabled = disabled;
|
|
903
|
+
}
|
|
904
|
+
if (elements.postalCode) {
|
|
905
|
+
elements.postalCode.disabled = disabled;
|
|
906
|
+
}
|
|
806
907
|
},
|
|
807
908
|
submit: () => onSubmitHandler(),
|
|
808
909
|
destroy: () => {
|
|
@@ -825,7 +926,7 @@ class PrimerWrapper {
|
|
|
825
926
|
}, method);
|
|
826
927
|
}
|
|
827
928
|
async renderCheckout(clientToken, checkoutOptions, checkoutRenderOptions) {
|
|
828
|
-
const { cardElements, paymentButtonElements, container, onSubmit, onInputChange, onMethodRender, onMethodRenderError, onMethodsAvailable, onCardInputValueChange, } = checkoutRenderOptions;
|
|
929
|
+
const { cardElements, paymentButtonElements, container, onSubmit, onInputChange, onMethodRender, onMethodRenderError, onMethodsAvailable, onCardInputValueChange, isCardholderNameRequired, isPostalCodeRequired, } = checkoutRenderOptions;
|
|
829
930
|
await this.initializeHeadlessCheckout(clientToken, checkoutOptions);
|
|
830
931
|
onMethodsAvailable?.(this.availableMethods);
|
|
831
932
|
await Promise.all(this.availableMethods.map(method => {
|
|
@@ -838,6 +939,8 @@ class PrimerWrapper {
|
|
|
838
939
|
onMethodRender,
|
|
839
940
|
onMethodRenderError,
|
|
840
941
|
onCardInputValueChange,
|
|
942
|
+
isCardholderNameRequired,
|
|
943
|
+
isPostalCodeRequired,
|
|
841
944
|
});
|
|
842
945
|
}
|
|
843
946
|
else {
|
|
@@ -1052,6 +1155,12 @@ class APIClient {
|
|
|
1052
1155
|
if (params.email !== undefined) {
|
|
1053
1156
|
payload.email_address = params.email;
|
|
1054
1157
|
}
|
|
1158
|
+
if (params.countryCode !== undefined) {
|
|
1159
|
+
payload.country_code = params.countryCode;
|
|
1160
|
+
}
|
|
1161
|
+
if (params.postalCode !== undefined) {
|
|
1162
|
+
payload.postal_code = params.postalCode;
|
|
1163
|
+
}
|
|
1055
1164
|
return (await this.request(API_ENDPOINTS.CREATE_PAYMENT, {
|
|
1056
1165
|
method: 'POST',
|
|
1057
1166
|
body: JSON.stringify(payload),
|
|
@@ -1356,6 +1465,34 @@ const renderError = (container, reqId) => {
|
|
|
1356
1465
|
}
|
|
1357
1466
|
};
|
|
1358
1467
|
|
|
1468
|
+
/**
|
|
1469
|
+
* @fileoverview Airwallex device fingerprinting script loader
|
|
1470
|
+
*/
|
|
1471
|
+
/**
|
|
1472
|
+
* Loads Airwallex device fingerprinting script for fraud prevention.
|
|
1473
|
+
* The script collects browser, screen, device, and interaction data.
|
|
1474
|
+
*
|
|
1475
|
+
* @param sessionId - Unique order session ID (UUID v4 format, max 128 chars)
|
|
1476
|
+
* @param isDemoMode - If true, uses demo environment URL for testing
|
|
1477
|
+
* @returns Promise that resolves when script is loaded
|
|
1478
|
+
*
|
|
1479
|
+
* @see https://www.airwallex.com/docs/payments/online-payments/native-api/device-fingerprinting
|
|
1480
|
+
*/
|
|
1481
|
+
async function loadAirwallexDeviceFingerprint(sessionId, isLivemode = true) {
|
|
1482
|
+
const scriptId = 'airwallex-fraud-api';
|
|
1483
|
+
const src = isLivemode
|
|
1484
|
+
? 'https://static.airwallex.com/webapp/fraud/device-fingerprint/index.js'
|
|
1485
|
+
: 'https://static-demo.airwallex.com/webapp/fraud/device-fingerprint/index.js';
|
|
1486
|
+
await loadScript$1({
|
|
1487
|
+
id: scriptId,
|
|
1488
|
+
src,
|
|
1489
|
+
async: true,
|
|
1490
|
+
attributes: {
|
|
1491
|
+
'data-order-session-id': sessionId,
|
|
1492
|
+
},
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1359
1496
|
/**
|
|
1360
1497
|
* @fileoverview Checkout instance manager for Funnefox SDK
|
|
1361
1498
|
*/
|
|
@@ -1364,6 +1501,8 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1364
1501
|
super();
|
|
1365
1502
|
this.counter = 0;
|
|
1366
1503
|
this.radarSessionId = null;
|
|
1504
|
+
this.airwallexDeviceId = null;
|
|
1505
|
+
this.cardSessionFieldConfig = {};
|
|
1367
1506
|
this.handleInputChange = (inputName, error) => {
|
|
1368
1507
|
this.emit(EVENTS.INPUT_ERROR, { name: inputName, error });
|
|
1369
1508
|
};
|
|
@@ -1381,6 +1520,17 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1381
1520
|
this.handleCardInputValueChange = (inputName, value) => {
|
|
1382
1521
|
if (inputName === 'emailAddress') {
|
|
1383
1522
|
this.cardEmailAddress = value?.trim() || undefined;
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
if (inputName === 'countryCode') {
|
|
1526
|
+
this.cardCountryCode = this.normalizeCountryCode(value);
|
|
1527
|
+
if (!this.isPostalCodeVisible()) {
|
|
1528
|
+
this.cardPostalCode = undefined;
|
|
1529
|
+
}
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
if (inputName === 'postalCode') {
|
|
1533
|
+
this.cardPostalCode = value?.trim() || undefined;
|
|
1384
1534
|
}
|
|
1385
1535
|
};
|
|
1386
1536
|
this.handleMethodRender = (method) => {
|
|
@@ -1401,13 +1551,19 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1401
1551
|
try {
|
|
1402
1552
|
this.onLoaderChangeWithRace(true);
|
|
1403
1553
|
this._setState('processing');
|
|
1404
|
-
const radarSessionId = await
|
|
1554
|
+
const [radarSessionId, airwallexDeviceId] = await Promise.all([
|
|
1555
|
+
this.radarSessionId,
|
|
1556
|
+
this.airwallexDeviceId,
|
|
1557
|
+
]);
|
|
1405
1558
|
const paymentResponse = await this.apiClient.createPayment({
|
|
1406
1559
|
orderId: this.orderId,
|
|
1407
1560
|
paymentMethodToken: paymentMethodTokenData.token,
|
|
1408
1561
|
email: this.getPaymentEmailAddress(),
|
|
1562
|
+
countryCode: this.getPaymentCountryCode(),
|
|
1563
|
+
postalCode: this.getPaymentPostalCode(),
|
|
1409
1564
|
clientMetadata: {
|
|
1410
1565
|
radarSessionId,
|
|
1566
|
+
airwallexDeviceId,
|
|
1411
1567
|
},
|
|
1412
1568
|
});
|
|
1413
1569
|
const result = this.apiClient.processPaymentResponse(paymentResponse);
|
|
@@ -1478,6 +1634,8 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1478
1634
|
this.primerWrapper = new PrimerWrapper();
|
|
1479
1635
|
this.isDestroyed = false;
|
|
1480
1636
|
this.cardEmailAddress = this.checkoutConfig.customer.email;
|
|
1637
|
+
this.shouldApplySessionCardholderNameConfig =
|
|
1638
|
+
this.checkoutConfig.card?.cardholderName?.required === undefined;
|
|
1481
1639
|
this._setupCallbackBridges();
|
|
1482
1640
|
}
|
|
1483
1641
|
_setupCallbackBridges() {
|
|
@@ -1517,7 +1675,7 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1517
1675
|
this.hideInitializingLoader();
|
|
1518
1676
|
}
|
|
1519
1677
|
}
|
|
1520
|
-
async createSession(
|
|
1678
|
+
async createSession() {
|
|
1521
1679
|
this.apiClient = new APIClient({
|
|
1522
1680
|
baseUrl: this.baseUrl || DEFAULTS.BASE_URL,
|
|
1523
1681
|
orgId: this.orgId,
|
|
@@ -1532,14 +1690,11 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1532
1690
|
clientMetadata: this.checkoutConfig.clientMetadata,
|
|
1533
1691
|
countryCode: this.checkoutConfig.customer.countryCode,
|
|
1534
1692
|
};
|
|
1535
|
-
this.sessionMethod = method;
|
|
1536
1693
|
const cacheKey = [
|
|
1537
|
-
//this.id,
|
|
1538
1694
|
this.orgId,
|
|
1539
1695
|
this.checkoutConfig.priceId,
|
|
1540
1696
|
this.checkoutConfig.customer.externalId,
|
|
1541
1697
|
this.checkoutConfig.customer.email,
|
|
1542
|
-
//method || 'default',
|
|
1543
1698
|
].join('-');
|
|
1544
1699
|
let sessionResponse;
|
|
1545
1700
|
// Return cached response if payload hasn't changed
|
|
@@ -1559,6 +1714,17 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1559
1714
|
.catch(() => '');
|
|
1560
1715
|
});
|
|
1561
1716
|
}
|
|
1717
|
+
// Initialize Airwallex device fingerprinting if enabled by backend
|
|
1718
|
+
if (response.data?.airwallex_risk_enabled) {
|
|
1719
|
+
const isLivemode = response.data?.is_livemode;
|
|
1720
|
+
const deviceId = generateUUID();
|
|
1721
|
+
this.airwallexDeviceId = loadAirwallexDeviceFingerprint(deviceId, isLivemode)
|
|
1722
|
+
.then(() => deviceId)
|
|
1723
|
+
.catch(() => {
|
|
1724
|
+
// Silently fail - return deviceId anyway
|
|
1725
|
+
return deviceId;
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1562
1728
|
this.isCollectingApplePayEmail =
|
|
1563
1729
|
!!response.data?.collect_apple_pay_email;
|
|
1564
1730
|
this.applySessionCardFieldConfig(response);
|
|
@@ -1573,7 +1739,9 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1573
1739
|
this.clientToken = sessionData.clientToken;
|
|
1574
1740
|
}
|
|
1575
1741
|
applySessionCardFieldConfig(response) {
|
|
1576
|
-
const cardConfig =
|
|
1742
|
+
const cardConfig = {
|
|
1743
|
+
...(this.checkoutConfig.card || {}),
|
|
1744
|
+
};
|
|
1577
1745
|
if (cardConfig.emailAddress?.visible === undefined &&
|
|
1578
1746
|
response.data?.show_email_field !== undefined) {
|
|
1579
1747
|
cardConfig.emailAddress = {
|
|
@@ -1581,19 +1749,41 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1581
1749
|
visible: response.data.show_email_field,
|
|
1582
1750
|
};
|
|
1583
1751
|
}
|
|
1584
|
-
if (
|
|
1752
|
+
if (this.shouldApplySessionCardholderNameConfig &&
|
|
1585
1753
|
response.data?.show_cardholder_name_field !== undefined) {
|
|
1586
1754
|
cardConfig.cardholderName = {
|
|
1587
1755
|
...cardConfig.cardholderName,
|
|
1588
1756
|
required: response.data.show_cardholder_name_field,
|
|
1589
1757
|
};
|
|
1590
1758
|
}
|
|
1759
|
+
const countryFieldOverrides = this.normalizeCountryFieldOverrides(response.data?.country_field_overrides);
|
|
1760
|
+
const detectedCountryCode = this.normalizeCountryCode(response.data?.detected_country_code) ||
|
|
1761
|
+
this.cardCountryCode;
|
|
1762
|
+
this.cardSessionFieldConfig = {
|
|
1763
|
+
...this.cardSessionFieldConfig,
|
|
1764
|
+
showCountrySelector: response.data?.show_country_selector_field ??
|
|
1765
|
+
this.cardSessionFieldConfig.showCountrySelector,
|
|
1766
|
+
showPostalCode: response.data?.show_postal_code_field ??
|
|
1767
|
+
this.cardSessionFieldConfig.showPostalCode,
|
|
1768
|
+
detectedCountryCode: detectedCountryCode || this.cardSessionFieldConfig.detectedCountryCode,
|
|
1769
|
+
validCountries: response.data?.valid_countries ||
|
|
1770
|
+
this.cardSessionFieldConfig.validCountries,
|
|
1771
|
+
countryFieldOverrides: countryFieldOverrides ||
|
|
1772
|
+
this.cardSessionFieldConfig.countryFieldOverrides,
|
|
1773
|
+
};
|
|
1591
1774
|
if (Object.keys(cardConfig).length > 0) {
|
|
1592
1775
|
this.checkoutConfig.card = cardConfig;
|
|
1593
1776
|
}
|
|
1777
|
+
this.cardCountryCode =
|
|
1778
|
+
this.cardSessionFieldConfig.detectedCountryCode || this.cardCountryCode;
|
|
1779
|
+
if (!this.isPostalCodeVisible()) {
|
|
1780
|
+
this.cardPostalCode = undefined;
|
|
1781
|
+
}
|
|
1594
1782
|
}
|
|
1595
1783
|
getPrimerCardConfig() {
|
|
1596
|
-
const cardConfig = {
|
|
1784
|
+
const cardConfig = {
|
|
1785
|
+
...(this.checkoutConfig.card || {}),
|
|
1786
|
+
};
|
|
1597
1787
|
delete cardConfig.emailAddress;
|
|
1598
1788
|
return Object.keys(cardConfig).length
|
|
1599
1789
|
? cardConfig
|
|
@@ -1703,6 +1893,8 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1703
1893
|
onSubmit: this.handleSubmit,
|
|
1704
1894
|
onInputChange: this.handleInputChange,
|
|
1705
1895
|
onCardInputValueChange: this.handleCardInputValueChange,
|
|
1896
|
+
isCardholderNameRequired: () => this.isCardholderNameRequired(),
|
|
1897
|
+
isPostalCodeRequired: () => this.isPostalCodeVisible(),
|
|
1706
1898
|
onMethodRender: this.handleMethodRender,
|
|
1707
1899
|
onMethodsAvailable: this.handleMethodsAvailable,
|
|
1708
1900
|
onMethodRenderError: this.handleMethodRenderError,
|
|
@@ -1861,12 +2053,58 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1861
2053
|
isProcessing() {
|
|
1862
2054
|
return ['processing', 'action_required'].includes(this.state);
|
|
1863
2055
|
}
|
|
2056
|
+
normalizeCountryCode(countryCode) {
|
|
2057
|
+
const normalized = countryCode?.trim().toUpperCase();
|
|
2058
|
+
return normalized || undefined;
|
|
2059
|
+
}
|
|
2060
|
+
normalizeCountryFieldOverrides(overrides) {
|
|
2061
|
+
if (!overrides) {
|
|
2062
|
+
return undefined;
|
|
2063
|
+
}
|
|
2064
|
+
return Object.entries(overrides).reduce((result, [countryCode, override]) => {
|
|
2065
|
+
const normalizedCountryCode = this.normalizeCountryCode(countryCode);
|
|
2066
|
+
if (normalizedCountryCode && override) {
|
|
2067
|
+
result[normalizedCountryCode] = override;
|
|
2068
|
+
}
|
|
2069
|
+
return result;
|
|
2070
|
+
}, {});
|
|
2071
|
+
}
|
|
2072
|
+
getSelectedCountryCode() {
|
|
2073
|
+
return (this.normalizeCountryCode(this.cardCountryCode) ||
|
|
2074
|
+
this.normalizeCountryCode(this.cardSessionFieldConfig.detectedCountryCode));
|
|
2075
|
+
}
|
|
2076
|
+
getCountryFieldOverride(countryCode = this.getSelectedCountryCode()) {
|
|
2077
|
+
if (!countryCode) {
|
|
2078
|
+
return undefined;
|
|
2079
|
+
}
|
|
2080
|
+
return this.cardSessionFieldConfig.countryFieldOverrides?.[countryCode];
|
|
2081
|
+
}
|
|
2082
|
+
isCardholderNameRequired() {
|
|
2083
|
+
return !!this.checkoutConfig.card?.cardholderName?.required;
|
|
2084
|
+
}
|
|
2085
|
+
isPostalCodeVisible(countryCode = this.getSelectedCountryCode()) {
|
|
2086
|
+
const defaultValue = !!this.cardSessionFieldConfig.showPostalCode;
|
|
2087
|
+
const overrideValue = this.getCountryFieldOverride(countryCode)?.show_postal_code;
|
|
2088
|
+
if (overrideValue === null || overrideValue === undefined) {
|
|
2089
|
+
return defaultValue;
|
|
2090
|
+
}
|
|
2091
|
+
return overrideValue;
|
|
2092
|
+
}
|
|
2093
|
+
getPaymentCountryCode() {
|
|
2094
|
+
return this.getSelectedCountryCode();
|
|
2095
|
+
}
|
|
2096
|
+
getPaymentPostalCode() {
|
|
2097
|
+
if (!this.isPostalCodeVisible()) {
|
|
2098
|
+
return undefined;
|
|
2099
|
+
}
|
|
2100
|
+
return this.cardPostalCode?.trim() || undefined;
|
|
2101
|
+
}
|
|
1864
2102
|
// Creates containers to render hosted inputs with labels and error messages,
|
|
1865
2103
|
// a card holder input with label and error, and a submit button.
|
|
1866
2104
|
async getDefaultSkinCheckoutOptions() {
|
|
1867
2105
|
const skinFactory = (await import('./chunk-index.es2.js'))
|
|
1868
2106
|
.default;
|
|
1869
|
-
const skin = await skinFactory(this.checkoutConfig);
|
|
2107
|
+
const skin = await skinFactory(this.checkoutConfig, this.cardSessionFieldConfig);
|
|
1870
2108
|
this.on(EVENTS.INPUT_ERROR, skin.onInputError);
|
|
1871
2109
|
this.on(EVENTS.STATUS_CHANGE, skin.onStatusChange);
|
|
1872
2110
|
this.on(EVENTS.ERROR, (error) => skin.onError(error));
|
|
@@ -1882,7 +2120,7 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1882
2120
|
}
|
|
1883
2121
|
async getCardDefaultSkinCheckoutOptions(node) {
|
|
1884
2122
|
const CardSkin = (await import('./chunk-index.es3.js')).default;
|
|
1885
|
-
const skin = new CardSkin(node, this.checkoutConfig);
|
|
2123
|
+
const skin = new CardSkin(node, this.checkoutConfig, this.cardSessionFieldConfig);
|
|
1886
2124
|
skin.init();
|
|
1887
2125
|
this.on(EVENTS.INPUT_ERROR, skin.onInputError);
|
|
1888
2126
|
this.on(EVENTS.METHOD_RENDER, skin.onMethodRender);
|
|
@@ -1899,7 +2137,7 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1899
2137
|
async initMethod(method, element, callbacks) {
|
|
1900
2138
|
this._ensureNotDestroyed();
|
|
1901
2139
|
if (!this.isReady()) {
|
|
1902
|
-
await this.createSession(
|
|
2140
|
+
await this.createSession();
|
|
1903
2141
|
}
|
|
1904
2142
|
if (callbacks.onRenderSuccess) {
|
|
1905
2143
|
this.on(EVENTS.METHOD_RENDER, callbacks.onRenderSuccess);
|
|
@@ -1943,6 +2181,8 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1943
2181
|
onSubmit: this.handleSubmit,
|
|
1944
2182
|
onInputChange: this.handleInputChange,
|
|
1945
2183
|
onCardInputValueChange: this.handleCardInputValueChange,
|
|
2184
|
+
isCardholderNameRequired: () => this.isCardholderNameRequired(),
|
|
2185
|
+
isPostalCodeRequired: () => this.isPostalCodeVisible(),
|
|
1946
2186
|
onMethodRender: this.handleMethodRender,
|
|
1947
2187
|
onMethodRenderError: this.handleMethodRenderError,
|
|
1948
2188
|
};
|
package/dist/chunk-index.es2.js
CHANGED
|
@@ -27,7 +27,7 @@ const paymentMethodTemplates = {
|
|
|
27
27
|
[PaymentMethod.APPLE_PAY]: applePayTemplate,
|
|
28
28
|
};
|
|
29
29
|
class DefaultSkin {
|
|
30
|
-
constructor(checkoutConfig) {
|
|
30
|
+
constructor(checkoutConfig, cardSessionFieldConfig) {
|
|
31
31
|
this.onLoaderChange = (isLoading) => {
|
|
32
32
|
document
|
|
33
33
|
.querySelectorAll(`${this.containerSelector} .loader-container`)
|
|
@@ -116,6 +116,7 @@ class DefaultSkin {
|
|
|
116
116
|
}
|
|
117
117
|
this.containerEl = containerEl;
|
|
118
118
|
this.checkoutConfig = checkoutConfig;
|
|
119
|
+
this.cardSessionFieldConfig = cardSessionFieldConfig;
|
|
119
120
|
}
|
|
120
121
|
initAccordion() {
|
|
121
122
|
const paymentMethodCards = this.containerEl.querySelectorAll('.ff-payment-method-card');
|
|
@@ -164,7 +165,7 @@ class DefaultSkin {
|
|
|
164
165
|
this.paymentMethodOrder.forEach(paymentMethod => {
|
|
165
166
|
paymentMethodContainers.insertAdjacentHTML('beforeend', paymentMethodTemplates[paymentMethod]);
|
|
166
167
|
});
|
|
167
|
-
this.cardInstance = new CardSkin(document.querySelector('#cardForm'), this.checkoutConfig);
|
|
168
|
+
this.cardInstance = new CardSkin(document.querySelector('#cardForm'), this.checkoutConfig, this.cardSessionFieldConfig);
|
|
168
169
|
this.cardInstance.init();
|
|
169
170
|
this.wireCardInputs();
|
|
170
171
|
}
|
|
@@ -195,8 +196,8 @@ class DefaultSkin {
|
|
|
195
196
|
};
|
|
196
197
|
}
|
|
197
198
|
}
|
|
198
|
-
const createDefaultSkin = async (checkoutConfig) => {
|
|
199
|
-
const skin = new DefaultSkin(checkoutConfig);
|
|
199
|
+
const createDefaultSkin = async (checkoutConfig, cardSessionFieldConfig) => {
|
|
200
|
+
const skin = new DefaultSkin(checkoutConfig, cardSessionFieldConfig);
|
|
200
201
|
await skin['init']();
|
|
201
202
|
return skin;
|
|
202
203
|
};
|