@morefin/cashier-bootstrapper 0.1.7 → 0.1.8

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
@@ -10,10 +10,10 @@ npm install @morefin/cashier-bootstrapper
10
10
 
11
11
  ## Usage
12
12
 
13
- Throughout the examples below, set a host variable once and reuse it:
13
+ Throughout the examples below, set an environment once and reuse it:
14
14
 
15
15
  ```typescript
16
- const CASHIER_HOST = 'http://uat-api.morefin.com'; // change per environment
16
+ const CASHIER_ENVIRONMENT = 'uat' as const; // 'production' | 'uat'
17
17
  ```
18
18
 
19
19
  ### Basic Embed
@@ -22,7 +22,7 @@ const CASHIER_HOST = 'http://uat-api.morefin.com'; // change per environment
22
22
  import { CashierBootstrapper } from '@morefin/cashier-bootstrapper';
23
23
 
24
24
  new CashierBootstrapper('#cashier-root', {
25
- properties: { host: CASHIER_HOST }
25
+ properties: { environment: CASHIER_ENVIRONMENT }
26
26
  });
27
27
  ```
28
28
 
@@ -39,11 +39,10 @@ new CashierBootstrapper('#cashier-root', {
39
39
  sessionId: 'session-abc',
40
40
  predefinedAmounts: [100, 200, 300],
41
41
  layout: 'default',
42
- transactionType: ('deosit'|'withdrawal')
42
+ transactionType: ('deposit'|'withdrawal')
43
43
  },
44
44
  properties: {
45
- host: CASHIER_HOST,
46
- cashierPath: '/cashier',
45
+ environment: CASHIER_ENVIRONMENT,
47
46
  iframe: {
48
47
  height: '720px',
49
48
  minHeight: '640px',
@@ -62,7 +61,7 @@ import { CashierBootstrapper } from '@morefin/cashier-bootstrapper';
62
61
 
63
62
  new CashierBootstrapper(null, {
64
63
  properties: {
65
- host: CASHIER_HOST,
64
+ environment: CASHIER_ENVIRONMENT,
66
65
  container: '#cashier-root'
67
66
  }
68
67
  });
@@ -74,7 +73,7 @@ new CashierBootstrapper(null, {
74
73
  import { CashierBootstrapper } from '@morefin/cashier-bootstrapper';
75
74
 
76
75
  new CashierBootstrapper('#cashier-root', {
77
- properties: { host: CASHIER_HOST }
76
+ properties: { environment: CASHIER_ENVIRONMENT }
78
77
  }, api => {
79
78
  api.setCss(`
