@bitpay-labs/bitcore-wallet-service 11.11.2 → 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 +13 -2
- package/src/lib/common/defaults.ts +31 -0
- package/src/lib/common/utils.ts +18 -1
- 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/tsskeygen.ts +16 -0
- package/src/lib/model/wallet.ts +1 -0
- package/src/lib/pushnotificationsservice.ts +2 -0
- package/src/lib/routes/maintenance.ts +11 -2
- package/src/lib/routes/middleware/authTssRequest.ts +28 -16
- package/src/lib/routes/middleware/verifyTssMessage.ts +4 -1
- package/src/lib/routes/services.ts +8 -2
- package/src/lib/routes/tss.ts +33 -30
- package/src/lib/routes/walletdata.ts +2 -0
- package/src/lib/server.ts +82 -19
- package/src/lib/storage.ts +39 -0
- package/src/lib/tss.ts +111 -7
- package/test/blockchainexplorer.test.ts +7 -0
- package/test/expressapp.test.ts +130 -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 +192 -4
- package/test/integration/tss.test.ts +428 -0
- package/ts_build/package.json +4 -4
- 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 +13 -1
- package/ts_build/src/lib/common/constants.d.ts.map +1 -1
- package/ts_build/src/lib/common/constants.js +13 -2
- 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 +38 -1
- package/ts_build/src/lib/common/index.d.ts.map +1 -1
- package/ts_build/src/lib/common/utils.d.ts +1 -0
- package/ts_build/src/lib/common/utils.d.ts.map +1 -1
- package/ts_build/src/lib/common/utils.js +16 -1
- package/ts_build/src/lib/common/utils.js.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/tsskeygen.d.ts +10 -0
- package/ts_build/src/lib/model/tsskeygen.d.ts.map +1 -1
- package/ts_build/src/lib/model/tsskeygen.js +3 -0
- package/ts_build/src/lib/model/tsskeygen.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/maintenance.d.ts.map +1 -1
- package/ts_build/src/lib/routes/maintenance.js +12 -4
- package/ts_build/src/lib/routes/maintenance.js.map +1 -1
- package/ts_build/src/lib/routes/middleware/authTssRequest.d.ts +6 -2
- package/ts_build/src/lib/routes/middleware/authTssRequest.d.ts.map +1 -1
- package/ts_build/src/lib/routes/middleware/authTssRequest.js +21 -14
- package/ts_build/src/lib/routes/middleware/authTssRequest.js.map +1 -1
- package/ts_build/src/lib/routes/middleware/verifyTssMessage.d.ts.map +1 -1
- package/ts_build/src/lib/routes/middleware/verifyTssMessage.js +2 -1
- package/ts_build/src/lib/routes/middleware/verifyTssMessage.js.map +1 -1
- package/ts_build/src/lib/routes/services.d.ts.map +1 -1
- package/ts_build/src/lib/routes/services.js +7 -2
- package/ts_build/src/lib/routes/services.js.map +1 -1
- package/ts_build/src/lib/routes/tss.d.ts.map +1 -1
- package/ts_build/src/lib/routes/tss.js +32 -30
- package/ts_build/src/lib/routes/tss.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 +23 -2
- package/ts_build/src/lib/server.d.ts.map +1 -1
- package/ts_build/src/lib/server.js +37 -5
- package/ts_build/src/lib/server.js.map +1 -1
- package/ts_build/src/lib/storage.d.ts +9 -0
- package/ts_build/src/lib/storage.d.ts.map +1 -1
- package/ts_build/src/lib/storage.js +26 -0
- package/ts_build/src/lib/storage.js.map +1 -1
- package/ts_build/src/lib/tss.d.ts +12 -0
- package/ts_build/src/lib/tss.d.ts.map +1 -1
- package/ts_build/src/lib/tss.js +64 -5
- package/ts_build/src/lib/tss.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 +120 -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 +182 -3
- package/ts_build/test/integration/server.test.js.map +1 -1
- package/ts_build/test/integration/tss.test.d.ts.map +1 -1
- package/ts_build/test/integration/tss.test.js +344 -0
- package/ts_build/test/integration/tss.test.js.map +1 -1
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
import * as chai from 'chai';
|
|
4
|
+
import * as crypto from 'crypto';
|
|
4
5
|
import * as sinon from 'sinon';
|
|
5
6
|
import 'chai/register-should';
|
|
6
7
|
import util from 'util';
|
|
7
8
|
import { WalletService } from '../../../src/lib/server';
|
|
9
|
+
import { TransakService } from '../../../src/externalservices/transak';
|
|
8
10
|
import * as TestData from '../../testdata';
|
|
9
11
|
import helpers from '../helpers';
|
|
10
12
|
import config from '../../../src/config';
|
|
@@ -12,6 +14,27 @@ import logger from '../../../src/lib/logger';
|
|
|
12
14
|
|
|
13
15
|
const should = chai.should();
|
|
14
16
|
|
|
17
|
+
const PARTNER_TOKEN = 'partnerToken1';
|
|
18
|
+
|
|
19
|
+
// The body bitpay-app sends to signedPaymentUrl, minus env/accessToken.
|
|
20
|
+
const appWidgetBody = () => ({
|
|
21
|
+
walletAddress: 'walletAddress1',
|
|
22
|
+
disableWalletAddressForm: true,
|
|
23
|
+
redirectURL: 'bitpay://transak',
|
|
24
|
+
exchangeScreenTitle: 'Bitpay - Buy crypto',
|
|
25
|
+
fiatAmount: '500',
|
|
26
|
+
fiatCurrency: 'USD',
|
|
27
|
+
network: 'mainnet',
|
|
28
|
+
cryptoCurrencyCode: 'BTC',
|
|
29
|
+
cryptoCurrencyList: 'BTC',
|
|
30
|
+
hideExchangeScreen: true,
|
|
31
|
+
themeColor: '2240C4',
|
|
32
|
+
hideMenu: false,
|
|
33
|
+
partnerOrderId: 'partnerOrderId1',
|
|
34
|
+
partnerCustomerId: 'partnerCustomerId1',
|
|
35
|
+
paymentMethod: 'credit_debit_card',
|
|
36
|
+
});
|
|
37
|
+
|
|
15
38
|
describe('Transak integration', () => {
|
|
16
39
|
const sandbox = sinon.createSandbox();
|
|
17
40
|
let server;
|
|
@@ -20,6 +43,27 @@ describe('Transak integration', () => {
|
|
|
20
43
|
let req;
|
|
21
44
|
let loggerError: sinon.SinonStub;
|
|
22
45
|
|
|
46
|
+
// Fake Transak state, reset before each test.
|
|
47
|
+
let refreshCalls: { url: string; opts: any }[];
|
|
48
|
+
let sessionCalls: { url: string; opts: any }[];
|
|
49
|
+
let orderCalls: { url: string; opts: any }[];
|
|
50
|
+
let tokens: string[];
|
|
51
|
+
let tokenExpiresAt: number | undefined;
|
|
52
|
+
let refreshStatus: number;
|
|
53
|
+
let refreshDelay: number;
|
|
54
|
+
let sessionStatuses: number[];
|
|
55
|
+
let sessionError: Error | null;
|
|
56
|
+
let sessionErrorBody: any;
|
|
57
|
+
let orderStatuses: number[];
|
|
58
|
+
let orderError: Error | null;
|
|
59
|
+
let orderBody: any;
|
|
60
|
+
|
|
61
|
+
const newReq = (env: string, extra = {}) => ({ headers: {}, body: { env, ...appWidgetBody(), ...extra } });
|
|
62
|
+
const partnerTokenId = (env: string, apiKey: string) =>
|
|
63
|
+
`${env}:${crypto.createHash('sha256').update(apiKey).digest('hex').slice(0, 12)}`;
|
|
64
|
+
const updatePartnerTokenCache = (update: any) =>
|
|
65
|
+
server.storage.db.collection('cache').updateMany({ key: { $regex: '^transakPartnerToken:' } }, update);
|
|
66
|
+
|
|
23
67
|
before(async () => {
|
|
24
68
|
await helpers.before();
|
|
25
69
|
});
|
|
@@ -52,9 +96,50 @@ describe('Transak integration', () => {
|
|
|
52
96
|
}
|
|
53
97
|
};
|
|
54
98
|
|
|
99
|
+
refreshCalls = [];
|
|
100
|
+
sessionCalls = [];
|
|
101
|
+
orderCalls = [];
|
|
102
|
+
tokens = [PARTNER_TOKEN];
|
|
103
|
+
tokenExpiresAt = Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60;
|
|
104
|
+
refreshStatus = 200;
|
|
105
|
+
refreshDelay = 0;
|
|
106
|
+
sessionStatuses = [];
|
|
107
|
+
sessionError = null;
|
|
108
|
+
sessionErrorBody = { error: { message: 'unauthorized' } };
|
|
109
|
+
orderStatuses = [];
|
|
110
|
+
orderError = null;
|
|
111
|
+
orderBody = 'data';
|
|
112
|
+
|
|
55
113
|
fakeRequest = {
|
|
56
|
-
get: (
|
|
57
|
-
|
|
114
|
+
get: (url, opts, cb) => {
|
|
115
|
+
if (url.includes('/partners/api/v2/order/')) {
|
|
116
|
+
orderCalls.push({ url, opts });
|
|
117
|
+
if (orderError) return cb(orderError, null);
|
|
118
|
+
return cb(null, { statusCode: orderStatuses.shift() ?? 200, body: orderBody });
|
|
119
|
+
}
|
|
120
|
+
return cb(null, { body: 'data' });
|
|
121
|
+
},
|
|
122
|
+
post: (url, opts, cb) => {
|
|
123
|
+
if (url.endsWith('/partners/api/v2/refresh-token')) {
|
|
124
|
+
refreshCalls.push({ url, opts });
|
|
125
|
+
const accessToken = tokens.length > 1 ? tokens.shift() : tokens[0];
|
|
126
|
+
const respond = () => cb(null, {
|
|
127
|
+
statusCode: refreshStatus,
|
|
128
|
+
body: refreshStatus === 200 ? { data: { accessToken, expiresAt: tokenExpiresAt } } : { error: 'unauthorized' }
|
|
129
|
+
});
|
|
130
|
+
return refreshDelay ? setTimeout(respond, refreshDelay) : respond();
|
|
131
|
+
}
|
|
132
|
+
if (url.endsWith('/api/v2/auth/session')) {
|
|
133
|
+
sessionCalls.push({ url, opts });
|
|
134
|
+
if (sessionError) return cb(sessionError, null);
|
|
135
|
+
const statusCode = sessionStatuses.shift() ?? 200;
|
|
136
|
+
return cb(null, {
|
|
137
|
+
statusCode,
|
|
138
|
+
body: statusCode === 200 ? { data: { widgetUrl: 'https://global.transak.com/?session=abc' } } : sessionErrorBody
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return cb(null, { body: 'data' });
|
|
142
|
+
},
|
|
58
143
|
};
|
|
59
144
|
|
|
60
145
|
await helpers.beforeEach();
|
|
@@ -90,62 +175,29 @@ describe('Transak integration', () => {
|
|
|
90
175
|
env: 'sandbox',
|
|
91
176
|
}
|
|
92
177
|
};
|
|
93
|
-
server.externalServices.transak.request =
|
|
178
|
+
server.externalServices.transak.request = {
|
|
179
|
+
get: () => { throw new Error('should not call Transak'); },
|
|
180
|
+
post: () => { throw new Error('should not call Transak'); },
|
|
181
|
+
};
|
|
94
182
|
});
|
|
95
183
|
|
|
96
|
-
it('should
|
|
184
|
+
it('should return a placeholder without calling Transak', async () => {
|
|
97
185
|
const data = await server.externalServices.transak.transakGetAccessToken(req);
|
|
98
|
-
should.
|
|
186
|
+
data.data.accessToken.should.equal('server-managed');
|
|
99
187
|
});
|
|
100
188
|
|
|
101
|
-
it('should
|
|
189
|
+
it('should return a placeholder for web without calling Transak', async () => {
|
|
102
190
|
req.body.context = 'web';
|
|
103
191
|
const data = await server.externalServices.transak.transakGetAccessToken(req);
|
|
104
|
-
should.
|
|
192
|
+
data.data.accessToken.should.equal('server-managed');
|
|
105
193
|
});
|
|
106
194
|
|
|
107
|
-
it('should
|
|
108
|
-
|
|
109
|
-
server.externalServices.transak.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
},
|
|
114
|
-
};
|
|
115
|
-
req.body.context = 'web';
|
|
116
|
-
req.body.deviceIp = '203.0.113.42';
|
|
117
|
-
await server.externalServices.transak.transakGetAccessToken(req);
|
|
118
|
-
capturedHeaders['x-user-ip'].should.equal('203.0.113.42');
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it('should return error if post returns error', async () => {
|
|
122
|
-
const fakeRequest2 = {
|
|
123
|
-
post: (_url, _opts, _cb) => { return _cb(new Error('Error'), null); },
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
server.externalServices.transak.request = fakeRequest2;
|
|
127
|
-
try {
|
|
128
|
-
await server.externalServices.transak.transakGetAccessToken(req);
|
|
129
|
-
should.fail('should have thrown');
|
|
130
|
-
} catch (err) {
|
|
131
|
-
loggerError.callCount.should.equal(1);
|
|
132
|
-
loggerError.args[0][0].should.contain('Transak request failed with error (');
|
|
133
|
-
err.message.should.equal('Failed to get Transak access token');
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it('should return error and not leak the response if body is empty', async () => {
|
|
138
|
-
const fakeRequest2 = {
|
|
139
|
-
post: (_url, opts, cb) => { return cb(null, { statusCode: 200, body: undefined, request: { headers: opts.headers } }); },
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
server.externalServices.transak.request = fakeRequest2;
|
|
143
|
-
try {
|
|
144
|
-
await server.externalServices.transak.transakGetAccessToken(req);
|
|
145
|
-
should.fail('should have thrown');
|
|
146
|
-
} catch (err) {
|
|
147
|
-
err.message.should.equal('Transak did not return an access token');
|
|
148
|
-
}
|
|
195
|
+
it('should return an integer expiresAt in seconds in the future', async () => {
|
|
196
|
+
const now = Math.floor(Date.now() / 1000);
|
|
197
|
+
const { data } = await server.externalServices.transak.transakGetAccessToken(req);
|
|
198
|
+
Number.isInteger(data.expiresAt).should.equal(true);
|
|
199
|
+
data.expiresAt.should.be.above(now);
|
|
200
|
+
data.expiresAt.should.be.at.most(now + 2 * 60 + 1);
|
|
149
201
|
});
|
|
150
202
|
|
|
151
203
|
it('should return error if transak is commented in config', async () => {
|
|
@@ -372,15 +424,8 @@ describe('Transak integration', () => {
|
|
|
372
424
|
headers: {},
|
|
373
425
|
body: {
|
|
374
426
|
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',
|
|
427
|
+
accessToken: 'clientSuppliedToken',
|
|
428
|
+
...appWidgetBody(),
|
|
384
429
|
}
|
|
385
430
|
};
|
|
386
431
|
server.externalServices.transak.request = fakeRequest;
|
|
@@ -388,25 +433,329 @@ describe('Transak integration', () => {
|
|
|
388
433
|
|
|
389
434
|
it('should get the paymentUrl properly if req is OK', async () => {
|
|
390
435
|
const data = await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
391
|
-
should.
|
|
436
|
+
data.urlWithSignature.should.equal('https://global.transak.com/?session=abc');
|
|
392
437
|
});
|
|
393
438
|
|
|
394
439
|
it('should get the paymentUrl properly if req is OK for web', async () => {
|
|
440
|
+
req.body.context = 'web';
|
|
441
|
+
const data = await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
442
|
+
data.urlWithSignature.should.equal('https://global.transak.com/?session=abc');
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it('should work for web with only the fields bitpay.com sends and no accessToken', async () => {
|
|
395
446
|
req.body = {
|
|
396
447
|
env: 'production',
|
|
397
|
-
accessToken: 'accessToken1',
|
|
398
448
|
context: 'web',
|
|
399
449
|
walletAddress: 'walletAddress1',
|
|
400
|
-
redirectURL: 'bitpay
|
|
450
|
+
redirectURL: 'https://bitpay.com/buy-crypto',
|
|
401
451
|
fiatAmount: '500',
|
|
402
452
|
fiatCurrency: 'USD',
|
|
403
|
-
|
|
453
|
+
defaultNetwork: 'bitcoin',
|
|
404
454
|
cryptoCurrencyCode: 'BTC',
|
|
405
|
-
|
|
406
|
-
partnerCustomerId: 'partnerCustomerId1',
|
|
455
|
+
deviceIp: '203.0.113.42',
|
|
407
456
|
};
|
|
408
457
|
const data = await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
409
|
-
should.exist(data);
|
|
458
|
+
should.exist(data.urlWithSignature);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('should work for the app without an accessToken', async () => {
|
|
462
|
+
delete req.body.accessToken;
|
|
463
|
+
const data = await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
464
|
+
should.exist(data.urlWithSignature);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it('should use the server-side partner token and ignore any client accessToken', async () => {
|
|
468
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
469
|
+
sessionCalls.length.should.equal(1);
|
|
470
|
+
sessionCalls[0].opts.headers['access-token'].should.equal(PARTNER_TOKEN);
|
|
471
|
+
refreshCalls[0].opts.headers['api-secret'].should.equal('secretKey2');
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it('should only send allowlisted fields in widgetParams', async () => {
|
|
475
|
+
req.body.context = 'web';
|
|
476
|
+
req.body.deviceIp = '203.0.113.42';
|
|
477
|
+
req.body.somethingElse = 'nope';
|
|
478
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
479
|
+
const widgetParams = sessionCalls[0].opts.body.widgetParams;
|
|
480
|
+
should.not.exist(widgetParams.accessToken);
|
|
481
|
+
should.not.exist(widgetParams.env);
|
|
482
|
+
should.not.exist(widgetParams.context);
|
|
483
|
+
should.not.exist(widgetParams.deviceIp);
|
|
484
|
+
should.not.exist(widgetParams.somethingElse);
|
|
485
|
+
JSON.stringify(sessionCalls[0].opts.body).should.not.contain(PARTNER_TOKEN);
|
|
486
|
+
JSON.stringify(sessionCalls[0].opts.body).should.not.contain('clientSuppliedToken');
|
|
487
|
+
widgetParams.should.deep.equal({
|
|
488
|
+
...appWidgetBody(),
|
|
489
|
+
apiKey: 'apiKey4',
|
|
490
|
+
referrerDomain: 'bitpay.com',
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it('should refresh the partner token once and reuse it across requests and instances', async () => {
|
|
495
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
496
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl({ headers: {}, body: { env: 'production', ...appWidgetBody() } });
|
|
497
|
+
|
|
498
|
+
const otherInstance = new TransakService(server.storage, server.lock);
|
|
499
|
+
otherInstance.request = fakeRequest;
|
|
500
|
+
await otherInstance.transakGetSignedPaymentUrl({ headers: {}, body: { env: 'production', ...appWidgetBody() } });
|
|
501
|
+
|
|
502
|
+
refreshCalls.length.should.equal(1);
|
|
503
|
+
sessionCalls.length.should.equal(3);
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it('should refresh only once for concurrent requests', async () => {
|
|
507
|
+
refreshDelay = 100;
|
|
508
|
+
await Promise.all([1, 2, 3, 4, 5].map(() => {
|
|
509
|
+
const instance = new TransakService(server.storage, server.lock);
|
|
510
|
+
instance.request = fakeRequest;
|
|
511
|
+
return instance.transakGetSignedPaymentUrl({ headers: {}, body: { env: 'production', ...appWidgetBody() } });
|
|
512
|
+
}));
|
|
513
|
+
refreshCalls.length.should.equal(1);
|
|
514
|
+
sessionCalls.length.should.equal(5);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('should keep a separate partner token per environment', async () => {
|
|
518
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
519
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl({ headers: {}, body: { env: 'production', context: 'web', ...appWidgetBody() } });
|
|
520
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl({ headers: {}, body: { env: 'sandbox', ...appWidgetBody() } });
|
|
521
|
+
refreshCalls.length.should.equal(3);
|
|
522
|
+
refreshCalls.map(c => c.opts.headers['x-api-key']).should.deep.equal(['apiKey2', 'apiKey4', 'apiKey1']);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('should refresh the partner token when it is close to expiring', async () => {
|
|
526
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
527
|
+
await updatePartnerTokenCache({ $set: { 'result.expiresAt': Date.now() + 5 * 60 * 1000 } });
|
|
528
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
529
|
+
refreshCalls.length.should.equal(2);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it('should use a fallback TTL when Transak returns expiresAt as a duration', async () => {
|
|
533
|
+
tokenExpiresAt = 7 * 24 * 60 * 60;
|
|
534
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
535
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
536
|
+
refreshCalls.length.should.equal(1);
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it('should use a fallback TTL when Transak omits expiresAt', async () => {
|
|
540
|
+
tokenExpiresAt = undefined;
|
|
541
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
542
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
543
|
+
refreshCalls.length.should.equal(1);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
it('should accept expiresAt in milliseconds', async () => {
|
|
547
|
+
tokenExpiresAt = Date.now() + 7 * 24 * 60 * 60 * 1000;
|
|
548
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
549
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
550
|
+
refreshCalls.length.should.equal(1);
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it('should not reuse a token cached for a rotated API key', async () => {
|
|
554
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
555
|
+
config.transak.production.apiKey = 'apiKey2-rotated';
|
|
556
|
+
tokens = ['rotatedToken'];
|
|
557
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
558
|
+
refreshCalls.length.should.equal(2);
|
|
559
|
+
refreshCalls[1].opts.headers['x-api-key'].should.equal('apiKey2-rotated');
|
|
560
|
+
sessionCalls[1].opts.headers['access-token'].should.equal('rotatedToken');
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it('should refresh and retry once when Transak rejects an older partner token', async () => {
|
|
564
|
+
tokens = ['staleToken', 'freshToken'];
|
|
565
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
566
|
+
await updatePartnerTokenCache({ $set: { 'result.fetchedAt': Date.now() - 60 * 60 * 1000 } });
|
|
567
|
+
sessionStatuses = [401, 200];
|
|
568
|
+
const data = await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
569
|
+
should.exist(data.urlWithSignature);
|
|
570
|
+
refreshCalls.length.should.equal(2);
|
|
571
|
+
sessionCalls.map(c => c.opts.headers['access-token']).should.deep.equal(['staleToken', 'staleToken', 'freshToken']);
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it('should fail rather than return an empty URL on repeated 401s', async () => {
|
|
575
|
+
tokens = ['token1', 'token2', 'token3'];
|
|
576
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
577
|
+
await updatePartnerTokenCache({ $set: { 'result.fetchedAt': Date.now() - 60 * 60 * 1000 } });
|
|
578
|
+
sessionStatuses = [401, 401, 401];
|
|
579
|
+
try {
|
|
580
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
581
|
+
should.fail('should have thrown');
|
|
582
|
+
} catch (err) {
|
|
583
|
+
err.message.should.equal('Failed to get Transak signed payment URL');
|
|
584
|
+
}
|
|
585
|
+
sessionCalls.length.should.equal(3);
|
|
586
|
+
refreshCalls.length.should.equal(2);
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
it('should not refresh when Transak rejects a token it just issued', async () => {
|
|
590
|
+
sessionStatuses = [401, 401, 401, 401, 401];
|
|
591
|
+
for (let i = 0; i < 5; i++) {
|
|
592
|
+
try {
|
|
593
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
594
|
+
should.fail('should have thrown');
|
|
595
|
+
} catch (err) {
|
|
596
|
+
err.message.should.equal('Transak rejected the access token');
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
refreshCalls.length.should.equal(1);
|
|
600
|
+
sessionCalls.length.should.equal(5);
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
it('should fail if Transak does not return a widget URL', async () => {
|
|
604
|
+
sessionStatuses = [500];
|
|
605
|
+
try {
|
|
606
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
607
|
+
should.fail('should have thrown');
|
|
608
|
+
} catch (err) {
|
|
609
|
+
err.message.should.equal('Failed to get Transak signed payment URL');
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
it('should fail without refreshing when another request holds the refresh lock too long', async () => {
|
|
614
|
+
const release: any = await util.promisify((cb) =>
|
|
615
|
+
server.lock.acquire(`transakPartnerToken:${partnerTokenId('production', 'apiKey2')}`, { lockTime: 60000 }, cb)
|
|
616
|
+
)();
|
|
617
|
+
const transak = server.externalServices.transak;
|
|
618
|
+
transak.partnerTokenLockWait = 200;
|
|
619
|
+
try {
|
|
620
|
+
await transak.transakGetSignedPaymentUrl(req);
|
|
621
|
+
should.fail('should have thrown');
|
|
622
|
+
} catch (err) {
|
|
623
|
+
err.message.should.equal('Failed to get Transak access token');
|
|
624
|
+
} finally {
|
|
625
|
+
release();
|
|
626
|
+
}
|
|
627
|
+
refreshCalls.length.should.equal(0);
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
it('should check the failure backoff before waiting on the refresh lock', async () => {
|
|
631
|
+
refreshStatus = 401;
|
|
632
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req).catch(() => {});
|
|
633
|
+
const release: any = await util.promisify((cb) =>
|
|
634
|
+
server.lock.acquire(`transakPartnerToken:${partnerTokenId('production', 'apiKey2')}`, { lockTime: 60000 }, cb)
|
|
635
|
+
)();
|
|
636
|
+
const started = Date.now();
|
|
637
|
+
try {
|
|
638
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
639
|
+
should.fail('should have thrown');
|
|
640
|
+
} catch (err) {
|
|
641
|
+
err.message.should.equal('Failed to get Transak access token');
|
|
642
|
+
} finally {
|
|
643
|
+
release();
|
|
644
|
+
}
|
|
645
|
+
(Date.now() - started).should.be.below(1000);
|
|
646
|
+
refreshCalls.length.should.equal(1);
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
it('should retry the refresh once the failure backoff has passed', async () => {
|
|
650
|
+
refreshStatus = 401;
|
|
651
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req).catch(() => {});
|
|
652
|
+
await server.storage.db.collection('cache').updateMany(
|
|
653
|
+
{ key: { $regex: '^transakPartnerTokenFailure:' } },
|
|
654
|
+
{ $set: { ts: Date.now() - 31 * 1000 } }
|
|
655
|
+
);
|
|
656
|
+
refreshStatus = 200;
|
|
657
|
+
const data = await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
658
|
+
should.exist(data.urlWithSignature);
|
|
659
|
+
refreshCalls.length.should.equal(2);
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
it('should keep serving a still-valid token when the proactive refresh fails', async () => {
|
|
663
|
+
tokens = ['currentToken'];
|
|
664
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
665
|
+
await updatePartnerTokenCache({ $set: { 'result.expiresAt': Date.now() + 5 * 60 * 1000 } });
|
|
666
|
+
refreshStatus = 500;
|
|
667
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
668
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
669
|
+
refreshCalls.length.should.equal(2);
|
|
670
|
+
sessionCalls.map(c => c.opts.headers['access-token']).should.deep.equal(['currentToken', 'currentToken', 'currentToken']);
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
it('should keep serving a still-valid token when the refresh lock is held too long', async () => {
|
|
674
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
675
|
+
await updatePartnerTokenCache({ $set: { 'result.expiresAt': Date.now() + 5 * 60 * 1000 } });
|
|
676
|
+
const release: any = await util.promisify((cb) =>
|
|
677
|
+
server.lock.acquire(`transakPartnerToken:${partnerTokenId('production', 'apiKey2')}`, { lockTime: 60000 }, cb)
|
|
678
|
+
)();
|
|
679
|
+
const transak = server.externalServices.transak;
|
|
680
|
+
transak.partnerTokenLockWait = 200;
|
|
681
|
+
try {
|
|
682
|
+
await transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
683
|
+
} finally {
|
|
684
|
+
release();
|
|
685
|
+
}
|
|
686
|
+
refreshCalls.length.should.equal(1);
|
|
687
|
+
sessionCalls[1].opts.headers['access-token'].should.equal(PARTNER_TOKEN);
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
it('should not serve a token past its real expiry when the refresh fails', async () => {
|
|
691
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
692
|
+
await updatePartnerTokenCache({ $set: { 'result.expiresAt': Date.now() + 10 * 1000 } });
|
|
693
|
+
refreshStatus = 500;
|
|
694
|
+
try {
|
|
695
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
696
|
+
should.fail('should have thrown');
|
|
697
|
+
} catch (err) {
|
|
698
|
+
err.message.should.equal('Transak did not return an access token');
|
|
699
|
+
}
|
|
700
|
+
sessionCalls.length.should.equal(1);
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
it('should refresh and retry once when Transak answers 403 for an older token', async () => {
|
|
704
|
+
tokens = ['staleToken', 'freshToken'];
|
|
705
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
706
|
+
await updatePartnerTokenCache({ $set: { 'result.fetchedAt': Date.now() - 60 * 60 * 1000 } });
|
|
707
|
+
sessionStatuses = [403, 200];
|
|
708
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(newReq('production'));
|
|
709
|
+
sessionCalls.map(c => c.opts.headers['access-token']).should.deep.equal(['staleToken', 'staleToken', 'freshToken']);
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
it('should log Transak\'s error message but not the token', async () => {
|
|
713
|
+
const loggerWarn = logger.warn as sinon.SinonStub;
|
|
714
|
+
sessionStatuses = [500];
|
|
715
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req).catch(() => null);
|
|
716
|
+
const logged = loggerWarn.args.map(a => String(a[0])).join('\n');
|
|
717
|
+
logged.should.contain('(unauthorized)');
|
|
718
|
+
logged.should.not.contain(PARTNER_TOKEN);
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
it('should log a non-string Transak error message as JSON', async () => {
|
|
722
|
+
const loggerWarn = logger.warn as sinon.SinonStub;
|
|
723
|
+
sessionStatuses = [400];
|
|
724
|
+
sessionErrorBody = { error: { message: { code: 'INVALID_PARAMS', fields: ['fiatAmount'] } } };
|
|
725
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req).catch(() => null);
|
|
726
|
+
const logged = loggerWarn.args.map(a => String(a[0])).join('\n');
|
|
727
|
+
logged.should.contain('({"code":"INVALID_PARAMS","fields":["fiatAmount"]})');
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
it('should log no detail when Transak sends no error message', async () => {
|
|
731
|
+
const loggerWarn = logger.warn as sinon.SinonStub;
|
|
732
|
+
sessionStatuses = [502];
|
|
733
|
+
sessionErrorBody = 'Bad Gateway';
|
|
734
|
+
const err = await server.externalServices.transak.transakGetSignedPaymentUrl(req).catch(e => e);
|
|
735
|
+
err.message.should.equal('Failed to get Transak signed payment URL');
|
|
736
|
+
loggerWarn.args.map(a => String(a[0])).join('\n').should.match(/status code 502: \S+$/m);
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
it('should forward defaultPaymentMethod when the app sends it instead of paymentMethod', async () => {
|
|
740
|
+
delete req.body.paymentMethod;
|
|
741
|
+
req.body.defaultPaymentMethod = 'credit_debit_card';
|
|
742
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
743
|
+
sessionCalls[0].opts.body.widgetParams.defaultPaymentMethod.should.equal('credit_debit_card');
|
|
744
|
+
should.not.exist(sessionCalls[0].opts.body.widgetParams.paymentMethod);
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
it('should fail and back off when the partner token refresh fails', async () => {
|
|
748
|
+
refreshStatus = 401;
|
|
749
|
+
for (let i = 0; i < 3; i++) {
|
|
750
|
+
try {
|
|
751
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl({ headers: {}, body: { env: 'production', ...appWidgetBody() } });
|
|
752
|
+
should.fail('should have thrown');
|
|
753
|
+
} catch (err) {
|
|
754
|
+
err.message.should.match(/Transak did not return an access token|Failed to get Transak access token/);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
refreshCalls.length.should.equal(1);
|
|
758
|
+
sessionCalls.length.should.equal(0);
|
|
410
759
|
});
|
|
411
760
|
|
|
412
761
|
it('should return error if there is some missing arguments', async () => {
|
|
@@ -422,81 +771,36 @@ describe('Transak integration', () => {
|
|
|
422
771
|
});
|
|
423
772
|
|
|
424
773
|
it('should forward the deviceIp as x-user-ip for web context', async () => {
|
|
425
|
-
let capturedHeaders;
|
|
426
|
-
server.externalServices.transak.request = {
|
|
427
|
-
post: (_url, opts, cb) => {
|
|
428
|
-
capturedHeaders = opts.headers;
|
|
429
|
-
return cb(null, { body: 'data' });
|
|
430
|
-
},
|
|
431
|
-
};
|
|
432
774
|
req.body.context = 'web';
|
|
433
775
|
req.body.deviceIp = '203.0.113.42';
|
|
434
776
|
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
435
|
-
|
|
777
|
+
sessionCalls[0].opts.headers['x-user-ip'].should.equal('203.0.113.42');
|
|
778
|
+
refreshCalls[0].opts.headers['x-user-ip'].should.equal('203.0.113.42');
|
|
436
779
|
});
|
|
437
780
|
|
|
438
781
|
it('should canonicalize an IPv4-mapped IPv6 deviceIp for web context', async () => {
|
|
439
|
-
let capturedHeaders;
|
|
440
|
-
server.externalServices.transak.request = {
|
|
441
|
-
post: (_url, opts, cb) => {
|
|
442
|
-
capturedHeaders = opts.headers;
|
|
443
|
-
return cb(null, { body: 'data' });
|
|
444
|
-
},
|
|
445
|
-
};
|
|
446
782
|
req.body.context = 'web';
|
|
447
783
|
req.body.deviceIp = '::ffff:203.0.113.42';
|
|
448
784
|
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
449
|
-
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
it('should not leak deviceIp into the widgetParams sent to Transak', async () => {
|
|
453
|
-
let capturedBody;
|
|
454
|
-
server.externalServices.transak.request = {
|
|
455
|
-
post: (_url, opts, cb) => {
|
|
456
|
-
capturedBody = opts.body;
|
|
457
|
-
return cb(null, { body: 'data' });
|
|
458
|
-
},
|
|
459
|
-
};
|
|
460
|
-
req.body.context = 'web';
|
|
461
|
-
req.body.deviceIp = '203.0.113.42';
|
|
462
|
-
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
463
|
-
should.not.exist(capturedBody.widgetParams.deviceIp);
|
|
785
|
+
sessionCalls[0].opts.headers['x-user-ip'].should.equal('203.0.113.42');
|
|
464
786
|
});
|
|
465
787
|
|
|
466
788
|
it('should ignore a body deviceIp for non-web context and use the request IP', async () => {
|
|
467
|
-
let capturedHeaders;
|
|
468
|
-
server.externalServices.transak.request = {
|
|
469
|
-
post: (_url, opts, cb) => {
|
|
470
|
-
capturedHeaders = opts.headers;
|
|
471
|
-
return cb(null, { body: 'data' });
|
|
472
|
-
},
|
|
473
|
-
};
|
|
474
789
|
req.headers['x-forwarded-for'] = '198.51.100.7';
|
|
475
790
|
req.body.deviceIp = '203.0.113.42';
|
|
476
791
|
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
477
|
-
|
|
792
|
+
sessionCalls[0].opts.headers['x-user-ip'].should.equal('198.51.100.7');
|
|
478
793
|
});
|
|
479
794
|
|
|
480
795
|
it('should fall back to the request IP for web context when deviceIp is not forwarded', async () => {
|
|
481
|
-
let capturedHeaders;
|
|
482
|
-
server.externalServices.transak.request = {
|
|
483
|
-
post: (_url, opts, cb) => {
|
|
484
|
-
capturedHeaders = opts.headers;
|
|
485
|
-
return cb(null, { body: 'data' });
|
|
486
|
-
},
|
|
487
|
-
};
|
|
488
796
|
req.body.context = 'web';
|
|
489
797
|
req.headers['x-forwarded-for'] = '198.51.100.7';
|
|
490
798
|
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
491
|
-
|
|
799
|
+
sessionCalls[0].opts.headers['x-user-ip'].should.equal('198.51.100.7');
|
|
492
800
|
});
|
|
493
801
|
|
|
494
802
|
it('should return error if post returns error', async () => {
|
|
495
|
-
|
|
496
|
-
post: (_url, _opts, _cb) => { return _cb(new Error('Error'), null); },
|
|
497
|
-
};
|
|
498
|
-
|
|
499
|
-
server.externalServices.transak.request = fakeRequest2;
|
|
803
|
+
sessionError = new Error('Error');
|
|
500
804
|
try {
|
|
501
805
|
await server.externalServices.transak.transakGetSignedPaymentUrl(req);
|
|
502
806
|
should.fail('should have thrown');
|
|
@@ -520,37 +824,147 @@ describe('Transak integration', () => {
|
|
|
520
824
|
});
|
|
521
825
|
|
|
522
826
|
describe('#transakGetOrderDetails', () => {
|
|
523
|
-
|
|
827
|
+
let ownedAddress: string;
|
|
828
|
+
|
|
829
|
+
beforeEach(async () => {
|
|
524
830
|
req = {
|
|
525
831
|
headers: {},
|
|
526
832
|
body: {
|
|
527
833
|
env: 'sandbox',
|
|
528
|
-
orderId: '
|
|
529
|
-
accessToken: 'accessToken1',
|
|
834
|
+
orderId: '6f1d2c3b-1a2b-4c5d-8e9f-0a1b2c3d4e5f',
|
|
530
835
|
}
|
|
531
836
|
};
|
|
532
837
|
server.externalServices.transak.request = fakeRequest;
|
|
838
|
+
ownedAddress = (await util.promisify(server.createAddress).call(server, {})).address;
|
|
839
|
+
orderBody = { data: { walletAddress: ownedAddress, status: 'PROCESSING' } };
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
it('should return an order for an address of the requesting wallet', async () => {
|
|
843
|
+
const data = await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
844
|
+
data.should.deep.equal({ data: { walletAddress: ownedAddress, status: 'PROCESSING' } });
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
it('should not return another wallet\'s order', async () => {
|
|
848
|
+
const { server: otherServer } = await helpers.createAndJoinWallet(1, 1, { offset: 1 });
|
|
849
|
+
const otherAddress = (await util.promisify(otherServer.createAddress).call(otherServer, {})).address;
|
|
850
|
+
orderBody = { data: { walletAddress: otherAddress, status: 'PROCESSING' } };
|
|
851
|
+
try {
|
|
852
|
+
await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
853
|
+
should.fail('should have thrown');
|
|
854
|
+
} catch (err) {
|
|
855
|
+
err.message.should.equal('Transak order not found');
|
|
856
|
+
}
|
|
857
|
+
// The owner can still read it.
|
|
858
|
+
otherServer.externalServices.transak.request = fakeRequest;
|
|
859
|
+
const data = await otherServer.externalServices.transak.transakGetOrderDetails({ headers: {}, body: { ...req.body } }, otherServer.walletId);
|
|
860
|
+
data.data.walletAddress.should.equal(otherAddress);
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
it('should not return an order without a wallet address', async () => {
|
|
864
|
+
orderBody = { data: { status: 'PROCESSING' } };
|
|
865
|
+
try {
|
|
866
|
+
await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
867
|
+
should.fail('should have thrown');
|
|
868
|
+
} catch (err) {
|
|
869
|
+
err.message.should.equal('Transak order not found');
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
it('should not return an order without a requesting wallet', async () => {
|
|
874
|
+
try {
|
|
875
|
+
await server.externalServices.transak.transakGetOrderDetails(req, undefined);
|
|
876
|
+
should.fail('should have thrown');
|
|
877
|
+
} catch (err) {
|
|
878
|
+
err.message.should.equal('Transak order not found');
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
it('should match an EVM order address in any case', async () => {
|
|
883
|
+
const { server: ethServer } = await helpers.createAndJoinWallet(1, 1, { coin: 'eth', offset: 1 });
|
|
884
|
+
ethServer.externalServices.transak.request = fakeRequest;
|
|
885
|
+
const ethAddress = (await util.promisify(ethServer.createAddress).call(ethServer, {})).address;
|
|
886
|
+
ethAddress.should.not.equal(ethAddress.toLowerCase());
|
|
887
|
+
for (const walletAddress of [ethAddress.toLowerCase(), ethAddress.toUpperCase().replace('0X', '0x')]) {
|
|
888
|
+
orderBody = { data: { walletAddress, status: 'PROCESSING' } };
|
|
889
|
+
const data = await ethServer.externalServices.transak.transakGetOrderDetails({ headers: {}, body: { ...req.body } }, ethServer.walletId);
|
|
890
|
+
data.data.walletAddress.should.equal(walletAddress);
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
it('should pass through a Transak error without an ownership check', async () => {
|
|
895
|
+
orderStatuses = [400];
|
|
896
|
+
orderBody = { error: { statusCode: 400, message: 'Invalid orderId' } };
|
|
897
|
+
const data = await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
898
|
+
data.should.deep.equal({ error: { statusCode: 400, message: 'Invalid orderId' } });
|
|
533
899
|
});
|
|
534
900
|
|
|
535
901
|
it('should work properly if req is OK', async () => {
|
|
536
|
-
const data = await server.externalServices.transak.transakGetOrderDetails(req);
|
|
902
|
+
const data = await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
537
903
|
should.exist(data);
|
|
538
904
|
});
|
|
539
905
|
|
|
540
906
|
it('should work properly if req is OK for web', async () => {
|
|
541
907
|
req.body.context = 'web';
|
|
542
|
-
const data = await server.externalServices.transak.transakGetOrderDetails(req);
|
|
908
|
+
const data = await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
543
909
|
should.exist(data);
|
|
544
910
|
});
|
|
545
911
|
|
|
546
|
-
it('should
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
912
|
+
it('should use the server-side partner token and ignore any client accessToken', async () => {
|
|
913
|
+
req.body.accessToken = 'clientSuppliedToken';
|
|
914
|
+
await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
915
|
+
orderCalls.length.should.equal(1);
|
|
916
|
+
orderCalls[0].url.should.equal('api1/partners/api/v2/order/6f1d2c3b-1a2b-4c5d-8e9f-0a1b2c3d4e5f');
|
|
917
|
+
orderCalls[0].opts.headers['access-token'].should.equal(PARTNER_TOKEN);
|
|
918
|
+
});
|
|
550
919
|
|
|
551
|
-
|
|
920
|
+
it('should forward the deviceIp as x-user-ip for web context', async () => {
|
|
921
|
+
req.body.context = 'web';
|
|
922
|
+
req.body.deviceIp = '203.0.113.42';
|
|
923
|
+
req.headers['x-forwarded-for'] = '198.51.100.7';
|
|
924
|
+
await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
925
|
+
orderCalls[0].opts.headers['x-user-ip'].should.equal('203.0.113.42');
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
it('should ignore a body deviceIp for non-web context and use the request IP', async () => {
|
|
929
|
+
req.body.deviceIp = '203.0.113.42';
|
|
930
|
+
req.headers['x-forwarded-for'] = '198.51.100.7';
|
|
931
|
+
await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
932
|
+
orderCalls[0].opts.headers['x-user-ip'].should.equal('198.51.100.7');
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
it('should share the partner token with signedPaymentUrl', async () => {
|
|
936
|
+
await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
937
|
+
await server.externalServices.transak.transakGetSignedPaymentUrl({ headers: {}, body: { env: 'sandbox', ...appWidgetBody() } });
|
|
938
|
+
refreshCalls.length.should.equal(1);
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
it('should refresh and retry once when Transak rejects an older partner token', async () => {
|
|
942
|
+
tokens = ['staleToken', 'freshToken'];
|
|
943
|
+
await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
944
|
+
await updatePartnerTokenCache({ $set: { 'result.fetchedAt': Date.now() - 60 * 60 * 1000 } });
|
|
945
|
+
orderStatuses = [401, 200];
|
|
946
|
+
await server.externalServices.transak.transakGetOrderDetails({ headers: {}, body: { ...req.body } }, server.walletId);
|
|
947
|
+
orderCalls.map(c => c.opts.headers['access-token']).should.deep.equal(['staleToken', 'staleToken', 'freshToken']);
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
for (const orderId of ['../orders', '../orders?limit=100', 'abc/def', 'abc?x=1', 'a#b', '', 'a'.repeat(65)]) {
|
|
951
|
+
it(`should reject orderId ${JSON.stringify(orderId)} without calling Transak`, async () => {
|
|
952
|
+
req.body.orderId = orderId;
|
|
953
|
+
try {
|
|
954
|
+
await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
955
|
+
should.fail('should have thrown');
|
|
956
|
+
} catch (err) {
|
|
957
|
+
err.message.should.match(/Invalid Transak orderId|Transak's request missing arguments/);
|
|
958
|
+
}
|
|
959
|
+
orderCalls.length.should.equal(0);
|
|
960
|
+
refreshCalls.length.should.equal(0);
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
it('should return error if get returns error', async () => {
|
|
965
|
+
orderError = new Error('Error');
|
|
552
966
|
try {
|
|
553
|
-
await server.externalServices.transak.transakGetOrderDetails(req);
|
|
967
|
+
await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
554
968
|
should.fail('should have thrown');
|
|
555
969
|
} catch (err) {
|
|
556
970
|
loggerError.callCount.should.equal(1);
|
|
@@ -560,13 +974,9 @@ describe('Transak integration', () => {
|
|
|
560
974
|
});
|
|
561
975
|
|
|
562
976
|
it('should return error and not leak the response if body is empty', async () => {
|
|
563
|
-
|
|
564
|
-
get: (_url, opts, cb) => { return cb(null, { statusCode: 200, body: undefined, request: { headers: opts.headers } }); },
|
|
565
|
-
};
|
|
566
|
-
|
|
567
|
-
server.externalServices.transak.request = fakeRequest2;
|
|
977
|
+
orderBody = undefined;
|
|
568
978
|
try {
|
|
569
|
-
await server.externalServices.transak.transakGetOrderDetails(req);
|
|
979
|
+
await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
570
980
|
should.fail('should have thrown');
|
|
571
981
|
} catch (err) {
|
|
572
982
|
err.message.should.equal('Transak did not return order details');
|
|
@@ -576,11 +986,11 @@ describe('Transak integration', () => {
|
|
|
576
986
|
it('should return error if there is some missing arguments', async () => {
|
|
577
987
|
delete req.body.orderId;
|
|
578
988
|
try {
|
|
579
|
-
await server.externalServices.transak.transakGetOrderDetails(req);
|
|
989
|
+
await server.externalServices.transak.transakGetOrderDetails(req, server.walletId);
|
|
580
990
|
should.fail('should have thrown');
|
|
581
991
|
} catch (err) {
|
|
582
992
|
err.message.should.equal('Transak\'s request missing arguments');
|
|
583
993
|
}
|
|
584
994
|
});
|
|
585
995
|
});
|
|
586
|
-
});
|
|
996
|
+
});
|