flintn-checkout 0.0.1
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 +193 -0
- package/dist/index.d.mts +66 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +197 -0
- package/dist/index.mjs +164 -0
- package/dist/react.d.mts +60 -0
- package/dist/react.d.ts +60 -0
- package/dist/react.js +251 -0
- package/dist/react.mjs +223 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# flintn-checkout
|
|
2
|
+
|
|
3
|
+
FlintN Payment SDK — Iframe-based payment widget with postMessage communication.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
```bash
|
|
7
|
+
npm install flintn-checkout
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Quick Start
|
|
11
|
+
|
|
12
|
+
### React
|
|
13
|
+
```tsx
|
|
14
|
+
import { useFlintNPayment } from 'flintn-checkout/react';
|
|
15
|
+
|
|
16
|
+
function Checkout() {
|
|
17
|
+
const { containerRef, isReady, paymentResult, error } = useFlintNPayment({
|
|
18
|
+
config: {
|
|
19
|
+
clientToken: 'your_client_token',
|
|
20
|
+
},
|
|
21
|
+
onPayment: (result) => {
|
|
22
|
+
if (result.status === 'PAYMENT_SUCCESS') {
|
|
23
|
+
console.log('Payment succeeded:', result.data);
|
|
24
|
+
} else {
|
|
25
|
+
console.log('Payment failed:', result.error);
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
onExpressPayment: (result) => {
|
|
29
|
+
console.log('Express payment:', result);
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
|
35
|
+
<div ref={containerRef} style={{ width: '100%', maxWidth: '440px', height: '600px' }} />
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Vanilla JavaScript
|
|
42
|
+
```javascript
|
|
43
|
+
import { FlintNPayment } from 'flintn-checkout';
|
|
44
|
+
|
|
45
|
+
const payment = new FlintNPayment({
|
|
46
|
+
config: {
|
|
47
|
+
clientToken: 'your_client_token',
|
|
48
|
+
maxWidth: '440px',
|
|
49
|
+
isCardHolderRequired: true,
|
|
50
|
+
successRedirectUrl: 'https://example.com/success',
|
|
51
|
+
},
|
|
52
|
+
onPayment: (result) => {
|
|
53
|
+
if (result.status === 'PAYMENT_SUCCESS') {
|
|
54
|
+
console.log('Payment succeeded:', result.data);
|
|
55
|
+
} else {
|
|
56
|
+
console.log('Payment failed:', result.error);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
onExpressPayment: (result) => {
|
|
60
|
+
console.log('Express payment:', result);
|
|
61
|
+
},
|
|
62
|
+
onReady: () => {
|
|
63
|
+
console.log('Widget ready');
|
|
64
|
+
},
|
|
65
|
+
debug: true,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
payment.mount('#payment-container');
|
|
69
|
+
|
|
70
|
+
// Cleanup when done
|
|
71
|
+
payment.unmount();
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### HTML
|
|
75
|
+
```html
|
|
76
|
+
<!DOCTYPE html>
|
|
77
|
+
<html>
|
|
78
|
+
<head>
|
|
79
|
+
<title>FlintN Checkout</title>
|
|
80
|
+
</head>
|
|
81
|
+
<body>
|
|
82
|
+
<div id="payment-container" style="max-width: 440px; height: 600px; margin: 0 auto;"></div>
|
|
83
|
+
|
|
84
|
+
<script type="module">
|
|
85
|
+
import { FlintNPayment } from 'flintn-checkout';
|
|
86
|
+
|
|
87
|
+
const payment = new FlintNPayment({
|
|
88
|
+
config: {
|
|
89
|
+
clientToken: 'your_client_token',
|
|
90
|
+
},
|
|
91
|
+
onPayment: (result) => {
|
|
92
|
+
console.log('Payment result:', result);
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
payment.mount('#payment-container');
|
|
97
|
+
</script>
|
|
98
|
+
</body>
|
|
99
|
+
</html>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Configuration
|
|
103
|
+
|
|
104
|
+
| Option | Type | Required | Default | Description |
|
|
105
|
+
|--------|------|----------|---------|-------------|
|
|
106
|
+
| `clientToken` | `string` | ✅ | — | Client token from your backend |
|
|
107
|
+
| `maxWidth` | `string` | ❌ | `'440px'` | Maximum width of widget |
|
|
108
|
+
| `isCardHolderRequired` | `boolean` | ❌ | `true` | Show cardholder name field |
|
|
109
|
+
| `successRedirectUrl` | `string` | ❌ | — | Redirect URL after successful payment |
|
|
110
|
+
|
|
111
|
+
## Callbacks
|
|
112
|
+
|
|
113
|
+
| Callback | Description |
|
|
114
|
+
|----------|-------------|
|
|
115
|
+
| `onPayment` | Fired when card payment completes (success or error) |
|
|
116
|
+
| `onExpressPayment` | Fired when express payment completes (Apple Pay, Google Pay, PayPal) |
|
|
117
|
+
| `onReady` | Fired when widget is loaded and ready |
|
|
118
|
+
| `onError` | Fired on SDK initialization error |
|
|
119
|
+
|
|
120
|
+
## React Hook Return Values
|
|
121
|
+
|
|
122
|
+
| Value | Type | Description |
|
|
123
|
+
|-------|------|-------------|
|
|
124
|
+
| `containerRef` | `RefObject<HTMLDivElement>` | Ref to attach to container element |
|
|
125
|
+
| `isReady` | `boolean` | Widget is loaded and ready |
|
|
126
|
+
| `paymentResult` | `PaymentResult \| null` | Result after payment attempt |
|
|
127
|
+
| `error` | `PaymentError \| null` | SDK error if any |
|
|
128
|
+
|
|
129
|
+
## Types
|
|
130
|
+
|
|
131
|
+
### PaymentResult
|
|
132
|
+
```typescript
|
|
133
|
+
interface PaymentResult {
|
|
134
|
+
status: 'PAYMENT_SUCCESS' | 'PAYMENT_ERROR' | 'PAYMENT_CANCELLED';
|
|
135
|
+
data?: string; // Payment ID on success
|
|
136
|
+
error?: {
|
|
137
|
+
code: string;
|
|
138
|
+
message: string;
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### PaymentError
|
|
144
|
+
```typescript
|
|
145
|
+
interface PaymentError {
|
|
146
|
+
code: string;
|
|
147
|
+
message: string;
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Supported Payment Methods
|
|
152
|
+
|
|
153
|
+
- 💳 Credit/Debit Cards (Visa, Mastercard, Amex, Discover)
|
|
154
|
+
- 🍎 Apple Pay
|
|
155
|
+
- 🔵 Google Pay
|
|
156
|
+
- 🟡 PayPal
|
|
157
|
+
|
|
158
|
+
## Debug Mode
|
|
159
|
+
|
|
160
|
+
Enable debug logs in console:
|
|
161
|
+
```typescript
|
|
162
|
+
// React
|
|
163
|
+
useFlintNPayment({
|
|
164
|
+
config: { clientToken: '...' },
|
|
165
|
+
debug: true,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Vanilla JS
|
|
169
|
+
new FlintNPayment({
|
|
170
|
+
config: { clientToken: '...' },
|
|
171
|
+
debug: true,
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Test Cards
|
|
176
|
+
|
|
177
|
+
| Card Number | Result |
|
|
178
|
+
|-------------|--------|
|
|
179
|
+
| `4111 1111 1111 1111` | Success |
|
|
180
|
+
| `4000 0000 0000 0002` | Declined |
|
|
181
|
+
|
|
182
|
+
Use any future expiry date and any 3-digit CVV.
|
|
183
|
+
|
|
184
|
+
## Browser Support
|
|
185
|
+
|
|
186
|
+
- Chrome (latest)
|
|
187
|
+
- Firefox (latest)
|
|
188
|
+
- Safari (latest)
|
|
189
|
+
- Edge (latest)
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
declare const EventType: {
|
|
2
|
+
readonly WIDGET_READY: "WIDGET_READY";
|
|
3
|
+
readonly WIDGET_CONFIG: "WIDGET_CONFIG";
|
|
4
|
+
readonly PAYMENT: "PAYMENT";
|
|
5
|
+
readonly EXPRESS_PAYMENT: "EXPRESS_PAYMENT";
|
|
6
|
+
readonly PAYMENT_SUCCESS: "PAYMENT_SUCCESS";
|
|
7
|
+
readonly PAYMENT_ERROR: "PAYMENT_ERROR";
|
|
8
|
+
readonly PAYMENT_CANCELLED: "PAYMENT_CANCELLED";
|
|
9
|
+
readonly REDIRECT: "REDIRECT";
|
|
10
|
+
};
|
|
11
|
+
type TEventType = (typeof EventType)[keyof typeof EventType];
|
|
12
|
+
interface FlintNConfig {
|
|
13
|
+
clientToken: string;
|
|
14
|
+
maxWidth?: string;
|
|
15
|
+
isCardHolderRequired?: boolean;
|
|
16
|
+
successRedirectUrl?: string;
|
|
17
|
+
}
|
|
18
|
+
interface PaymentResult {
|
|
19
|
+
status: 'PAYMENT_SUCCESS' | 'PAYMENT_ERROR' | 'PAYMENT_CANCELLED';
|
|
20
|
+
data?: string;
|
|
21
|
+
error?: {
|
|
22
|
+
code: string;
|
|
23
|
+
message: string;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
interface PaymentError {
|
|
27
|
+
code: string;
|
|
28
|
+
message: string;
|
|
29
|
+
}
|
|
30
|
+
interface FlintNPaymentOptions {
|
|
31
|
+
origin?: string;
|
|
32
|
+
config: FlintNConfig;
|
|
33
|
+
onPayment?: (result: PaymentResult) => void;
|
|
34
|
+
onExpressPayment?: (result: PaymentResult) => void;
|
|
35
|
+
onReady?: () => void;
|
|
36
|
+
onError?: (error: PaymentError) => void;
|
|
37
|
+
debug?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
declare class FlintNPayment {
|
|
41
|
+
private options;
|
|
42
|
+
private origin;
|
|
43
|
+
private iframe;
|
|
44
|
+
private container;
|
|
45
|
+
private isReady;
|
|
46
|
+
private messageHandler;
|
|
47
|
+
constructor(options: FlintNPaymentOptions);
|
|
48
|
+
mount(selector: string | HTMLElement): void;
|
|
49
|
+
unmount(): void;
|
|
50
|
+
getIsReady(): boolean;
|
|
51
|
+
private handleMessage;
|
|
52
|
+
private isValidRedirectUrl;
|
|
53
|
+
private handlePaymentResult;
|
|
54
|
+
private sendConfig;
|
|
55
|
+
private log;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
declare const parseOrigin: (origin: string) => string;
|
|
59
|
+
declare const buildIframeSrc: (origin: string) => string;
|
|
60
|
+
declare const createIframeElement: (src: string) => HTMLIFrameElement;
|
|
61
|
+
declare const validateConfig: (config: {
|
|
62
|
+
clientToken?: string;
|
|
63
|
+
}) => void;
|
|
64
|
+
declare const sanitizeToken: (token: string) => string;
|
|
65
|
+
|
|
66
|
+
export { EventType, type FlintNConfig, FlintNPayment, type FlintNPaymentOptions, type PaymentError, type PaymentResult, type TEventType, buildIframeSrc, createIframeElement, parseOrigin, sanitizeToken, validateConfig };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
declare const EventType: {
|
|
2
|
+
readonly WIDGET_READY: "WIDGET_READY";
|
|
3
|
+
readonly WIDGET_CONFIG: "WIDGET_CONFIG";
|
|
4
|
+
readonly PAYMENT: "PAYMENT";
|
|
5
|
+
readonly EXPRESS_PAYMENT: "EXPRESS_PAYMENT";
|
|
6
|
+
readonly PAYMENT_SUCCESS: "PAYMENT_SUCCESS";
|
|
7
|
+
readonly PAYMENT_ERROR: "PAYMENT_ERROR";
|
|
8
|
+
readonly PAYMENT_CANCELLED: "PAYMENT_CANCELLED";
|
|
9
|
+
readonly REDIRECT: "REDIRECT";
|
|
10
|
+
};
|
|
11
|
+
type TEventType = (typeof EventType)[keyof typeof EventType];
|
|
12
|
+
interface FlintNConfig {
|
|
13
|
+
clientToken: string;
|
|
14
|
+
maxWidth?: string;
|
|
15
|
+
isCardHolderRequired?: boolean;
|
|
16
|
+
successRedirectUrl?: string;
|
|
17
|
+
}
|
|
18
|
+
interface PaymentResult {
|
|
19
|
+
status: 'PAYMENT_SUCCESS' | 'PAYMENT_ERROR' | 'PAYMENT_CANCELLED';
|
|
20
|
+
data?: string;
|
|
21
|
+
error?: {
|
|
22
|
+
code: string;
|
|
23
|
+
message: string;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
interface PaymentError {
|
|
27
|
+
code: string;
|
|
28
|
+
message: string;
|
|
29
|
+
}
|
|
30
|
+
interface FlintNPaymentOptions {
|
|
31
|
+
origin?: string;
|
|
32
|
+
config: FlintNConfig;
|
|
33
|
+
onPayment?: (result: PaymentResult) => void;
|
|
34
|
+
onExpressPayment?: (result: PaymentResult) => void;
|
|
35
|
+
onReady?: () => void;
|
|
36
|
+
onError?: (error: PaymentError) => void;
|
|
37
|
+
debug?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
declare class FlintNPayment {
|
|
41
|
+
private options;
|
|
42
|
+
private origin;
|
|
43
|
+
private iframe;
|
|
44
|
+
private container;
|
|
45
|
+
private isReady;
|
|
46
|
+
private messageHandler;
|
|
47
|
+
constructor(options: FlintNPaymentOptions);
|
|
48
|
+
mount(selector: string | HTMLElement): void;
|
|
49
|
+
unmount(): void;
|
|
50
|
+
getIsReady(): boolean;
|
|
51
|
+
private handleMessage;
|
|
52
|
+
private isValidRedirectUrl;
|
|
53
|
+
private handlePaymentResult;
|
|
54
|
+
private sendConfig;
|
|
55
|
+
private log;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
declare const parseOrigin: (origin: string) => string;
|
|
59
|
+
declare const buildIframeSrc: (origin: string) => string;
|
|
60
|
+
declare const createIframeElement: (src: string) => HTMLIFrameElement;
|
|
61
|
+
declare const validateConfig: (config: {
|
|
62
|
+
clientToken?: string;
|
|
63
|
+
}) => void;
|
|
64
|
+
declare const sanitizeToken: (token: string) => string;
|
|
65
|
+
|
|
66
|
+
export { EventType, type FlintNConfig, FlintNPayment, type FlintNPaymentOptions, type PaymentError, type PaymentResult, type TEventType, buildIframeSrc, createIframeElement, parseOrigin, sanitizeToken, validateConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
EventType: () => EventType,
|
|
24
|
+
FlintNPayment: () => FlintNPayment,
|
|
25
|
+
buildIframeSrc: () => buildIframeSrc,
|
|
26
|
+
createIframeElement: () => createIframeElement,
|
|
27
|
+
parseOrigin: () => parseOrigin,
|
|
28
|
+
sanitizeToken: () => sanitizeToken,
|
|
29
|
+
validateConfig: () => validateConfig
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
|
|
33
|
+
// src/types.ts
|
|
34
|
+
var EventType = {
|
|
35
|
+
WIDGET_READY: "WIDGET_READY",
|
|
36
|
+
WIDGET_CONFIG: "WIDGET_CONFIG",
|
|
37
|
+
PAYMENT: "PAYMENT",
|
|
38
|
+
EXPRESS_PAYMENT: "EXPRESS_PAYMENT",
|
|
39
|
+
PAYMENT_SUCCESS: "PAYMENT_SUCCESS",
|
|
40
|
+
PAYMENT_ERROR: "PAYMENT_ERROR",
|
|
41
|
+
PAYMENT_CANCELLED: "PAYMENT_CANCELLED",
|
|
42
|
+
REDIRECT: "REDIRECT"
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// src/utils.ts
|
|
46
|
+
var parseOrigin = (origin) => {
|
|
47
|
+
try {
|
|
48
|
+
return new URL(origin).origin;
|
|
49
|
+
} catch {
|
|
50
|
+
try {
|
|
51
|
+
return new URL(`https://${origin}`).origin;
|
|
52
|
+
} catch {
|
|
53
|
+
throw new Error(`[FlintN SDK] Invalid origin: ${origin}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var buildIframeSrc = (origin) => {
|
|
58
|
+
return parseOrigin(origin) + "/";
|
|
59
|
+
};
|
|
60
|
+
var createIframeElement = (src) => {
|
|
61
|
+
const iframe = document.createElement("iframe");
|
|
62
|
+
iframe.src = src;
|
|
63
|
+
iframe.title = "FlintN Checkout";
|
|
64
|
+
iframe.style.cssText = "width: 100%; height: 100%; border: none;";
|
|
65
|
+
iframe.setAttribute("sandbox", "allow-scripts allow-forms allow-popups allow-same-origin");
|
|
66
|
+
iframe.setAttribute("allow", "payment; clipboard-write");
|
|
67
|
+
return iframe;
|
|
68
|
+
};
|
|
69
|
+
var validateConfig = (config) => {
|
|
70
|
+
if (!config.clientToken) {
|
|
71
|
+
throw new Error("[FlintN SDK] clientToken is required");
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
var sanitizeToken = (token) => {
|
|
75
|
+
if (token.length <= 20) return token;
|
|
76
|
+
return token.substring(0, 20) + "...";
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// src/flintn-payment.ts
|
|
80
|
+
var DEFAULT_ORIGIN = "https://pay.flintn.com/iframe";
|
|
81
|
+
var FlintNPayment = class {
|
|
82
|
+
constructor(options) {
|
|
83
|
+
this.iframe = null;
|
|
84
|
+
this.container = null;
|
|
85
|
+
this.isReady = false;
|
|
86
|
+
this.messageHandler = null;
|
|
87
|
+
validateConfig(options.config);
|
|
88
|
+
this.options = options;
|
|
89
|
+
this.origin = parseOrigin(options.origin || DEFAULT_ORIGIN);
|
|
90
|
+
this.log("Initialized with origin:", this.origin);
|
|
91
|
+
}
|
|
92
|
+
mount(selector) {
|
|
93
|
+
this.container = typeof selector === "string" ? document.querySelector(selector) : selector;
|
|
94
|
+
if (!this.container) {
|
|
95
|
+
const selectorDescription = typeof selector === "string" ? selector : `<${selector.tagName.toLowerCase()}${selector.id ? ` id="${selector.id}"` : ""}${selector.className ? ` class="${selector.className}"` : ""}>`;
|
|
96
|
+
throw new Error(`[FlintN SDK] Container not found: ${selectorDescription}`);
|
|
97
|
+
}
|
|
98
|
+
this.iframe = createIframeElement(buildIframeSrc(this.origin));
|
|
99
|
+
this.container.appendChild(this.iframe);
|
|
100
|
+
this.messageHandler = this.handleMessage.bind(this);
|
|
101
|
+
window.addEventListener("message", this.messageHandler);
|
|
102
|
+
this.log("Mounted to container");
|
|
103
|
+
}
|
|
104
|
+
unmount() {
|
|
105
|
+
if (this.messageHandler) {
|
|
106
|
+
window.removeEventListener("message", this.messageHandler);
|
|
107
|
+
this.messageHandler = null;
|
|
108
|
+
}
|
|
109
|
+
if (this.iframe && this.container && this.container.contains(this.iframe)) {
|
|
110
|
+
this.container.removeChild(this.iframe);
|
|
111
|
+
}
|
|
112
|
+
this.iframe = null;
|
|
113
|
+
this.isReady = false;
|
|
114
|
+
this.log("Unmounted");
|
|
115
|
+
}
|
|
116
|
+
getIsReady() {
|
|
117
|
+
return this.isReady;
|
|
118
|
+
}
|
|
119
|
+
handleMessage(event) {
|
|
120
|
+
if (event.origin !== this.origin) return;
|
|
121
|
+
const { type, payload } = event.data || {};
|
|
122
|
+
if (!type) return;
|
|
123
|
+
this.log("Received message:", type, payload ?? "");
|
|
124
|
+
switch (type) {
|
|
125
|
+
case EventType.WIDGET_READY:
|
|
126
|
+
this.isReady = true;
|
|
127
|
+
this.sendConfig();
|
|
128
|
+
this.options.onReady?.();
|
|
129
|
+
break;
|
|
130
|
+
case EventType.PAYMENT:
|
|
131
|
+
this.handlePaymentResult(payload, this.options.onPayment);
|
|
132
|
+
break;
|
|
133
|
+
case EventType.EXPRESS_PAYMENT:
|
|
134
|
+
this.handlePaymentResult(payload, this.options.onExpressPayment);
|
|
135
|
+
break;
|
|
136
|
+
case EventType.REDIRECT:
|
|
137
|
+
if (payload?.url && this.isValidRedirectUrl(payload.url)) {
|
|
138
|
+
window.location.href = payload.url;
|
|
139
|
+
}
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
isValidRedirectUrl(url) {
|
|
144
|
+
try {
|
|
145
|
+
const parsed = new URL(url);
|
|
146
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
147
|
+
this.log("Invalid redirect URL protocol:", parsed.protocol);
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
if (parsed.origin === window.location.origin) {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
const configRedirectUrl = this.options.config.successRedirectUrl;
|
|
154
|
+
if (configRedirectUrl) {
|
|
155
|
+
const configParsed = new URL(configRedirectUrl);
|
|
156
|
+
if (parsed.origin === configParsed.origin) {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
this.log("Redirect URL not in allowed origins:", url);
|
|
161
|
+
return false;
|
|
162
|
+
} catch {
|
|
163
|
+
this.log("Invalid redirect URL:", url);
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
handlePaymentResult(payload, callback) {
|
|
168
|
+
if (!callback) return;
|
|
169
|
+
callback(payload);
|
|
170
|
+
}
|
|
171
|
+
sendConfig() {
|
|
172
|
+
if (!this.iframe?.contentWindow) return;
|
|
173
|
+
this.iframe.contentWindow.postMessage(
|
|
174
|
+
{
|
|
175
|
+
type: EventType.WIDGET_CONFIG,
|
|
176
|
+
payload: this.options.config
|
|
177
|
+
},
|
|
178
|
+
this.origin
|
|
179
|
+
);
|
|
180
|
+
this.log("Sent config:", sanitizeToken(this.options.config.clientToken));
|
|
181
|
+
}
|
|
182
|
+
log(...args) {
|
|
183
|
+
if (this.options.debug) {
|
|
184
|
+
console.log("[FlintN SDK]", ...args);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
189
|
+
0 && (module.exports = {
|
|
190
|
+
EventType,
|
|
191
|
+
FlintNPayment,
|
|
192
|
+
buildIframeSrc,
|
|
193
|
+
createIframeElement,
|
|
194
|
+
parseOrigin,
|
|
195
|
+
sanitizeToken,
|
|
196
|
+
validateConfig
|
|
197
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var EventType = {
|
|
3
|
+
WIDGET_READY: "WIDGET_READY",
|
|
4
|
+
WIDGET_CONFIG: "WIDGET_CONFIG",
|
|
5
|
+
PAYMENT: "PAYMENT",
|
|
6
|
+
EXPRESS_PAYMENT: "EXPRESS_PAYMENT",
|
|
7
|
+
PAYMENT_SUCCESS: "PAYMENT_SUCCESS",
|
|
8
|
+
PAYMENT_ERROR: "PAYMENT_ERROR",
|
|
9
|
+
PAYMENT_CANCELLED: "PAYMENT_CANCELLED",
|
|
10
|
+
REDIRECT: "REDIRECT"
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/utils.ts
|
|
14
|
+
var parseOrigin = (origin) => {
|
|
15
|
+
try {
|
|
16
|
+
return new URL(origin).origin;
|
|
17
|
+
} catch {
|
|
18
|
+
try {
|
|
19
|
+
return new URL(`https://${origin}`).origin;
|
|
20
|
+
} catch {
|
|
21
|
+
throw new Error(`[FlintN SDK] Invalid origin: ${origin}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
var buildIframeSrc = (origin) => {
|
|
26
|
+
return parseOrigin(origin) + "/";
|
|
27
|
+
};
|
|
28
|
+
var createIframeElement = (src) => {
|
|
29
|
+
const iframe = document.createElement("iframe");
|
|
30
|
+
iframe.src = src;
|
|
31
|
+
iframe.title = "FlintN Checkout";
|
|
32
|
+
iframe.style.cssText = "width: 100%; height: 100%; border: none;";
|
|
33
|
+
iframe.setAttribute("sandbox", "allow-scripts allow-forms allow-popups allow-same-origin");
|
|
34
|
+
iframe.setAttribute("allow", "payment; clipboard-write");
|
|
35
|
+
return iframe;
|
|
36
|
+
};
|
|
37
|
+
var validateConfig = (config) => {
|
|
38
|
+
if (!config.clientToken) {
|
|
39
|
+
throw new Error("[FlintN SDK] clientToken is required");
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var sanitizeToken = (token) => {
|
|
43
|
+
if (token.length <= 20) return token;
|
|
44
|
+
return token.substring(0, 20) + "...";
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// src/flintn-payment.ts
|
|
48
|
+
var DEFAULT_ORIGIN = "https://pay.flintn.com/iframe";
|
|
49
|
+
var FlintNPayment = class {
|
|
50
|
+
constructor(options) {
|
|
51
|
+
this.iframe = null;
|
|
52
|
+
this.container = null;
|
|
53
|
+
this.isReady = false;
|
|
54
|
+
this.messageHandler = null;
|
|
55
|
+
validateConfig(options.config);
|
|
56
|
+
this.options = options;
|
|
57
|
+
this.origin = parseOrigin(options.origin || DEFAULT_ORIGIN);
|
|
58
|
+
this.log("Initialized with origin:", this.origin);
|
|
59
|
+
}
|
|
60
|
+
mount(selector) {
|
|
61
|
+
this.container = typeof selector === "string" ? document.querySelector(selector) : selector;
|
|
62
|
+
if (!this.container) {
|
|
63
|
+
const selectorDescription = typeof selector === "string" ? selector : `<${selector.tagName.toLowerCase()}${selector.id ? ` id="${selector.id}"` : ""}${selector.className ? ` class="${selector.className}"` : ""}>`;
|
|
64
|
+
throw new Error(`[FlintN SDK] Container not found: ${selectorDescription}`);
|
|
65
|
+
}
|
|
66
|
+
this.iframe = createIframeElement(buildIframeSrc(this.origin));
|
|
67
|
+
this.container.appendChild(this.iframe);
|
|
68
|
+
this.messageHandler = this.handleMessage.bind(this);
|
|
69
|
+
window.addEventListener("message", this.messageHandler);
|
|
70
|
+
this.log("Mounted to container");
|
|
71
|
+
}
|
|
72
|
+
unmount() {
|
|
73
|
+
if (this.messageHandler) {
|
|
74
|
+
window.removeEventListener("message", this.messageHandler);
|
|
75
|
+
this.messageHandler = null;
|
|
76
|
+
}
|
|
77
|
+
if (this.iframe && this.container && this.container.contains(this.iframe)) {
|
|
78
|
+
this.container.removeChild(this.iframe);
|
|
79
|
+
}
|
|
80
|
+
this.iframe = null;
|
|
81
|
+
this.isReady = false;
|
|
82
|
+
this.log("Unmounted");
|
|
83
|
+
}
|
|
84
|
+
getIsReady() {
|
|
85
|
+
return this.isReady;
|
|
86
|
+
}
|
|
87
|
+
handleMessage(event) {
|
|
88
|
+
if (event.origin !== this.origin) return;
|
|
89
|
+
const { type, payload } = event.data || {};
|
|
90
|
+
if (!type) return;
|
|
91
|
+
this.log("Received message:", type, payload ?? "");
|
|
92
|
+
switch (type) {
|
|
93
|
+
case EventType.WIDGET_READY:
|
|
94
|
+
this.isReady = true;
|
|
95
|
+
this.sendConfig();
|
|
96
|
+
this.options.onReady?.();
|
|
97
|
+
break;
|
|
98
|
+
case EventType.PAYMENT:
|
|
99
|
+
this.handlePaymentResult(payload, this.options.onPayment);
|
|
100
|
+
break;
|
|
101
|
+
case EventType.EXPRESS_PAYMENT:
|
|
102
|
+
this.handlePaymentResult(payload, this.options.onExpressPayment);
|
|
103
|
+
break;
|
|
104
|
+
case EventType.REDIRECT:
|
|
105
|
+
if (payload?.url && this.isValidRedirectUrl(payload.url)) {
|
|
106
|
+
window.location.href = payload.url;
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
isValidRedirectUrl(url) {
|
|
112
|
+
try {
|
|
113
|
+
const parsed = new URL(url);
|
|
114
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
115
|
+
this.log("Invalid redirect URL protocol:", parsed.protocol);
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (parsed.origin === window.location.origin) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
const configRedirectUrl = this.options.config.successRedirectUrl;
|
|
122
|
+
if (configRedirectUrl) {
|
|
123
|
+
const configParsed = new URL(configRedirectUrl);
|
|
124
|
+
if (parsed.origin === configParsed.origin) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
this.log("Redirect URL not in allowed origins:", url);
|
|
129
|
+
return false;
|
|
130
|
+
} catch {
|
|
131
|
+
this.log("Invalid redirect URL:", url);
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
handlePaymentResult(payload, callback) {
|
|
136
|
+
if (!callback) return;
|
|
137
|
+
callback(payload);
|
|
138
|
+
}
|
|
139
|
+
sendConfig() {
|
|
140
|
+
if (!this.iframe?.contentWindow) return;
|
|
141
|
+
this.iframe.contentWindow.postMessage(
|
|
142
|
+
{
|
|
143
|
+
type: EventType.WIDGET_CONFIG,
|
|
144
|
+
payload: this.options.config
|
|
145
|
+
},
|
|
146
|
+
this.origin
|
|
147
|
+
);
|
|
148
|
+
this.log("Sent config:", sanitizeToken(this.options.config.clientToken));
|
|
149
|
+
}
|
|
150
|
+
log(...args) {
|
|
151
|
+
if (this.options.debug) {
|
|
152
|
+
console.log("[FlintN SDK]", ...args);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
export {
|
|
157
|
+
EventType,
|
|
158
|
+
FlintNPayment,
|
|
159
|
+
buildIframeSrc,
|
|
160
|
+
createIframeElement,
|
|
161
|
+
parseOrigin,
|
|
162
|
+
sanitizeToken,
|
|
163
|
+
validateConfig
|
|
164
|
+
};
|
package/dist/react.d.mts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
declare const EventType: {
|
|
2
|
+
readonly WIDGET_READY: "WIDGET_READY";
|
|
3
|
+
readonly WIDGET_CONFIG: "WIDGET_CONFIG";
|
|
4
|
+
readonly PAYMENT: "PAYMENT";
|
|
5
|
+
readonly EXPRESS_PAYMENT: "EXPRESS_PAYMENT";
|
|
6
|
+
readonly PAYMENT_SUCCESS: "PAYMENT_SUCCESS";
|
|
7
|
+
readonly PAYMENT_ERROR: "PAYMENT_ERROR";
|
|
8
|
+
readonly PAYMENT_CANCELLED: "PAYMENT_CANCELLED";
|
|
9
|
+
readonly REDIRECT: "REDIRECT";
|
|
10
|
+
};
|
|
11
|
+
type TEventType = (typeof EventType)[keyof typeof EventType];
|
|
12
|
+
interface FlintNConfig {
|
|
13
|
+
clientToken: string;
|
|
14
|
+
maxWidth?: string;
|
|
15
|
+
isCardHolderRequired?: boolean;
|
|
16
|
+
successRedirectUrl?: string;
|
|
17
|
+
}
|
|
18
|
+
interface PaymentResult {
|
|
19
|
+
status: 'PAYMENT_SUCCESS' | 'PAYMENT_ERROR' | 'PAYMENT_CANCELLED';
|
|
20
|
+
data?: string;
|
|
21
|
+
error?: {
|
|
22
|
+
code: string;
|
|
23
|
+
message: string;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
interface PaymentError {
|
|
27
|
+
code: string;
|
|
28
|
+
message: string;
|
|
29
|
+
}
|
|
30
|
+
interface FlintNPaymentOptions {
|
|
31
|
+
origin?: string;
|
|
32
|
+
config: FlintNConfig;
|
|
33
|
+
onPayment?: (result: PaymentResult) => void;
|
|
34
|
+
onExpressPayment?: (result: PaymentResult) => void;
|
|
35
|
+
onReady?: () => void;
|
|
36
|
+
onError?: (error: PaymentError) => void;
|
|
37
|
+
debug?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Options for the useFlintNPayment hook.
|
|
42
|
+
*
|
|
43
|
+
* Extends FlintNPaymentOptions but omits `onReady` and `onError` callbacks
|
|
44
|
+
* as they are managed internally by the hook. Use the returned `isReady`
|
|
45
|
+
* and `error` values instead.
|
|
46
|
+
*
|
|
47
|
+
* All other FlintNPaymentOptions fields (config, onPayment, onExpressPayment, debug)
|
|
48
|
+
* can be passed directly.
|
|
49
|
+
*/
|
|
50
|
+
interface UseFlintNPaymentOptions extends Omit<FlintNPaymentOptions, 'onReady' | 'onError'> {
|
|
51
|
+
}
|
|
52
|
+
interface UseFlintNPaymentReturn {
|
|
53
|
+
containerRef: React.RefObject<HTMLDivElement | null>;
|
|
54
|
+
isReady: boolean;
|
|
55
|
+
paymentResult: PaymentResult | null;
|
|
56
|
+
error: PaymentError | null;
|
|
57
|
+
}
|
|
58
|
+
declare function useFlintNPayment(options: UseFlintNPaymentOptions): UseFlintNPaymentReturn;
|
|
59
|
+
|
|
60
|
+
export { EventType, type FlintNConfig, type FlintNPaymentOptions, type PaymentError, type PaymentResult, type TEventType, type UseFlintNPaymentOptions, type UseFlintNPaymentReturn, useFlintNPayment };
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
declare const EventType: {
|
|
2
|
+
readonly WIDGET_READY: "WIDGET_READY";
|
|
3
|
+
readonly WIDGET_CONFIG: "WIDGET_CONFIG";
|
|
4
|
+
readonly PAYMENT: "PAYMENT";
|
|
5
|
+
readonly EXPRESS_PAYMENT: "EXPRESS_PAYMENT";
|
|
6
|
+
readonly PAYMENT_SUCCESS: "PAYMENT_SUCCESS";
|
|
7
|
+
readonly PAYMENT_ERROR: "PAYMENT_ERROR";
|
|
8
|
+
readonly PAYMENT_CANCELLED: "PAYMENT_CANCELLED";
|
|
9
|
+
readonly REDIRECT: "REDIRECT";
|
|
10
|
+
};
|
|
11
|
+
type TEventType = (typeof EventType)[keyof typeof EventType];
|
|
12
|
+
interface FlintNConfig {
|
|
13
|
+
clientToken: string;
|
|
14
|
+
maxWidth?: string;
|
|
15
|
+
isCardHolderRequired?: boolean;
|
|
16
|
+
successRedirectUrl?: string;
|
|
17
|
+
}
|
|
18
|
+
interface PaymentResult {
|
|
19
|
+
status: 'PAYMENT_SUCCESS' | 'PAYMENT_ERROR' | 'PAYMENT_CANCELLED';
|
|
20
|
+
data?: string;
|
|
21
|
+
error?: {
|
|
22
|
+
code: string;
|
|
23
|
+
message: string;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
interface PaymentError {
|
|
27
|
+
code: string;
|
|
28
|
+
message: string;
|
|
29
|
+
}
|
|
30
|
+
interface FlintNPaymentOptions {
|
|
31
|
+
origin?: string;
|
|
32
|
+
config: FlintNConfig;
|
|
33
|
+
onPayment?: (result: PaymentResult) => void;
|
|
34
|
+
onExpressPayment?: (result: PaymentResult) => void;
|
|
35
|
+
onReady?: () => void;
|
|
36
|
+
onError?: (error: PaymentError) => void;
|
|
37
|
+
debug?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Options for the useFlintNPayment hook.
|
|
42
|
+
*
|
|
43
|
+
* Extends FlintNPaymentOptions but omits `onReady` and `onError` callbacks
|
|
44
|
+
* as they are managed internally by the hook. Use the returned `isReady`
|
|
45
|
+
* and `error` values instead.
|
|
46
|
+
*
|
|
47
|
+
* All other FlintNPaymentOptions fields (config, onPayment, onExpressPayment, debug)
|
|
48
|
+
* can be passed directly.
|
|
49
|
+
*/
|
|
50
|
+
interface UseFlintNPaymentOptions extends Omit<FlintNPaymentOptions, 'onReady' | 'onError'> {
|
|
51
|
+
}
|
|
52
|
+
interface UseFlintNPaymentReturn {
|
|
53
|
+
containerRef: React.RefObject<HTMLDivElement | null>;
|
|
54
|
+
isReady: boolean;
|
|
55
|
+
paymentResult: PaymentResult | null;
|
|
56
|
+
error: PaymentError | null;
|
|
57
|
+
}
|
|
58
|
+
declare function useFlintNPayment(options: UseFlintNPaymentOptions): UseFlintNPaymentReturn;
|
|
59
|
+
|
|
60
|
+
export { EventType, type FlintNConfig, type FlintNPaymentOptions, type PaymentError, type PaymentResult, type TEventType, type UseFlintNPaymentOptions, type UseFlintNPaymentReturn, useFlintNPayment };
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/react/index.ts
|
|
21
|
+
var react_exports = {};
|
|
22
|
+
__export(react_exports, {
|
|
23
|
+
EventType: () => EventType,
|
|
24
|
+
useFlintNPayment: () => useFlintNPayment
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(react_exports);
|
|
27
|
+
|
|
28
|
+
// src/react/use-flintn-payment.ts
|
|
29
|
+
var import_react = require("react");
|
|
30
|
+
|
|
31
|
+
// src/types.ts
|
|
32
|
+
var EventType = {
|
|
33
|
+
WIDGET_READY: "WIDGET_READY",
|
|
34
|
+
WIDGET_CONFIG: "WIDGET_CONFIG",
|
|
35
|
+
PAYMENT: "PAYMENT",
|
|
36
|
+
EXPRESS_PAYMENT: "EXPRESS_PAYMENT",
|
|
37
|
+
PAYMENT_SUCCESS: "PAYMENT_SUCCESS",
|
|
38
|
+
PAYMENT_ERROR: "PAYMENT_ERROR",
|
|
39
|
+
PAYMENT_CANCELLED: "PAYMENT_CANCELLED",
|
|
40
|
+
REDIRECT: "REDIRECT"
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// src/utils.ts
|
|
44
|
+
var parseOrigin = (origin) => {
|
|
45
|
+
try {
|
|
46
|
+
return new URL(origin).origin;
|
|
47
|
+
} catch {
|
|
48
|
+
try {
|
|
49
|
+
return new URL(`https://${origin}`).origin;
|
|
50
|
+
} catch {
|
|
51
|
+
throw new Error(`[FlintN SDK] Invalid origin: ${origin}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var buildIframeSrc = (origin) => {
|
|
56
|
+
return parseOrigin(origin) + "/";
|
|
57
|
+
};
|
|
58
|
+
var createIframeElement = (src) => {
|
|
59
|
+
const iframe = document.createElement("iframe");
|
|
60
|
+
iframe.src = src;
|
|
61
|
+
iframe.title = "FlintN Checkout";
|
|
62
|
+
iframe.style.cssText = "width: 100%; height: 100%; border: none;";
|
|
63
|
+
iframe.setAttribute("sandbox", "allow-scripts allow-forms allow-popups allow-same-origin");
|
|
64
|
+
iframe.setAttribute("allow", "payment; clipboard-write");
|
|
65
|
+
return iframe;
|
|
66
|
+
};
|
|
67
|
+
var validateConfig = (config) => {
|
|
68
|
+
if (!config.clientToken) {
|
|
69
|
+
throw new Error("[FlintN SDK] clientToken is required");
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var sanitizeToken = (token) => {
|
|
73
|
+
if (token.length <= 20) return token;
|
|
74
|
+
return token.substring(0, 20) + "...";
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// src/flintn-payment.ts
|
|
78
|
+
var DEFAULT_ORIGIN = "https://pay.flintn.com/iframe";
|
|
79
|
+
var FlintNPayment = class {
|
|
80
|
+
constructor(options) {
|
|
81
|
+
this.iframe = null;
|
|
82
|
+
this.container = null;
|
|
83
|
+
this.isReady = false;
|
|
84
|
+
this.messageHandler = null;
|
|
85
|
+
validateConfig(options.config);
|
|
86
|
+
this.options = options;
|
|
87
|
+
this.origin = parseOrigin(options.origin || DEFAULT_ORIGIN);
|
|
88
|
+
this.log("Initialized with origin:", this.origin);
|
|
89
|
+
}
|
|
90
|
+
mount(selector) {
|
|
91
|
+
this.container = typeof selector === "string" ? document.querySelector(selector) : selector;
|
|
92
|
+
if (!this.container) {
|
|
93
|
+
const selectorDescription = typeof selector === "string" ? selector : `<${selector.tagName.toLowerCase()}${selector.id ? ` id="${selector.id}"` : ""}${selector.className ? ` class="${selector.className}"` : ""}>`;
|
|
94
|
+
throw new Error(`[FlintN SDK] Container not found: ${selectorDescription}`);
|
|
95
|
+
}
|
|
96
|
+
this.iframe = createIframeElement(buildIframeSrc(this.origin));
|
|
97
|
+
this.container.appendChild(this.iframe);
|
|
98
|
+
this.messageHandler = this.handleMessage.bind(this);
|
|
99
|
+
window.addEventListener("message", this.messageHandler);
|
|
100
|
+
this.log("Mounted to container");
|
|
101
|
+
}
|
|
102
|
+
unmount() {
|
|
103
|
+
if (this.messageHandler) {
|
|
104
|
+
window.removeEventListener("message", this.messageHandler);
|
|
105
|
+
this.messageHandler = null;
|
|
106
|
+
}
|
|
107
|
+
if (this.iframe && this.container && this.container.contains(this.iframe)) {
|
|
108
|
+
this.container.removeChild(this.iframe);
|
|
109
|
+
}
|
|
110
|
+
this.iframe = null;
|
|
111
|
+
this.isReady = false;
|
|
112
|
+
this.log("Unmounted");
|
|
113
|
+
}
|
|
114
|
+
getIsReady() {
|
|
115
|
+
return this.isReady;
|
|
116
|
+
}
|
|
117
|
+
handleMessage(event) {
|
|
118
|
+
if (event.origin !== this.origin) return;
|
|
119
|
+
const { type, payload } = event.data || {};
|
|
120
|
+
if (!type) return;
|
|
121
|
+
this.log("Received message:", type, payload ?? "");
|
|
122
|
+
switch (type) {
|
|
123
|
+
case EventType.WIDGET_READY:
|
|
124
|
+
this.isReady = true;
|
|
125
|
+
this.sendConfig();
|
|
126
|
+
this.options.onReady?.();
|
|
127
|
+
break;
|
|
128
|
+
case EventType.PAYMENT:
|
|
129
|
+
this.handlePaymentResult(payload, this.options.onPayment);
|
|
130
|
+
break;
|
|
131
|
+
case EventType.EXPRESS_PAYMENT:
|
|
132
|
+
this.handlePaymentResult(payload, this.options.onExpressPayment);
|
|
133
|
+
break;
|
|
134
|
+
case EventType.REDIRECT:
|
|
135
|
+
if (payload?.url && this.isValidRedirectUrl(payload.url)) {
|
|
136
|
+
window.location.href = payload.url;
|
|
137
|
+
}
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
isValidRedirectUrl(url) {
|
|
142
|
+
try {
|
|
143
|
+
const parsed = new URL(url);
|
|
144
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
145
|
+
this.log("Invalid redirect URL protocol:", parsed.protocol);
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
if (parsed.origin === window.location.origin) {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
const configRedirectUrl = this.options.config.successRedirectUrl;
|
|
152
|
+
if (configRedirectUrl) {
|
|
153
|
+
const configParsed = new URL(configRedirectUrl);
|
|
154
|
+
if (parsed.origin === configParsed.origin) {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
this.log("Redirect URL not in allowed origins:", url);
|
|
159
|
+
return false;
|
|
160
|
+
} catch {
|
|
161
|
+
this.log("Invalid redirect URL:", url);
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
handlePaymentResult(payload, callback) {
|
|
166
|
+
if (!callback) return;
|
|
167
|
+
callback(payload);
|
|
168
|
+
}
|
|
169
|
+
sendConfig() {
|
|
170
|
+
if (!this.iframe?.contentWindow) return;
|
|
171
|
+
this.iframe.contentWindow.postMessage(
|
|
172
|
+
{
|
|
173
|
+
type: EventType.WIDGET_CONFIG,
|
|
174
|
+
payload: this.options.config
|
|
175
|
+
},
|
|
176
|
+
this.origin
|
|
177
|
+
);
|
|
178
|
+
this.log("Sent config:", sanitizeToken(this.options.config.clientToken));
|
|
179
|
+
}
|
|
180
|
+
log(...args) {
|
|
181
|
+
if (this.options.debug) {
|
|
182
|
+
console.log("[FlintN SDK]", ...args);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// src/react/use-flintn-payment.ts
|
|
188
|
+
function useFlintNPayment(options) {
|
|
189
|
+
const containerRef = (0, import_react.useRef)(null);
|
|
190
|
+
const paymentRef = (0, import_react.useRef)(null);
|
|
191
|
+
const onPaymentRef = (0, import_react.useRef)(options.onPayment);
|
|
192
|
+
const onExpressPaymentRef = (0, import_react.useRef)(options.onExpressPayment);
|
|
193
|
+
const [isReady, setIsReady] = (0, import_react.useState)(false);
|
|
194
|
+
const [paymentResult, setPaymentResult] = (0, import_react.useState)(null);
|
|
195
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
196
|
+
(0, import_react.useEffect)(() => {
|
|
197
|
+
onPaymentRef.current = options.onPayment;
|
|
198
|
+
}, [options.onPayment]);
|
|
199
|
+
(0, import_react.useEffect)(() => {
|
|
200
|
+
onExpressPaymentRef.current = options.onExpressPayment;
|
|
201
|
+
}, [options.onExpressPayment]);
|
|
202
|
+
(0, import_react.useEffect)(() => {
|
|
203
|
+
if (!containerRef.current) return;
|
|
204
|
+
setIsReady(false);
|
|
205
|
+
setPaymentResult(null);
|
|
206
|
+
setError(null);
|
|
207
|
+
try {
|
|
208
|
+
paymentRef.current = new FlintNPayment({
|
|
209
|
+
...options,
|
|
210
|
+
onReady: () => setIsReady(true),
|
|
211
|
+
onPayment: (result) => {
|
|
212
|
+
setPaymentResult(result);
|
|
213
|
+
onPaymentRef.current?.(result);
|
|
214
|
+
},
|
|
215
|
+
onExpressPayment: (result) => {
|
|
216
|
+
setPaymentResult(result);
|
|
217
|
+
onExpressPaymentRef.current?.(result);
|
|
218
|
+
},
|
|
219
|
+
onError: (err) => setError(err)
|
|
220
|
+
});
|
|
221
|
+
paymentRef.current.mount(containerRef.current);
|
|
222
|
+
} catch (err) {
|
|
223
|
+
setError({
|
|
224
|
+
code: "INIT_ERROR",
|
|
225
|
+
message: err instanceof Error ? err.message : "Failed to initialize SDK"
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
return () => {
|
|
229
|
+
paymentRef.current?.unmount();
|
|
230
|
+
paymentRef.current = null;
|
|
231
|
+
};
|
|
232
|
+
}, [
|
|
233
|
+
options.origin,
|
|
234
|
+
options.debug,
|
|
235
|
+
options.config.clientToken,
|
|
236
|
+
options.config.maxWidth,
|
|
237
|
+
options.config.isCardHolderRequired,
|
|
238
|
+
options.config.successRedirectUrl
|
|
239
|
+
]);
|
|
240
|
+
return {
|
|
241
|
+
containerRef,
|
|
242
|
+
isReady,
|
|
243
|
+
paymentResult,
|
|
244
|
+
error
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
248
|
+
0 && (module.exports = {
|
|
249
|
+
EventType,
|
|
250
|
+
useFlintNPayment
|
|
251
|
+
});
|
package/dist/react.mjs
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// src/react/use-flintn-payment.ts
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
|
|
4
|
+
// src/types.ts
|
|
5
|
+
var EventType = {
|
|
6
|
+
WIDGET_READY: "WIDGET_READY",
|
|
7
|
+
WIDGET_CONFIG: "WIDGET_CONFIG",
|
|
8
|
+
PAYMENT: "PAYMENT",
|
|
9
|
+
EXPRESS_PAYMENT: "EXPRESS_PAYMENT",
|
|
10
|
+
PAYMENT_SUCCESS: "PAYMENT_SUCCESS",
|
|
11
|
+
PAYMENT_ERROR: "PAYMENT_ERROR",
|
|
12
|
+
PAYMENT_CANCELLED: "PAYMENT_CANCELLED",
|
|
13
|
+
REDIRECT: "REDIRECT"
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/utils.ts
|
|
17
|
+
var parseOrigin = (origin) => {
|
|
18
|
+
try {
|
|
19
|
+
return new URL(origin).origin;
|
|
20
|
+
} catch {
|
|
21
|
+
try {
|
|
22
|
+
return new URL(`https://${origin}`).origin;
|
|
23
|
+
} catch {
|
|
24
|
+
throw new Error(`[FlintN SDK] Invalid origin: ${origin}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var buildIframeSrc = (origin) => {
|
|
29
|
+
return parseOrigin(origin) + "/";
|
|
30
|
+
};
|
|
31
|
+
var createIframeElement = (src) => {
|
|
32
|
+
const iframe = document.createElement("iframe");
|
|
33
|
+
iframe.src = src;
|
|
34
|
+
iframe.title = "FlintN Checkout";
|
|
35
|
+
iframe.style.cssText = "width: 100%; height: 100%; border: none;";
|
|
36
|
+
iframe.setAttribute("sandbox", "allow-scripts allow-forms allow-popups allow-same-origin");
|
|
37
|
+
iframe.setAttribute("allow", "payment; clipboard-write");
|
|
38
|
+
return iframe;
|
|
39
|
+
};
|
|
40
|
+
var validateConfig = (config) => {
|
|
41
|
+
if (!config.clientToken) {
|
|
42
|
+
throw new Error("[FlintN SDK] clientToken is required");
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var sanitizeToken = (token) => {
|
|
46
|
+
if (token.length <= 20) return token;
|
|
47
|
+
return token.substring(0, 20) + "...";
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// src/flintn-payment.ts
|
|
51
|
+
var DEFAULT_ORIGIN = "https://pay.flintn.com/iframe";
|
|
52
|
+
var FlintNPayment = class {
|
|
53
|
+
constructor(options) {
|
|
54
|
+
this.iframe = null;
|
|
55
|
+
this.container = null;
|
|
56
|
+
this.isReady = false;
|
|
57
|
+
this.messageHandler = null;
|
|
58
|
+
validateConfig(options.config);
|
|
59
|
+
this.options = options;
|
|
60
|
+
this.origin = parseOrigin(options.origin || DEFAULT_ORIGIN);
|
|
61
|
+
this.log("Initialized with origin:", this.origin);
|
|
62
|
+
}
|
|
63
|
+
mount(selector) {
|
|
64
|
+
this.container = typeof selector === "string" ? document.querySelector(selector) : selector;
|
|
65
|
+
if (!this.container) {
|
|
66
|
+
const selectorDescription = typeof selector === "string" ? selector : `<${selector.tagName.toLowerCase()}${selector.id ? ` id="${selector.id}"` : ""}${selector.className ? ` class="${selector.className}"` : ""}>`;
|
|
67
|
+
throw new Error(`[FlintN SDK] Container not found: ${selectorDescription}`);
|
|
68
|
+
}
|
|
69
|
+
this.iframe = createIframeElement(buildIframeSrc(this.origin));
|
|
70
|
+
this.container.appendChild(this.iframe);
|
|
71
|
+
this.messageHandler = this.handleMessage.bind(this);
|
|
72
|
+
window.addEventListener("message", this.messageHandler);
|
|
73
|
+
this.log("Mounted to container");
|
|
74
|
+
}
|
|
75
|
+
unmount() {
|
|
76
|
+
if (this.messageHandler) {
|
|
77
|
+
window.removeEventListener("message", this.messageHandler);
|
|
78
|
+
this.messageHandler = null;
|
|
79
|
+
}
|
|
80
|
+
if (this.iframe && this.container && this.container.contains(this.iframe)) {
|
|
81
|
+
this.container.removeChild(this.iframe);
|
|
82
|
+
}
|
|
83
|
+
this.iframe = null;
|
|
84
|
+
this.isReady = false;
|
|
85
|
+
this.log("Unmounted");
|
|
86
|
+
}
|
|
87
|
+
getIsReady() {
|
|
88
|
+
return this.isReady;
|
|
89
|
+
}
|
|
90
|
+
handleMessage(event) {
|
|
91
|
+
if (event.origin !== this.origin) return;
|
|
92
|
+
const { type, payload } = event.data || {};
|
|
93
|
+
if (!type) return;
|
|
94
|
+
this.log("Received message:", type, payload ?? "");
|
|
95
|
+
switch (type) {
|
|
96
|
+
case EventType.WIDGET_READY:
|
|
97
|
+
this.isReady = true;
|
|
98
|
+
this.sendConfig();
|
|
99
|
+
this.options.onReady?.();
|
|
100
|
+
break;
|
|
101
|
+
case EventType.PAYMENT:
|
|
102
|
+
this.handlePaymentResult(payload, this.options.onPayment);
|
|
103
|
+
break;
|
|
104
|
+
case EventType.EXPRESS_PAYMENT:
|
|
105
|
+
this.handlePaymentResult(payload, this.options.onExpressPayment);
|
|
106
|
+
break;
|
|
107
|
+
case EventType.REDIRECT:
|
|
108
|
+
if (payload?.url && this.isValidRedirectUrl(payload.url)) {
|
|
109
|
+
window.location.href = payload.url;
|
|
110
|
+
}
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
isValidRedirectUrl(url) {
|
|
115
|
+
try {
|
|
116
|
+
const parsed = new URL(url);
|
|
117
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
118
|
+
this.log("Invalid redirect URL protocol:", parsed.protocol);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (parsed.origin === window.location.origin) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
const configRedirectUrl = this.options.config.successRedirectUrl;
|
|
125
|
+
if (configRedirectUrl) {
|
|
126
|
+
const configParsed = new URL(configRedirectUrl);
|
|
127
|
+
if (parsed.origin === configParsed.origin) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
this.log("Redirect URL not in allowed origins:", url);
|
|
132
|
+
return false;
|
|
133
|
+
} catch {
|
|
134
|
+
this.log("Invalid redirect URL:", url);
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
handlePaymentResult(payload, callback) {
|
|
139
|
+
if (!callback) return;
|
|
140
|
+
callback(payload);
|
|
141
|
+
}
|
|
142
|
+
sendConfig() {
|
|
143
|
+
if (!this.iframe?.contentWindow) return;
|
|
144
|
+
this.iframe.contentWindow.postMessage(
|
|
145
|
+
{
|
|
146
|
+
type: EventType.WIDGET_CONFIG,
|
|
147
|
+
payload: this.options.config
|
|
148
|
+
},
|
|
149
|
+
this.origin
|
|
150
|
+
);
|
|
151
|
+
this.log("Sent config:", sanitizeToken(this.options.config.clientToken));
|
|
152
|
+
}
|
|
153
|
+
log(...args) {
|
|
154
|
+
if (this.options.debug) {
|
|
155
|
+
console.log("[FlintN SDK]", ...args);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// src/react/use-flintn-payment.ts
|
|
161
|
+
function useFlintNPayment(options) {
|
|
162
|
+
const containerRef = useRef(null);
|
|
163
|
+
const paymentRef = useRef(null);
|
|
164
|
+
const onPaymentRef = useRef(options.onPayment);
|
|
165
|
+
const onExpressPaymentRef = useRef(options.onExpressPayment);
|
|
166
|
+
const [isReady, setIsReady] = useState(false);
|
|
167
|
+
const [paymentResult, setPaymentResult] = useState(null);
|
|
168
|
+
const [error, setError] = useState(null);
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
onPaymentRef.current = options.onPayment;
|
|
171
|
+
}, [options.onPayment]);
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
onExpressPaymentRef.current = options.onExpressPayment;
|
|
174
|
+
}, [options.onExpressPayment]);
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
if (!containerRef.current) return;
|
|
177
|
+
setIsReady(false);
|
|
178
|
+
setPaymentResult(null);
|
|
179
|
+
setError(null);
|
|
180
|
+
try {
|
|
181
|
+
paymentRef.current = new FlintNPayment({
|
|
182
|
+
...options,
|
|
183
|
+
onReady: () => setIsReady(true),
|
|
184
|
+
onPayment: (result) => {
|
|
185
|
+
setPaymentResult(result);
|
|
186
|
+
onPaymentRef.current?.(result);
|
|
187
|
+
},
|
|
188
|
+
onExpressPayment: (result) => {
|
|
189
|
+
setPaymentResult(result);
|
|
190
|
+
onExpressPaymentRef.current?.(result);
|
|
191
|
+
},
|
|
192
|
+
onError: (err) => setError(err)
|
|
193
|
+
});
|
|
194
|
+
paymentRef.current.mount(containerRef.current);
|
|
195
|
+
} catch (err) {
|
|
196
|
+
setError({
|
|
197
|
+
code: "INIT_ERROR",
|
|
198
|
+
message: err instanceof Error ? err.message : "Failed to initialize SDK"
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return () => {
|
|
202
|
+
paymentRef.current?.unmount();
|
|
203
|
+
paymentRef.current = null;
|
|
204
|
+
};
|
|
205
|
+
}, [
|
|
206
|
+
options.origin,
|
|
207
|
+
options.debug,
|
|
208
|
+
options.config.clientToken,
|
|
209
|
+
options.config.maxWidth,
|
|
210
|
+
options.config.isCardHolderRequired,
|
|
211
|
+
options.config.successRedirectUrl
|
|
212
|
+
]);
|
|
213
|
+
return {
|
|
214
|
+
containerRef,
|
|
215
|
+
isReady,
|
|
216
|
+
paymentResult,
|
|
217
|
+
error
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
export {
|
|
221
|
+
EventType,
|
|
222
|
+
useFlintNPayment
|
|
223
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "flintn-checkout",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "FlintN Payment SDK — drop-in iframe checkout for card payments and wallets with localization and theming.",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"flintn",
|
|
10
|
+
"payments",
|
|
11
|
+
"payment",
|
|
12
|
+
"checkout",
|
|
13
|
+
"payment-gateway",
|
|
14
|
+
"payment-widget",
|
|
15
|
+
"payment-iframe",
|
|
16
|
+
"iframe",
|
|
17
|
+
"tokenization",
|
|
18
|
+
"card-payments",
|
|
19
|
+
"3ds",
|
|
20
|
+
"apple-pay",
|
|
21
|
+
"google-pay",
|
|
22
|
+
"web-sdk"
|
|
23
|
+
],
|
|
24
|
+
"author": "FlintN (https://flintn.com), support@flintn.com",
|
|
25
|
+
"homepage": "https://tutorvue-group.gitbook.io/flintn-docs",
|
|
26
|
+
"repository": { "url": "https://github.com" },
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"import": "./dist/index.mjs",
|
|
30
|
+
"require": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts"
|
|
32
|
+
},
|
|
33
|
+
"./react": {
|
|
34
|
+
"import": "./dist/react.mjs",
|
|
35
|
+
"require": "./dist/react.js",
|
|
36
|
+
"types": "./dist/react.d.ts"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"files": ["dist"],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsup",
|
|
42
|
+
"dev": "tsup --watch",
|
|
43
|
+
"typecheck": "tsc --noEmit"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"react": ">=17.0.0"
|
|
47
|
+
},
|
|
48
|
+
"peerDependenciesMeta": {
|
|
49
|
+
"react": { "optional": true }
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/react": "^19.0.0",
|
|
53
|
+
"react": "^19.0.0",
|
|
54
|
+
"tsup": "^8.0.0",
|
|
55
|
+
"typescript": "^5.3.0"
|
|
56
|
+
},
|
|
57
|
+
"license": "MIT"
|
|
58
|
+
}
|