@coincircuit/checkout 0.2.1 → 0.3.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.
@@ -43,7 +43,21 @@ function createMessageListener(allowedOrigin, handlers) {
43
43
  }
44
44
 
45
45
  // src/utils.ts
46
- var DEFAULT_BASE_URL = "https://checkout.coincircuit.io";
46
+ var DEFAULT_BASE_URL = "http://localhost:3002";
47
+ var ALLOWED_BASE_URLS = [
48
+ "https://checkout.coincircuit.io",
49
+ "https://sandbox-checkout.coincircuit.io",
50
+ "http://localhost:3002"
51
+ ];
52
+ function validateBaseUrl(url) {
53
+ const normalized = url.replace(/\/+$/, "");
54
+ if (!ALLOWED_BASE_URLS.includes(normalized)) {
55
+ throw new Error(
56
+ `CoinCircuitCheckout: invalid baseUrl "${url}". Must be one of: ${ALLOWED_BASE_URLS.join(", ")}`
57
+ );
58
+ }
59
+ return normalized;
60
+ }
47
61
  function buildCheckoutUrl(baseUrl, reference, theme) {
48
62
  const url = new URL(`/pay/${encodeURIComponent(reference)}`, baseUrl);
49
63
  url.searchParams.set("embed", "true");
@@ -89,6 +103,13 @@ function injectStyles() {
89
103
  border-radius: 16px;
90
104
  background: #fff;
91
105
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.4);
106
+ opacity: 0;
107
+ transform: scale(0.96);
108
+ transition: opacity 0.25s ease, transform 0.25s ease;
109
+ }
110
+ #${OVERLAY_ID} .cc-modal.cc-ready {
111
+ opacity: 1;
112
+ transform: scale(1);
92
113
  }
93
114
  #${OVERLAY_ID} .cc-close {
94
115
  position: fixed;
@@ -126,14 +147,14 @@ function injectStyles() {
126
147
  display: flex;
127
148
  align-items: center;
128
149
  justify-content: center;
129
- background: #fff;
150
+ z-index: 1;
130
151
  transition: opacity 0.2s;
131
152
  }
132
153
  #${OVERLAY_ID} .cc-spinner div {
133
154
  width: 36px;
134
155
  height: 36px;
135
- border: 3px solid #e5e7eb;
136
- border-top-color: #6366f1;
156
+ border: 3px solid rgba(255, 255, 255, 0.3);
157
+ border-top-color: #fff;
137
158
  border-radius: 50%;
138
159
  animation: cc-spin 0.8s linear infinite;
139
160
  }
