@funnelfox/billing 0.5.7 → 0.5.8

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.
@@ -7,212 +7,1991 @@
7
7
  */
8
8
  'use strict';
9
9
 
10
- var index = require('./funnelfox-billing.cjs.js');
11
- var index$1 = require('./chunk-index.cjs2.js');
12
-
13
- var template = "<div class=\"ff-skin-default\" id=\"ff-payment-method-containers\">\n <div id=\"success-screen\" style=\"display: none\">\n <div class=\"success\">\n <img alt=\"Loading\" src=\"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIHZpZXdCb3g9IjAgMCA1MCA1MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTI1IDAuMTExMzI4QzIwLjA3NzQgMC4xMTEzMjggMTUuMjY1NCAxLjU3MTA0IDExLjE3MjUgNC4zMDU4NkM3LjA3OTUyIDcuMDQwNjkgMy44ODk0NSAxMC45Mjc4IDIuMDA1NjYgMTUuNDc1N0MwLjEyMTg4MyAyMC4wMjM1IC0wLjM3MSAyNS4wMjc4IDAuNTg5MzQzIDI5Ljg1NThDMS41NDk2OSAzNC42ODM4IDMuOTIwMTIgMzkuMTE4NSA3LjQwMDkgNDIuNTk5M0MxMC44ODE3IDQ2LjA4MDEgMTUuMzE2NCA0OC40NTA1IDIwLjE0NDQgNDkuNDEwOUMyNC45NzI0IDUwLjM3MTIgMjkuOTc2NyA0OS44NzgzIDM0LjUyNDYgNDcuOTk0NkMzOS4wNzI0IDQ2LjExMDggNDIuOTU5NSA0Mi45MjA3IDQ1LjY5NDQgMzguODI3N0M0OC40MjkyIDM0LjczNDggNDkuODg4OSAyOS45MjI4IDQ5Ljg4ODkgMjUuMDAwMkM0OS44ODg5IDE4LjM5OTMgNDcuMjY2NyAxMi4wNjg3IDQyLjU5OTEgNy40MDExMkMzNy45MzE1IDIuNzMzNTQgMzEuNjAwOSAwLjExMTMyOCAyNSAwLjExMTMyOFpNNDEuMjU1NiAxNi42NDY5TDIwLjgxNTYgMzcuMDcxM0w4Ljc0NDQ0IDI1LjAwMDJDOC4zMzE4OCAyNC41ODc3IDguMTAwMTEgMjQuMDI4MSA4LjEwMDExIDIzLjQ0NDdDOC4xMDAxMSAyMi44NjEyIDguMzMxODggMjIuMzAxNyA4Ljc0NDQ0IDIxLjg4OTFDOS4xNTcgMjEuNDc2NSA5LjcxNjU1IDIxLjI0NDggMTAuMyAyMS4yNDQ4QzEwLjg4MzQgMjEuMjQ0OCAxMS40NDMgMjEuNDc2NSAxMS44NTU2IDIxLjg4OTFMMjAuODQ2NyAzMC44ODAyTDM4LjE3NTYgMTMuNTY2OUMzOC4zNzk4IDEzLjM2MjYgMzguNjIyMyAxMy4yMDA2IDM4Ljg4OTIgMTMuMDlDMzkuMTU2MiAxMi45Nzk1IDM5LjQ0MjIgMTIuOTIyNiAzOS43MzExIDEyLjkyMjZDNDAuMDIgMTIuOTIyNiA0MC4zMDYxIDEyLjk3OTUgNDAuNTczIDEzLjA5QzQwLjgzOTkgMTMuMjAwNiA0MS4wODI0IDEzLjM2MjYgNDEuMjg2NyAxMy41NjY5QzQxLjQ5MDkgMTMuNzcxMiA0MS42NTMgMTQuMDEzNyA0MS43NjM1IDE0LjI4MDZDNDEuODc0MSAxNC41NDc1IDQxLjkzMSAxNC44MzM1IDQxLjkzMSAxNS4xMjI0QzQxLjkzMSAxNS40MTEzIDQxLjg3NDEgMTUuNjk3NCA0MS43NjM1IDE1Ljk2NDNDNDEuNjUzIDE2LjIzMTIgNDEuNDkwOSAxNi40NzM3IDQxLjI4NjcgMTYuNjc4TDQxLjI1NTYgMTYuNjQ2OVoiIGZpbGw9IiM4RURGQzIiLz4KPC9zdmc+Cg==\">\n <div>Payment completed successfully</div>\n </div>\n </div>\n</div>\n";
10
+ /* eslint-disable @typescript-eslint/no-explicit-any */
11
+ /**
12
+ * @fileoverview Lightweight event emitter for Funnefox SDK
13
+ */
14
+ class EventEmitter {
15
+ constructor() {
16
+ this._events = new Map();
17
+ }
18
+ on(eventName, handler) {
19
+ if (typeof handler !== 'function') {
20
+ throw new Error('Event handler must be a function');
21
+ }
22
+ if (!this._events.has(eventName)) {
23
+ this._events.set(eventName, []);
24
+ }
25
+ this._events.get(eventName).push(handler);
26
+ return this;
27
+ }
28
+ once(eventName, handler) {
29
+ if (typeof handler !== 'function') {
30
+ throw new Error('Event handler must be a function');
31
+ }
32
+ const onceWrapper = (...args) => {
33
+ this.off(eventName, onceWrapper);
34
+ handler.apply(this, args);
35
+ };
36
+ return this.on(eventName, onceWrapper);
37
+ }
38
+ off(eventName, handler = null) {
39
+ if (!this._events.has(eventName)) {
40
+ return this;
41
+ }
42
+ if (handler === null) {
43
+ this._events.delete(eventName);
44
+ return this;
45
+ }
46
+ const handlers = this._events.get(eventName);
47
+ const index = handlers.indexOf(handler);
48
+ if (index !== -1) {
49
+ handlers.splice(index, 1);
50
+ if (handlers.length === 0) {
51
+ this._events.delete(eventName);
52
+ }
53
+ }
54
+ return this;
55
+ }
56
+ emit(eventName, ...args) {
57
+ if (!this._events.has(eventName)) {
58
+ return false;
59
+ }
60
+ const handlers = this._events.get(eventName).slice();
61
+ for (const handler of handlers) {
62
+ try {
63
+ handler.apply(this, args);
64
+ }
65
+ catch (error) {
66
+ // eslint-disable-next-line no-console
67
+ console.warn(`Error in event handler for "${String(eventName)}":`, error);
68
+ }
69
+ }
70
+ return true;
71
+ }
72
+ listenerCount(eventName) {
73
+ return this._events.has(eventName)
74
+ ? this._events.get(eventName).length
75
+ : 0;
76
+ }
77
+ eventNames() {
78
+ return Array.from(this._events.keys());
79
+ }
80
+ removeAllListeners() {
81
+ this._events.clear();
82
+ return this;
83
+ }
84
+ listeners(eventName) {
85
+ return this._events.has(eventName)
86
+ ? this._events.get(eventName).slice()
87
+ : [];
88
+ }
89
+ }
14
90
 
15
- var cardTemplate = "<!-- Card Payments Section -->\n<div class=\"ff-payment-method-card ff-payment-method-payment-card\">\n <label class=\"ff-payment-method-label\">\n <input type=\"radio\" name=\"payment-method\" value=\"PAYMENT_CARD\" class=\"ff-payment-method-radio\">\n <div class=\"ff-payment-method-header\">\n <div class=\"ff-card-logos\">\n <img class=\"payment-method-icon\" style=\"max-height: 30px\" src=\"https://assets.fnlfx.com/common/checkout/cards.webp\" alt=\"visa, mastercard\">\n </div>\n </div>\n </label>\n <div class=\"ff-payment-method-content\">\n <div class=\"ff-card-form-container ff-payment-container\" id=\"cardForm\">\n <div class=\"loader-container\">\n <div class=\"loader\"></div>\n </div>\n <div class=\"payment-errors-container\"></div>\n <button class=\"ff-card-form-submit-button\" id=\"submitButton\">\n Continue\n </button>\n <p class=\"ff-security-message\">\n Your payment information is secure with SSL/TLS encryption\n </p>\n </div>\n </div>\n</div>\n";
91
+ /**
92
+ * @fileoverview Custom error classes for Funnefox SDK
93
+ */
94
+ class FunnefoxSDKError extends Error {
95
+ constructor(message, code = ERROR_CODES.SDK_ERROR, details = null) {
96
+ super(message);
97
+ this.name = 'FunnefoxSDKError';
98
+ this.code = code;
99
+ this.details = details;
100
+ if (Error.captureStackTrace) {
101
+ Error.captureStackTrace(this, FunnefoxSDKError);
102
+ }
103
+ }
104
+ }
105
+ class ValidationError extends FunnefoxSDKError {
106
+ constructor(field, message, value = null) {
107
+ super(`Invalid ${field}: ${message}`, ERROR_CODES.VALIDATION_ERROR);
108
+ this.name = 'ValidationError';
109
+ this.field = field;
110
+ this.value = value;
111
+ }
112
+ }
113
+ class APIError extends FunnefoxSDKError {
114
+ constructor(message, statusCode = null, options = {}) {
115
+ super(message, options.errorCode || ERROR_CODES.API_ERROR);
116
+ this.name = 'APIError';
117
+ this.statusCode = statusCode;
118
+ this.errorCode = options.errorCode || null;
119
+ this.errorType = options.errorType || null;
120
+ this.requestId = options.requestId || null;
121
+ this.response = options.response || null;
122
+ }
123
+ }
124
+ class PrimerError extends FunnefoxSDKError {
125
+ constructor(message, primerError = null) {
126
+ super(message, ERROR_CODES.PRIMER_ERROR);
127
+ this.name = 'PrimerError';
128
+ this.primerError = primerError;
129
+ }
130
+ }
131
+ class CheckoutError extends FunnefoxSDKError {
132
+ constructor(message, phase = null) {
133
+ super(message, ERROR_CODES.CHECKOUT_ERROR);
134
+ this.name = 'CheckoutError';
135
+ this.phase = phase;
136
+ }
137
+ }
138
+ class ConfigurationError extends FunnefoxSDKError {
139
+ constructor(message) {
140
+ super(message, ERROR_CODES.CONFIGURATION_ERROR);
141
+ this.name = 'ConfigurationError';
142
+ }
143
+ }
144
+ class NetworkError extends FunnefoxSDKError {
145
+ constructor(message, originalError = null) {
146
+ super(message, ERROR_CODES.NETWORK_ERROR);
147
+ this.name = 'NetworkError';
148
+ this.originalError = originalError;
149
+ }
150
+ }
16
151
 
17
- var paypalTemplate = "<div class=\"ff-payment-method-card ff-payment-method-paypal\">\n <label class=\"ff-payment-method-label\">\n <input type=\"radio\" name=\"payment-method\" value=\"PAYPAL\" class=\"ff-payment-method-radio\">\n <div class=\"ff-payment-method-header\">\n <div class=\"ff-payment-logo ff-paypal-logo\">\n <img class=\"payment-method-icon\" style=\"max-height: 22px\" src=\"https://assets.fnlfx.com/common/checkout/paypal.webp\" alt=\"PayPal logo\">\n </div>\n </div>\n </label>\n <div class=\"ff-payment-method-content\">\n <ul class=\"ff-payment-features\">\n <li class=\"ff-payment-feature\">\n <svg width=\"13\" height=\"11\" viewBox=\"0 0 13 11\" fill=\"none\">\n <path stroke=\"#F8545D\" d=\"M11.1997 1.80133L6.99269 8.63774C6.68226 9.14218 5.9932 9.24721 5.54659 8.85815L2.19971 5.94254\" stroke-width=\"2.66667\" stroke-linecap=\"round\"></path>\n </svg>\n <span> Fast, convenient payment option </span>\n </li>\n <li class=\"ff-payment-feature\">\n <svg width=\"13\" height=\"11\" viewBox=\"0 0 13 11\" fill=\"none\">\n <path stroke=\"#F8545D\" d=\"M11.1997 1.80133L6.99269 8.63774C6.68226 9.14218 5.9932 9.24721 5.54659 8.85815L2.19971 5.94254\" stroke-width=\"2.66667\" stroke-linecap=\"round\"></path>\n </svg>\n <span> Keeps your financial info safe with end-to-end encryption </span>\n </li>\n <li class=\"ff-payment-feature\">\n <svg width=\"13\" height=\"11\" viewBox=\"0 0 13 11\" fill=\"none\">\n <path stroke=\"#F8545D\" d=\"M11.1997 1.80133L6.99269 8.63774C6.68226 9.14218 5.9932 9.24721 5.54659 8.85815L2.19971 5.94254\" stroke-width=\"2.66667\" stroke-linecap=\"round\"></path>\n </svg>\n <span> Backed by PayPal’s industry-leading fraud protection </span>\n </li>\n </ul>\n <div class=\"ff-paypal-button-container ff-payment-container\" id=\"paypalButton\"></div>\n <div class=\"loader-container payment-button-loader\">\n <div class=\"loader\"></div>\n </div>\n <div class=\"payment-errors-container\"></div>\n <p class=\"ff-security-message\">\n Your payment information is secure with SSL/TLS encryption\n </p>\n </div>\n</div>\n";
152
+ /**
153
+ * @fileoverview Dynamic loader for Primer SDK
154
+ * Loads Primer script and CSS from CDN independently of bundler
155
+ */
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
+ /**
167
+ * Injects a script tag into the document head
168
+ */
169
+ function injectScript$1(src, integrity) {
170
+ return new Promise((resolve, reject) => {
171
+ // Check if script already exists
172
+ const existingScript = document.querySelector(`script[src="${src}"]`);
173
+ if (existingScript) {
174
+ resolve(existingScript);
175
+ return;
176
+ }
177
+ const script = document.createElement('script');
178
+ script.src = src;
179
+ script.async = true;
180
+ script.crossOrigin = 'anonymous';
181
+ if (integrity) {
182
+ script.integrity = integrity;
183
+ }
184
+ script.onload = () => resolve(script);
185
+ script.onerror = () => reject(new Error(`Failed to load Primer SDK script from ${src}`));
186
+ document.head.appendChild(script);
187
+ });
188
+ }
189
+ /**
190
+ * Injects a CSS link tag into the document head
191
+ */
192
+ function injectCSS(href, integrity) {
193
+ return new Promise((resolve, reject) => {
194
+ // Check if stylesheet already exists
195
+ const existingLink = document.querySelector(`link[href="${href}"]`);
196
+ if (existingLink) {
197
+ resolve(existingLink);
198
+ return;
199
+ }
200
+ const link = document.createElement('link');
201
+ link.rel = 'stylesheet';
202
+ link.href = href;
203
+ link.crossOrigin = 'anonymous';
204
+ if (integrity) {
205
+ link.integrity = integrity;
206
+ }
207
+ link.onload = () => resolve(link);
208
+ link.onerror = () => reject(new Error(`Failed to load Primer SDK CSS from ${href}`));
209
+ document.head.appendChild(link);
210
+ });
211
+ }
212
+ /**
213
+ * Waits for window.Primer to be available
214
+ */
215
+ function waitForPrimer(timeout = 10000) {
216
+ return new Promise((resolve, reject) => {
217
+ const startTime = Date.now();
218
+ const check = () => {
219
+ if (typeof window !== 'undefined' &&
220
+ window.Primer &&
221
+ typeof window.Primer.createHeadless === 'function') {
222
+ resolve();
223
+ return;
224
+ }
225
+ if (Date.now() - startTime > timeout) {
226
+ reject(new Error('Timeout waiting for Primer SDK to initialize on window'));
227
+ return;
228
+ }
229
+ setTimeout(check, 50);
230
+ };
231
+ check();
232
+ });
233
+ }
234
+ /**
235
+ * Loads the Primer SDK script and CSS from CDN
236
+ * @param version - The version of Primer SDK to load (default: 2.57.3)
237
+ * @returns Promise that resolves when SDK is loaded and ready
238
+ */
239
+ async function loadPrimerSDK(version) {
240
+ // Already loaded
241
+ if (isLoaded) {
242
+ return;
243
+ }
244
+ // Already loading - return existing promise
245
+ if (loadingPromise) {
246
+ return loadingPromise;
247
+ }
248
+ // Check if Primer is already available (user may have loaded it manually)
249
+ if (typeof window !== 'undefined' &&
250
+ window.Primer &&
251
+ typeof window.Primer.createHeadless === 'function') {
252
+ isLoaded = true;
253
+ return;
254
+ }
255
+ const ver = version || DEFAULT_VERSION;
256
+ const jsUrl = `${PRIMER_CDN_BASE}/v${ver}/Primer.min.js`;
257
+ const cssUrl = `${PRIMER_CDN_BASE}/v${ver}/Checkout.css`;
258
+ const hashes = INTEGRITY_HASHES[ver];
259
+ loadingPromise = (async () => {
260
+ try {
261
+ // Load CSS and JS in parallel
262
+ await Promise.all([
263
+ injectCSS(cssUrl, hashes?.css),
264
+ injectScript$1(jsUrl, hashes?.js),
265
+ ]);
266
+ // Wait for Primer to be available on window
267
+ await waitForPrimer();
268
+ isLoaded = true;
269
+ }
270
+ catch (error) {
271
+ loadingPromise = null;
272
+ throw error;
273
+ }
274
+ })();
275
+ return loadingPromise;
276
+ }
18
277
 
