@riocrypto/common-server 1.0.2927 → 1.0.2929
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,125 @@ 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
|
+
// Every window of `length` hex characters in the text, lowercase and in the
|
|
40
|
+
// order they appear. A reference has to be a run of hex, so anything else in
|
|
41
|
+
// the concepto is a boundary rather than a candidate.
|
|
42
|
+
const hexWindows = (text, length) => {
|
|
43
|
+
var _a;
|
|
44
|
+
const windows = new Set();
|
|
45
|
+
for (const run of (_a = text.toLowerCase().match(/[0-9a-f]+/g)) !== null && _a !== void 0 ? _a : []) {
|
|
46
|
+
for (let i = 0; i + length <= run.length; i += 1) {
|
|
47
|
+
windows.add(run.slice(i, i + length));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return Array.from(windows);
|
|
51
|
+
};
|
|
15
52
|
const getOrderFromReference = (reference, mongoose) => __awaiter(void 0, void 0, void 0, function* () {
|
|
16
|
-
|
|
53
|
+
var _a, e_1, _b, _c;
|
|
17
54
|
if (!reference) {
|
|
18
55
|
throw new Error("Invalid reference. Reference cannot be empty.");
|
|
19
56
|
}
|
|
20
|
-
// Convert reference to lowercase to ensure case-insensitive comparison
|
|
21
|
-
const referenceText = reference.toLowerCase();
|
|
22
57
|
const Order = (0, order_1.buildOrder)(mongoose);
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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;
|
|
58
|
+
const activeStatus = { $nin: excludedStatuses };
|
|
59
|
+
// New format, exact and indexed. The stored reference is what the customer was
|
|
60
|
+
// shown, upper case, so the windows are compared the same way.
|
|
61
|
+
const newFormatCandidates = hexWindows(reference, NEW_FORMAT_LENGTH).map((window) => window.toUpperCase());
|
|
62
|
+
const oldFormatWindows = hexWindows(reference, OLD_FORMAT_LENGTH);
|
|
63
|
+
// A reference is a run of hex, so text without one cannot contain any order's
|
|
64
|
+
// token in either format. Static references (SPR…) and free text end here
|
|
65
|
+
// instead of walking the active orders to confirm what is already known.
|
|
66
|
+
if (!newFormatCandidates.length && !oldFormatWindows.length) {
|
|
67
|
+
throw new Error("Order not found");
|
|
68
|
+
}
|
|
69
|
+
if (newFormatCandidates.length) {
|
|
70
|
+
const order = yield Order.findOne({
|
|
71
|
+
"paymentInstructions.reference": { $in: newFormatCandidates },
|
|
72
|
+
status: activeStatus,
|
|
73
|
+
}).sort({ createdAt: 1 });
|
|
74
|
+
if (order) {
|
|
75
|
+
return order;
|
|
49
76
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
77
|
+
}
|
|
78
|
+
// The indexed lookup missed, so the token is compared against each active buy
|
|
79
|
+
// order's id the way it always was. This still catches an order whose stored
|
|
80
|
+
// reference is not its own token, such as one paid through a bulk, and one
|
|
81
|
+
// that was never given a stored reference at all. It runs only on a miss, so a
|
|
82
|
+
// burst of valid payments never reaches it, and it is a cursor with an _id
|
|
83
|
+
// projection rather than skip pagination over the same set. Placed before the
|
|
84
|
+
// old-format lookup so a full match here still beats a six-character one
|
|
85
|
+
// anywhere, as it always has.
|
|
86
|
+
const newFormatWindowSet = new Set(newFormatCandidates.map((candidate) => candidate.toLowerCase()));
|
|
87
|
+
const oldFormatWindowSet = new Set(oldFormatWindows);
|
|
88
|
+
let scannedOldFormatMatchId = null;
|
|
89
|
+
const activeBuyOrders = Order.find({
|
|
90
|
+
status: activeStatus,
|
|
91
|
+
side: common_1.Side.Buy,
|
|
92
|
+
})
|
|
93
|
+
.select("_id")
|
|
94
|
+
.lean()
|
|
95
|
+
.cursor();
|
|
96
|
+
try {
|
|
97
|
+
for (var _d = true, activeBuyOrders_1 = __asyncValues(activeBuyOrders), activeBuyOrders_1_1; activeBuyOrders_1_1 = yield activeBuyOrders_1.next(), _a = activeBuyOrders_1_1.done, !_a;) {
|
|
98
|
+
_c = activeBuyOrders_1_1.value;
|
|
99
|
+
_d = false;
|
|
100
|
+
try {
|
|
101
|
+
const candidate = _c;
|
|
102
|
+
const orderId = candidate._id.toString().toLowerCase();
|
|
103
|
+
const reference8Chars = orderId.slice(8, 10) + orderId.slice(-6);
|
|
104
|
+
if (newFormatWindowSet.has(reference8Chars)) {
|
|
105
|
+
return Order.findById(candidate._id);
|
|
106
|
+
}
|
|
107
|
+
if (!scannedOldFormatMatchId &&
|
|
108
|
+
oldFormatWindowSet.has(orderId.slice(-6))) {
|
|
109
|
+
scannedOldFormatMatchId = candidate._id;
|
|
110
|
+
}
|
|
62
111
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
foundOrderOldFormat = yield Order.findById(order._id);
|
|
112
|
+
finally {
|
|
113
|
+
_d = true;
|
|
66
114
|
}
|
|
67
115
|
}
|
|
68
|
-
skip += batchSize;
|
|
69
116
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
117
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
118
|
+
finally {
|
|
119
|
+
try {
|
|
120
|
+
if (!_d && !_a && (_b = activeBuyOrders_1.return)) yield _b.call(activeBuyOrders_1);
|
|
121
|
+
}
|
|
122
|
+
finally { if (e_1) throw e_1.error; }
|
|
73
123
|
}
|
|
74
|
-
|
|
75
|
-
|
|
124
|
+
// Old format: the payer typed only the last six characters, and the scan above
|
|
125
|
+
// found an order ending in them. Used only once no order anywhere matched the
|
|
126
|
+
// full token.
|
|
127
|
+
if (scannedOldFormatMatchId) {
|
|
128
|
+
return Order.findById(scannedOldFormatMatchId);
|
|
76
129
|
}
|
|
77
|
-
|
|
130
|
+
throw new Error("Order not found");
|
|
78
131
|
});
|
|
79
132
|
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: "" } } });
|