@morefin/cashier-bootstrapper 0.3.4 → 0.3.6

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
@@ -37,11 +37,12 @@ new CashierBootstrapper('#cashier-root', {
37
37
  terminalId: 'terminal-456',
38
38
  userId: 'user-789',
39
39
  sessionId: 'session-abc',
40
+ locale: 'sv_SE',
41
+ channel: 'ios',
40
42
  predefinedAmounts: [100, 200, 300],
41
43
  layout: 'default',
42
44
  transactionType: ('deposit'|'withdrawal'),
43
45
  attributes: {
44
- channel: 'web',
45
46
  campaign: 'spring-launch',
46
47
  isVip: true
47
48
  }
@@ -62,7 +63,9 @@ new CashierBootstrapper('#cashier-root', {
62
63
  });
63
64
  ```
64
65
 
65
- `requestParams.attributes` sends custom cashier data as the `attributes` query parameter. Use it for values the cashier should receive as part of the request payload.
66
+ `requestParams.channel` selects the cashier payment method order channel. Supported values are `windows`, `mac`, `ios`, `android`, and `other`; when omitted, the bootstrapper derives it from the user device.
67
+
68
+ `requestParams.attributes` sends custom cashier data as the `attributes` query parameter. Use it for values the cashier should receive as part of the request payload. It does not control payment method ordering.
66
69
 
67
70
  `properties.iframe.attributes` applies plain HTML attributes directly to the rendered `<iframe>`. Use it for DOM concerns such as `data-*`, `loading`, or other iframe element attributes.
68
71
 
@@ -150,12 +153,16 @@ interface CashierRequestParams {
150
153
  terminalId?: string;
151
154
  userId?: string;
152
155
  sessionId?: string;
156
+ locale?: string;
157
+ channel?: CashierRuntimeChannel | string;
153
158
  predefinedAmounts?: number[];
154
159
  layout?: string;
155
160
  transactionType?: string;
156
161
  attributes?: Record<string, unknown>;
157
162
  }
158
163
 
164
+ type CashierRuntimeChannel = 'windows' | 'mac' | 'ios' | 'android' | 'other';
165
+
159
166
  type CashierEnvironment = 'production' | 'uat';
160
167
 
161
168
  interface CashierIframeOptions {
@@ -18,6 +18,7 @@ export function example2(container) {
18
18
  terminalId: 'terminal-456',
19
19
  userId: 'user-789',
20
20
  sessionId: 'session-abc',
21
+ locale: 'sv_SE',
21
22
  predefinedAmounts: [100, 200, 300],
22
23
  layout: 'default'
23
24
  },
@@ -46,6 +47,7 @@ export function example4(container) {
46
47
  merchantId: 'merchant-123',
47
48
  terminalId: 'terminal-456',
48
49
  userId: 'user-789',
50
+ locale: 'sv_SE',
49
51
  predefinedAmounts: [100, 200, 300],
50
52
  layout: 'default'
51
53
  },
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { CashierIframeApi, CashierIframeConfig } from './types';
2
- export type { CashierCallbacks, CashierEnvironment, CashierRequestParams, CashierIframeApi, CashierIframeConfig, CashierIframeProperties, CashierResultCallbackPayload, CashierRedirectPayload, CashierValidationFailedPayload } from './types';
2
+ export type { CashierCallbacks, CashierEnvironment, CashierRequestParams, CashierRuntimeChannel, CashierIframeApi, CashierIframeConfig, CashierIframeProperties, CashierResultCallbackPayload, CashierRedirectPayload, CashierValidationFailedPayload } from './types';
3
3
  type FingerprintJSGlobal = {
4
4
  load: () => Promise<{
5
5
  get: () => Promise<{
@@ -18,6 +18,9 @@ export declare class CashierBootstrapper {
18
18
  private redirectOverlayPanel?;
19
19
  private redirectOverlayIframe?;
20
20
  private overlayPositionSyncHandler?;
21
+ private managedRedirectTab?;
22
+ private managedRedirectTabTransactionId?;
23
+ private managedRedirectTabCheckIntervalId?;
21
24
  private origin;
22
25
  private ready;
23
26
  private readonly fullConfig;
@@ -41,6 +44,10 @@ export declare class CashierBootstrapper {
41
44
  private normalizeRedirectTarget;
42
45
  private normalizeRedirectMethod;
43
46
  private normalizeRedirectParameters;
47
+ private openManagedRedirectTab;
48
+ private openManagedRedirectTabWithPost;
49
+ private trackManagedRedirectTab;
50
+ private clearManagedRedirectTabWatcher;
44
51
  private openPopupWithPost;
45
52
  private writePostFormToWindow;
46
53
  private submitPostForm;
package/dist/index.js CHANGED
@@ -2,10 +2,12 @@ const DEFAULT_IFRAME_ALLOW = 'geolocation *;camera *;payment *;clipboard-read *;
2
2
  const CASHIER_PATH = '/cashier';
3
3
  const FINGERPRINT_CDN = 'https://cdn.jsdelivr.net/npm/@fingerprintjs/fingerprintjs@4/dist/fp.min.js';
4
4
  const CASHIER_REDIRECT_EVENT = 'CASHIER_REDIRECT';
5
+ const CASHIER_REDIRECT_TAB_CLOSED_EVENT = 'CASHIER_REDIRECT_TAB_CLOSED';
5
6
  const TOP_URL_REPLACE_EVENT = 'TOP_URL_REPLACE';
6
7
  const CASHIER_IFRAME_OVERLAY_CLOSE_EVENT = 'CASHIER_IFRAME_OVERLAY_CLOSE';
7
8
  const CASHIER_RESULT_EVENT = 'CASHIER_RESULT';
8
9
  const CASHIER_VALIDATION_FAILED_EVENT = 'CASHIER_VALIDATION_FAILED';
10
+ const CASHIER_RUNTIME_CHANNELS = ['windows', 'mac', 'ios', 'android', 'other'];
9
11
  const CASHIER_ENVIRONMENT_CONFIG = {
10
12
  production: {
11
13
  host: 'https://api.morefin.com',
@@ -64,6 +66,37 @@ async function generateFingerprint() {
64
66
  return undefined;
65
67
  }
66
68
  }
69
+ function normalizeRuntimeChannel(channel) {
70
+ if (channel == null) {
71
+ return undefined;
72
+ }
73
+ const normalized = channel.trim().toLowerCase();
74
+ return CASHIER_RUNTIME_CHANNELS.includes(normalized)
75
+ ? normalized
76
+ : undefined;
77
+ }
78
+ function detectRuntimeChannel() {
79
+ if (typeof navigator === 'undefined') {
80
+ return undefined;
81
+ }
82
+ const userAgent = `${navigator.userAgent ?? ''} ${navigator.appVersion ?? ''}`.toLowerCase();
83
+ if (userAgent.includes('android')) {
84
+ return 'android';
85
+ }
86
+ if (userAgent.includes('iphone') || userAgent.includes('ipad') || userAgent.includes('ipod')) {
87
+ return 'ios';
88
+ }
89
+ if (userAgent.includes('windows')) {
90
+ return 'windows';
91
+ }
92
+ if (userAgent.includes('mac')) {
93
+ return 'mac';
94
+ }
95
+ return 'other';
96
+ }
97
+ function resolveRuntimeChannel(channel) {
98
+ return channel == null ? detectRuntimeChannel() : normalizeRuntimeChannel(channel);
99
+ }
67
100
  function buildQueryString(requestParams, fingerprint) {
68
101
  const query = new URLSearchParams();
69
102
  if (requestParams.merchantId != null) {
@@ -78,6 +111,13 @@ function buildQueryString(requestParams, fingerprint) {
78
111
  if (requestParams.sessionId != null) {
79
112
  query.append('sessionId', String(requestParams.sessionId));
80
113
  }
114
+ if (requestParams.locale != null && requestParams.locale !== '' && requestParams.locale !== 'undefined') {
115
+ query.append('locale', String(requestParams.locale));
116
+ }
117
+ const channel = resolveRuntimeChannel(requestParams.channel);
118
+ if (channel) {
119
+ query.append('channel', channel);
120
+ }
81
121
  if (Array.isArray(requestParams.predefinedAmounts)) {
82
122
  requestParams.predefinedAmounts.forEach(amount => {
83
123
  query.append('predefinedAmounts', String(amount));
@@ -296,6 +336,7 @@ export class CashierBootstrapper {
296
336
  const redirectTarget = this.normalizeRedirectTarget(redirectPayload.target);
297
337
  const redirectMethod = this.normalizeRedirectMethod(redirectPayload.method);
298
338
  const redirectParameters = this.normalizeRedirectParameters(redirectPayload.parameters);
339
+ const transactionId = this.asString(redirectPayload.transactionId);
299
340
  if (typeof redirectUrl !== 'string' || redirectUrl.trim() === '') {
300
341
  return;
301
342
  }
@@ -315,12 +356,12 @@ export class CashierBootstrapper {
315
356
  case 'tab':
316
357
  console.log('[CashierBootstrapper] Handling redirect target "tab".', { redirectUrl, redirectMethod, redirectParameters });
317
358
  if (redirectMethod === 'POST') {
318
- if (!this.openPopupWithPost(redirectUrl, redirectParameters)) {
359
+ if (!this.openManagedRedirectTabWithPost(redirectUrl, redirectParameters, transactionId)) {
319
360
  this.submitPostForm(redirectUrl, redirectParameters, '_blank');
320
361
  }
321
362
  }
322
363
  else {
323
- window.open(redirectUrl, '_blank', 'noopener,noreferrer');
364
+ this.openManagedRedirectTab(redirectUrl, transactionId);
324
365
  }
325
366
  break;
326
367
  case 'window':
@@ -453,6 +494,55 @@ export class CashierBootstrapper {
453
494
  return acc;
454
495
  }, {});
455
496
  }
497
+ openManagedRedirectTab(url, transactionId) {
498
+ if (typeof window === 'undefined') {
499
+ return false;
500
+ }
501
+ const popup = window.open(url, '_blank');
502
+ if (!popup) {
503
+ return false;
504
+ }
505
+ this.trackManagedRedirectTab(popup, transactionId);
506
+ return true;
507
+ }
508
+ openManagedRedirectTabWithPost(url, parameters, transactionId) {
509
+ if (typeof window === 'undefined') {
510
+ return false;
511
+ }
512
+ const popup = window.open('', '_blank');
513
+ if (!popup) {
514
+ return false;
515
+ }
516
+ if (!this.writePostFormToWindow(popup, url, parameters)) {
517
+ return false;
518
+ }
519
+ this.trackManagedRedirectTab(popup, transactionId);
520
+ return true;
521
+ }
522
+ trackManagedRedirectTab(tab, transactionId) {
523
+ this.clearManagedRedirectTabWatcher();
524
+ this.managedRedirectTab = tab;
525
+ this.managedRedirectTabTransactionId = transactionId;
526
+ this.managedRedirectTabCheckIntervalId = setInterval(() => {
527
+ if (!this.managedRedirectTab?.closed) {
528
+ return;
529
+ }
530
+ const closedTransactionId = this.managedRedirectTabTransactionId;
531
+ this.managedRedirectTab = null;
532
+ this.managedRedirectTabTransactionId = undefined;
533
+ this.clearManagedRedirectTabWatcher();
534
+ this.postMessage(CASHIER_REDIRECT_TAB_CLOSED_EVENT, {
535
+ transactionId: closedTransactionId
536
+ });
537
+ }, 500);
538
+ }
539
+ clearManagedRedirectTabWatcher() {
540
+ if (this.managedRedirectTabCheckIntervalId === undefined) {
541
+ return;
542
+ }
543
+ clearInterval(this.managedRedirectTabCheckIntervalId);
544
+ this.managedRedirectTabCheckIntervalId = undefined;
545
+ }
456
546
  openPopupWithPost(url, parameters) {
457
547
  if (typeof window === 'undefined') {
458
548
  return false;
package/dist/types.d.ts CHANGED
@@ -6,11 +6,14 @@ export interface CashierRequestParams {
6
6
  terminalId?: string;
7
7
  userId?: string;
8
8
  sessionId?: string;
9
+ locale?: string;
10
+ channel?: CashierRuntimeChannel | string;
9
11
  predefinedAmounts?: number[];
10
12
  layout?: string;
11
13
  transactionType?: string;
12
14
  attributes?: Record<string, unknown>;
13
15
  }
16
+ export type CashierRuntimeChannel = 'windows' | 'mac' | 'ios' | 'android' | 'other';
14
17
  export type CashierEnvironment = 'production' | 'uat' | 'local' | 'local4200';
15
18
  /**
16
19
  * Bootstrap properties configuration
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morefin/cashier-bootstrapper",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "Bootstrap service for initializing cashier payment page data from API",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",