19
- var googlePayTemplate = "<!-- Google Pay Section -->\n<div class=\"ff-payment-method-card ff-payment-method-google-pay\">\n <label class=\"ff-payment-method-label\">\n <input type=\"radio\" name=\"payment-method\" value=\"GOOGLE_PAY\" class=\"ff-payment-method-radio\" checked=\"checked\">\n <div class=\"ff-payment-method-header\">\n <div class=\"ff-payment-logo ff-google-pay-logo\">\n <svg class=\"payment-method-icon\" height=\"26\" viewBox=\"0 0 59 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M27.3601 11.6862V18.4674H25.208V1.72177H30.9132C32.3591 1.72177 33.592 2.20374 34.6008 3.16768C35.632 4.13162 36.1476 5.30852 36.1476 6.69838C36.1476 8.12187 35.632 9.29877 34.6008 10.2515C33.6032 11.2042 32.3703 11.675 30.9132 11.675H27.3601V11.6862ZM27.3601 3.78415V9.62382H30.958C31.8099 9.62382 32.5272 9.3324 33.0876 8.76076C33.6593 8.18912 33.9507 7.49419 33.9507 6.70959C33.9507 5.9362 33.6593 5.25248 33.0876 4.68084C32.5272 4.08678 31.8211 3.79536 30.958 3.79536H27.3601V3.78415Z\" fill=\"currentColor\"/>\n <path d=\"M41.7742 6.63107C43.3658 6.63107 44.6212 7.057 45.5403 7.90885C46.4594 8.7607 46.9189 9.9264 46.9189 11.4059V18.4673H44.8677V16.8757H44.7781C43.8926 18.1871 42.7045 18.8372 41.225 18.8372C39.9584 18.8372 38.9048 18.4673 38.0529 17.7164C37.2011 16.9654 36.7751 16.0351 36.7751 14.9142C36.7751 13.7261 37.2235 12.7846 38.1202 12.0896C39.0169 11.3835 40.2162 11.036 41.7069 11.036C42.9847 11.036 44.0383 11.2714 44.8565 11.7422V11.249C44.8565 10.498 44.5651 9.87035 43.9711 9.34355C43.377 8.81674 42.6821 8.55895 41.8863 8.55895C40.6869 8.55895 39.7342 9.06333 39.0393 10.0833L37.145 8.8952C38.1874 7.38205 39.7342 6.63107 41.7742 6.63107ZM38.9944 14.9478C38.9944 15.5083 39.2298 15.979 39.7118 16.3489C40.1826 16.7188 40.743 16.9093 41.3819 16.9093C42.2898 16.9093 43.0968 16.5731 43.8029 15.9006C44.5091 15.228 44.8677 14.4435 44.8677 13.5356C44.1952 13.0088 43.2649 12.7397 42.0656 12.7397C41.1913 12.7397 40.4628 12.9527 39.8799 13.3674C39.2859 13.8046 38.9944 14.3314 38.9944 14.9478Z\" fill=\"currentColor\"/>\n <path d=\"M58.6207 7.00095L51.4472 23.5H49.2279L51.8955 17.7276L47.1655 7.00095H49.5081L52.9155 15.228H52.9604L56.2781 7.00095H58.6207Z\" fill=\"currentColor\"/>\n <path d=\"M18.8001 10.3187C18.8001 9.61709 18.7373 8.94569 18.6208 8.30008H9.6001V11.9989L14.7953 12C14.5846 13.2307 13.9064 14.2799 12.8674 14.9793V17.379H15.9598C17.7655 15.7078 18.8001 13.2375 18.8001 10.3187Z\" fill=\"#4285F4\"/>\n <path d=\"M12.8685 14.9793C12.0076 15.5599 10.8991 15.8995 9.60228 15.8995C7.09717 15.8995 4.97202 14.2115 4.21096 11.9361H1.021V14.411C2.60141 17.5472 5.84965 19.6992 9.60228 19.6992C12.1959 19.6992 14.3749 18.8462 15.9609 17.3779L12.8685 14.9793Z\" fill=\"#34A853\"/>\n <path d=\"M3.91043 10.1002C3.91043 9.46129 4.01691 8.8437 4.21082 8.26309V5.78824H1.02086C0.367396 7.08507 -0.000244141 8.54891 -0.000244141 10.1002C-0.000244141 11.6514 0.368516 13.1153 1.02086 14.4121L4.21082 11.9373C4.01691 11.3567 3.91043 10.7391 3.91043 10.1002Z\" fill=\"#FABB05\"/>\n <path d=\"M9.60228 4.29974C11.0179 4.29974 12.2856 4.78731 13.2865 5.74004L16.027 3.00179C14.3626 1.45164 12.1926 0.500031 9.60228 0.500031C5.85077 0.500031 2.60141 2.65208 1.021 5.78824L4.21096 8.26309C4.97202 5.98775 7.09717 4.29974 9.60228 4.29974Z\" fill=\"#E94235\"/>\n </svg>\n </div>\n </div>\n </label>\n <div class=\"ff-payment-method-content\">\n <ul class=\"ff-payment-features\">\n <li class=\"ff-payment-feature\">\n <svg width=\"13\" height=\"11\" viewBox=\"0 0 13 11\" fill=\"none\">\n <path stroke=\"#F8545D\" d=\"M11.1997 1.80133L6.99269 8.63774C6.68226 9.14218 5.9932 9.24721 5.54659 8.85815L2.19971 5.94254\" stroke-width=\"2.66667\" stroke-linecap=\"round\"></path>\n </svg>\n <span>Easy and private payments with Face/Touch ID</span>\n </li>\n <li class=\"ff-payment-feature\">\n <svg width=\"13\" height=\"11\" viewBox=\"0 0 13 11\" fill=\"none\">\n <path stroke=\"#F8545D\" d=\"M11.1997 1.80133L6.99269 8.63774C6.68226 9.14218 5.9932 9.24721 5.54659 8.85815L2.19971 5.94254\" stroke-width=\"2.66667\" stroke-linecap=\"round\"></path>\n </svg>\n <span>Keeps your financial info safe with end-to-end encryption</span>\n </li>\n <li class=\"ff-payment-feature\">\n <svg width=\"13\" height=\"11\" viewBox=\"0 0 13 11\" fill=\"none\">\n <path stroke=\"#F8545D\" d=\"M11.1997 1.80133L6.99269 8.63774C6.68226 9.14218 5.9932 9.24721 5.54659 8.85815L2.19971 5.94254\" stroke-width=\"2.66667\" stroke-linecap=\"round\"></path>\n </svg>\n <span>Protected by Google Pay's unique Device Account Number</span>\n </li>\n </ul>\n <div class=\"ff-google-pay-button-container ff-payment-container\" id=\"googlePayButton\"></div>\n <div class=\"loader-container payment-button-loader\">\n <div class=\"loader\"></div>\n </div>\n <div class=\"payment-errors-container\"></div>\n <p class=\"ff-security-message\">\n Your payment information is secure with SSL/TLS encryption\n </p>\n </div>\n</div>\n";
278
+ /* eslint-disable @typescript-eslint/no-explicit-any */
279
+ /**
280
+ * @fileoverview Helper utilities for Funnefox SDK
281
+ */
282
+ function merge(...objects) {
283
+ const result = {};
284
+ for (const obj of objects) {
285
+ if (obj && typeof obj === 'object') {
286
+ for (const key in obj) {
287
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
288
+ if (typeof obj[key] === 'object' &&
289
+ !Array.isArray(obj[key]) &&
290
+ obj[key] !== null) {
291
+ result[key] = merge(result[key] || {}, obj[key]);
292
+ }
293
+ else {
294
+ result[key] = obj[key];
295
+ }
296
+ }
297
+ }
298
+ }
299
+ }
300
+ return result;
301
+ }
302
+ function generateId(prefix = '') {
303
+ const timestamp = Date.now().toString(36);
304
+ const random = Math.random().toString(36).substr(2, 5);
305
+ return `${prefix}${timestamp}_${random}`;
306
+ }
307
+ function sleep(ms) {
308
+ return new Promise(resolve => setTimeout(resolve, ms));
309
+ }
310
+ async function retry(fn, maxAttempts = 3, baseDelay = 1000) {
311
+ let lastError;
312
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
313
+ try {
314
+ return await fn();
315
+ }
316
+ catch (error) {
317
+ lastError = error;
318
+ if (attempt === maxAttempts)
319
+ throw lastError;
320
+ const delay = baseDelay * Math.pow(2, attempt - 1);
321
+ await sleep(delay);
322
+ }
323
+ }
324
+ throw lastError;
325
+ }
326
+ function withTimeout(promise, timeoutMs, message = 'Operation timed out') {
327
+ const timeoutPromise = new Promise((_, reject) => {
328
+ setTimeout(() => reject(new Error(message)), timeoutMs);
329
+ });
330
+ return Promise.race([promise, timeoutPromise]);
331
+ }
20
332
 