80
79
  .payment-layout-root .payment-container {
@@ -99,7 +98,7 @@ Iframe-based embed that loads the cashier URL and exposes runtime controls once
99
98
 
100
99
  **Parameters:**
101
100
  - `container: string | HTMLElement | null | undefined` - Where the iframe is appended. If omitted, `config.properties.container` is used (falling back to `document.body`).
102
- - `config?: CashierIframeConfig` - Request params and iframe properties. `properties.host` is required.
101
+ - `config?: CashierIframeConfig` - Request params and iframe properties. `properties.environment` is required.
103
102
  - `onReady?: (api: CashierIframeApi) => void` - Called when the iframe loads; exposes helpers:
104
103
  - `api.setCss(css: string)` – inject CSS inside the cashier iframe
105
104
  - `api.updateData(data: object)` – post updated `APP_DATA` to the cashier
@@ -115,10 +114,11 @@ interface CashierRequestParams {
115
114
  sessionId?: string;
116
115
  predefinedAmounts?: number[];
117
116
  layout?: string;
118
- fingerprint?: string;
119
117
  transactionType?: string;
120
118
  }
121
119
 
120
+ type CashierEnvironment = 'production' | 'uat';
121
+
122
122
  interface CashierIframeOptions {
123
123
  width?: string;
124
124
  height?: string;
@@ -130,9 +130,8 @@ interface CashierIframeOptions {
130
130
  }
131
131
 
132
132
  interface CashierIframeProperties {
133
- host: string;
133
+ environment: CashierEnvironment;
134
134
  container?: string | HTMLElement;
135
- cashierPath?: string;
136
135
  iframe?: CashierIframeOptions;
137
136
  }
138
137
 
@@ -144,6 +143,9 @@ interface CashierIframeConfig {
144
143
 
145
144
  ## Defaults
146
145
 
147
- - `cashierPath`: `/cashier`
146
+ - Cashier path is fixed to `/cashier`
147
+ - `environment: 'production'` resolves to `https://api.morefin.com`
148
+ - `environment: 'uat'` resolves to `https://uat-api.morefin.com`
149
+ - Iframe URL origin is validated to allow only `api.morefin.com` and `uat-api.morefin.com`
148
150
  - `iframe.width`/`iframe.height`: `100%`
149
151
  - `iframe.allow`: `geolocation *;camera *;payment *;clipboard-read *;clipboard-write *;autoplay *;microphone *;fullscreen *;accelerometer *;magnetometer *;gyroscope *;picture-in-picture *;otp-credentials *;`
@@ -3,11 +3,11 @@
3
3
  * This file is not included in the npm package, it's just for reference.
4
4
  */
5
5
  import { CashierBootstrapper } from './index';
6
- const CASHIER_HOST = 'http://localhost:7082'; // change per environment
6
+ const CASHIER_ENVIRONMENT = 'uat';
7
7
  // Example 1: Minimal configuration with selector
8
8
  export function example1() {
9
9
  new CashierBootstrapper('#cashier-root', {
10
- properties: { host: CASHIER_HOST }
10
+ properties: { environment: CASHIER_ENVIRONMENT }
11
11
  });
12
12
  }
13
13
  // Example 2: Custom request params + iframe options
@@ -22,8 +22,7 @@ export function example2(container) {
22
22
  layout: 'default'
23
23
  },
24
24
  properties: {
25
- host: CASHIER_HOST,
26
- cashierPath: '/cashier',
25
+ environment: CASHIER_ENVIRONMENT,
27
26
  iframe: {
28
27
  height: '720px',
29
28
  minHeight: '640px',
@@ -37,7 +36,7 @@ export function example2(container) {
37
36
  // Example 3: Provide container via config instead of constructor
38
37
  export function example3() {
39
38
  new CashierBootstrapper(null, {
40
- properties: { host: CASHIER_HOST, container: '#cashier-root' }
39
+ properties: { environment: CASHIER_ENVIRONMENT, container: '#cashier-root' }
41
40
  });
42
41
  }
43
42
  // Example 4: Update CSS at runtime after iframe is ready
@@ -51,7 +50,7 @@ export function example4(container) {
51
50
  layout: 'default'
52
51
  },
53
52
  properties: {
54
- host: CASHIER_HOST
53
+ environment: CASHIER_ENVIRONMENT
55
54
  }
56
55
  }, api => {
57
56
  api.setCss(`
@@ -64,7 +63,7 @@ export function example4(container) {
64
63
  // Example 5: Updating data via the postMessage API
65
64
  export function example5(container) {
66
65
  new CashierBootstrapper(container, {
67
- properties: { host: CASHIER_HOST }
66
+ properties: { environment: CASHIER_ENVIRONMENT }
68
67
  }, api => {
69
68
  api.updateData({
70
69
  userId: 'updated-user'
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { CashierIframeApi, CashierIframeConfig } from './types';
2
- export type { CashierRequestParams, CashierIframeApi, CashierIframeConfig, CashierIframeProperties } from './types';
2
+ export type { CashierEnvironment, CashierRequestParams, CashierIframeApi, CashierIframeConfig, CashierIframeProperties } from './types';
3
3
  type FingerprintJSGlobal = {
4
4
  load: () => Promise<{
5
5
  get: () => Promise<{
@@ -17,6 +17,7 @@ export declare class CashierBootstrapper {
17
17
  private origin;
18
18
  private ready;
19
19
  private readonly fullConfig;
20
+ private readonly host;
20
21
  private readonly onReady?;
21
22
  constructor(container: string | HTMLElement | null | undefined, config?: CashierIframeConfig, onReady?: (api: CashierIframeApi) => void);
22
23
  private bootstrapIframe;
package/dist/index.js CHANGED
@@ -1,5 +1,11 @@
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
+ const CASHIER_PATH = '/cashier';
2
3
  const FINGERPRINT_CDN = 'https://cdn.jsdelivr.net/npm/@fingerprintjs/fingerprintjs@4/dist/fp.min.js';
4
+ const CASHIER_HOSTS = {
5
+ production: 'https://api.morefin.com',
6
+ uat: 'https://uat-api.morefin.com'
7
+ };
8
+ const ALLOWED_CASHIER_DOMAINS = new Set(['api.morefin.com', 'uat-api.morefin.com']);
3
9
  let fpLoaderPromise;
4
10
  function loadFingerprintLibrary() {
5
11
  if (typeof window === 'undefined') {
@@ -40,7 +46,7 @@ async function generateFingerprint() {
40
46
  return undefined;
41
47
  }
42
48
  }
43
- function buildQueryString(requestParams) {
49
+ function buildQueryString(requestParams, fingerprint) {
44
50
  const query = new URLSearchParams();
45
51
  if (requestParams.merchantId != null) {
46
52
  query.append('merchantId', String(requestParams.merchantId));
@@ -56,14 +62,14 @@ function buildQueryString(requestParams) {
56
62
  }
57
63
  if (Array.isArray(requestParams.predefinedAmounts)) {
58
64
  requestParams.predefinedAmounts.forEach(amount => {
59
- query.append('predefinedAmounts', String(amount));
65
+ query.append('predefined_amounts', String(amount));
60
66
  });
61
67
  }
62
68
  if (requestParams.layout != null && requestParams.layout !== '' && requestParams.layout !== 'undefined') {
63
69
  query.append('layout', requestParams.layout);
64
70
  }
65
- if (requestParams.fingerprint) {
66
- query.append('fingerprint', requestParams.fingerprint);
71
+ if (fingerprint) {
72
+ query.append('fingerprint', fingerprint);
67
73
  }
68
74
  if (requestParams.transactionType) {
69
75
  query.append('transactionType', requestParams.transactionType);
@@ -71,12 +77,6 @@ function buildQueryString(requestParams) {
71
77
  const qs = query.toString();
72
78
  return qs ? `?${qs}` : '';
73
79
  }
74
- function normalizePath(path) {
75
- if (!path) {
76
- return '/cashier';
77
- }
78
- return path.startsWith('/') ? path : `/${path}`;
79
- }
80
80
  function resolveContainer(container) {
81
81
  if (container instanceof HTMLElement) {
82
82
  return container;
@@ -90,34 +90,44 @@ function resolveContainer(container) {
90
90
  }
91
91
  return document.body;
92
92
  }
93
- function buildIframeUrl(host, cashierPath, requestParams) {
93
+ function resolveHost(environment) {
94
+ const host = CASHIER_HOSTS[environment];
95
+ if (!host) {
96
+ throw new Error(`Unsupported cashier environment "${environment}"`);
97
+ }
98
+ return host;
99
+ }
100
+ function parseAndVerifyCashierUrl(url) {
101
+ const parsedUrl = new URL(url);
102
+ if (!ALLOWED_CASHIER_DOMAINS.has(parsedUrl.hostname)) {
103
+ throw new Error(`Cashier domain "${parsedUrl.hostname}" is not allowed`);
104
+ }
105
+ return parsedUrl;
106
+ }
107
+ function buildIframeUrl(host, requestParams, fingerprint) {
94
108
  const trimmedHost = host.endsWith('/') ? host.slice(0, -1) : host;
95
- const path = normalizePath(cashierPath);
96
- return `${trimmedHost}${path}${buildQueryString(requestParams)}`;
109
+ const url = `${trimmedHost}${CASHIER_PATH}${buildQueryString(requestParams, fingerprint)}`;
110
+ parseAndVerifyCashierUrl(url);
111
+ return url;
97
112
  }
98
113
  function getOriginFromUrl(url) {
99
- try {
100
- return new URL(url).origin;
101
- }
102
- catch {
103
- return '*';
104
- }
114
+ return parseAndVerifyCashierUrl(url).origin;
105
115
  }
106
116
  export class CashierBootstrapper {
107
117
  constructor(container, config = {}, onReady) {
108
118
  this.origin = '*';
109
119
  this.ready = false;
110
120
  this.onReady = onReady;
111
- const host = config.properties?.host;
112
- if (!host) {
113
- throw new Error('Cashier host is required to bootstrap the iframe');
121
+ const environment = config.properties?.environment;
122
+ if (!environment) {
123
+ throw new Error('Cashier environment is required to bootstrap the iframe');
114
124
  }
125
+ this.host = resolveHost(environment);
115
126
  this.fullConfig = {
116
127
  requestParams: { ...config.requestParams },
117
128
  properties: {
118
129
  ...config.properties,
119
- host,
120
- cashierPath: normalizePath(config.properties?.cashierPath),
130
+ environment,
121
131
  iframe: {
122
132
  width: '100%',
123
133
  height: '100%',
@@ -132,10 +142,7 @@ export class CashierBootstrapper {
132
142
  }
133
143
  async bootstrapIframe() {
134
144
  const fp = await generateFingerprint();
135
- if (fp) {
136
- this.fullConfig.requestParams.fingerprint = fp;
137
- }
138
- const src = buildIframeUrl(this.fullConfig.properties.host, this.fullConfig.properties.cashierPath ?? '/cashier', this.fullConfig.requestParams);
145
+ const src = buildIframeUrl(this.host, this.fullConfig.requestParams, fp);
139
146
  this.origin = getOriginFromUrl(src);
140
147
  if (this.iframe) {
141
148
  this.iframe.src = src;
package/dist/types.d.ts CHANGED
@@ -8,14 +8,14 @@ export interface CashierRequestParams {
8
8
  sessionId?: string;
9
9
  predefinedAmounts?: number[];
10
10
  layout?: string;
11
- fingerprint?: string;
12
11
  transactionType?: string;
13
12
  }
13
+ export type CashierEnvironment = 'production' | 'uat';
14
14
  /**
15
15
  * Bootstrap properties configuration
16
16
  */
17
17
  export interface BootstrapProperties {
18
- host: string;
18
+ environment: CashierEnvironment;
19
19
  /**
20
20
  * Selector or element where HTML should be rendered
21
21
  * Default: 'body' (replaces entire body)
@@ -70,10 +70,6 @@ export interface CashierIframeProperties extends BootstrapProperties {
70
70
  * Optional DOM container (selector or element) where the iframe is appended.
71
71
  */
72
72
  container?: string | HTMLElement;
73
- /**
74
- * Path to the cashier page. Default: '/cashier'.
75
- */
76
- cashierPath?: string;
77
73
  /**
78
74
  * Iframe tuning options (dimensions, attributes).
79
75
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morefin/cashier-bootstrapper",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
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",