@bloque/payments-core 0.0.12 → 0.1.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 CHANGED
@@ -8,15 +8,26 @@ Core JavaScript library for integrating Bloque hosted checkout into any web appl
8
8
  pnpm install @bloque/payments-core
9
9
  ```
10
10
 
11
+ ## Authentication
12
+
13
+ The checkout component accepts two browser-safe credentials:
14
+
15
+ | Option | Required | Description |
16
+ |--------|----------|-------------|
17
+ | `publishableKey` | Yes | Your `pk_test_*` / `pk_live_*` key — identifies the merchant |
18
+ | `clientSecret` | Recommended | A scoped JWT from `bloque.checkout.create()` — authorizes payment execution |
19
+
20
+ > `publicApiKey` is still accepted for backward compatibility but is deprecated in favor of `publishableKey`.
21
+
11
22
  ## Quick Start
12
23
 
13
24
  ```javascript
14
25
  import { createCheckout } from '@bloque/payments-core';
15
26
 
16
- // First, create a payment intent using @bloque/payments on your backend
17
- // Then use the checkout_id to mount the checkout
18
27
  const checkout = createCheckout({
19
28
  checkoutId: 'checkout_123abc',
29
+ publishableKey: 'pk_test_...',
30
+ clientSecret: 'eyJ...', // from your backend
20
31
  container: '#checkout-container',
21
32
  onSuccess: (data) => {
22
33
  console.log('Payment successful!', data);
@@ -30,6 +41,25 @@ const checkout = createCheckout({
30
41
  checkout.destroy();
31
42
  ```
32
43
 
44
+ ## 3D Secure
45
+
46
+ When the hosted checkout triggers a 3DS challenge, the overlay is rendered at the **parent page** level (not inside the checkout iframe) via `postMessage`:
47
+
48
+ ```javascript
49
+ const checkout = createCheckout({
50
+ checkoutId: 'checkout_123abc',
51
+ publishableKey: 'pk_test_...',
52
+ clientSecret: 'eyJ...',
53
+ container: '#checkout-container',
54
+ threeDsAuthType: 'challenge_v2', // sandbox only
55
+ onThreeDSChallenge: () => {
56
+ console.log('3DS challenge overlay is visible');
57
+ },
58
+ onSuccess: (data) => console.log('ok', data),
59
+ onError: (err) => console.error(err),
60
+ });
61
+ ```
62
+
33
63
  ## Documentation
34
64
 
35
65
  For complete documentation, examples, and API reference, visit:
@@ -0,0 +1,2 @@
1
+ /** Mastercard ID Check logo (from buy checkout) as a data URI for 3DS splash UI. */
2
+ export declare const MC_ID_CHECK_LOGO_DATA_URI: string;
@@ -5,6 +5,11 @@ export declare class BloqueCheckout {
5
5
  private options;
6
6
  private messageListener;
7
7
  private isReady;
8
+ /** Full-screen 3DS overlay mounted on document.body (avoids nested iframes). */
9
+ private threeDsOverlay;
10
+ private threeDsTimers;
11
+ private threeDsContentEl;
12
+ private threeDsStatusEl;
8
13
  /**
9
14
  * Initialize global configuration for all BloqueCheckout instances
10
15
  * This allows you to set publicApiKey and mode once instead of passing them to every checkout
@@ -24,7 +29,14 @@ export declare class BloqueCheckout {
24
29
  mount(container: HTMLElement | string): void;
25
30
  private setupMessageListener;
26
31
  private handleCheckoutReady;
32
+ private clearThreeDsTimers;
33
+ private removeThreeDsOverlay;
34
+ private setThreeDsStatus;
35
+ private showMcLogoInContent;
36
+ private handleThreeDSChallenge;
27
37
  private handlePaymentResult;
38
+ private finishThreeDsThenDispatch;
39
+ private dispatchPaymentResult;
28
40
  private handlePaymentError;
29
41
  isCheckoutReady(): boolean;
30
42
  getIframe(): HTMLIFrameElement | null;
package/dist/index.cjs CHANGED
@@ -28,13 +28,26 @@ __webpack_require__.d(__webpack_exports__, {
28
28
  createCheckout: ()=>createCheckout,
29
29
  init: ()=>init
30
30
  });
31
+ const MC_ID_CHECK_LOGO_DATA_URI = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 504.16287 144"><rect width="504.16287" height="144" fill="none"/><rect x="78.4951" y="43.6968" width="31.5" height="56.6064" fill="#ff5f00"/><path d="M224.41386,306a35.9375,35.9375,0,0,1,13.7499-28.3032,36,36,0,1,0,0,56.6064A35.938,35.938,0,0,1,224.41386,306Z" transform="translate(-143.91856 -234)" fill="#eb001b"/><path d="M296.409,306a35.99867,35.99867,0,0,1-58.24521,28.3032,36.00518,36.00518,0,0,0,0-56.6064A35.99867,35.99867,0,0,1,296.409,306Z" transform="translate(-143.91856 -234)" fill="#f79e1b"/><path d="M292.97436,328.3077v-1.1589h.4673v-.2361h-1.1901v.2361h.4675v1.1589Zm2.3105,0v-1.3973h-.3648l-.4196.9611-.4197-.9611h-.365v1.3973h.2576v-1.054l.3935.9087h.26711l.39349-.911v1.0563Z" transform="translate(-143.91856 -234)" fill="#f79e1b"/><polygon points="189.157 108.202 187.824 108.202 187.824 96.055 187.824 71.759 187.824 47.463 187.824 35.316 189.157 35.316 189.157 59.611 189.157 83.907 189.157 108.202"/><path d="M369.97109,328.30869h-3.74122V285.37558h3.74122Zm27.293-42.93311a29.13247,29.13247,0,0,1,9.66016,1.50244,20.54648,20.54648,0,0,1,7.29883,4.29346,18.82,18.82,0,0,1,4.62988,6.76172,25.2182,25.2182,0,0,1,0,17.81738,18.81762,18.81762,0,0,1-4.62988,6.76221,20.53928,20.53928,0,0,1-7.29883,4.29346,29.11984,29.11984,0,0,1-9.66016,1.50244H381.59316V285.37558Zm-11.92968,3.55713v35.81885h11.92968a24.72126,24.72126,0,0,0,8.28028-1.27295,16.77648,16.77648,0,0,0,6.041-3.603,15.0198,15.0198,0,0,0,3.71094-5.64306,22.35805,22.35805,0,0,0,0-14.78125,15.15681,15.15681,0,0,0-3.71094-5.65772,16.49932,16.49932,0,0,0-6.041-3.60351,25.01066,25.01066,0,0,0-8.28028-1.25733Zm78.6289-4.04785a22.97654,22.97654,0,0,1,5.33692.61328,21.59063,21.59063,0,0,1,4.8291,1.76319,19.26143,19.26143,0,0,1,4.0791,2.77539,17.01572,17.01572,0,0,1,3.1123,3.68017l-3.12793,2.085a15.116,15.116,0,0,0-2.62207-3.03564,16.4616,16.4616,0,0,0-3.34277-2.3003,17.16135,17.16135,0,0,0-3.9248-1.45654,18.46749,18.46749,0,0,0-4.33985-.50586,19.03537,19.03537,0,0,0-7.12988,1.31836,16.99574,16.99574,0,0,0-5.73438,3.7417,17.48439,17.48439,0,0,0-3.833,5.7959,20.75862,20.75862,0,0,0,0,14.96533,17.47928,17.47928,0,0,0,3.833,5.7959,16.98717,16.98717,0,0,0,5.73438,3.74121,19.01623,19.01623,0,0,0,7.12988,1.31885,18.82191,18.82191,0,0,0,4.32422-.49072,16.68442,16.68442,0,0,0,3.91016-1.44141,16.42818,16.42818,0,0,0,3.34277-2.2998,14.76727,14.76727,0,0,0,2.62207-3.0669l3.06641,2.23877a18.18515,18.18515,0,0,1-3.17383,3.61865,19.8084,19.8084,0,0,1-4.04785,2.72949,21.03046,21.03046,0,0,1-4.76856,1.73243,23.97561,23.97561,0,0,1-14-1.02735,21.025,21.025,0,0,1-6.97656-4.52343,20.74492,20.74492,0,0,1-4.61523-6.93067,24.37931,24.37931,0,0,1,0-17.74023,20.719,20.719,0,0,1,4.61523-6.94629,21.03249,21.03249,0,0,1,6.97656-4.52344A23.13114,23.13114,0,0,1,463.96327,284.88486Zm28.85742,19.56494a11.0293,11.0293,0,0,1,2.00879-2.45312,10.72962,10.72962,0,0,1,2.499-1.70215,12.47653,12.47653,0,0,1,2.82129-.981,13.73243,13.73243,0,0,1,2.94433-.32226,14.39955,14.39955,0,0,1,4.9375.8125,11.13893,11.13893,0,0,1,3.84863,2.31543,10.13945,10.13945,0,0,1,2.48438,3.66455,12.79658,12.79658,0,0,1,.874,4.83007v17.69483h-3.49609v-16.6211a11.82475,11.82475,0,0,0-.61328-3.92578,7.84633,7.84633,0,0,0-1.80957-2.959,7.9592,7.9592,0,0,0-2.91309-1.85547,11.13969,11.13969,0,0,0-3.92578-.644,10.2964,10.2964,0,0,0-3.78711.68994,9.05794,9.05794,0,0,0-3.0664,1.96289,9.23828,9.23828,0,0,0-2.05469,3.03564,9.78042,9.78042,0,0,0-.752,3.8794v16.4375H489.3246V284.14853h3.49609Zm43.21-5.45849a13.11521,13.11521,0,0,1,5.35058,1.08886,12.68765,12.68765,0,0,1,4.2627,3.03565,14.43555,14.43555,0,0,1,2.83691,4.66162,16.863,16.863,0,0,1,1.07324,5.93408c0,.26563-.00488.5166-.01465.751q-.01611.353-.04589.69043H525.29726a12.40913,12.40913,0,0,0,1.11914,4.477,10.56862,10.56862,0,0,0,2.43847,3.32715,10.27766,10.27766,0,0,0,3.44922,2.07031,12.11186,12.11186,0,0,0,4.15528.70508,12.80778,12.80778,0,0,0,5.42871-1.104,15.199,15.199,0,0,0,4.32324-3.00537l1.87109,2.39209a17.14471,17.14471,0,0,1-2.80566,2.30029,14.98357,14.98357,0,0,1-2.91406,1.47168,14.37333,14.37333,0,0,1-3.02051.76661,22.32086,22.32086,0,0,1-3.09668.21484,15.28005,15.28005,0,0,1-5.82715-1.08887,13.5515,13.5515,0,0,1-4.59961-3.05127,13.79423,13.79423,0,0,1-3.00586-4.69189,16.37017,16.37017,0,0,1-1.07324-6.04151A16.12411,16.12411,0,0,1,522.82851,307.9a14.25392,14.25392,0,0,1,3.00488-4.72266,13.57607,13.57607,0,0,1,4.53906-3.082A14.43287,14.43287,0,0,1,536.03066,298.99131Zm-.06153,3.09716a10.50706,10.50706,0,0,0-4.0332.75147,10.10774,10.10774,0,0,0-3.2041,2.08545,10.81339,10.81339,0,0,0-2.25391,3.17383,12.58891,12.58891,0,0,0-1.11914,4.04834h20.63867a12.478,12.478,0,0,0-1.01269-4.09424,10.31139,10.31139,0,0,0-2.16211-3.17383,9.61078,9.61078,0,0,0-6.85352-2.791Zm33.70117-3.09716a15.28282,15.28282,0,0,1,5.96485,1.1499,12.75921,12.75921,0,0,1,4.67676,3.32763l-2.26954,2.36133a12.53947,12.53947,0,0,0-3.78711-2.62207,11.37194,11.37194,0,0,0-4.67675-.93554,11.053,11.053,0,0,0-4.40039.874,10.80754,10.80754,0,0,0-3.542,2.42285,11.07186,11.07186,0,0,0-2.34668,3.67969,13.22753,13.22753,0,0,0,0,9.292,10.74991,10.74991,0,0,0,5.88868,6.07227,11.053,11.053,0,0,0,4.40039.874,11.31977,11.31977,0,0,0,4.73828-.98145,13.16026,13.16026,0,0,0,3.81738-2.60644l2.17774,2.39209a13.07136,13.07136,0,0,1-4.69239,3.32715,16.10337,16.10337,0,0,1-11.91406.01513,13.92431,13.92431,0,0,1-7.72754-7.835,16.58924,16.58924,0,0,1,0-11.82226,13.88959,13.88959,0,0,1,7.72754-7.85059A15.3168,15.3168,0,0,1,569.6703,298.99131Zm21.15918,12.63476h4.69239l12.14355-12.1748h4.416l-13.73828,13.61621,13.98339,15.24121h-4.44629l-12.35839-13.52393h-4.69239v13.52393h-3.49609V284.14853h3.49609Z" transform="translate(-143.91856 -234)"/></svg>');
31
32
  const DEFAULT_CHECKOUT_URL = 'https://payments.bloque.app/checkout';
33
+ function isUrl(s) {
34
+ return /^https?:\/\//i.test(s.trim());
35
+ }
36
+ function decodeHtmlEntities(encoded) {
37
+ const textarea = document.createElement('textarea');
38
+ textarea.innerHTML = encoded;
39
+ return textarea.value;
40
+ }
32
41
  class BloqueCheckout {
33
42
  static globalConfig = null;
34
43
  iframe = null;
35
44
  options;
36
45
  messageListener = null;
37
46
  isReady = false;
47
+ threeDsOverlay = null;
48
+ threeDsTimers = [];
49
+ threeDsContentEl = null;
50
+ threeDsStatusEl = null;
38
51
  static init(config) {
39
52
  BloqueCheckout.globalConfig = config;
40
53
  }
@@ -43,13 +56,15 @@ class BloqueCheckout {
43
56
  }
44
57
  constructor(options){
45
58
  if (!options.checkoutId) throw new Error('[BloqueCheckout] checkoutId is required');
46
- const publicApiKey = options.publicApiKey || BloqueCheckout.globalConfig?.publicApiKey;
59
+ const publishableKey = options.publishableKey || options.publicApiKey || BloqueCheckout.globalConfig?.publishableKey || BloqueCheckout.globalConfig?.publicApiKey;
47
60
  const mode = options.mode || BloqueCheckout.globalConfig?.mode || 'production';
48
61
  const checkoutUrl = options.checkoutUrl || BloqueCheckout.globalConfig?.checkoutUrl || DEFAULT_CHECKOUT_URL;
49
- if (!publicApiKey) throw new Error('[BloqueCheckout] publicApiKey is required. Either pass it as an option or call BloqueCheckout.init() first.');
62
+ if (!publishableKey) throw new Error('[BloqueCheckout] publishableKey (or publicApiKey) is required. Either pass it as an option or call BloqueCheckout.init() first.');
50
63
  this.options = {
51
64
  checkoutId: options.checkoutId,
52
- publicApiKey,
65
+ clientSecret: options.clientSecret,
66
+ publishableKey,
67
+ publicApiKey: publishableKey,
53
68
  mode,
54
69
  checkoutUrl,
55
70
  appearance: options.appearance,
@@ -61,7 +76,9 @@ class BloqueCheckout {
61
76
  onSuccess: options.onSuccess,
62
77
  onError: options.onError,
63
78
  onPending: options.onPending,
64
- iframeStyles: options.iframeStyles
79
+ iframeStyles: options.iframeStyles,
80
+ three_ds_auth_type: options.three_ds_auth_type,
81
+ onThreeDSChallenge: options.onThreeDSChallenge
65
82
  };
66
83
  }
67
84
  createIframe() {
@@ -104,11 +121,14 @@ class BloqueCheckout {
104
121
  }
105
122
  setupMessageListener() {
106
123
  this.messageListener = (event)=>{
107
- const { type, data, error } = event.data || {};
124
+ const { type, data, error, threeDsData } = event.data || {};
108
125
  switch(type){
109
126
  case 'checkout-ready':
110
127
  this.handleCheckoutReady();
111
128
  break;
129
+ case '3ds-challenge':
130
+ this.handleThreeDSChallenge(threeDsData);
131
+ break;
112
132
  case 'payment-result':
113
133
  if (data) this.handlePaymentResult(data);
114
134
  break;
@@ -126,17 +146,124 @@ class BloqueCheckout {
126
146
  if (this.iframe?.contentWindow) this.iframe.contentWindow.postMessage({
127
147
  type: 'checkout-init',
128
148
  checkoutId: this.options.checkoutId,
149
+ clientSecret: this.options.clientSecret,
150
+ publishableKey: this.options.publishableKey,
129
151
  publicApiKey: this.options.publicApiKey,
130
- mode: this.options.mode
152
+ mode: this.options.mode,
153
+ ...void 0 !== this.options.three_ds_auth_type ? {
154
+ three_ds_auth_type: this.options.three_ds_auth_type
155
+ } : {}
131
156
  }, '*');
132
157
  this.options.onReady?.();
133
158
  }
159
+ clearThreeDsTimers() {
160
+ for (const t of this.threeDsTimers)clearTimeout(t);
161
+ this.threeDsTimers = [];
162
+ }
163
+ removeThreeDsOverlay() {
164
+ this.clearThreeDsTimers();
165
+ this.threeDsContentEl = null;
166
+ this.threeDsStatusEl = null;
167
+ if (this.threeDsOverlay?.parentNode) this.threeDsOverlay.parentNode.removeChild(this.threeDsOverlay);
168
+ this.threeDsOverlay = null;
169
+ }
170
+ setThreeDsStatus(text) {
171
+ if (this.threeDsStatusEl) this.threeDsStatusEl.textContent = text;
172
+ }
173
+ showMcLogoInContent() {
174
+ if (!this.threeDsContentEl) return;
175
+ this.threeDsContentEl.replaceChildren();
176
+ const wrap = document.createElement('div');
177
+ wrap.style.cssText = 'display:flex;align-items:center;justify-content:center;width:100%;min-height:400px;background:#fff;';
178
+ const img = document.createElement('img');
179
+ img.src = MC_ID_CHECK_LOGO_DATA_URI;
180
+ img.alt = 'Mastercard Identity Check';
181
+ img.style.cssText = 'width:256px;max-width:80%;height:auto;';
182
+ wrap.appendChild(img);
183
+ this.threeDsContentEl.appendChild(wrap);
184
+ }
185
+ handleThreeDSChallenge(data) {
186
+ if (!data?.iframe || "u" < typeof document) return;
187
+ this.options.onThreeDSChallenge?.();
188
+ this.removeThreeDsOverlay();
189
+ const root = document.createElement('div');
190
+ root.className = 'bloque-3ds-overlay';
191
+ root.style.cssText = 'position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,0.5);display:flex;flex-direction:column;font-family:system-ui,sans-serif;';
192
+ const panel = document.createElement('div');
193
+ panel.style.cssText = 'margin:auto;width:min(480px,96vw);max-height:90vh;display:flex;flex-direction:column;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 25px 50px -12px rgba(0,0,0,0.25);';
194
+ const header = document.createElement('div');
195
+ header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid #e5e7eb;';
196
+ const title = document.createElement('span');
197
+ title.textContent = 'Verificación segura';
198
+ title.style.cssText = 'font-weight:600;font-size:15px;color:#111827;';
199
+ const closeBtn = document.createElement('button');
200
+ closeBtn.type = 'button';
201
+ closeBtn.setAttribute('aria-label', 'Cerrar');
202
+ closeBtn.textContent = '✕';
203
+ closeBtn.style.cssText = 'border:none;background:transparent;font-size:20px;line-height:1;cursor:pointer;color:#6b7280;padding:4px 8px;';
204
+ closeBtn.addEventListener('click', ()=>{
205
+ this.removeThreeDsOverlay();
206
+ this.options.onError?.('3D Secure verification was cancelled');
207
+ });
208
+ header.appendChild(title);
209
+ header.appendChild(closeBtn);
210
+ const statusEl = document.createElement('div');
211
+ statusEl.style.cssText = 'padding:8px 16px;font-size:13px;color:#4b5563;text-align:center;';
212
+ statusEl.textContent = 'Iniciando verificación segura...';
213
+ const contentEl = document.createElement('div');
214
+ contentEl.style.cssText = 'flex:1;min-height:400px;border-top:1px solid #e5e7eb;';
215
+ panel.appendChild(header);
216
+ panel.appendChild(statusEl);
217
+ panel.appendChild(contentEl);
218
+ root.appendChild(panel);
219
+ document.body.appendChild(root);
220
+ this.threeDsOverlay = root;
221
+ this.threeDsStatusEl = statusEl;
222
+ this.threeDsContentEl = contentEl;
223
+ this.showMcLogoInContent();
224
+ const decoded = decodeHtmlEntities(data.iframe);
225
+ const splashTimer = setTimeout(()=>{
226
+ this.setThreeDsStatus('Complete la verificación de su banco para continuar');
227
+ if (!this.threeDsContentEl) return;
228
+ this.threeDsContentEl.replaceChildren();
229
+ const frameWrap = document.createElement('div');
230
+ frameWrap.style.cssText = 'width:100%;min-height:400px;background:#fff;';
231
+ const iframeEl = document.createElement('iframe');
232
+ iframeEl.title = '3D Secure verification';
233
+ iframeEl.style.cssText = 'width:100%;height:400px;border:0;display:block;';
234
+ if (isUrl(decoded)) {
235
+ iframeEl.src = decoded.trim();
236
+ iframeEl.setAttribute('sandbox', "allow-scripts allow-forms allow-popups allow-same-origin");
237
+ } else {
238
+ iframeEl.srcdoc = decoded;
239
+ iframeEl.setAttribute('sandbox', "allow-scripts allow-forms allow-popups");
240
+ }
241
+ frameWrap.appendChild(iframeEl);
242
+ this.threeDsContentEl.appendChild(frameWrap);
243
+ }, 3000);
244
+ this.threeDsTimers.push(splashTimer);
245
+ }
134
246
  handlePaymentResult(data) {
247
+ if (this.threeDsOverlay) return void this.finishThreeDsThenDispatch(data);
248
+ this.dispatchPaymentResult(data);
249
+ }
250
+ finishThreeDsThenDispatch(data) {
251
+ this.clearThreeDsTimers();
252
+ this.setThreeDsStatus('Autenticación completada, redirigiendo...');
253
+ this.showMcLogoInContent();
254
+ const t = setTimeout(()=>{
255
+ this.removeThreeDsOverlay();
256
+ this.dispatchPaymentResult(data);
257
+ }, 2000);
258
+ this.threeDsTimers.push(t);
259
+ }
260
+ dispatchPaymentResult(data) {
135
261
  if ('approved' === data.status) this.options.onSuccess?.(data);
136
262
  else if ('pending' === data.status) this.options.onPending?.(data);
137
263
  else if ('rejected' === data.status) this.options.onError?.(data.message || 'Payment was rejected');
138
264
  }
139
265
  handlePaymentError(error) {
266
+ if (this.threeDsOverlay) this.removeThreeDsOverlay();
140
267
  this.options.onError?.(error);
141
268
  }
142
269
  isCheckoutReady() {
@@ -154,6 +281,7 @@ class BloqueCheckout {
154
281
  };
155
282
  }
156
283
  destroy() {
284
+ this.removeThreeDsOverlay();
157
285
  if (this.iframe) {
158
286
  this.iframe.remove();
159
287
  this.iframe = null;
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export { BloqueCheckout, createCheckout, init } from './checkout';
2
- export type { AppearanceConfig, BloqueCheckoutOptions, BloqueInitOptions, CheckoutMessage, CheckoutMessageType, PaymentMethod, PaymentResult, } from './types';
2
+ export type { AppearanceConfig, BloqueCheckoutOptions, BloqueInitOptions, CheckoutMessage, CheckoutMessageType, PaymentMethod, PaymentResult, ThreeDSChallengeData, } from './types';
package/dist/index.js CHANGED
@@ -1,10 +1,23 @@
1
+ const MC_ID_CHECK_LOGO_DATA_URI = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 504.16287 144"><rect width="504.16287" height="144" fill="none"/><rect x="78.4951" y="43.6968" width="31.5" height="56.6064" fill="#ff5f00"/><path d="M224.41386,306a35.9375,35.9375,0,0,1,13.7499-28.3032,36,36,0,1,0,0,56.6064A35.938,35.938,0,0,1,224.41386,306Z" transform="translate(-143.91856 -234)" fill="#eb001b"/><path d="M296.409,306a35.99867,35.99867,0,0,1-58.24521,28.3032,36.00518,36.00518,0,0,0,0-56.6064A35.99867,35.99867,0,0,1,296.409,306Z" transform="translate(-143.91856 -234)" fill="#f79e1b"/><path d="M292.97436,328.3077v-1.1589h.4673v-.2361h-1.1901v.2361h.4675v1.1589Zm2.3105,0v-1.3973h-.3648l-.4196.9611-.4197-.9611h-.365v1.3973h.2576v-1.054l.3935.9087h.26711l.39349-.911v1.0563Z" transform="translate(-143.91856 -234)" fill="#f79e1b"/><polygon points="189.157 108.202 187.824 108.202 187.824 96.055 187.824 71.759 187.824 47.463 187.824 35.316 189.157 35.316 189.157 59.611 189.157 83.907 189.157 108.202"/><path d="M369.97109,328.30869h-3.74122V285.37558h3.74122Zm27.293-42.93311a29.13247,29.13247,0,0,1,9.66016,1.50244,20.54648,20.54648,0,0,1,7.29883,4.29346,18.82,18.82,0,0,1,4.62988,6.76172,25.2182,25.2182,0,0,1,0,17.81738,18.81762,18.81762,0,0,1-4.62988,6.76221,20.53928,20.53928,0,0,1-7.29883,4.29346,29.11984,29.11984,0,0,1-9.66016,1.50244H381.59316V285.37558Zm-11.92968,3.55713v35.81885h11.92968a24.72126,24.72126,0,0,0,8.28028-1.27295,16.77648,16.77648,0,0,0,6.041-3.603,15.0198,15.0198,0,0,0,3.71094-5.64306,22.35805,22.35805,0,0,0,0-14.78125,15.15681,15.15681,0,0,0-3.71094-5.65772,16.49932,16.49932,0,0,0-6.041-3.60351,25.01066,25.01066,0,0,0-8.28028-1.25733Zm78.6289-4.04785a22.97654,22.97654,0,0,1,5.33692.61328,21.59063,21.59063,0,0,1,4.8291,1.76319,19.26143,19.26143,0,0,1,4.0791,2.77539,17.01572,17.01572,0,0,1,3.1123,3.68017l-3.12793,2.085a15.116,15.116,0,0,0-2.62207-3.03564,16.4616,16.4616,0,0,0-3.34277-2.3003,17.16135,17.16135,0,0,0-3.9248-1.45654,18.46749,18.46749,0,0,0-4.33985-.50586,19.03537,19.03537,0,0,0-7.12988,1.31836,16.99574,16.99574,0,0,0-5.73438,3.7417,17.48439,17.48439,0,0,0-3.833,5.7959,20.75862,20.75862,0,0,0,0,14.96533,17.47928,17.47928,0,0,0,3.833,5.7959,16.98717,16.98717,0,0,0,5.73438,3.74121,19.01623,19.01623,0,0,0,7.12988,1.31885,18.82191,18.82191,0,0,0,4.32422-.49072,16.68442,16.68442,0,0,0,3.91016-1.44141,16.42818,16.42818,0,0,0,3.34277-2.2998,14.76727,14.76727,0,0,0,2.62207-3.0669l3.06641,2.23877a18.18515,18.18515,0,0,1-3.17383,3.61865,19.8084,19.8084,0,0,1-4.04785,2.72949,21.03046,21.03046,0,0,1-4.76856,1.73243,23.97561,23.97561,0,0,1-14-1.02735,21.025,21.025,0,0,1-6.97656-4.52343,20.74492,20.74492,0,0,1-4.61523-6.93067,24.37931,24.37931,0,0,1,0-17.74023,20.719,20.719,0,0,1,4.61523-6.94629,21.03249,21.03249,0,0,1,6.97656-4.52344A23.13114,23.13114,0,0,1,463.96327,284.88486Zm28.85742,19.56494a11.0293,11.0293,0,0,1,2.00879-2.45312,10.72962,10.72962,0,0,1,2.499-1.70215,12.47653,12.47653,0,0,1,2.82129-.981,13.73243,13.73243,0,0,1,2.94433-.32226,14.39955,14.39955,0,0,1,4.9375.8125,11.13893,11.13893,0,0,1,3.84863,2.31543,10.13945,10.13945,0,0,1,2.48438,3.66455,12.79658,12.79658,0,0,1,.874,4.83007v17.69483h-3.49609v-16.6211a11.82475,11.82475,0,0,0-.61328-3.92578,7.84633,7.84633,0,0,0-1.80957-2.959,7.9592,7.9592,0,0,0-2.91309-1.85547,11.13969,11.13969,0,0,0-3.92578-.644,10.2964,10.2964,0,0,0-3.78711.68994,9.05794,9.05794,0,0,0-3.0664,1.96289,9.23828,9.23828,0,0,0-2.05469,3.03564,9.78042,9.78042,0,0,0-.752,3.8794v16.4375H489.3246V284.14853h3.49609Zm43.21-5.45849a13.11521,13.11521,0,0,1,5.35058,1.08886,12.68765,12.68765,0,0,1,4.2627,3.03565,14.43555,14.43555,0,0,1,2.83691,4.66162,16.863,16.863,0,0,1,1.07324,5.93408c0,.26563-.00488.5166-.01465.751q-.01611.353-.04589.69043H525.29726a12.40913,12.40913,0,0,0,1.11914,4.477,10.56862,10.56862,0,0,0,2.43847,3.32715,10.27766,10.27766,0,0,0,3.44922,2.07031,12.11186,12.11186,0,0,0,4.15528.70508,12.80778,12.80778,0,0,0,5.42871-1.104,15.199,15.199,0,0,0,4.32324-3.00537l1.87109,2.39209a17.14471,17.14471,0,0,1-2.80566,2.30029,14.98357,14.98357,0,0,1-2.91406,1.47168,14.37333,14.37333,0,0,1-3.02051.76661,22.32086,22.32086,0,0,1-3.09668.21484,15.28005,15.28005,0,0,1-5.82715-1.08887,13.5515,13.5515,0,0,1-4.59961-3.05127,13.79423,13.79423,0,0,1-3.00586-4.69189,16.37017,16.37017,0,0,1-1.07324-6.04151A16.12411,16.12411,0,0,1,522.82851,307.9a14.25392,14.25392,0,0,1,3.00488-4.72266,13.57607,13.57607,0,0,1,4.53906-3.082A14.43287,14.43287,0,0,1,536.03066,298.99131Zm-.06153,3.09716a10.50706,10.50706,0,0,0-4.0332.75147,10.10774,10.10774,0,0,0-3.2041,2.08545,10.81339,10.81339,0,0,0-2.25391,3.17383,12.58891,12.58891,0,0,0-1.11914,4.04834h20.63867a12.478,12.478,0,0,0-1.01269-4.09424,10.31139,10.31139,0,0,0-2.16211-3.17383,9.61078,9.61078,0,0,0-6.85352-2.791Zm33.70117-3.09716a15.28282,15.28282,0,0,1,5.96485,1.1499,12.75921,12.75921,0,0,1,4.67676,3.32763l-2.26954,2.36133a12.53947,12.53947,0,0,0-3.78711-2.62207,11.37194,11.37194,0,0,0-4.67675-.93554,11.053,11.053,0,0,0-4.40039.874,10.80754,10.80754,0,0,0-3.542,2.42285,11.07186,11.07186,0,0,0-2.34668,3.67969,13.22753,13.22753,0,0,0,0,9.292,10.74991,10.74991,0,0,0,5.88868,6.07227,11.053,11.053,0,0,0,4.40039.874,11.31977,11.31977,0,0,0,4.73828-.98145,13.16026,13.16026,0,0,0,3.81738-2.60644l2.17774,2.39209a13.07136,13.07136,0,0,1-4.69239,3.32715,16.10337,16.10337,0,0,1-11.91406.01513,13.92431,13.92431,0,0,1-7.72754-7.835,16.58924,16.58924,0,0,1,0-11.82226,13.88959,13.88959,0,0,1,7.72754-7.85059A15.3168,15.3168,0,0,1,569.6703,298.99131Zm21.15918,12.63476h4.69239l12.14355-12.1748h4.416l-13.73828,13.61621,13.98339,15.24121h-4.44629l-12.35839-13.52393h-4.69239v13.52393h-3.49609V284.14853h3.49609Z" transform="translate(-143.91856 -234)"/></svg>');
1
2
  const DEFAULT_CHECKOUT_URL = 'https://payments.bloque.app/checkout';
3
+ function isUrl(s) {
4
+ return /^https?:\/\//i.test(s.trim());
5
+ }
6
+ function decodeHtmlEntities(encoded) {
7
+ const textarea = document.createElement('textarea');
8
+ textarea.innerHTML = encoded;
9
+ return textarea.value;
10
+ }
2
11
  class BloqueCheckout {
3
12
  static globalConfig = null;
4
13
  iframe = null;
5
14
  options;
6
15
  messageListener = null;
7
16
  isReady = false;
17
+ threeDsOverlay = null;
18
+ threeDsTimers = [];
19
+ threeDsContentEl = null;
20
+ threeDsStatusEl = null;
8
21
  static init(config) {
9
22
  BloqueCheckout.globalConfig = config;
10
23
  }
@@ -13,13 +26,15 @@ class BloqueCheckout {
13
26
  }
14
27
  constructor(options){
15
28
  if (!options.checkoutId) throw new Error('[BloqueCheckout] checkoutId is required');
16
- const publicApiKey = options.publicApiKey || BloqueCheckout.globalConfig?.publicApiKey;
29
+ const publishableKey = options.publishableKey || options.publicApiKey || BloqueCheckout.globalConfig?.publishableKey || BloqueCheckout.globalConfig?.publicApiKey;
17
30
  const mode = options.mode || BloqueCheckout.globalConfig?.mode || 'production';
18
31
  const checkoutUrl = options.checkoutUrl || BloqueCheckout.globalConfig?.checkoutUrl || DEFAULT_CHECKOUT_URL;
19
- if (!publicApiKey) throw new Error('[BloqueCheckout] publicApiKey is required. Either pass it as an option or call BloqueCheckout.init() first.');
32
+ if (!publishableKey) throw new Error('[BloqueCheckout] publishableKey (or publicApiKey) is required. Either pass it as an option or call BloqueCheckout.init() first.');
20
33
  this.options = {
21
34
  checkoutId: options.checkoutId,
22
- publicApiKey,
35
+ clientSecret: options.clientSecret,
36
+ publishableKey,
37
+ publicApiKey: publishableKey,
23
38
  mode,
24
39
  checkoutUrl,
25
40
  appearance: options.appearance,
@@ -31,7 +46,9 @@ class BloqueCheckout {
31
46
  onSuccess: options.onSuccess,
32
47
  onError: options.onError,
33
48
  onPending: options.onPending,
34
- iframeStyles: options.iframeStyles
49
+ iframeStyles: options.iframeStyles,
50
+ three_ds_auth_type: options.three_ds_auth_type,
51
+ onThreeDSChallenge: options.onThreeDSChallenge
35
52
  };
36
53
  }
37
54
  createIframe() {
@@ -74,11 +91,14 @@ class BloqueCheckout {
74
91
  }
75
92
  setupMessageListener() {
76
93
  this.messageListener = (event)=>{
77
- const { type, data, error } = event.data || {};
94
+ const { type, data, error, threeDsData } = event.data || {};
78
95
  switch(type){
79
96
  case 'checkout-ready':
80
97
  this.handleCheckoutReady();
81
98
  break;
99
+ case '3ds-challenge':
100
+ this.handleThreeDSChallenge(threeDsData);
101
+ break;
82
102
  case 'payment-result':
83
103
  if (data) this.handlePaymentResult(data);
84
104
  break;
@@ -96,17 +116,124 @@ class BloqueCheckout {
96
116
  if (this.iframe?.contentWindow) this.iframe.contentWindow.postMessage({
97
117
  type: 'checkout-init',
98
118
  checkoutId: this.options.checkoutId,
119
+ clientSecret: this.options.clientSecret,
120
+ publishableKey: this.options.publishableKey,
99
121
  publicApiKey: this.options.publicApiKey,
100
- mode: this.options.mode
122
+ mode: this.options.mode,
123
+ ...void 0 !== this.options.three_ds_auth_type ? {
124
+ three_ds_auth_type: this.options.three_ds_auth_type
125
+ } : {}
101
126
  }, '*');
102
127
  this.options.onReady?.();
103
128
  }
129
+ clearThreeDsTimers() {
130
+ for (const t of this.threeDsTimers)clearTimeout(t);
131
+ this.threeDsTimers = [];
132
+ }
133
+ removeThreeDsOverlay() {
134
+ this.clearThreeDsTimers();
135
+ this.threeDsContentEl = null;
136
+ this.threeDsStatusEl = null;
137
+ if (this.threeDsOverlay?.parentNode) this.threeDsOverlay.parentNode.removeChild(this.threeDsOverlay);
138
+ this.threeDsOverlay = null;
139
+ }
140
+ setThreeDsStatus(text) {
141
+ if (this.threeDsStatusEl) this.threeDsStatusEl.textContent = text;
142
+ }
143
+ showMcLogoInContent() {
144
+ if (!this.threeDsContentEl) return;
145
+ this.threeDsContentEl.replaceChildren();
146
+ const wrap = document.createElement('div');
147
+ wrap.style.cssText = 'display:flex;align-items:center;justify-content:center;width:100%;min-height:400px;background:#fff;';
148
+ const img = document.createElement('img');
149
+ img.src = MC_ID_CHECK_LOGO_DATA_URI;
150
+ img.alt = 'Mastercard Identity Check';
151
+ img.style.cssText = 'width:256px;max-width:80%;height:auto;';
152
+ wrap.appendChild(img);
153
+ this.threeDsContentEl.appendChild(wrap);
154
+ }
155
+ handleThreeDSChallenge(data) {
156
+ if (!data?.iframe || "u" < typeof document) return;
157
+ this.options.onThreeDSChallenge?.();
158
+ this.removeThreeDsOverlay();
159
+ const root = document.createElement('div');
160
+ root.className = 'bloque-3ds-overlay';
161
+ root.style.cssText = 'position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,0.5);display:flex;flex-direction:column;font-family:system-ui,sans-serif;';
162
+ const panel = document.createElement('div');
163
+ panel.style.cssText = 'margin:auto;width:min(480px,96vw);max-height:90vh;display:flex;flex-direction:column;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 25px 50px -12px rgba(0,0,0,0.25);';
164
+ const header = document.createElement('div');
165
+ header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid #e5e7eb;';
166
+ const title = document.createElement('span');
167
+ title.textContent = 'Verificación segura';
168
+ title.style.cssText = 'font-weight:600;font-size:15px;color:#111827;';
169
+ const closeBtn = document.createElement('button');
170
+ closeBtn.type = 'button';
171
+ closeBtn.setAttribute('aria-label', 'Cerrar');
172
+ closeBtn.textContent = '✕';
173
+ closeBtn.style.cssText = 'border:none;background:transparent;font-size:20px;line-height:1;cursor:pointer;color:#6b7280;padding:4px 8px;';
174
+ closeBtn.addEventListener('click', ()=>{
175
+ this.removeThreeDsOverlay();
176
+ this.options.onError?.('3D Secure verification was cancelled');
177
+ });
178
+ header.appendChild(title);
179
+ header.appendChild(closeBtn);
180
+ const statusEl = document.createElement('div');
181
+ statusEl.style.cssText = 'padding:8px 16px;font-size:13px;color:#4b5563;text-align:center;';
182
+ statusEl.textContent = 'Iniciando verificación segura...';
183
+ const contentEl = document.createElement('div');
184
+ contentEl.style.cssText = 'flex:1;min-height:400px;border-top:1px solid #e5e7eb;';
185
+ panel.appendChild(header);
186
+ panel.appendChild(statusEl);
187
+ panel.appendChild(contentEl);
188
+ root.appendChild(panel);
189
+ document.body.appendChild(root);
190
+ this.threeDsOverlay = root;
191
+ this.threeDsStatusEl = statusEl;
192
+ this.threeDsContentEl = contentEl;
193
+ this.showMcLogoInContent();
194
+ const decoded = decodeHtmlEntities(data.iframe);
195
+ const splashTimer = setTimeout(()=>{
196
+ this.setThreeDsStatus('Complete la verificación de su banco para continuar');
197
+ if (!this.threeDsContentEl) return;
198
+ this.threeDsContentEl.replaceChildren();
199
+ const frameWrap = document.createElement('div');
200
+ frameWrap.style.cssText = 'width:100%;min-height:400px;background:#fff;';
201
+ const iframeEl = document.createElement('iframe');
202
+ iframeEl.title = '3D Secure verification';
203
+ iframeEl.style.cssText = 'width:100%;height:400px;border:0;display:block;';
204
+ if (isUrl(decoded)) {
205
+ iframeEl.src = decoded.trim();
206
+ iframeEl.setAttribute('sandbox', "allow-scripts allow-forms allow-popups allow-same-origin");
207
+ } else {
208
+ iframeEl.srcdoc = decoded;
209
+ iframeEl.setAttribute('sandbox', "allow-scripts allow-forms allow-popups");
210
+ }
211
+ frameWrap.appendChild(iframeEl);
212
+ this.threeDsContentEl.appendChild(frameWrap);
213
+ }, 3000);
214
+ this.threeDsTimers.push(splashTimer);
215
+ }
104
216
  handlePaymentResult(data) {
217
+ if (this.threeDsOverlay) return void this.finishThreeDsThenDispatch(data);
218
+ this.dispatchPaymentResult(data);
219
+ }
220
+ finishThreeDsThenDispatch(data) {
221
+ this.clearThreeDsTimers();
222
+ this.setThreeDsStatus('Autenticación completada, redirigiendo...');
223
+ this.showMcLogoInContent();
224
+ const t = setTimeout(()=>{
225
+ this.removeThreeDsOverlay();
226
+ this.dispatchPaymentResult(data);
227
+ }, 2000);
228
+ this.threeDsTimers.push(t);
229
+ }
230
+ dispatchPaymentResult(data) {
105
231
  if ('approved' === data.status) this.options.onSuccess?.(data);
106
232
  else if ('pending' === data.status) this.options.onPending?.(data);
107
233
  else if ('rejected' === data.status) this.options.onError?.(data.message || 'Payment was rejected');
108
234
  }
109
235
  handlePaymentError(error) {
236
+ if (this.threeDsOverlay) this.removeThreeDsOverlay();
110
237
  this.options.onError?.(error);
111
238
  }
112
239
  isCheckoutReady() {
@@ -124,6 +251,7 @@ class BloqueCheckout {
124
251
  };
125
252
  }
126
253
  destroy() {
254
+ this.removeThreeDsOverlay();
127
255
  if (this.iframe) {
128
256
  this.iframe.remove();
129
257
  this.iframe = null;
@@ -1,15 +1,19 @@
1
+ /**
2
+ * Internal reference types. NOT exported from the @bloque/payments-core barrel.
3
+ * The canonical versions live in @bloque/payments.
4
+ */
1
5
  import type { PaymentSubmitPayload } from './payment-submit';
2
6
  export type PaymentMethodType = 'card' | 'pse' | 'cash';
3
7
  export interface CreatePaymentParams {
4
- checkoutId?: string;
8
+ paymentUrn: string;
5
9
  payment: PaymentSubmitPayload;
6
10
  }
7
11
  export interface PaymentResponse {
8
12
  id: string;
9
13
  object: 'payment';
10
- status: 'pending' | 'processing' | 'completed' | 'failed';
14
+ status: 'approved' | 'rejected' | 'pending';
15
+ message: string;
11
16
  amount: number;
12
17
  currency: string;
13
18
  created_at: string;
14
- updated_at: string;
15
19
  }
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type PaymentMethod = 'card' | 'pse';
1
+ export type PaymentMethod = 'card' | 'pse' | 'cash';
2
2
  export interface AppearanceConfig {
3
3
  /**
4
4
  * Primary color for buttons and accents
@@ -18,9 +18,11 @@ export interface AppearanceConfig {
18
18
  }
19
19
  export interface BloqueInitOptions {
20
20
  /**
21
- * Your Bloque public API key
21
+ * Your Bloque publishable API key (pk_live_... or pk_test_...)
22
22
  */
23
- publicApiKey: string;
23
+ publishableKey?: string;
24
+ /** @deprecated Use publishableKey instead */
25
+ publicApiKey?: string;
24
26
  /**
25
27
  * Operation mode
26
28
  * @default 'production'
@@ -38,8 +40,14 @@ export interface BloqueCheckoutOptions {
38
40
  */
39
41
  checkoutId: string;
40
42
  /**
41
- * Your Bloque public API key (optional if you called BloqueCheckout.init())
43
+ * Checkout-scoped JWT returned from the create checkout API
44
+ */
45
+ clientSecret?: string;
46
+ /**
47
+ * Your Bloque publishable key (pk_live_... or pk_test_...)
42
48
  */
49
+ publishableKey?: string;
50
+ /** @deprecated Use publishableKey instead */
43
51
  publicApiKey?: string;
44
52
  /**
45
53
  * Operation mode (optional if you called BloqueCheckout.init())
@@ -88,6 +96,14 @@ export interface BloqueCheckoutOptions {
88
96
  * Custom CSS styles to apply to the iframe
89
97
  */
90
98
  iframeStyles?: Record<string, string>;
99
+ /**
100
+ * Sandbox-only Wompi 3DS scenario (e.g. challenge_v2). Forwarded to hosted checkout via checkout-init.
101
+ */
102
+ three_ds_auth_type?: string;
103
+ /**
104
+ * Called when the hosted checkout starts a 3DS challenge (overlay is shown in the parent page).
105
+ */
106
+ onThreeDSChallenge?: () => void;
91
107
  }
92
108
  export interface PaymentResult {
93
109
  payment_id: string;
@@ -98,10 +114,14 @@ export interface PaymentResult {
98
114
  reference: string;
99
115
  created_at: string;
100
116
  }
101
- export type CheckoutMessageType = 'checkout-ready' | 'checkout-init' | 'payment-result' | 'payment-error';
117
+ export type CheckoutMessageType = 'checkout-ready' | 'checkout-init' | 'payment-result' | 'payment-error' | '3ds-challenge';
118
+ export interface ThreeDSChallengeData {
119
+ iframe: string;
120
+ }
102
121
  export interface CheckoutMessage {
103
122
  type: CheckoutMessageType;
104
123
  checkoutId?: string;
105
124
  data?: PaymentResult;
106
125
  error?: string;
126
+ threeDsData?: ThreeDSChallengeData;
107
127
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bloque/payments-core",
3
- "version": "0.0.12",
3
+ "version": "0.1.1",
4
4
  "description": "Core utilities and types for Bloque Payments.",
5
5
  "type": "module",
6
6
  "keywords": [