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