@coincircuit/checkout 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-VYS76EOY.js +336 -0
- package/dist/index.cjs +367 -0
- package/dist/index.d.cts +91 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.js +16 -0
- package/dist/react.cjs +418 -0
- package/dist/react.d.cts +28 -0
- package/dist/react.d.ts +28 -0
- package/dist/react.js +67 -0
- package/dist/types-B0nOHvDT.d.cts +72 -0
- package/dist/types-B0nOHvDT.d.ts +72 -0
- package/package.json +65 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
// src/events.ts
|
|
2
|
+
var CHECKOUT_MESSAGE_TYPES = {
|
|
3
|
+
READY: "coincircuit:ready",
|
|
4
|
+
PAYMENT_COMPLETE: "coincircuit:payment_complete",
|
|
5
|
+
PAYMENT_FAILED: "coincircuit:payment_failed",
|
|
6
|
+
EXPIRED: "coincircuit:expired",
|
|
7
|
+
CLOSE: "coincircuit:close"
|
|
8
|
+
};
|
|
9
|
+
function isCheckoutMessage(value) {
|
|
10
|
+
if (typeof value !== "object" || value === null) return false;
|
|
11
|
+
const msg = value;
|
|
12
|
+
return typeof msg.type === "string" && msg.type.startsWith("coincircuit:");
|
|
13
|
+
}
|
|
14
|
+
function createMessageListener(allowedOrigin, handlers) {
|
|
15
|
+
function onMessage(event) {
|
|
16
|
+
if (event.origin !== allowedOrigin) return;
|
|
17
|
+
const message = event.data;
|
|
18
|
+
if (!isCheckoutMessage(message)) return;
|
|
19
|
+
switch (message.type) {
|
|
20
|
+
case CHECKOUT_MESSAGE_TYPES.READY:
|
|
21
|
+
handlers.onReady?.();
|
|
22
|
+
break;
|
|
23
|
+
case CHECKOUT_MESSAGE_TYPES.PAYMENT_COMPLETE:
|
|
24
|
+
if (message.data) {
|
|
25
|
+
handlers.onPaymentComplete?.(message.data);
|
|
26
|
+
}
|
|
27
|
+
break;
|
|
28
|
+
case CHECKOUT_MESSAGE_TYPES.PAYMENT_FAILED:
|
|
29
|
+
if (message.data) {
|
|
30
|
+
handlers.onPaymentFailed?.(message.data);
|
|
31
|
+
}
|
|
32
|
+
break;
|
|
33
|
+
case CHECKOUT_MESSAGE_TYPES.EXPIRED:
|
|
34
|
+
handlers.onExpired?.();
|
|
35
|
+
break;
|
|
36
|
+
case CHECKOUT_MESSAGE_TYPES.CLOSE:
|
|
37
|
+
handlers.onClose?.();
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
window.addEventListener("message", onMessage);
|
|
42
|
+
return () => window.removeEventListener("message", onMessage);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/utils.ts
|
|
46
|
+
var DEFAULT_BASE_URL = "https://checkout.coincircuit.io";
|
|
47
|
+
function buildCheckoutUrl(baseUrl, reference, theme) {
|
|
48
|
+
const url = new URL(`/pay/${encodeURIComponent(reference)}`, baseUrl);
|
|
49
|
+
url.searchParams.set("embed", "true");
|
|
50
|
+
if (theme) {
|
|
51
|
+
url.searchParams.set("theme", theme);
|
|
52
|
+
}
|
|
53
|
+
return url.toString();
|
|
54
|
+
}
|
|
55
|
+
function extractOrigin(baseUrl) {
|
|
56
|
+
try {
|
|
57
|
+
return new URL(baseUrl).origin;
|
|
58
|
+
} catch {
|
|
59
|
+
return baseUrl;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
var OVERLAY_ID = "coincircuit-checkout-overlay";
|
|
63
|
+
var STYLES_ID = "coincircuit-checkout-styles";
|
|
64
|
+
function injectStyles() {
|
|
65
|
+
if (document.getElementById(STYLES_ID)) return;
|
|
66
|
+
const style = document.createElement("style");
|
|
67
|
+
style.id = STYLES_ID;
|
|
68
|
+
style.textContent = `
|
|
69
|
+
#${OVERLAY_ID} {
|
|
70
|
+
position: fixed;
|
|
71
|
+
inset: 0;
|
|
72
|
+
z-index: 999999;
|
|
73
|
+
display: flex;
|
|
74
|
+
align-items: center;
|
|
75
|
+
justify-content: center;
|
|
76
|
+
background: rgba(0, 0, 0, 0.6);
|
|
77
|
+
backdrop-filter: blur(4px);
|
|
78
|
+
opacity: 0;
|
|
79
|
+
transition: opacity 0.2s ease;
|
|
80
|
+
}
|
|
81
|
+
#${OVERLAY_ID}.cc-visible {
|
|
82
|
+
opacity: 1;
|
|
83
|
+
}
|
|
84
|
+
#${OVERLAY_ID} .cc-modal {
|
|
85
|
+
position: relative;
|
|
86
|
+
width: 100%;
|
|
87
|
+
max-width: 450px;
|
|
88
|
+
height: 90vh;
|
|
89
|
+
max-height: 700px;
|
|
90
|
+
border-radius: 16px;
|
|
91
|
+
overflow: hidden;
|
|
92
|
+
background: #fff;
|
|
93
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.4);
|
|
94
|
+
}
|
|
95
|
+
#${OVERLAY_ID} .cc-close {
|
|
96
|
+
position: absolute;
|
|
97
|
+
top: 12px;
|
|
98
|
+
right: 12px;
|
|
99
|
+
z-index: 10;
|
|
100
|
+
width: 32px;
|
|
101
|
+
height: 32px;
|
|
102
|
+
border: none;
|
|
103
|
+
border-radius: 50%;
|
|
104
|
+
background: rgba(0, 0, 0, 0.5);
|
|
105
|
+
color: #fff;
|
|
106
|
+
font-size: 18px;
|
|
107
|
+
line-height: 1;
|
|
108
|
+
cursor: pointer;
|
|
109
|
+
display: flex;
|
|
110
|
+
align-items: center;
|
|
111
|
+
justify-content: center;
|
|
112
|
+
transition: background 0.15s;
|
|
113
|
+
}
|
|
114
|
+
#${OVERLAY_ID} .cc-close:hover {
|
|
115
|
+
background: rgba(0, 0, 0, 0.7);
|
|
116
|
+
}
|
|
117
|
+
#${OVERLAY_ID} iframe {
|
|
118
|
+
width: 100%;
|
|
119
|
+
height: 100%;
|
|
120
|
+
border: none;
|
|
121
|
+
display: block;
|
|
122
|
+
}
|
|
123
|
+
#${OVERLAY_ID} .cc-spinner {
|
|
124
|
+
position: absolute;
|
|
125
|
+
inset: 0;
|
|
126
|
+
display: flex;
|
|
127
|
+
align-items: center;
|
|
128
|
+
justify-content: center;
|
|
129
|
+
background: #fff;
|
|
130
|
+
transition: opacity 0.2s;
|
|
131
|
+
}
|
|
132
|
+
#${OVERLAY_ID} .cc-spinner div {
|
|
133
|
+
width: 36px;
|
|
134
|
+
height: 36px;
|
|
135
|
+
border: 3px solid #e5e7eb;
|
|
136
|
+
border-top-color: #6366f1;
|
|
137
|
+
border-radius: 50%;
|
|
138
|
+
animation: cc-spin 0.8s linear infinite;
|
|
139
|
+
}
|
|
140
|
+
@keyframes cc-spin { to { transform: rotate(360deg); } }
|
|
141
|
+
@media (max-width: 480px) {
|
|
142
|
+
#${OVERLAY_ID} .cc-modal {
|
|
143
|
+
max-width: 100%;
|
|
144
|
+
height: 100vh;
|
|
145
|
+
max-height: 100vh;
|
|
146
|
+
border-radius: 0;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
`;
|
|
150
|
+
document.head.appendChild(style);
|
|
151
|
+
}
|
|
152
|
+
function createOverlay(checkoutUrl, onCloseClick) {
|
|
153
|
+
injectStyles();
|
|
154
|
+
const overlay = document.createElement("div");
|
|
155
|
+
overlay.id = OVERLAY_ID;
|
|
156
|
+
overlay.addEventListener("click", (e) => {
|
|
157
|
+
if (e.target === overlay) onCloseClick();
|
|
158
|
+
});
|
|
159
|
+
const onKeydown = (e) => {
|
|
160
|
+
if (e.key === "Escape") onCloseClick();
|
|
161
|
+
};
|
|
162
|
+
document.addEventListener("keydown", onKeydown);
|
|
163
|
+
overlay.dataset.keydownAttached = "true";
|
|
164
|
+
overlay._removeKeydown = () => document.removeEventListener("keydown", onKeydown);
|
|
165
|
+
const modal = document.createElement("div");
|
|
166
|
+
modal.className = "cc-modal";
|
|
167
|
+
const spinner = document.createElement("div");
|
|
168
|
+
spinner.className = "cc-spinner";
|
|
169
|
+
spinner.innerHTML = "<div></div>";
|
|
170
|
+
const iframe = document.createElement("iframe");
|
|
171
|
+
iframe.src = checkoutUrl;
|
|
172
|
+
iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox");
|
|
173
|
+
iframe.setAttribute("referrerpolicy", "strict-origin-when-cross-origin");
|
|
174
|
+
iframe.setAttribute("title", "CoinCircuit Checkout");
|
|
175
|
+
iframe.setAttribute("allow", "payment");
|
|
176
|
+
modal.appendChild(spinner);
|
|
177
|
+
modal.appendChild(iframe);
|
|
178
|
+
overlay.appendChild(modal);
|
|
179
|
+
return { overlay, iframe };
|
|
180
|
+
}
|
|
181
|
+
function showOverlay(overlay) {
|
|
182
|
+
document.body.appendChild(overlay);
|
|
183
|
+
document.body.style.overflow = "hidden";
|
|
184
|
+
overlay.offsetHeight;
|
|
185
|
+
overlay.classList.add("cc-visible");
|
|
186
|
+
}
|
|
187
|
+
function removeOverlay(overlay) {
|
|
188
|
+
overlay._removeKeydown?.();
|
|
189
|
+
overlay.classList.remove("cc-visible");
|
|
190
|
+
document.body.style.overflow = "";
|
|
191
|
+
setTimeout(() => {
|
|
192
|
+
overlay.parentNode?.removeChild(overlay);
|
|
193
|
+
}, 200);
|
|
194
|
+
}
|
|
195
|
+
function hideSpinner(overlay) {
|
|
196
|
+
const spinner = overlay.querySelector(".cc-spinner");
|
|
197
|
+
if (spinner) {
|
|
198
|
+
spinner.style.opacity = "0";
|
|
199
|
+
setTimeout(() => spinner.parentNode?.removeChild(spinner), 200);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function showErrorToast(message) {
|
|
203
|
+
const toast = document.createElement("div");
|
|
204
|
+
toast.textContent = message;
|
|
205
|
+
Object.assign(toast.style, {
|
|
206
|
+
position: "fixed",
|
|
207
|
+
top: "20px",
|
|
208
|
+
left: "50%",
|
|
209
|
+
transform: "translateX(-50%) translateY(-10px)",
|
|
210
|
+
zIndex: "9999999",
|
|
211
|
+
background: "#ef4444",
|
|
212
|
+
color: "#fff",
|
|
213
|
+
padding: "12px 24px",
|
|
214
|
+
borderRadius: "8px",
|
|
215
|
+
fontSize: "14px",
|
|
216
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
217
|
+
boxShadow: "0 4px 12px rgba(0,0,0,0.3)",
|
|
218
|
+
opacity: "0",
|
|
219
|
+
transition: "opacity 0.2s, transform 0.2s",
|
|
220
|
+
maxWidth: "90vw",
|
|
221
|
+
textAlign: "center"
|
|
222
|
+
});
|
|
223
|
+
document.body.appendChild(toast);
|
|
224
|
+
toast.offsetHeight;
|
|
225
|
+
toast.style.opacity = "1";
|
|
226
|
+
toast.style.transform = "translateX(-50%) translateY(0)";
|
|
227
|
+
setTimeout(() => {
|
|
228
|
+
toast.style.opacity = "0";
|
|
229
|
+
toast.style.transform = "translateX(-50%) translateY(-10px)";
|
|
230
|
+
setTimeout(() => toast.parentNode?.removeChild(toast), 200);
|
|
231
|
+
}, 6e3);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/checkout.ts
|
|
235
|
+
var CoinCircuitCheckout = class {
|
|
236
|
+
baseUrl;
|
|
237
|
+
origin;
|
|
238
|
+
publicKey;
|
|
239
|
+
overlay = null;
|
|
240
|
+
removeMessageListener = null;
|
|
241
|
+
loadTimeout = null;
|
|
242
|
+
constructor(config = {}) {
|
|
243
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
244
|
+
this.origin = extractOrigin(this.baseUrl);
|
|
245
|
+
this.publicKey = config.publicKey;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Opens the checkout modal overlay.
|
|
249
|
+
*
|
|
250
|
+
* @param options - Configuration for this checkout session.
|
|
251
|
+
* @throws If reference is missing.
|
|
252
|
+
*/
|
|
253
|
+
open(options) {
|
|
254
|
+
this.close();
|
|
255
|
+
if (!options.reference) {
|
|
256
|
+
throw new Error('CoinCircuitCheckout: "reference" is required.');
|
|
257
|
+
}
|
|
258
|
+
const checkoutUrl = buildCheckoutUrl(
|
|
259
|
+
this.baseUrl,
|
|
260
|
+
options.reference,
|
|
261
|
+
options.theme
|
|
262
|
+
);
|
|
263
|
+
const { overlay, iframe } = createOverlay(checkoutUrl, () => {
|
|
264
|
+
options.onClose?.();
|
|
265
|
+
this.close();
|
|
266
|
+
});
|
|
267
|
+
this.overlay = overlay;
|
|
268
|
+
this.loadTimeout = setTimeout(() => {
|
|
269
|
+
showErrorToast(
|
|
270
|
+
"Checkout failed to load. The checkout service may be unavailable or blocked by security policy."
|
|
271
|
+
);
|
|
272
|
+
this.close();
|
|
273
|
+
}, 15e3);
|
|
274
|
+
iframe.addEventListener("error", () => {
|
|
275
|
+
if (this.loadTimeout) clearTimeout(this.loadTimeout);
|
|
276
|
+
showErrorToast("Checkout failed to load. Please try again.");
|
|
277
|
+
this.close();
|
|
278
|
+
});
|
|
279
|
+
this.removeMessageListener = createMessageListener(this.origin, {
|
|
280
|
+
onReady: () => {
|
|
281
|
+
if (this.loadTimeout) {
|
|
282
|
+
clearTimeout(this.loadTimeout);
|
|
283
|
+
this.loadTimeout = null;
|
|
284
|
+
}
|
|
285
|
+
if (this.overlay) hideSpinner(this.overlay);
|
|
286
|
+
options.onReady?.();
|
|
287
|
+
},
|
|
288
|
+
onPaymentComplete: (data) => {
|
|
289
|
+
options.onPaymentComplete?.(data);
|
|
290
|
+
},
|
|
291
|
+
onPaymentFailed: (data) => {
|
|
292
|
+
options.onPaymentFailed?.(data);
|
|
293
|
+
},
|
|
294
|
+
onExpired: () => {
|
|
295
|
+
options.onExpired?.();
|
|
296
|
+
},
|
|
297
|
+
onClose: () => {
|
|
298
|
+
options.onClose?.();
|
|
299
|
+
this.close();
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
showOverlay(overlay);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Closes the checkout modal and cleans up listeners.
|
|
306
|
+
*/
|
|
307
|
+
close() {
|
|
308
|
+
if (this.loadTimeout) {
|
|
309
|
+
clearTimeout(this.loadTimeout);
|
|
310
|
+
this.loadTimeout = null;
|
|
311
|
+
}
|
|
312
|
+
if (this.removeMessageListener) {
|
|
313
|
+
this.removeMessageListener();
|
|
314
|
+
this.removeMessageListener = null;
|
|
315
|
+
}
|
|
316
|
+
if (this.overlay) {
|
|
317
|
+
removeOverlay(this.overlay);
|
|
318
|
+
this.overlay = null;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Returns true if the checkout modal is currently open.
|
|
323
|
+
*/
|
|
324
|
+
isOpen() {
|
|
325
|
+
return this.overlay !== null;
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
export {
|
|
330
|
+
CHECKOUT_MESSAGE_TYPES,
|
|
331
|
+
createMessageListener,
|
|
332
|
+
DEFAULT_BASE_URL,
|
|
333
|
+
buildCheckoutUrl,
|
|
334
|
+
extractOrigin,
|
|
335
|
+
CoinCircuitCheckout
|
|
336
|
+
};
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
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
|
+
CHECKOUT_MESSAGE_TYPES: () => CHECKOUT_MESSAGE_TYPES,
|
|
24
|
+
CoinCircuitCheckout: () => CoinCircuitCheckout,
|
|
25
|
+
DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
|
|
26
|
+
buildCheckoutUrl: () => buildCheckoutUrl,
|
|
27
|
+
createMessageListener: () => createMessageListener,
|
|
28
|
+
extractOrigin: () => extractOrigin
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
|
|
32
|
+
// src/events.ts
|
|
33
|
+
var CHECKOUT_MESSAGE_TYPES = {
|
|
34
|
+
READY: "coincircuit:ready",
|
|
35
|
+
PAYMENT_COMPLETE: "coincircuit:payment_complete",
|
|
36
|
+
PAYMENT_FAILED: "coincircuit:payment_failed",
|
|
37
|
+
EXPIRED: "coincircuit:expired",
|
|
38
|
+
CLOSE: "coincircuit:close"
|
|
39
|
+
};
|
|
40
|
+
function isCheckoutMessage(value) {
|
|
41
|
+
if (typeof value !== "object" || value === null) return false;
|
|
42
|
+
const msg = value;
|
|
43
|
+
return typeof msg.type === "string" && msg.type.startsWith("coincircuit:");
|
|
44
|
+
}
|
|
45
|
+
function createMessageListener(allowedOrigin, handlers) {
|
|
46
|
+
function onMessage(event) {
|
|
47
|
+
if (event.origin !== allowedOrigin) return;
|
|
48
|
+
const message = event.data;
|
|
49
|
+
if (!isCheckoutMessage(message)) return;
|
|
50
|
+
switch (message.type) {
|
|
51
|
+
case CHECKOUT_MESSAGE_TYPES.READY:
|
|
52
|
+
handlers.onReady?.();
|
|
53
|
+
break;
|
|
54
|
+
case CHECKOUT_MESSAGE_TYPES.PAYMENT_COMPLETE:
|
|
55
|
+
if (message.data) {
|
|
56
|
+
handlers.onPaymentComplete?.(message.data);
|
|
57
|
+
}
|
|
58
|
+
break;
|
|
59
|
+
case CHECKOUT_MESSAGE_TYPES.PAYMENT_FAILED:
|
|
60
|
+
if (message.data) {
|
|
61
|
+
handlers.onPaymentFailed?.(message.data);
|
|
62
|
+
}
|
|
63
|
+
break;
|
|
64
|
+
case CHECKOUT_MESSAGE_TYPES.EXPIRED:
|
|
65
|
+
handlers.onExpired?.();
|
|
66
|
+
break;
|
|
67
|
+
case CHECKOUT_MESSAGE_TYPES.CLOSE:
|
|
68
|
+
handlers.onClose?.();
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
window.addEventListener("message", onMessage);
|
|
73
|
+
return () => window.removeEventListener("message", onMessage);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/utils.ts
|
|
77
|
+
var DEFAULT_BASE_URL = "https://checkout.coincircuit.io";
|
|
78
|
+
function buildCheckoutUrl(baseUrl, reference, theme) {
|
|
79
|
+
const url = new URL(`/pay/${encodeURIComponent(reference)}`, baseUrl);
|
|
80
|
+
url.searchParams.set("embed", "true");
|
|
81
|
+
if (theme) {
|
|
82
|
+
url.searchParams.set("theme", theme);
|
|
83
|
+
}
|
|
84
|
+
return url.toString();
|
|
85
|
+
}
|
|
86
|
+
function extractOrigin(baseUrl) {
|
|
87
|
+
try {
|
|
88
|
+
return new URL(baseUrl).origin;
|
|
89
|
+
} catch {
|
|
90
|
+
return baseUrl;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
var OVERLAY_ID = "coincircuit-checkout-overlay";
|
|
94
|
+
var STYLES_ID = "coincircuit-checkout-styles";
|
|
95
|
+
function injectStyles() {
|
|
96
|
+
if (document.getElementById(STYLES_ID)) return;
|
|
97
|
+
const style = document.createElement("style");
|
|
98
|
+
style.id = STYLES_ID;
|
|
99
|
+
style.textContent = `
|
|
100
|
+
#${OVERLAY_ID} {
|
|
101
|
+
position: fixed;
|
|
102
|
+
inset: 0;
|
|
103
|
+
z-index: 999999;
|
|
104
|
+
display: flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
justify-content: center;
|
|
107
|
+
background: rgba(0, 0, 0, 0.6);
|
|
108
|
+
backdrop-filter: blur(4px);
|
|
109
|
+
opacity: 0;
|
|
110
|
+
transition: opacity 0.2s ease;
|
|
111
|
+
}
|
|
112
|
+
#${OVERLAY_ID}.cc-visible {
|
|
113
|
+
opacity: 1;
|
|
114
|
+
}
|
|
115
|
+
#${OVERLAY_ID} .cc-modal {
|
|
116
|
+
position: relative;
|
|
117
|
+
width: 100%;
|
|
118
|
+
max-width: 450px;
|
|
119
|
+
height: 90vh;
|
|
120
|
+
max-height: 700px;
|
|
121
|
+
border-radius: 16px;
|
|
122
|
+
overflow: hidden;
|
|
123
|
+
background: #fff;
|
|
124
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.4);
|
|
125
|
+
}
|
|
126
|
+
#${OVERLAY_ID} .cc-close {
|
|
127
|
+
position: absolute;
|
|
128
|
+
top: 12px;
|
|
129
|
+
right: 12px;
|
|
130
|
+
z-index: 10;
|
|
131
|
+
width: 32px;
|
|
132
|
+
height: 32px;
|
|
133
|
+
border: none;
|
|
134
|
+
border-radius: 50%;
|
|
135
|
+
background: rgba(0, 0, 0, 0.5);
|
|
136
|
+
color: #fff;
|
|
137
|
+
font-size: 18px;
|
|
138
|
+
line-height: 1;
|
|
139
|
+
cursor: pointer;
|
|
140
|
+
display: flex;
|
|
141
|
+
align-items: center;
|
|
142
|
+
justify-content: center;
|
|
143
|
+
transition: background 0.15s;
|
|
144
|
+
}
|
|
145
|
+
#${OVERLAY_ID} .cc-close:hover {
|
|
146
|
+
background: rgba(0, 0, 0, 0.7);
|
|
147
|
+
}
|
|
148
|
+
#${OVERLAY_ID} iframe {
|
|
149
|
+
width: 100%;
|
|
150
|
+
height: 100%;
|
|
151
|
+
border: none;
|
|
152
|
+
display: block;
|
|
153
|
+
}
|
|
154
|
+
#${OVERLAY_ID} .cc-spinner {
|
|
155
|
+
position: absolute;
|
|
156
|
+
inset: 0;
|
|
157
|
+
display: flex;
|
|
158
|
+
align-items: center;
|
|
159
|
+
justify-content: center;
|
|
160
|
+
background: #fff;
|
|
161
|
+
transition: opacity 0.2s;
|
|
162
|
+
}
|
|
163
|
+
#${OVERLAY_ID} .cc-spinner div {
|
|
164
|
+
width: 36px;
|
|
165
|
+
height: 36px;
|
|
166
|
+
border: 3px solid #e5e7eb;
|
|
167
|
+
border-top-color: #6366f1;
|
|
168
|
+
border-radius: 50%;
|
|
169
|
+
animation: cc-spin 0.8s linear infinite;
|
|
170
|
+
}
|
|
171
|
+
@keyframes cc-spin { to { transform: rotate(360deg); } }
|
|
172
|
+
@media (max-width: 480px) {
|
|
173
|
+
#${OVERLAY_ID} .cc-modal {
|
|
174
|
+
max-width: 100%;
|
|
175
|
+
height: 100vh;
|
|
176
|
+
max-height: 100vh;
|
|
177
|
+
border-radius: 0;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
`;
|
|
181
|
+
document.head.appendChild(style);
|
|
182
|
+
}
|
|
183
|
+
function createOverlay(checkoutUrl, onCloseClick) {
|
|
184
|
+
injectStyles();
|
|
185
|
+
const overlay = document.createElement("div");
|
|
186
|
+
overlay.id = OVERLAY_ID;
|
|
187
|
+
overlay.addEventListener("click", (e) => {
|
|
188
|
+
if (e.target === overlay) onCloseClick();
|
|
189
|
+
});
|
|
190
|
+
const onKeydown = (e) => {
|
|
191
|
+
if (e.key === "Escape") onCloseClick();
|
|
192
|
+
};
|
|
193
|
+
document.addEventListener("keydown", onKeydown);
|
|
194
|
+
overlay.dataset.keydownAttached = "true";
|
|
195
|
+
overlay._removeKeydown = () => document.removeEventListener("keydown", onKeydown);
|
|
196
|
+
const modal = document.createElement("div");
|
|
197
|
+
modal.className = "cc-modal";
|
|
198
|
+
const spinner = document.createElement("div");
|
|
199
|
+
spinner.className = "cc-spinner";
|
|
200
|
+
spinner.innerHTML = "<div></div>";
|
|
201
|
+
const iframe = document.createElement("iframe");
|
|
202
|
+
iframe.src = checkoutUrl;
|
|
203
|
+
iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox");
|
|
204
|
+
iframe.setAttribute("referrerpolicy", "strict-origin-when-cross-origin");
|
|
205
|
+
iframe.setAttribute("title", "CoinCircuit Checkout");
|
|
206
|
+
iframe.setAttribute("allow", "payment");
|
|
207
|
+
modal.appendChild(spinner);
|
|
208
|
+
modal.appendChild(iframe);
|
|
209
|
+
overlay.appendChild(modal);
|
|
210
|
+
return { overlay, iframe };
|
|
211
|
+
}
|
|
212
|
+
function showOverlay(overlay) {
|
|
213
|
+
document.body.appendChild(overlay);
|
|
214
|
+
document.body.style.overflow = "hidden";
|
|
215
|
+
overlay.offsetHeight;
|
|
216
|
+
overlay.classList.add("cc-visible");
|
|
217
|
+
}
|
|
218
|
+
function removeOverlay(overlay) {
|
|
219
|
+
overlay._removeKeydown?.();
|
|
220
|
+
overlay.classList.remove("cc-visible");
|
|
221
|
+
document.body.style.overflow = "";
|
|
222
|
+
setTimeout(() => {
|
|
223
|
+
overlay.parentNode?.removeChild(overlay);
|
|
224
|
+
}, 200);
|
|
225
|
+
}
|
|
226
|
+
function hideSpinner(overlay) {
|
|
227
|
+
const spinner = overlay.querySelector(".cc-spinner");
|
|
228
|
+
if (spinner) {
|
|
229
|
+
spinner.style.opacity = "0";
|
|
230
|
+
setTimeout(() => spinner.parentNode?.removeChild(spinner), 200);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function showErrorToast(message) {
|
|
234
|
+
const toast = document.createElement("div");
|
|
235
|
+
toast.textContent = message;
|
|
236
|
+
Object.assign(toast.style, {
|
|
237
|
+
position: "fixed",
|
|
238
|
+
top: "20px",
|
|
239
|
+
left: "50%",
|
|
240
|
+
transform: "translateX(-50%) translateY(-10px)",
|
|
241
|
+
zIndex: "9999999",
|
|
242
|
+
background: "#ef4444",
|
|
243
|
+
color: "#fff",
|
|
244
|
+
padding: "12px 24px",
|
|
245
|
+
borderRadius: "8px",
|
|
246
|
+
fontSize: "14px",
|
|
247
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
248
|
+
boxShadow: "0 4px 12px rgba(0,0,0,0.3)",
|
|
249
|
+
opacity: "0",
|
|
250
|
+
transition: "opacity 0.2s, transform 0.2s",
|
|
251
|
+
maxWidth: "90vw",
|
|
252
|
+
textAlign: "center"
|
|
253
|
+
});
|
|
254
|
+
document.body.appendChild(toast);
|
|
255
|
+
toast.offsetHeight;
|
|
256
|
+
toast.style.opacity = "1";
|
|
257
|
+
toast.style.transform = "translateX(-50%) translateY(0)";
|
|
258
|
+
setTimeout(() => {
|
|
259
|
+
toast.style.opacity = "0";
|
|
260
|
+
toast.style.transform = "translateX(-50%) translateY(-10px)";
|
|
261
|
+
setTimeout(() => toast.parentNode?.removeChild(toast), 200);
|
|
262
|
+
}, 6e3);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/checkout.ts
|
|
266
|
+
var CoinCircuitCheckout = class {
|
|
267
|
+
baseUrl;
|
|
268
|
+
origin;
|
|
269
|
+
publicKey;
|
|
270
|
+
overlay = null;
|
|
271
|
+
removeMessageListener = null;
|
|
272
|
+
loadTimeout = null;
|
|
273
|
+
constructor(config = {}) {
|
|
274
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
275
|
+
this.origin = extractOrigin(this.baseUrl);
|
|
276
|
+
this.publicKey = config.publicKey;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Opens the checkout modal overlay.
|
|
280
|
+
*
|
|
281
|
+
* @param options - Configuration for this checkout session.
|
|
282
|
+
* @throws If reference is missing.
|
|
283
|
+
*/
|
|
284
|
+
open(options) {
|
|
285
|
+
this.close();
|
|
286
|
+
if (!options.reference) {
|
|
287
|
+
throw new Error('CoinCircuitCheckout: "reference" is required.');
|
|
288
|
+
}
|
|
289
|
+
const checkoutUrl = buildCheckoutUrl(
|
|
290
|
+
this.baseUrl,
|
|
291
|
+
options.reference,
|
|
292
|
+
options.theme
|
|
293
|
+
);
|
|
294
|
+
const { overlay, iframe } = createOverlay(checkoutUrl, () => {
|
|
295
|
+
options.onClose?.();
|
|
296
|
+
this.close();
|
|
297
|
+
});
|
|
298
|
+
this.overlay = overlay;
|
|
299
|
+
this.loadTimeout = setTimeout(() => {
|
|
300
|
+
showErrorToast(
|
|
301
|
+
"Checkout failed to load. The checkout service may be unavailable or blocked by security policy."
|
|
302
|
+
);
|
|
303
|
+
this.close();
|
|
304
|
+
}, 15e3);
|
|
305
|
+
iframe.addEventListener("error", () => {
|
|
306
|
+
if (this.loadTimeout) clearTimeout(this.loadTimeout);
|
|
307
|
+
showErrorToast("Checkout failed to load. Please try again.");
|
|
308
|
+
this.close();
|
|
309
|
+
});
|
|
310
|
+
this.removeMessageListener = createMessageListener(this.origin, {
|
|
311
|
+
onReady: () => {
|
|
312
|
+
if (this.loadTimeout) {
|
|
313
|
+
clearTimeout(this.loadTimeout);
|
|
314
|
+
this.loadTimeout = null;
|
|
315
|
+
}
|
|
316
|
+
if (this.overlay) hideSpinner(this.overlay);
|
|
317
|
+
options.onReady?.();
|
|
318
|
+
},
|
|
319
|
+
onPaymentComplete: (data) => {
|
|
320
|
+
options.onPaymentComplete?.(data);
|
|
321
|
+
},
|
|
322
|
+
onPaymentFailed: (data) => {
|
|
323
|
+
options.onPaymentFailed?.(data);
|
|
324
|
+
},
|
|
325
|
+
onExpired: () => {
|
|
326
|
+
options.onExpired?.();
|
|
327
|
+
},
|
|
328
|
+
onClose: () => {
|
|
329
|
+
options.onClose?.();
|
|
330
|
+
this.close();
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
showOverlay(overlay);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Closes the checkout modal and cleans up listeners.
|
|
337
|
+
*/
|
|
338
|
+
close() {
|
|
339
|
+
if (this.loadTimeout) {
|
|
340
|
+
clearTimeout(this.loadTimeout);
|
|
341
|
+
this.loadTimeout = null;
|
|
342
|
+
}
|
|
343
|
+
if (this.removeMessageListener) {
|
|
344
|
+
this.removeMessageListener();
|
|
345
|
+
this.removeMessageListener = null;
|
|
346
|
+
}
|
|
347
|
+
if (this.overlay) {
|
|
348
|
+
removeOverlay(this.overlay);
|
|
349
|
+
this.overlay = null;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Returns true if the checkout modal is currently open.
|
|
354
|
+
*/
|
|
355
|
+
isOpen() {
|
|
356
|
+
return this.overlay !== null;
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
360
|
+
0 && (module.exports = {
|
|
361
|
+
CHECKOUT_MESSAGE_TYPES,
|
|
362
|
+
CoinCircuitCheckout,
|
|
363
|
+
DEFAULT_BASE_URL,
|
|
364
|
+
buildCheckoutUrl,
|
|
365
|
+
createMessageListener,
|
|
366
|
+
extractOrigin
|
|
367
|
+
});
|