@bitpay-labs/bitcore-wallet-service 11.11.3 → 11.11.4
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/package.json +4 -4
- package/src/config.ts +8 -0
- package/src/externalservices/coingecko.ts +6 -1
- package/src/externalservices/transak.ts +394 -70
- package/src/lib/blockchainexplorer.ts +4 -0
- package/src/lib/blockchainexplorers/v8.ts +1 -0
- package/src/lib/blockchainmonitor.ts +1 -0
- package/src/lib/chain/arc/index.ts +95 -0
- package/src/lib/chain/eth/index.ts +41 -23
- package/src/lib/chain/index.ts +2 -0
- package/src/lib/common/constants.ts +12 -1
- package/src/lib/common/defaults.ts +31 -0
- package/src/lib/emailservice.ts +3 -1
- package/src/lib/errors/errordefinitions.ts +4 -0
- package/src/lib/model/preferences.ts +4 -0
- package/src/lib/model/wallet.ts +1 -0
- package/src/lib/pushnotificationsservice.ts +2 -0
- package/src/lib/routes/services.ts +1 -1
- package/src/lib/routes/walletdata.ts +2 -0
- package/src/lib/server.ts +20 -2
- package/test/blockchainexplorer.test.ts +7 -0
- package/test/expressapp.test.ts +31 -0
- package/test/integration/externalservices/coinGecko.test.ts +42 -2
- package/test/integration/externalservices/transak.test.ts +545 -135
- package/test/integration/fiatrateservice.test.ts +13 -0
- package/test/integration/helpers.ts +1 -0
- package/test/integration/server.test.ts +139 -3
- package/ts_build/package.json +1 -1
- package/ts_build/src/config.d.ts.map +1 -1
- package/ts_build/src/config.js +8 -0
- package/ts_build/src/config.js.map +1 -1
- package/ts_build/src/externalservices/coingecko.d.ts.map +1 -1
- package/ts_build/src/externalservices/coingecko.js +6 -1
- package/ts_build/src/externalservices/coingecko.js.map +1 -1
- package/ts_build/src/externalservices/transak.d.ts +28 -2
- package/ts_build/src/externalservices/transak.d.ts.map +1 -1
- package/ts_build/src/externalservices/transak.js +274 -57
- package/ts_build/src/externalservices/transak.js.map +1 -1
- package/ts_build/src/lib/blockchainexplorer.d.ts.map +1 -1
- package/ts_build/src/lib/blockchainexplorer.js +4 -0
- package/ts_build/src/lib/blockchainexplorer.js.map +1 -1
- package/ts_build/src/lib/blockchainexplorers/v8.d.ts.map +1 -1
- package/ts_build/src/lib/blockchainexplorers/v8.js +1 -0
- package/ts_build/src/lib/blockchainexplorers/v8.js.map +1 -1
- package/ts_build/src/lib/blockchainmonitor.d.ts.map +1 -1
- package/ts_build/src/lib/blockchainmonitor.js +1 -0
- package/ts_build/src/lib/blockchainmonitor.js.map +1 -1
- package/ts_build/src/lib/chain/arc/index.d.ts +20 -0
- package/ts_build/src/lib/chain/arc/index.d.ts.map +1 -0
- package/ts_build/src/lib/chain/arc/index.js +95 -0
- package/ts_build/src/lib/chain/arc/index.js.map +1 -0
- package/ts_build/src/lib/chain/eth/index.d.ts +1 -0
- package/ts_build/src/lib/chain/eth/index.d.ts.map +1 -1
- package/ts_build/src/lib/chain/eth/index.js +38 -19
- package/ts_build/src/lib/chain/eth/index.js.map +1 -1
- package/ts_build/src/lib/chain/index.d.ts.map +1 -1
- package/ts_build/src/lib/chain/index.js +2 -0
- package/ts_build/src/lib/chain/index.js.map +1 -1
- package/ts_build/src/lib/common/constants.d.ts +12 -0
- package/ts_build/src/lib/common/constants.d.ts.map +1 -1
- package/ts_build/src/lib/common/constants.js +12 -1
- package/ts_build/src/lib/common/constants.js.map +1 -1
- package/ts_build/src/lib/common/defaults.d.ts +24 -0
- package/ts_build/src/lib/common/defaults.d.ts.map +1 -1
- package/ts_build/src/lib/common/defaults.js +30 -0
- package/ts_build/src/lib/common/defaults.js.map +1 -1
- package/ts_build/src/lib/common/index.d.ts +36 -0
- package/ts_build/src/lib/common/index.d.ts.map +1 -1
- package/ts_build/src/lib/emailservice.d.ts.map +1 -1
- package/ts_build/src/lib/emailservice.js +2 -0
- package/ts_build/src/lib/emailservice.js.map +1 -1
- package/ts_build/src/lib/errors/errordefinitions.d.ts +2 -0
- package/ts_build/src/lib/errors/errordefinitions.d.ts.map +1 -1
- package/ts_build/src/lib/errors/errordefinitions.js +2 -0
- package/ts_build/src/lib/errors/errordefinitions.js.map +1 -1
- package/ts_build/src/lib/model/preferences.d.ts +2 -0
- package/ts_build/src/lib/model/preferences.d.ts.map +1 -1
- package/ts_build/src/lib/model/preferences.js +3 -0
- package/ts_build/src/lib/model/preferences.js.map +1 -1
- package/ts_build/src/lib/model/wallet.d.ts.map +1 -1
- package/ts_build/src/lib/model/wallet.js +1 -0
- package/ts_build/src/lib/model/wallet.js.map +1 -1
- package/ts_build/src/lib/pushnotificationsservice.d.ts.map +1 -1
- package/ts_build/src/lib/pushnotificationsservice.js +2 -0
- package/ts_build/src/lib/pushnotificationsservice.js.map +1 -1
- package/ts_build/src/lib/routes/services.js +1 -1
- package/ts_build/src/lib/routes/services.js.map +1 -1
- package/ts_build/src/lib/routes/walletdata.d.ts.map +1 -1
- package/ts_build/src/lib/routes/walletdata.js +2 -0
- package/ts_build/src/lib/routes/walletdata.js.map +1 -1
- package/ts_build/src/lib/server.d.ts.map +1 -1
- package/ts_build/src/lib/server.js +17 -2
- package/ts_build/src/lib/server.js.map +1 -1
- package/ts_build/test/blockchainexplorer.test.js +6 -0
- package/ts_build/test/blockchainexplorer.test.js.map +1 -1
- package/ts_build/test/expressapp.test.js +30 -0
- package/ts_build/test/expressapp.test.js.map +1 -1
- package/ts_build/test/integration/externalservices/coinGecko.test.js +38 -2
- package/ts_build/test/integration/externalservices/coinGecko.test.js.map +1 -1
- package/ts_build/test/integration/externalservices/transak.test.d.ts.map +1 -1
- package/ts_build/test/integration/externalservices/transak.test.js +498 -128
- package/ts_build/test/integration/externalservices/transak.test.js.map +1 -1
- package/ts_build/test/integration/fiatrateservice.test.js +11 -0
- package/ts_build/test/integration/fiatrateservice.test.js.map +1 -1
- package/ts_build/test/integration/helpers.d.ts.map +1 -1
- package/ts_build/test/integration/helpers.js +1 -0
- package/ts_build/test/integration/helpers.js.map +1 -1
- package/ts_build/test/integration/server.test.js +135 -3
- package/ts_build/test/integration/server.test.js.map +1 -1
|
@@ -37,15 +37,35 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
const chai = __importStar(require("chai"));
|
|
40
|
+
const crypto = __importStar(require("crypto"));
|
|
40
41
|
const sinon = __importStar(require("sinon"));
|
|
41
42
|
require("chai/register-should");
|
|
42
43
|
const util_1 = __importDefault(require("util"));
|
|
43
44
|
const server_1 = require("../../../src/lib/server");
|
|
45
|
+
const transak_1 = require("../../../src/externalservices/transak");
|
|
44
46
|
const TestData = __importStar(require("../../testdata"));
|
|
45
47
|
const helpers_1 = __importDefault(require("../helpers"));
|
|
46
48
|
const config_1 = __importDefault(require("../../../src/config"));
|
|
47
49
|
const logger_1 = __importDefault(require("../../../src/lib/logger"));
|
|
48
50
|
const should = chai.should();
|
|
51
|
+
const PARTNER_TOKEN = 'partnerToken1';
|
|
52
|
+
const appWidgetBody = () => ({
|
|
53
|
+
walletAddress: 'walletAddress1',
|
|
54
|
+
disableWalletAddressForm: true,
|
|
55
|
+
redirectURL: 'bitpay://transak',
|
|
56
|
+
exchangeScreenTitle: 'Bitpay - Buy crypto',
|
|
57
|
+
fiatAmount: '500',
|
|
58
|
+
fiatCurrency: 'USD',
|
|
59
|
+
network: 'mainnet',
|
|
60
|
+
cryptoCurrencyCode: 'BTC',
|
|
61
|
+
cryptoCurrencyList: 'BTC',
|
|
62
|
+
hideExchangeScreen: true,
|
|
63
|
+
themeColor: '2240C4',
|
|
64
|
+
hideMenu: false,
|
|
65
|
+
partnerOrderId: 'partnerOrderId1',
|
|
66
|
+
partnerCustomerId: 'partnerCustomerId1',
|
|
67
|
+
paymentMethod: 'credit_debit_card',
|
|
68
|
+
});
|
|
49
69
|
describe('Transak integration', () => {
|
|
50
70
|
const sandbox = sinon.createSandbox();
|
|
51
71
|
let server;
|
|
@@ -53,6 +73,22 @@ describe('Transak integration', () => {
|
|
|
53
73
|
let fakeRequest;
|
|
54
74
|
let req;
|
|
55
75
|
let loggerError;
|
|
76
|
+
let refreshCalls;
|
|
77
|
+
let sessionCalls;
|
|
78
|
+
let orderCalls;
|
|
79
|
+
let tokens;
|
|
80
|
+
let tokenExpiresAt;
|
|
81
|
+
let refreshStatus;
|
|
82
|
+
let refreshDelay;
|
|
83
|
+
let sessionStatuses;
|
|
84
|
+
let sessionError;
|
|
85
|
+
let sessionErrorBody;
|
|
86
|
+
let orderStatuses;
|
|
87
|
+
let orderError;
|
|
88
|
+
let orderBody;
|
|
89
|
+
const newReq = (env, extra = {}) => ({ headers: {}, body: { env, ...appWidgetBody(), ...extra } });
|
|
90
|
+
const partnerTokenId = (env, apiKey) => `${env}:${crypto.createHash('sha256').update(apiKey).digest('hex').slice(0, 12)}`;
|
|
91
|
+
const updatePartnerTokenCache = (update) => server.storage.db.collection('cache').updateMany({ key: { $regex: '^transakPartnerToken:' } }, update);
|
|
56
92
|
before(async () => {
|
|
57
93
|
await helpers_1.default.before();
|
|
58
94
|
});
|
|
@@ -83,9 +119,51 @@ describe('Transak integration', () => {
|
|
|
83
119
|
widgetApi: 'widgetApi4',
|
|
84
120
|
}
|
|
85
121
|
};
|
|
122
|
+
refreshCalls = [];
|
|
123
|
+
sessionCalls = [];
|
|
124
|
+
orderCalls = [];
|
|
125
|
+
tokens = [PARTNER_TOKEN];
|
|
126
|
+
tokenExpiresAt = Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60;
|
|
127
|
+
refreshStatus = 200;
|
|
128
|
+
refreshDelay = 0;
|
|
129
|
+
sessionStatuses = [];
|
|
130
|
+
sessionError = null;
|
|
131
|
+
sessionErrorBody = { error: { message: 'unauthorized' } };
|
|
132
|
+
orderStatuses = [];
|
|
133
|
+
orderError = null;
|
|
134
|
+
orderBody = 'data';
|
|
86
135
|
fakeRequest = {
|
|
87
|
-
get: (
|
|
88
|
-
|
|
136
|
+
get: (url, opts, cb) => {
|
|
137
|
+
if (url.includes('/partners/api/v2/order/')) {
|
|
138
|
+
orderCalls.push({ url, opts });
|
|
139
|
+
if (orderError)
|
|
140
|
+
return cb(orderError, null);
|
|
141
|
+
return cb(null, { statusCode: orderStatuses.shift() ?? 200, body: orderBody });
|
|
142
|
+
}
|
|
143
|
+
return cb(null, { body: 'data' });
|
|
144
|
+
},
|
|
145
|
+
post: (url, opts, cb) => {
|
|
146
|
+
if (url.endsWith('/partners/api/v2/refresh-token')) {
|
|
147
|
+
refreshCalls.push({ url, opts });
|
|
148
|
+
const accessToken = tokens.length > 1 ? tokens.shift() : tokens[0];
|
|
149
|
+
const respond = () => cb(null, {
|
|
150
|
+
statusCode: refreshStatus,
|
|
151
|
+
body: refreshStatus === 200 ? { data: { accessToken, expiresAt: tokenExpiresAt } } : { error: 'unauthorized' }
|
|
152
|
+
});
|
|
153
|
+
return refreshDelay ? setTimeout(respond, refreshDelay) : respond();
|
|
154
|
+
}
|
|
155
|
+
if (url.endsWith('/api/v2/auth/session')) {
|
|
156
|
+
sessionCalls.push({ url, opts });
|
|
157
|
+
if (sessionError)
|
|
158
|
+
return cb(sessionError, null);
|
|
159
|
+
const statusCode = sessionStatuses.shift() ?? 200;
|
|
160
|
+
return cb(null, {
|
|
161
|
+
statusCode,
|
|
162
|
+
body: statusCode === 200 ? { data: { widgetUrl: 'https://global.transak.com/?session=abc' } } : sessionErrorBody
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return cb(null, { body: 'data' });
|
|
166
|
+
},
|
|
89
167
|
};
|
|
90
168
|
await helpers_1.default.beforeEach();
|
|
91
169
|
({ wallet } = await helpers_1.default.createAndJoinWallet(1, 1));
|
|
@@ -115,57 +193,26 @@ describe('Transak integration', () => {
|
|
|
115
193
|
env: 'sandbox',
|
|
116
194
|
}
|
|
117
195
|
};
|
|
118
|
-
server.externalServices.transak.request =
|
|
196
|
+
server.externalServices.transak.request = {
|
|
197
|
+
get: () => { throw new Error('should not call Transak'); },
|
|
198
|
+
post: () => { throw new Error('should not call Transak'); },
|
|
199
|
+
};
|
|
119
200
|
});
|
|
120
|
-
it('should
|
|
201
|
+
it('should return a placeholder without calling Transak', async () => {
|
|
121
202
|
const data = await server.externalServices.transak.transakGetAccessToken(req);
|
|
122
|
-
should.
|
|
203
|
+
data.data.accessToken.should.equal('server-managed');
|
|
123
204
|
});
|
|
124
|
-
it('should
|
|
205
|
+
it('should return a placeholder for web without calling Transak', async () => {
|
|
125
206
|
req.body.context = 'web';
|
|
126
207
|
const data = await server.externalServices.transak.transakGetAccessToken(req);
|
|
127
|
-
should.
|
|
128
|
-
});
|
|
129
|
-
it('should forward the deviceIp as x-user-ip for web context', async () => {
|
|
130
|
-
let capturedHeaders;
|
|
131
|
-
server.externalServices.transak.request = {
|
|
132
|
-
post: (_url, opts, cb) => {
|
|
133
|
-
capturedHeaders = opts.headers;
|
|
134
|
-
return cb(null, { body: 'data' });
|
|
135
|
-
},
|
|
136
|
-
};
|
|
137
|
-
req.body.context = 'web';
|
|
138
|
-
req.body.deviceIp = '203.0.113.42';
|
|
139
|
-
await server.externalServices.transak.transakGetAccessToken(req);
|
|
140
|
-
capturedHeaders['x-user-ip'].should.equal('203.0.113.42');
|
|
141
|
-
});
|
|
142
|
-
it('should return error if post returns error', async () => {
|
|
143
|
-
const fakeRequest2 = {
|
|
144
|
-
post: (_url, _opts, _cb) => { return _cb(new Error('Error'), null); },
|
|
145
|
-
};
|
|
146
|
-
server.externalServices.transak.request = fakeRequest2;
|
|
147
|
-
try {
|
|
148
|
-
await server.externalServices.transak.transakGetAccessToken(req);
|
|
149
|
-
should.fail('should have thrown');
|
|
150
|
-
}
|
|
151
|
-
catch (err) {
|
|
152
|
-
loggerError.callCount.should.equal(1);
|
|
153
|
-
loggerError.args[0][0].should.contain('Transak request failed with error (');
|
|
154
|
-
err.message.should.equal('Failed to get Transak access token');
|
|
155
|
-
}
|
|
208
|
+
data.data.accessToken.should.equal('server-managed');
|
|
156
209
|
});
|
|
157
|
-
it('should return
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
await server.externalServices.transak.transakGetAccessToken(req);
|
|
164
|
-
should.fail('should have thrown');
|
|
165
|
-
}
|
|
166
|
-
catch (err) {
|
|
167
|
-
err.message.should.equal('Transak did not return an access token');
|
|
168
|
-
}
|
|
210
|
+
it('should return an integer expiresAt in seconds in the future', async () => {
|
|
211
|
+
const now = Math.floor(Date.now() / 1000);
|
|
212
|
+
const { data } = await server.externalServices.transak.transakGetAccessToken(req);
|
|
213
|
+
Number.isInteger(data.expiresAt).should.equal(true);
|
|
214
|
+
data.expiresAt.should.be.above(now);
|
|
215
|
+
data.expiresAt.should.be.at.most(now + 2 * 60 + 1);
|
|
169
216
|
});
|
|
170
217
|
it('should return error if transak is commented in config', async () => {
|
|
171
218
|
config_1.default.transak = undefined;
|
|
@@ -372,39 +419,307 @@ describe('Transak integration', () => {
|
|
|
372
419
|
headers: {},
|
|
373
420
|
body: {
|
|
374
421
|
env: 'production',
|
|
375
|
-
accessToken: '
|
|
376
|
-
|
|
377
|
-
redirectURL: 'bitpay://transak',
|
|
378
|
-
fiatAmount: '500',
|
|
379
|
-
fiatCurrency: 'USD',
|
|
380
|
-
network: 'mainnet',
|
|
381
|
-
cryptoCurrencyCode: 'BTC',
|
|
382
|
-
partnerOrderId: 'partnerOrderId1',
|
|
383
|
-
partnerCustomerId: 'partnerCustomerId1',
|
|
422
|
+
accessToken: 'clientSuppliedToken',
|
|
423
|
+
...appWidgetBody(),
|
|
384
424
|
}
|
|
385
425
|
};
|
|
386
426
|
server.externalServices.transak.request = fakeRequest;
|
|
387
427
|
});
|
|
388
428
|
it('should get the paymentUrl properly if req is OK', async () => {
|
|
389
429
|
const data = await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
390
|
-
should.
|
|
430
|
+
data.urlWithSignature.should.equal('https://global.transak.com/?session=abc');
|
|
391
431
|
});
|
|
392
432
|
it('should get the paymentUrl properly if req is OK for web', async () => {
|
|
433
|
+
req.body.context = 'web';
|
|
434
|
+
const data = await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
435
|
+
data.urlWithSignature.should.equal('https://global.transak.com/?session=abc');
|
|
436
|
+
});
|
|
437
|
+
it('should work for web with only the fields bitpay.com sends and no accessToken', async () => {
|
|
393
438
|
req.body = {
|
|
394
439
|
env: 'production',
|
|
395
|
-
accessToken: 'accessToken1',
|
|
396
440
|
context: 'web',
|
|
397
441
|
walletAddress: 'walletAddress1',
|
|
398
|
-
redirectURL: 'bitpay
|
|
442
|
+
redirectURL: 'https://bitpay.com/buy-crypto',
|
|
399
443
|
fiatAmount: '500',
|
|
400
444
|
fiatCurrency: 'USD',
|
|
401
|
-
|
|
445
|
+
defaultNetwork: 'bitcoin',
|
|
402
446
|
cryptoCurrencyCode: 'BTC',
|
|
403
|
-
|
|
404
|
-
partnerCustomerId: 'partnerCustomerId1',
|
|
447
|
+
deviceIp: '203.0.113.42',
|
|
405
448
|
};
|
|
406
449
|
const data = await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
407
|
-
should.exist(data);
|
|
450
|
+
should.exist(data.urlWithSignature);
|
|
451
|
+
});
|
|
452
|
+
it('should work for the app without an accessToken', async () => {
|
|
453
|
+
delete req.body.accessToken;
|
|
454
|
+
const data = await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
455
|
+
should.exist(data.urlWithSignature);
|
|
456
|
+
});
|
|
457
|
+
it('should use the server-side partner token and ignore any client accessToken', async () => {
|
|
458
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
459
|
+
sessionCalls.length.should.equal(1);
|
|
460
|
+
sessionCalls[0].opts.headers['access-token'].should.equal(PARTNER_TOKEN);
|
|
461
|
+
refreshCalls[0].opts.headers['api-secret'].should.equal('secretKey2');
|
|
462
|
+
});
|
|
463
|
+
it('should only send allowlisted fields in widgetParams', async () => {
|
|
464
|
+
req.body.context = 'web';
|
|
465
|
+
req.body.deviceIp = '203.0.113.42';
|
|
466
|
+
req.body.somethingElse = 'nope';
|
|
467
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
468
|
+
const widgetParams = sessionCalls[0].opts.body.widgetParams;
|
|
469
|
+
should.not.exist(widgetParams.accessToken);
|
|
470
|
+
should.not.exist(widgetParams.env);
|
|
471
|
+
should.not.exist(widgetParams.context);
|
|
472
|
+
should.not.exist(widgetParams.deviceIp);
|
|
473
|
+
should.not.exist(widgetParams.somethingElse);
|
|
474
|
+
JSON.stringify(sessionCalls[0].opts.body).should.not.contain(PARTNER_TOKEN);
|
|
475
|
+
JSON.stringify(sessionCalls[0].opts.body).should.not.contain('clientSuppliedToken');
|
|
476
|
+
widgetParams.should.deep.equal({
|
|
477
|
+
...appWidgetBody(),
|
|
478
|
+
apiKey: 'apiKey4',
|
|
479
|
+
referrerDomain: 'bitpay.com',
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
it('should refresh the partner token once and reuse it across requests and instances', async () => {
|
|
483
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
484
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl({ headers: {}, body: { env: 'production', ...appWidgetBody() } });
|
|
485
|
+
const otherInstance = new transak_1.TransakService(server.storage, server.lock);
|
|
486
|
+
otherInstance.request = fakeRequest;
|
|
487
|
+
await otherInstance.transakGetSignedPaymentUrl({ headers: {}, body: { env: 'production', ...appWidgetBody() } });
|
|
488
|
+
refreshCalls.length.should.equal(1);
|
|
489
|
+
sessionCalls.length.should.equal(3);
|
|
490
|
+
});
|
|
491
|
+
it('should refresh only once for concurrent requests', async () => {
|
|
492
|
+
refreshDelay = 100;
|
|
493
|
+
await Promise.all([1, 2, 3, 4, 5].map(() => {
|
|
494
|
+
const instance = new transak_1.TransakService(server.storage, server.lock);
|
|
495
|
+
instance.request = fakeRequest;
|
|
496
|
+
return instance.transakGetSignedPaymentUrl({ headers: {}, body: { env: 'production', ...appWidgetBody() } });
|
|
497
|
+
}));
|
|
498
|
+
refreshCalls.length.should.equal(1);
|
|
499
|
+
sessionCalls.length.should.equal(5);
|
|
500
|
+
});
|
|
501
|
+
it('should keep a separate partner token per environment', async () => {
|
|
502
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
503
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl({ headers: {}, body: { env: 'production', context: 'web', ...appWidgetBody() } });
|
|
504
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl({ headers: {}, body: { env: 'sandbox', ...appWidgetBody() } });
|
|
505
|
+
refreshCalls.length.should.equal(3);
|
|
506
|
+
refreshCalls.map(c => c.opts.headers['x-api-key']).should.deep.equal(['apiKey2', 'apiKey4', 'apiKey1']);
|
|
507
|
+
});
|
|
508
|
+
it('should refresh the partner token when it is close to expiring', async () => {
|
|
509
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
510
|
+
await updatePartnerTokenCache({ $set: { 'result.expiresAt': Date.now() + 5 * 60 * 1000 } });
|
|
511
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
512
|
+
refreshCalls.length.should.equal(2);
|
|
513
|
+
});
|
|
514
|
+
it('should use a fallback TTL when Transak returns expiresAt as a duration', async () => {
|
|
515
|
+
tokenExpiresAt = 7 * 24 * 60 * 60;
|
|
516
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
517
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
518
|
+
refreshCalls.length.should.equal(1);
|
|
519
|
+
});
|
|
520
|
+
it('should use a fallback TTL when Transak omits expiresAt', async () => {
|
|
521
|
+
tokenExpiresAt = undefined;
|
|
522
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
523
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
524
|
+
refreshCalls.length.should.equal(1);
|
|
525
|
+
});
|
|
526
|
+
it('should accept expiresAt in milliseconds', async () => {
|
|
527
|
+
tokenExpiresAt = Date.now() + 7 * 24 * 60 * 60 * 1000;
|
|
528
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
529
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
530
|
+
refreshCalls.length.should.equal(1);
|
|
531
|
+
});
|
|
532
|
+
it('should not reuse a token cached for a rotated API key', async () => {
|
|
533
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
534
|
+
config_1.default.transak.production.apiKey = 'apiKey2-rotated';
|
|
535
|
+
tokens = ['rotatedToken'];
|
|
536
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
537
|
+
refreshCalls.length.should.equal(2);
|
|
538
|
+
refreshCalls[1].opts.headers['x-api-key'].should.equal('apiKey2-rotated');
|
|
539
|
+
sessionCalls[1].opts.headers['access-token'].should.equal('rotatedToken');
|
|
540
|
+
});
|
|
541
|
+
it('should refresh and retry once when Transak rejects an older partner token', async () => {
|
|
542
|
+
tokens = ['staleToken', 'freshToken'];
|
|
543
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
544
|
+
await updatePartnerTokenCache({ $set: { 'result.fetchedAt': Date.now() - 60 * 60 * 1000 } });
|
|
545
|
+
sessionStatuses = [401, 200];
|
|
546
|
+
const data = await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
547
|
+
should.exist(data.urlWithSignature);
|
|
548
|
+
refreshCalls.length.should.equal(2);
|
|
549
|
+
sessionCalls.map(c => c.opts.headers['access-token']).should.deep.equal(['staleToken', 'staleToken', 'freshToken']);
|
|
550
|
+
});
|
|
551
|
+
it('should fail rather than return an empty URL on repeated 401s', async () => {
|
|
552
|
+
tokens = ['token1', 'token2', 'token3'];
|
|
553
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
554
|
+
await updatePartnerTokenCache({ $set: { 'result.fetchedAt': Date.now() - 60 * 60 * 1000 } });
|
|
555
|
+
sessionStatuses = [401, 401, 401];
|
|
556
|
+
try {
|
|
557
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
558
|
+
should.fail('should have thrown');
|
|
559
|
+
}
|
|
560
|
+
catch (err) {
|
|
561
|
+
err.message.should.equal('Failed to get Transak signed payment URL');
|
|
562
|
+
}
|
|
563
|
+
sessionCalls.length.should.equal(3);
|
|
564
|
+
refreshCalls.length.should.equal(2);
|
|
565
|
+
});
|
|
566
|
+
it('should not refresh when Transak rejects a token it just issued', async () => {
|
|
567
|
+
sessionStatuses = [401, 401, 401, 401, 401];
|
|
568
|
+
for (let i = 0; i < 5; i++) {
|
|
569
|
+
try {
|
|
570
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
571
|
+
should.fail('should have thrown');
|
|
572
|
+
}
|
|
573
|
+
catch (err) {
|
|
574
|
+
err.message.should.equal('Transak rejected the access token');
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
refreshCalls.length.should.equal(1);
|
|
578
|
+
sessionCalls.length.should.equal(5);
|
|
579
|
+
});
|
|
580
|
+
it('should fail if Transak does not return a widget URL', async () => {
|
|
581
|
+
sessionStatuses = [500];
|
|
582
|
+
try {
|
|
583
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
584
|
+
should.fail('should have thrown');
|
|
585
|
+
}
|
|
586
|
+
catch (err) {
|
|
587
|
+
err.message.should.equal('Failed to get Transak signed payment URL');
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
it('should fail without refreshing when another request holds the refresh lock too long', async () => {
|
|
591
|
+
const release = await util_1.default.promisify((cb) => server.lock.acquire(`transakPartnerToken:${partnerTokenId('production', 'apiKey2')}`, { lockTime: 60000 }, cb))();
|
|
592
|
+
const transak = server.externalServices.transak;
|
|
593
|
+
transak.partnerTokenLockWait = 200;
|
|
594
|
+
try {
|
|
595
|
+
await transak.transakGetSignedPaymentUrl(req);
|
|
596
|
+
should.fail('should have thrown');
|
|
597
|
+
}
|
|
598
|
+
catch (err) {
|
|
599
|
+
err.message.should.equal('Failed to get Transak access token');
|
|
600
|
+
}
|
|
601
|
+
finally {
|
|
602
|
+
release();
|
|
603
|
+
}
|
|
604
|
+
refreshCalls.length.should.equal(0);
|
|
605
|
+
});
|
|
606
|
+
it('should check the failure backoff before waiting on the refresh lock', async () => {
|
|
607
|
+
refreshStatus = 401;
|
|
608
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req).catch(() => { });
|
|
609
|
+
const release = await util_1.default.promisify((cb) => server.lock.acquire(`transakPartnerToken:${partnerTokenId('production', 'apiKey2')}`, { lockTime: 60000 }, cb))();
|
|
610
|
+
const started = Date.now();
|
|
611
|
+
try {
|
|
612
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
613
|
+
should.fail('should have thrown');
|
|
614
|
+
}
|
|
615
|
+
catch (err) {
|
|
616
|
+
err.message.should.equal('Failed to get Transak access token');
|
|
617
|
+
}
|
|
618
|
+
finally {
|
|
619
|
+
release();
|
|
620
|
+
}
|
|
621
|
+
(Date.now() - started).should.be.below(1000);
|
|
622
|
+
refreshCalls.length.should.equal(1);
|
|
623
|
+
});
|
|
624
|
+
it('should retry the refresh once the failure backoff has passed', async () => {
|
|
625
|
+
refreshStatus = 401;
|
|
626
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req).catch(() => { });
|
|
627
|
+
await server.storage.db.collection('cache').updateMany({ key: { $regex: '^transakPartnerTokenFailure:' } }, { $set: { ts: Date.now() - 31 * 1000 } });
|
|
628
|
+
refreshStatus = 200;
|
|
629
|
+
const data = await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
630
|
+
should.exist(data.urlWithSignature);
|
|
631
|
+
refreshCalls.length.should.equal(2);
|
|
632
|
+
});
|
|
633
|
+
it('should keep serving a still-valid token when the proactive refresh fails', async () => {
|
|
634
|
+
tokens = ['currentToken'];
|
|
635
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
636
|
+
await updatePartnerTokenCache({ $set: { 'result.expiresAt': Date.now() + 5 * 60 * 1000 } });
|
|
637
|
+
refreshStatus = 500;
|
|
638
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
639
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
640
|
+
refreshCalls.length.should.equal(2);
|
|
641
|
+
sessionCalls.map(c => c.opts.headers['access-token']).should.deep.equal(['currentToken', 'currentToken', 'currentToken']);
|
|
642
|
+
});
|
|
643
|
+
it('should keep serving a still-valid token when the refresh lock is held too long', async () => {
|
|
644
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
645
|
+
await updatePartnerTokenCache({ $set: { 'result.expiresAt': Date.now() + 5 * 60 * 1000 } });
|
|
646
|
+
const release = await util_1.default.promisify((cb) => server.lock.acquire(`transakPartnerToken:${partnerTokenId('production', 'apiKey2')}`, { lockTime: 60000 }, cb))();
|
|
647
|
+
const transak = server.externalServices.transak;
|
|
648
|
+
transak.partnerTokenLockWait = 200;
|
|
649
|
+
try {
|
|
650
|
+
await transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
651
|
+
}
|
|
652
|
+
finally {
|
|
653
|
+
release();
|
|
654
|
+
}
|
|
655
|
+
refreshCalls.length.should.equal(1);
|
|
656
|
+
sessionCalls[1].opts.headers['access-token'].should.equal(PARTNER_TOKEN);
|
|
657
|
+
});
|
|
658
|
+
it('should not serve a token past its real expiry when the refresh fails', async () => {
|
|
659
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
660
|
+
await updatePartnerTokenCache({ $set: { 'result.expiresAt': Date.now() + 10 * 1000 } });
|
|
661
|
+
refreshStatus = 500;
|
|
662
|
+
try {
|
|
663
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
664
|
+
should.fail('should have thrown');
|
|
665
|
+
}
|
|
666
|
+
catch (err) {
|
|
667
|
+
err.message.should.equal('Transak did not return an access token');
|
|
668
|
+
}
|
|
669
|
+
sessionCalls.length.should.equal(1);
|
|
670
|
+
});
|
|
671
|
+
it('should refresh and retry once when Transak answers 403 for an older token', async () => {
|
|
672
|
+
tokens = ['staleToken', 'freshToken'];
|
|
673
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
674
|
+
await updatePartnerTokenCache({ $set: { 'result.fetchedAt': Date.now() - 60 * 60 * 1000 } });
|
|
675
|
+
sessionStatuses = [403, 200];
|
|
676
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
677
|
+
sessionCalls.map(c => c.opts.headers['access-token']).should.deep.equal(['staleToken', 'staleToken', 'freshToken']);
|
|
678
|
+
});
|
|
679
|
+
it('should log Transak\'s error message but not the token', async () => {
|
|
680
|
+
const loggerWarn = logger_1.default.warn;
|
|
681
|
+
sessionStatuses = [500];
|
|
682
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req).catch(() => null);
|
|
683
|
+
const logged = loggerWarn.args.map(a => String(a[0])).join('\n');
|
|
684
|
+
logged.should.contain('(unauthorized)');
|
|
685
|
+
logged.should.not.contain(PARTNER_TOKEN);
|
|
686
|
+
});
|
|
687
|
+
it('should log a non-string Transak error message as JSON', async () => {
|
|
688
|
+
const loggerWarn = logger_1.default.warn;
|
|
689
|
+
sessionStatuses = [400];
|
|
690
|
+
sessionErrorBody = { error: { message: { code: 'INVALID_PARAMS', fields: ['fiatAmount'] } } };
|
|
691
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req).catch(() => null);
|
|
692
|
+
const logged = loggerWarn.args.map(a => String(a[0])).join('\n');
|
|
693
|
+
logged.should.contain('({"code":"INVALID_PARAMS","fields":["fiatAmount"]})');
|
|
694
|
+
});
|
|
695
|
+
it('should log no detail when Transak sends no error message', async () => {
|
|
696
|
+
const loggerWarn = logger_1.default.warn;
|
|
697
|
+
sessionStatuses = [502];
|
|
698
|
+
sessionErrorBody = 'Bad Gateway';
|
|
699
|
+
const err = await server.externalServices.transak.transakGetSignedPaymentUrl(req).catch(e => e);
|
|
700
|
+
err.message.should.equal('Failed to get Transak signed payment URL');
|
|
701
|
+
loggerWarn.args.map(a => String(a[0])).join('\n').should.match(/status code 502: \S+$/m);
|
|
702
|
+
});
|
|
703
|
+
it('should forward defaultPaymentMethod when the app sends it instead of paymentMethod', async () => {
|
|
704
|
+
delete req.body.paymentMethod;
|
|
705
|
+
req.body.defaultPaymentMethod = 'credit_debit_card';
|
|
706
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
707
|
+
sessionCalls[0].opts.body.widgetParams.defaultPaymentMethod.should.equal('credit_debit_card');
|
|
708
|
+
should.not.exist(sessionCalls[0].opts.body.widgetParams.paymentMethod);
|
|
709
|
+
});
|
|
710
|
+
it('should fail and back off when the partner token refresh fails', async () => {
|
|
711
|
+
refreshStatus = 401;
|
|
712
|
+
for (let i = 0; i < 3; i++) {
|
|
713
|
+
try {
|
|
714
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl({ headers: {}, body: { env: 'production', ...appWidgetBody() } });
|
|
715
|
+
should.fail('should have thrown');
|
|
716
|
+
}
|
|
717
|
+
catch (err) {
|
|
718
|
+
err.message.should.match(/Transak did not return an access token|Failed to get Transak access token/);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
refreshCalls.length.should.equal(1);
|
|
722
|
+
sessionCalls.length.should.equal(0);
|
|
408
723
|
});
|
|
409
724
|
it('should return error if there is some missing arguments', async () => {
|
|
410
725
|
delete req.body.context;
|
|
@@ -418,75 +733,32 @@ describe('Transak integration', () => {
|
|
|
418
733
|
}
|
|
419
734
|
});
|
|
420
735
|
it('should forward the deviceIp as x-user-ip for web context', async () => {
|
|
421
|
-
let capturedHeaders;
|
|
422
|
-
server.externalServices.transak.request = {
|
|
423
|
-
post: (_url, opts, cb) => {
|
|
424
|
-
capturedHeaders = opts.headers;
|
|
425
|
-
return cb(null, { body: 'data' });
|
|
426
|
-
},
|
|
427
|
-
};
|
|
428
736
|
req.body.context = 'web';
|
|
429
737
|
req.body.deviceIp = '203.0.113.42';
|
|
430
738
|
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
431
|
-
|
|
739
|
+
sessionCalls[0].opts.headers['x-user-ip'].should.equal('203.0.113.42');
|
|
740
|
+
refreshCalls[0].opts.headers['x-user-ip'].should.equal('203.0.113.42');
|
|
432
741
|
});
|
|
433
742
|
it('should canonicalize an IPv4-mapped IPv6 deviceIp for web context', async () => {
|
|
434
|
-
let capturedHeaders;
|
|
435
|
-
server.externalServices.transak.request = {
|
|
436
|
-
post: (_url, opts, cb) => {
|
|
437
|
-
capturedHeaders = opts.headers;
|
|
438
|
-
return cb(null, { body: 'data' });
|
|
439
|
-
},
|
|
440
|
-
};
|
|
441
743
|
req.body.context = 'web';
|
|
442
744
|
req.body.deviceIp = '::ffff:203.0.113.42';
|
|
443
745
|
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
444
|
-
|
|
445
|
-
});
|
|
446
|
-
it('should not leak deviceIp into the widgetParams sent to Transak', async () => {
|
|
447
|
-
let capturedBody;
|
|
448
|
-
server.externalServices.transak.request = {
|
|
449
|
-
post: (_url, opts, cb) => {
|
|
450
|
-
capturedBody = opts.body;
|
|
451
|
-
return cb(null, { body: 'data' });
|
|
452
|
-
},
|
|
453
|
-
};
|
|
454
|
-
req.body.context = 'web';
|
|
455
|
-
req.body.deviceIp = '203.0.113.42';
|
|
456
|
-
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
457
|
-
should.not.exist(capturedBody.widgetParams.deviceIp);
|
|
746
|
+
sessionCalls[0].opts.headers['x-user-ip'].should.equal('203.0.113.42');
|
|
458
747
|
});
|
|
459
748
|
it('should ignore a body deviceIp for non-web context and use the request IP', async () => {
|
|
460
|
-
let capturedHeaders;
|
|
461
|
-
server.externalServices.transak.request = {
|
|
462
|
-
post: (_url, opts, cb) => {
|
|
463
|
-
capturedHeaders = opts.headers;
|
|
464
|
-
return cb(null, { body: 'data' });
|
|
465
|
-
},
|
|
466
|
-
};
|
|
467
749
|
req.headers['x-forwarded-for'] = '198.51.100.7';
|
|
468
750
|
req.body.deviceIp = '203.0.113.42';
|
|
469
751
|
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
470
|
-
|
|
752
|
+
sessionCalls[0].opts.headers['x-user-ip'].should.equal('198.51.100.7');
|
|
471
753
|
});
|
|
472
754
|
it('should fall back to the request IP for web context when deviceIp is not forwarded', async () => {
|
|
473
|
-
let capturedHeaders;
|
|
474
|
-
server.externalServices.transak.request = {
|
|
475
|
-
post: (_url, opts, cb) => {
|
|
476
|
-
capturedHeaders = opts.headers;
|
|
477
|
-
return cb(null, { body: 'data' });
|
|
478
|
-
},
|
|
479
|
-
};
|
|
480
755
|
req.body.context = 'web';
|
|
481
756
|
req.headers['x-forwarded-for'] = '198.51.100.7';
|
|
482
757
|
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
483
|
-
|
|
758
|
+
sessionCalls[0].opts.headers['x-user-ip'].should.equal('198.51.100.7');
|
|
484
759
|
});
|
|
485
760
|
it('should return error if post returns error', async () => {
|
|
486
|
-
|
|
487
|
-
post: (_url, _opts, _cb) => { return _cb(new Error('Error'), null); },
|
|
488
|
-
};
|
|
489
|
-
server.externalServices.transak.request = fakeRequest2;
|
|
761
|
+
sessionError = new Error('Error');
|
|
490
762
|
try {
|
|
491
763
|
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
492
764
|
should.fail('should have thrown');
|
|
@@ -509,33 +781,134 @@ describe('Transak integration', () => {
|
|
|
509
781
|
});
|
|
510
782
|
});
|
|
511
783
|
describe('#transakGetOrderDetails', () => {
|
|
512
|
-
|
|
784
|
+
let ownedAddress;
|
|
785
|
+
beforeEach(async () => {
|
|
513
786
|
req = {
|
|
514
787
|
headers: {},
|
|
515
788
|
body: {
|
|
516
789
|
env: 'sandbox',
|
|
517
|
-
orderId: '
|
|
518
|
-
accessToken: 'accessToken1',
|
|
790
|
+
orderId: '6f1d2c3b-1a2b-4c5d-8e9f-0a1b2c3d4e5f',
|
|
519
791
|
}
|
|
520
792
|
};
|
|
521
793
|
server.externalServices.transak.request = fakeRequest;
|
|
794
|
+
ownedAddress = (await util_1.default.promisify(server.createAddress).call(server, {})).address;
|
|
795
|
+
orderBody = { data: { walletAddress: ownedAddress, status: 'PROCESSING' } };
|
|
796
|
+
});
|
|
797
|
+
it('should return an order for an address of the requesting wallet', async () => {
|
|
798
|
+
const data = await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
799
|
+
data.should.deep.equal({ data: { walletAddress: ownedAddress, status: 'PROCESSING' } });
|
|
800
|
+
});
|
|
801
|
+
it('should not return another wallet\'s order', async () => {
|
|
802
|
+
const { server: otherServer } = await helpers_1.default.createAndJoinWallet(1, 1, { offset: 1 });
|
|
803
|
+
const otherAddress = (await util_1.default.promisify(otherServer.createAddress).call(otherServer, {})).address;
|
|
804
|
+
orderBody = { data: { walletAddress: otherAddress, status: 'PROCESSING' } };
|
|
805
|
+
try {
|
|
806
|
+
await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
807
|
+
should.fail('should have thrown');
|
|
808
|
+
}
|
|
809
|
+
catch (err) {
|
|
810
|
+
err.message.should.equal('Transak order not found');
|
|
811
|
+
}
|
|
812
|
+
otherServer.externalServices.transak.request = fakeRequest;
|
|
813
|
+
const data = await otherServer.externalServices.transak.transakGetOrderDetails({ headers: {}, body: { ...req.body } }, otherServer.walletId);
|
|
814
|
+
data.data.walletAddress.should.equal(otherAddress);
|
|
815
|
+
});
|
|
816
|
+
it('should not return an order without a wallet address', async () => {
|
|
817
|
+
orderBody = { data: { status: 'PROCESSING' } };
|
|
818
|
+
try {
|
|
819
|
+
await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
820
|
+
should.fail('should have thrown');
|
|
821
|
+
}
|
|
822
|
+
catch (err) {
|
|
823
|
+
err.message.should.equal('Transak order not found');
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
it('should not return an order without a requesting wallet', async () => {
|
|
827
|
+
try {
|
|
828
|
+
await server.externalServices.transak.transakGetOrderDetails(req, undefined);
|
|
829
|
+
should.fail('should have thrown');
|
|
830
|
+
}
|
|
831
|
+
catch (err) {
|
|
832
|
+
err.message.should.equal('Transak order not found');
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
it('should match an EVM order address in any case', async () => {
|
|
836
|
+
const { server: ethServer } = await helpers_1.default.createAndJoinWallet(1, 1, { coin: 'eth', offset: 1 });
|
|
837
|
+
ethServer.externalServices.transak.request = fakeRequest;
|
|
838
|
+
const ethAddress = (await util_1.default.promisify(ethServer.createAddress).call(ethServer, {})).address;
|
|
839
|
+
ethAddress.should.not.equal(ethAddress.toLowerCase());
|
|
840
|
+
for (const walletAddress of [ethAddress.toLowerCase(), ethAddress.toUpperCase().replace('0X', '0x')]) {
|
|
841
|
+
orderBody = { data: { walletAddress, status: 'PROCESSING' } };
|
|
842
|
+
const data = await ethServer.externalServices.transak.transakGetOrderDetails({ headers: {}, body: { ...req.body } }, ethServer.walletId);
|
|
843
|
+
data.data.walletAddress.should.equal(walletAddress);
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
it('should pass through a Transak error without an ownership check', async () => {
|
|
847
|
+
orderStatuses = [400];
|
|
848
|
+
orderBody = { error: { statusCode: 400, message: 'Invalid orderId' } };
|
|
849
|
+
const data = await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
850
|
+
data.should.deep.equal({ error: { statusCode: 400, message: 'Invalid orderId' } });
|
|
522
851
|
});
|
|
523
852
|
it('should work properly if req is OK', async () => {
|
|
524
|
-
const data = await server.externalServices.transak.transakGetOrderDetails(req);
|
|
853
|
+
const data = await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
525
854
|
should.exist(data);
|
|
526
855
|
});
|
|
527
856
|
it('should work properly if req is OK for web', async () => {
|
|
528
857
|
req.body.context = 'web';
|
|
529
|
-
const data = await server.externalServices.transak.transakGetOrderDetails(req);
|
|
858
|
+
const data = await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
530
859
|
should.exist(data);
|
|
531
860
|
});
|
|
861
|
+
it('should use the server-side partner token and ignore any client accessToken', async () => {
|
|
862
|
+
req.body.accessToken = 'clientSuppliedToken';
|
|
863
|
+
await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
864
|
+
orderCalls.length.should.equal(1);
|
|
865
|
+
orderCalls[0].url.should.equal('api1/partners/api/v2/order/6f1d2c3b-1a2b-4c5d-8e9f-0a1b2c3d4e5f');
|
|
866
|
+
orderCalls[0].opts.headers['access-token'].should.equal(PARTNER_TOKEN);
|
|
867
|
+
});
|
|
868
|
+
it('should forward the deviceIp as x-user-ip for web context', async () => {
|
|
869
|
+
req.body.context = 'web';
|
|
870
|
+
req.body.deviceIp = '203.0.113.42';
|
|
871
|
+
req.headers['x-forwarded-for'] = '198.51.100.7';
|
|
872
|
+
await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
873
|
+
orderCalls[0].opts.headers['x-user-ip'].should.equal('203.0.113.42');
|
|
874
|
+
});
|
|
875
|
+
it('should ignore a body deviceIp for non-web context and use the request IP', async () => {
|
|
876
|
+
req.body.deviceIp = '203.0.113.42';
|
|
877
|
+
req.headers['x-forwarded-for'] = '198.51.100.7';
|
|
878
|
+
await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
879
|
+
orderCalls[0].opts.headers['x-user-ip'].should.equal('198.51.100.7');
|
|
880
|
+
});
|
|
881
|
+
it('should share the partner token with signedPaymentUrl', async () => {
|
|
882
|
+
await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
883
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl({ headers: {}, body: { env: 'sandbox', ...appWidgetBody() } });
|
|
884
|
+
refreshCalls.length.should.equal(1);
|
|
885
|
+
});
|
|
886
|
+
it('should refresh and retry once when Transak rejects an older partner token', async () => {
|
|
887
|
+
tokens = ['staleToken', 'freshToken'];
|
|
888
|
+
await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
889
|
+
await updatePartnerTokenCache({ $set: { 'result.fetchedAt': Date.now() - 60 * 60 * 1000 } });
|
|
890
|
+
orderStatuses = [401, 200];
|
|
891
|
+
await server.externalServices.transak.transakGetOrderDetails({ headers: {}, body: { ...req.body } }, server.walletId);
|
|
892
|
+
orderCalls.map(c => c.opts.headers['access-token']).should.deep.equal(['staleToken', 'staleToken', 'freshToken']);
|
|
893
|
+
});
|
|
894
|
+
for (const orderId of ['../orders', '../orders?limit=100', 'abc/def', 'abc?x=1', 'a#b', '', 'a'.repeat(65)]) {
|
|
895
|
+
it(`should reject orderId ${JSON.stringify(orderId)} without calling Transak`, async () => {
|
|
896
|
+
req.body.orderId = orderId;
|
|
897
|
+
try {
|
|
898
|
+
await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
899
|
+
should.fail('should have thrown');
|
|
900
|
+
}
|
|
901
|
+
catch (err) {
|
|
902
|
+
err.message.should.match(/Invalid Transak orderId|Transak's request missing arguments/);
|
|
903
|
+
}
|
|
904
|
+
orderCalls.length.should.equal(0);
|
|
905
|
+
refreshCalls.length.should.equal(0);
|
|
906
|
+
});
|
|
907
|
+
}
|
|
532
908
|
it('should return error if get returns error', async () => {
|
|
533
|
-
|
|
534
|
-
get: (_url, _opts, _cb) => { return _cb(new Error('Error'), null); },
|
|
535
|
-
};
|
|
536
|
-
server.externalServices.transak.request = fakeRequest2;
|
|
909
|
+
orderError = new Error('Error');
|
|
537
910
|
try {
|
|
538
|
-
await server.externalServices.transak.transakGetOrderDetails(req);
|
|
911
|
+
await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
539
912
|
should.fail('should have thrown');
|
|
540
913
|
}
|
|
541
914
|
catch (err) {
|
|
@@ -546,12 +919,9 @@ describe('Transak integration', () => {
|
|
|
546
919
|
;
|
|
547
920
|
});
|
|
548
921
|
it('should return error and not leak the response if body is empty', async () => {
|
|
549
|
-
|
|
550
|
-
get: (_url, opts, cb) => { return cb(null, { statusCode: 200, body: undefined, request: { headers: opts.headers } }); },
|
|
551
|
-
};
|
|
552
|
-
server.externalServices.transak.request = fakeRequest2;
|
|
922
|
+
orderBody = undefined;
|
|
553
923
|
try {
|
|
554
|
-
await server.externalServices.transak.transakGetOrderDetails(req);
|
|
924
|
+
await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
555
925
|
should.fail('should have thrown');
|
|
556
926
|
}
|
|
557
927
|
catch (err) {
|
|
@@ -562,7 +932,7 @@ describe('Transak integration', () => {
|
|
|
562
932
|
it('should return error if there is some missing arguments', async () => {
|
|
563
933
|
delete req.body.orderId;
|
|
564
934
|
try {
|
|
565
|
-
await server.externalServices.transak.transakGetOrderDetails(req);
|
|
935
|
+
await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
566
936
|
should.fail('should have thrown');
|
|
567
937
|
}
|
|
568
938
|
catch (err) {
|