21
- var applePayTemplate = "<!-- Apple Pay Section -->\n<div class=\"ff-payment-method-card ff-payment-method-apple-pay\">\n <label class=\"ff-payment-method-label\">\n <input type=\"radio\" name=\"payment-method\" value=\"APPLE_PAY\" class=\"ff-payment-method-radio\">\n <div class=\"ff-payment-method-header\">\n <div class=\"ff-payment-logo ff-apple-pay-logo\">\n <svg class=\"payment-method-icon\" height=\"26\" viewBox=\"0 0 63 26\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M8.41022 4.82398C9.5916 4.92293 10.773 4.23026 11.5113 3.35205C12.2374 2.4491 12.7173 1.23692 12.5943 0C11.5483 0.0494767 10.2561 0.692674 9.51777 1.59562C8.84093 2.37488 8.26255 3.63654 8.41022 4.82398ZM22.4638 20.7555V1.47193H29.6628C33.3793 1.47193 35.9758 4.04471 35.9758 7.80494C35.9758 11.5652 33.33 14.1627 29.5644 14.1627H25.4418V20.7555H22.4638ZM12.5819 5.05898C11.5411 4.99877 10.5914 5.37358 9.82438 5.67633C9.33075 5.87116 8.91274 6.03614 8.59472 6.03614C8.23784 6.03614 7.80257 5.86234 7.31387 5.66719C6.6735 5.4115 5.94138 5.11916 5.17364 5.13319C3.41387 5.15793 1.77716 6.15983 0.878819 7.75545C-0.967091 10.9467 0.398882 15.6717 2.18326 18.2693C3.05699 19.5556 4.10301 20.9657 5.48129 20.9163C6.08765 20.8933 6.52383 20.7072 6.97524 20.5147C7.49492 20.293 8.0348 20.0628 8.87776 20.0628C9.69151 20.0628 10.2078 20.287 10.7033 20.5023C11.1746 20.707 11.6271 20.9036 12.2989 20.8915C13.7264 20.8668 14.6247 19.6051 15.4984 18.3187C16.4413 16.9381 16.8557 15.5906 16.9186 15.3861C16.9222 15.3745 16.9246 15.3665 16.9259 15.3625C16.9244 15.361 16.9128 15.3556 16.8922 15.3462C16.577 15.2011 14.1679 14.0926 14.1448 11.1199C14.1216 8.62473 16.0556 7.36054 16.3601 7.16153C16.3786 7.14944 16.3911 7.14125 16.3968 7.137C15.1662 5.30636 13.2464 5.10845 12.5819 5.05898ZM41.4153 20.9039C43.2858 20.9039 45.0209 19.9515 45.8085 18.4424H45.8701V20.7555H48.6266V11.157C48.6266 8.37393 46.4115 6.5804 43.0027 6.5804C39.8401 6.5804 37.5019 8.39866 37.4158 10.8972H40.0985C40.32 9.70979 41.4153 8.93054 42.9166 8.93054C44.7379 8.93054 45.7593 9.78401 45.7593 11.3549V12.4186L42.0429 12.6413C38.5849 12.8516 36.7143 14.274 36.7143 16.7479C36.7143 19.2464 38.6464 20.9039 41.4153 20.9039ZM42.215 18.6156C40.6275 18.6156 39.6184 17.8487 39.6184 16.6736C39.6184 15.4615 40.5906 14.7564 42.4488 14.6451L45.7591 14.4348V15.5233C45.7591 17.3292 44.2332 18.6156 42.215 18.6156ZM57.7699 21.51C56.5762 24.8868 55.2103 26 52.306 26C52.0845 26 51.3461 25.9753 51.1739 25.9258V23.6127C51.3585 23.6375 51.8138 23.6622 52.0476 23.6622C53.3643 23.6622 54.1027 23.1056 54.558 21.6584L54.8288 20.8049L49.7833 6.76594H52.8967L56.4039 18.1579H56.4655L59.9727 6.76594H63L57.7699 21.51ZM25.4416 3.99524H28.875C31.4592 3.99524 32.936 5.38059 32.936 7.81731C32.936 10.254 31.4592 11.6518 28.8627 11.6518H25.4416V3.99524Z\" fill=\"currentColor\"/>\n </svg>\n </div>\n </div>\n </label>\n <div class=\"ff-payment-method-content\">\n <ul class=\"ff-payment-features\">\n <li class=\"ff-payment-feature\">\n <svg width=\"13\" height=\"11\" viewBox=\"0 0 13 11\" fill=\"none\">\n <path stroke=\"#F8545D\" d=\"M11.1997 1.80133L6.99269 8.63774C6.68226 9.14218 5.9932 9.24721 5.54659 8.85815L2.19971 5.94254\" stroke-width=\"2.66667\" stroke-linecap=\"round\"></path>\n </svg>\n <span>Easy and private payments with Face/Touch ID</span>\n </li>\n <li class=\"ff-payment-feature\">\n <svg width=\"13\" height=\"11\" viewBox=\"0 0 13 11\" fill=\"none\">\n <path stroke=\"#F8545D\" d=\"M11.1997 1.80133L6.99269 8.63774C6.68226 9.14218 5.9932 9.24721 5.54659 8.85815L2.19971 5.94254\" stroke-width=\"2.66667\" stroke-linecap=\"round\"></path>\n </svg>\n <span>Keeps your financial info safe with end-to-end encryption</span>\n </li>\n <li class=\"ff-payment-feature\">\n <svg width=\"13\" height=\"11\" viewBox=\"0 0 13 11\" fill=\"none\">\n <path stroke=\"#F8545D\" d=\"M11.1997 1.80133L6.99269 8.63774C6.68226 9.14218 5.9932 9.24721 5.54659 8.85815L2.19971 5.94254\" stroke-width=\"2.66667\" stroke-linecap=\"round\"></path>\n </svg>\n <span>Protected by Apple Pay’s unique Device Account Number</span>\n </li>\n </ul>\n <div class=\"ff-apple-pay-button-container ff-payment-container\" id=\"applePayButton\"></div>\n <div class=\"loader-container payment-button-loader\">\n <div class=\"loader\"></div>\n </div>\n <div class=\"payment-errors-container\"></div>\n <p class=\"ff-security-message\">\n Your payment information is secure with Apple Pay encryption\n </p>\n </div>\n</div>\n";
333
+ /**
334
+ * @fileoverview Headless checkout cache manager
335
+ */
336
+ /**
337
+ * Manages caching and sequential creation of Primer headless checkout instances.
338
+ * Ensures that multiple checkouts with the same configuration reuse the same instance,
339
+ * and that creations happen sequentially to avoid race conditions.
340
+ */
341
+ class HeadlessManager {
342
+ constructor() {
343
+ this.cache = new Map();
344
+ this.queue = Promise.resolve();
345
+ }
346
+ /**
347
+ * Generates a cache key from clientToken and serializable options
348
+ */
349
+ generateKey(clientToken, options) {
350
+ const serializableOptions = {
351
+ paymentHandling: options.paymentHandling,
352
+ apiVersion: options.apiVersion,
353
+ style: options.style,
354
+ card: options.card,
355
+ applePay: options.applePay,
356
+ paypal: options.paypal,
357
+ googlePay: options.googlePay,
358
+ };
359
+ return `${clientToken}:${JSON.stringify(serializableOptions)}`;
360
+ }
361
+ /**
362
+ * Gets a cached headless instance or creates a new one.
363
+ * Ensures sequential creation order to avoid race conditions.
364
+ */
365
+ getOrCreate(clientToken, options) {
366
+ const key = this.generateKey(clientToken, options);
367
+ // Return cached promise if exists
368
+ const cached = this.cache.get(key);
369
+ if (cached)
370
+ return cached;
371
+ // Create new headless in sequential order
372
+ const previousQueue = this.queue;
373
+ const promise = (async () => {
374
+ await previousQueue; // Wait for previous creation
375
+ const primerOptions = merge({
376
+ paymentHandling: 'MANUAL',
377
+ apiVersion: '2.4',
378
+ }, options);
379
+ try {
380
+ const headlessResult = await window.Primer.createHeadless(clientToken, primerOptions);
381
+ const headless = await headlessResult;
382
+ await headless.start();
383
+ return headless;
384
+ }
385
+ catch (error) {
386
+ // Remove from cache on failure
387
+ this.cache.delete(key);
388
+ throw new PrimerError('Failed to create Primer headless checkout', error);
389
+ }
390
+ })();
391
+ this.cache.set(key, promise);
392
+ this.queue = promise.catch(() => { }); // Update queue, ignore errors
393
+ return promise;
394
+ }
395
+ /**
396
+ * Removes a headless instance from the cache
397
+ */
398
+ remove(headlessPromise) {
399
+ for (const [key, value] of this.cache.entries()) {
400
+ if (value === headlessPromise) {
401
+ this.cache.delete(key);
402
+ break;
403
+ }
404
+ }
405
+ }
406
+ /**
407
+ * Clears all cached instances
408
+ */
409
+ clear() {
410
+ this.cache.clear();
411
+ }
412
+ }
22
413
 
23
- if(typeof document!=="undefined")document.head.appendChild(document.createElement("style")).textContent="/* Main container */\n.ff-skin-default {\n display: flex;\n flex-direction: column;\n text-align: left;\n gap: 8px;\n width: 100%;\n max-width: 400px;\n margin: 0 auto;\n color: #000000;\n}\n\n/* Payment method cards */\n.ff-payment-method-card {\n display: none;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n background-color: #ffffff;\n padding: 20px;\n transition: border-color 0.2s ease;\n height: auto;\n box-shadow: 0px 0px 10px 0px #eee;\n}\n\n.ff-payment-method-card.visible {\n display: block;\n}\n\n.payment-errors-container {\n background-color: #d1000033;\n color: #d10000;\n font-size: 14px;\n padding: 16px 12px;\n border-radius: 8px;\n}\n.payment-errors-container:empty {\n display: none;\n}\n\n/* Label wrapper */\n.ff-payment-method-label {\n display: flex;\n align-items: flex-start;\n gap: 16px;\n cursor: pointer;\n width: 100%;\n}\n\n/* Custom radio button styling */\n.ff-payment-method-radio {\n appearance: none;\n -webkit-appearance: none;\n -moz-appearance: none;\n width: 24px;\n height: 24px;\n min-width: 24px;\n min-height: 24px;\n border: 1px solid #9e9e9e;\n border-radius: 50%;\n background-color: #ffffff;\n cursor: pointer;\n position: relative;\n margin: 0;\n flex-shrink: 0;\n transition: border-color 0.2s ease;\n}\n\n.ff-card-form-submit-button {\n display: block;\n cursor: pointer;\n width: 100%;\n padding: 16px 0;\n border-radius: 16px;\n background-color: #000000;\n color: #ffffff;\n border: none;\n font-size: 16px;\n margin: 12px 0 16px;\n}\n\n.ff-payment-method-radio:checked {\n border-color: #e32f41;\n background-color: #ffffff;\n}\n\n.ff-payment-method-radio:checked::after {\n content: '';\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background-color: #e32f41;\n}\n\n/* Payment method content */\n.ff-payment-method-content {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 16px;\n max-height: 0;\n height: 0;\n overflow: hidden;\n opacity: 0;\n transition:\n opacity 0.3s ease,\n margin-top 0.3s ease;\n margin-top: 0;\n}\n\n.ff-payment-method-card.expanded .ff-payment-method-content {\n max-height: 2000px;\n height: auto;\n opacity: 1;\n margin-top: 16px;\n}\n.ff-payment-method-card.expanded .ff-payment-method-label {\n margin-bottom: 16px;\n}\n\n/* Payment method header */\n.ff-payment-method-header {\n display: flex;\n align-items: center;\n}\n\n/* Google Pay Logo */\n.ff-google-pay-logo {\n display: flex;\n align-items: center;\n gap: 4px;\n font-weight: 500;\n font-size: 18px;\n}\n\n/* Payment features list */\n.ff-payment-features {\n list-style: none;\n padding: 0;\n margin: 0;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.ff-payment-feature {\n display: flex;\n align-items: baseline;\n text-align: left;\n gap: 8px;\n}\n\n.ff-checkmark-icon {\n width: 20px;\n height: 20px;\n min-width: 20px;\n color: #e32f41;\n flex-shrink: 0;\n margin-top: 2px;\n}\n\n.ff-payment-feature span {\n color: #333333;\n font-size: 14px;\n line-height: 1.5;\n}\n\n/* Google Pay button container */\n.ff-google-pay-button-container {\n display: flex;\n justify-content: center;\n}\n\n/* hack for FFB-169 */\n.ff-google-pay-button-container button, .ff-apple-pay-button-container button {\n height: 54px !important;\n border-radius: 28px !important;\n}\n\n/* Security message */\n.ff-security-message {\n text-align: center;\n color: #999999;\n font-size: 14px;\n padding: 0;\n margin: 0\n}\n\n/* Card logos container */\n.ff-card-logos {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n/* Card form container */\n.ff-card-form-container {\n position: relative;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.loader-container {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: flex;\n justify-content: center;\n align-items: center;\n background-color: rgba(255, 255, 255);\n z-index: 2;\n}\n\n.payment-button-loader {\n position: relative;\n height: 50px;\n}\n\n.loader {\n width: 24px;\n height: 24px;\n border: 4px solid #e32f41;\n border-top: 4px solid transparent;\n border-radius: 50%;\n animation: spin 1s linear infinite;\n}\n\n@keyframes spin {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n\n/* Responsive adjustments */\n@media (max-width: 768px) {\n .ff-payment-method-card {\n padding: 16px;\n }\n\n .ff-payment-method-label {\n gap: 12px;\n }\n\n .ff-card-logos {\n gap: 8px;\n }\n}\n\n.ff-payment-container {\n position: relative;\n}\n\n.success {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 16px;\n}\n";
414
+ exports.PaymentMethod = void 0;
415
+ (function (PaymentMethod) {
416
+ PaymentMethod["GOOGLE_PAY"] = "GOOGLE_PAY";
417
+ PaymentMethod["APPLE_PAY"] = "APPLE_PAY";
418
+ PaymentMethod["PAYPAL"] = "PAYPAL";
419
+ PaymentMethod["PAYMENT_CARD"] = "PAYMENT_CARD";
420
+ })(exports.PaymentMethod || (exports.PaymentMethod = {}));
24
421
 
