@riocrypto/common-server 1.0.2910 → 1.0.2912
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.
|
@@ -17,6 +17,16 @@ const common_1 = require("@riocrypto/common");
|
|
|
17
17
|
const https_1 = __importDefault(require("https"));
|
|
18
18
|
const axios_with_logging_1 = require("./axios-with-logging");
|
|
19
19
|
const secret_manager_client_1 = require("./secret-manager-client");
|
|
20
|
+
/**
|
|
21
|
+
* Deadline for one FX price report.
|
|
22
|
+
*
|
|
23
|
+
* Generous enough for a round trip through the public ingress, and short
|
|
24
|
+
* enough that a stalled path costs a gap in reports rather than a
|
|
25
|
+
* publisher that never sends again. Comfortably inside the staleness
|
|
26
|
+
* thresholds market-data judges these feeds by, so an abandoned report
|
|
27
|
+
* does not by itself make a source look dead.
|
|
28
|
+
*/
|
|
29
|
+
const FX_OBSERVATION_TIMEOUT_MS = 5000;
|
|
20
30
|
class ClusterClient {
|
|
21
31
|
constructor(baseUrl, clusterApiKey) {
|
|
22
32
|
this.baseUrl = baseUrl;
|
|
@@ -1013,7 +1023,17 @@ class ClusterClient {
|
|
|
1013
1023
|
// means the report was recorded while another source is active.
|
|
1014
1024
|
reportFXPriceObservation(params) {
|
|
1015
1025
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1016
|
-
const response = yield this.axios.post(`${this.baseUrl}/api/market/data/fx-observation`, params, {
|
|
1026
|
+
const response = yield this.axios.post(`${this.baseUrl}/api/market/data/fx-observation`, params, {
|
|
1027
|
+
headers: { "x-cluster-api-key": this.clusterApiKey },
|
|
1028
|
+
// The shared axios instance has no default timeout, which is fine
|
|
1029
|
+
// for the request/response calls on this client but not for one
|
|
1030
|
+
// published on a fixed interval. Without a deadline a stalled
|
|
1031
|
+
// ingest path leaves requests open indefinitely and the publisher
|
|
1032
|
+
// accumulates them. Scoped to this call rather than set as a
|
|
1033
|
+
// client default, because several endpoints here are legitimately
|
|
1034
|
+
// slow and would start failing.
|
|
1035
|
+
timeout: FX_OBSERVATION_TIMEOUT_MS,
|
|
1036
|
+
});
|
|
1017
1037
|
return response.data;
|
|
1018
1038
|
});
|
|
1019
1039
|
}
|
|
@@ -44,7 +44,11 @@ class CurrencyAPIClient {
|
|
|
44
44
|
if (!cachedResponse) {
|
|
45
45
|
throw new common_1.GenericInternalError(`Unable to get exchange rate from currencyAPI for ${from} to ${to}`);
|
|
46
46
|
}
|
|
47
|
-
|
|
47
|
+
// Assigns the outer binding deliberately. Declaring a local here
|
|
48
|
+
// shadowed it, so the cached rate was computed and thrown away and
|
|
49
|
+
// the zero check below then threw, which made this whole fallback
|
|
50
|
+
// unreachable on the one path it exists for.
|
|
51
|
+
exchangeRate = cachedResponse.data[to].value;
|
|
48
52
|
}
|
|
49
53
|
if (!exchangeRate) {
|
|
50
54
|
(_a = logger_1.default.getLogger()) === null || _a === void 0 ? void 0 : _a.error("Unable to get exchange rate");
|
|
@@ -28,8 +28,10 @@ const pickByShare = (candidates, position) => {
|
|
|
28
28
|
// the only one available to them, which is why the candidate set comes from the
|
|
29
29
|
// corridor rather than from the rule. What keeps an order away from a rail is the
|
|
30
30
|
// customer, by switching it off or, while that direction is opt-in, by never
|
|
31
|
-
// turning it on.
|
|
32
|
-
//
|
|
31
|
+
// turning it on. Two exceptions: a split set to apply to all users divides the
|
|
32
|
+
// corridor by percentage alone and asks nobody, and a corridor the customer has
|
|
33
|
+
// no answer on record for at all is one they were never asked about, so silence
|
|
34
|
+
// there is not read as a refusal.
|
|
33
35
|
//
|
|
34
36
|
// Does no IO of its own - the routing config and the user's enablement are passed
|
|
35
37
|
// in - and the only nondeterminism is the per-order roll, which a caller can
|
|
@@ -118,13 +120,24 @@ const resolveProcessor = ({ country, fiat, purpose, routingConfig, excludedProce
|
|
|
118
120
|
const isGated = purpose !== common_1.ProcessorRoutingPurpose.BankAccountVerification;
|
|
119
121
|
const disabled = isGated ? (enablement === null || enablement === void 0 ? void 0 : enablement.disabled) || [] : [];
|
|
120
122
|
const unset = isGated && requireExplicitEnablement ? (enablement === null || enablement === void 0 ? void 0 : enablement.unset) || [] : [];
|
|
123
|
+
// Silence only means no in a corridor the customer has actually been asked
|
|
124
|
+
// about. Accounts are seeded and shown for the country they onboarded in, while
|
|
125
|
+
// a quote may name any corridor, so a corridor with nothing at all on record is
|
|
126
|
+
// one nobody has ever offered them rather than one they turned down. Refusing
|
|
127
|
+
// there would refuse a price over an account the customer has no page to go and
|
|
128
|
+
// turn on, so the gate stands down and the corridor routes as it did before
|
|
129
|
+
// opt-in. Anything they have answered, in either direction, puts the gate back.
|
|
130
|
+
const wasAsked = Boolean(enablement && (enablement.enabled.length || enablement.disabled.length));
|
|
121
131
|
// Recorded either way, and only acted on when the split leaves the choice with
|
|
122
132
|
// the customer. An order routed over their objection is the one that will be
|
|
123
133
|
// asked about later, so what they had said needs to survive on the decision.
|
|
124
134
|
const enablementOverridden = isGated && Boolean(rule === null || rule === void 0 ? void 0 : rule.appliesToAllUsers);
|
|
135
|
+
// Both sets are still reported below whether or not they were acted on, so a
|
|
136
|
+
// decision that routed past unspoken rails can be told from one that had none.
|
|
137
|
+
const gatedUnset = wasAsked ? unset : [];
|
|
125
138
|
const candidates = enablementOverridden
|
|
126
139
|
? eligible
|
|
127
|
-
: eligible.filter((processor) => !disabled.includes(processor) && !
|
|
140
|
+
: eligible.filter((processor) => !disabled.includes(processor) && !gatedUnset.includes(processor));
|
|
128
141
|
// What was filtered, carried by every decision below. Kept in one place so a
|
|
129
142
|
// branch cannot quietly stop reporting one of them, since these sets are the
|
|
130
143
|
// whole reason a decision can still be explained weeks later.
|
|
@@ -136,7 +149,7 @@ const resolveProcessor = ({ country, fiat, purpose, routingConfig, excludedProce
|
|
|
136
149
|
// their rails off and can turn one back on; the other has never told us
|
|
137
150
|
// which account their bank will let them pay, and needs to whitelist one
|
|
138
151
|
// before anything can be routed.
|
|
139
|
-
reason:
|
|
152
|
+
reason: gatedUnset.length
|
|
140
153
|
? common_1.RoutingDecisionReason.NoAcceptedCandidate
|
|
141
154
|
: common_1.RoutingDecisionReason.NoEnabledCandidate }, filtered), { decidedAt });
|
|
142
155
|
}
|