@morefin/cashier-bootstrapper 0.1.8 → 0.1.9
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 +49 -9
- package/dist/index.d.ts +19 -1
- package/dist/index.js +258 -18
- package/dist/types.d.ts +16 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,9 +85,39 @@ new CashierBootstrapper('#cashier-root', {
|
|
|
85
85
|
});
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
+
### Transaction Result Callbacks
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { CashierBootstrapper } from '@morefin/cashier-bootstrapper';
|
|
92
|
+
|
|
93
|
+
new CashierBootstrapper('#cashier-root', {
|
|
94
|
+
properties: { environment: CASHIER_ENVIRONMENT },
|
|
95
|
+
callbacks: {
|
|
96
|
+
onSuccess: ({ status, transactionId }) => {
|
|
97
|
+
console.log('Cashier success callback', status, transactionId);
|
|
98
|
+
},
|
|
99
|
+
onFailure: ({ status, message, transactionId }) => {
|
|
100
|
+
console.log('Cashier failure callback', status, message, transactionId);
|
|
101
|
+
},
|
|
102
|
+
onCancel: ({ status, transactionId }) => {
|
|
103
|
+
console.log('Cashier cancel callback', status, transactionId);
|
|
104
|
+
},
|
|
105
|
+
onValidationFailed: ({ message }) => {
|
|
106
|
+
console.log('Cashier validation failed callback', message);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Callback mapping:
|
|
113
|
+
- `onSuccess`: result status `APPROVED` or `DECLINED`
|
|
114
|
+
- `onFailure`: result status `INVALID` (or explicit cashier error event)
|
|
115
|
+
- `onCancel`: result status `CANCELLED`
|
|
116
|
+
- `onValidationFailed`: `/validate` request failed; `message` is forwarded from cashier
|
|
117
|
+
|
|
88
118
|
## Examples Folder
|
|
89
119
|
|
|
90
|
-
-
|
|
120
|
+
- Repository examples live in `../examples/cashier-bootstrapper` (outside this package directory), including `../examples/cashier-bootstrapper/npm`.
|
|
91
121
|
- `src/example-usage.ts` contains additional snippets for reference.
|
|
92
122
|
|
|
93
123
|
## API
|
|
@@ -99,6 +129,7 @@ Iframe-based embed that loads the cashier URL and exposes runtime controls once
|
|
|
99
129
|
**Parameters:**
|
|
100
130
|
- `container: string | HTMLElement | null | undefined` - Where the iframe is appended. If omitted, `config.properties.container` is used (falling back to `document.body`).
|
|
101
131
|
- `config?: CashierIframeConfig` - Request params and iframe properties. `properties.environment` is required.
|
|
132
|
+
- `config.callbacks?: CashierCallbacks` - Host callback handlers for cashier result events.
|
|
102
133
|
- `onReady?: (api: CashierIframeApi) => void` - Called when the iframe loads; exposes helpers:
|
|
103
134
|
- `api.setCss(css: string)` – inject CSS inside the cashier iframe
|
|
104
135
|
- `api.updateData(data: object)` – post updated `APP_DATA` to the cashier
|
|
@@ -138,14 +169,23 @@ interface CashierIframeProperties {
|
|
|
138
169
|
interface CashierIframeConfig {
|
|
139
170
|
requestParams?: CashierRequestParams;
|
|
140
171
|
properties?: CashierIframeProperties;
|
|
172
|
+
callbacks?: CashierCallbacks;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
interface CashierResultCallbackPayload {
|
|
176
|
+
status?: string;
|
|
177
|
+
transactionId?: string;
|
|
178
|
+
message?: string;
|
|
141
179
|
}
|
|
142
|
-
```
|
|
143
180
|
|
|
144
|
-
|
|
181
|
+
interface CashierValidationFailedPayload {
|
|
182
|
+
message: string;
|
|
183
|
+
}
|
|
145
184
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
185
|
+
interface CashierCallbacks {
|
|
186
|
+
onSuccess?: (payload: CashierResultCallbackPayload) => void;
|
|
187
|
+
onFailure?: (payload: CashierResultCallbackPayload) => void;
|
|
188
|
+
onCancel?: (payload: CashierResultCallbackPayload) => void;
|
|
189
|
+
onValidationFailed?: (payload: CashierValidationFailedPayload) => void;
|
|
190
|
+
}
|
|
191
|
+
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CashierIframeApi, CashierIframeConfig } from './types';
|
|
2
|
-
export type { CashierEnvironment, CashierRequestParams, CashierIframeApi, CashierIframeConfig, CashierIframeProperties } from './types';
|
|
2
|
+
export type { CashierCallbacks, CashierEnvironment, CashierRequestParams, CashierIframeApi, CashierIframeConfig, CashierIframeProperties, CashierResultCallbackPayload, CashierValidationFailedPayload } from './types';
|
|
3
3
|
type FingerprintJSGlobal = {
|
|
4
4
|
load: () => Promise<{
|
|
5
5
|
get: () => Promise<{
|
|
@@ -14,16 +14,34 @@ declare global {
|
|
|
14
14
|
}
|
|
15
15
|
export declare class CashierBootstrapper {
|
|
16
16
|
private iframe?;
|
|
17
|
+
private redirectOverlayElement?;
|
|
18
|
+
private redirectOverlayPanel?;
|
|
19
|
+
private redirectOverlayIframe?;
|
|
20
|
+
private overlayPositionSyncHandler?;
|
|
17
21
|
private origin;
|
|
18
22
|
private ready;
|
|
19
23
|
private readonly fullConfig;
|
|
20
24
|
private readonly host;
|
|
25
|
+
private readonly allowedHostnames;
|
|
21
26
|
private readonly onReady?;
|
|
27
|
+
private readonly callbacks?;
|
|
28
|
+
private readonly messageListener;
|
|
22
29
|
constructor(container: string | HTMLElement | null | undefined, config?: CashierIframeConfig, onReady?: (api: CashierIframeApi) => void);
|
|
23
30
|
private bootstrapIframe;
|
|
24
31
|
private createIframeShell;
|
|
25
32
|
private handleLoad;
|
|
26
33
|
private postMessage;
|
|
34
|
+
private handleIframeMessage;
|
|
35
|
+
private handleCashierResultEvent;
|
|
36
|
+
private handleCashierValidationFailedEvent;
|
|
37
|
+
private handleCashierRedirect;
|
|
38
|
+
private openProviderRedirectOverlay;
|
|
39
|
+
private closeProviderRedirectOverlay;
|
|
40
|
+
private syncProviderRedirectOverlayToCashierIframe;
|
|
41
|
+
private normalizeRedirectTarget;
|
|
42
|
+
private normalizeResultStatus;
|
|
43
|
+
private asString;
|
|
44
|
+
private invokeCallback;
|
|
27
45
|
/**
|
|
28
46
|
* API exposed to host pages for runtime control.
|
|
29
47
|
*/
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
const DEFAULT_IFRAME_ALLOW = 'geolocation *;camera *;payment *;clipboard-read *;clipboard-write *;autoplay *;microphone *;fullscreen *;accelerometer *;magnetometer *;gyroscope *;picture-in-picture *;otp-credentials *;';
|
|
2
2
|
const CASHIER_PATH = '/cashier';
|
|
3
3
|
const FINGERPRINT_CDN = 'https://cdn.jsdelivr.net/npm/@fingerprintjs/fingerprintjs@4/dist/fp.min.js';
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
const CASHIER_REDIRECT_EVENT = 'CASHIER_REDIRECT';
|
|
5
|
+
const TOP_URL_REPLACE_EVENT = 'TOP_URL_REPLACE';
|
|
6
|
+
const CASHIER_IFRAME_OVERLAY_CLOSE_EVENT = 'CASHIER_IFRAME_OVERLAY_CLOSE';
|
|
7
|
+
const CASHIER_RESULT_EVENT = 'CASHIER_RESULT';
|
|
8
|
+
const CASHIER_VALIDATION_FAILED_EVENT = 'CASHIER_VALIDATION_FAILED';
|
|
9
|
+
const CASHIER_ENVIRONMENT_CONFIG = {
|
|
10
|
+
production: {
|
|
11
|
+
host: 'https://api.morefin.com',
|
|
12
|
+
allowedHostnames: ['api.morefin.com']
|
|
13
|
+
},
|
|
14
|
+
uat: {
|
|
15
|
+
host: 'https://uat-api.morefin.com',
|
|
16
|
+
allowedHostnames: ['uat-api.morefin.com']
|
|
17
|
+
},
|
|
18
|
+
local: {
|
|
19
|
+
host: 'http://localhost:7082',
|
|
20
|
+
allowedHostnames: ['localhost', '127.0.0.1']
|
|
21
|
+
},
|
|
22
|
+
local4200: {
|
|
23
|
+
host: 'http://localhost:4200',
|
|
24
|
+
allowedHostnames: ['localhost', '127.0.0.1']
|
|
25
|
+
}
|
|
7
26
|
};
|
|
8
|
-
const ALLOWED_CASHIER_DOMAINS = new Set(['api.morefin.com', 'uat-api.morefin.com']);
|
|
9
27
|
let fpLoaderPromise;
|
|
10
28
|
function loadFingerprintLibrary() {
|
|
11
29
|
if (typeof window === 'undefined') {
|
|
@@ -62,7 +80,7 @@ function buildQueryString(requestParams, fingerprint) {
|
|
|
62
80
|
}
|
|
63
81
|
if (Array.isArray(requestParams.predefinedAmounts)) {
|
|
64
82
|
requestParams.predefinedAmounts.forEach(amount => {
|
|
65
|
-
query.append('
|
|
83
|
+
query.append('predefinedAmounts', String(amount));
|
|
66
84
|
});
|
|
67
85
|
}
|
|
68
86
|
if (requestParams.layout != null && requestParams.layout !== '' && requestParams.layout !== 'undefined') {
|
|
@@ -90,39 +108,43 @@ function resolveContainer(container) {
|
|
|
90
108
|
}
|
|
91
109
|
return document.body;
|
|
92
110
|
}
|
|
93
|
-
function
|
|
94
|
-
const
|
|
95
|
-
if (!
|
|
111
|
+
function resolveEnvironmentConfig(environment) {
|
|
112
|
+
const config = CASHIER_ENVIRONMENT_CONFIG[environment];
|
|
113
|
+
if (!config) {
|
|
96
114
|
throw new Error(`Unsupported cashier environment "${environment}"`);
|
|
97
115
|
}
|
|
98
|
-
return
|
|
116
|
+
return config;
|
|
99
117
|
}
|
|
100
|
-
function parseAndVerifyCashierUrl(url) {
|
|
118
|
+
function parseAndVerifyCashierUrl(url, allowedHostnames) {
|
|
101
119
|
const parsedUrl = new URL(url);
|
|
102
|
-
if (!
|
|
120
|
+
if (!allowedHostnames.includes(parsedUrl.hostname)) {
|
|
103
121
|
throw new Error(`Cashier domain "${parsedUrl.hostname}" is not allowed`);
|
|
104
122
|
}
|
|
105
123
|
return parsedUrl;
|
|
106
124
|
}
|
|
107
|
-
function buildIframeUrl(host, requestParams, fingerprint) {
|
|
125
|
+
function buildIframeUrl(host, requestParams, allowedHostnames, fingerprint) {
|
|
108
126
|
const trimmedHost = host.endsWith('/') ? host.slice(0, -1) : host;
|
|
109
127
|
const url = `${trimmedHost}${CASHIER_PATH}${buildQueryString(requestParams, fingerprint)}`;
|
|
110
|
-
parseAndVerifyCashierUrl(url);
|
|
128
|
+
parseAndVerifyCashierUrl(url, allowedHostnames);
|
|
111
129
|
return url;
|
|
112
130
|
}
|
|
113
|
-
function getOriginFromUrl(url) {
|
|
114
|
-
return parseAndVerifyCashierUrl(url).origin;
|
|
131
|
+
function getOriginFromUrl(url, allowedHostnames) {
|
|
132
|
+
return parseAndVerifyCashierUrl(url, allowedHostnames).origin;
|
|
115
133
|
}
|
|
116
134
|
export class CashierBootstrapper {
|
|
117
135
|
constructor(container, config = {}, onReady) {
|
|
118
136
|
this.origin = '*';
|
|
119
137
|
this.ready = false;
|
|
138
|
+
this.messageListener = (event) => this.handleIframeMessage(event);
|
|
120
139
|
this.onReady = onReady;
|
|
140
|
+
this.callbacks = config.callbacks;
|
|
121
141
|
const environment = config.properties?.environment;
|
|
122
142
|
if (!environment) {
|
|
123
143
|
throw new Error('Cashier environment is required to bootstrap the iframe');
|
|
124
144
|
}
|
|
125
|
-
|
|
145
|
+
const environmentConfig = resolveEnvironmentConfig(environment);
|
|
146
|
+
this.host = environmentConfig.host;
|
|
147
|
+
this.allowedHostnames = environmentConfig.allowedHostnames;
|
|
126
148
|
this.fullConfig = {
|
|
127
149
|
requestParams: { ...config.requestParams },
|
|
128
150
|
properties: {
|
|
@@ -138,12 +160,15 @@ export class CashierBootstrapper {
|
|
|
138
160
|
};
|
|
139
161
|
const target = resolveContainer(container ?? this.fullConfig.properties.container);
|
|
140
162
|
this.iframe = this.createIframeShell(target);
|
|
163
|
+
if (typeof window !== 'undefined') {
|
|
164
|
+
window.addEventListener('message', this.messageListener);
|
|
165
|
+
}
|
|
141
166
|
void this.bootstrapIframe();
|
|
142
167
|
}
|
|
143
168
|
async bootstrapIframe() {
|
|
144
169
|
const fp = await generateFingerprint();
|
|
145
|
-
const src = buildIframeUrl(this.host, this.fullConfig.requestParams, fp);
|
|
146
|
-
this.origin = getOriginFromUrl(src);
|
|
170
|
+
const src = buildIframeUrl(this.host, this.fullConfig.requestParams, this.allowedHostnames, fp);
|
|
171
|
+
this.origin = getOriginFromUrl(src, this.allowedHostnames);
|
|
147
172
|
if (this.iframe) {
|
|
148
173
|
this.iframe.src = src;
|
|
149
174
|
this.iframe.onload = () => this.handleLoad();
|
|
@@ -186,6 +211,221 @@ export class CashierBootstrapper {
|
|
|
186
211
|
}
|
|
187
212
|
this.iframe.contentWindow.postMessage({ eventType, payload }, this.origin);
|
|
188
213
|
}
|
|
214
|
+
handleIframeMessage(event) {
|
|
215
|
+
const sourceIsMainCashierIframe = !!this.iframe?.contentWindow && event.source === this.iframe.contentWindow;
|
|
216
|
+
const sourceIsOverlayIframe = !!this.redirectOverlayIframe?.contentWindow && event.source === this.redirectOverlayIframe.contentWindow;
|
|
217
|
+
if (!sourceIsMainCashierIframe && !sourceIsOverlayIframe) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (event.origin !== this.origin) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (!event.data || typeof event.data !== 'object') {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const { eventType, payload } = event.data;
|
|
227
|
+
if (eventType === CASHIER_IFRAME_OVERLAY_CLOSE_EVENT) {
|
|
228
|
+
if (sourceIsOverlayIframe) {
|
|
229
|
+
this.closeProviderRedirectOverlay();
|
|
230
|
+
}
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (!sourceIsMainCashierIframe) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (eventType === TOP_URL_REPLACE_EVENT) {
|
|
237
|
+
this.handleCashierRedirect({ url: payload?.url, target: 'window' });
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (eventType === CASHIER_RESULT_EVENT) {
|
|
241
|
+
this.handleCashierResultEvent(payload);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (eventType === CASHIER_VALIDATION_FAILED_EVENT) {
|
|
245
|
+
this.handleCashierValidationFailedEvent(payload);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (eventType === CASHIER_REDIRECT_EVENT) {
|
|
249
|
+
this.handleCashierRedirect(payload);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
handleCashierResultEvent(payload) {
|
|
253
|
+
if (!payload || typeof payload !== 'object') {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const resultPayload = payload;
|
|
257
|
+
const status = this.normalizeResultStatus(resultPayload['status']);
|
|
258
|
+
const callbackPayload = {
|
|
259
|
+
status,
|
|
260
|
+
transactionId: this.asString(resultPayload['transactionId']),
|
|
261
|
+
message: this.asString(resultPayload['message'])
|
|
262
|
+
};
|
|
263
|
+
if (!status) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (status === 'approved' || status === 'declined') {
|
|
267
|
+
this.invokeCallback(this.callbacks?.onSuccess, callbackPayload);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (status === 'cancelled') {
|
|
271
|
+
this.invokeCallback(this.callbacks?.onCancel, callbackPayload);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (status === 'invalid' || status === 'error') {
|
|
275
|
+
this.invokeCallback(this.callbacks?.onFailure, callbackPayload);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
handleCashierValidationFailedEvent(payload) {
|
|
279
|
+
if (!payload || typeof payload !== 'object') {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const validationPayload = payload;
|
|
283
|
+
const message = this.asString(validationPayload['message']) ?? 'Validation failed';
|
|
284
|
+
const callbackPayload = { message };
|
|
285
|
+
this.invokeCallback(this.callbacks?.onValidationFailed, callbackPayload);
|
|
286
|
+
}
|
|
287
|
+
handleCashierRedirect(payload) {
|
|
288
|
+
if (!payload || typeof payload !== 'object') {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const redirectUrl = payload.url;
|
|
292
|
+
const redirectTarget = this.normalizeRedirectTarget(payload.target);
|
|
293
|
+
if (typeof redirectUrl !== 'string' || redirectUrl.trim() === '') {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
switch (redirectTarget) {
|
|
297
|
+
case 'iframe':
|
|
298
|
+
console.log('[CashierBootstrapper] Handling redirect target "iframe".', { redirectUrl });
|
|
299
|
+
if (!this.openProviderRedirectOverlay(redirectUrl) && this.iframe) {
|
|
300
|
+
console.warn('[CashierBootstrapper] Could not open provider overlay; falling back to redirect inside cashier iframe.');
|
|
301
|
+
this.iframe.src = redirectUrl;
|
|
302
|
+
}
|
|
303
|
+
break;
|
|
304
|
+
case 'tab':
|
|
305
|
+
console.log('[CashierBootstrapper] Handling redirect target "tab".', { redirectUrl });
|
|
306
|
+
window.open(redirectUrl, '_blank', 'noopener,noreferrer');
|
|
307
|
+
break;
|
|
308
|
+
case 'window':
|
|
309
|
+
default:
|
|
310
|
+
console.log('[CashierBootstrapper] Handling redirect target "window".', { redirectUrl });
|
|
311
|
+
window.location.assign(redirectUrl);
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
openProviderRedirectOverlay(redirectUrl) {
|
|
316
|
+
if (typeof document === 'undefined' || typeof window === 'undefined') {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
if (!this.iframe) {
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
this.closeProviderRedirectOverlay();
|
|
323
|
+
try {
|
|
324
|
+
const overlay = document.createElement('div');
|
|
325
|
+
overlay.setAttribute('data-cashier-provider-overlay', 'true');
|
|
326
|
+
overlay.style.position = 'fixed';
|
|
327
|
+
overlay.style.top = '0';
|
|
328
|
+
overlay.style.left = '0';
|
|
329
|
+
overlay.style.right = '0';
|
|
330
|
+
overlay.style.bottom = '0';
|
|
331
|
+
overlay.style.zIndex = '2147483000';
|
|
332
|
+
overlay.style.background = 'transparent';
|
|
333
|
+
overlay.style.pointerEvents = 'none';
|
|
334
|
+
const panel = document.createElement('div');
|
|
335
|
+
panel.style.position = 'fixed';
|
|
336
|
+
panel.style.top = '0';
|
|
337
|
+
panel.style.left = '0';
|
|
338
|
+
panel.style.width = '0';
|
|
339
|
+
panel.style.height = '0';
|
|
340
|
+
panel.style.background = '#fff';
|
|
341
|
+
panel.style.overflow = 'hidden';
|
|
342
|
+
panel.style.display = 'flex';
|
|
343
|
+
panel.style.flexDirection = 'column';
|
|
344
|
+
panel.style.pointerEvents = 'auto';
|
|
345
|
+
const iframe = document.createElement('iframe');
|
|
346
|
+
iframe.src = redirectUrl;
|
|
347
|
+
iframe.style.border = 'none';
|
|
348
|
+
iframe.style.width = '100%';
|
|
349
|
+
iframe.style.height = '100%';
|
|
350
|
+
iframe.allow = DEFAULT_IFRAME_ALLOW;
|
|
351
|
+
iframe.setAttribute('data-provider-redirect-iframe', 'true');
|
|
352
|
+
panel.appendChild(iframe);
|
|
353
|
+
overlay.appendChild(panel);
|
|
354
|
+
document.body.appendChild(overlay);
|
|
355
|
+
this.redirectOverlayElement = overlay;
|
|
356
|
+
this.redirectOverlayPanel = panel;
|
|
357
|
+
this.redirectOverlayIframe = iframe;
|
|
358
|
+
this.syncProviderRedirectOverlayToCashierIframe();
|
|
359
|
+
const sync = () => this.syncProviderRedirectOverlayToCashierIframe();
|
|
360
|
+
this.overlayPositionSyncHandler = sync;
|
|
361
|
+
window.addEventListener('resize', sync);
|
|
362
|
+
window.addEventListener('scroll', sync, true);
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
catch (err) {
|
|
366
|
+
console.warn('Failed to mount provider redirect iframe overlay.', err);
|
|
367
|
+
this.closeProviderRedirectOverlay();
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
closeProviderRedirectOverlay() {
|
|
372
|
+
if (typeof window !== 'undefined' && this.overlayPositionSyncHandler) {
|
|
373
|
+
window.removeEventListener('resize', this.overlayPositionSyncHandler);
|
|
374
|
+
window.removeEventListener('scroll', this.overlayPositionSyncHandler, true);
|
|
375
|
+
}
|
|
376
|
+
this.overlayPositionSyncHandler = undefined;
|
|
377
|
+
const overlay = this.redirectOverlayElement;
|
|
378
|
+
if (overlay && overlay.parentNode) {
|
|
379
|
+
overlay.parentNode.removeChild(overlay);
|
|
380
|
+
}
|
|
381
|
+
this.redirectOverlayElement = undefined;
|
|
382
|
+
this.redirectOverlayPanel = undefined;
|
|
383
|
+
this.redirectOverlayIframe = undefined;
|
|
384
|
+
}
|
|
385
|
+
syncProviderRedirectOverlayToCashierIframe() {
|
|
386
|
+
const panel = this.redirectOverlayPanel;
|
|
387
|
+
const iframe = this.iframe;
|
|
388
|
+
if (!panel || !iframe) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const rect = iframe.getBoundingClientRect();
|
|
392
|
+
panel.style.left = `${rect.left}px`;
|
|
393
|
+
panel.style.top = `${rect.top}px`;
|
|
394
|
+
panel.style.width = `${rect.width}px`;
|
|
395
|
+
panel.style.height = `${rect.height}px`;
|
|
396
|
+
panel.style.borderRadius = window.getComputedStyle(iframe).borderRadius || '0';
|
|
397
|
+
}
|
|
398
|
+
normalizeRedirectTarget(target) {
|
|
399
|
+
if (target === 'iframe' || target === 'tab' || target === 'window') {
|
|
400
|
+
return target;
|
|
401
|
+
}
|
|
402
|
+
return 'window';
|
|
403
|
+
}
|
|
404
|
+
normalizeResultStatus(status) {
|
|
405
|
+
if (typeof status !== 'string') {
|
|
406
|
+
return undefined;
|
|
407
|
+
}
|
|
408
|
+
const normalized = status.trim().toLowerCase();
|
|
409
|
+
return normalized || undefined;
|
|
410
|
+
}
|
|
411
|
+
asString(value) {
|
|
412
|
+
if (typeof value !== 'string') {
|
|
413
|
+
return undefined;
|
|
414
|
+
}
|
|
415
|
+
const normalized = value.trim();
|
|
416
|
+
return normalized || undefined;
|
|
417
|
+
}
|
|
418
|
+
invokeCallback(callback, payload) {
|
|
419
|
+
if (!callback) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
try {
|
|
423
|
+
callback(payload);
|
|
424
|
+
}
|
|
425
|
+
catch (err) {
|
|
426
|
+
console.error('[CashierBootstrapper] Host callback threw an error.', err);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
189
429
|
/**
|
|
190
430
|
* API exposed to host pages for runtime control.
|
|
191
431
|
*/
|
package/dist/types.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export interface CashierRequestParams {
|
|
|
10
10
|
layout?: string;
|
|
11
11
|
transactionType?: string;
|
|
12
12
|
}
|
|
13
|
-
export type CashierEnvironment = 'production' | 'uat';
|
|
13
|
+
export type CashierEnvironment = 'production' | 'uat' | 'local' | 'local4200';
|
|
14
14
|
/**
|
|
15
15
|
* Bootstrap properties configuration
|
|
16
16
|
*/
|
|
@@ -81,6 +81,7 @@ export interface CashierIframeProperties extends BootstrapProperties {
|
|
|
81
81
|
export interface CashierIframeConfig {
|
|
82
82
|
requestParams?: CashierRequestParams;
|
|
83
83
|
properties?: CashierIframeProperties;
|
|
84
|
+
callbacks?: CashierCallbacks;
|
|
84
85
|
}
|
|
85
86
|
/**
|
|
86
87
|
* Minimal API surface returned to hosts after iframe creation.
|
|
@@ -92,3 +93,17 @@ export interface CashierIframeApi {
|
|
|
92
93
|
pause: () => void;
|
|
93
94
|
resume: () => void;
|
|
94
95
|
}
|
|
96
|
+
export interface CashierResultCallbackPayload {
|
|
97
|
+
status?: string;
|
|
98
|
+
transactionId?: string;
|
|
99
|
+
message?: string;
|
|
100
|
+
}
|
|
101
|
+
export interface CashierValidationFailedPayload {
|
|
102
|
+
message: string;
|
|
103
|
+
}
|
|
104
|
+
export interface CashierCallbacks {
|
|
105
|
+
onSuccess?: (payload: CashierResultCallbackPayload) => void;
|
|
106
|
+
onFailure?: (payload: CashierResultCallbackPayload) => void;
|
|
107
|
+
onCancel?: (payload: CashierResultCallbackPayload) => void;
|
|
108
|
+
onValidationFailed?: (payload: CashierValidationFailedPayload) => void;
|
|
109
|
+
}
|