25
- const paymentMethodTemplates = {
26
- [index.PaymentMethod.PAYMENT_CARD]: cardTemplate,
27
- [index.PaymentMethod.PAYPAL]: paypalTemplate,
28
- [index.PaymentMethod.GOOGLE_PAY]: googlePayTemplate,
29
- [index.PaymentMethod.APPLE_PAY]: applePayTemplate,
422
+ /**
423
+ * @fileoverview Constants for Funnefox SDK
424
+ */
425
+ const SDK_VERSION = '0.5.8';
426
+ const DEFAULTS = {
427
+ BASE_URL: 'https://billing.funnelfox.com',
428
+ REGION: 'default',
429
+ SANDBOX: false,
430
+ REQUEST_TIMEOUT: 30000,
431
+ RETRY_ATTEMPTS: 3,
432
+ RETRY_BASE_DELAY: 1000,
30
433
  };
31
- class DefaultSkin {
32
- constructor(checkoutConfig) {
33
- this.onLoaderChange = (isLoading) => {
34
- document
35
- .querySelectorAll(`${this.containerSelector} .loader-container`)
36
- ?.forEach(loaderEl => {
37
- loaderEl.style.display = isLoading ? 'flex' : 'none';
38
- });
39
- };
40
- this.onError = (error, paymentMethod) => {
41
- if (!error) {
42
- this.containerEl
43
- .querySelectorAll('.payment-errors-container')
44
- ?.forEach(container => {
45
- container.innerHTML = '';
434
+ const CHECKOUT_STATES = {
435
+ INITIALIZING: 'initializing',
436
+ READY: 'ready',
437
+ PROCESSING: 'processing',
438
+ ACTION_REQUIRED: 'action_required',
439
+ UPDATING: 'updating',
440
+ COMPLETED: 'completed',
441
+ ERROR: 'error',
442
+ DESTROYED: 'destroyed',
443
+ };
444
+ const EVENTS = {
445
+ SUCCESS: 'success',
446
+ ERROR: 'error',
447
+ STATUS_CHANGE: 'status-change',
448
+ DESTROY: 'destroy',
449
+ INPUT_ERROR: 'input-error',
450
+ LOADER_CHANGE: 'loader-change',
451
+ METHOD_RENDER: 'method-render',
452
+ METHOD_RENDER_ERROR: 'method-render-error',
453
+ START_PURCHASE: 'start-purchase',
454
+ PURCHASE_FAILURE: 'purchase-failure',
455
+ PURCHASE_COMPLETED: 'purchase-completed',
456
+ PURCHASE_CANCELLED: 'purchase-cancelled',
457
+ METHODS_AVAILABLE: 'methods-available',
458
+ };
459
+ const API_ENDPOINTS = {
460
+ CREATE_CLIENT_SESSION: '/v1/checkout/create_client_session',
461
+ UPDATE_CLIENT_SESSION: '/v1/checkout/update_client_session',
462
+ CREATE_PAYMENT: '/v1/checkout/create_payment',
463
+ RESUME_PAYMENT: '/v1/checkout/resume_payment',
464
+ };
465
+ const ERROR_CODES = {
466
+ SDK_ERROR: 'SDK_ERROR',
467
+ VALIDATION_ERROR: 'VALIDATION_ERROR',
468
+ API_ERROR: 'API_ERROR',
469
+ PRIMER_ERROR: 'PRIMER_ERROR',
470
+ CHECKOUT_ERROR: 'CHECKOUT_ERROR',
471
+ CONFIGURATION_ERROR: 'CONFIGURATION_ERROR',
472
+ NETWORK_ERROR: 'NETWORK_ERROR',
473
+ };
474
+ const ALLOWED_BUTTON_PAYMENT_METHODS = [
475
+ exports.PaymentMethod.GOOGLE_PAY,
476
+ exports.PaymentMethod.APPLE_PAY,
477
+ exports.PaymentMethod.PAYPAL,
478
+ ];
479
+ const ALLOWED_CARD_PAYMENT_METHODS = [
480
+ exports.PaymentMethod.PAYMENT_CARD,
481
+ ];
482
+ const ALLOWED_PAYMENT_METHODS = [
483
+ ...ALLOWED_BUTTON_PAYMENT_METHODS,
484
+ ...ALLOWED_CARD_PAYMENT_METHODS,
485
+ ];
486
+ const inputStyle = {
487
+ input: {
488
+ error: {
489
+ borderColor: 'rgb(227, 47, 65)',
490
+ },
491
+ base: {
492
+ borderWidth: '1px',
493
+ borderStyle: 'solid',
494
+ borderColor: 'rgb(0 0 0 / 10%)',
495
+ height: '36px',
496
+ paddingHorizontal: 10,
497
+ borderRadius: '6px',
498
+ },
499
+ },
500
+ };
501
+ ({
502
+ paddingLeft: inputStyle.input.base.paddingHorizontal + 'px',
503
+ paddingRight: inputStyle.input.base.paddingHorizontal + 'px'});
504
+ const DEFAULT_PAYMENT_METHOD_ORDER = [
505
+ exports.PaymentMethod.APPLE_PAY,
506
+ exports.PaymentMethod.GOOGLE_PAY,
507
+ exports.PaymentMethod.PAYPAL,
508
+ exports.PaymentMethod.PAYMENT_CARD,
509
+ ];
510
+ const DEFAULT_BUTTONS_STYLES = {
511
+ [exports.PaymentMethod.APPLE_PAY]: {
512
+ buttonStyle: 'black',
513
+ },
514
+ [exports.PaymentMethod.PAYPAL]: {
515
+ buttonColor: 'gold',
516
+ buttonShape: 'pill',
517
+ buttonLabel: 'pay',
518
+ buttonSize: 'large',
519
+ buttonHeight: 54,
520
+ },
521
+ [exports.PaymentMethod.GOOGLE_PAY]: {
522
+ buttonColor: 'black',
523
+ buttonSizeMode: 'fill',
524
+ buttonType: 'pay',
525
+ },
526
+ };
527
+
528
+ /**
529
+ * @fileoverview Primer SDK integration wrapper
530
+ */
531
+ class PrimerWrapper {
532
+ constructor() {
533
+ this.isInitialized = false;
534
+ this.destroyCallbacks = [];
535
+ this.currentHeadless = null;
536
+ this.availableMethods = [];
537
+ this.paymentMethodsInterfaces = [];
538
+ }
539
+ isPrimerAvailable() {
540
+ return (typeof window !== 'undefined' &&
541
+ window.Primer &&
542
+ typeof window.Primer?.createHeadless === 'function');
543
+ }
544
+ /**
545
+ * Loads Primer SDK if not already available
546
+ * @param version - Optional version to load (uses default if not specified)
547
+ */
548
+ async ensurePrimerLoaded(version) {
549
+ if (this.isPrimerAvailable()) {
550
+ return;
551
+ }
552
+ try {
553
+ await loadPrimerSDK(version);
554
+ }
555
+ catch (error) {
556
+ throw new PrimerError('Failed to load Primer SDK', error);
557
+ }
558
+ }
559
+ ensurePrimerAvailable() {
560
+ if (!this.isPrimerAvailable()) {
561
+ throw new PrimerError('Primer SDK not found. Please include the Primer SDK script before initializing FunnefoxSDK.');
562
+ }
563
+ }
564
+ async createHeadlessCheckout(clientToken, options) {
565
+ await this.ensurePrimerLoaded();
566
+ this.currentHeadless = PrimerWrapper.headlessManager.getOrCreate(clientToken, options);
567
+ return this.currentHeadless;
568
+ }
569
+ disableButtons(disabled) {
570
+ if (!this.paymentMethodsInterfaces)
571
+ return;
572
+ for (const paymentMethodInterface of this.paymentMethodsInterfaces) {
573
+ paymentMethodInterface.setDisabled(disabled);
574
+ }
575
+ }
576
+ async renderButton(allowedPaymentMethod, { htmlNode, onMethodRenderError, onMethodRender, }) {
577
+ let button;
578
+ // Ensure Primer SDK is loaded
579
+ await this.ensurePrimerLoaded();
580
+ if (!this.currentHeadless) {
581
+ throw new PrimerError('Headless checkout not found');
582
+ }
583
+ try {
584
+ const headless = await this.currentHeadless;
585
+ const pmManager = await headless.createPaymentMethodManager(allowedPaymentMethod);
586
+ if (!pmManager) {
587
+ throw new Error('Payment method manager is not available');
588
+ }
589
+ /* hack for FFB-169 & FFB-242
590
+ * Primer SDK does not allow to set the height of the googlepay and applepay buttons, so we need to use a hack to set the height of the button.
591
+ */
592
+ const wrapper = document.createElement('div');
593
+ wrapper.className = generateId('funnefox-primer-button-wrapper');
594
+ const styleEl = document.createElement('style');
595
+ document.head.appendChild(styleEl);
596
+ const sheet = styleEl.sheet;
597
+ if (sheet) {
598
+ sheet.insertRule(`
599
+ .${wrapper.className} {
600
+ width: 100% !important;
601
+ }
602
+ `);
603
+ sheet.insertRule(`
604
+ .${wrapper.className} button {
605
+ height: 54px !important;
606
+ border-radius: 28px !important;
607
+ }
608
+ `);
609
+ }
610
+ htmlNode.appendChild(wrapper);
611
+ /* end hack */
612
+ button = pmManager.createButton();
613
+ await button.render(wrapper, {});
614
+ this.destroyCallbacks.push(() => button.clean());
615
+ onMethodRender(allowedPaymentMethod);
616
+ return {
617
+ setDisabled: (disabled) => {
618
+ button.setDisabled(disabled);
619
+ },
620
+ destroy: () => {
621
+ styleEl.remove();
622
+ button.clean();
623
+ },
624
+ };
625
+ }
626
+ catch (error) {
627
+ onMethodRenderError(allowedPaymentMethod);
628
+ throw new PrimerError('Failed to initialize Primer checkout', error);
629
+ }
630
+ }
631
+ async initMethod(method, htmlNode, options) {
632
+ try {
633
+ if (method === exports.PaymentMethod.PAYMENT_CARD) {
634
+ if (!options.cardElements ||
635
+ !options.onSubmit ||
636
+ !options.onInputChange) {
637
+ throw new PrimerError('Card elements, onSubmit, and onInputChange are required for PAYMENT_CARD method');
638
+ }
639
+ const cardInterface = await this.renderCardCheckoutWithElements(options.cardElements, {
640
+ onSubmit: options.onSubmit,
641
+ onInputChange: options.onInputChange,
642
+ onMethodRenderError: options.onMethodRenderError,
643
+ onMethodRender: options.onMethodRender,
644
+ });
645
+ this.paymentMethodsInterfaces.push(cardInterface);
646
+ return cardInterface;
647
+ }
648
+ else {
649
+ const buttonInterface = await this.renderButton(method, {
650
+ htmlNode,
651
+ onMethodRenderError: options.onMethodRenderError,
652
+ onMethodRender: options.onMethodRender,
653
+ });
654
+ this.paymentMethodsInterfaces.push(buttonInterface);
655
+ return buttonInterface;
656
+ }
657
+ }
658
+ catch (error) {
659
+ throw new PrimerError('Failed to initialize Primer checkout', error);
660
+ }
661
+ }
662
+ async renderCardCheckoutWithElements(elements, { onSubmit, onInputChange, onMethodRenderError, onMethodRender, }) {
663
+ try {
664
+ if (!this.currentHeadless) {
665
+ throw new PrimerError('Headless checkout not found');
666
+ }
667
+ const headless = await this.currentHeadless;
668
+ const pmManager = await headless.createPaymentMethodManager('PAYMENT_CARD');
669
+ if (!pmManager) {
670
+ throw new Error('Payment method manager is not available');
671
+ }
672
+ const { cardNumberInput, expiryInput, cvvInput } = pmManager.createHostedInputs();
673
+ const validateForm = async () => {
674
+ if (!pmManager)
675
+ return false;
676
+ const { valid, validationErrors } = await pmManager.validate();
677
+ const cardHolderError = validationErrors.find(v => v.name === 'cardholderName');
678
+ dispatchError('cardholderName', cardHolderError?.message || null);
679
+ return valid;
680
+ };
681
+ const dispatchError = (inputName, error) => {
682
+ onInputChange(inputName, error);
683
+ };
684
+ const onHostedInputChange = (name) => (event) => {
685
+ const input = event;
686
+ if (input.submitted) {
687
+ dispatchError(name, input.error);
688
+ }
689
+ };
690
+ const cardHolderOnChange = async (e) => {
691
+ pmManager.setCardholderName(e.target.value);
692
+ dispatchError('cardholderName', null);
693
+ };
694
+ elements.cardholderName?.addEventListener('input', cardHolderOnChange);
695
+ cardNumberInput.addEventListener('change', onHostedInputChange('cardNumber'));
696
+ expiryInput.addEventListener('change', onHostedInputChange('expiryDate'));
697
+ cvvInput.addEventListener('change', onHostedInputChange('cvv'));
698
+ const onSubmitHandler = async () => {
699
+ if (!(await validateForm())) {
700
+ return;
701
+ }
702
+ try {
703
+ onSubmit(true);
704
+ await pmManager.submit();
705
+ }
706
+ catch (error) {
707
+ const primerError = new PrimerError('Failed to submit payment', error);
708
+ throw primerError;
709
+ }
710
+ finally {
711
+ onSubmit(false);
712
+ }
713
+ };
714
+ elements.button?.addEventListener('click', onSubmitHandler);
715
+ await Promise.all([
716
+ cardNumberInput.render(elements.cardNumber, {
717
+ placeholder: '1234 1234 1234 1234',
718
+ ariaLabel: 'Card number',
719
+ style: inputStyle,
720
+ }),
721
+ expiryInput.render(elements.expiryDate, {
722
+ placeholder: 'MM/YY',
723
+ ariaLabel: 'Expiry date',
724
+ style: inputStyle,
725
+ }),
726
+ cvvInput.render(elements.cvv, {
727
+ placeholder: '123',
728
+ ariaLabel: 'CVV',
729
+ style: inputStyle,
730
+ }),
731
+ ]);
732
+ const onDestroy = () => {
733
+ pmManager.removeHostedInputs();
734
+ elements.cardholderName?.removeEventListener('change', cardHolderOnChange);
735
+ elements.button?.removeEventListener('click', onSubmitHandler);
736
+ };
737
+ this.destroyCallbacks.push(onDestroy);
738
+ onMethodRender(exports.PaymentMethod.PAYMENT_CARD);
739
+ return {
740
+ setDisabled: (disabled) => {
741
+ cardNumberInput.setDisabled(disabled);
742
+ expiryInput.setDisabled(disabled);
743
+ cvvInput.setDisabled(disabled);
744
+ if (elements.button) {
745
+ elements.button.disabled = disabled;
746
+ }
747
+ if (elements.cardholderName) {
748
+ elements.cardholderName.disabled = disabled;
749
+ }
750
+ },
751
+ submit: () => onSubmitHandler(),
752
+ destroy: () => {
753
+ this.destroyCallbacks = this.destroyCallbacks.filter(callback => callback !== onDestroy);
754
+ onDestroy();
755
+ },
756
+ };
757
+ }
758
+ catch (error) {
759
+ onMethodRenderError(exports.PaymentMethod.PAYMENT_CARD);
760
+ throw new PrimerError('Failed to initialize Primer checkout', error);
761
+ }
762
+ }
763
+ async initializeHeadlessCheckout(clientToken, primerOptions) {
764
+ await this.createHeadlessCheckout(clientToken, {
765
+ ...primerOptions,
766
+ onTokenizeSuccess: this.wrapTokenizeHandler(primerOptions.onTokenizeSuccess),
767
+ onResumeSuccess: this.wrapResumeHandler(primerOptions.onResumeSuccess),
768
+ onAvailablePaymentMethodsLoad: (items) => {
769
+ let isApplePayAvailable = false;
770
+ this.availableMethods = ALLOWED_PAYMENT_METHODS.filter(method => {
771
+ return items.some((item) => {
772
+ if (item.type === exports.PaymentMethod.APPLE_PAY) {
773
+ isApplePayAvailable = true;
774
+ }
775
+ return item.type === method;
776
+ });
777
+ });
778
+ if (isApplePayAvailable) {
779
+ this.availableMethods = this.availableMethods.filter(method => method !== exports.PaymentMethod.GOOGLE_PAY);
780
+ }
781
+ if (this.availableMethods.length === 0) {
782
+ throw new PrimerError('No allowed payment methods found');
783
+ }
784
+ },
785
+ });
786
+ }
787
+ async renderCheckout(clientToken, checkoutOptions, checkoutRenderOptions) {
788
+ const { cardElements, paymentButtonElements, container, onSubmit, onInputChange, onMethodRender, onMethodRenderError, onMethodsAvailable, } = checkoutRenderOptions;
789
+ await this.initializeHeadlessCheckout(clientToken, checkoutOptions);
790
+ onMethodsAvailable?.(this.availableMethods);
791
+ await Promise.all(this.availableMethods.map(method => {
792
+ if (method === exports.PaymentMethod.PAYMENT_CARD) {
793
+ // For card, use the main container
794
+ return this.initMethod(method, container, {
795
+ cardElements,
796
+ onSubmit,
797
+ onInputChange,
798
+ onMethodRender,
799
+ onMethodRenderError,
46
800
  });
47
- return;
48
801
  }
49
- let errorContainer = null;
50
- if (paymentMethod) {
51
- const methodKey = paymentMethod.replace('_', '-').toLowerCase();
52
- errorContainer = this.containerEl.querySelector(`.ff-payment-method-${methodKey} .payment-errors-container`);
802
+ else {
803
+ const buttonElementsMap = {
804
+ [exports.PaymentMethod.PAYPAL]: paymentButtonElements.paypal,
805
+ [exports.PaymentMethod.GOOGLE_PAY]: paymentButtonElements.googlePay,
806
+ [exports.PaymentMethod.APPLE_PAY]: paymentButtonElements.applePay,
807
+ };
808
+ // For buttons, use the specific button container element
809
+ const buttonElement = buttonElementsMap[method];
810
+ return this.initMethod(method, buttonElement, {
811
+ onMethodRender,
812
+ onMethodRenderError,
813
+ });
814
+ }
815
+ }));
816
+ this.isInitialized = true;
817
+ }
818
+ wrapTokenizeHandler(handler) {
819
+ return async (paymentMethodTokenData, primerHandler) => {
820
+ try {
821
+ await handler(paymentMethodTokenData, primerHandler);
53
822
  }
54
- if (errorContainer) {
55
- errorContainer.textContent = error?.message || '';
823
+ catch (error) {
824
+ // eslint-disable-next-line no-console
825
+ console.error('Error in tokenize handler:', error);
826
+ primerHandler.handleFailure('Payment processing failed. Please try again.');
56
827
  }
57
828
  };
58
- this.onStatusChange = (state, oldState) => {
59
- const isLoading = ['initializing'].includes(state);
60
- if (!isLoading && oldState === 'initializing') {
61
- this.onLoaderChange(false);
829
+ }
830
+ wrapResumeHandler(handler) {
831
+ return async (resumeTokenData, primerHandler) => {
832
+ try {
833
+ await handler(resumeTokenData, primerHandler);
62
834
  }
63
- if (state === 'updating') {
64
- this.onLoaderChange(true);
835
+ catch (error) {
836
+ // eslint-disable-next-line no-console
837
+ console.error('Error in resume handler:', error);
838
+ primerHandler.handleFailure('Payment processing failed. Please try again.');
65
839
  }
66
- if (state === 'ready' && oldState === 'updating') {
67
- this.onLoaderChange(false);
840
+ };
841
+ }
842
+ async destroy() {
843
+ if (this.currentHeadless) {
844
+ PrimerWrapper.headlessManager.remove(this.currentHeadless);
845
+ this.currentHeadless = null;
846
+ }
847
+ if (this.destroyCallbacks) {
848
+ try {
849
+ Promise.all(this.destroyCallbacks.map(destroy => destroy()));
850
+ }
851
+ catch (error) {
852
+ // eslint-disable-next-line no-console
853
+ console.warn('Error destroying Primer checkout:', error);
68
854
  }
855
+ }
856
+ this.destroyCallbacks = [];
857
+ this.isInitialized = false;
858
+ }
859
+ createHandlers(handlers) {
860
+ return {
861
+ handleSuccess: () => {
862
+ if (handlers.onSuccess)
863
+ handlers.onSuccess();
864
+ },
865
+ handleFailure: (message) => {
866
+ if (handlers.onError)
867
+ handlers.onError(new Error(message));
868
+ },
869
+ continueWithNewClientToken: (newClientToken) => {
870
+ if (handlers.onActionRequired)
871
+ handlers.onActionRequired(newClientToken);
872
+ },
69
873
  };
70
- this.onSuccess = () => {
71
- const successScreenString = document.querySelector('#success-screen')?.innerHTML;
72
- const containers = document.querySelectorAll('.ff-payment-container');
73
- containers.forEach(container => {
74
- container.innerHTML = successScreenString;
75
- });
76
- this.onLoaderChange(false);
874
+ }
875
+ getCurrentCheckout() {
876
+ return this.destroyCallbacks;
877
+ }
878
+ isActive() {
879
+ return this.isInitialized && this.destroyCallbacks.length > 0;
880
+ }
881
+ validateContainer(selector) {
882
+ const element = document.querySelector(selector);
883
+ if (!element) {
884
+ throw new PrimerError(`Checkout container not found: ${selector}`);
885
+ }
886
+ const computedStyle = window.getComputedStyle(element);
887
+ if (computedStyle.display === 'none') {
888
+ // eslint-disable-next-line no-console
889
+ console.warn('Checkout container is hidden, this may cause display issues');
890
+ }
891
+ return element;
892
+ }
893
+ }
894
+ PrimerWrapper.headlessManager = new HeadlessManager();
895
+
896
+ /**
897
+ * @fileoverview Input validation utilities for Funnefox SDK
898
+ */
899
+ function sanitizeString(input) {
900
+ return input?.trim() || '';
901
+ }
902
+ function requireString(value, fieldName) {
903
+ const sanitized = sanitizeString(value);
904
+ if (sanitized.length === 0) {
905
+ throw new ValidationError(fieldName, 'must be a non-empty string', value);
906
+ }
907
+ return true;
908
+ }
909
+
910
+ /**
911
+ * @fileoverview API client for Funnefox backend integration
912
+ */
913
+ class APIClient {
914
+ constructor(config) {
915
+ this.baseUrl = config.baseUrl.replace(/\/$/, '');
916
+ this.orgId = config.orgId;
917
+ this.timeout = config.timeout || 30000;
918
+ this.retryAttempts = config.retryAttempts || 3;
919
+ }
920
+ async request(endpoint, options = {}) {
921
+ const url = `${this.baseUrl}/${this.orgId}${endpoint}`;
922
+ const requestOptions = {
923
+ method: 'GET',
924
+ headers: {
925
+ 'Content-Type': 'application/json',
926
+ 'X-SDK-Version': SDK_VERSION,
927
+ ...(options.headers || {}),
928
+ },
929
+ ...options,
77
930
  };
78
- this.onDestroy = () => {
79
- if (this.containerEl.innerHTML) {
80
- this.containerEl.innerHTML = '';
931
+ try {
932
+ return await retry(async () => {
933
+ return await withTimeout(this._makeRequest(url, requestOptions), this.timeout, 'Request timed out');
934
+ }, this.retryAttempts);
935
+ }
936
+ catch (error) {
937
+ if (error instanceof Error && error.name === 'APIError') {
938
+ throw error;
939
+ }
940
+ throw new NetworkError('Network request failed', error);
941
+ }
942
+ }
943
+ async _makeRequest(url, options) {
944
+ let response;
945
+ try {
946
+ response = await fetch(url, options);
947
+ }
948
+ catch (error) {
949
+ if (error instanceof Error && error.name === 'NetworkError') {
950
+ throw error;
81
951
  }
952
+ throw new NetworkError('Network request failed', error);
953
+ }
954
+ let data;
955
+ try {
956
+ data = await response.json();
957
+ }
958
+ catch {
959
+ throw new APIError('Invalid JSON response', response.status, {});
960
+ }
961
+ if (!response.ok) {
962
+ const d = data;
963
+ const message = d.error?.[0]?.msg || 'Failed to create payment';
964
+ throw new APIError(message, response.status, {
965
+ response: data,
966
+ });
967
+ }
968
+ return data;
969
+ }
970
+ async createClientSession(params) {
971
+ const payload = {
972
+ region: params.region || 'default',
973
+ integration_type: 'primer',
974
+ pp_ident: params.priceId,
975
+ external_id: params.externalId,
976
+ email_address: params.email,
977
+ client_metadata: params.clientMetadata || {},
82
978
  };
83
- this.onInputError = (event) => {
84
- this.cardInstance.onInputError(event);
979
+ if (params.countryCode !== undefined) {
980
+ payload.country_code = params.countryCode;
981
+ }
982
+ return (await this.request(API_ENDPOINTS.CREATE_CLIENT_SESSION, {
983
+ method: 'POST',
984
+ body: JSON.stringify(payload),
985
+ }));
986
+ }
987
+ async updateClientSession(params) {
988
+ const payload = {
989
+ order_id: params.orderId,
990
+ client_token: params.clientToken,
991
+ pp_ident: params.priceId,
992
+ client_metadata: params.clientMetadata || {},
993
+ };
994
+ return await this.request(API_ENDPOINTS.UPDATE_CLIENT_SESSION, {
995
+ method: 'POST',
996
+ body: JSON.stringify(payload),
997
+ });
998
+ }
999
+ async createPayment(params) {
1000
+ const payload = {
1001
+ order_id: params.orderId,
1002
+ payment_method_token: params.paymentMethodToken,
1003
+ client_metadata: params.clientMetadata || {},
85
1004
  };
86
- this.onMethodRender = (paymentMethod) => {
87
- const methodKey = paymentMethod.replace('_', '-').toLowerCase();
88
- const methodContainer = this.containerEl.querySelector(`.ff-payment-method-${methodKey}`);
89
- if (paymentMethod === index.PaymentMethod.PAYMENT_CARD) {
90
- this.cardInstance.onMethodRender();
1005
+ return (await this.request(API_ENDPOINTS.CREATE_PAYMENT, {
1006
+ method: 'POST',
1007
+ body: JSON.stringify(payload),
1008
+ }));
1009
+ }
1010
+ async resumePayment(params) {
1011
+ const payload = {
1012
+ order_id: params.orderId,
1013
+ resume_token: params.resumeToken,
1014
+ };
1015
+ return (await this.request(API_ENDPOINTS.RESUME_PAYMENT, {
1016
+ method: 'POST',
1017
+ body: JSON.stringify(payload),
1018
+ }));
1019
+ }
1020
+ processSessionResponse(response) {
1021
+ if (response.status === 'error') {
1022
+ const firstError = response.error?.[0];
1023
+ const message = firstError?.msg || 'Session creation failed';
1024
+ throw new APIError(message, null, {
1025
+ errorCode: firstError?.code,
1026
+ errorType: firstError?.type,
1027
+ requestId: response.req_id,
1028
+ response,
1029
+ });
1030
+ }
1031
+ const data = response.data;
1032
+ return {
1033
+ type: 'session_created',
1034
+ orderId: data.order_id,
1035
+ clientToken: data.client_token,
1036
+ };
1037
+ }
1038
+ processPaymentResponse(response) {
1039
+ if (response.status === 'error') {
1040
+ const firstError = response.error?.[0];
1041
+ const message = firstError?.msg || 'Payment request failed';
1042
+ throw new APIError(message, null, {
1043
+ errorCode: firstError?.code,
1044
+ errorType: firstError?.type,
1045
+ response,
1046
+ });
1047
+ }
1048
+ const data = response.data;
1049
+ if (data.action_required_token) {
1050
+ return {
1051
+ type: 'action_required',
1052
+ orderId: data.order_id,
1053
+ clientToken: data.action_required_token,
1054
+ };
1055
+ }
1056
+ if (data.checkout_status) {
1057
+ switch (data.checkout_status) {
1058
+ case 'succeeded':
1059
+ return {
1060
+ type: 'success',
1061
+ orderId: data.order_id,
1062
+ status: 'succeeded',
1063
+ };
1064
+ case 'failed':
1065
+ throw new APIError(data.failed_message_for_user || 'Payment failed', null, { response });
1066
+ case 'cancelled':
1067
+ throw new APIError('Payment was cancelled by user', null, {
1068
+ response,
1069
+ });
1070
+ case 'processing':
1071
+ return {
1072
+ type: 'processing',
1073
+ orderId: data.order_id,
1074
+ status: 'processing',
1075
+ };
1076
+ default:
1077
+ throw new APIError(`Unhandled checkout status: ${data.checkout_status}`, null, { response });
91
1078
  }
92
- if (methodContainer) {
93
- methodContainer.classList.add('visible');
1079
+ }
1080
+ throw new APIError('Invalid payment response format', null, { response });
1081
+ }
1082
+ async oneClick(payload) {
1083
+ return (await this.request(`/billing/${this.orgId}/v1/checkout/one_click`, {
1084
+ method: 'POST',
1085
+ body: JSON.stringify(payload),
1086
+ }));
1087
+ }
1088
+ }
1089
+
1090
+ var loaderHtml = "<div class=\"ff-sdk-loader-container\">\n <div class=\"ff-sdk-loader\"></div>\n</div>\n";
1091
+
1092
+ if(typeof document!=="undefined")document.head.appendChild(document.createElement("style")).textContent=".ff-sdk-loader-container {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: flex;\n justify-content: center;\n align-items: center;\n background-color: rgba(255, 255, 255);\n z-index: 2;\n}\n\n.ff-sdk-loader {\n width: 24px;\n height: 24px;\n border: 4px solid #e32f41;\n border-top: 4px solid transparent;\n border-radius: 50%;\n animation: spin 1s linear infinite;\n}\n\n@keyframes spin {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n }";
1093
+
1094
+ const loaderConfig = {
1095
+ html: loaderHtml,
1096
+ selectors: {
1097
+ loaderContainer: '.ff-sdk-loader-container'},
1098
+ };
1099
+ const renderLoader = (container) => {
1100
+ const loaderContainer = document.querySelector(container);
1101
+ if (loaderContainer) {
1102
+ loaderContainer.innerHTML = loaderConfig.html;
1103
+ }
1104
+ };
1105
+ const hideLoader = () => {
1106
+ const loaderContainer = document.querySelector(loaderConfig.selectors.loaderContainer);
1107
+ if (loaderContainer) {
1108
+ loaderContainer.remove();
1109
+ }
1110
+ };
1111
+
1112
+ var V3_URL = 'https://js.stripe.com/v3';
1113
+ var V3_URL_REGEX = /^https:\/\/js\.stripe\.com\/v3\/?(\?.*)?$/;
1114
+ var EXISTING_SCRIPT_MESSAGE = 'loadStripe.setLoadParameters was called but an existing Stripe.js script already exists in the document; existing script parameters will be used';
1115
+ var findScript = function findScript() {
1116
+ var scripts = document.querySelectorAll("script[src^=\"".concat(V3_URL, "\"]"));
1117
+
1118
+ for (var i = 0; i < scripts.length; i++) {
1119
+ var script = scripts[i];
1120
+
1121
+ if (!V3_URL_REGEX.test(script.src)) {
1122
+ continue;
1123
+ }
1124
+
1125
+ return script;
1126
+ }
1127
+
1128
+ return null;
1129
+ };
1130
+
1131
+ var injectScript = function injectScript(params) {
1132
+ var queryString = '';
1133
+ var script = document.createElement('script');
1134
+ script.src = "".concat(V3_URL).concat(queryString);
1135
+ var headOrBody = document.head || document.body;
1136
+
1137
+ if (!headOrBody) {
1138
+ throw new Error('Expected document.body not to be null. Stripe.js requires a <body> element.');
1139
+ }
1140
+
1141
+ headOrBody.appendChild(script);
1142
+ return script;
1143
+ };
1144
+
1145
+ var registerWrapper = function registerWrapper(stripe, startTime) {
1146
+ if (!stripe || !stripe._registerWrapper) {
1147
+ return;
1148
+ }
1149
+
1150
+ stripe._registerWrapper({
1151
+ name: 'stripe-js',
1152
+ version: "4.6.0",
1153
+ startTime: startTime
1154
+ });
1155
+ };
1156
+
1157
+ var stripePromise = null;
1158
+ var onErrorListener = null;
1159
+ var onLoadListener = null;
1160
+
1161
+ var onError = function onError(reject) {
1162
+ return function () {
1163
+ reject(new Error('Failed to load Stripe.js'));
1164
+ };
1165
+ };
1166
+
1167
+ var onLoad = function onLoad(resolve, reject) {
1168
+ return function () {
1169
+ if (window.Stripe) {
1170
+ resolve(window.Stripe);
1171
+ } else {
1172
+ reject(new Error('Stripe.js not available'));
1173
+ }
1174
+ };
1175
+ };
1176
+
1177
+ var loadScript = function loadScript(params) {
1178
+ // Ensure that we only attempt to load Stripe.js at most once
1179
+ if (stripePromise !== null) {
1180
+ return stripePromise;
1181
+ }
1182
+
1183
+ stripePromise = new Promise(function (resolve, reject) {
1184
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
1185
+ // Resolve to null when imported server side. This makes the module
1186
+ // safe to import in an isomorphic code base.
1187
+ resolve(null);
1188
+ return;
1189
+ }
1190
+
1191
+ if (window.Stripe) {
1192
+ resolve(window.Stripe);
1193
+ return;
1194
+ }
1195
+
1196
+ try {
1197
+ var script = findScript();
1198
+
1199
+ if (script && params) ; else if (!script) {
1200
+ script = injectScript(params);
1201
+ } else if (script && onLoadListener !== null && onErrorListener !== null) {
1202
+ var _script$parentNode;
1203
+
1204
+ // remove event listeners
1205
+ script.removeEventListener('load', onLoadListener);
1206
+ script.removeEventListener('error', onErrorListener); // if script exists, but we are reloading due to an error,
1207
+ // reload script to trigger 'load' event
1208
+
1209
+ (_script$parentNode = script.parentNode) === null || _script$parentNode === void 0 ? void 0 : _script$parentNode.removeChild(script);
1210
+ script = injectScript(params);
1211
+ }
1212
+
1213
+ onLoadListener = onLoad(resolve, reject);
1214
+ onErrorListener = onError(reject);
1215
+ script.addEventListener('load', onLoadListener);
1216
+ script.addEventListener('error', onErrorListener);
1217
+ } catch (error) {
1218
+ reject(error);
1219
+ return;
1220
+ }
1221
+ }); // Resets stripePromise on error
1222
+
1223
+ return stripePromise["catch"](function (error) {
1224
+ stripePromise = null;
1225
+ return Promise.reject(error);
1226
+ });
1227
+ };
1228
+ var initStripe = function initStripe(maybeStripe, args, startTime) {
1229
+ if (maybeStripe === null) {
1230
+ return null;
1231
+ }
1232
+
1233
+ var stripe = maybeStripe.apply(undefined, args);
1234
+ registerWrapper(stripe, startTime);
1235
+ return stripe;
1236
+ }; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
1237
+
1238
+ var stripePromise$1;
1239
+ var loadCalled = false;
1240
+
1241
+ var getStripePromise = function getStripePromise() {
1242
+ if (stripePromise$1) {
1243
+ return stripePromise$1;
1244
+ }
1245
+
1246
+ stripePromise$1 = loadScript(null)["catch"](function (error) {
1247
+ // clear cache on error
1248
+ stripePromise$1 = null;
1249
+ return Promise.reject(error);
1250
+ });
1251
+ return stripePromise$1;
1252
+ }; // Execute our own script injection after a tick to give users time to do their
1253
+ // own script injection.
1254
+
1255
+
1256
+ Promise.resolve().then(function () {
1257
+ return getStripePromise();
1258
+ })["catch"](function (error) {
1259
+ if (!loadCalled) {
1260
+ console.warn(error);
1261
+ }
1262
+ });
1263
+ var loadStripe = function loadStripe() {
1264
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
1265
+ args[_key] = arguments[_key];
1266
+ }
1267
+
1268
+ loadCalled = true;
1269
+ var startTime = Date.now(); // if previous attempts are unsuccessful, will re-load script
1270
+
1271
+ return getStripePromise().then(function (maybeStripe) {
1272
+ return initStripe(maybeStripe, args, startTime);
1273
+ });
1274
+ };
1275
+
1276
+ var errorHtml = "<div class=\"ff-sdk-error-container\">\n <p class=\"ff-sdk-error-message\">The payment form didn’t load correctly.</p>\n <p>Please refresh the page to try again.</p>\n <p>If the problem persists, please contact support.</p>\n <p class=\"ff-sdk-error-request-id\"></p>\n</div>\n";
1277
+
1278
+ if(typeof document!=="undefined")document.head.appendChild(document.createElement("style")).textContent=".ff-sdk-error-container {\n background-color: #d1000033;\n color: #d10000;\n font-size: 14px;\n padding: 16px 12px;\n border-radius: 8px;\n}";
1279
+
1280
+ const errorConfig = {
1281
+ html: errorHtml,
1282
+ selectors: {
1283
+ errorRequestId: '.ff-sdk-error-request-id',
1284
+ },
1285
+ };
1286
+ const renderError = (container, reqId) => {
1287
+ const errorContainer = document.querySelector(container);
1288
+ if (errorContainer) {
1289
+ errorContainer.innerHTML = errorConfig.html;
1290
+ if (reqId) {
1291
+ const errorRequestId = errorContainer.querySelector(errorConfig.selectors.errorRequestId);
1292
+ if (errorRequestId) {
1293
+ errorRequestId.textContent = `Request ID: ${reqId}`;
94
1294
  }
1295
+ }
1296
+ }
1297
+ };
1298
+
1299
+ /**
1300
+ * @fileoverview Checkout instance manager for Funnefox SDK
1301
+ */
1302
+ class CheckoutInstance extends EventEmitter {
1303
+ constructor(config) {
1304
+ super();
1305
+ this.counter = 0;
1306
+ this.radarSessionId = null;
1307
+ this.handleInputChange = (inputName, error) => {
1308
+ this.emit(EVENTS.INPUT_ERROR, { name: inputName, error });
1309
+ };
1310
+ this.handleMethodRender = (method) => {
1311
+ this.emit(EVENTS.METHOD_RENDER, method);
95
1312
  };
96
- this.onMethodsAvailable = (methods) => {
97
- this.availableMethods = methods;
98
- this.initAccordion();
99
- methods.forEach(this.onMethodRender);
1313
+ this.handleMethodRenderError = (method) => {
1314
+ this.emit(EVENTS.METHOD_RENDER_ERROR, method);
100
1315
  };
101
- this.onStartPurchase = (paymentMethod) => {
102
- this.currentPurchaseMethod = paymentMethod;
1316
+ this.handleSubmit = (isSubmitting) => {
1317
+ this.onLoaderChangeWithRace(isSubmitting);
1318
+ this._setState(isSubmitting ? 'processing' : 'ready');
1319
+ };
1320
+ this.handleTokenizeSuccess = async (paymentMethodTokenData, primerHandler) => {
1321
+ try {
1322
+ this.onLoaderChangeWithRace(true);
1323
+ this._setState('processing');
1324
+ const radarSessionId = await this.radarSessionId;
1325
+ const paymentResponse = await this.apiClient.createPayment({
1326
+ orderId: this.orderId,
1327
+ paymentMethodToken: paymentMethodTokenData.token,
1328
+ clientMetadata: {
1329
+ radarSessionId,
1330
+ },
1331
+ });
1332
+ const result = this.apiClient.processPaymentResponse(paymentResponse);
1333
+ await this._processPaymentResult(result, primerHandler);
1334
+ }
1335
+ catch (error) {
1336
+ this._setState('error');
1337
+ this.emit(EVENTS.PURCHASE_FAILURE, new Error(error.message || 'Payment processing failed'));
1338
+ primerHandler.handleFailure(error.message || 'Payment processing failed');
1339
+ }
1340
+ finally {
1341
+ this.onLoaderChangeWithRace(false);
1342
+ this._setState('ready');
1343
+ }
103
1344
  };
104
- this.onPurchaseFailure = (error) => {
105
- if (this.currentPurchaseMethod) {
106
- this.onError(error, this.currentPurchaseMethod);
1345
+ this.handleResumeSuccess = async (resumeTokenData, primerHandler) => {
1346
+ try {
1347
+ this.onLoaderChangeWithRace(true);
1348
+ this._setState('processing');
1349
+ const resumeResponse = await this.apiClient.resumePayment({
1350
+ orderId: this.orderId,
1351
+ resumeToken: resumeTokenData.resumeToken,
1352
+ });
1353
+ const result = this.apiClient.processPaymentResponse(resumeResponse);
1354
+ await this._processPaymentResult(result, primerHandler);
107
1355
  }
108
- this.currentPurchaseMethod = null;
1356
+ catch (error) {
1357
+ this._setState('error');
1358
+ this.emit(EVENTS.PURCHASE_FAILURE, new Error(error.message || 'Payment processing failed'));
1359
+ primerHandler.handleFailure(error.message || 'Payment processing failed');
1360
+ }
1361
+ finally {
1362
+ this.emit(EVENTS.PURCHASE_COMPLETED);
1363
+ this.onLoaderChangeWithRace(false);
1364
+ this._setState('ready');
1365
+ }
1366
+ };
1367
+ this.handleMethodsAvailable = (methods) => {
1368
+ this.emit(EVENTS.METHODS_AVAILABLE, methods);
109
1369
  };
110
- this.onPurchaseCompleted = () => {
111
- this.currentPurchaseMethod = null;
1370
+ this.onLoaderChangeWithRace = (state) => {
1371
+ const isLoading = !!(state ? ++this.counter : --this.counter);
1372
+ this.primerWrapper.disableButtons(isLoading);
1373
+ this.emit(EVENTS.LOADER_CHANGE, isLoading);
112
1374
  };
113
- this.containerSelector = checkoutConfig.container;
114
- this.paymentMethodOrder = checkoutConfig.paymentMethodOrder;
115
- const containerEl = document.querySelector(this.containerSelector);
116
- if (!containerEl) {
117
- throw new Error(`Container element not found for selector: ${this.containerSelector}`);
1375
+ this.id = generateId('checkout_');
1376
+ this.orgId = config.orgId;
1377
+ this.baseUrl = config.baseUrl;
1378
+ this.region = config.region;
1379
+ this.checkoutConfig = { ...config.checkoutConfig };
1380
+ this.callbacks = {
1381
+ onSuccess: this.checkoutConfig.onSuccess,
1382
+ onError: this.checkoutConfig.onError,
1383
+ onStatusChange: this.checkoutConfig.onStatusChange,
1384
+ onDestroy: this.checkoutConfig.onDestroy,
1385
+ };
1386
+ delete this.checkoutConfig?.onSuccess;
1387
+ delete this.checkoutConfig?.onError;
1388
+ delete this.checkoutConfig?.onStatusChange;
1389
+ delete this.checkoutConfig?.onDestroy;
1390
+ this.state = 'initializing';
1391
+ this.orderId = null;
1392
+ this.clientToken = null;
1393
+ this.primerWrapper = new PrimerWrapper();
1394
+ this.isDestroyed = false;
1395
+ this._setupCallbackBridges();
1396
+ }
1397
+ _setupCallbackBridges() {
1398
+ if (this.callbacks.onSuccess) {
1399
+ this.on(EVENTS.SUCCESS, this.callbacks.onSuccess);
1400
+ }
1401
+ if (this.callbacks.onError) {
1402
+ this.on(EVENTS.ERROR, this.callbacks.onError);
1403
+ }
1404
+ if (this.callbacks.onStatusChange) {
1405
+ this.on(EVENTS.STATUS_CHANGE, this.callbacks.onStatusChange);
1406
+ }
1407
+ if (this.callbacks.onDestroy) {
1408
+ this.on(EVENTS.DESTROY, this.callbacks.onDestroy);
118
1409
  }
119
- this.containerEl = containerEl;
120
- this.checkoutConfig = checkoutConfig;
121
1410
  }
122
- initAccordion() {
123
- const paymentMethodCards = this.containerEl.querySelectorAll('.ff-payment-method-card');
124
- const radioButtons = this.containerEl.querySelectorAll('.ff-payment-method-radio');
125
- const handleAccordion = (checkedRadio) => {
126
- paymentMethodCards.forEach(card => {
127
- const radio = card.querySelector('.ff-payment-method-radio');
128
- if (radio === checkedRadio && radio?.checked) {
129
- card.classList.add('expanded');
130
- }
131
- else {
132
- card.classList.remove('expanded');
133
- }
134
- });
1411
+ removeAllListeners() {
1412
+ return super.removeAllListeners();
1413
+ }
1414
+ async initialize() {
1415
+ try {
1416
+ this.showInitializingLoader();
1417
+ this._setState('initializing');
1418
+ await this.createSession();
1419
+ await this._initializePrimerCheckout();
1420
+ this._setState('ready');
1421
+ this.checkoutConfig?.onInitialized?.();
1422
+ return this;
1423
+ }
1424
+ catch (error) {
1425
+ this._setState('error');
1426
+ renderError(this.checkoutConfig.container, error?.response?.req_id);
1427
+ this.emit(EVENTS.ERROR, error);
1428
+ throw error;
1429
+ }
1430
+ finally {
1431
+ this.hideInitializingLoader();
1432
+ }
1433
+ }
1434
+ async createSession() {
1435
+ this.apiClient = new APIClient({
1436
+ baseUrl: this.baseUrl || DEFAULTS.BASE_URL,
1437
+ orgId: this.orgId,
1438
+ timeout: DEFAULTS.REQUEST_TIMEOUT,
1439
+ retryAttempts: DEFAULTS.RETRY_ATTEMPTS,
1440
+ });
1441
+ const sessionParams = {
1442
+ priceId: this.checkoutConfig.priceId,
1443
+ externalId: this.checkoutConfig.customer.externalId,
1444
+ email: this.checkoutConfig.customer.email,
1445
+ region: this.region || DEFAULTS.REGION,
1446
+ clientMetadata: this.checkoutConfig.clientMetadata,
1447
+ countryCode: this.checkoutConfig.customer.countryCode,
135
1448
  };
136
- const checkedRadio = Array.from(radioButtons).find(radio => this.availableMethods.includes(radio.value));
137
- if (!checkedRadio) {
138
- throw new Error('Default skin accordion initialization error: No radio button found');
139
- }
140
- setTimeout(() => {
141
- checkedRadio.checked = true;
142
- handleAccordion(checkedRadio);
143
- }, 0);
144
- radioButtons.forEach(radio => {
145
- radio.addEventListener('change', () => {
146
- if (radio.checked) {
147
- handleAccordion(radio);
1449
+ const cacheKey = [
1450
+ this.orgId,
1451
+ this.checkoutConfig.priceId,
1452
+ this.checkoutConfig.customer.externalId,
1453
+ this.checkoutConfig.customer.email,
1454
+ ].join('-');
1455
+ let sessionResponse;
1456
+ // Return cached response if payload hasn't changed
1457
+ const cachedResponse = CheckoutInstance.sessionCache.get(cacheKey);
1458
+ if (cachedResponse) {
1459
+ sessionResponse = await cachedResponse;
1460
+ }
1461
+ else {
1462
+ const sessionRequest = this.apiClient
1463
+ .createClientSession(sessionParams)
1464
+ .then(response => {
1465
+ if (response.data?.stripe_public_key) {
1466
+ loadStripe(response.data?.stripe_public_key).then(stripe => {
1467
+ this.radarSessionId = stripe
1468
+ .createRadarSession()
1469
+ .then(session => session?.radarSession?.id)
1470
+ .catch(() => '');
1471
+ });
148
1472
  }
1473
+ return response;
149
1474
  });
150
- });
1475
+ // Cache the successful response
1476
+ CheckoutInstance.sessionCache.set(cacheKey, sessionRequest);
1477
+ sessionResponse = await sessionRequest;
1478
+ }
1479
+ const sessionData = this.apiClient.processSessionResponse(sessionResponse);
1480
+ this.orderId = sessionData.orderId;
1481
+ this.clientToken = sessionData.clientToken;
151
1482
  }
152
- wireCardInputs() {
153
- this.cardInstance.wireCardInputs();
154
- const button = this.containerEl.querySelector('#submitButton');
155
- if (!button) {
156
- throw new Error('One or more card input elements are missing in the default skin');
1483
+ convertCardSelectorsToElements(selectors, container) {
1484
+ const cardNumber = container.querySelector(selectors.cardNumber);
1485
+ const expiryDate = container.querySelector(selectors.expiryDate);
1486
+ const cvv = container.querySelector(selectors.cvv);
1487
+ const cardholderName = container.querySelector(selectors.cardholderName);
1488
+ const button = container.querySelector(selectors.button);
1489
+ if (!cardNumber || !expiryDate || !cvv || !button) {
1490
+ throw new CheckoutError('Required card input elements not found in container');
157
1491
  }
158
- this.cardInputElements = {
159
- ...this.cardInstance.getCardInputElements(),
1492
+ return {
1493
+ cardNumber,
1494
+ expiryDate,
1495
+ cvv,
1496
+ cardholderName,
160
1497
  button,
161
1498
  };
162
1499
  }
163
- async init() {
164
- this.containerEl.insertAdjacentHTML('beforeend', template);
165
- const paymentMethodContainers = this.containerEl.querySelector('#ff-payment-method-containers');
166
- this.paymentMethodOrder.forEach(paymentMethod => {
167
- paymentMethodContainers.insertAdjacentHTML('beforeend', paymentMethodTemplates[paymentMethod]);
1500
+ convertPaymentButtonSelectorsToElements(selectors) {
1501
+ const paypal = document.querySelector(selectors.paypal);
1502
+ const googlePay = document.querySelector(selectors.googlePay);
1503
+ const applePay = document.querySelector(selectors.applePay);
1504
+ if (!paypal || !googlePay || !applePay) {
1505
+ throw new CheckoutError('Required payment button elements not found in container');
1506
+ }
1507
+ return {
1508
+ paypal,
1509
+ googlePay,
1510
+ applePay,
1511
+ };
1512
+ }
1513
+ async _initializePrimerCheckout() {
1514
+ // Get container element
1515
+ const containerElement = this.getContainer();
1516
+ if (!containerElement) {
1517
+ throw new CheckoutError(`Checkout container not found: ${this.checkoutConfig.container}`);
1518
+ }
1519
+ // Get selectors (either from config or default skin)
1520
+ let cardElements;
1521
+ let paymentButtonElements;
1522
+ let checkoutOptions;
1523
+ if (!this.checkoutConfig.cardSelectors ||
1524
+ !this.checkoutConfig.paymentButtonSelectors) {
1525
+ this.checkoutConfig.paymentMethodOrder =
1526
+ this.checkoutConfig.paymentMethodOrder || DEFAULT_PAYMENT_METHOD_ORDER;
1527
+ const defaultSkinCheckoutOptions = await this.getDefaultSkinCheckoutOptions();
1528
+ if (!defaultSkinCheckoutOptions.cardElements ||
1529
+ !defaultSkinCheckoutOptions.paymentButtonElements) {
1530
+ throw new CheckoutError('Default skin must provide cardSelectors and paymentButtonSelectors');
1531
+ }
1532
+ cardElements =
1533
+ defaultSkinCheckoutOptions.cardElements;
1534
+ paymentButtonElements = defaultSkinCheckoutOptions.paymentButtonElements;
1535
+ checkoutOptions = this.getCheckoutOptions(defaultSkinCheckoutOptions);
1536
+ }
1537
+ else {
1538
+ if (this.checkoutConfig.paymentMethodOrder) {
1539
+ // eslint-disable-next-line no-console
1540
+ console.warn('paymentMethodOrder is using only for default skin and will be ignored if you are using custom checkout');
1541
+ }
1542
+ cardElements = this.convertCardSelectorsToElements(this.checkoutConfig.cardSelectors, containerElement);
1543
+ paymentButtonElements = this.convertPaymentButtonSelectorsToElements(this.checkoutConfig.paymentButtonSelectors);
1544
+ checkoutOptions = this.getCheckoutOptions({});
1545
+ }
1546
+ await this.primerWrapper.renderCheckout(this.clientToken, checkoutOptions, {
1547
+ container: containerElement,
1548
+ cardElements,
1549
+ paymentButtonElements,
1550
+ onSubmit: this.handleSubmit,
1551
+ onInputChange: this.handleInputChange,
1552
+ onMethodRender: this.handleMethodRender,
1553
+ onMethodsAvailable: this.handleMethodsAvailable,
1554
+ onMethodRenderError: this.handleMethodRenderError,
168
1555
  });
169
- this.cardInstance = new index$1.default(document.querySelector('#cardForm'), this.checkoutConfig);
170
- this.cardInstance.init();
171
- this.wireCardInputs();
172
1556
  }
173
- renderCardForm() {
174
- // Card form is part of the base template; no-op for default skin.
1557
+ async _processPaymentResult(result, primerHandler) {
1558
+ if (result.orderId) {
1559
+ this.orderId = result.orderId;
1560
+ }
1561
+ switch (result.type) {
1562
+ case 'success':
1563
+ this._setState('completed');
1564
+ this.emit(EVENTS.SUCCESS, {
1565
+ orderId: result.orderId,
1566
+ status: result.status,
1567
+ });
1568
+ primerHandler.handleSuccess();
1569
+ break;
1570
+ case 'action_required':
1571
+ this._setState('action_required');
1572
+ this.clientToken = result.clientToken;
1573
+ primerHandler.continueWithNewClientToken(result.clientToken);
1574
+ break;
1575
+ case 'processing':
1576
+ this._setState('processing');
1577
+ setTimeout(() => {
1578
+ primerHandler.handleFailure('Payment is still processing. Please check back later.');
1579
+ }, 30000);
1580
+ break;
1581
+ default:
1582
+ throw new CheckoutError(`Unknown payment result type: ${result.type}`);
1583
+ }
175
1584
  }
176
- getCardInputElements() {
1585
+ getCheckoutOptions(options) {
1586
+ let wasPaymentProcessedStarted = false;
177
1587
  return {
178
- ...this.cardInstance.getCardInputElements(),
179
- button: this.cardInputElements.button,
1588
+ ...this.checkoutConfig,
1589
+ ...options,
1590
+ onTokenizeSuccess: this.handleTokenizeSuccess,
1591
+ onResumeSuccess: this.handleResumeSuccess,
1592
+ onResumeError: error => {
1593
+ if (error.stack?.includes('PROCESSOR_3DS') &&
1594
+ error.code === 'RESUME_ERROR' &&
1595
+ error.message?.includes('fetch resume key')) {
1596
+ // Ignore 3DS close error, because it is not understandable by user
1597
+ return;
1598
+ }
1599
+ this.emit(EVENTS.PURCHASE_FAILURE, error);
1600
+ },
1601
+ onCheckoutFail: error => {
1602
+ this.emit(EVENTS.PURCHASE_FAILURE, error);
1603
+ },
1604
+ onTokenizeError: error => {
1605
+ this.emit(EVENTS.PURCHASE_FAILURE, error);
1606
+ },
1607
+ onTokenizeShouldStart: data => {
1608
+ wasPaymentProcessedStarted = true;
1609
+ this.emit(EVENTS.ERROR, undefined);
1610
+ this.emit(EVENTS.START_PURCHASE, data.paymentMethodType);
1611
+ return true;
1612
+ },
1613
+ onPaymentMethodAction: action => {
1614
+ switch (action) {
1615
+ case 'PAYMENT_METHOD_SELECTED':
1616
+ this.emit(EVENTS.ERROR, undefined);
1617
+ break;
1618
+ case 'PAYMENT_METHOD_UNSELECTED':
1619
+ if (!wasPaymentProcessedStarted) {
1620
+ this.emit(EVENTS.PURCHASE_CANCELLED);
1621
+ }
1622
+ wasPaymentProcessedStarted = false;
1623
+ break;
1624
+ }
1625
+ },
180
1626
  };
181
1627
  }
182
- getPaymentButtonElements() {
1628
+ async updatePrice(newPriceId, clientMetadata) {
1629
+ this._ensureNotDestroyed();
1630
+ requireString(newPriceId, 'priceId');
1631
+ if (this.state === 'processing') {
1632
+ throw new CheckoutError('Cannot update price while payment is processing');
1633
+ }
1634
+ try {
1635
+ this._setState('updating');
1636
+ // Invalidate session cache
1637
+ CheckoutInstance.sessionCache.clear();
1638
+ await this.apiClient.updateClientSession({
1639
+ orderId: this.orderId,
1640
+ clientToken: this.clientToken,
1641
+ priceId: newPriceId,
1642
+ clientMetadata,
1643
+ });
1644
+ this.checkoutConfig.priceId = newPriceId;
1645
+ this._setState('ready');
1646
+ }
1647
+ catch (error) {
1648
+ this._setState('error');
1649
+ this.emit(EVENTS.ERROR, error);
1650
+ throw error;
1651
+ }
1652
+ }
1653
+ getStatus() {
183
1654
  return {
184
- paypal: this.containerEl.querySelector('#paypalButton'),
185
- googlePay: this.containerEl.querySelector('#googlePayButton'),
186
- applePay: this.containerEl.querySelector('#applePayButton'),
1655
+ id: this.id,
1656
+ state: this.state,
1657
+ orderId: this.orderId,
1658
+ priceId: this.checkoutConfig.priceId,
1659
+ isDestroyed: this.isDestroyed,
187
1660
  };
188
1661
  }
189
- getCheckoutOptions() {
1662
+ async destroy() {
1663
+ if (this.isDestroyed)
1664
+ return;
1665
+ try {
1666
+ CheckoutInstance.sessionCache.clear();
1667
+ await this.primerWrapper.destroy();
1668
+ this._setState('destroyed');
1669
+ this.orderId = null;
1670
+ this.clientToken = null;
1671
+ this.isDestroyed = true;
1672
+ this.emit(EVENTS.DESTROY);
1673
+ this.removeAllListeners();
1674
+ }
1675
+ catch (error) {
1676
+ // eslint-disable-next-line no-console
1677
+ console.warn('Error during checkout cleanup:', error);
1678
+ }
1679
+ }
1680
+ _setState(newState) {
1681
+ if (this.state !== newState) {
1682
+ const oldState = this.state;
1683
+ this.state = newState;
1684
+ this.emit(EVENTS.STATUS_CHANGE, newState, oldState);
1685
+ }
1686
+ }
1687
+ _ensureNotDestroyed() {
1688
+ if (this.isDestroyed) {
1689
+ throw new CheckoutError('Checkout instance has been destroyed');
1690
+ }
1691
+ }
1692
+ getContainer() {
1693
+ return document.querySelector(this.checkoutConfig.container);
1694
+ }
1695
+ isInState(state) {
1696
+ return this.state === state;
1697
+ }
1698
+ isReady() {
1699
+ return this.state === 'ready' && !this.isDestroyed;
1700
+ }
1701
+ isProcessing() {
1702
+ return ['processing', 'action_required'].includes(this.state);
1703
+ }
1704
+ // Creates containers to render hosted inputs with labels and error messages,
1705
+ // a card holder input with label and error, and a submit button.
1706
+ async getDefaultSkinCheckoutOptions() {
1707
+ const skinFactory = (await Promise.resolve().then(function () { return require('./chunk-index.cjs2.js'); }))
1708
+ .default;
1709
+ const skin = await skinFactory(this.checkoutConfig);
1710
+ this.on(EVENTS.INPUT_ERROR, skin.onInputError);
1711
+ this.on(EVENTS.STATUS_CHANGE, skin.onStatusChange);
1712
+ this.on(EVENTS.ERROR, (error) => skin.onError(error));
1713
+ this.on(EVENTS.LOADER_CHANGE, skin.onLoaderChange);
1714
+ this.on(EVENTS.DESTROY, skin.onDestroy);
1715
+ this.on(EVENTS.SUCCESS, skin.onSuccess);
1716
+ this.on(EVENTS.START_PURCHASE, skin.onStartPurchase);
1717
+ this.on(EVENTS.PURCHASE_FAILURE, skin.onPurchaseFailure);
1718
+ this.on(EVENTS.PURCHASE_COMPLETED, skin.onPurchaseCompleted);
1719
+ this.on(EVENTS.METHODS_AVAILABLE, skin.onMethodsAvailable);
1720
+ this.on(EVENTS.METHODS_AVAILABLE, this.hideInitializingLoader);
1721
+ return skin.getCheckoutOptions();
1722
+ }
1723
+ async getCardDefaultSkinCheckoutOptions(node) {
1724
+ const CardSkin = (await Promise.resolve().then(function () { return require('./chunk-index.cjs3.js'); })).default;
1725
+ const skin = new CardSkin(node, this.checkoutConfig);
1726
+ skin.init();
1727
+ this.on(EVENTS.INPUT_ERROR, skin.onInputError);
1728
+ this.on(EVENTS.METHOD_RENDER, skin.onMethodRender);
1729
+ this.on(EVENTS.SUCCESS, skin.onDestroy);
1730
+ this.on(EVENTS.DESTROY, skin.onDestroy);
1731
+ return skin.getCheckoutOptions();
1732
+ }
1733
+ showInitializingLoader() {
1734
+ renderLoader(this.checkoutConfig.container);
1735
+ }
1736
+ hideInitializingLoader() {
1737
+ hideLoader();
1738
+ }
1739
+ async initMethod(method, element, callbacks) {
1740
+ this._ensureNotDestroyed();
1741
+ if (!this.isReady()) {
1742
+ await this.createSession();
1743
+ }
1744
+ if (callbacks.onRenderSuccess) {
1745
+ this.on(EVENTS.METHOD_RENDER, callbacks.onRenderSuccess);
1746
+ }
1747
+ if (callbacks.onRenderError) {
1748
+ this.on(EVENTS.METHOD_RENDER_ERROR, callbacks.onRenderError);
1749
+ }
1750
+ if (callbacks.onLoaderChange) {
1751
+ this.on(EVENTS.LOADER_CHANGE, callbacks.onLoaderChange);
1752
+ }
1753
+ if (callbacks.onPaymentSuccess) {
1754
+ this.on(EVENTS.SUCCESS, callbacks.onPaymentSuccess);
1755
+ }
1756
+ if (callbacks.onPaymentFail) {
1757
+ this.on(EVENTS.PURCHASE_FAILURE, callbacks.onPaymentFail);
1758
+ }
1759
+ if (callbacks.onPaymentCancel) {
1760
+ this.on(EVENTS.PURCHASE_CANCELLED, callbacks.onPaymentCancel);
1761
+ }
1762
+ if (callbacks.onErrorMessageChange) {
1763
+ this.on(EVENTS.ERROR, callbacks.onErrorMessageChange);
1764
+ }
1765
+ if (callbacks.onPaymentStarted) {
1766
+ this.on(EVENTS.START_PURCHASE, callbacks.onPaymentStarted);
1767
+ }
1768
+ if (callbacks.onMethodsAvailable) {
1769
+ this.on(EVENTS.METHODS_AVAILABLE, callbacks.onMethodsAvailable);
1770
+ }
1771
+ let checkoutOptions = this.getCheckoutOptions({});
1772
+ let methodOptions = {
1773
+ onMethodRender: this.handleMethodRender,
1774
+ onMethodRenderError: this.handleMethodRenderError,
1775
+ };
1776
+ if (method === exports.PaymentMethod.PAYMENT_CARD) {
1777
+ const cardDefaultOptions = await this.getCardDefaultSkinCheckoutOptions(element);
1778
+ checkoutOptions = this.getCheckoutOptions({
1779
+ ...cardDefaultOptions,
1780
+ });
1781
+ methodOptions = {
1782
+ cardElements: cardDefaultOptions.cardElements,
1783
+ onSubmit: this.handleSubmit,
1784
+ onInputChange: this.handleInputChange,
1785
+ onMethodRender: this.handleMethodRender,
1786
+ onMethodRenderError: this.handleMethodRenderError,
1787
+ };
1788
+ }
1789
+ await this.primerWrapper.initializeHeadlessCheckout(this.clientToken, checkoutOptions);
1790
+ const methodInterface = await this.primerWrapper.initMethod(method, element, methodOptions);
190
1791
  return {
191
- ...this.cardInstance.getCheckoutOptions(),
192
- cardElements: this.getCardInputElements(),
193
- paymentButtonElements: this.getPaymentButtonElements(),
194
- applePay: {
195
- buttonStyle: 'black',
1792
+ ...methodInterface,
1793
+ destroy: async () => {
1794
+ await methodInterface.destroy();
1795
+ await this.destroy();
196
1796
  },
197
- paypal: {
198
- buttonColor: 'gold',
199
- buttonShape: 'pill',
200
- buttonLabel: 'pay',
201
- buttonSize: 'large',
202
- buttonHeight: 54,
1797
+ };
1798
+ }
1799
+ }
1800
+ CheckoutInstance.sessionCache = new Map();
1801
+
1802
+ function getErrorImage(orgId, options) {
1803
+ const params = new URLSearchParams({
1804
+ message: options.message,
1805
+ code: options.code,
1806
+ timestamp: Date.now().toString(),
1807
+ sdk_version: SDK_VERSION,
1808
+ });
1809
+ if (options.req_id) {
1810
+ params.append('req_id', options.req_id);
1811
+ }
1812
+ const url = `https://billing.funnelfox.com/sdk_report/${encodeURIComponent(orgId)}/crash?${params.toString()}`;
1813
+ const img = new Image();
1814
+ img.src = url;
1815
+ img.style.display = 'none';
1816
+ document.body.appendChild(img);
1817
+ }
1818
+
1819
+ /**
1820
+ * @fileoverview Public API with configuration and orchestration logic
1821
+ */
1822
+ let defaultConfig = null;
1823
+ function configure(config) {
1824
+ defaultConfig = config;
1825
+ }
1826
+ function resolveConfig(options, functionName) {
1827
+ const { orgId, apiConfig } = options || {};
1828
+ const finalOrgId = orgId || defaultConfig?.orgId;
1829
+ if (!finalOrgId) {
1830
+ throw new Error(`orgId is required. Pass it to ${functionName}() or call configure() first.`);
1831
+ }
1832
+ const finalBaseUrl = apiConfig?.baseUrl || defaultConfig?.baseUrl || DEFAULTS.BASE_URL;
1833
+ const finalRegion = apiConfig?.region || defaultConfig?.region || DEFAULTS.REGION;
1834
+ return {
1835
+ orgId: finalOrgId,
1836
+ baseUrl: finalBaseUrl,
1837
+ region: finalRegion,
1838
+ };
1839
+ }
1840
+ async function createCheckout(options) {
1841
+ try {
1842
+ const { ...checkoutConfig } = options;
1843
+ // Ensure Primer SDK is loaded before creating checkout
1844
+ const primerWrapper = new PrimerWrapper();
1845
+ await primerWrapper.ensurePrimerLoaded();
1846
+ const config = resolveConfig(options, 'createCheckout');
1847
+ const checkout = new CheckoutInstance({
1848
+ ...config,
1849
+ checkoutConfig: {
1850
+ ...checkoutConfig,
203
1851
  },
204
- googlePay: {
205
- buttonColor: 'black',
206
- buttonSizeMode: 'fill',
207
- buttonType: 'pay',
1852
+ });
1853
+ await checkout.initialize();
1854
+ return checkout;
1855
+ }
1856
+ catch (error) {
1857
+ getErrorImage(options.orgId, {
1858
+ message: error.message,
1859
+ code: error.code,
1860
+ req_id: error?.response?.req_id,
1861
+ });
1862
+ throw error;
1863
+ }
1864
+ }
1865
+ async function createClientSession(params) {
1866
+ const { priceId, externalId, email, clientMetadata, countryCode } = params;
1867
+ const config = resolveConfig(params, 'createClientSession');
1868
+ const apiClient = new APIClient({
1869
+ baseUrl: config.baseUrl,
1870
+ orgId: config.orgId,
1871
+ timeout: DEFAULTS.REQUEST_TIMEOUT,
1872
+ retryAttempts: DEFAULTS.RETRY_ATTEMPTS,
1873
+ });
1874
+ const sessionResponse = await apiClient.createClientSession({
1875
+ priceId,
1876
+ externalId,
1877
+ email,
1878
+ region: config.region,
1879
+ clientMetadata,
1880
+ countryCode,
1881
+ });
1882
+ return apiClient.processSessionResponse(sessionResponse);
1883
+ }
1884
+ async function silentPurchase(options) {
1885
+ const { priceId, externalId, clientMetadata, orgId, baseUrl } = options;
1886
+ const apiClient = new APIClient({
1887
+ baseUrl: baseUrl,
1888
+ orgId: orgId,
1889
+ timeout: DEFAULTS.REQUEST_TIMEOUT,
1890
+ retryAttempts: DEFAULTS.RETRY_ATTEMPTS,
1891
+ });
1892
+ try {
1893
+ const response = await apiClient.oneClick({
1894
+ pp_ident: priceId,
1895
+ external_id: externalId,
1896
+ client_metadata: clientMetadata,
1897
+ });
1898
+ if (response.status !== 'success' &&
1899
+ response.error.some(({ code }) => code === 'double_purchase')) {
1900
+ throw new APIError('This product was already purchased');
1901
+ }
1902
+ else if (response.status !== 'success') {
1903
+ return false;
1904
+ }
1905
+ return true;
1906
+ }
1907
+ catch (error) {
1908
+ getErrorImage(orgId, {
1909
+ message: error.message,
1910
+ code: error.code,
1911
+ req_id: error?.response?.req_id,
1912
+ });
1913
+ throw error;
1914
+ }
1915
+ }
1916
+ async function initMethod(method, element, options) {
1917
+ try {
1918
+ const checkoutInstance = new CheckoutInstance({
1919
+ orgId: options.orgId,
1920
+ baseUrl: options.baseUrl,
1921
+ checkoutConfig: {
1922
+ priceId: options.priceId,
1923
+ customer: {
1924
+ externalId: options.externalId,
1925
+ email: options.email,
1926
+ },
1927
+ container: '',
1928
+ clientMetadata: options.meta,
1929
+ card: options.card,
1930
+ style: options.style,
1931
+ applePay: {
1932
+ ...DEFAULT_BUTTONS_STYLES[exports.PaymentMethod.APPLE_PAY],
1933
+ ...(options.applePay || {}),
1934
+ },
1935
+ paypal: {
1936
+ ...DEFAULT_BUTTONS_STYLES[exports.PaymentMethod.PAYPAL],
1937
+ ...(options.paypal || {}),
1938
+ },
1939
+ googlePay: {
1940
+ ...DEFAULT_BUTTONS_STYLES[exports.PaymentMethod.GOOGLE_PAY],
1941
+ ...(options.googlePay || {}),
1942
+ },
208
1943
  },
209
- };
1944
+ });
1945
+ return checkoutInstance.initMethod(method, element, {
1946
+ onRenderSuccess: options.onRenderSuccess,
1947
+ onRenderError: options.onRenderError,
1948
+ onLoaderChange: options.onLoaderChange,
1949
+ onPaymentSuccess: options.onPaymentSuccess,
1950
+ onPaymentFail: options.onPaymentFail,
1951
+ onPaymentCancel: options.onPaymentCancel,
1952
+ onErrorMessageChange: options.onErrorMessageChange,
1953
+ onPaymentStarted: options.onPaymentStarted,
1954
+ onMethodsAvailable: options.onMethodsAvailable,
1955
+ });
1956
+ }
1957
+ catch (error) {
1958
+ getErrorImage(options.orgId, {
1959
+ message: error.message,
1960
+ code: error.code,
1961
+ req_id: error?.response?.req_id,
1962
+ });
1963
+ throw error;
210
1964
  }
211
1965
  }
212
- const createDefaultSkin = async (checkoutConfig) => {
213
- const skin = new DefaultSkin(checkoutConfig);
214
- await skin['init']();
215
- return skin;
1966
+
1967
+ /**
1968
+ * @fileoverview Main entry point for @funnelfox/billing
1969
+ */
1970
+ const Billing = {
1971
+ configure: configure,
1972
+ createCheckout: createCheckout,
1973
+ createClientSession: createClientSession,
1974
+ initMethod: initMethod,
1975
+ silentPurchase: silentPurchase,
216
1976
  };
1977
+ if (typeof window !== 'undefined') {
1978
+ window.Billing = Billing;
1979
+ }
217
1980
 
218
- exports.default = createDefaultSkin;
1981
+ exports.APIError = APIError;
1982
+ exports.Billing = Billing;
1983
+ exports.CHECKOUT_STATES = CHECKOUT_STATES;
1984
+ exports.CheckoutError = CheckoutError;
1985
+ exports.ConfigurationError = ConfigurationError;
1986
+ exports.DEFAULTS = DEFAULTS;
1987
+ exports.DEFAULT_BUTTONS_STYLES = DEFAULT_BUTTONS_STYLES;
1988
+ exports.ERROR_CODES = ERROR_CODES;
1989
+ exports.EVENTS = EVENTS;
1990
+ exports.FunnefoxSDKError = FunnefoxSDKError;
1991
+ exports.NetworkError = NetworkError;
1992
+ exports.PrimerError = PrimerError;
1993
+ exports.SDK_VERSION = SDK_VERSION;
1994
+ exports.ValidationError = ValidationError;
1995
+ exports.configure = configure;
1996
+ exports.createCheckout = createCheckout;
1997
+ exports.createClientSession = createClientSession;