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 +1 -10
- package/dist/index.d.mts +1 -2
- package/dist/index.d.ts +1 -2
- package/dist/index.js +138 -39
- package/dist/index.mjs +138 -39
- package/dist/react.d.mts +1 -2
- package/dist/react.d.ts +1 -2
- package/dist/react.js +151 -65
- package/dist/react.mjs +151 -65
- package/package.json +2 -2
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 | — |
|
|
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
|
-
|
|
116
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
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
|
-
|
|
262
|
-
|
|
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
|
-
|
|
266
|
-
|
|
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
|
-
|
|
338
|
-
|
|
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
|
-
|
|
471
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
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
|
-
|
|
225
|
-
|
|
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
|
-
|
|
229
|
-
|
|
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
|
-
|
|
301
|
-
|
|
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
|
-
|
|
434
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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,
|
|
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'> {
|