@nehorai/credits 2.1.0 → 3.0.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/LICENSE +21 -0
- package/dist/adapters/index.cjs +3 -2
- 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 +3 -2
- package/dist/adapters/index.js.map +1 -1
- package/dist/core/index.cjs +218 -2
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +183 -5
- package/dist/core/index.d.ts +183 -5
- package/dist/core/index.js +199 -2
- package/dist/core/index.js.map +1 -1
- package/dist/index.cjs +477 -59
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +456 -59
- package/dist/index.js.map +1 -1
- package/dist/repository/index.cjs +394 -44
- package/dist/repository/index.cjs.map +1 -1
- package/dist/repository/index.d.cts +36 -7
- package/dist/repository/index.d.ts +36 -7
- package/dist/repository/index.js +391 -43
- 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 +33 -20
- 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 +33 -20
- package/dist/service/index.js.map +1 -1
- package/dist/{types-Dzc443vy.d.cts → types-C3sxDvNb.d.cts} +43 -2
- package/dist/{types-Dzc443vy.d.ts → types-C3sxDvNb.d.ts} +43 -2
- package/dist/{types-BhCdep_t.d.ts → types-CY-oHIMA.d.ts} +55 -4
- package/dist/{types-wtksMa4n.d.cts → types-DOjguhEX.d.cts} +55 -4
- package/package.json +70 -71
package/dist/core/index.cjs
CHANGED
|
@@ -24,9 +24,11 @@ __export(core_exports, {
|
|
|
24
24
|
CREDIT_AMOUNT_SCALE: () => CREDIT_AMOUNT_SCALE,
|
|
25
25
|
CreditError: () => CreditError,
|
|
26
26
|
CreditErrorCode: () => CreditErrorCode,
|
|
27
|
+
NO_EXPIRED_BACKING_PREFIX: () => NO_EXPIRED_BACKING_PREFIX,
|
|
27
28
|
RESERVATION_STATUSES: () => RESERVATION_STATUSES,
|
|
28
29
|
RESERVED_JOURNAL_KEY_PREFIX: () => RESERVED_JOURNAL_KEY_PREFIX,
|
|
29
30
|
TERMINAL_RESERVATION_STATUSES: () => TERMINAL_RESERVATION_STATUSES,
|
|
31
|
+
allocateExpiredBacking: () => allocateExpiredBacking,
|
|
30
32
|
assertDirectStatusWriteAllowed: () => assertDirectStatusWriteAllowed,
|
|
31
33
|
assertHoldPlaced: () => assertHoldPlaced,
|
|
32
34
|
assertKnownReservationStatus: () => assertKnownReservationStatus,
|
|
@@ -36,12 +38,16 @@ __export(core_exports, {
|
|
|
36
38
|
assertRepresentableTierAmount: () => assertRepresentableTierAmount,
|
|
37
39
|
assertUnkeyedDirectReservation: () => assertUnkeyedDirectReservation,
|
|
38
40
|
assertUnreferencedDirectTransaction: () => assertUnreferencedDirectTransaction,
|
|
41
|
+
assertValidBalanceTarget: () => assertValidBalanceTarget,
|
|
39
42
|
assertValidCreditAmount: () => assertValidCreditAmount,
|
|
40
43
|
assertValidIdempotencyKey: () => assertValidIdempotencyKey,
|
|
41
44
|
assertValidStoredAmount: () => assertValidStoredAmount,
|
|
42
45
|
assertValidStoredAmountRaw: () => assertValidStoredAmountRaw,
|
|
46
|
+
assertValidTierBalanceInput: () => assertValidTierBalanceInput,
|
|
43
47
|
backedBalanceFloor: () => backedBalanceFloor,
|
|
44
48
|
calculateAvailableCredits: () => calculateAvailableCredits,
|
|
49
|
+
canonicalAmount: () => canonicalAmount,
|
|
50
|
+
cappedBalance: () => cappedBalance,
|
|
45
51
|
classifyDatabaseError: () => classifyDatabaseError,
|
|
46
52
|
commitReservationWithJournal: () => commitReservationWithJournal,
|
|
47
53
|
createDeferredExecutor: () => createDeferredExecutor,
|
|
@@ -57,9 +63,13 @@ __export(core_exports, {
|
|
|
57
63
|
createUnsupportedOperationError: () => createUnsupportedOperationError,
|
|
58
64
|
createUserNotFoundError: () => createUserNotFoundError,
|
|
59
65
|
describePaymentMismatch: () => describePaymentMismatch,
|
|
66
|
+
expiredBackingPatch: () => expiredBackingPatch,
|
|
60
67
|
genericDeferred: () => genericDeferred,
|
|
61
68
|
getSqlState: () => getSqlState,
|
|
62
69
|
hasPlacedHold: () => hasPlacedHold,
|
|
70
|
+
heldExpiredBacking: () => heldExpiredBacking,
|
|
71
|
+
holdExpiredBacking: () => holdExpiredBacking,
|
|
72
|
+
holdPlacedAtAfter: () => holdPlacedAtAfter,
|
|
63
73
|
isCreditError: () => isCreditError,
|
|
64
74
|
isCreditedOutcome: () => isCreditedOutcome,
|
|
65
75
|
isIdempotencyConflictError: () => isIdempotencyConflictError,
|
|
@@ -75,12 +85,21 @@ __export(core_exports, {
|
|
|
75
85
|
isValidCreditAmount: () => isValidCreditAmount,
|
|
76
86
|
isValidIdempotencyKey: () => isValidIdempotencyKey,
|
|
77
87
|
isWinningOutcome: () => isWinningOutcome,
|
|
88
|
+
multiplyAmount: () => multiplyAmount,
|
|
78
89
|
noopDeferred: () => noopDeferred,
|
|
79
90
|
normalizePaymentRef: () => normalizePaymentRef,
|
|
80
91
|
numericToCents: () => numericToCents,
|
|
92
|
+
planBalanceCap: () => planBalanceCap,
|
|
93
|
+
planBalanceTarget: () => planBalanceTarget,
|
|
94
|
+
planHoldCommit: () => planHoldCommit,
|
|
95
|
+
recordedExpiredBacking: () => recordedExpiredBacking,
|
|
81
96
|
releaseReservationWithJournal: () => releaseReservationWithJournal,
|
|
82
97
|
reserveCreditsForOperation: () => reserveCreditsForOperation,
|
|
83
98
|
sameAmount: () => sameAmount,
|
|
99
|
+
settleExpiredHoldBacking: () => settleExpiredHoldBacking,
|
|
100
|
+
spendableBalance: () => spendableBalance,
|
|
101
|
+
storedAmountCeil: () => storedAmountCeil,
|
|
102
|
+
storedAmountFloor: () => storedAmountFloor,
|
|
84
103
|
storedMonthlyLimit: () => storedMonthlyLimit,
|
|
85
104
|
sumAmounts: () => sumAmounts,
|
|
86
105
|
synchronousDeferred: () => synchronousDeferred,
|
|
@@ -260,6 +279,40 @@ function floatSum(values) {
|
|
|
260
279
|
for (const value of values) total += value;
|
|
261
280
|
return total;
|
|
262
281
|
}
|
|
282
|
+
function multiplyAmount(unit, quantity) {
|
|
283
|
+
const cents = toCents(unit);
|
|
284
|
+
const product = cents === null ? Number.NaN : cents * quantity;
|
|
285
|
+
if (cents === null || cents < 0 || !Number.isSafeInteger(quantity) || quantity < 0 || !Number.isSafeInteger(product) || product > MAX_CENTS) {
|
|
286
|
+
throw new CreditError(
|
|
287
|
+
`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}`,
|
|
288
|
+
CreditErrorCode.INVALID_AMOUNT,
|
|
289
|
+
{ unit, quantity, max: CREDIT_AMOUNT_MAX, scale: CREDIT_AMOUNT_SCALE }
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
return product / 100;
|
|
293
|
+
}
|
|
294
|
+
var FLOAT_NOISE_FLOOR = 1e-6;
|
|
295
|
+
var FLOAT_NOISE_RELATIVE = 64 * Number.EPSILON;
|
|
296
|
+
function floatNoise(value) {
|
|
297
|
+
return Math.max(FLOAT_NOISE_FLOOR, Math.abs(value) * FLOAT_NOISE_RELATIVE);
|
|
298
|
+
}
|
|
299
|
+
function canonicalAmount(value) {
|
|
300
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return value;
|
|
301
|
+
const cents = Math.round(value * 100);
|
|
302
|
+
if (!Number.isSafeInteger(cents)) return value;
|
|
303
|
+
const onGrid = cents / 100 + 0;
|
|
304
|
+
return Math.abs(value - onGrid) < floatNoise(value) ? onGrid : value;
|
|
305
|
+
}
|
|
306
|
+
function storedAmountFloor(value) {
|
|
307
|
+
const canonical = canonicalAmount(value);
|
|
308
|
+
if (!Number.isFinite(canonical) || toCents(canonical) !== null) return canonical;
|
|
309
|
+
return Math.floor(canonical * 100) / 100;
|
|
310
|
+
}
|
|
311
|
+
function storedAmountCeil(value) {
|
|
312
|
+
const canonical = canonicalAmount(value);
|
|
313
|
+
if (!Number.isFinite(canonical) || toCents(canonical) !== null) return canonical;
|
|
314
|
+
return Math.ceil(canonical * 100) / 100;
|
|
315
|
+
}
|
|
263
316
|
function isValidCreditAmount(value) {
|
|
264
317
|
if (typeof value !== "number") return false;
|
|
265
318
|
const cents = toCents(value);
|
|
@@ -312,6 +365,150 @@ function backedBalanceFloor(reserved, bonusCredits) {
|
|
|
312
365
|
assertRepresentableAmount(floor, "backedBalanceFloor", { reserved, bonusCredits });
|
|
313
366
|
return floor;
|
|
314
367
|
}
|
|
368
|
+
function cappedBalance(balance, cap, reserved, bonusCredits) {
|
|
369
|
+
return Math.min(balance, Math.max(cap, backedBalanceFloor(reserved, bonusCredits)));
|
|
370
|
+
}
|
|
371
|
+
function heldExpiredBacking(balance, stored) {
|
|
372
|
+
if (!(stored !== void 0 && stored > 0)) return 0;
|
|
373
|
+
return Math.max(0, Math.min(stored, balance));
|
|
374
|
+
}
|
|
375
|
+
function spendableBalance(balance, expiredHoldBacking) {
|
|
376
|
+
return sumAmounts(balance, -heldExpiredBacking(balance, expiredHoldBacking));
|
|
377
|
+
}
|
|
378
|
+
function planBalanceTarget(target, reserved, bonusCredits) {
|
|
379
|
+
assertValidBalanceTarget(target, "balance target", { reserved, bonusCredits });
|
|
380
|
+
const balance = Math.max(target, backedBalanceFloor(reserved, bonusCredits));
|
|
381
|
+
return { balance, expiredHoldBacking: sumAmounts(balance, -target) };
|
|
382
|
+
}
|
|
383
|
+
function planBalanceCap(balance, cap, reserved, bonusCredits, expiredHoldBacking) {
|
|
384
|
+
const spendable = spendableBalance(balance, expiredHoldBacking);
|
|
385
|
+
const target = Math.min(spendable, cap);
|
|
386
|
+
const kept = Math.max(
|
|
387
|
+
sumAmounts(balance, -spendable),
|
|
388
|
+
sumAmounts(backedBalanceFloor(reserved, bonusCredits), -target)
|
|
389
|
+
);
|
|
390
|
+
const next = Math.min(balance, sumAmounts(target, kept));
|
|
391
|
+
return { balance: next, expiredHoldBacking: Math.max(0, sumAmounts(next, -target)) };
|
|
392
|
+
}
|
|
393
|
+
function settleExpiredHoldBacking(balance, expiredHoldBacking, holdBacking) {
|
|
394
|
+
const held = heldExpiredBacking(balance, expiredHoldBacking);
|
|
395
|
+
const used = Math.max(0, Math.min(held, holdBacking));
|
|
396
|
+
return { used, remaining: sumAmounts(held, -used) };
|
|
397
|
+
}
|
|
398
|
+
function planHoldCommit(balance, bonusCredits, expiredHoldBacking, holdBacking, amount) {
|
|
399
|
+
const backing = settleExpiredHoldBacking(balance, expiredHoldBacking, holdBacking);
|
|
400
|
+
const rest = sumAmounts(amount, -backing.used);
|
|
401
|
+
const fromSpendable = Math.min(spendableBalance(balance, expiredHoldBacking), rest);
|
|
402
|
+
const fromBonus = sumAmounts(rest, -fromSpendable);
|
|
403
|
+
return {
|
|
404
|
+
covered: fromBonus <= bonusCredits,
|
|
405
|
+
balance: sumAmounts(balance, -backing.used, -fromSpendable),
|
|
406
|
+
bonusCredits: sumAmounts(bonusCredits, -fromBonus),
|
|
407
|
+
expiredHoldBacking: backing.remaining
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
var NO_EXPIRED_BACKING_PREFIX = {
|
|
411
|
+
holdPlacedAt: "",
|
|
412
|
+
id: "",
|
|
413
|
+
share: 0
|
|
414
|
+
};
|
|
415
|
+
function compareHolds(a, b) {
|
|
416
|
+
if (a.holdPlacedAt !== b.holdPlacedAt) return a.holdPlacedAt < b.holdPlacedAt ? -1 : 1;
|
|
417
|
+
if (a.id === b.id) return 0;
|
|
418
|
+
return a.id < b.id ? -1 : 1;
|
|
419
|
+
}
|
|
420
|
+
function recordedExpiredBacking(hold) {
|
|
421
|
+
const stored = hold.expiredBacking;
|
|
422
|
+
if (typeof stored !== "number" || !Number.isFinite(stored) || stored <= 0) return 0;
|
|
423
|
+
if (!isValidCreditAmount(hold.amount)) return 0;
|
|
424
|
+
return Math.min(storedAmountCeil(stored), hold.amount);
|
|
425
|
+
}
|
|
426
|
+
function allocateExpiredBacking(holds, total) {
|
|
427
|
+
const placed = holds.filter((hold) => hold.holdPlacedAt && isValidCreditAmount(hold.amount)).map((hold) => ({ ...hold, holdPlacedAt: hold.holdPlacedAt })).sort(compareHolds);
|
|
428
|
+
const wanted = Math.max(0, total);
|
|
429
|
+
if (wanted === 0) return { total: 0, frontier: null };
|
|
430
|
+
const recorded = placed.filter((hold) => recordedExpiredBacking(hold) > 0);
|
|
431
|
+
const shares = new Map(recorded.map((hold) => [hold.id, recordedExpiredBacking(hold)]));
|
|
432
|
+
let left = sumAmounts(wanted, ...[...shares.values()].map((share) => -share));
|
|
433
|
+
for (const hold of [...recorded].reverse()) {
|
|
434
|
+
if (left >= 0) break;
|
|
435
|
+
const share = shares.get(hold.id);
|
|
436
|
+
const cut = Math.min(share, -left);
|
|
437
|
+
shares.set(hold.id, sumAmounts(share, -cut));
|
|
438
|
+
left = sumAmounts(left, cut);
|
|
439
|
+
}
|
|
440
|
+
let frontier = NO_EXPIRED_BACKING_PREFIX;
|
|
441
|
+
for (const hold of placed) {
|
|
442
|
+
if (left <= 0) break;
|
|
443
|
+
if (shares.has(hold.id)) continue;
|
|
444
|
+
const share = Math.min(hold.amount, left);
|
|
445
|
+
frontier = { holdPlacedAt: hold.holdPlacedAt, id: hold.id, share };
|
|
446
|
+
left = sumAmounts(left, -share);
|
|
447
|
+
}
|
|
448
|
+
for (const hold of recorded) {
|
|
449
|
+
if (left <= 0) break;
|
|
450
|
+
const share = shares.get(hold.id);
|
|
451
|
+
const added = Math.min(sumAmounts(hold.amount, -share), left);
|
|
452
|
+
shares.set(hold.id, sumAmounts(share, added));
|
|
453
|
+
left = sumAmounts(left, -added);
|
|
454
|
+
}
|
|
455
|
+
const rewrites = recorded.filter((hold) => shares.get(hold.id) !== hold.expiredBacking).map((hold) => ({ id: hold.id, expiredBacking: shares.get(hold.id) }));
|
|
456
|
+
return { total: wanted, frontier, ...rewrites.length > 0 ? { rewrites } : {} };
|
|
457
|
+
}
|
|
458
|
+
function holdExpiredBacking(hold, frontier, rewrites) {
|
|
459
|
+
const rewritten = rewrites?.find((rewrite) => rewrite.id === hold.id);
|
|
460
|
+
const recorded = recordedExpiredBacking(
|
|
461
|
+
rewritten ? { ...hold, expiredBacking: rewritten.expiredBacking } : hold
|
|
462
|
+
);
|
|
463
|
+
if (recorded > 0) return recorded;
|
|
464
|
+
if (!frontier || !hold.holdPlacedAt) return 0;
|
|
465
|
+
const order = compareHolds({ holdPlacedAt: hold.holdPlacedAt, id: hold.id }, frontier);
|
|
466
|
+
if (order > 0) return 0;
|
|
467
|
+
return order < 0 ? hold.amount : Math.min(frontier.share, hold.amount);
|
|
468
|
+
}
|
|
469
|
+
function holdPlacedAtAfter(now, frontier) {
|
|
470
|
+
if (!frontier?.holdPlacedAt) return now;
|
|
471
|
+
const earliest = new Date(Date.parse(frontier.holdPlacedAt) + 1).toISOString();
|
|
472
|
+
return now > earliest ? now : earliest;
|
|
473
|
+
}
|
|
474
|
+
function expiredBackingPatch(next, stored) {
|
|
475
|
+
const total = Math.max(0, next.total);
|
|
476
|
+
const patch = {};
|
|
477
|
+
const storedTotal = stored.expiredHoldBacking;
|
|
478
|
+
if (total > 0 || storedTotal !== void 0 && storedTotal !== 0) {
|
|
479
|
+
patch.expiredHoldBacking = total;
|
|
480
|
+
}
|
|
481
|
+
if (total <= 0) {
|
|
482
|
+
if (stored.expiredBackingFrontier) patch.expiredBackingFrontier = null;
|
|
483
|
+
} else if (next.frontier) {
|
|
484
|
+
patch.expiredBackingFrontier = next.frontier;
|
|
485
|
+
}
|
|
486
|
+
return patch;
|
|
487
|
+
}
|
|
488
|
+
function assertValidBalanceTarget(value, field, context) {
|
|
489
|
+
assertRepresentableAmount(value, field, context);
|
|
490
|
+
if (value < 0) {
|
|
491
|
+
throw new CreditError(
|
|
492
|
+
`${field} cannot be negative (got ${value})`,
|
|
493
|
+
CreditErrorCode.INVALID_AMOUNT,
|
|
494
|
+
{ field, value, ...context }
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
function assertValidTierBalanceInput(input, context) {
|
|
499
|
+
if (input.balanceCap === void 0) {
|
|
500
|
+
if (input.balance !== void 0) assertValidBalanceTarget(input.balance, "balance", context);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if (input.balance !== void 0) {
|
|
504
|
+
throw new CreditError(
|
|
505
|
+
"A tier update cannot set both balance and balanceCap",
|
|
506
|
+
CreditErrorCode.INVALID_AMOUNT,
|
|
507
|
+
{ balance: input.balance, balanceCap: input.balanceCap, ...context }
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
assertValidBalanceTarget(input.balanceCap, "balanceCap", context);
|
|
511
|
+
}
|
|
315
512
|
function assertRepresentableTierAmount(value, field, context) {
|
|
316
513
|
if (value === Number.POSITIVE_INFINITY) return;
|
|
317
514
|
assertRepresentableAmount(value, field, context);
|
|
@@ -714,9 +911,9 @@ var creditSystemConfigSchema = import_zod.z.object({
|
|
|
714
911
|
*/
|
|
715
912
|
reservationExpiryMs: import_zod.z.number().positive().default(5 * 60 * 1e3),
|
|
716
913
|
/**
|
|
717
|
-
* Default credits for new users (free tier)
|
|
914
|
+
* Default credits for new users (free tier). 0 means no signup grant.
|
|
718
915
|
*/
|
|
719
|
-
defaultFreeCredits: import_zod.z.number().
|
|
916
|
+
defaultFreeCredits: import_zod.z.number().min(0).default(25),
|
|
720
917
|
/**
|
|
721
918
|
* Grace period in days after subscription expires before downgrade
|
|
722
919
|
*/
|
|
@@ -1088,9 +1285,11 @@ async function reserveCreditsForOperation(repository, userId, amount, operationT
|
|
|
1088
1285
|
CREDIT_AMOUNT_SCALE,
|
|
1089
1286
|
CreditError,
|
|
1090
1287
|
CreditErrorCode,
|
|
1288
|
+
NO_EXPIRED_BACKING_PREFIX,
|
|
1091
1289
|
RESERVATION_STATUSES,
|
|
1092
1290
|
RESERVED_JOURNAL_KEY_PREFIX,
|
|
1093
1291
|
TERMINAL_RESERVATION_STATUSES,
|
|
1292
|
+
allocateExpiredBacking,
|
|
1094
1293
|
assertDirectStatusWriteAllowed,
|
|
1095
1294
|
assertHoldPlaced,
|
|
1096
1295
|
assertKnownReservationStatus,
|
|
@@ -1100,12 +1299,16 @@ async function reserveCreditsForOperation(repository, userId, amount, operationT
|
|
|
1100
1299
|
assertRepresentableTierAmount,
|
|
1101
1300
|
assertUnkeyedDirectReservation,
|
|
1102
1301
|
assertUnreferencedDirectTransaction,
|
|
1302
|
+
assertValidBalanceTarget,
|
|
1103
1303
|
assertValidCreditAmount,
|
|
1104
1304
|
assertValidIdempotencyKey,
|
|
1105
1305
|
assertValidStoredAmount,
|
|
1106
1306
|
assertValidStoredAmountRaw,
|
|
1307
|
+
assertValidTierBalanceInput,
|
|
1107
1308
|
backedBalanceFloor,
|
|
1108
1309
|
calculateAvailableCredits,
|
|
1310
|
+
canonicalAmount,
|
|
1311
|
+
cappedBalance,
|
|
1109
1312
|
classifyDatabaseError,
|
|
1110
1313
|
commitReservationWithJournal,
|
|
1111
1314
|
createDeferredExecutor,
|
|
@@ -1121,9 +1324,13 @@ async function reserveCreditsForOperation(repository, userId, amount, operationT
|
|
|
1121
1324
|
createUnsupportedOperationError,
|
|
1122
1325
|
createUserNotFoundError,
|
|
1123
1326
|
describePaymentMismatch,
|
|
1327
|
+
expiredBackingPatch,
|
|
1124
1328
|
genericDeferred,
|
|
1125
1329
|
getSqlState,
|
|
1126
1330
|
hasPlacedHold,
|
|
1331
|
+
heldExpiredBacking,
|
|
1332
|
+
holdExpiredBacking,
|
|
1333
|
+
holdPlacedAtAfter,
|
|
1127
1334
|
isCreditError,
|
|
1128
1335
|
isCreditedOutcome,
|
|
1129
1336
|
isIdempotencyConflictError,
|
|
@@ -1139,12 +1346,21 @@ async function reserveCreditsForOperation(repository, userId, amount, operationT
|
|
|
1139
1346
|
isValidCreditAmount,
|
|
1140
1347
|
isValidIdempotencyKey,
|
|
1141
1348
|
isWinningOutcome,
|
|
1349
|
+
multiplyAmount,
|
|
1142
1350
|
noopDeferred,
|
|
1143
1351
|
normalizePaymentRef,
|
|
1144
1352
|
numericToCents,
|
|
1353
|
+
planBalanceCap,
|
|
1354
|
+
planBalanceTarget,
|
|
1355
|
+
planHoldCommit,
|
|
1356
|
+
recordedExpiredBacking,
|
|
1145
1357
|
releaseReservationWithJournal,
|
|
1146
1358
|
reserveCreditsForOperation,
|
|
1147
1359
|
sameAmount,
|
|
1360
|
+
settleExpiredHoldBacking,
|
|
1361
|
+
spendableBalance,
|
|
1362
|
+
storedAmountCeil,
|
|
1363
|
+
storedAmountFloor,
|
|
1148
1364
|
storedMonthlyLimit,
|
|
1149
1365
|
sumAmounts,
|
|
1150
1366
|
synchronousDeferred,
|