@riocrypto/common-server 1.0.2926 → 1.0.2928
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.
|
@@ -8,72 +8,140 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
+
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
12
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
13
|
+
var m = o[Symbol.asyncIterator], i;
|
|
14
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
15
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
16
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
17
|
+
};
|
|
11
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
19
|
exports.getOrderFromReference = void 0;
|
|
13
20
|
const order_1 = require("../models/order");
|
|
14
21
|
const common_1 = require("@riocrypto/common");
|
|
22
|
+
// A reference is 8 hex characters of the order id (two from the random bytes
|
|
23
|
+
// plus the last six); older orders were referenced by the last six alone. The
|
|
24
|
+
// payer types it into a concepto that can carry anything else around it, so
|
|
25
|
+
// matching has always been "does the text contain the token", and that is what
|
|
26
|
+
// is preserved here.
|
|
27
|
+
const NEW_FORMAT_LENGTH = 8;
|
|
28
|
+
const OLD_FORMAT_LENGTH = 6;
|
|
29
|
+
// Statuses a payment can no longer be applied to. Anything failed is included
|
|
30
|
+
// by name so that a status added later is excluded without a change here.
|
|
31
|
+
const excludedStatuses = Object.values(common_1.OrderStatus).filter((status) => {
|
|
32
|
+
const statusLower = status.toLowerCase();
|
|
33
|
+
return (statusLower === "complete" ||
|
|
34
|
+
statusLower === "cancelled" ||
|
|
35
|
+
statusLower === "expired" ||
|
|
36
|
+
statusLower === "refundcomplete" ||
|
|
37
|
+
statusLower.includes("failed"));
|
|
38
|
+
});
|
|
39
|
+
const HEX_DIGITS = "0123456789abcdef";
|
|
40
|
+
// Every window of `length` hex characters in the text, lowercase and in the
|
|
41
|
+
// order they appear. A reference has to be a run of hex, so anything else in
|
|
42
|
+
// the concepto is a boundary rather than a candidate.
|
|
43
|
+
const hexWindows = (text, length) => {
|
|
44
|
+
var _a;
|
|
45
|
+
const windows = new Set();
|
|
46
|
+
for (const run of (_a = text.toLowerCase().match(/[0-9a-f]+/g)) !== null && _a !== void 0 ? _a : []) {
|
|
47
|
+
for (let i = 0; i + length <= run.length; i += 1) {
|
|
48
|
+
windows.add(run.slice(i, i + length));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return Array.from(windows);
|
|
52
|
+
};
|
|
53
|
+
// A concepto is short, so a bound this size is never reached by a real payment.
|
|
54
|
+
// It is here so that a pathological text cannot turn the old-format fallback
|
|
55
|
+
// into a query with tens of thousands of keys.
|
|
56
|
+
const MAX_OLD_FORMAT_WINDOWS = 24;
|
|
15
57
|
const getOrderFromReference = (reference, mongoose) => __awaiter(void 0, void 0, void 0, function* () {
|
|
16
|
-
|
|
58
|
+
var _a, e_1, _b, _c;
|
|
17
59
|
if (!reference) {
|
|
18
60
|
throw new Error("Invalid reference. Reference cannot be empty.");
|
|
19
61
|
}
|
|
20
|
-
// Convert reference to lowercase to ensure case-insensitive comparison
|
|
21
|
-
const referenceText = reference.toLowerCase();
|
|
22
62
|
const Order = (0, order_1.buildOrder)(mongoose);
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
let skip = 0;
|
|
35
|
-
let foundOrder = null;
|
|
36
|
-
let foundOrderOldFormat = null;
|
|
37
|
-
while (!foundOrder) {
|
|
38
|
-
// Query only active orders (exclude completed/cancelled/failed) in batches
|
|
39
|
-
const orders = yield Order.find({
|
|
40
|
-
status: { $nin: excludedStatuses },
|
|
41
|
-
})
|
|
42
|
-
.select("_id") // Only select the ID field to minimize memory usage
|
|
43
|
-
.skip(skip)
|
|
44
|
-
.limit(batchSize)
|
|
45
|
-
.lean(); // Use lean() for better performance
|
|
46
|
-
// If no more orders, break the loop
|
|
47
|
-
if (orders.length === 0) {
|
|
48
|
-
break;
|
|
63
|
+
const activeStatus = { $nin: excludedStatuses };
|
|
64
|
+
// New format, exact and indexed. The stored reference is what the customer was
|
|
65
|
+
// shown, upper case, so the windows are compared the same way.
|
|
66
|
+
const newFormatCandidates = hexWindows(reference, NEW_FORMAT_LENGTH).map((window) => window.toUpperCase());
|
|
67
|
+
if (newFormatCandidates.length) {
|
|
68
|
+
const order = yield Order.findOne({
|
|
69
|
+
"paymentInstructions.reference": { $in: newFormatCandidates },
|
|
70
|
+
status: activeStatus,
|
|
71
|
+
}).sort({ createdAt: 1 });
|
|
72
|
+
if (order) {
|
|
73
|
+
return order;
|
|
49
74
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
75
|
+
}
|
|
76
|
+
const oldFormatWindows = hexWindows(reference, OLD_FORMAT_LENGTH).slice(0, MAX_OLD_FORMAT_WINDOWS);
|
|
77
|
+
// Buy orders that were never given a stored reference, from before the bank
|
|
78
|
+
// payment listener wrote one. Only these are still compared against the id in
|
|
79
|
+
// code. Walked once, with a cursor over a bounded query rather than paging
|
|
80
|
+
// through every active order with skip, and before the old-format lookup so a
|
|
81
|
+
// full match here still beats a six-character one anywhere, as it always has.
|
|
82
|
+
const newFormatWindowSet = new Set(newFormatCandidates.map((candidate) => candidate.toLowerCase()));
|
|
83
|
+
const oldFormatWindowSet = new Set(oldFormatWindows);
|
|
84
|
+
let legacyOldFormatMatchId = null;
|
|
85
|
+
const legacyOrders = Order.find({
|
|
86
|
+
status: activeStatus,
|
|
87
|
+
side: common_1.Side.Buy,
|
|
88
|
+
"paymentInstructions.reference": { $exists: false },
|
|
89
|
+
})
|
|
90
|
+
.select("_id")
|
|
91
|
+
.lean()
|
|
92
|
+
.cursor();
|
|
93
|
+
try {
|
|
94
|
+
for (var _d = true, legacyOrders_1 = __asyncValues(legacyOrders), legacyOrders_1_1; legacyOrders_1_1 = yield legacyOrders_1.next(), _a = legacyOrders_1_1.done, !_a;) {
|
|
95
|
+
_c = legacyOrders_1_1.value;
|
|
96
|
+
_d = false;
|
|
97
|
+
try {
|
|
98
|
+
const legacy = _c;
|
|
99
|
+
const orderId = legacy._id.toString().toLowerCase();
|
|
100
|
+
const reference8Chars = orderId.slice(8, 10) + orderId.slice(-6);
|
|
101
|
+
if (newFormatWindowSet.has(reference8Chars)) {
|
|
102
|
+
return Order.findById(legacy._id);
|
|
103
|
+
}
|
|
104
|
+
if (!legacyOldFormatMatchId && oldFormatWindowSet.has(orderId.slice(-6))) {
|
|
105
|
+
legacyOldFormatMatchId = legacy._id;
|
|
106
|
+
}
|
|
62
107
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
foundOrderOldFormat = yield Order.findById(order._id);
|
|
108
|
+
finally {
|
|
109
|
+
_d = true;
|
|
66
110
|
}
|
|
67
111
|
}
|
|
68
|
-
skip += batchSize;
|
|
69
112
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
113
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
114
|
+
finally {
|
|
115
|
+
try {
|
|
116
|
+
if (!_d && !_a && (_b = legacyOrders_1.return)) yield _b.call(legacyOrders_1);
|
|
117
|
+
}
|
|
118
|
+
finally { if (e_1) throw e_1.error; }
|
|
119
|
+
}
|
|
120
|
+
// Old format: the payer typed only the last six characters. Those are also the
|
|
121
|
+
// last six of a stored new-format reference, so each six-character window is
|
|
122
|
+
// widened to every two-character prefix it could sit behind and looked up on
|
|
123
|
+
// the same index. 256 keys per window keeps this a point lookup rather than a
|
|
124
|
+
// scan, which a regex with a leading wildcard could not be.
|
|
125
|
+
if (oldFormatWindows.length) {
|
|
126
|
+
const oldFormatCandidates = [];
|
|
127
|
+
for (const window of oldFormatWindows) {
|
|
128
|
+
for (const first of HEX_DIGITS) {
|
|
129
|
+
for (const second of HEX_DIGITS) {
|
|
130
|
+
oldFormatCandidates.push((first + second + window).toUpperCase());
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const order = yield Order.findOne({
|
|
135
|
+
"paymentInstructions.reference": { $in: oldFormatCandidates },
|
|
136
|
+
status: activeStatus,
|
|
137
|
+
}).sort({ createdAt: 1 });
|
|
138
|
+
if (order) {
|
|
139
|
+
return order;
|
|
140
|
+
}
|
|
73
141
|
}
|
|
74
|
-
if (
|
|
75
|
-
|
|
142
|
+
if (legacyOldFormatMatchId) {
|
|
143
|
+
return Order.findById(legacyOldFormatMatchId);
|
|
76
144
|
}
|
|
77
|
-
|
|
145
|
+
throw new Error("Order not found");
|
|
78
146
|
});
|
|
79
147
|
exports.getOrderFromReference = getOrderFromReference;
|
package/build/models/order.js
CHANGED
|
@@ -412,6 +412,11 @@ const buildOrder = (mongoose) => {
|
|
|
412
412
|
orderSchema.index({ status: 1, createdAt: 1 });
|
|
413
413
|
orderSchema.index({ clientReferenceId: 1 }, { sparse: true });
|
|
414
414
|
orderSchema.index({ "paymentsReceived.blockchainTxId": 1 }, { sparse: true });
|
|
415
|
+
// Every inbound bank deposit is matched to its order by the reference the
|
|
416
|
+
// customer typed. Before this, that meant loading every active order and
|
|
417
|
+
// comparing ids in code on each webhook, which under a burst of payments held
|
|
418
|
+
// the fintoc pod long enough for its liveness probe to kill it mid-deposit.
|
|
419
|
+
orderSchema.index({ "paymentInstructions.reference": 1 }, { sparse: true });
|
|
415
420
|
// At most one order per real quoteId. Imported orders use quoteId "" and are
|
|
416
421
|
// excluded by the partial filter (which also serves the createOrder lookup).
|
|
417
422
|
orderSchema.index({ quoteId: 1 }, { unique: true, partialFilterExpression: { quoteId: { $gt: "" } } });
|
package/build/services/logger.js
CHANGED
|
@@ -278,6 +278,22 @@ class LoggerService {
|
|
|
278
278
|
// Bearer tokens: Bearer <token>
|
|
279
279
|
/(\bBearer\s+)([^\s,;"]+)/gi,
|
|
280
280
|
];
|
|
281
|
+
// Cloud Logging reads a top level "severity" field from JSON stdout lines.
|
|
282
|
+
// Without it every stdout line is stored as INFO and stderr as ERROR.
|
|
283
|
+
this.gcpSeverity = (0, winston_1.format)((info) => {
|
|
284
|
+
var _a;
|
|
285
|
+
const severities = {
|
|
286
|
+
error: "ERROR",
|
|
287
|
+
warn: "WARNING",
|
|
288
|
+
info: "INFO",
|
|
289
|
+
http: "INFO",
|
|
290
|
+
verbose: "DEBUG",
|
|
291
|
+
debug: "DEBUG",
|
|
292
|
+
silly: "DEBUG",
|
|
293
|
+
};
|
|
294
|
+
info.severity = (_a = severities[info.level]) !== null && _a !== void 0 ? _a : "DEFAULT";
|
|
295
|
+
return info;
|
|
296
|
+
});
|
|
281
297
|
this.logger = null;
|
|
282
298
|
this.setupConsoleMasking();
|
|
283
299
|
this.setupStreamMasking();
|
|
@@ -453,11 +469,16 @@ class LoggerService {
|
|
|
453
469
|
service: process.env.OTEL_SERVICE_NAME,
|
|
454
470
|
}));
|
|
455
471
|
}
|
|
472
|
+
// One JSON line per log event. prettyPrint spreads each event over many
|
|
473
|
+
// stdout lines and Cloud Logging stores and bills every line as its own
|
|
474
|
+
// entry with roughly 800 bytes of metadata, which multiplied ingestion
|
|
475
|
+
// volume by 7x to 25x depending on the service.
|
|
476
|
+
const outputFormat = process.env.RIO_ENV === common_1.RioEnv.Local ? winston_1.format.prettyPrint() : winston_1.format.json();
|
|
456
477
|
this.logger = (0, winston_1.createLogger)({
|
|
457
478
|
level: logLevel,
|
|
458
479
|
format: winston_1.format.combine(winston_1.format.timestamp(), this.maskSensitiveData(), this.requestMethodFilter(), // Add the request method filter
|
|
459
480
|
this.handleCustomErrorFormat(), // Add the custom error format
|
|
460
|
-
|
|
481
|
+
this.gcpSeverity(), outputFormat),
|
|
461
482
|
transports: transportsArray,
|
|
462
483
|
});
|
|
463
484
|
});
|