flintn-checkout 0.0.10 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -111,7 +111,7 @@ Do **not** set a fixed `height` on the container — it will leave empty space b
111
111
  | Option | Type | Required | Default | Description |
112
112
  |--------|------|----------|---------|-------------|
113
113
  | `config` | `FlintNConfig` | Yes | — | Checkout configuration |
114
- | `onPayment` | `(result: PaymentResult) => void` | No | — | Card payment result callback |
114
+ | `onPayment` | `(result: PaymentResult) => void` | No | — | Payment result callback (success, error, cancelled) |
115
115
  | `onReady` | `() => void` | No | — | Widget loaded and ready |
116
116
  | `onError` | `(error: PaymentError) => void` | No | — | SDK initialization error |
117
117
  | `debug` | `boolean` | No | `false` | Enable console debug logs |
@@ -500,15 +500,6 @@ useFlintNFields({
500
500
  });
501
501
  ```
502
502
 
503
- ## Test Cards
504
-
505
- | Card Number | Result |
506
- |-------------|--------|
507
- | `4111 1111 1111 1111` | Success |
508
- | `4000 0000 0000 0002` | Declined |
509
-
510
- Use any future expiry date and any 3-digit CVV.
511
-
512
503
  ## Browser Support
513
504
 
514
505
  - Chrome (latest)
package/dist/index.d.mts CHANGED
@@ -2,7 +2,6 @@ declare const EventType: {
2
2
  readonly WIDGET_READY: "WIDGET_READY";
3
3
  readonly WIDGET_CONFIG: "WIDGET_CONFIG";
4
4
  readonly PAYMENT: "PAYMENT";
5
- readonly EXPRESS_PAYMENT: "EXPRESS_PAYMENT";
6
5
  readonly PAYMENT_SUCCESS: "PAYMENT_SUCCESS";
7
6
  readonly PAYMENT_ERROR: "PAYMENT_ERROR";
8
7
  readonly PAYMENT_CANCELLED: "PAYMENT_CANCELLED";
@@ -62,7 +61,6 @@ interface FlintNPaymentOptions {
62
61
  origin?: string;
63
62
  config: FlintNConfig;
64
63
  onPayment?: (result: PaymentResult) => void;
65
- onExpressPayment?: (result: PaymentResult) => void;
66
64
  onReady?: () => void;
67
65
  onError?: (error: PaymentError) => void;
68
66
  debug?: boolean;
@@ -147,6 +145,7 @@ interface FlintNFieldInternalCallbacks {
147
145
  onFocus: (fieldType: TFieldType) => void;
148
146
  onBlur: (fieldType: TFieldType, state: FieldState) => void;
149
147
  onHeight: (fieldType: TFieldType, height: number) => void;
148
+ onError?: (err: Error) => void;
150
149
  }
151
150
  interface FlintNField {
152
151
  mount(selector: string | HTMLElement): void;
package/dist/index.d.ts CHANGED
@@ -2,7 +2,6 @@ declare const EventType: {
2
2
  readonly WIDGET_READY: "WIDGET_READY";
3
3
  readonly WIDGET_CONFIG: "WIDGET_CONFIG";
4
4
  readonly PAYMENT: "PAYMENT";
5
- readonly EXPRESS_PAYMENT: "EXPRESS_PAYMENT";
6
5
  readonly PAYMENT_SUCCESS: "PAYMENT_SUCCESS";
7
6
  readonly PAYMENT_ERROR: "PAYMENT_ERROR";
8
7
  readonly PAYMENT_CANCELLED: "PAYMENT_CANCELLED";
@@ -62,7 +61,6 @@ interface FlintNPaymentOptions {
62
61
  origin?: string;
63
62
  config: FlintNConfig;
64
63
  onPayment?: (result: PaymentResult) => void;
65
- onExpressPayment?: (result: PaymentResult) => void;
66
64
  onReady?: () => void;
67
65
  onError?: (error: PaymentError) => void;
68
66
  debug?: boolean;
@@ -147,6 +145,7 @@ interface FlintNFieldInternalCallbacks {
147
145
  onFocus: (fieldType: TFieldType) => void;
148
146
  onBlur: (fieldType: TFieldType, state: FieldState) => void;
149
147
  onHeight: (fieldType: TFieldType, height: number) => void;
148
+ onError?: (err: Error) => void;
150
149
  }
151
150
  interface FlintNField {
152
151
  mount(selector: string | HTMLElement): void;
package/dist/index.js CHANGED
@@ -40,7 +40,6 @@ var EventType = {
40
40
  WIDGET_READY: "WIDGET_READY",
41
41
  WIDGET_CONFIG: "WIDGET_CONFIG",
42
42
  PAYMENT: "PAYMENT",
43
- EXPRESS_PAYMENT: "EXPRESS_PAYMENT",
44
43
  PAYMENT_SUCCESS: "PAYMENT_SUCCESS",
45
44
  PAYMENT_ERROR: "PAYMENT_ERROR",
46
45
  PAYMENT_CANCELLED: "PAYMENT_CANCELLED",
@@ -112,15 +111,38 @@ var sanitizeToken = (token) => {
112
111
  // src/flintn-payment.ts
113
112
  var DEFAULT_ORIGIN = "https://pay.flintn.com";
114
113
  function createFlintNPayment(options) {
115
- validateConfig(options.config);
116
- const origin = parseOrigin(options.origin || DEFAULT_ORIGIN);
114
+ const log = (...args) => {
115
+ if (options.debug) console.log("[FlintN SDK]", ...args);
116
+ };
117
+ const reportInitError = (err) => {
118
+ const error = {
119
+ code: "INIT_ERROR",
120
+ message: err instanceof Error ? err.message : "Failed to initialize SDK"
121
+ };
122
+ queueMicrotask(() => {
123
+ if (options.onError) options.onError(error);
124
+ else console.error("[FlintN SDK]", error);
125
+ });
126
+ };
127
+ let origin;
128
+ try {
129
+ validateConfig(options.config);
130
+ origin = parseOrigin(options.origin || DEFAULT_ORIGIN);
131
+ } catch (err) {
132
+ reportInitError(err);
133
+ return {
134
+ mount() {
135
+ },
136
+ unmount() {
137
+ },
138
+ getIsReady: () => false
139
+ };
140
+ }
117
141
  let iframe = null;
118
142
  let container = null;
119
143
  let isReady = false;
120
144
  let messageHandler = null;
121
- const log = (...args) => {
122
- if (options.debug) console.log("[FlintN SDK]", ...args);
123
- };
145
+ let iframeErrorHandler = null;
124
146
  log("Initialized with origin:", origin);
125
147
  const sendConfig = () => {
126
148
  if (!iframe?.contentWindow) return;
@@ -130,10 +152,6 @@ function createFlintNPayment(options) {
130
152
  );
131
153
  log("Sent config:", sanitizeToken(options.config.clientSessionId));
132
154
  };
133
- const handlePaymentResult = (payload, callback) => {
134
- if (!callback) return;
135
- callback(payload);
136
- };
137
155
  const isValidRedirectUrl = (url) => {
138
156
  try {
139
157
  const parsed = new URL(url);
@@ -166,10 +184,7 @@ function createFlintNPayment(options) {
166
184
  options.onReady?.();
167
185
  break;
168
186
  case EventType.PAYMENT:
169
- handlePaymentResult(payload, options.onPayment);
170
- break;
171
- case EventType.EXPRESS_PAYMENT:
172
- handlePaymentResult(payload, options.onExpressPayment);
187
+ options.onPayment?.(payload);
173
188
  break;
174
189
  case EventType.REDIRECT:
175
190
  if (payload?.url && isValidRedirectUrl(payload.url)) {
@@ -186,12 +201,13 @@ function createFlintNPayment(options) {
186
201
  const mount = (selector) => {
187
202
  container = typeof selector === "string" ? document.querySelector(selector) : selector;
188
203
  if (!container) {
189
- const selectorDescription = typeof selector === "string" ? selector : `<${selector.tagName.toLowerCase()}${selector.id ? ` id="${selector.id}"` : ""}${selector.className ? ` class="${selector.className}"` : ""}>`;
190
- throw new Error(
191
- `[FlintN SDK] Container not found: ${selectorDescription}`
192
- );
204
+ const description = typeof selector === "string" ? selector : `<${selector.tagName.toLowerCase()}>`;
205
+ reportInitError(new Error(`Container not found: ${description}`));
206
+ return;
193
207
  }
194
208
  iframe = createIframeElement(buildIframeSrc(origin));
209
+ iframeErrorHandler = () => reportInitError(new Error("Failed to load checkout iframe"));
210
+ iframe.addEventListener("error", iframeErrorHandler);
195
211
  container.appendChild(iframe);
196
212
  messageHandler = handleMessage;
197
213
  window.addEventListener("message", messageHandler);
@@ -202,15 +218,19 @@ function createFlintNPayment(options) {
202
218
  window.removeEventListener("message", messageHandler);
203
219
  messageHandler = null;
204
220
  }
221
+ if (iframe && iframeErrorHandler) {
222
+ iframe.removeEventListener("error", iframeErrorHandler);
223
+ iframeErrorHandler = null;
224
+ }
205
225
  if (iframe && container && container.contains(iframe)) {
206
226
  container.removeChild(iframe);
207
227
  }
208
228
  iframe = null;
229
+ container = null;
209
230
  isReady = false;
210
231
  log("Unmounted");
211
232
  };
212
- const getIsReady = () => isReady;
213
- return { mount, unmount, getIsReady };
233
+ return { mount, unmount, getIsReady: () => isReady };
214
234
  }
215
235
 
216
236
  // src/flintn-field.ts
@@ -221,6 +241,7 @@ var FIELD_TITLES = {
221
241
  };
222
242
  function createFlintNField(fieldType, origin, clientSessionId, options, callbacks, debug = false, formStyles) {
223
243
  let iframe = null;
244
+ let iframeErrorHandler = null;
224
245
  let container = null;
225
246
  let state = {
226
247
  isEmpty: true,
@@ -231,6 +252,14 @@ function createFlintNField(fieldType, origin, clientSessionId, options, callback
231
252
  const log = (...args) => {
232
253
  if (debug) console.log(`[FlintN SDK][${fieldType}]`, ...args);
233
254
  };
255
+ const reportError = (err) => {
256
+ const error = err instanceof Error ? err : new Error(String(err));
257
+ if (callbacks.onError) {
258
+ queueMicrotask(() => callbacks.onError(error));
259
+ } else {
260
+ console.error(`[FlintN SDK][${fieldType}]`, error);
261
+ }
262
+ };
234
263
  const sendMessage = (type, payload) => {
235
264
  if (!iframe?.contentWindow) return;
236
265
  iframe.contentWindow.postMessage({ type, payload }, origin);
@@ -258,18 +287,35 @@ function createFlintNField(fieldType, origin, clientSessionId, options, callback
258
287
  const mount = (selector) => {
259
288
  container = typeof selector === "string" ? document.querySelector(selector) : selector;
260
289
  if (!container) {
261
- throw new Error(
262
- `[FlintN SDK] Field container not found for "${fieldType}": ${typeof selector === "string" ? selector : "element"}`
290
+ reportError(
291
+ new Error(
292
+ `Field container not found for "${fieldType}": ${typeof selector === "string" ? selector : "element"}`
293
+ )
263
294
  );
295
+ return;
264
296
  }
265
- const src = buildIframeSrc(origin) + fieldType;
266
- iframe = createIframeElement2(src);
297
+ try {
298
+ const src = buildIframeSrc(origin) + fieldType;
299
+ iframe = createIframeElement2(src);
300
+ } catch (err) {
301
+ reportError(err);
302
+ return;
303
+ }
304
+ iframeErrorHandler = () => {
305
+ reportError(new Error(`Failed to load ${fieldType} iframe`));
306
+ };
307
+ iframe.addEventListener("error", iframeErrorHandler);
267
308
  container.appendChild(iframe);
268
309
  log("Mounted", fieldType);
269
310
  };
270
311
  const unmount = () => {
312
+ if (iframe && iframeErrorHandler) {
313
+ iframe.removeEventListener("error", iframeErrorHandler);
314
+ iframeErrorHandler = null;
315
+ }
271
316
  if (iframe && container?.contains(iframe)) container.removeChild(iframe);
272
317
  iframe = null;
318
+ container = null;
273
319
  state = { isEmpty: true, isValid: false, isFocused: false, error: null };
274
320
  log("Unmounted", fieldType);
275
321
  };
@@ -333,11 +379,46 @@ function createFlintNField(fieldType, origin, clientSessionId, options, callback
333
379
  // src/flintn-fields.ts
334
380
  var DEFAULT_ORIGIN2 = "https://pay.flintn.com";
335
381
  var OPERATION_TIMEOUT = 3e4;
382
+ function createInertFields() {
383
+ const errorResult = {
384
+ status: "PAYMENT_ERROR",
385
+ error: { code: "INIT_ERROR", message: "SDK init failed" }
386
+ };
387
+ return {
388
+ createField: () => {
389
+ throw new Error(
390
+ "[FlintN SDK] Cannot create field \u2014 SDK init failed (see onError)"
391
+ );
392
+ },
393
+ getField: () => void 0,
394
+ validate: async () => ({ isValid: false, errors: {} }),
395
+ submit: async () => errorResult,
396
+ unmount: () => {
397
+ },
398
+ getIsReady: () => false
399
+ };
400
+ }
336
401
  function createFlintNFields(options) {
337
- if (!options.config?.clientSessionId) {
338
- throw new Error("[FlintN SDK] config.clientSessionId is required");
402
+ const reportInitError = (err) => {
403
+ const error = {
404
+ code: "INIT_ERROR",
405
+ message: err instanceof Error ? err.message : "Failed to initialize fields"
406
+ };
407
+ queueMicrotask(() => {
408
+ if (options.onError) options.onError(error);
409
+ else console.error("[FlintN SDK]", error);
410
+ });
411
+ };
412
+ let origin;
413
+ try {
414
+ if (!options.config?.clientSessionId) {
415
+ throw new Error("config.clientSessionId is required");
416
+ }
417
+ origin = parseOrigin(options.origin || DEFAULT_ORIGIN2);
418
+ } catch (err) {
419
+ reportInitError(err);
420
+ return createInertFields();
339
421
  }
340
- const origin = parseOrigin(options.origin || DEFAULT_ORIGIN2);
341
422
  const fields = /* @__PURE__ */ new Map();
342
423
  const readyFields = /* @__PURE__ */ new Set();
343
424
  let allReady = false;
@@ -438,7 +519,8 @@ function createFlintNFields(options) {
438
519
  onFocus: onFieldFocus,
439
520
  onBlur: onFieldBlur,
440
521
  onHeight: () => {
441
- }
522
+ },
523
+ onError: reportInitError
442
524
  },
443
525
  options.debug,
444
526
  options.config.styles
@@ -467,12 +549,22 @@ function createFlintNFields(options) {
467
549
  };
468
550
  const submit = async (submitOptions = {}, skipValidation = false) => {
469
551
  if (!fields.has(FieldType.CARD_NUMBER)) {
470
- throw new Error(
471
- '[FlintN SDK] card-number field not initialized. Call createField("card-number") before submit().'
472
- );
552
+ return {
553
+ status: "PAYMENT_ERROR",
554
+ error: {
555
+ code: "CARD_NUMBER_NOT_INITIALIZED",
556
+ message: 'card-number field not initialized. Call createField("card-number") before submit().'
557
+ }
558
+ };
473
559
  }
474
560
  if (pendingSubmit) {
475
- throw new Error("[FlintN SDK] A submit is already in progress");
561
+ return {
562
+ status: "PAYMENT_ERROR",
563
+ error: {
564
+ code: "SUBMIT_IN_PROGRESS",
565
+ message: "A submit is already in progress"
566
+ }
567
+ };
476
568
  }
477
569
  if (!skipValidation) {
478
570
  const validation = await validate();
@@ -489,17 +581,24 @@ function createFlintNFields(options) {
489
581
  return new Promise((resolve, reject) => {
490
582
  const timer = setTimeout(() => {
491
583
  if (pendingSubmit && pendingSubmit.timer === timer) {
492
- pendingSubmit.reject(new Error("[FlintN SDK] Submit timed out"));
584
+ const timeoutResult = {
585
+ status: "PAYMENT_ERROR",
586
+ error: {
587
+ code: "SUBMIT_TIMEOUT",
588
+ message: "Submit timed out"
589
+ }
590
+ };
591
+ pendingSubmit.resolve(timeoutResult);
592
+ options.onPayment?.(timeoutResult);
493
593
  pendingSubmit = null;
494
594
  }
495
595
  }, OPERATION_TIMEOUT);
496
596
  pendingSubmit = { resolve, reject, timer };
497
- for (const field of fields.values()) {
498
- field._sendMessage(FieldEventType.FIELD_SUBMIT, {
499
- clientSessionId: options.config.clientSessionId,
500
- cardholderName: submitOptions.cardholderName
501
- });
502
- }
597
+ const coordinator = fields.get(FieldType.CARD_NUMBER);
598
+ coordinator?._sendMessage(FieldEventType.FIELD_SUBMIT, {
599
+ clientSessionId: options.config.clientSessionId,
600
+ cardholderName: submitOptions.cardholderName
601
+ });
503
602
  log("Submit initiated");
504
603
  });
505
604
  };
package/dist/index.mjs CHANGED
@@ -3,7 +3,6 @@ var EventType = {
3
3
  WIDGET_READY: "WIDGET_READY",
4
4
  WIDGET_CONFIG: "WIDGET_CONFIG",
5
5
  PAYMENT: "PAYMENT",
6
- EXPRESS_PAYMENT: "EXPRESS_PAYMENT",
7
6
  PAYMENT_SUCCESS: "PAYMENT_SUCCESS",
8
7
  PAYMENT_ERROR: "PAYMENT_ERROR",
9
8
  PAYMENT_CANCELLED: "PAYMENT_CANCELLED",
@@ -75,15 +74,38 @@ var sanitizeToken = (token) => {
75
74
  // src/flintn-payment.ts
76
75
  var DEFAULT_ORIGIN = "https://pay.flintn.com";
77
76
  function createFlintNPayment(options) {
78
- validateConfig(options.config);
79
- const origin = parseOrigin(options.origin || DEFAULT_ORIGIN);
77
+ const log = (...args) => {
78
+ if (options.debug) console.log("[FlintN SDK]", ...args);
79
+ };
80
+ const reportInitError = (err) => {
81
+ const error = {
82
+ code: "INIT_ERROR",
83
+ message: err instanceof Error ? err.message : "Failed to initialize SDK"
84
+ };
85
+ queueMicrotask(() => {
86
+ if (options.onError) options.onError(error);
87
+ else console.error("[FlintN SDK]", error);
88
+ });
89
+ };
90
+ let origin;
91
+ try {
92
+ validateConfig(options.config);
93
+ origin = parseOrigin(options.origin || DEFAULT_ORIGIN);
94
+ } catch (err) {
95
+ reportInitError(err);
96
+ return {
97
+ mount() {
98
+ },
99
+ unmount() {
100
+ },
101
+ getIsReady: () => false
102
+ };
103
+ }
80
104
  let iframe = null;
81
105
  let container = null;
82
106
  let isReady = false;
83
107
  let messageHandler = null;
84
- const log = (...args) => {
85
- if (options.debug) console.log("[FlintN SDK]", ...args);
86
- };
108
+ let iframeErrorHandler = null;
87
109
  log("Initialized with origin:", origin);
88
110
  const sendConfig = () => {
89
111
  if (!iframe?.contentWindow) return;
@@ -93,10 +115,6 @@ function createFlintNPayment(options) {
93
115
  );
94
116
  log("Sent config:", sanitizeToken(options.config.clientSessionId));
95
117
  };
96
- const handlePaymentResult = (payload, callback) => {
97
- if (!callback) return;
98
- callback(payload);
99
- };
100
118
  const isValidRedirectUrl = (url) => {
101
119
  try {
102
120
  const parsed = new URL(url);
@@ -129,10 +147,7 @@ function createFlintNPayment(options) {
129
147
  options.onReady?.();
130
148
  break;
131
149
  case EventType.PAYMENT:
132
- handlePaymentResult(payload, options.onPayment);
133
- break;
134
- case EventType.EXPRESS_PAYMENT:
135
- handlePaymentResult(payload, options.onExpressPayment);
150
+ options.onPayment?.(payload);
136
151
  break;
137
152
  case EventType.REDIRECT:
138
153
  if (payload?.url && isValidRedirectUrl(payload.url)) {
@@ -149,12 +164,13 @@ function createFlintNPayment(options) {
149
164
  const mount = (selector) => {
150
165
  container = typeof selector === "string" ? document.querySelector(selector) : selector;
151
166
  if (!container) {
152
- const selectorDescription = typeof selector === "string" ? selector : `<${selector.tagName.toLowerCase()}${selector.id ? ` id="${selector.id}"` : ""}${selector.className ? ` class="${selector.className}"` : ""}>`;
153
- throw new Error(
154
- `[FlintN SDK] Container not found: ${selectorDescription}`
155
- );
167
+ const description = typeof selector === "string" ? selector : `<${selector.tagName.toLowerCase()}>`;
168
+ reportInitError(new Error(`Container not found: ${description}`));
169
+ return;
156
170
  }
157
171
  iframe = createIframeElement(buildIframeSrc(origin));
172
+ iframeErrorHandler = () => reportInitError(new Error("Failed to load checkout iframe"));
173
+ iframe.addEventListener("error", iframeErrorHandler);
158
174
  container.appendChild(iframe);
159
175
  messageHandler = handleMessage;
160
176
  window.addEventListener("message", messageHandler);
@@ -165,15 +181,19 @@ function createFlintNPayment(options) {
165
181
  window.removeEventListener("message", messageHandler);
166
182
  messageHandler = null;
167
183
  }
184
+ if (iframe && iframeErrorHandler) {
185
+ iframe.removeEventListener("error", iframeErrorHandler);
186
+ iframeErrorHandler = null;
187
+ }
168
188
  if (iframe && container && container.contains(iframe)) {
169
189
  container.removeChild(iframe);
170
190
  }
171
191
  iframe = null;
192
+ container = null;
172
193
  isReady = false;
173
194
  log("Unmounted");
174
195
  };
175
- const getIsReady = () => isReady;
176
- return { mount, unmount, getIsReady };
196
+ return { mount, unmount, getIsReady: () => isReady };
177
197
  }
178
198
 
179
199
  // src/flintn-field.ts
@@ -184,6 +204,7 @@ var FIELD_TITLES = {
184
204
  };
185
205
  function createFlintNField(fieldType, origin, clientSessionId, options, callbacks, debug = false, formStyles) {
186
206
  let iframe = null;
207
+ let iframeErrorHandler = null;
187
208
  let container = null;
188
209
  let state = {
189
210
  isEmpty: true,
@@ -194,6 +215,14 @@ function createFlintNField(fieldType, origin, clientSessionId, options, callback
194
215
  const log = (...args) => {
195
216
  if (debug) console.log(`[FlintN SDK][${fieldType}]`, ...args);
196
217
  };
218
+ const reportError = (err) => {
219
+ const error = err instanceof Error ? err : new Error(String(err));
220
+ if (callbacks.onError) {
221
+ queueMicrotask(() => callbacks.onError(error));
222
+ } else {
223
+ console.error(`[FlintN SDK][${fieldType}]`, error);
224
+ }
225
+ };
197
226
  const sendMessage = (type, payload) => {
198
227
  if (!iframe?.contentWindow) return;
199
228
  iframe.contentWindow.postMessage({ type, payload }, origin);
@@ -221,18 +250,35 @@ function createFlintNField(fieldType, origin, clientSessionId, options, callback
221
250
  const mount = (selector) => {
222
251
  container = typeof selector === "string" ? document.querySelector(selector) : selector;
223
252
  if (!container) {
224
- throw new Error(
225
- `[FlintN SDK] Field container not found for "${fieldType}": ${typeof selector === "string" ? selector : "element"}`
253
+ reportError(
254
+ new Error(
255
+ `Field container not found for "${fieldType}": ${typeof selector === "string" ? selector : "element"}`
256
+ )
226
257
  );
258
+ return;
227
259
  }
228
- const src = buildIframeSrc(origin) + fieldType;
229
- iframe = createIframeElement2(src);
260
+ try {
261
+ const src = buildIframeSrc(origin) + fieldType;
262
+ iframe = createIframeElement2(src);
263
+ } catch (err) {
264
+ reportError(err);
265
+ return;
266
+ }
267
+ iframeErrorHandler = () => {
268
+ reportError(new Error(`Failed to load ${fieldType} iframe`));
269
+ };
270
+ iframe.addEventListener("error", iframeErrorHandler);
230
271
  container.appendChild(iframe);
231
272
  log("Mounted", fieldType);
232
273
  };
233
274
  const unmount = () => {
275
+ if (iframe && iframeErrorHandler) {
276
+ iframe.removeEventListener("error", iframeErrorHandler);
277
+ iframeErrorHandler = null;
278
+ }
234
279
  if (iframe && container?.contains(iframe)) container.removeChild(iframe);
235
280
  iframe = null;
281
+ container = null;
236
282
  state = { isEmpty: true, isValid: false, isFocused: false, error: null };
237
283
  log("Unmounted", fieldType);
238
284
  };
@@ -296,11 +342,46 @@ function createFlintNField(fieldType, origin, clientSessionId, options, callback
296
342
  // src/flintn-fields.ts
297
343
  var DEFAULT_ORIGIN2 = "https://pay.flintn.com";
298
344
  var OPERATION_TIMEOUT = 3e4;
345
+ function createInertFields() {
346
+ const errorResult = {
347
+ status: "PAYMENT_ERROR",
348
+ error: { code: "INIT_ERROR", message: "SDK init failed" }
349
+ };
350
+ return {
351
+ createField: () => {
352
+ throw new Error(
353
+ "[FlintN SDK] Cannot create field \u2014 SDK init failed (see onError)"
354
+ );
355
+ },
356
+ getField: () => void 0,
357
+ validate: async () => ({ isValid: false, errors: {} }),
358
+ submit: async () => errorResult,
359
+ unmount: () => {
360
+ },
361
+ getIsReady: () => false
362
+ };
363
+ }
299
364
  function createFlintNFields(options) {
300
- if (!options.config?.clientSessionId) {
301
- throw new Error("[FlintN SDK] config.clientSessionId is required");
365
+ const reportInitError = (err) => {
366
+ const error = {
367
+ code: "INIT_ERROR",
368
+ message: err instanceof Error ? err.message : "Failed to initialize fields"
369
+ };
370
+ queueMicrotask(() => {
371
+ if (options.onError) options.onError(error);
372
+ else console.error("[FlintN SDK]", error);
373
+ });
374
+ };
375
+ let origin;
376
+ try {
377
+ if (!options.config?.clientSessionId) {
378
+ throw new Error("config.clientSessionId is required");
379
+ }
380
+ origin = parseOrigin(options.origin || DEFAULT_ORIGIN2);
381
+ } catch (err) {
382
+ reportInitError(err);
383
+ return createInertFields();
302
384
  }
303
- const origin = parseOrigin(options.origin || DEFAULT_ORIGIN2);
304
385
  const fields = /* @__PURE__ */ new Map();
305
386
  const readyFields = /* @__PURE__ */ new Set();
306
387
  let allReady = false;
@@ -401,7 +482,8 @@ function createFlintNFields(options) {
401
482
  onFocus: onFieldFocus,
402
483
  onBlur: onFieldBlur,
403
484
  onHeight: () => {
404
- }
485
+ },
486
+ onError: reportInitError
405
487
  },
406
488
  options.debug,
407
489
  options.config.styles
@@ -430,12 +512,22 @@ function createFlintNFields(options) {
430
512
  };
431
513
  const submit = async (submitOptions = {}, skipValidation = false) => {
432
514
  if (!fields.has(FieldType.CARD_NUMBER)) {
433
- throw new Error(
434
- '[FlintN SDK] card-number field not initialized. Call createField("card-number") before submit().'
435
- );
515
+ return {
516
+ status: "PAYMENT_ERROR",
517
+ error: {
518
+ code: "CARD_NUMBER_NOT_INITIALIZED",
519
+ message: 'card-number field not initialized. Call createField("card-number") before submit().'
520
+ }
521
+ };
436
522
  }
437
523
  if (pendingSubmit) {
438
- throw new Error("[FlintN SDK] A submit is already in progress");
524
+ return {
525
+ status: "PAYMENT_ERROR",
526
+ error: {
527
+ code: "SUBMIT_IN_PROGRESS",
528
+ message: "A submit is already in progress"
529
+ }
530
+ };
439
531
  }
440
532
  if (!skipValidation) {
441
533
  const validation = await validate();
@@ -452,17 +544,24 @@ function createFlintNFields(options) {
452
544
  return new Promise((resolve, reject) => {
453
545
  const timer = setTimeout(() => {
454
546
  if (pendingSubmit && pendingSubmit.timer === timer) {
455
- pendingSubmit.reject(new Error("[FlintN SDK] Submit timed out"));
547
+ const timeoutResult = {
548
+ status: "PAYMENT_ERROR",
549
+ error: {
550
+ code: "SUBMIT_TIMEOUT",
551
+ message: "Submit timed out"
552
+ }
553
+ };
554
+ pendingSubmit.resolve(timeoutResult);
555
+ options.onPayment?.(timeoutResult);
456
556
  pendingSubmit = null;
457
557
  }
458
558
  }, OPERATION_TIMEOUT);
459
559
  pendingSubmit = { resolve, reject, timer };
460
- for (const field of fields.values()) {
461
- field._sendMessage(FieldEventType.FIELD_SUBMIT, {
462
- clientSessionId: options.config.clientSessionId,
463
- cardholderName: submitOptions.cardholderName
464
- });
465
- }
560
+ const coordinator = fields.get(FieldType.CARD_NUMBER);
561
+ coordinator?._sendMessage(FieldEventType.FIELD_SUBMIT, {
562
+ clientSessionId: options.config.clientSessionId,
563
+ cardholderName: submitOptions.cardholderName
564
+ });
466
565
  log("Submit initiated");
467
566
  });
468
567
  };
package/dist/react.d.mts CHANGED
@@ -50,7 +50,6 @@ interface FlintNPaymentOptions {
50
50
  origin?: string;
51
51
  config: FlintNConfig;
52
52
  onPayment?: (result: PaymentResult) => void;
53
- onExpressPayment?: (result: PaymentResult) => void;
54
53
  onReady?: () => void;
55
54
  onError?: (error: PaymentError) => void;
56
55
  debug?: boolean;
@@ -109,7 +108,7 @@ interface FlintNFieldsOptions {
109
108
  * as they are managed internally by the hook. Use the returned `isReady`
110
109
  * and `error` values instead.
111
110
  *
112
- * All other FlintNPaymentOptions fields (config, onPayment, onExpressPayment, debug)
111
+ * All other FlintNPaymentOptions fields (config, onPayment, debug)
113
112
  * can be passed directly.
114
113
  */
115
114
  interface UseFlintNPaymentOptions extends Omit<FlintNPaymentOptions, 'onReady' | 'onError'> {