@@ -166,7 +187,8 @@ function createOverlay(checkoutUrl, onCloseClick) {
166
187
  modal.className = "cc-modal";
167
188
  const spinner = document.createElement("div");
168
189
  spinner.className = "cc-spinner";
169
- spinner.innerHTML = "<div></div>";
190
+ const spinnerRing = document.createElement("div");
191
+ spinner.appendChild(spinnerRing);
170
192
  const iframe = document.createElement("iframe");
171
193
  iframe.src = checkoutUrl;
172
194
  iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox");
@@ -178,8 +200,8 @@ function createOverlay(checkoutUrl, onCloseClick) {
178
200
  closeBtn.setAttribute("aria-label", "Close checkout");
179
201
  closeBtn.textContent = "\u2715";
180
202
  closeBtn.addEventListener("click", onCloseClick);
181
- modal.appendChild(spinner);
182
203
  modal.appendChild(iframe);
204
+ overlay.appendChild(spinner);
183
205
  overlay.appendChild(modal);
184
206
  overlay.appendChild(closeBtn);
185
207
  return { overlay, iframe };
@@ -204,6 +226,10 @@ function hideSpinner(overlay) {
204
226
  spinner.style.opacity = "0";
205
227
  setTimeout(() => spinner.parentNode?.removeChild(spinner), 200);
206
228
  }
229
+ const modal = overlay.querySelector(".cc-modal");
230
+ if (modal) {
231
+ modal.classList.add("cc-ready");
232
+ }
207
233
  }
208
234
  function showErrorToast(message) {
209
235
  const toast = document.createElement("div");
@@ -245,8 +271,9 @@ var CoinCircuitCheckout = class {
245
271
  overlay = null;
246
272
  removeMessageListener = null;
247
273
  loadTimeout = null;
274
+ openedAt = 0;
248
275
  constructor(config = {}) {
249
- this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
276
+ this.baseUrl = config.baseUrl ? validateBaseUrl(config.baseUrl) : DEFAULT_BASE_URL;
250
277
  this.origin = extractOrigin(this.baseUrl);
251
278
  this.publicKey = config.publicKey;
252
279
  }
@@ -261,6 +288,8 @@ var CoinCircuitCheckout = class {
261
288
  if (!options.reference) {
262
289
  throw new Error('CoinCircuitCheckout: "reference" is required.');
263
290
  }
291
+ this.openedAt = performance.now();
292
+ console.log("[CoinCircuit] open() called \u2014 waiting for coincircuit:ready");
264
293
  const checkoutUrl = buildCheckoutUrl(
265
294
  this.baseUrl,
266
295
  options.reference,
@@ -279,11 +308,17 @@ var CoinCircuitCheckout = class {
279
308
  }, 15e3);
280
309
  iframe.addEventListener("error", () => {
281
310
  if (this.loadTimeout) clearTimeout(this.loadTimeout);
311
+ console.error(`[CoinCircuit] iframe error after ${Math.round(performance.now() - this.openedAt)}ms`);
282
312
  showErrorToast("Checkout failed to load. Please try again.");
283
313
  this.close();
284
314
  });
315
+ iframe.addEventListener("load", () => {
316
+ console.log(`[CoinCircuit] iframe DOM loaded at ${Math.round(performance.now() - this.openedAt)}ms (still waiting for coincircuit:ready)`);
317
+ });
285
318
  this.removeMessageListener = createMessageListener(this.origin, {
286
319
  onReady: () => {
320
+ const elapsed = Math.round(performance.now() - this.openedAt);
321
+ console.log(`[CoinCircuit] coincircuit:ready received at ${elapsed}ms`);
287
322
  if (this.loadTimeout) {
288
323
  clearTimeout(this.loadTimeout);
289
324
  this.loadTimeout = null;
@@ -292,6 +327,7 @@ var CoinCircuitCheckout = class {
292
327
  options.onReady?.();
293
328
  },
294
329
  onPaymentComplete: (data) => {
330
+ console.log(`[CoinCircuit] coincircuit:payment_complete at ${Math.round(performance.now() - this.openedAt)}ms`);
295
331
  options.onPaymentComplete?.(data);
296
332
  },
297
333
  onPaymentFailed: (data) => {
package/dist/index.cjs CHANGED
@@ -74,7 +74,21 @@ function createMessageListener(allowedOrigin, handlers) {
74
74
  }
75
75
 
76
76
  // src/utils.ts
77
- var DEFAULT_BASE_URL = "https://checkout.coincircuit.io";
77
+ var DEFAULT_BASE_URL = "http://localhost:3002";
78
+ var ALLOWED_BASE_URLS = [
79
+ "https://checkout.coincircuit.io",
80
+ "https://sandbox-checkout.coincircuit.io",
81
+ "http://localhost:3002"
82
+ ];
83
+ function validateBaseUrl(url) {
84
+ const normalized = url.replace(/\/+$/, "");
85
+ if (!ALLOWED_BASE_URLS.includes(normalized)) {
86
+ throw new Error(
87
+ `CoinCircuitCheckout: invalid baseUrl "${url}". Must be one of: ${ALLOWED_BASE_URLS.join(", ")}`
88
+ );
89
+ }
90
+ return normalized;
91
+ }
78
92
  function buildCheckoutUrl(baseUrl, reference, theme) {
79
93
  const url = new URL(`/pay/${encodeURIComponent(reference)}`, baseUrl);
80
94
  url.searchParams.set("embed", "true");
@@ -120,6 +134,13 @@ function injectStyles() {
120
134
  border-radius: 16px;
121
135
  background: #fff;
122
136
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.4);
137
+ opacity: 0;
138
+ transform: scale(0.96);
139
+ transition: opacity 0.25s ease, transform 0.25s ease;
140
+ }
141
+ #${OVERLAY_ID} .cc-modal.cc-ready {
142
+ opacity: 1;
143
+ transform: scale(1);
123
144
  }
124
145
  #${OVERLAY_ID} .cc-close {
125
146
  position: fixed;
@@ -157,14 +178,14 @@ function injectStyles() {
157
178
  display: flex;
158
179
  align-items: center;
159
180
  justify-content: center;
160
- background: #fff;
181
+ z-index: 1;
161
182
  transition: opacity 0.2s;
162
183
  }
163
184
  #${OVERLAY_ID} .cc-spinner div {
164
185
  width: 36px;
165
186
  height: 36px;
166
- border: 3px solid #e5e7eb;
167
- border-top-color: #6366f1;
187
+ border: 3px solid rgba(255, 255, 255, 0.3);
188
+ border-top-color: #fff;
168
189
  border-radius: 50%;
169
190
  animation: cc-spin 0.8s linear infinite;
170
191
  }
@@ -197,7 +218,8 @@ function createOverlay(checkoutUrl, onCloseClick) {
197
218
  modal.className = "cc-modal";
198
219
  const spinner = document.createElement("div");
199
220
  spinner.className = "cc-spinner";
200
- spinner.innerHTML = "<div></div>";
221
+ const spinnerRing = document.createElement("div");
222
+ spinner.appendChild(spinnerRing);
201
223
  const iframe = document.createElement("iframe");
202
224
  iframe.src = checkoutUrl;
203
225
  iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox");
@@ -209,8 +231,8 @@ function createOverlay(checkoutUrl, onCloseClick) {
209
231
  closeBtn.setAttribute("aria-label", "Close checkout");
210
232
  closeBtn.textContent = "\u2715";
211
233
  closeBtn.addEventListener("click", onCloseClick);
212
- modal.appendChild(spinner);
213
234
  modal.appendChild(iframe);
235
+ overlay.appendChild(spinner);
214
236
  overlay.appendChild(modal);
215
237
  overlay.appendChild(closeBtn);
216
238
  return { overlay, iframe };
@@ -235,6 +257,10 @@ function hideSpinner(overlay) {
235
257
  spinner.style.opacity = "0";
236
258
  setTimeout(() => spinner.parentNode?.removeChild(spinner), 200);
237
259
  }
260
+ const modal = overlay.querySelector(".cc-modal");
261
+ if (modal) {
262
+ modal.classList.add("cc-ready");
263
+ }
238
264
  }
239
265
  function showErrorToast(message) {
240
266
  const toast = document.createElement("div");
@@ -276,8 +302,9 @@ var CoinCircuitCheckout = class {
276
302
  overlay = null;
277
303
  removeMessageListener = null;
278
304
  loadTimeout = null;
305
+ openedAt = 0;
279
306
  constructor(config = {}) {
280
- this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
307
+ this.baseUrl = config.baseUrl ? validateBaseUrl(config.baseUrl) : DEFAULT_BASE_URL;
281
308
  this.origin = extractOrigin(this.baseUrl);
282
309
  this.publicKey = config.publicKey;
283
310
  }
@@ -292,6 +319,8 @@ var CoinCircuitCheckout = class {
292
319
  if (!options.reference) {
293
320
  throw new Error('CoinCircuitCheckout: "reference" is required.');
294
321
  }
322
+ this.openedAt = performance.now();
323
+ console.log("[CoinCircuit] open() called \u2014 waiting for coincircuit:ready");
295
324
  const checkoutUrl = buildCheckoutUrl(
296
325
  this.baseUrl,
297
326
  options.reference,
@@ -310,11 +339,17 @@ var CoinCircuitCheckout = class {
310
339
  }, 15e3);
311
340
  iframe.addEventListener("error", () => {
312
341
  if (this.loadTimeout) clearTimeout(this.loadTimeout);
342
+ console.error(`[CoinCircuit] iframe error after ${Math.round(performance.now() - this.openedAt)}ms`);
313
343
  showErrorToast("Checkout failed to load. Please try again.");
314
344
  this.close();
315
345
  });
346
+ iframe.addEventListener("load", () => {
347
+ console.log(`[CoinCircuit] iframe DOM loaded at ${Math.round(performance.now() - this.openedAt)}ms (still waiting for coincircuit:ready)`);
348
+ });
316
349
  this.removeMessageListener = createMessageListener(this.origin, {
317
350
  onReady: () => {
351
+ const elapsed = Math.round(performance.now() - this.openedAt);
352
+ console.log(`[CoinCircuit] coincircuit:ready received at ${elapsed}ms`);
318
353
  if (this.loadTimeout) {
319
354
  clearTimeout(this.loadTimeout);
320
355
  this.loadTimeout = null;
@@ -323,6 +358,7 @@ var CoinCircuitCheckout = class {
323
358
  options.onReady?.();
324
359
  },
325
360
  onPaymentComplete: (data) => {
361
+ console.log(`[CoinCircuit] coincircuit:payment_complete at ${Math.round(performance.now() - this.openedAt)}ms`);
326
362
  options.onPaymentComplete?.(data);
327
363
  },
328
364
  onPaymentFailed: (data) => {
package/dist/index.d.cts CHANGED
@@ -24,6 +24,7 @@ declare class CoinCircuitCheckout {
24
24
  private overlay;
25
25
  private removeMessageListener;
26
26
  private loadTimeout;
27
+ private openedAt;
27
28
  constructor(config?: CheckoutConfig);
28
29
  /**
29
30
  * Opens the checkout modal overlay.
@@ -78,7 +79,7 @@ interface CheckoutEventHandlers {
78
79
  */
79
80
  declare function createMessageListener(allowedOrigin: string, handlers: CheckoutEventHandlers): () => void;
80
81
 
81
- declare const DEFAULT_BASE_URL = "https://checkout.coincircuit.io";
82
+ declare const DEFAULT_BASE_URL = "http://localhost:3002";
82
83
  /**
83
84
  * Builds the checkout URL with embed flag.
84
85
  */
package/dist/index.d.ts CHANGED
@@ -24,6 +24,7 @@ declare class CoinCircuitCheckout {
24
24
  private overlay;
25
25
  private removeMessageListener;
26
26
  private loadTimeout;
27
+ private openedAt;
27
28
  constructor(config?: CheckoutConfig);
28
29
  /**
29
30
  * Opens the checkout modal overlay.
@@ -78,7 +79,7 @@ interface CheckoutEventHandlers {
78
79
  */
79
80
  declare function createMessageListener(allowedOrigin: string, handlers: CheckoutEventHandlers): () => void;
80
81
 
81
- declare const DEFAULT_BASE_URL = "https://checkout.coincircuit.io";
82
+ declare const DEFAULT_BASE_URL = "http://localhost:3002";
82
83
  /**
83
84
  * Builds the checkout URL with embed flag.
84
85
  */
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  buildCheckoutUrl,
6
6
  createMessageListener,
7
7
  extractOrigin
8
- } from "./chunk-NLDQKWTU.js";
8
+ } from "./chunk-IC5IZ3JH.js";
9
9
  export {
10
10
  CHECKOUT_MESSAGE_TYPES,
11
11
  CoinCircuitCheckout,
package/dist/react.cjs CHANGED
@@ -70,7 +70,21 @@ function createMessageListener(allowedOrigin, handlers) {
70
70
  }
71
71
 
72
72
  // src/utils.ts
73
- var DEFAULT_BASE_URL = "https://checkout.coincircuit.io";
73
+ var DEFAULT_BASE_URL = "http://localhost:3002";
74
+ var ALLOWED_BASE_URLS = [
75
+ "https://checkout.coincircuit.io",
76
+ "https://sandbox-checkout.coincircuit.io",
77
+ "http://localhost:3002"
78
+ ];
79
+ function validateBaseUrl(url) {
80
+ const normalized = url.replace(/\/+$/, "");
81
+ if (!ALLOWED_BASE_URLS.includes(normalized)) {
82
+ throw new Error(
83
+ `CoinCircuitCheckout: invalid baseUrl "${url}". Must be one of: ${ALLOWED_BASE_URLS.join(", ")}`
84
+ );
85
+ }
86
+ return normalized;
87
+ }
74
88
  function buildCheckoutUrl(baseUrl, reference, theme) {
75
89
  const url = new URL(`/pay/${encodeURIComponent(reference)}`, baseUrl);
76
90
  url.searchParams.set("embed", "true");
@@ -116,6 +130,13 @@ function injectStyles() {
116
130
  border-radius: 16px;
117
131
  background: #fff;
118
132
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.4);
133
+ opacity: 0;
134
+ transform: scale(0.96);
135
+ transition: opacity 0.25s ease, transform 0.25s ease;
136
+ }
137
+ #${OVERLAY_ID} .cc-modal.cc-ready {
138
+ opacity: 1;
139
+ transform: scale(1);
119
140
  }
120
141
  #${OVERLAY_ID} .cc-close {
121
142
  position: fixed;
@@ -153,14 +174,14 @@ function injectStyles() {
153
174
  display: flex;
154
175
  align-items: center;
155
176
  justify-content: center;
156
- background: #fff;
177
+ z-index: 1;
157
178
  transition: opacity 0.2s;
158
179
  }
159
180
  #${OVERLAY_ID} .cc-spinner div {
160
181
  width: 36px;
161
182
  height: 36px;
162
- border: 3px solid #e5e7eb;
163
- border-top-color: #6366f1;
183
+ border: 3px solid rgba(255, 255, 255, 0.3);
184
+ border-top-color: #fff;
164
185
  border-radius: 50%;
165
186
  animation: cc-spin 0.8s linear infinite;
166
187
  }
@@ -193,7 +214,8 @@ function createOverlay(checkoutUrl, onCloseClick) {
193
214
  modal.className = "cc-modal";
194
215
  const spinner = document.createElement("div");
195
216
  spinner.className = "cc-spinner";
196
- spinner.innerHTML = "<div></div>";
217
+ const spinnerRing = document.createElement("div");
218
+ spinner.appendChild(spinnerRing);
197
219
  const iframe = document.createElement("iframe");
198
220
  iframe.src = checkoutUrl;
199
221
  iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox");
@@ -205,8 +227,8 @@ function createOverlay(checkoutUrl, onCloseClick) {
205
227
  closeBtn.setAttribute("aria-label", "Close checkout");
206
228
  closeBtn.textContent = "\u2715";
207
229
  closeBtn.addEventListener("click", onCloseClick);
208
- modal.appendChild(spinner);
209
230
  modal.appendChild(iframe);
231
+ overlay.appendChild(spinner);
210
232
  overlay.appendChild(modal);
211
233
  overlay.appendChild(closeBtn);
212
234
  return { overlay, iframe };
@@ -231,6 +253,10 @@ function hideSpinner(overlay) {
231
253
  spinner.style.opacity = "0";
232
254
  setTimeout(() => spinner.parentNode?.removeChild(spinner), 200);
233
255
  }
256
+ const modal = overlay.querySelector(".cc-modal");
257
+ if (modal) {
258
+ modal.classList.add("cc-ready");
259
+ }
234
260
  }
235
261
  function showErrorToast(message) {
236
262
  const toast = document.createElement("div");
@@ -272,8 +298,9 @@ var CoinCircuitCheckout = class {
272
298
  overlay = null;
273
299
  removeMessageListener = null;
274
300
  loadTimeout = null;
301
+ openedAt = 0;
275
302
  constructor(config = {}) {
276
- this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
303
+ this.baseUrl = config.baseUrl ? validateBaseUrl(config.baseUrl) : DEFAULT_BASE_URL;
277
304
  this.origin = extractOrigin(this.baseUrl);
278
305
  this.publicKey = config.publicKey;
279
306
  }
@@ -288,6 +315,8 @@ var CoinCircuitCheckout = class {
288
315
  if (!options.reference) {
289
316
  throw new Error('CoinCircuitCheckout: "reference" is required.');
290
317
  }
318
+ this.openedAt = performance.now();
319
+ console.log("[CoinCircuit] open() called \u2014 waiting for coincircuit:ready");
291
320
  const checkoutUrl = buildCheckoutUrl(
292
321
  this.baseUrl,
293
322
  options.reference,
@@ -306,11 +335,17 @@ var CoinCircuitCheckout = class {
306
335
  }, 15e3);
307
336
  iframe.addEventListener("error", () => {
308
337
  if (this.loadTimeout) clearTimeout(this.loadTimeout);
338
+ console.error(`[CoinCircuit] iframe error after ${Math.round(performance.now() - this.openedAt)}ms`);
309
339
  showErrorToast("Checkout failed to load. Please try again.");
310
340
  this.close();
311
341
  });
342
+ iframe.addEventListener("load", () => {
343
+ console.log(`[CoinCircuit] iframe DOM loaded at ${Math.round(performance.now() - this.openedAt)}ms (still waiting for coincircuit:ready)`);
344
+ });
312
345
  this.removeMessageListener = createMessageListener(this.origin, {
313
346
  onReady: () => {
347
+ const elapsed = Math.round(performance.now() - this.openedAt);
348
+ console.log(`[CoinCircuit] coincircuit:ready received at ${elapsed}ms`);
314
349
  if (this.loadTimeout) {
315
350
  clearTimeout(this.loadTimeout);
316
351
  this.loadTimeout = null;
@@ -319,6 +354,7 @@ var CoinCircuitCheckout = class {
319
354
  options.onReady?.();
320
355
  },
321
356
  onPaymentComplete: (data) => {
357
+ console.log(`[CoinCircuit] coincircuit:payment_complete at ${Math.round(performance.now() - this.openedAt)}ms`);
322
358
  options.onPaymentComplete?.(data);
323
359
  },
324
360
  onPaymentFailed: (data) => {
package/dist/react.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  CoinCircuitCheckout
3
- } from "./chunk-NLDQKWTU.js";
3
+ } from "./chunk-IC5IZ3JH.js";
4
4
 
5
5
  // src/react.ts
6
6
  import { useRef, useCallback, useEffect, createElement } from "react";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coincircuit/checkout",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Embed CoinCircuit checkout in your site via iframe, with vanilla JS and React support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",