@nehorai/credits 2.2.0 → 3.1.0
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/dist/adapters/index.cjs +169 -155
- package/dist/adapters/index.cjs.map +1 -1
- package/dist/adapters/index.d.cts +2 -2
- package/dist/adapters/index.d.ts +2 -2
- package/dist/adapters/index.js +169 -155
- package/dist/adapters/index.js.map +1 -1
- package/dist/core/index.cjs +148 -24
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +113 -22
- package/dist/core/index.d.ts +113 -22
- package/dist/core/index.js +137 -22
- package/dist/core/index.js.map +1 -1
- package/dist/index-BrogG0Gc.d.cts +277 -0
- package/dist/index-DOvJES2m.d.ts +277 -0
- package/dist/index.cjs +324 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +311 -69
- package/dist/index.js.map +1 -1
- package/dist/repository/index.cjs +254 -49
- package/dist/repository/index.cjs.map +1 -1
- package/dist/repository/index.d.cts +3 -254
- package/dist/repository/index.d.ts +3 -254
- package/dist/repository/index.js +254 -49
- package/dist/repository/index.js.map +1 -1
- package/dist/sdk/index.cjs +1 -0
- package/dist/sdk/index.cjs.map +1 -1
- package/dist/sdk/index.d.cts +2 -2
- package/dist/sdk/index.d.ts +2 -2
- package/dist/sdk/index.js +1 -0
- package/dist/sdk/index.js.map +1 -1
- package/dist/service/index.cjs +42 -19
- package/dist/service/index.cjs.map +1 -1
- package/dist/service/index.d.cts +2 -2
- package/dist/service/index.d.ts +2 -2
- package/dist/service/index.js +42 -19
- package/dist/service/index.js.map +1 -1
- package/dist/{types-3c-IAwZI.d.ts → types-BL3YqxUD.d.ts} +28 -6
- package/dist/{types-MaViK5tD.d.cts → types-C3sxDvNb.d.cts} +29 -6
- package/dist/{types-MaViK5tD.d.ts → types-C3sxDvNb.d.ts} +29 -6
- package/dist/{types-XSjNXO5l.d.cts → types-YLAB8bFk.d.cts} +28 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -167,6 +167,40 @@ function floatSum(values) {
|
|
|
167
167
|
for (const value of values) total += value;
|
|
168
168
|
return total;
|
|
169
169
|
}
|
|
170
|
+
function multiplyAmount(unit, quantity) {
|
|
171
|
+
const cents = toCents(unit);
|
|
172
|
+
const product = cents === null ? Number.NaN : cents * quantity;
|
|
173
|
+
if (cents === null || cents < 0 || !Number.isSafeInteger(quantity) || quantity < 0 || !Number.isSafeInteger(product) || product > MAX_CENTS) {
|
|
174
|
+
throw new CreditError(
|
|
175
|
+
`Cannot price ${quantity} units at ${unit}: the price must be a non-negative amount exact to ${CREDIT_AMOUNT_SCALE} decimals, the quantity a whole number, and the result at most ${CREDIT_AMOUNT_MAX}`,
|
|
176
|
+
CreditErrorCode.INVALID_AMOUNT,
|
|
177
|
+
{ unit, quantity, max: CREDIT_AMOUNT_MAX, scale: CREDIT_AMOUNT_SCALE }
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
return product / 100;
|
|
181
|
+
}
|
|
182
|
+
var FLOAT_NOISE_FLOOR = 1e-6;
|
|
183
|
+
var FLOAT_NOISE_RELATIVE = 64 * Number.EPSILON;
|
|
184
|
+
function floatNoise(value) {
|
|
185
|
+
return Math.max(FLOAT_NOISE_FLOOR, Math.abs(value) * FLOAT_NOISE_RELATIVE);
|
|
186
|
+
}
|
|
187
|
+
function canonicalAmount(value) {
|
|
188
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return value;
|
|
189
|
+
const cents = Math.round(value * 100);
|
|
190
|
+
if (!Number.isSafeInteger(cents)) return value;
|
|
191
|
+
const onGrid = cents / 100 + 0;
|
|
192
|
+
return Math.abs(value - onGrid) < floatNoise(value) ? onGrid : value;
|
|
193
|
+
}
|
|
194
|
+
function storedAmountFloor(value) {
|
|
195
|
+
const canonical = canonicalAmount(value);
|
|
196
|
+
if (!Number.isFinite(canonical) || toCents(canonical) !== null) return canonical;
|
|
197
|
+
return Math.floor(canonical * 100) / 100;
|
|
198
|
+
}
|
|
199
|
+
function storedAmountCeil(value) {
|
|
200
|
+
const canonical = canonicalAmount(value);
|
|
201
|
+
if (!Number.isFinite(canonical) || toCents(canonical) !== null) return canonical;
|
|
202
|
+
return Math.ceil(canonical * 100) / 100;
|
|
203
|
+
}
|
|
170
204
|
function isValidCreditAmount(value) {
|
|
171
205
|
if (typeof value !== "number") return false;
|
|
172
206
|
const cents = toCents(value);
|
|
@@ -222,14 +256,19 @@ function backedBalanceFloor(reserved, bonusCredits) {
|
|
|
222
256
|
function cappedBalance(balance, cap, reserved, bonusCredits) {
|
|
223
257
|
return Math.min(balance, Math.max(cap, backedBalanceFloor(reserved, bonusCredits)));
|
|
224
258
|
}
|
|
225
|
-
function
|
|
226
|
-
|
|
259
|
+
function heldExpiredBacking(balance, stored) {
|
|
260
|
+
if (!(stored !== void 0 && stored > 0)) return 0;
|
|
261
|
+
return Math.max(0, Math.min(stored, balance));
|
|
262
|
+
}
|
|
263
|
+
function spendableBalance(balance, expiredHoldBacking) {
|
|
264
|
+
return sumAmounts(balance, -heldExpiredBacking(balance, expiredHoldBacking));
|
|
227
265
|
}
|
|
228
266
|
function planBalanceTarget(target, reserved, bonusCredits) {
|
|
267
|
+
assertValidBalanceTarget(target, "balance target", { reserved, bonusCredits });
|
|
229
268
|
const balance = Math.max(target, backedBalanceFloor(reserved, bonusCredits));
|
|
230
269
|
return { balance, expiredHoldBacking: sumAmounts(balance, -target) };
|
|
231
270
|
}
|
|
232
|
-
function planBalanceCap(balance, cap, reserved, bonusCredits, expiredHoldBacking
|
|
271
|
+
function planBalanceCap(balance, cap, reserved, bonusCredits, expiredHoldBacking) {
|
|
233
272
|
const spendable = spendableBalance(balance, expiredHoldBacking);
|
|
234
273
|
const target = Math.min(spendable, cap);
|
|
235
274
|
const kept = Math.max(
|
|
@@ -240,8 +279,8 @@ function planBalanceCap(balance, cap, reserved, bonusCredits, expiredHoldBacking
|
|
|
240
279
|
return { balance: next, expiredHoldBacking: Math.max(0, sumAmounts(next, -target)) };
|
|
241
280
|
}
|
|
242
281
|
function settleExpiredHoldBacking(balance, expiredHoldBacking, holdBacking) {
|
|
243
|
-
const held =
|
|
244
|
-
const used = Math.min(held, holdBacking);
|
|
282
|
+
const held = heldExpiredBacking(balance, expiredHoldBacking);
|
|
283
|
+
const used = Math.max(0, Math.min(held, holdBacking));
|
|
245
284
|
return { used, remaining: sumAmounts(held, -used) };
|
|
246
285
|
}
|
|
247
286
|
function planHoldCommit(balance, bonusCredits, expiredHoldBacking, holdBacking, amount) {
|
|
@@ -256,27 +295,83 @@ function planHoldCommit(balance, bonusCredits, expiredHoldBacking, holdBacking,
|
|
|
256
295
|
expiredHoldBacking: backing.remaining
|
|
257
296
|
};
|
|
258
297
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
298
|
+
var NO_EXPIRED_BACKING_PREFIX = {
|
|
299
|
+
holdPlacedAt: "",
|
|
300
|
+
id: "",
|
|
301
|
+
share: 0
|
|
302
|
+
};
|
|
303
|
+
function compareHolds(a, b) {
|
|
304
|
+
if (a.holdPlacedAt !== b.holdPlacedAt) return a.holdPlacedAt < b.holdPlacedAt ? -1 : 1;
|
|
305
|
+
if (a.id === b.id) return 0;
|
|
306
|
+
return a.id < b.id ? -1 : 1;
|
|
307
|
+
}
|
|
308
|
+
function recordedExpiredBacking(hold) {
|
|
309
|
+
const stored = hold.expiredBacking;
|
|
310
|
+
if (typeof stored !== "number" || !Number.isFinite(stored) || stored <= 0) return 0;
|
|
311
|
+
if (!isValidCreditAmount(hold.amount)) return 0;
|
|
312
|
+
return Math.min(storedAmountCeil(stored), hold.amount);
|
|
313
|
+
}
|
|
314
|
+
function allocateExpiredBacking(holds, total) {
|
|
315
|
+
const placed = holds.filter((hold) => hold.holdPlacedAt && isValidCreditAmount(hold.amount)).map((hold) => ({ ...hold, holdPlacedAt: hold.holdPlacedAt })).sort(compareHolds);
|
|
316
|
+
const wanted = Math.max(0, total);
|
|
317
|
+
if (wanted === 0) return { total: 0, frontier: null };
|
|
318
|
+
const recorded = placed.filter((hold) => recordedExpiredBacking(hold) > 0);
|
|
319
|
+
const shares = new Map(recorded.map((hold) => [hold.id, recordedExpiredBacking(hold)]));
|
|
320
|
+
let left = sumAmounts(wanted, ...[...shares.values()].map((share) => -share));
|
|
321
|
+
for (const hold of [...recorded].reverse()) {
|
|
322
|
+
if (left >= 0) break;
|
|
323
|
+
const share = shares.get(hold.id);
|
|
324
|
+
const cut = Math.min(share, -left);
|
|
325
|
+
shares.set(hold.id, sumAmounts(share, -cut));
|
|
326
|
+
left = sumAmounts(left, cut);
|
|
269
327
|
}
|
|
328
|
+
let frontier = NO_EXPIRED_BACKING_PREFIX;
|
|
270
329
|
for (const hold of placed) {
|
|
330
|
+
if (left <= 0) break;
|
|
331
|
+
if (shares.has(hold.id)) continue;
|
|
332
|
+
const share = Math.min(hold.amount, left);
|
|
333
|
+
frontier = { holdPlacedAt: hold.holdPlacedAt, id: hold.id, share };
|
|
334
|
+
left = sumAmounts(left, -share);
|
|
335
|
+
}
|
|
336
|
+
for (const hold of recorded) {
|
|
337
|
+
if (left <= 0) break;
|
|
271
338
|
const share = shares.get(hold.id);
|
|
272
339
|
const added = Math.min(sumAmounts(hold.amount, -share), left);
|
|
273
340
|
shares.set(hold.id, sumAmounts(share, added));
|
|
274
341
|
left = sumAmounts(left, -added);
|
|
275
342
|
}
|
|
276
|
-
|
|
343
|
+
const rewrites = recorded.filter((hold) => shares.get(hold.id) !== hold.expiredBacking).map((hold) => ({ id: hold.id, expiredBacking: shares.get(hold.id) }));
|
|
344
|
+
return { total: wanted, frontier, ...rewrites.length > 0 ? { rewrites } : {} };
|
|
277
345
|
}
|
|
278
|
-
function
|
|
279
|
-
|
|
346
|
+
function holdExpiredBacking(hold, frontier, rewrites) {
|
|
347
|
+
const rewritten = rewrites?.find((rewrite) => rewrite.id === hold.id);
|
|
348
|
+
const recorded = recordedExpiredBacking(
|
|
349
|
+
rewritten ? { ...hold, expiredBacking: rewritten.expiredBacking } : hold
|
|
350
|
+
);
|
|
351
|
+
if (recorded > 0) return recorded;
|
|
352
|
+
if (!frontier || !hold.holdPlacedAt) return 0;
|
|
353
|
+
const order = compareHolds({ holdPlacedAt: hold.holdPlacedAt, id: hold.id }, frontier);
|
|
354
|
+
if (order > 0) return 0;
|
|
355
|
+
return order < 0 ? hold.amount : Math.min(frontier.share, hold.amount);
|
|
356
|
+
}
|
|
357
|
+
function holdPlacedAtAfter(now, frontier) {
|
|
358
|
+
if (!frontier?.holdPlacedAt) return now;
|
|
359
|
+
const earliest = new Date(Date.parse(frontier.holdPlacedAt) + 1).toISOString();
|
|
360
|
+
return now > earliest ? now : earliest;
|
|
361
|
+
}
|
|
362
|
+
function expiredBackingPatch(next, stored) {
|
|
363
|
+
const total = Math.max(0, next.total);
|
|
364
|
+
const patch = {};
|
|
365
|
+
const storedTotal = stored.expiredHoldBacking;
|
|
366
|
+
if (total > 0 || storedTotal !== void 0 && storedTotal !== 0) {
|
|
367
|
+
patch.expiredHoldBacking = total;
|
|
368
|
+
}
|
|
369
|
+
if (total <= 0) {
|
|
370
|
+
if (stored.expiredBackingFrontier) patch.expiredBackingFrontier = null;
|
|
371
|
+
} else if (next.frontier) {
|
|
372
|
+
patch.expiredBackingFrontier = next.frontier;
|
|
373
|
+
}
|
|
374
|
+
return patch;
|
|
280
375
|
}
|
|
281
376
|
function assertValidBalanceTarget(value, field, context) {
|
|
282
377
|
assertRepresentableAmount(value, field, context);
|
|
@@ -289,7 +384,10 @@ function assertValidBalanceTarget(value, field, context) {
|
|
|
289
384
|
}
|
|
290
385
|
}
|
|
291
386
|
function assertValidTierBalanceInput(input, context) {
|
|
292
|
-
if (input.balanceCap === void 0)
|
|
387
|
+
if (input.balanceCap === void 0) {
|
|
388
|
+
if (input.balance !== void 0) assertValidBalanceTarget(input.balance, "balance", context);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
293
391
|
if (input.balance !== void 0) {
|
|
294
392
|
throw new CreditError(
|
|
295
393
|
"A tier update cannot set both balance and balanceCap",
|
|
@@ -815,17 +913,25 @@ var DEFAULT_CONFIG = {
|
|
|
815
913
|
}
|
|
816
914
|
};
|
|
817
915
|
var currentConfig = DEFAULT_CONFIG;
|
|
916
|
+
function isOperationCostMap(value) {
|
|
917
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
918
|
+
return Object.values(value).every((cost) => isRepresentableAmount(cost) && cost >= 0);
|
|
919
|
+
}
|
|
818
920
|
function loadConfigFromEnv() {
|
|
819
921
|
const envConfig = {};
|
|
820
922
|
const operationCostsEnv = process.env.CREDITS_OPERATION_COSTS;
|
|
821
923
|
if (operationCostsEnv) {
|
|
822
924
|
try {
|
|
823
925
|
const parsed = JSON.parse(operationCostsEnv);
|
|
824
|
-
if (
|
|
926
|
+
if (isOperationCostMap(parsed)) {
|
|
825
927
|
envConfig.operationCosts = {
|
|
826
928
|
...DEFAULT_CONFIG.operationCosts,
|
|
827
929
|
...parsed
|
|
828
930
|
};
|
|
931
|
+
} else {
|
|
932
|
+
console.warn(
|
|
933
|
+
"[Credits Config] Ignoring CREDITS_OPERATION_COSTS: it must be a JSON object whose costs are numbers >= 0, exact to the cent"
|
|
934
|
+
);
|
|
829
935
|
}
|
|
830
936
|
} catch {
|
|
831
937
|
console.warn("[Credits Config] Failed to parse CREDITS_OPERATION_COSTS env var as JSON");
|
|
@@ -1255,7 +1361,7 @@ function validateBalanceUpdate(current, updates) {
|
|
|
1255
1361
|
const reserved = next(current.reserved ?? 0, updates.reserved, updates.reservedIncrement);
|
|
1256
1362
|
const errors = [];
|
|
1257
1363
|
if (balance < 0) errors.push(`Balance cannot be negative: ${balance}`);
|
|
1258
|
-
const marked = updates.balance === void 0 ? current.expiredHoldBacking
|
|
1364
|
+
const marked = updates.balance === void 0 ? heldExpiredBacking(current.balance, current.expiredHoldBacking) : 0;
|
|
1259
1365
|
if ((updates.balanceIncrement ?? 0) < 0 && balance >= 0 && balance < marked) {
|
|
1260
1366
|
errors.push(`Balance (${balance}) cannot go below expired hold backing (${marked})`);
|
|
1261
1367
|
}
|
|
@@ -1273,6 +1379,50 @@ function assertValidBalanceUpdate(current, updates) {
|
|
|
1273
1379
|
throw new Error(`Invalid balance update: ${result.errors.join("; ")}`);
|
|
1274
1380
|
}
|
|
1275
1381
|
}
|
|
1382
|
+
function signupGrantJournalInput(userId, tier, grant) {
|
|
1383
|
+
if (grant <= 0) return null;
|
|
1384
|
+
return {
|
|
1385
|
+
userId,
|
|
1386
|
+
entryType: "credit",
|
|
1387
|
+
amount: grant,
|
|
1388
|
+
balanceBefore: 0,
|
|
1389
|
+
balanceAfter: grant,
|
|
1390
|
+
source: "bonus",
|
|
1391
|
+
referenceId: `signup-${userId}`,
|
|
1392
|
+
referenceType: "adjustment",
|
|
1393
|
+
description: `Signup grant for ${tier} tier.`,
|
|
1394
|
+
metadata: { tier }
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
function tierChangeJournalInput(input) {
|
|
1398
|
+
const { userId, previousTier, newTier, previousBalance, newBalance, bonusCredits } = input;
|
|
1399
|
+
const change = sumAmounts(newBalance, -previousBalance);
|
|
1400
|
+
if (change === 0) return null;
|
|
1401
|
+
const raised = change > 0;
|
|
1402
|
+
const amount = raised ? newBalance : 0;
|
|
1403
|
+
const balanceBefore = sumAmounts(previousBalance, bonusCredits);
|
|
1404
|
+
const balanceAfter = sumAmounts(newBalance, bonusCredits);
|
|
1405
|
+
assertRepresentableFields(
|
|
1406
|
+
{
|
|
1407
|
+
"journal amount": amount,
|
|
1408
|
+
"journal balanceBefore": balanceBefore,
|
|
1409
|
+
"journal balanceAfter": balanceAfter
|
|
1410
|
+
},
|
|
1411
|
+
{ userId, operation: "updateUserTier" }
|
|
1412
|
+
);
|
|
1413
|
+
return {
|
|
1414
|
+
userId,
|
|
1415
|
+
entryType: raised ? "credit" : "debit",
|
|
1416
|
+
amount,
|
|
1417
|
+
balanceBefore,
|
|
1418
|
+
balanceAfter,
|
|
1419
|
+
source: raised ? "subscription_upgrade" : "subscription_downgrade",
|
|
1420
|
+
referenceId: `tier-${Date.now()}`,
|
|
1421
|
+
referenceType: "subscription",
|
|
1422
|
+
description: `Tier changed from ${previousTier} to ${newTier}.`,
|
|
1423
|
+
metadata: { previousTier, newTier, previousBalance, newBalance }
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1276
1426
|
|
|
1277
1427
|
// src/repository/memory/snapshot.ts
|
|
1278
1428
|
function copyRecord(record) {
|
|
@@ -1407,6 +1557,7 @@ function commitTransitionJournal(store, input, existingId) {
|
|
|
1407
1557
|
userId: input.userId,
|
|
1408
1558
|
entryType: input.entryType,
|
|
1409
1559
|
amount: input.amount,
|
|
1560
|
+
balanceBefore: input.balanceBefore,
|
|
1410
1561
|
balanceAfter: input.balanceAfter,
|
|
1411
1562
|
source: input.source,
|
|
1412
1563
|
referenceId: input.reservationId,
|
|
@@ -1456,16 +1607,28 @@ function openHolds(store, userId) {
|
|
|
1456
1607
|
(reservation) => reservation.status === "reserved"
|
|
1457
1608
|
);
|
|
1458
1609
|
}
|
|
1610
|
+
function expiredBackingInForce(store, credits) {
|
|
1611
|
+
const total = heldExpiredBacking(credits.balance, credits.expiredHoldBacking);
|
|
1612
|
+
if (total > 0 && (!credits.expiredBackingFrontier || credits.expiredHoldBacking !== total)) {
|
|
1613
|
+
return allocateExpiredBacking(openHolds(store, credits.userId), total);
|
|
1614
|
+
}
|
|
1615
|
+
return { total, frontier: credits.expiredBackingFrontier ?? null };
|
|
1616
|
+
}
|
|
1617
|
+
function writeRecordedShares(store, userId, allocation) {
|
|
1618
|
+
for (const { id, expiredBacking } of allocation.rewrites ?? []) {
|
|
1619
|
+
const hold = getReservation(store, userId, id);
|
|
1620
|
+
if (hold) hold.expiredBacking = expiredBacking;
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1459
1623
|
function writeExpiredHoldBacking(store, credits, next) {
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
const shares = attributeExpiredBacking(holds, next);
|
|
1464
|
-
for (const hold of holds) {
|
|
1465
|
-
const share = shares.get(hold.id) ?? 0;
|
|
1466
|
-
if (share !== (hold.expiredBacking ?? 0)) hold.expiredBacking = share;
|
|
1624
|
+
if (next <= 0) {
|
|
1625
|
+
Object.assign(credits, expiredBackingPatch({ total: 0 }, credits));
|
|
1626
|
+
return;
|
|
1467
1627
|
}
|
|
1468
|
-
|
|
1628
|
+
if (next === credits.expiredHoldBacking) return;
|
|
1629
|
+
const allocation = allocateExpiredBacking(openHolds(store, credits.userId), next);
|
|
1630
|
+
writeRecordedShares(store, credits.userId, allocation);
|
|
1631
|
+
Object.assign(credits, expiredBackingPatch(allocation, credits));
|
|
1469
1632
|
}
|
|
1470
1633
|
function getReservation(store, userId, reservationId) {
|
|
1471
1634
|
return store.reservations.get(userId)?.get(reservationId);
|
|
@@ -1514,6 +1677,7 @@ async function reserveCreditsV2(store, input) {
|
|
|
1514
1677
|
};
|
|
1515
1678
|
}
|
|
1516
1679
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1680
|
+
const backing = expiredBackingInForce(store, credits);
|
|
1517
1681
|
const reservation = {
|
|
1518
1682
|
id: generateId(),
|
|
1519
1683
|
userId: input.userId,
|
|
@@ -1526,7 +1690,7 @@ async function reserveCreditsV2(store, input) {
|
|
|
1526
1690
|
// The hold-origin fact. Written here and nowhere else, in the same
|
|
1527
1691
|
// critical section that raises `reserved` below, so the row and the hold
|
|
1528
1692
|
// become visible together. `createReservation` cannot set it.
|
|
1529
|
-
holdPlacedAt: now
|
|
1693
|
+
holdPlacedAt: holdPlacedAtAfter(now, backing.total > 0 ? backing.frontier : null)
|
|
1530
1694
|
};
|
|
1531
1695
|
if (!store.reservations.has(input.userId)) {
|
|
1532
1696
|
store.reservations.set(input.userId, /* @__PURE__ */ new Map());
|
|
@@ -1544,6 +1708,8 @@ async function reserveCreditsV2(store, input) {
|
|
|
1544
1708
|
);
|
|
1545
1709
|
}
|
|
1546
1710
|
credits.reserved = nextReserved;
|
|
1711
|
+
writeRecordedShares(store, input.userId, backing);
|
|
1712
|
+
Object.assign(credits, expiredBackingPatch(backing, credits));
|
|
1547
1713
|
credits.updatedAt = now;
|
|
1548
1714
|
return { outcome: "created", reservation: copyRecord(reservation) };
|
|
1549
1715
|
});
|
|
@@ -1564,11 +1730,12 @@ async function commitReservationV2(store, userId, reservationId, options) {
|
|
|
1564
1730
|
const amount = reservation.amount;
|
|
1565
1731
|
await yieldPoint(store);
|
|
1566
1732
|
assertReservedCoversHold(credits, userId, reservationId, amount);
|
|
1733
|
+
const backing = expiredBackingInForce(store, credits);
|
|
1567
1734
|
const paid = planHoldCommit(
|
|
1568
1735
|
credits.balance,
|
|
1569
1736
|
credits.bonusCredits,
|
|
1570
|
-
|
|
1571
|
-
reservation.
|
|
1737
|
+
backing.total,
|
|
1738
|
+
holdExpiredBacking(reservation, backing.frontier, backing.rewrites),
|
|
1572
1739
|
amount
|
|
1573
1740
|
);
|
|
1574
1741
|
if (!paid.covered) {
|
|
@@ -1586,6 +1753,7 @@ async function commitReservationV2(store, userId, reservationId, options) {
|
|
|
1586
1753
|
operation: "commitReservation",
|
|
1587
1754
|
entryType: "debit",
|
|
1588
1755
|
amount,
|
|
1756
|
+
balanceBefore: sumAmounts(credits.balance, credits.bonusCredits),
|
|
1589
1757
|
balanceAfter,
|
|
1590
1758
|
source: "operation_commit",
|
|
1591
1759
|
reservationId,
|
|
@@ -1613,9 +1781,10 @@ async function commitReservationV2(store, userId, reservationId, options) {
|
|
|
1613
1781
|
credits.bonusCredits = nextBonusCredits;
|
|
1614
1782
|
credits.reserved = nextReserved;
|
|
1615
1783
|
credits.monthlyUsed = nextMonthlyUsed;
|
|
1784
|
+
writeRecordedShares(store, userId, backing);
|
|
1616
1785
|
Object.assign(
|
|
1617
1786
|
credits,
|
|
1618
|
-
|
|
1787
|
+
expiredBackingPatch({ total: paid.expiredHoldBacking, frontier: backing.frontier }, credits)
|
|
1619
1788
|
);
|
|
1620
1789
|
credits.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1621
1790
|
const journalEntryId = commitTransitionJournal(store, journal, existingEntryId);
|
|
@@ -1696,10 +1865,11 @@ function finishUnspent(store, reservation, transition) {
|
|
|
1696
1865
|
reservation.id,
|
|
1697
1866
|
reservation.amount
|
|
1698
1867
|
);
|
|
1868
|
+
const inForce = expiredBackingInForce(store, credits);
|
|
1699
1869
|
const backing = settleExpiredHoldBacking(
|
|
1700
1870
|
credits.balance,
|
|
1701
|
-
|
|
1702
|
-
reservation.
|
|
1871
|
+
inForce.total,
|
|
1872
|
+
holdExpiredBacking(reservation, inForce.frontier, inForce.rewrites)
|
|
1703
1873
|
);
|
|
1704
1874
|
const nextBalance = sumAmounts(credits.balance, -backing.used);
|
|
1705
1875
|
const journal = {
|
|
@@ -1707,6 +1877,7 @@ function finishUnspent(store, reservation, transition) {
|
|
|
1707
1877
|
operation: `${transition.transition}Reservation`,
|
|
1708
1878
|
entryType: "credit",
|
|
1709
1879
|
amount: 0,
|
|
1880
|
+
balanceBefore: sumAmounts(credits.balance, credits.bonusCredits),
|
|
1710
1881
|
balanceAfter: sumAmounts(nextBalance, credits.bonusCredits),
|
|
1711
1882
|
source: transition.source,
|
|
1712
1883
|
reservationId: reservation.id,
|
|
@@ -1729,7 +1900,11 @@ function finishUnspent(store, reservation, transition) {
|
|
|
1729
1900
|
reservation.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1730
1901
|
credits.reserved = nextReserved;
|
|
1731
1902
|
credits.balance = nextBalance;
|
|
1732
|
-
|
|
1903
|
+
writeRecordedShares(store, reservation.userId, inForce);
|
|
1904
|
+
Object.assign(
|
|
1905
|
+
credits,
|
|
1906
|
+
expiredBackingPatch({ total: backing.remaining, frontier: inForce.frontier }, credits)
|
|
1907
|
+
);
|
|
1733
1908
|
credits.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1734
1909
|
return commitTransitionJournal(store, journal, existingEntryId);
|
|
1735
1910
|
}
|
|
@@ -1772,7 +1947,7 @@ var InMemoryCreditRepository = class {
|
|
|
1772
1947
|
* simultaneous first deliveries of the same reference would then both credit.
|
|
1773
1948
|
*/
|
|
1774
1949
|
seedUser(userId, tier, initialBalance) {
|
|
1775
|
-
|
|
1950
|
+
assertValidBalanceTarget(initialBalance, "initialBalance", { userId });
|
|
1776
1951
|
const configured = getConfigMonthlyLimit(tier);
|
|
1777
1952
|
assertRepresentableTierAmount(configured, "monthlyLimit", { userId, tier });
|
|
1778
1953
|
const monthlyLimit = storedMonthlyLimit(configured);
|
|
@@ -1791,6 +1966,8 @@ var InMemoryCreditRepository = class {
|
|
|
1791
1966
|
updatedAt: now
|
|
1792
1967
|
};
|
|
1793
1968
|
this.store.users.set(userId, credits);
|
|
1969
|
+
const grantLine = signupGrantJournalInput(userId, tier, initialBalance);
|
|
1970
|
+
if (grantLine) this.recordJournalEntry(grantLine);
|
|
1794
1971
|
return credits;
|
|
1795
1972
|
}
|
|
1796
1973
|
async updateUserCredits(userId, updates) {
|
|
@@ -1853,9 +2030,11 @@ var InMemoryCreditRepository = class {
|
|
|
1853
2030
|
userId,
|
|
1854
2031
|
operation: "updateUserCredits"
|
|
1855
2032
|
});
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
2033
|
+
writeExpiredHoldBacking(
|
|
2034
|
+
this.store,
|
|
2035
|
+
next,
|
|
2036
|
+
updates.balance !== void 0 ? 0 : Math.min(heldExpiredBacking(credits.balance, credits.expiredHoldBacking), next.balance)
|
|
2037
|
+
);
|
|
1859
2038
|
next.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1860
2039
|
this.store.users.set(userId, next);
|
|
1861
2040
|
}
|
|
@@ -1869,24 +2048,37 @@ var InMemoryCreditRepository = class {
|
|
|
1869
2048
|
if (!credits) {
|
|
1870
2049
|
throw new Error(`User ${userId} not found`);
|
|
1871
2050
|
}
|
|
1872
|
-
|
|
1873
|
-
|
|
2051
|
+
let planned;
|
|
2052
|
+
if (input.balance !== void 0) {
|
|
2053
|
+
planned = planBalanceTarget(input.balance, credits.reserved, credits.bonusCredits);
|
|
2054
|
+
} else if (input.balanceCap !== void 0 && !isFreeTier(credits.tier)) {
|
|
2055
|
+
planned = planBalanceCap(
|
|
1874
2056
|
credits.balance,
|
|
1875
2057
|
input.balanceCap,
|
|
1876
2058
|
credits.reserved,
|
|
1877
2059
|
credits.bonusCredits,
|
|
1878
2060
|
credits.expiredHoldBacking
|
|
1879
2061
|
);
|
|
1880
|
-
credits.balance = capped.balance;
|
|
1881
|
-
writeExpiredHoldBacking(this.store, credits, capped.expiredHoldBacking);
|
|
1882
2062
|
}
|
|
2063
|
+
const journalLine = planned ? tierChangeJournalInput({
|
|
2064
|
+
userId,
|
|
2065
|
+
previousTier: credits.tier,
|
|
2066
|
+
newTier: input.tier,
|
|
2067
|
+
previousBalance: credits.balance,
|
|
2068
|
+
newBalance: planned.balance,
|
|
2069
|
+
bonusCredits: credits.bonusCredits
|
|
2070
|
+
}) : null;
|
|
1883
2071
|
credits.tier = input.tier;
|
|
1884
2072
|
credits.monthlyLimit = input.monthlyLimit;
|
|
1885
|
-
if (
|
|
1886
|
-
const planned = planBalanceTarget(input.balance, credits.reserved, credits.bonusCredits);
|
|
2073
|
+
if (planned) {
|
|
1887
2074
|
credits.balance = planned.balance;
|
|
1888
2075
|
writeExpiredHoldBacking(this.store, credits, planned.expiredHoldBacking);
|
|
1889
2076
|
}
|
|
2077
|
+
writeExpiredHoldBacking(
|
|
2078
|
+
this.store,
|
|
2079
|
+
credits,
|
|
2080
|
+
heldExpiredBacking(credits.balance, credits.expiredHoldBacking)
|
|
2081
|
+
);
|
|
1890
2082
|
if (input.monthlyUsed !== void 0) credits.monthlyUsed = input.monthlyUsed;
|
|
1891
2083
|
if (input.subscriptionExpiresAt !== void 0) {
|
|
1892
2084
|
if (input.subscriptionExpiresAt === null) {
|
|
@@ -1897,6 +2089,7 @@ var InMemoryCreditRepository = class {
|
|
|
1897
2089
|
}
|
|
1898
2090
|
credits.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1899
2091
|
this.store.users.set(userId, credits);
|
|
2092
|
+
if (journalLine) this.recordJournalEntry(journalLine);
|
|
1900
2093
|
}
|
|
1901
2094
|
// ==================== Reservations ====================
|
|
1902
2095
|
async createReservation(input) {
|
|
@@ -2048,6 +2241,11 @@ var InMemoryCreditRepository = class {
|
|
|
2048
2241
|
{ userId, operation: "addCredits" }
|
|
2049
2242
|
);
|
|
2050
2243
|
credits.bonusCredits = nextBonusCredits;
|
|
2244
|
+
writeExpiredHoldBacking(
|
|
2245
|
+
this.store,
|
|
2246
|
+
credits,
|
|
2247
|
+
heldExpiredBacking(credits.balance, credits.expiredHoldBacking)
|
|
2248
|
+
);
|
|
2051
2249
|
credits.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2052
2250
|
this.store.users.set(userId, credits);
|
|
2053
2251
|
const transaction = this.recordTransaction({
|
|
@@ -2067,6 +2265,7 @@ var InMemoryCreditRepository = class {
|
|
|
2067
2265
|
userId,
|
|
2068
2266
|
entryType: "credit",
|
|
2069
2267
|
amount,
|
|
2268
|
+
balanceBefore: previousTotal,
|
|
2070
2269
|
balanceAfter: newTotal,
|
|
2071
2270
|
source: payload.source,
|
|
2072
2271
|
referenceId: transaction.id ?? paymentRef ?? "unknown",
|
|
@@ -2100,7 +2299,7 @@ var InMemoryCreditRepository = class {
|
|
|
2100
2299
|
});
|
|
2101
2300
|
}
|
|
2102
2301
|
}
|
|
2103
|
-
async deductCreditsAtomic(userId, amount) {
|
|
2302
|
+
async deductCreditsAtomic(userId, amount, journal) {
|
|
2104
2303
|
assertValidCreditAmount(amount, { userId, operation: "deductCredits" });
|
|
2105
2304
|
const credits = this.store.users.get(userId);
|
|
2106
2305
|
if (!credits) {
|
|
@@ -2126,11 +2325,26 @@ var InMemoryCreditRepository = class {
|
|
|
2126
2325
|
{ previousBalance, balance: nextBalance, bonusCredits: nextBonusCredits, newBalance },
|
|
2127
2326
|
{ userId, operation: "deductCredits" }
|
|
2128
2327
|
);
|
|
2328
|
+
const held = heldExpiredBacking(credits.balance, credits.expiredHoldBacking);
|
|
2129
2329
|
credits.balance = nextBalance;
|
|
2130
2330
|
credits.bonusCredits = nextBonusCredits;
|
|
2331
|
+
writeExpiredHoldBacking(this.store, credits, held);
|
|
2131
2332
|
credits.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2132
2333
|
this.store.users.set(userId, credits);
|
|
2133
|
-
return { previousBalance, newBalance };
|
|
2334
|
+
if (!journal) return { previousBalance, newBalance };
|
|
2335
|
+
const entry = this.recordJournalEntry({
|
|
2336
|
+
userId,
|
|
2337
|
+
entryType: "debit",
|
|
2338
|
+
amount,
|
|
2339
|
+
balanceBefore: previousBalance,
|
|
2340
|
+
balanceAfter: newBalance,
|
|
2341
|
+
source: journal.source ?? "operation_commit",
|
|
2342
|
+
referenceId: journal.referenceId,
|
|
2343
|
+
referenceType: journal.referenceType ?? "adjustment",
|
|
2344
|
+
description: journal.description,
|
|
2345
|
+
metadata: journal.metadata
|
|
2346
|
+
});
|
|
2347
|
+
return { previousBalance, newBalance, journalEntryId: entry.id };
|
|
2134
2348
|
}
|
|
2135
2349
|
// ==================== Transactions ====================
|
|
2136
2350
|
async createTransaction(input) {
|
|
@@ -2244,6 +2458,11 @@ var InMemoryCreditRepository = class {
|
|
|
2244
2458
|
assertRepresentableAmount(input.balanceAfter, "journal balanceAfter", {
|
|
2245
2459
|
userId: input.userId
|
|
2246
2460
|
});
|
|
2461
|
+
if (input.balanceBefore !== void 0) {
|
|
2462
|
+
assertRepresentableAmount(input.balanceBefore, "journal balanceBefore", {
|
|
2463
|
+
userId: input.userId
|
|
2464
|
+
});
|
|
2465
|
+
}
|
|
2247
2466
|
assertValidIdempotencyKey(input.idempotencyKey, { userId: input.userId });
|
|
2248
2467
|
assertPublicJournalKey(input.idempotencyKey, input.userId);
|
|
2249
2468
|
const key = input.idempotencyKey !== void 0 ? scopedKey(input.userId, input.idempotencyKey) : void 0;
|
|
@@ -2259,6 +2478,7 @@ var InMemoryCreditRepository = class {
|
|
|
2259
2478
|
userId: input.userId,
|
|
2260
2479
|
entryType: input.entryType,
|
|
2261
2480
|
amount: input.amount,
|
|
2481
|
+
...input.balanceBefore !== void 0 ? { balanceBefore: input.balanceBefore } : {},
|
|
2262
2482
|
balanceAfter: input.balanceAfter,
|
|
2263
2483
|
source: input.source,
|
|
2264
2484
|
referenceId: input.referenceId,
|
|
@@ -2411,6 +2631,7 @@ var InMemoryCreditRepository = class {
|
|
|
2411
2631
|
userId,
|
|
2412
2632
|
entryType: change > 0 ? "credit" : "debit",
|
|
2413
2633
|
amount: Math.abs(change),
|
|
2634
|
+
balanceBefore: sumAmounts(previousBalance, credits.bonusCredits),
|
|
2414
2635
|
balanceAfter,
|
|
2415
2636
|
source: "monthly_reset",
|
|
2416
2637
|
referenceId: `reset-${Date.now()}`,
|
|
@@ -2490,7 +2711,8 @@ var InMemoryCreditRepository = class {
|
|
|
2490
2711
|
userId,
|
|
2491
2712
|
entryType: "debit",
|
|
2492
2713
|
amount: 0,
|
|
2493
|
-
//
|
|
2714
|
+
// 0, however far the cap lowered the balance: the totals record the move
|
|
2715
|
+
balanceBefore: sumAmounts(previousBalance, credits.bonusCredits),
|
|
2494
2716
|
balanceAfter,
|
|
2495
2717
|
source: "subscription_downgrade",
|
|
2496
2718
|
referenceId: `downgrade-${Date.now()}`,
|
|
@@ -2653,7 +2875,8 @@ var CreditsService = class {
|
|
|
2653
2875
|
userId,
|
|
2654
2876
|
entryType: "debit",
|
|
2655
2877
|
amount: 0,
|
|
2656
|
-
//
|
|
2878
|
+
// 0, however far the cap lowered the balance: the totals record the move
|
|
2879
|
+
balanceBefore: sumAmounts(data.balance, data.bonusCredits),
|
|
2657
2880
|
// The ledger balance, matching every other journal writer: the V2
|
|
2658
2881
|
// transitions and add/deduct all record `balance + bonusCredits`, so
|
|
2659
2882
|
// recording `balance` alone made the audit trail disagree with itself
|
|
@@ -2697,6 +2920,7 @@ var CreditsService = class {
|
|
|
2697
2920
|
userId,
|
|
2698
2921
|
entryType: balanceChange > 0 ? "credit" : "debit",
|
|
2699
2922
|
amount: Math.abs(balanceChange),
|
|
2923
|
+
balanceBefore: sumAmounts(data.balance, data.bonusCredits),
|
|
2700
2924
|
balanceAfter: sumAmounts(
|
|
2701
2925
|
resetResult.credits.balance,
|
|
2702
2926
|
resetResult.credits.bonusCredits
|
|
@@ -2931,9 +3155,18 @@ var CreditsService = class {
|
|
|
2931
3155
|
*/
|
|
2932
3156
|
async deductCredits(userId, amount, options) {
|
|
2933
3157
|
assertValidCreditAmount(amount, { userId, operation: "deductCredits" });
|
|
3158
|
+
const operationType = options?.operationType;
|
|
3159
|
+
const journal = {
|
|
3160
|
+
referenceId: options?.requestId ?? `deduct-${Date.now()}`,
|
|
3161
|
+
description: operationType ? `Deducted ${amount} credits for ${getOperationLabel(operationType)}` : `Deducted ${amount} credits`,
|
|
3162
|
+
metadata: {
|
|
3163
|
+
...options?.metadata,
|
|
3164
|
+
...operationType && { operationType }
|
|
3165
|
+
}
|
|
3166
|
+
};
|
|
2934
3167
|
let deduction;
|
|
2935
3168
|
try {
|
|
2936
|
-
deduction = await this.repository.deductCreditsAtomic(userId, amount);
|
|
3169
|
+
deduction = await this.repository.deductCreditsAtomic(userId, amount, journal);
|
|
2937
3170
|
} catch (error) {
|
|
2938
3171
|
if (isCreditError(error) && error.code === CreditErrorCode.INVALID_AMOUNT) throw error;
|
|
2939
3172
|
const credits = await this.repository.getUserCredits(userId);
|
|
@@ -2949,21 +3182,18 @@ var CreditsService = class {
|
|
|
2949
3182
|
shortfall: sumAmounts(amount, -available)
|
|
2950
3183
|
};
|
|
2951
3184
|
}
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
...operationType && { operationType }
|
|
2965
|
-
}
|
|
2966
|
-
});
|
|
3185
|
+
if (deduction.journalEntryId === void 0) {
|
|
3186
|
+
await this.repository.createJournalEntry({
|
|
3187
|
+
userId,
|
|
3188
|
+
entryType: "debit",
|
|
3189
|
+
amount,
|
|
3190
|
+
balanceBefore: deduction.previousBalance,
|
|
3191
|
+
balanceAfter: deduction.newBalance,
|
|
3192
|
+
source: "operation_commit",
|
|
3193
|
+
referenceType: "adjustment",
|
|
3194
|
+
...journal
|
|
3195
|
+
});
|
|
3196
|
+
}
|
|
2967
3197
|
if (operationType) {
|
|
2968
3198
|
await this.logUsage({
|
|
2969
3199
|
userId,
|
|
@@ -3037,7 +3267,8 @@ var CreditsService = class {
|
|
|
3037
3267
|
]);
|
|
3038
3268
|
const historyEntries = entries.map((entry) => {
|
|
3039
3269
|
const type = this.mapSourceToHistoryType(entry.source);
|
|
3040
|
-
const
|
|
3270
|
+
const signedAmount = entry.entryType === "credit" ? entry.amount : -entry.amount;
|
|
3271
|
+
const creditsChange = entry.balanceBefore !== void 0 ? sumAmounts(entry.balanceAfter, -entry.balanceBefore) : signedAmount;
|
|
3041
3272
|
return {
|
|
3042
3273
|
id: entry.id,
|
|
3043
3274
|
type,
|
|
@@ -3973,6 +4204,7 @@ export {
|
|
|
3973
4204
|
CreditsSDKError,
|
|
3974
4205
|
CreditsService,
|
|
3975
4206
|
InMemoryCreditRepository,
|
|
4207
|
+
NO_EXPIRED_BACKING_PREFIX,
|
|
3976
4208
|
NetworkError,
|
|
3977
4209
|
RESERVATION_STATUSES,
|
|
3978
4210
|
RESERVED_JOURNAL_KEY_PREFIX,
|
|
@@ -3985,6 +4217,7 @@ export {
|
|
|
3985
4217
|
UNLIMITED_BALANCE_SENTINEL,
|
|
3986
4218
|
ValidationError,
|
|
3987
4219
|
addCreditsThroughRepository,
|
|
4220
|
+
allocateExpiredBacking,
|
|
3988
4221
|
assertDirectStatusWriteAllowed,
|
|
3989
4222
|
assertHoldPlaced,
|
|
3990
4223
|
assertKnownReservationStatus,
|
|
@@ -4001,9 +4234,9 @@ export {
|
|
|
4001
4234
|
assertValidStoredAmount,
|
|
4002
4235
|
assertValidStoredAmountRaw,
|
|
4003
4236
|
assertValidTierBalanceInput,
|
|
4004
|
-
attributeExpiredBacking,
|
|
4005
4237
|
backedBalanceFloor,
|
|
4006
4238
|
calculateAvailableCredits,
|
|
4239
|
+
canonicalAmount,
|
|
4007
4240
|
cappedBalance,
|
|
4008
4241
|
checkAndNotifyLowBalance,
|
|
4009
4242
|
classifyDatabaseError,
|
|
@@ -4030,7 +4263,7 @@ export {
|
|
|
4030
4263
|
createUnsupportedOperationError,
|
|
4031
4264
|
createUserNotFoundError,
|
|
4032
4265
|
describePaymentMismatch,
|
|
4033
|
-
|
|
4266
|
+
expiredBackingPatch,
|
|
4034
4267
|
generateId,
|
|
4035
4268
|
generateRequestId2 as generateRequestId,
|
|
4036
4269
|
genericDeferred,
|
|
@@ -4056,6 +4289,9 @@ export {
|
|
|
4056
4289
|
getValidOperationTypes,
|
|
4057
4290
|
getValidTiers,
|
|
4058
4291
|
hasPlacedHold,
|
|
4292
|
+
heldExpiredBacking,
|
|
4293
|
+
holdExpiredBacking,
|
|
4294
|
+
holdPlacedAtAfter,
|
|
4059
4295
|
initializeConfig,
|
|
4060
4296
|
isCreditError,
|
|
4061
4297
|
isCreditErrorMessage,
|
|
@@ -4081,6 +4317,7 @@ export {
|
|
|
4081
4317
|
isWinningOutcome,
|
|
4082
4318
|
loadConfigFromEnv,
|
|
4083
4319
|
monthlyResetBalance,
|
|
4320
|
+
multiplyAmount,
|
|
4084
4321
|
noopDeferred,
|
|
4085
4322
|
normalizePaymentRef,
|
|
4086
4323
|
notifySubscriptionExpired,
|
|
@@ -4092,6 +4329,7 @@ export {
|
|
|
4092
4329
|
planBalanceCap,
|
|
4093
4330
|
planBalanceTarget,
|
|
4094
4331
|
planHoldCommit,
|
|
4332
|
+
recordedExpiredBacking,
|
|
4095
4333
|
registerConfigProvider,
|
|
4096
4334
|
registerNotificationHandler,
|
|
4097
4335
|
releaseReservationWithJournal,
|
|
@@ -4102,13 +4340,17 @@ export {
|
|
|
4102
4340
|
resetConfig,
|
|
4103
4341
|
sameAmount,
|
|
4104
4342
|
settleExpiredHoldBacking,
|
|
4343
|
+
signupGrantJournalInput,
|
|
4105
4344
|
spendableBalance,
|
|
4345
|
+
storedAmountCeil,
|
|
4346
|
+
storedAmountFloor,
|
|
4106
4347
|
storedMonthlyLimit,
|
|
4107
4348
|
sumAmounts,
|
|
4108
4349
|
supportsCreditsV2,
|
|
4109
4350
|
supportsIdempotentCredit,
|
|
4110
4351
|
synchronousDeferred,
|
|
4111
4352
|
terminalStatusOf,
|
|
4353
|
+
tierChangeJournalInput,
|
|
4112
4354
|
tierSchema,
|
|
4113
4355
|
toCents,
|
|
4114
4356
|
toClientUserCredits,
|