@finteqhub/sdk-js 0.10.1 → 0.12.0-beta.2

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
@@ -1,11 +1,66 @@
1
1
  # processing-sdk
2
2
 
3
- Use `new FinteqHubProcessing(apiUrl: string, fingerprintVisitorId: string, merchantId: string, sessionId: string)` to create an instance of the FinteqHubProcessing object. The FinteqHubProcessing object is your entrypoint to FinteqHub processing SDK.
3
+ Use `new FinteqHubProcessing(options: ProcessingOptions)` to create an instance of the FinteqHubProcessing object. The FinteqHubProcessing object is your entrypoint to FinteqHub processing SDK.
4
4
 
5
5
  ```
6
- const processing = new FinteqHubProcessing('api-url', 'fingerprint-visitor-id', 'merchant-id', 'session-id');
6
+ interface ProcessingOptions {
7
+ apiUrl: string;
8
+ fingerprintVisitorId: string;
9
+ merchantId: string;
10
+ sessionId: string;
11
+ isSecure?: boolean; // default false
12
+ retryOptions?: RetryOptions;
13
+ }
14
+
15
+ const processing = new FinteqHubProcessing({
16
+ apiUrl: 'api-url',
17
+ fingerprintVisitorId: 'fingerprint-visitor-id',
18
+ merchantId: 'merchant-id',
19
+ sessionId: 'session-id',
20
+ });
7
21
  ```
8
22
 
23
+ ## Retries and error diagnostics
24
+
25
+ Failed HTTP requests are retried automatically with exponential backoff (`100ms → 200ms → 500ms → 1000ms → 2000ms`; every retry after the fifth waits 2000ms). Retries can be configured via the `retryOptions` constructor option:
26
+
27
+ ```
28
+ interface RetryOptions {
29
+ retryCount?: number; // number of retries after the initial attempt, default 5; 0 disables retries
30
+ retryStatusCode?: (statusCode: number) => boolean; // default: statusCode < 200 || statusCode === 408 || statusCode >= 500
31
+ }
32
+ ```
33
+
34
+ Network errors (the browser could not reach the server at all, e.g. `TypeError: Failed to fetch`, or the connection dropped while the response body was being read) are always retried too.
35
+
36
+ Every retry attempt is reported with `console.warn`. When a request finally fails, the SDK logs `console.error` (`sdk-js: request failed: <message>`) with a full diagnostic dump and rejects with a `RequestError` whose `diagnostics` field carries the same payload — include it in your error reporting. `diagnostics.kind` tells the failure class apart:
37
+
38
+ - `"network"` — the browser never got a complete response (e.g. `TypeError: Failed to fetch`, or the body could not be read); `diagnostics.error` describes the thrown error, `diagnostics.response.status` is present when headers had arrived before the failure, `message` is `request to <url> failed after N attempt(s): ...`;
39
+ - `"http_error"` — an error response (non-200 status, or an `error` field in the body); `message` is the error text from the response body (or `unexpected response status <code>` when the body has none), `diagnostics.response.status` carries the HTTP status and `diagnostics.response.error` the error text from the body;
40
+ - `"invalid_json"` — the response body is not valid JSON; both `diagnostics.error` (the parse error; its message follows the same policy as the body — sanitized for non-200, omitted for 200 responses) and `diagnostics.response` are set.
41
+
42
+ Every `diagnostics` payload also includes:
43
+
44
+ - `sdkVersion`;
45
+ - `request`: url, method, `x-request-id`, session id, and per-attempt log `attempts: [{ durationMs, status?, error? }]` — attempt durations help tell an instant failure (DNS/connection refused) from a hang (timeout/handshake);
46
+ - `response.body` for non-200 responses — truncated to 500 chars with long digit runs masked (`***`); the body of a 200 response is never included since it may carry session credentials; the request body is never included anywhere;
47
+ - `response.error` — the `error` field of the response body, for any status (including a 200 response with an `error` field); it is a business error text, so it is neither truncated nor masked;
48
+ - `environment`: `navigator.onLine`, `document.visibilityState`, `navigator.connection` (`effectiveType`/`rtt`/`downlink`, Chromium only), timestamp.
49
+
50
+ Diagnostics are collected regardless of whether retries are enabled.
51
+
52
+ ## SDK identification header
53
+
54
+ Every request the SDK makes carries an extra header:
55
+
56
+ ```
57
+ X-Finteqhub-SDK: sdk-js/<version>
58
+ ```
59
+
60
+ The value contains the SDK name and version (kept in sync with `package.json` by a test) — for example `sdk-js/0.11.0`. FinteqHub uses this header to identify traffic coming from the official SDK integration — for example to notify affected merchants when a security fix is released. It does not affect authentication or request routing.
61
+
62
+ The header is added automatically to every request and cannot be disabled.
63
+
9
64
  ## API
