@riocrypto/common-server 1.0.2897 → 1.0.2899
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/build/helpers/get-processor.d.ts +2 -1
- package/build/helpers/get-processor.js +10 -6
- package/build/helpers/processor-enablement.d.ts +2 -1
- package/build/helpers/processor-enablement.js +22 -22
- package/build/helpers/resolve-processor.js +26 -43
- package/build/models/rio-settings.d.ts +2 -0
- package/build/models/rio-settings.js +3 -0
- package/package.json +2 -2
|
@@ -3,7 +3,7 @@ export declare const getEnvironmentExcludedProcessors: (purpose: ProcessorRoutin
|
|
|
3
3
|
export type ProcessorSelection = RoutingDecision & {
|
|
4
4
|
processor: Processor;
|
|
5
5
|
};
|
|
6
|
-
export declare const getProcessor: ({ country, fiat, side, isBinanceRFQ, routingConfig, enablement, requireExplicitPayinEnablement, customRioBankAccount, }: {
|
|
6
|
+
export declare const getProcessor: ({ country, fiat, side, isBinanceRFQ, routingConfig, enablement, requireExplicitPayinEnablement, requireExplicitPayoutEnablement, customRioBankAccount, }: {
|
|
7
7
|
country: Country;
|
|
8
8
|
fiat: Fiat;
|
|
9
9
|
side: Side;
|
|
@@ -11,6 +11,7 @@ export declare const getProcessor: ({ country, fiat, side, isBinanceRFQ, routing
|
|
|
11
11
|
routingConfig?: ProcessorRoutingConfig | undefined;
|
|
12
12
|
enablement?: ProcessorEnablementState | undefined;
|
|
13
13
|
requireExplicitPayinEnablement?: boolean | undefined;
|
|
14
|
+
requireExplicitPayoutEnablement?: boolean | undefined;
|
|
14
15
|
customRioBankAccount?: {
|
|
15
16
|
processor?: Processor | undefined;
|
|
16
17
|
} | undefined;
|
|
@@ -27,7 +27,7 @@ const getEnvironmentExcludedProcessors = (purpose) => [
|
|
|
27
27
|
...(isSTPAvailable(purpose) ? [] : [common_1.Processor.SPEI_STP]),
|
|
28
28
|
];
|
|
29
29
|
exports.getEnvironmentExcludedProcessors = getEnvironmentExcludedProcessors;
|
|
30
|
-
const getProcessor = ({ country, fiat, side, isBinanceRFQ, routingConfig, enablement, requireExplicitPayinEnablement, customRioBankAccount, }) => {
|
|
30
|
+
const getProcessor = ({ country, fiat, side, isBinanceRFQ, routingConfig, enablement, requireExplicitPayinEnablement, requireExplicitPayoutEnablement, customRioBankAccount, }) => {
|
|
31
31
|
if (isBinanceRFQ === true) {
|
|
32
32
|
return {
|
|
33
33
|
processor: common_1.Processor.BinanceRFQ,
|
|
@@ -56,17 +56,21 @@ const getProcessor = ({ country, fiat, side, isBinanceRFQ, routingConfig, enable
|
|
|
56
56
|
routingConfig,
|
|
57
57
|
excludedProcessors,
|
|
58
58
|
enablement,
|
|
59
|
-
requireExplicitEnablement:
|
|
59
|
+
requireExplicitEnablement: side === common_1.Side.Buy
|
|
60
|
+
? requireExplicitPayinEnablement
|
|
61
|
+
: requireExplicitPayoutEnablement,
|
|
60
62
|
customRioBankAccount,
|
|
61
63
|
});
|
|
62
64
|
if (routed.processor) {
|
|
63
65
|
return Object.assign(Object.assign({}, routed), { processor: routed.processor });
|
|
64
66
|
}
|
|
65
|
-
// The customer has never told us which of these accounts
|
|
66
|
-
//
|
|
67
|
-
//
|
|
67
|
+
// The customer has never told us which of these accounts works for them, so
|
|
68
|
+
// there is nothing to route to. Says so in their terms, since the fix is
|
|
69
|
+
// theirs to make rather than something support has to escalate.
|
|
68
70
|
if (routed.reason === common_1.RoutingDecisionReason.NoAcceptedCandidate) {
|
|
69
|
-
throw new common_1.GenericInputError(
|
|
71
|
+
throw new common_1.GenericInputError(side === common_1.Side.Buy
|
|
72
|
+
? "You need to turn on a payment account for this currency before you can buy. You can do that from the payment accounts page, once your bank allows transfers to it."
|
|
73
|
+
: "You need to turn on a payout account for this currency before you can sell. You can do that from the payout accounts page.");
|
|
70
74
|
}
|
|
71
75
|
// Every rail in this corridor is switched off for this direction, so there is
|
|
72
76
|
// nowhere to send them - a rail at zero percent would still have taken the
|
|
@@ -30,9 +30,10 @@ export declare const setProcessorEnablement: ({ mongoose, userId, country, fiat,
|
|
|
30
30
|
adminId?: string | undefined;
|
|
31
31
|
requireExplicitEnablement?: boolean | undefined;
|
|
32
32
|
}) => Promise<void>;
|
|
33
|
-
export declare const
|
|
33
|
+
export declare const ensureDefaultEnablement: ({ mongoose, userId, country, fiat, side, }: {
|
|
34
34
|
mongoose: Mongoose;
|
|
35
35
|
userId: string;
|
|
36
36
|
country: Country;
|
|
37
37
|
fiat: Fiat;
|
|
38
|
+
side: Side;
|
|
38
39
|
}) => Promise<Processor | undefined>;
|
|
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.
|
|
12
|
+
exports.ensureDefaultEnablement = exports.setProcessorEnablement = exports.getProcessorEnablementState = exports.isProcessorEnabled = void 0;
|
|
13
13
|
const common_1 = require("@riocrypto/common");
|
|
14
14
|
const processor_readiness_1 = require("../models/processor-readiness");
|
|
15
15
|
const get_processor_1 = require("./get-processor");
|
|
@@ -20,22 +20,21 @@ const getPurpose = (side) => side === common_1.Side.Buy
|
|
|
20
20
|
: common_1.ProcessorRoutingPurpose.Sell;
|
|
21
21
|
// Whether one rail may carry one customer's orders in one direction.
|
|
22
22
|
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return (_b = (_a = enablement === null || enablement === void 0 ? void 0 : enablement[side]) === null || _a === void 0 ? void 0 : _a.isEnabled) !== null && _b !== void 0 ? _b : (side === common_1.Side.Sell || !requireExplicitEnablement);
|
|
30
|
-
};
|
|
23
|
+
// What silence means is the caller's to decide, by passing the setting for this
|
|
24
|
+
// direction: `requireExplicitPayinEnablement` on a buy and
|
|
25
|
+
// `requireExplicitPayoutEnablement` on a sell. On, a rail nobody has spoken about
|
|
26
|
+
// cannot carry the order; off, it can, which is how both directions behaved before
|
|
27
|
+
// enablement became opt-in.
|
|
28
|
+
const isProcessorEnabled = ({ enablement, side, requireExplicitEnablement, }) => { var _a, _b; return (_b = (_a = enablement === null || enablement === void 0 ? void 0 : enablement[side]) === null || _a === void 0 ? void 0 : _a.isEnabled) !== null && _b !== void 0 ? _b : !requireExplicitEnablement; };
|
|
31
29
|
exports.isProcessorEnabled = isProcessorEnabled;
|
|
32
30
|
// What a customer has said about each rail in one corridor and one direction.
|
|
33
31
|
//
|
|
34
32
|
// Reports the three sets rather than a verdict so that the policy lives in one
|
|
35
33
|
// place, in routing, instead of being decided here and again wherever a page
|
|
36
|
-
// needs to show the same thing.
|
|
37
|
-
//
|
|
38
|
-
//
|
|
34
|
+
// needs to show the same thing. Whether an unset rail counts as usable is that
|
|
35
|
+
// policy, and it belongs to the caller with the settings in hand. Only rails the
|
|
36
|
+
// corridor currently serves are reported, so a record left behind by a rail we no
|
|
37
|
+
// longer route to is ignored rather than resurrected.
|
|
39
38
|
const getProcessorEnablementState = ({ mongoose, userId, country, fiat, side, }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
40
39
|
const ProcessorReadiness = (0, processor_readiness_1.buildProcessorReadiness)(mongoose);
|
|
41
40
|
const records = yield ProcessorReadiness.find({
|
|
@@ -98,7 +97,7 @@ const setProcessorEnablement = ({ mongoose, userId, country, fiat, side, process
|
|
|
98
97
|
});
|
|
99
98
|
const stillUsable = [
|
|
100
99
|
...state.enabled,
|
|
101
|
-
...(
|
|
100
|
+
...(requireExplicitEnablement ? [] : state.unset),
|
|
102
101
|
].filter((candidate) => candidate !== processor);
|
|
103
102
|
if (!stillUsable.length) {
|
|
104
103
|
throw new common_1.GenericInputError(side === common_1.Side.Buy
|
|
@@ -124,9 +123,9 @@ const setProcessorEnablement = ({ mongoose, userId, country, fiat, side, process
|
|
|
124
123
|
}, { upsert: true });
|
|
125
124
|
});
|
|
126
125
|
exports.setProcessorEnablement = setProcessorEnablement;
|
|
127
|
-
// Gives a customer with
|
|
128
|
-
// have routed them to anyway, so opt-in never leaves somebody unable to
|
|
129
|
-
//
|
|
126
|
+
// Gives a customer with nothing turned on in one direction the rail their corridor
|
|
127
|
+
// would have routed them to anyway, so opt-in never leaves somebody unable to
|
|
128
|
+
// trade. Called when an onboarding is approved, and by the backfill for accounts
|
|
130
129
|
// that predate all of this.
|
|
131
130
|
//
|
|
132
131
|
// Skips anyone who has already turned a rail on, because their answer is better
|
|
@@ -135,13 +134,14 @@ exports.setProcessorEnablement = setProcessorEnablement;
|
|
|
135
134
|
// the corridor's own order, which is the rail routing would have picked for them
|
|
136
135
|
// before opt-in existed. Returns the rail it turned on, or nothing if it left
|
|
137
136
|
// them alone.
|
|
138
|
-
const
|
|
137
|
+
const ensureDefaultEnablement = ({ mongoose, userId, country, fiat, side, }) => __awaiter(void 0, void 0, void 0, function* () {
|
|
138
|
+
const purpose = getPurpose(side);
|
|
139
139
|
const state = yield (0, exports.getProcessorEnablementState)({
|
|
140
140
|
mongoose,
|
|
141
141
|
userId,
|
|
142
142
|
country,
|
|
143
143
|
fiat,
|
|
144
|
-
side
|
|
144
|
+
side,
|
|
145
145
|
});
|
|
146
146
|
if (state.enabled.length) {
|
|
147
147
|
return undefined;
|
|
@@ -150,10 +150,10 @@ const ensureDefaultPayinEnablement = ({ mongoose, userId, country, fiat, }) => _
|
|
|
150
150
|
// seeding a rail this deployment cannot reach would hand the customer an
|
|
151
151
|
// account nothing can route to and read as opt-in having blocked them.
|
|
152
152
|
const unreachable = [
|
|
153
|
-
...(0, get_processor_1.getEnvironmentExcludedProcessors)(
|
|
153
|
+
...(0, get_processor_1.getEnvironmentExcludedProcessors)(purpose),
|
|
154
154
|
...state.disabled,
|
|
155
155
|
];
|
|
156
|
-
const seed = (0, common_1.getDefaultProcessor)(country, fiat,
|
|
156
|
+
const seed = (0, common_1.getDefaultProcessor)(country, fiat, purpose, unreachable) ||
|
|
157
157
|
state.unset.find((candidate) => !unreachable.includes(candidate));
|
|
158
158
|
if (!seed || !state.unset.includes(seed)) {
|
|
159
159
|
return undefined;
|
|
@@ -163,10 +163,10 @@ const ensureDefaultPayinEnablement = ({ mongoose, userId, country, fiat, }) => _
|
|
|
163
163
|
userId,
|
|
164
164
|
country,
|
|
165
165
|
fiat,
|
|
166
|
-
side
|
|
166
|
+
side,
|
|
167
167
|
processor: seed,
|
|
168
168
|
isEnabled: true,
|
|
169
169
|
});
|
|
170
170
|
return seed;
|
|
171
171
|
});
|
|
172
|
-
exports.
|
|
172
|
+
exports.ensureDefaultEnablement = ensureDefaultEnablement;
|
|
@@ -27,7 +27,9 @@ const pickByShare = (candidates, position) => {
|
|
|
27
27
|
// belonging to a corridor with no rule at all still takes their orders when it is
|
|
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
|
-
// customer, by switching it off or,
|
|
30
|
+
// customer, by switching it off or, while that direction is opt-in, by never
|
|
31
|
+
// turning it on. Unless the rule says otherwise: a split set to apply to all
|
|
32
|
+
// users divides the corridor by percentage alone and asks nobody.
|
|
31
33
|
//
|
|
32
34
|
// Does no IO of its own - the routing config and the user's enablement are passed
|
|
33
35
|
// in - and the only nondeterminism is the per-order roll, which a caller can
|
|
@@ -110,28 +112,33 @@ const resolveProcessor = ({ country, fiat, purpose, routingConfig, excludedProce
|
|
|
110
112
|
// can refuse a wire from Bancrea as easily as a SPEI transfer from Fintoc.
|
|
111
113
|
//
|
|
112
114
|
// Verifications are exempt because they are ours to place against an account the
|
|
113
|
-
// customer gave us, not a rail they have to accept.
|
|
115
|
+
// customer gave us, not a rail they have to accept. So is a corridor whose split
|
|
116
|
+
// has been set to apply to everyone, which is the deliberate exception: see
|
|
117
|
+
// `appliesToAllUsers`.
|
|
114
118
|
const isGated = purpose !== common_1.ProcessorRoutingPurpose.BankAccountVerification;
|
|
115
119
|
const disabled = isGated ? (enablement === null || enablement === void 0 ? void 0 : enablement.disabled) || [] : [];
|
|
116
|
-
const unset = isGated && requireExplicitEnablement
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
+
const unset = isGated && requireExplicitEnablement ? (enablement === null || enablement === void 0 ? void 0 : enablement.unset) || [] : [];
|
|
121
|
+
// Recorded either way, and only acted on when the split leaves the choice with
|
|
122
|
+
// the customer. An order routed over their objection is the one that will be
|
|
123
|
+
// asked about later, so what they had said needs to survive on the decision.
|
|
124
|
+
const enablementOverridden = isGated && Boolean(rule === null || rule === void 0 ? void 0 : rule.appliesToAllUsers);
|
|
125
|
+
const candidates = enablementOverridden
|
|
126
|
+
? eligible
|
|
127
|
+
: eligible.filter((processor) => !disabled.includes(processor) && !unset.includes(processor));
|
|
128
|
+
// What was filtered, carried by every decision below. Kept in one place so a
|
|
129
|
+
// branch cannot quietly stop reporting one of them, since these sets are the
|
|
130
|
+
// whole reason a decision can still be explained weeks later.
|
|
131
|
+
const filtered = Object.assign({ corridor, excluded: excludedProcessors, disabled,
|
|
132
|
+
unset }, (enablementOverridden ? { enablementOverridden: true } : {}));
|
|
120
133
|
if (!candidates.length) {
|
|
121
|
-
return {
|
|
134
|
+
return Object.assign(Object.assign({
|
|
122
135
|
// Told apart because they are different conversations. One customer turned
|
|
123
136
|
// their rails off and can turn one back on; the other has never told us
|
|
124
137
|
// which account their bank will let them pay, and needs to whitelist one
|
|
125
138
|
// before anything can be routed.
|
|
126
139
|
reason: unset.length
|
|
127
140
|
? common_1.RoutingDecisionReason.NoAcceptedCandidate
|
|
128
|
-
: common_1.RoutingDecisionReason.NoEnabledCandidate,
|
|
129
|
-
corridor,
|
|
130
|
-
excluded: excludedProcessors,
|
|
131
|
-
disabled,
|
|
132
|
-
unset,
|
|
133
|
-
decidedAt,
|
|
134
|
-
};
|
|
141
|
+
: common_1.RoutingDecisionReason.NoEnabledCandidate }, filtered), { decidedAt });
|
|
135
142
|
}
|
|
136
143
|
const shares = candidates
|
|
137
144
|
.map((processor) => {
|
|
@@ -151,42 +158,18 @@ const resolveProcessor = ({ country, fiat, purpose, routingConfig, excludedProce
|
|
|
151
158
|
// position gets is predictable rather than rolled.
|
|
152
159
|
if (!shares.length) {
|
|
153
160
|
const fallback = (0, common_1.getDefaultProcessor)(country, fiat, purpose, excludedProcessors);
|
|
154
|
-
return {
|
|
155
|
-
processor: fallback && candidates.includes(fallback) ? fallback : candidates[0],
|
|
156
|
-
reason: common_1.RoutingDecisionReason.EnabledRailWithoutShare,
|
|
157
|
-
corridor,
|
|
158
|
-
excluded: excludedProcessors,
|
|
159
|
-
disabled,
|
|
160
|
-
unset,
|
|
161
|
-
decidedAt,
|
|
162
|
-
};
|
|
161
|
+
return Object.assign(Object.assign({ processor: fallback && candidates.includes(fallback) ? fallback : candidates[0], reason: common_1.RoutingDecisionReason.EnabledRailWithoutShare }, filtered), { decidedAt });
|
|
163
162
|
}
|
|
164
163
|
if (shares.length === 1) {
|
|
165
|
-
return {
|
|
166
|
-
|
|
167
|
-
reason: common_1.RoutingDecisionReason.OnlyCandidate,
|
|
168
|
-
corridor,
|
|
169
|
-
excluded: excludedProcessors,
|
|
170
|
-
disabled,
|
|
171
|
-
unset,
|
|
172
|
-
percentages,
|
|
173
|
-
decidedAt,
|
|
174
|
-
};
|
|
164
|
+
return Object.assign(Object.assign({ processor: shares[0].processor, reason: common_1.RoutingDecisionReason.OnlyCandidate }, filtered), { percentages,
|
|
165
|
+
decidedAt });
|
|
175
166
|
}
|
|
176
167
|
// Rolled per order, so the realized share converges on the configured
|
|
177
168
|
// percentages by order count. Nothing is persisted here: a caller that needs
|
|
178
169
|
// the same answer twice passes back the seed it used.
|
|
179
170
|
const seed = randomSeed !== null && randomSeed !== void 0 ? randomSeed : Math.random();
|
|
180
|
-
return {
|
|
181
|
-
processor: pickByShare(shares, seed),
|
|
182
|
-
reason: common_1.RoutingDecisionReason.Split,
|
|
183
|
-
corridor,
|
|
184
|
-
excluded: excludedProcessors,
|
|
185
|
-
disabled,
|
|
186
|
-
unset,
|
|
187
|
-
percentages,
|
|
171
|
+
return Object.assign(Object.assign({ processor: pickByShare(shares, seed), reason: common_1.RoutingDecisionReason.Split }, filtered), { percentages,
|
|
188
172
|
seed,
|
|
189
|
-
decidedAt
|
|
190
|
-
};
|
|
173
|
+
decidedAt });
|
|
191
174
|
};
|
|
192
175
|
exports.resolveProcessor = resolveProcessor;
|
|
@@ -56,6 +56,7 @@ interface RioSettingsAttrs {
|
|
|
56
56
|
fxTradingPolicies?: FXTradingPolicies;
|
|
57
57
|
processorRouting?: ProcessorRoutingConfig;
|
|
58
58
|
requireExplicitPayinEnablement?: boolean;
|
|
59
|
+
requireExplicitPayoutEnablement?: boolean;
|
|
59
60
|
TVFXDataProvider: {
|
|
60
61
|
[key in Fiat]: TVFXDataProvider;
|
|
61
62
|
};
|
|
@@ -151,6 +152,7 @@ interface RioSettingsDoc extends mongoose.Document {
|
|
|
151
152
|
fxTradingPolicies?: FXTradingPolicies;
|
|
152
153
|
processorRouting?: ProcessorRoutingConfig;
|
|
153
154
|
requireExplicitPayinEnablement?: boolean;
|
|
155
|
+
requireExplicitPayoutEnablement?: boolean;
|
|
154
156
|
TVFXDataProvider: {
|
|
155
157
|
[key in Fiat]: TVFXDataProvider;
|
|
156
158
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@riocrypto/common-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2899",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@google-cloud/secret-manager": "^5.6.0",
|
|
29
29
|
"@google-cloud/storage": "^7.19.0",
|
|
30
30
|
"@hyperdx/node-opentelemetry": "^0.10.3",
|
|
31
|
-
"@riocrypto/common": "1.0.
|
|
31
|
+
"@riocrypto/common": "1.0.2711",
|
|
32
32
|
"@slack/web-api": "^7.15.0",
|
|
33
33
|
"@types/express": "^4.17.25",
|
|
34
34
|
"axios": "1.18.1",
|