@staxpayments/staxpayments-js 2.30.18

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/dist/client.js ADDED
@@ -0,0 +1,972 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.StaxPaymentsCredentials = exports.StaxPaymentsBaseClient = exports.StaxApiCredentials = exports.SignatureFormat = exports.RoundingMode = exports.PromptType = exports.HealthcareType = exports.CardType = exports.CVMType = exports.AVSResponse = void 0;
8
+ var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
9
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
10
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
11
+ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
12
+ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
13
+ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
14
+ var _axios = _interopRequireDefault(require("axios"));
15
+ var _cryptoutils = _interopRequireDefault(require("./cryptoutils"));
16
+ var _https = _interopRequireDefault(require("https"));
17
+ var _httpsBrowserify = _interopRequireDefault(require("https-browserify"));
18
+ var _aesJs = _interopRequireDefault(require("aes-js"));
19
+ var _sha = require("@noble/hashes/sha256");
20
+ var _utils = require("@noble/hashes/utils");
21
+ /**
22
+ * Copyright 2019-2026 BlockChyp, Inc. All rights reserved. Use of this code is governed
23
+ * by a license that can be found in the LICENSE file.
24
+ *
25
+ * This file was generated automatically by the BlockChyp SDK Generator. Changes to this
26
+ * file will be lost every time the code is regenerated.
27
+ */
28
+
29
+ // optionalNodeModule resolves a Node built-in at run time, returning undefined
30
+ // in the browser. The name is held in a variable so the browser bundler cannot
31
+ // statically resolve it and try to shim it into the bundle.
32
+ function optionalNodeModule(name) {
33
+ try {
34
+ if (typeof module !== 'undefined' && typeof module.require === 'function') {
35
+ return module.require(name);
36
+ }
37
+ } catch (e) {
38
+ // Not running under Node.
39
+ }
40
+ return undefined;
41
+ }
42
+
43
+ /* eslint-disable no-unused-vars */
44
+ var CardType = exports.CardType = Object.freeze({
45
+ CREDIT: 0,
46
+ DEBIT: 1,
47
+ EBT: 2,
48
+ BLOCKCHAIN_GIFT: 3,
49
+ HEALTHCARE: 4
50
+ });
51
+ var SignatureFormat = exports.SignatureFormat = Object.freeze({
52
+ NONE: '',
53
+ PNG: 'png',
54
+ JPG: 'jpg',
55
+ GIF: 'gif'
56
+ });
57
+ var RoundingMode = exports.RoundingMode = Object.freeze({
58
+ UP: 'up',
59
+ NEAREST: 'nearest',
60
+ DOWN: 'down'
61
+ });
62
+ var PromptType = exports.PromptType = Object.freeze({
63
+ AMOUNT: 'amount',
64
+ EMAIL: 'email',
65
+ PHONE_NUMBER: 'phone',
66
+ CUSTOMER_NUMBER: 'customer-number',
67
+ REWARDS_NUMBER: 'rewards-number',
68
+ FIRST_NAME: 'first-name',
69
+ LAST_NAME: 'last-name'
70
+ });
71
+ var AVSResponse = exports.AVSResponse = Object.freeze({
72
+ NOT_APPLICABLE: '',
73
+ NOT_SUPPORTED: 'not_supported',
74
+ RETRY: 'retry',
75
+ NO_MATCH: 'no_match',
76
+ ADDRESS_MATCH: 'address_match',
77
+ POSTAL_CODE_MATCH: 'zip_match',
78
+ ADDRESS_AND_POSTAL_CODE_MATCH: 'match'
79
+ });
80
+ var CVMType = exports.CVMType = Object.freeze({
81
+ SIGNATURE: 'Signature',
82
+ OFFLINE_PIN: 'Offline PIN',
83
+ ONLINE_PIN: 'Online PIN',
84
+ CDCVM: 'CDCVM',
85
+ NO_CVM: 'No CVM'
86
+ });
87
+ var HealthcareType = exports.HealthcareType = Object.freeze({
88
+ HEALTHCARE: 'healthcare',
89
+ PRESCRIPTION: 'prescription',
90
+ VISION: 'vision',
91
+ CLINIC: 'clinic',
92
+ DENTAL: 'dental'
93
+ });
94
+ /* eslint-enable no-unused-vars */
95
+
96
+ var VERSION = require('../package.json').version;
97
+ var USER_AGENT = "StaxPayments-JavaScript/".concat(VERSION);
98
+ // Some browsers do not allow setting the user-agent header, so we set
99
+ // an alternative if running from a browser.
100
+ var AGENT_HEADER = typeof window === 'undefined' ? 'User-Agent' : 'X-Requested-With';
101
+
102
+ // DEFAULT_CORE_HOST is the default Stax core API host used when none is supplied.
103
+ var DEFAULT_CORE_HOST = 'https://apiprod.fattlabs.com';
104
+
105
+ // TRANSIENT_CREDENTIALS_PATH is the core API path that exchanges a Stax bearer
106
+ // token for short-lived Stax Payments transient credentials. This is an
107
+ // internal mechanism of the SDK and is never exposed as a public method.
108
+ var TRANSIENT_CREDENTIALS_PATH = '/terminals/transient-credentials';
109
+
110
+ // EXPIRY_SKEW_MS refreshes transient credentials this many milliseconds before
111
+ // their stated expiry to avoid a credential lapsing mid-request (clock-skew
112
+ // buffer).
113
+ var EXPIRY_SKEW_MS = 30 * 1000;
114
+
115
+ // EXPIRY_FALLBACK_MS is how long transient credentials are assumed to live when
116
+ // the core API does not state an expiry. The core endpoint does not populate
117
+ // expiresAt yet, so in practice this is the credential lifetime.
118
+ var EXPIRY_FALLBACK_MS = 8 * 60 * 60 * 1000;
119
+
120
+ // OFFLINE_FIXED_KEY is the static half of the offline route cache key. It is
121
+ // hashed together with the current signing key, so a cache file is readable
122
+ // only by a client holding the same credentials. It matches the constant used
123
+ // by the other Stax Payments SDKs, so the cache format is portable between
124
+ // them.
125
+ var OFFLINE_FIXED_KEY = 'cb22789c9d5c344a10e0474f134db39e25eb3bbf5a1b1a5e89b507f15ea9519c';
126
+
127
+ // StaxPaymentsBaseClient holds the shared transport: host configuration,
128
+ // transient-credential exchange, and the gateway/dashboard/terminal/core
129
+ // request plumbing. Each API namespace ships a client that wraps this base and
130
+ // adds only the endpoint methods for that namespace (e.g. PaymentsClient); the
131
+ // root StaxPaymentsClient builds one base and shares it across namespaces.
132
+ var StaxPaymentsBaseClient = exports.StaxPaymentsBaseClient = /*#__PURE__*/function () {
133
+ // Construct the shared transport with your Stax bearer token. Terminal
134
+ // transactions transparently exchange it for short-lived transient
135
+ // credentials.
136
+ function StaxPaymentsBaseClient(creds) {
137
+ var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
138
+ (0, _classCallCheck2["default"])(this, StaxPaymentsBaseClient);
139
+ this.gatewayHost = opts.gatewayHost || 'https://api.blockchyp.com';
140
+ this.testGatewayHost = 'https://test.blockchyp.com';
141
+ this.dashboardHost = 'https://dashboard.blockchyp.com';
142
+ this.coreHost = opts.coreHost || DEFAULT_CORE_HOST;
143
+ this.bearerToken = creds.bearerToken;
144
+ // bcCredentials holds the short-lived BlockChyp transient credentials
145
+ // obtained by exchanging the Stax bearer token. SDK-managed; never set by
146
+ // the integrator.
147
+ this.bcCredentials = undefined;
148
+ this.bcCredentialsExpiresAtMs = 0;
149
+ // Single-flight guard: concurrent callers share one in-flight exchange.
150
+ this.bcCredentialsInFlight = undefined;
151
+ this.https = true;
152
+ this.cloudRelay = false;
153
+ this.routeCacheTTL = 60;
154
+ this.gatewayTimeout = 20;
155
+ this.terminalTimeout = 120;
156
+ this._routeCache = {};
157
+ }
158
+ return (0, _createClass2["default"])(StaxPaymentsBaseClient, [{
159
+ key: "getGatewayHost",
160
+ value: function getGatewayHost() {
161
+ return this.gatewayHost;
162
+ }
163
+ }, {
164
+ key: "getDashboardHost",
165
+ value: function getDashboardHost() {
166
+ return this.dashboardHost;
167
+ }
168
+ }, {
169
+ key: "setGatewayHost",
170
+ value: function setGatewayHost(host) {
171
+ this.gatewayHost = host;
172
+ }
173
+ }, {
174
+ key: "setDashboardHost",
175
+ value: function setDashboardHost(host) {
176
+ this.dashboardHost = host;
177
+ }
178
+ }, {
179
+ key: "setTestGatewayHost",
180
+ value: function setTestGatewayHost(host) {
181
+ this.testGatewayHost = host;
182
+ }
183
+
184
+ // setCoreHost overrides the Stax core API host. An empty host resets it to the
185
+ // default. Cached credentials remain valid; subsequent exchanges and
186
+ // core-routed calls use the new host.
187
+ }, {
188
+ key: "setCoreHost",
189
+ value: function setCoreHost(host) {
190
+ this.coreHost = host || DEFAULT_CORE_HOST;
191
+ }
192
+ }, {
193
+ key: "heartbeat",
194
+ value: function heartbeat() {
195
+ return this._gatewayRequest('get', '/api/heartbeat');
196
+ }
197
+
198
+ // routeTransaction runs a transaction end to end, dispatching based on whether
199
+ // a terminal is named: to the terminal (card-present), directly or via cloud
200
+ // relay, when a terminal name is supplied, or to the gateway (card-not-present)
201
+ // otherwise.
202
+ }, {
203
+ key: "routeTransaction",
204
+ value: function () {
205
+ var _routeTransaction = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee(method, request, terminalPath, cloudPath) {
206
+ var response, route, _t;
207
+ return _regenerator["default"].wrap(function (_context) {
208
+ while (1) switch (_context.prev = _context.next) {
209
+ case 0:
210
+ _context.next = 1;
211
+ return this.ensure();
212
+ case 1:
213
+ _context.next = 2;
214
+ return this._populateSignatureOptions(request);
215
+ case 2:
216
+ if (!this.isTerminalRouted(request)) {
217
+ _context.next = 8;
218
+ break;
219
+ }
220
+ _context.next = 3;
221
+ return this._resolveTerminalRoute(request.terminalName);
222
+ case 3:
223
+ route = _context.sent;
224
+ if (!route.cloudRelayEnabled) {
225
+ _context.next = 5;
226
+ break;
227
+ }
228
+ _context.next = 4;
229
+ return this._relayRequest(method, cloudPath, request);
230
+ case 4:
231
+ _t = _context.sent;
232
+ _context.next = 7;
233
+ break;
234
+ case 5:
235
+ _context.next = 6;
236
+ return this._terminalRequest(method, route, terminalPath, request);
237
+ case 6:
238
+ _t = _context.sent;
239
+ case 7:
240
+ response = _t;
241
+ _context.next = 10;
242
+ break;
243
+ case 8:
244
+ _context.next = 9;
245
+ return this._gatewayRequest(method, cloudPath, request);
246
+ case 9:
247
+ response = _context.sent;
248
+ case 10:
249
+ _context.next = 11;
250
+ return this._handleSignature(request, response ? response.data : undefined);
251
+ case 11:
252
+ return _context.abrupt("return", response);
253
+ case 12:
254
+ case "end":
255
+ return _context.stop();
256
+ }
257
+ }, _callee, this);
258
+ }));
259
+ function routeTransaction(_x, _x2, _x3, _x4) {
260
+ return _routeTransaction.apply(this, arguments);
261
+ }
262
+ return routeTransaction;
263
+ }()
264
+ }, {
265
+ key: "routeTransactionPost",
266
+ value: function () {
267
+ var _routeTransactionPost = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee2(request, terminalPath, cloudPath) {
268
+ return _regenerator["default"].wrap(function (_context2) {
269
+ while (1) switch (_context2.prev = _context2.next) {
270
+ case 0:
271
+ return _context2.abrupt("return", this.routeTransaction('post', request, terminalPath, cloudPath));
272
+ case 1:
273
+ case "end":
274
+ return _context2.stop();
275
+ }
276
+ }, _callee2, this);
277
+ }));
278
+ function routeTransactionPost(_x5, _x6, _x7) {
279
+ return _routeTransactionPost.apply(this, arguments);
280
+ }
281
+ return routeTransactionPost;
282
+ }()
283
+ }, {
284
+ key: "returnValidationError",
285
+ value: function returnValidationError(desc) {
286
+ var result = {
287
+ data: {
288
+ approved: false,
289
+ success: false,
290
+ error: desc
291
+ }
292
+ };
293
+ return result;
294
+ }
295
+ }, {
296
+ key: "validateRequest",
297
+ value: function validateRequest(request) {
298
+ if (!this.validateCurrency(request.amount)) {
299
+ return false;
300
+ }
301
+ return true;
302
+ }
303
+ }, {
304
+ key: "validateCurrency",
305
+ value: function validateCurrency(val) {
306
+ var amt = parseFloat(val);
307
+ console.log(amt);
308
+ if (amt && !isNaN(amt)) {
309
+ var decMatch = val.match(/\./g || []);
310
+ if (decMatch && decMatch.length > 1) {
311
+ return false;
312
+ }
313
+ return true;
314
+ }
315
+ return false;
316
+ }
317
+
318
+ // isTerminalRouted reports whether a request names a terminal. Whether that
319
+ // terminal is reached directly or over cloud relay is a property of its
320
+ // route, resolved per terminal, not a client-wide setting.
321
+ }, {
322
+ key: "isTerminalRouted",
323
+ value: function isTerminalRouted(request) {
324
+ return Boolean(request && request.terminalName);
325
+ }
326
+
327
+ // ensure guarantees the client holds valid merchant-scoped transient
328
+ // credentials, exchanging the Stax bearer token via the core API when the
329
+ // cache is empty or near expiry. Concurrent callers share one in-flight
330
+ // exchange (single-flight). Terminal calls invoke this before routing.
331
+ }, {
332
+ key: "ensure",
333
+ value: function () {
334
+ var _ensure = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee3() {
335
+ return _regenerator["default"].wrap(function (_context3) {
336
+ while (1) switch (_context3.prev = _context3.next) {
337
+ case 0:
338
+ if (!(this.bcCredentials && Date.now() + EXPIRY_SKEW_MS < this.bcCredentialsExpiresAtMs)) {
339
+ _context3.next = 1;
340
+ break;
341
+ }
342
+ return _context3.abrupt("return");
343
+ case 1:
344
+ if (!this.bcCredentialsInFlight) {
345
+ _context3.next = 3;
346
+ break;
347
+ }
348
+ _context3.next = 2;
349
+ return this.bcCredentialsInFlight;
350
+ case 2:
351
+ return _context3.abrupt("return");
352
+ case 3:
353
+ this.bcCredentialsInFlight = this._exchange();
354
+ _context3.prev = 4;
355
+ _context3.next = 5;
356
+ return this.bcCredentialsInFlight;
357
+ case 5:
358
+ _context3.prev = 5;
359
+ this.bcCredentialsInFlight = undefined;
360
+ return _context3.finish(5);
361
+ case 6:
362
+ case "end":
363
+ return _context3.stop();
364
+ }
365
+ }, _callee3, this, [[4,, 5, 6]]);
366
+ }));
367
+ function ensure() {
368
+ return _ensure.apply(this, arguments);
369
+ }
370
+ return ensure;
371
+ }()
372
+ }, {
373
+ key: "_exchange",
374
+ value: function () {
375
+ var _exchange2 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee4() {
376
+ var response, data;
377
+ return _regenerator["default"].wrap(function (_context4) {
378
+ while (1) switch (_context4.prev = _context4.next) {
379
+ case 0:
380
+ _context4.next = 1;
381
+ return this._coreRequest('get', TRANSIENT_CREDENTIALS_PATH);
382
+ case 1:
383
+ response = _context4.sent;
384
+ data = response.data;
385
+ this.bcCredentials = new StaxPaymentsCredentials(data.apiKey, data.bearerToken, data.signingKey);
386
+ this.bcCredentialsExpiresAtMs = data.expiresAt ? Date.parse(data.expiresAt) : Date.now() + EXPIRY_FALLBACK_MS;
387
+ case 2:
388
+ case "end":
389
+ return _context4.stop();
390
+ }
391
+ }, _callee4, this);
392
+ }));
393
+ function _exchange() {
394
+ return _exchange2.apply(this, arguments);
395
+ }
396
+ return _exchange;
397
+ }()
398
+ }, {
399
+ key: "_relayRequest",
400
+ value: function _relayRequest(method, path, request) {
401
+ return this._gatewayRequest(method, path, request, true);
402
+ }
403
+ }, {
404
+ key: "_uploadRequest",
405
+ value: function () {
406
+ var _uploadRequest2 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee5(path, request, content) {
407
+ var config;
408
+ return _regenerator["default"].wrap(function (_context5) {
409
+ while (1) switch (_context5.prev = _context5.next) {
410
+ case 0:
411
+ _context5.next = 1;
412
+ return this.ensure();
413
+ case 1:
414
+ config = {
415
+ method: 'post',
416
+ url: this._assembleDashboardUrl(path),
417
+ timeout: this._getTimeout(request, this.gatewayTimeout) * 1000,
418
+ headers: (0, _defineProperty2["default"])({}, AGENT_HEADER, USER_AGENT)
419
+ };
420
+ config.data = content;
421
+ if (this.bcCredentials && this.bcCredentials.apiKey) {
422
+ config.headers = Object.assign(config.headers, _cryptoutils["default"].generateGatewayHeaders(this.bcCredentials));
423
+ }
424
+ if (request.fileSize) {
425
+ config.headers['X-File-Size'] = request.fileSize.toFixed();
426
+ }
427
+ if (request.fileName) {
428
+ config.headers['X-Upload-File-Name'] = request.fileName;
429
+ }
430
+ if (request.uploadId) {
431
+ config.headers['X-Upload-ID'] = request.uploadId;
432
+ }
433
+ return _context5.abrupt("return", (0, _axios["default"])(config));
434
+ case 2:
435
+ case "end":
436
+ return _context5.stop();
437
+ }
438
+ }, _callee5, this);
439
+ }));
440
+ function _uploadRequest(_x8, _x9, _x0) {
441
+ return _uploadRequest2.apply(this, arguments);
442
+ }
443
+ return _uploadRequest;
444
+ }()
445
+ }, {
446
+ key: "_dashboardRequest",
447
+ value: function () {
448
+ var _dashboardRequest2 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee6(method, path, request) {
449
+ var config;
450
+ return _regenerator["default"].wrap(function (_context6) {
451
+ while (1) switch (_context6.prev = _context6.next) {
452
+ case 0:
453
+ _context6.next = 1;
454
+ return this.ensure();
455
+ case 1:
456
+ config = {
457
+ method: method,
458
+ url: this._assembleDashboardUrl(path),
459
+ timeout: this._getTimeout(request, this.gatewayTimeout) * 1000,
460
+ headers: (0, _defineProperty2["default"])((0, _defineProperty2["default"])({}, AGENT_HEADER, USER_AGENT), 'Content-Type', 'application/json')
461
+ };
462
+ if (method !== 'get') {
463
+ config.data = request;
464
+ }
465
+ if (this.bcCredentials && this.bcCredentials.apiKey) {
466
+ config.headers = Object.assign(config.headers, _cryptoutils["default"].generateGatewayHeaders(this.bcCredentials));
467
+ }
468
+ return _context6.abrupt("return", (0, _axios["default"])(config));
469
+ case 2:
470
+ case "end":
471
+ return _context6.stop();
472
+ }
473
+ }, _callee6, this);
474
+ }));
475
+ function _dashboardRequest(_x1, _x10, _x11) {
476
+ return _dashboardRequest2.apply(this, arguments);
477
+ }
478
+ return _dashboardRequest;
479
+ }()
480
+ }, {
481
+ key: "_gatewayRequest",
482
+ value: function () {
483
+ var _gatewayRequest2 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee7(method, path, request, relay) {
484
+ var config;
485
+ return _regenerator["default"].wrap(function (_context7) {
486
+ while (1) switch (_context7.prev = _context7.next) {
487
+ case 0:
488
+ _context7.next = 1;
489
+ return this.ensure();
490
+ case 1:
491
+ config = {
492
+ method: method,
493
+ url: this._assembleGatewayUrl(path, request),
494
+ timeout: this._getTimeout(request, relay ? this.terminalTimeout : this.gatewayTimeout) * 1000,
495
+ headers: (0, _defineProperty2["default"])((0, _defineProperty2["default"])({}, AGENT_HEADER, USER_AGENT), 'Content-Type', 'application/json')
496
+ };
497
+ if (method !== 'get') {
498
+ config.data = request;
499
+ }
500
+ if (this.bcCredentials && this.bcCredentials.apiKey) {
501
+ config.headers = Object.assign(config.headers, _cryptoutils["default"].generateGatewayHeaders(this.bcCredentials));
502
+ }
503
+ return _context7.abrupt("return", (0, _axios["default"])(config));
504
+ case 2:
505
+ case "end":
506
+ return _context7.stop();
507
+ }
508
+ }, _callee7, this);
509
+ }));
510
+ function _gatewayRequest(_x12, _x13, _x14, _x15) {
511
+ return _gatewayRequest2.apply(this, arguments);
512
+ }
513
+ return _gatewayRequest;
514
+ }() // _coreRequest sends a request to the Stax core API authenticated with the
515
+ // Stax bearer token. It mirrors _gatewayRequest/_dashboardRequest, differing
516
+ // only in host (coreHost) and auth (the Stax bearer token rather than HMAC
517
+ // credentials). Used by core-routed endpoints and the internal
518
+ // transient-credential exchange.
519
+ }, {
520
+ key: "_coreRequest",
521
+ value: function _coreRequest(method, path, request) {
522
+ var config = {
523
+ method: method,
524
+ url: this._assembleCoreUrl(path),
525
+ timeout: this._getTimeout(request, this.gatewayTimeout) * 1000,
526
+ headers: (0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])({}, AGENT_HEADER, USER_AGENT), 'Content-Type', 'application/json'), "Authorization", "Bearer ".concat(this.bearerToken))
527
+ };
528
+ if (method !== 'get') {
529
+ config.data = request;
530
+ }
531
+ return (0, _axios["default"])(config);
532
+ }
533
+ }, {
534
+ key: "_getTimeout",
535
+ value: function _getTimeout(request, defaultTimeout) {
536
+ if (request && 'timeout' in request) {
537
+ return request['timeout'];
538
+ }
539
+ return defaultTimeout;
540
+ }
541
+ }, {
542
+ key: "_assembleDashboardUrl",
543
+ value: function _assembleDashboardUrl(path) {
544
+ return this.dashboardHost + path;
545
+ }
546
+ }, {
547
+ key: "_assembleCoreUrl",
548
+ value: function _assembleCoreUrl(path) {
549
+ return this.coreHost + path;
550
+ }
551
+ }, {
552
+ key: "_assembleGatewayUrl",
553
+ value: function _assembleGatewayUrl(path, payload) {
554
+ var result = '';
555
+ if (payload && payload.test) {
556
+ result = result + this.testGatewayHost;
557
+ } else {
558
+ result = result + this.gatewayHost;
559
+ }
560
+ result = result + path;
561
+ return result;
562
+ }
563
+ }, {
564
+ key: "_terminalRequest",
565
+ value: function () {
566
+ var _terminalRequest2 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee8(method, route, path, request) {
567
+ var url, config;
568
+ return _regenerator["default"].wrap(function (_context8) {
569
+ while (1) switch (_context8.prev = _context8.next) {
570
+ case 0:
571
+ _context8.next = 1;
572
+ return this._assembleTerminalUrl(route, path);
573
+ case 1:
574
+ url = _context8.sent;
575
+ config = {
576
+ method: method,
577
+ url: url,
578
+ headers: (0, _defineProperty2["default"])((0, _defineProperty2["default"])({}, AGENT_HEADER, USER_AGENT), 'Content-Type', 'application/json'),
579
+ timeout: this._getTimeout(request, this.terminalTimeout) * 1000
580
+ };
581
+ if (this.https) {
582
+ if (_https["default"]) {
583
+ config.httpsAgent = new _https["default"].Agent({
584
+ rejectUnauthorized: false
585
+ });
586
+ } else {
587
+ config.httpsAgent = new _httpsBrowserify["default"].Agent({
588
+ rejectUnauthorized: false
589
+ });
590
+ config.httpsAgent.protocol = 'https:';
591
+ }
592
+ }
593
+ if (request) {
594
+ config.data = {
595
+ apiKey: route.transientCredentials.apiKey,
596
+ bearerToken: route.transientCredentials.bearerToken,
597
+ signingKey: route.transientCredentials.signingKey,
598
+ request: request
599
+ };
600
+ }
601
+ return _context8.abrupt("return", (0, _axios["default"])(config));
602
+ case 2:
603
+ case "end":
604
+ return _context8.stop();
605
+ }
606
+ }, _callee8, this);
607
+ }));
608
+ function _terminalRequest(_x16, _x17, _x18, _x19) {
609
+ return _terminalRequest2.apply(this, arguments);
610
+ }
611
+ return _terminalRequest;
612
+ }()
613
+ }, {
614
+ key: "_assembleTerminalUrl",
615
+ value: function _assembleTerminalUrl(route, path) {
616
+ var result = 'http';
617
+ if (this.https) {
618
+ result = result + 's';
619
+ }
620
+ result = result + '://';
621
+ result = result + route.ipAddress;
622
+ if (this.https) {
623
+ result = result + ':8443';
624
+ } else {
625
+ result = result + ':8080';
626
+ }
627
+ result = result + path;
628
+ return result;
629
+ }
630
+
631
+ // _populateSignatureOptions infers the signature image format from the
632
+ // requested file extension when the caller did not state one, and rejects a
633
+ // format the terminal cannot produce before the transaction is sent.
634
+ }, {
635
+ key: "_populateSignatureOptions",
636
+ value: function () {
637
+ var _populateSignatureOptions2 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee9(request) {
638
+ var parts, valid;
639
+ return _regenerator["default"].wrap(function (_context9) {
640
+ while (1) switch (_context9.prev = _context9.next) {
641
+ case 0:
642
+ if (!(!request || !request.sigFile)) {
643
+ _context9.next = 1;
644
+ break;
645
+ }
646
+ return _context9.abrupt("return");
647
+ case 1:
648
+ if (!request.sigFormat) {
649
+ parts = String(request.sigFile).split('.');
650
+ request.sigFormat = parts[parts.length - 1].toLowerCase();
651
+ }
652
+ valid = [SignatureFormat.NONE, SignatureFormat.PNG, SignatureFormat.JPG, SignatureFormat.GIF];
653
+ if (!(valid.indexOf(request.sigFormat) < 0)) {
654
+ _context9.next = 2;
655
+ break;
656
+ }
657
+ throw new Error('invalid signature format: ' + request.sigFormat);
658
+ case 2:
659
+ case "end":
660
+ return _context9.stop();
661
+ }
662
+ }, _callee9);
663
+ }));
664
+ function _populateSignatureOptions(_x20) {
665
+ return _populateSignatureOptions2.apply(this, arguments);
666
+ }
667
+ return _populateSignatureOptions;
668
+ }() // _handleSignature writes the captured signature image to the file the caller
669
+ // asked for and clears it from the response, so the hex payload is not left
670
+ // in an object the caller is likely to log. Writing needs a filesystem, so in
671
+ // the browser the image is left on the response instead.
672
+ }, {
673
+ key: "_handleSignature",
674
+ value: function () {
675
+ var _handleSignature2 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee0(request, response) {
676
+ var fs, content;
677
+ return _regenerator["default"].wrap(function (_context0) {
678
+ while (1) switch (_context0.prev = _context0.next) {
679
+ case 0:
680
+ if (!(!request || !request.sigFile || !response || !response.sigFile)) {
681
+ _context0.next = 1;
682
+ break;
683
+ }
684
+ return _context0.abrupt("return");
685
+ case 1:
686
+ fs = optionalNodeModule('fs');
687
+ if (fs) {
688
+ _context0.next = 2;
689
+ break;
690
+ }
691
+ return _context0.abrupt("return");
692
+ case 2:
693
+ content = Buffer.from(response.sigFile, 'hex');
694
+ response.sigFile = '';
695
+ fs.writeFileSync(request.sigFile, content, {
696
+ mode: 384
697
+ });
698
+ case 3:
699
+ case "end":
700
+ return _context0.stop();
701
+ }
702
+ }, _callee0);
703
+ }));
704
+ function _handleSignature(_x21, _x22) {
705
+ return _handleSignature2.apply(this, arguments);
706
+ }
707
+ return _handleSignature;
708
+ }() // _offlineCacheFile is the path routes are persisted to, or undefined in the
709
+ // browser, where there is nothing to persist to.
710
+ }, {
711
+ key: "_offlineCacheFile",
712
+ value: function _offlineCacheFile() {
713
+ var os = optionalNodeModule('os');
714
+ var path = optionalNodeModule('path');
715
+ if (!os || !path) {
716
+ return undefined;
717
+ }
718
+ return path.join(os.tmpdir(), '.staxpayments_routes');
719
+ }
720
+
721
+ // _routeCacheKey scopes a cached route to the credentials that resolved it, so
722
+ // rotating credentials cannot serve a route resolved under the previous set.
723
+ }, {
724
+ key: "_routeCacheKey",
725
+ value: function _routeCacheKey(terminalName) {
726
+ return (this.bcCredentials ? this.bcCredentials.apiKey : '') + terminalName;
727
+ }
728
+
729
+ // _deriveOfflineKey hashes the fixed key together with the current signing
730
+ // key. The cache is therefore readable only while the same credentials are
731
+ // held, and unreadable to anything else that finds the file.
732
+ }, {
733
+ key: "_deriveOfflineKey",
734
+ value: function _deriveOfflineKey() {
735
+ var signingKey = this.bcCredentials ? this.bcCredentials.signingKey : '';
736
+ var input = new Uint8Array([].concat((0, _toConsumableArray2["default"])((0, _utils.hexToBytes)(OFFLINE_FIXED_KEY)), (0, _toConsumableArray2["default"])((0, _utils.hexToBytes)(signingKey))));
737
+ return (0, _sha.sha256)(input);
738
+ }
739
+
740
+ // AES/CBC/PKCS7 over the first 16 bytes of the derived key, hex encoded with
741
+ // the IV prefixed. The scheme is shared with the other Stax Payments SDKs.
742
+ }, {
743
+ key: "_encrypt",
744
+ value: function _encrypt(value) {
745
+ var key = this._deriveOfflineKey().slice(0, 16);
746
+ var iv = (0, _utils.randomBytes)(16);
747
+ var cbc = new _aesJs["default"].ModeOfOperation.cbc(key, iv);
748
+ var encrypted = cbc.encrypt(_aesJs["default"].padding.pkcs7.pad((0, _utils.utf8ToBytes)(value)));
749
+ return (0, _utils.bytesToHex)(iv) + (0, _utils.bytesToHex)(encrypted);
750
+ }
751
+ }, {
752
+ key: "_decrypt",
753
+ value: function _decrypt(value) {
754
+ var key = this._deriveOfflineKey().slice(0, 16);
755
+ var raw = (0, _utils.hexToBytes)(value);
756
+ var cbc = new _aesJs["default"].ModeOfOperation.cbc(key, raw.slice(0, 16));
757
+ var decrypted = _aesJs["default"].padding.pkcs7.strip(cbc.decrypt(raw.slice(16)));
758
+ return _aesJs["default"].utils.utf8.fromBytes(decrypted);
759
+ }
760
+ }, {
761
+ key: "_readOfflineCache",
762
+ value: function _readOfflineCache() {
763
+ var fs = optionalNodeModule('fs');
764
+ var file = this._offlineCacheFile();
765
+ if (!fs || !file) {
766
+ return undefined;
767
+ }
768
+ try {
769
+ if (!fs.existsSync(file)) {
770
+ return undefined;
771
+ }
772
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
773
+ } catch (e) {
774
+ // An unreadable or corrupt cache is a missing cache, never a failed
775
+ // transaction.
776
+ return undefined;
777
+ }
778
+ }
779
+
780
+ // _readFromOfflineCache returns a persisted route. Credentials are decrypted
781
+ // on the way out. When stale is false an expired entry is ignored; when true
782
+ // it is served anyway, which is what keeps a terminal reachable while the
783
+ // gateway is not.
784
+ }, {
785
+ key: "_readFromOfflineCache",
786
+ value: function _readFromOfflineCache(terminalName, stale) {
787
+ var cache = this._readOfflineCache();
788
+ if (!cache || !cache.routes) {
789
+ return undefined;
790
+ }
791
+ var entry = cache.routes[this._routeCacheKey(terminalName)];
792
+ if (!entry) {
793
+ return undefined;
794
+ }
795
+ if (!stale && Date.parse(entry.TTL) <= Date.now()) {
796
+ return undefined;
797
+ }
798
+ try {
799
+ var route = entry.Route;
800
+ route.transientCredentials = {
801
+ apiKey: this._decrypt(route.transientCredentials.apiKey),
802
+ bearerToken: this._decrypt(route.transientCredentials.bearerToken),
803
+ signingKey: this._decrypt(route.transientCredentials.signingKey)
804
+ };
805
+ return route;
806
+ } catch (e) {
807
+ // Written under different credentials, so it cannot be decrypted now.
808
+ return undefined;
809
+ }
810
+ }
811
+ }, {
812
+ key: "_updateOfflineCache",
813
+ value: function _updateOfflineCache(route, ttlMs) {
814
+ var fs = optionalNodeModule('fs');
815
+ var file = this._offlineCacheFile();
816
+ if (!fs || !file) {
817
+ return;
818
+ }
819
+ try {
820
+ var cache = this._readOfflineCache() || {
821
+ routes: {}
822
+ };
823
+ if (!cache.routes) {
824
+ cache.routes = {};
825
+ }
826
+ cache.routes[this._routeCacheKey(route.terminalName)] = {
827
+ TTL: new Date(ttlMs).toISOString(),
828
+ Route: Object.assign({}, route, {
829
+ transientCredentials: {
830
+ apiKey: this._encrypt(route.transientCredentials.apiKey),
831
+ bearerToken: this._encrypt(route.transientCredentials.bearerToken),
832
+ signingKey: this._encrypt(route.transientCredentials.signingKey)
833
+ }
834
+ })
835
+ };
836
+ fs.writeFileSync(file, JSON.stringify(cache), {
837
+ mode: 384
838
+ });
839
+ } catch (e) {
840
+ // Persisting is an optimization; the in-memory cache still stands.
841
+ }
842
+ }
843
+
844
+ // _requestRouteFromGateway resolves a route and rejects anything that is not
845
+ // a usable one, so a failed lookup is never cached or routed on.
846
+ }, {
847
+ key: "_requestRouteFromGateway",
848
+ value: function () {
849
+ var _requestRouteFromGateway2 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee1(terminalName) {
850
+ var routeResponse, route;
851
+ return _regenerator["default"].wrap(function (_context1) {
852
+ while (1) switch (_context1.prev = _context1.next) {
853
+ case 0:
854
+ _context1.next = 1;
855
+ return this._gatewayRequest('get', '/api/terminal-route?terminal=' + encodeURIComponent(terminalName));
856
+ case 1:
857
+ routeResponse = _context1.sent;
858
+ route = routeResponse.data;
859
+ if (!(!route || route.success === false || !route.ipAddress)) {
860
+ _context1.next = 2;
861
+ break;
862
+ }
863
+ throw new Error('unknown terminal: ' + terminalName);
864
+ case 2:
865
+ route.exists = true;
866
+ route.https = true;
867
+ return _context1.abrupt("return", route);
868
+ case 3:
869
+ case "end":
870
+ return _context1.stop();
871
+ }
872
+ }, _callee1, this);
873
+ }));
874
+ function _requestRouteFromGateway(_x23) {
875
+ return _requestRouteFromGateway2.apply(this, arguments);
876
+ }
877
+ return _requestRouteFromGateway;
878
+ }()
879
+ }, {
880
+ key: "_resolveTerminalRoute",
881
+ value: function () {
882
+ var _resolveTerminalRoute2 = (0, _asyncToGenerator2["default"])(/*#__PURE__*/_regenerator["default"].mark(function _callee10(terminalName) {
883
+ var key, cacheEntry, offline, route, stale, ttl, _t2;
884
+ return _regenerator["default"].wrap(function (_context10) {
885
+ while (1) switch (_context10.prev = _context10.next) {
886
+ case 0:
887
+ key = this._routeCacheKey(terminalName);
888
+ cacheEntry = this._routeCache[key];
889
+ if (!(cacheEntry && cacheEntry.ttl >= new Date().getTime())) {
890
+ _context10.next = 1;
891
+ break;
892
+ }
893
+ return _context10.abrupt("return", cacheEntry.route);
894
+ case 1:
895
+ if (!((terminalName.match(/\./g) || []).length === 3)) {
896
+ _context10.next = 2;
897
+ break;
898
+ }
899
+ return _context10.abrupt("return", {
900
+ terminalName: terminalName,
901
+ ipAddress: terminalName,
902
+ cloudRelayEnabled: false,
903
+ exists: true,
904
+ https: false,
905
+ transientCredentials: {
906
+ apiKey: '',
907
+ bearerToken: '',
908
+ signingKey: ''
909
+ }
910
+ });
911
+ case 2:
912
+ offline = this._readFromOfflineCache(terminalName, false);
913
+ if (!offline) {
914
+ _context10.next = 3;
915
+ break;
916
+ }
917
+ return _context10.abrupt("return", offline);
918
+ case 3:
919
+ _context10.prev = 3;
920
+ _context10.next = 4;
921
+ return this._requestRouteFromGateway(terminalName);
922
+ case 4:
923
+ route = _context10.sent;
924
+ _context10.next = 7;
925
+ break;
926
+ case 5:
927
+ _context10.prev = 5;
928
+ _t2 = _context10["catch"](3);
929
+ // The gateway is unreachable or does not know the terminal. A stale
930
+ // persisted route is better than no transaction.
931
+ stale = this._readFromOfflineCache(terminalName, true);
932
+ if (!stale) {
933
+ _context10.next = 6;
934
+ break;
935
+ }
936
+ return _context10.abrupt("return", stale);
937
+ case 6:
938
+ throw _t2;
939
+ case 7:
940
+ ttl = new Date().getTime() + this.routeCacheTTL * 60000;
941
+ this._routeCache[key] = {
942
+ ttl: ttl,
943
+ route: route
944
+ };
945
+ this._updateOfflineCache(route, ttl);
946
+ return _context10.abrupt("return", route);
947
+ case 8:
948
+ case "end":
949
+ return _context10.stop();
950
+ }
951
+ }, _callee10, this, [[3, 5]]);
952
+ }));
953
+ function _resolveTerminalRoute(_x24) {
954
+ return _resolveTerminalRoute2.apply(this, arguments);
955
+ }
956
+ return _resolveTerminalRoute;
957
+ }()
958
+ }]);
959
+ }(); // StaxApiCredentials is the Stax bearer token used to construct a client. It is
960
+ // the only credential an integrator supplies; the BlockChyp gateway credentials
961
+ // are obtained and managed internally by the SDK.
962
+ var StaxApiCredentials = exports.StaxApiCredentials = /*#__PURE__*/(0, _createClass2["default"])(function StaxApiCredentials(bearerToken) {
963
+ (0, _classCallCheck2["default"])(this, StaxApiCredentials);
964
+ this.bearerToken = bearerToken;
965
+ }); // StaxPaymentsCredentials models the BlockChyp gateway credentials the SDK
966
+ // obtains by exchanging the bearer token. It is internal to the SDK.
967
+ var StaxPaymentsCredentials = exports.StaxPaymentsCredentials = /*#__PURE__*/(0, _createClass2["default"])(function StaxPaymentsCredentials(apiKey, bearerToken, signingKey) {
968
+ (0, _classCallCheck2["default"])(this, StaxPaymentsCredentials);
969
+ this.apiKey = apiKey;
970
+ this.bearerToken = bearerToken;
971
+ this.signingKey = signingKey;
972
+ });