10
65
 
11
66
  ### processing.getSession()
@@ -39,7 +94,7 @@ import FingerprintJS from "@fingerprintjs/fingerprintjs";
39
94
  const fp = await FingerprintJS.load();
40
95
  const result = await fp.get();
41
96
 
42
- const processing = new FinteqHubProcessing(apiUrl, result.visitorId, merchantId, sessionId);
97
+ const processing = new FinteqHubProcessing({ apiUrl, fingerprintVisitorId: result.visitorId, merchantId, sessionId });
43
98
  const session = await processing.getSession();
44
99
 
45
100
  const data = {/** collect data from form and session **/}
@@ -49,3 +104,17 @@ processing
49
104
  .then(result => console.log(result))
50
105
  .catch(error => console.warn(error));
51
106
  ```
107
+
108
+ ## Releasing
109
+
110
+ On every version bump update **both** `package.json` `version` and `SDK_VERSION` in `src/version.ts` — they must stay in sync so the `X-Finteqhub-SDK` header reports the right version. `src/version.test.ts` fails CI if they drift (`node scripts/sync-version.js` updates `src/version.ts` from `package.json`). Describe the release in [CHANGELOG.md](CHANGELOG.md), including migration notes for breaking changes.
111
+
112
+ ### Beta releases
113
+
114
+ To try changes before bumping the version, run the `publish-beta` workflow (GitHub → Actions → publish-beta → Run workflow, pick your branch). It publishes `<current version>-beta.<run number>` to npm under the `beta` dist-tag — `latest` and the version in the repo stay untouched. Install it with:
115
+
116
+ ```
117
+ npm i @finteqhub/sdk-js@beta
118
+ ```
119
+
120
+ or pin the exact version printed in the workflow summary.
@@ -1,4 +1,57 @@
1
1
  import { ProcessOperationRedirectResponse, SessionResponse, SubmitData } from "./typings";
2
+ export interface RetryOptions {
3
+ retryCount?: number;
4
+ retryStatusCode?: (statusCode: number) => boolean;
5
+ }
6
+ export interface ProcessingOptions {
7
+ apiUrl: string;
8
+ fingerprintVisitorId: string;
9
+ merchantId: string;
10
+ sessionId: string;
11
+ isSecure?: boolean;
12
+ retryOptions?: RetryOptions;
13
+ }
14
+ export declare type RequestAttempt = {
15
+ durationMs: number;
16
+ status?: number;
17
+ error?: string;
18
+ };
19
+ export declare type RequestDiagnostics = {
20
+ kind: "network" | "http_error" | "invalid_json";
21
+ sdkVersion: string;
22
+ error?: {
23
+ name: string;
24
+ message: string;
25
+ stack?: string;
26
+ cause?: unknown;
27
+ };
28
+ response?: {
29
+ status: number;
30
+ body?: string;
31
+ error?: string;
32
+ };
33
+ request: {
34
+ url: string;
35
+ method: string;
36
+ requestId: string;
37
+ sessionId: string;
38
+ attempts: RequestAttempt[];
39
+ };
40
+ environment: {
41
+ online: boolean;
42
+ visibility: string;
43
+ connection?: {
44
+ effectiveType?: string;
45
+ rtt?: number;
46
+ downlink?: number;
47
+ };
48
+ timestamp: string;
49
+ };
50
+ };
51
+ export declare class RequestError extends Error {
52
+ diagnostics: RequestDiagnostics;
53
+ constructor(message: string, diagnostics: RequestDiagnostics);
54
+ }
2
55
  export declare class FinteqHubProcessing {
3
56
  private apiUrl;
4
57
  private fingerprintVisitorId;
@@ -6,9 +59,14 @@ export declare class FinteqHubProcessing {
6
59
  private sessionId;
7
60
  private projectId;
8
61
  private isSecure;
9
- constructor(apiUrl: string, fingerprintVisitorId: string, merchantId: string, sessionId: string, isSecure?: boolean);
62
+ private retryOptions;
63
+ constructor(options: ProcessingOptions);
10
64
  getSession(): Promise<SessionResponse>;
11
65
  submitForm(data: SubmitData): Promise<ProcessOperationRedirectResponse>;
12
66
  private processOperation;
13
67
  private sendPost;
68
+ private request;
69
+ private fetchWithRetry;
70
+ private responseError;
71
+ private collectDiagnostics;
14
72
  }
@@ -7,36 +7,50 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { getDeviceData, uuid } from "./utils";
10
+ import { getDeviceData, uuid, validateOptions } from "./utils";
11
+ import { SDK_HEADER_NAME, SDK_HEADER_VALUE, SDK_VERSION } from "./version";
12
+ export class RequestError extends Error {
13
+ constructor(message, diagnostics) {
14
+ super(message);
15
+ this.name = "RequestError";
16
+ this.diagnostics = diagnostics;
17
+ }
18
+ }
19
+ const DEFAULT_RETRY_COUNT = 5;
20
+ const RETRY_DELAYS_MS = [100, 200, 500, 1000, 2000];
21
+ const MAX_BODY_SNIPPET_LENGTH = 500;
22
+ // masks long digit runs (PANs, phone numbers) before a response body lands in logs
23
+ const sanitizeBody = (body) => body.slice(0, MAX_BODY_SNIPPET_LENGTH).replace(/\d{6,}/g, "***");
24
+ // 400 is deliberately not retried: the backend returns it for deterministic validation
25
+ // failures and for duplicate-submit rejections — retrying either only delays the error
26
+ const defaultRetryStatusCode = (statusCode) => statusCode < 200 || statusCode === 408 || statusCode >= 500;
11
27
  export class FinteqHubProcessing {
12
- constructor(apiUrl, fingerprintVisitorId, merchantId, sessionId, isSecure = false) {
13
- this.apiUrl = apiUrl;
14
- this.fingerprintVisitorId = fingerprintVisitorId;
15
- this.merchantId = merchantId;
16
- this.sessionId = sessionId;
17
- this.isSecure = isSecure;
28
+ constructor(options) {
29
+ var _a, _b;
30
+ validateOptions(options);
31
+ this.apiUrl = options.apiUrl;
32
+ this.fingerprintVisitorId = options.fingerprintVisitorId;
33
+ this.merchantId = options.merchantId;
34
+ this.sessionId = options.sessionId;
35
+ this.isSecure = (_a = options.isSecure) !== null && _a !== void 0 ? _a : false;
36
+ this.retryOptions = (_b = options.retryOptions) !== null && _b !== void 0 ? _b : {};
18
37
  }
19
38
  getSession() {
20
39
  return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
21
40
  var _a;
22
41
  try {
23
42
  const url = this.isSecure ? `${this.apiUrl}/v1/secure/sessions/${this.sessionId}` : `${this.apiUrl}/v1/sessions/${this.sessionId}`;
24
- const response = yield fetch(url, {
43
+ const result = yield this.request(url, {
25
44
  method: "GET",
26
45
  headers: {
27
46
  "Content-Type": "application/json;charset=UTF-8",
28
47
  "x-merchant-id": this.merchantId,
29
48
  "x-request-id": uuid(),
49
+ [SDK_HEADER_NAME]: SDK_HEADER_VALUE,
30
50
  },
31
51
  });
32
- const result = yield response.json();
33
- if (result.error || response.status !== 200) {
34
- reject(new Error(result.error));
35
- }
36
- else {
37
- this.projectId = (_a = result.operation) === null || _a === void 0 ? void 0 : _a.projectId;
38
- resolve(result);
39
- }
52
+ this.projectId = (_a = result.operation) === null || _a === void 0 ? void 0 : _a.projectId;
53
+ resolve(result);
40
54
  }
41
55
  catch (e) {
42
56
  reject(e);
@@ -47,15 +61,9 @@ export class FinteqHubProcessing {
47
61
  return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
48
62
  try {
49
63
  const url = this.isSecure ? `${this.apiUrl}/v1/secure/transactions/submit-form` : `${this.apiUrl}/v1/transactions/submit-form`;
50
- const response = yield this.sendPost(url, Object.assign({ session: Object.assign({ fingerprint: this.fingerprintVisitorId }, getDeviceData()) }, data));
51
- const result = yield response.json();
52
- if (result.error || response.status !== 200) {
53
- reject(new Error(result.error));
54
- }
55
- else {
56
- const url = this.isSecure ? `${this.apiUrl}/v1/secure/operations/${result.operationId}` : `${this.apiUrl}/v1/operations/${result.operationId}`;
57
- this.processOperation(url, resolve, reject);
58
- }
64
+ const result = yield this.sendPost(url, Object.assign({ session: Object.assign({ fingerprint: this.fingerprintVisitorId }, getDeviceData()) }, data));
65
+ const operationUrl = this.isSecure ? `${this.apiUrl}/v1/secure/operations/${result.operationId}` : `${this.apiUrl}/v1/operations/${result.operationId}`;
66
+ this.processOperation(operationUrl, resolve, reject);
59
67
  }
60
68
  catch (e) {
61
69
  reject(e);
@@ -65,36 +73,30 @@ export class FinteqHubProcessing {
65
73
  processOperation(url, resolve, reject) {
66
74
  return __awaiter(this, void 0, void 0, function* () {
67
75
  try {
68
- const response = yield this.sendPost(url, {});
69
- const result = yield response.json();
70
- if (response.status === 200) {
71
- if (result.type === "redirect") {
72
- resolve(result);
73
- }
74
- else if (result.type === "wait") {
75
- setTimeout(() => {
76
- this.processOperation(url, resolve, reject);
77
- }, result.waitInterval * 1000 || 10000);
78
- }
79
- else if (result.type === "submitForm") {
80
- let iframe = document.createElement("iframe");
81
- iframe.src = result.formUrl;
82
- iframe.style.display = "none";
83
- document.body.appendChild(iframe);
84
- iframe.onload = () => {
85
- this.processOperation(url, resolve, reject);
86
- if (iframe) {
87
- document.body.removeChild(iframe);
88
- iframe = null;
89
- }
90
- };
91
- }
92
- else {
93
- reject(new Error("unknown process operation type"));
94
- }
76
+ const result = yield this.sendPost(url, {});
77
+ if (result.type === "redirect") {
78
+ resolve(result);
79
+ }
80
+ else if (result.type === "wait") {
81
+ setTimeout(() => {
82
+ this.processOperation(url, resolve, reject);
83
+ }, result.waitInterval * 1000 || 10000);
84
+ }
85
+ else if (result.type === "submitForm") {
86
+ let iframe = document.createElement("iframe");
87
+ iframe.src = result.formUrl;
88
+ iframe.style.display = "none";
89
+ document.body.appendChild(iframe);
90
+ iframe.onload = () => {
91
+ this.processOperation(url, resolve, reject);
92
+ if (iframe) {
93
+ document.body.removeChild(iframe);
94
+ iframe = null;
95
+ }
96
+ };
95
97
  }
96
98
  else {
97
- reject(new Error(result.error));
99
+ reject(new Error("unknown process operation type"));
98
100
  }
99
101
  }
100
102
  catch (e) {
@@ -103,7 +105,7 @@ export class FinteqHubProcessing {
103
105
  });
104
106
  }
105
107
  sendPost(url, data) {
106
- return fetch(url, {
108
+ return this.request(url, {
107
109
  method: "POST",
108
110
  headers: {
109
111
  "Content-Type": "application/json;charset=UTF-8",
@@ -112,8 +114,115 @@ export class FinteqHubProcessing {
112
114
  "x-fingerprint": this.fingerprintVisitorId,
113
115
  "x-session-id": this.sessionId,
114
116
  "x-project-id": this.projectId,
117
+ [SDK_HEADER_NAME]: SDK_HEADER_VALUE,
115
118
  },
116
119
  body: JSON.stringify(data),
117
120
  });
118
121
  }
122
+ request(url, options) {
123
+ return __awaiter(this, void 0, void 0, function* () {
124
+ const { response, text, attempts } = yield this.fetchWithRetry(url, options);
125
+ const body = response.status !== 200 ? sanitizeBody(text) : undefined;
126
+ let result;
127
+ try {
128
+ result = JSON.parse(text);
129
+ }
130
+ catch (e) {
131
+ // V8 quotes an excerpt of the input in the parse error message (and stack), so it
132
+ // follows the same policy as the body: sanitized for non-200, omitted for 200 responses
133
+ const parseError = {
134
+ name: e.name,
135
+ message: response.status !== 200
136
+ ? sanitizeBody(e.message)
137
+ : "parse error message omitted (200 response body may carry credentials)",
138
+ };
139
+ throw this.responseError("invalid_json", `request to ${url} returned invalid JSON (status ${response.status})`, url, options, attempts, { status: response.status, body }, parseError);
140
+ }
141
+ if ((result === null || result === void 0 ? void 0 : result.error) || response.status !== 200) {
142
+ throw this.responseError("http_error", (result === null || result === void 0 ? void 0 : result.error) || `unexpected response status ${response.status}`, url, options, attempts, { status: response.status, body, error: result === null || result === void 0 ? void 0 : result.error });
143
+ }
144
+ return result;
145
+ });
146
+ }
147
+ fetchWithRetry(url, options) {
148
+ var _a, _b;
149
+ return __awaiter(this, void 0, void 0, function* () {
150
+ const retryCount = (_a = this.retryOptions.retryCount) !== null && _a !== void 0 ? _a : DEFAULT_RETRY_COUNT;
151
+ const retryStatusCode = (_b = this.retryOptions.retryStatusCode) !== null && _b !== void 0 ? _b : defaultRetryStatusCode;
152
+ const attempts = [];
153
+ for (let attempt = 1;; attempt += 1) {
154
+ let reason;
155
+ let response;
156
+ let result;
157
+ const startedAt = Date.now();
158
+ try {
159
+ response = yield fetch(url, options);
160
+ // the connection can also drop while the body is being read — that is a network failure too
161
+ const text = yield response.text();
162
+ result = { response, text };
163
+ attempts.push({ durationMs: Date.now() - startedAt, status: response.status });
164
+ }
165
+ catch (e) {
166
+ // status is known when the failure happened while reading the body of a received response
167
+ attempts.push({ durationMs: Date.now() - startedAt, status: response === null || response === void 0 ? void 0 : response.status, error: e.message });
168
+ if (attempt > retryCount) {
169
+ const message = `request to ${url} failed after ${attempt} attempt(s): ${e.message}`;
170
+ const diagnostics = this.collectDiagnostics("network", url, options, attempts, e, response ? { status: response.status } : undefined);
171
+ console.error(`sdk-js: request failed: ${message}`, diagnostics);
172
+ throw new RequestError(message, diagnostics);
173
+ }
174
+ reason = e.message;
175
+ }
176
+ // retryStatusCode is user code — called outside the try so its errors are not
177
+ // recorded as a network failure of the attempt
178
+ if (result) {
179
+ if (!retryStatusCode(result.response.status) || attempt > retryCount) {
180
+ return Object.assign(Object.assign({}, result), { attempts });
181
+ }
182
+ reason = `status code ${result.response.status}`;
183
+ }
184
+ const delay = RETRY_DELAYS_MS[Math.min(attempt - 1, RETRY_DELAYS_MS.length - 1)];
185
+ console.warn(`sdk-js: request to ${url} failed (${reason}), retry ${attempt}/${retryCount} in ${delay}ms`, {
186
+ requestId: options.headers["x-request-id"],
187
+ });
188
+ yield new Promise((resolve) => setTimeout(resolve, delay));
189
+ }
190
+ });
191
+ }
192
+ responseError(kind, message, url, options, attempts, response, error) {
193
+ const diagnostics = this.collectDiagnostics(kind, url, options, attempts, error, response);
194
+ console.error(`sdk-js: request failed: ${message}`, diagnostics);
195
+ return new RequestError(message, diagnostics);
196
+ }
197
+ collectDiagnostics(kind, url, options, attempts, error, response) {
198
+ const connection = navigator.connection;
199
+ return {
200
+ kind,
201
+ sdkVersion: SDK_VERSION,
202
+ error: error
203
+ ? {
204
+ name: error.name,
205
+ message: error.message,
206
+ stack: error.stack,
207
+ cause: error.cause,
208
+ }
209
+ : undefined,
210
+ response,
211
+ request: {
212
+ url,
213
+ method: options.method,
214
+ requestId: options.headers["x-request-id"],
215
+ sessionId: this.sessionId,
216
+ attempts,
217
+ },
218
+ environment: {
219
+ online: navigator.onLine,
220
+ visibility: document.visibilityState,
221
+ connection: connection
222
+ ? { effectiveType: connection.effectiveType, rtt: connection.rtt, downlink: connection.downlink }
223
+ : undefined,
224
+ timestamp: new Date().toISOString(),
225
+ },
226
+ };
227
+ }
119
228
  }
@@ -1,3 +1,4 @@
1
+ import type { ProcessingOptions } from "./processing";
1
2
  export declare const DeviceType: {
2
3
  Unknown: string;
3
4
  Computer: string;
@@ -9,6 +10,7 @@ export declare const DeviceType: {
9
10
  };
10
11
  export declare function uuid(): string;
11
12
  export declare function getDeviceType(): string;
13
+ export declare function validateOptions(options: ProcessingOptions): void;
12
14
  export declare function getDeviceData(): {
13
15
  device: {
14
16
  type: string;
package/dist/src/utils.js CHANGED
@@ -40,6 +40,32 @@ export function getDeviceType() {
40
40
  }
41
41
  return DeviceType.Unknown;
42
42
  }
43
+ const REQUIRED_OPTIONS = ["apiUrl", "fingerprintVisitorId", "merchantId", "sessionId"];
44
+ export function validateOptions(options) {
45
+ if (typeof options !== "object" || options === null) {
46
+ throw new TypeError("sdk-js: constructor expects an options object: { apiUrl, fingerprintVisitorId, merchantId, sessionId, isSecure?, retryOptions? }");
47
+ }
48
+ for (const key of REQUIRED_OPTIONS) {
49
+ if (typeof options[key] !== "string" || options[key] === "") {
50
+ throw new TypeError(`sdk-js: option "${key}" must be a non-empty string`);
51
+ }
52
+ }
53
+ if (options.isSecure !== undefined && typeof options.isSecure !== "boolean") {
54
+ throw new TypeError('sdk-js: option "isSecure" must be a boolean');
55
+ }
56
+ if (options.retryOptions !== undefined) {
57
+ if (typeof options.retryOptions !== "object" || options.retryOptions === null) {
58
+ throw new TypeError('sdk-js: option "retryOptions" must be an object');
59
+ }
60
+ const { retryCount, retryStatusCode } = options.retryOptions;
61
+ if (retryCount !== undefined && (!Number.isInteger(retryCount) || retryCount < 0)) {
62
+ throw new TypeError('sdk-js: option "retryOptions.retryCount" must be a non-negative integer');
63
+ }
64
+ if (retryStatusCode !== undefined && typeof retryStatusCode !== "function") {
65
+ throw new TypeError('sdk-js: option "retryOptions.retryStatusCode" must be a function');
66
+ }
67
+ }
68
+ }
43
69
  export function getDeviceData() {
44
70
  var _a, _b, _c;
45
71
  return {
@@ -0,0 +1,3 @@
1
+ export declare const SDK_HEADER_NAME = "X-Finteqhub-SDK";
2
+ export declare const SDK_VERSION = "0.12.0-beta.2";
3
+ export declare const SDK_HEADER_VALUE: string;
@@ -0,0 +1,3 @@
1
+ export const SDK_HEADER_NAME = "X-Finteqhub-SDK";
2
+ export const SDK_VERSION = "0.12.0-beta.2";
3
+ export const SDK_HEADER_VALUE = `sdk-js/${SDK_VERSION}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finteqhub/sdk-js",
3
- "version": "0.10.1",
3
+ "version": "0.12.0-beta.2",
4
4
  "description": "SDK for interacting with FinteqHub processing API",
5
5
  "main": "./dist/index.js",
6
6
  "typings": "./dist/index.d.ts",