@amboras-dev/money 0.0.0-stage → 0.2.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/README.md +401 -2
- package/dist/ai.cjs +185 -0
- package/dist/ai.d.cts +97 -0
- package/dist/ai.d.ts +97 -0
- package/dist/ai.js +175 -0
- package/dist/allocate-CZRg4TOP.cjs +222 -0
- package/dist/allocate-X7_jAtOj.js +199 -0
- package/dist/amount-BV0O-aN8.cjs +241 -0
- package/dist/amount-BoYBMgK9.js +200 -0
- package/dist/arithmetic-BhNwisjb.js +132 -0
- package/dist/arithmetic-DjT4Kzgt.cjs +233 -0
- package/dist/billing.cjs +184 -0
- package/dist/billing.d.cts +81 -0
- package/dist/billing.d.ts +81 -0
- package/dist/billing.js +175 -0
- package/dist/commerce.cjs +536 -0
- package/dist/commerce.d.cts +293 -0
- package/dist/commerce.d.ts +293 -0
- package/dist/commerce.js +516 -0
- package/dist/format-BdPnijvn.js +99 -0
- package/dist/format-DLhno7eX.cjs +128 -0
- package/dist/fx.cjs +407 -0
- package/dist/fx.d.cts +157 -0
- package/dist/fx.d.ts +157 -0
- package/dist/fx.js +374 -0
- package/dist/index.cjs +352 -0
- package/dist/index.d.cts +300 -0
- package/dist/index.d.ts +300 -0
- package/dist/index.js +276 -0
- package/dist/input.cjs +163 -0
- package/dist/input.d.cts +203 -0
- package/dist/input.d.ts +203 -0
- package/dist/input.js +153 -0
- package/dist/iso-minor-CqkWDxHS.js +48 -0
- package/dist/iso-minor-wSwJN57T.cjs +65 -0
- package/dist/medusa-4K8N1pUc.d.cts +87 -0
- package/dist/medusa-DBXJXXvF.d.ts +87 -0
- package/dist/medusa-DqJY1qpj.js +116 -0
- package/dist/medusa-lRY9xlsf.cjs +175 -0
- package/dist/money-B-3aFjNJ.d.cts +248 -0
- package/dist/money-B-3aFjNJ.d.ts +248 -0
- package/dist/money-B3wH_Z4S.cjs +3990 -0
- package/dist/money-WhnGiI00.js +3583 -0
- package/dist/number-text-BWu67jUq.cjs +145 -0
- package/dist/number-text-DE-q4jO5.js +134 -0
- package/dist/own-D7544D3k.js +4 -0
- package/dist/own-oP9DXFWv.cjs +9 -0
- package/dist/percent-C0InC2t2.cjs +43 -0
- package/dist/percent-D1VB9xXG.js +20 -0
- package/dist/rate-CVeMii4a.cjs +217 -0
- package/dist/rate-DBPiYgT1.d.ts +63 -0
- package/dist/rate-DicStBue.js +152 -0
- package/dist/rate-tqK8ijG0.d.cts +63 -0
- package/dist/reporting-CG-ai2sM.d.cts +31 -0
- package/dist/reporting-tpGJbqYp.d.ts +31 -0
- package/dist/retail-1mu888J1.js +69 -0
- package/dist/retail-C_BPLvMj.d.cts +31 -0
- package/dist/retail-Cml8YSKg.cjs +86 -0
- package/dist/retail-DRUTTQoJ.d.ts +31 -0
- package/dist/rounding-DHBtNCHG.cjs +79 -0
- package/dist/rounding-g9rvye0k.js +50 -0
- package/dist/sql.cjs +290 -0
- package/dist/sql.d.cts +147 -0
- package/dist/sql.d.ts +147 -0
- package/dist/sql.js +271 -0
- package/dist/testing.cjs +61995 -0
- package/dist/testing.d.cts +125 -0
- package/dist/testing.d.ts +125 -0
- package/dist/testing.js +61986 -0
- package/dist/wire-Bhuut8cW.cjs +2097 -0
- package/dist/wire-CaUN9Vf_.d.ts +309 -0
- package/dist/wire-sW0yzqth.js +1888 -0
- package/dist/wire-xjYXv4go.d.cts +309 -0
- package/dist/wire.cjs +37 -0
- package/dist/wire.d.cts +2 -0
- package/dist/wire.d.ts +2 -0
- package/dist/wire.js +2 -0
- package/package.json +158 -3
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { R as DecimalText, i as Money } from "./money-B-3aFjNJ.js";
|
|
2
|
+
//#region src/billing/usage.d.ts
|
|
3
|
+
/** USD with sub-cent precision, as canonical decimal text (`'0.001234'`). Never merchant money. */
|
|
4
|
+
type UsdAmount = DecimalText;
|
|
5
|
+
interface UsdCostLine {
|
|
6
|
+
/** Tokens, seconds, images...; negative for an adjustment. */
|
|
7
|
+
quantity: number;
|
|
8
|
+
/** USD per million units (token prices). Give this or `usdPerUnit`. */
|
|
9
|
+
usdPerMillion?: number | DecimalText;
|
|
10
|
+
/** USD per unit. */
|
|
11
|
+
usdPerUnit?: number | DecimalText;
|
|
12
|
+
/** A surcharge multiplier (long-context 2x, 1.5x). Default 1. */
|
|
13
|
+
multiplier?: number | DecimalText;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The exact sum of `quantity x rate x multiplier` over `lines`, rounded half-up once to 6
|
|
17
|
+
* decimals. Replaces the token-cost formulas (`computeCost`, the unpriced-model fallback,
|
|
18
|
+
* the cloner's `estimateCost`). Each line needs exactly one of `usdPerMillion` / `usdPerUnit`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function usdCost(lines: readonly UsdCostLine[]): UsdAmount;
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/billing/credits.d.ts
|
|
23
|
+
/**
|
|
24
|
+
* Credits for a USD cost: `ceil(usd x 100)` (0.07 gives 7, 1.1 gives 110, 0.0049 and 1e-7
|
|
25
|
+
* give 1). Text and Money are exact. A number is first rounded half-even to 8 decimals, so
|
|
26
|
+
* float noise never adds a credit (`0.07 * 3` gives 21, `1.1 * 1.1` gives 121). USD Money is
|
|
27
|
+
* cents already. A negative cost throws `amount_invalid`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function usdToCredits(usd: UsdAmount | number | Money): number;
|
|
30
|
+
/** The USD a number of credits is worth: `credits / 100`, exact. */
|
|
31
|
+
export declare function creditsToUsd(credits: number): UsdAmount;
|
|
32
|
+
/**
|
|
33
|
+
* The credits charged for a share (0..1) of a quoted run: `min(quoted, ceil(quoted x share))`,
|
|
34
|
+
* with the product held to 6 decimals (half-up) before the ceil, as the quote does, so a float
|
|
35
|
+
* share such as 0.28000000000000003 never adds a credit.
|
|
36
|
+
*/
|
|
37
|
+
export declare function prorateCredits(quoted: number, share: number | DecimalText): number;
|
|
38
|
+
/** Credits rounded up to a whole `step` (default 50) for a quote: `ceil(credits / step) x step`. */
|
|
39
|
+
export declare function bucketCredits(credits: number, step?: number): number;
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/billing/plans.d.ts
|
|
42
|
+
/** The yearly price: `yearly` when set, else `monthly x 12`. */
|
|
43
|
+
export declare function annualPrice(input: {
|
|
44
|
+
monthly: Money;
|
|
45
|
+
yearly?: Money | null;
|
|
46
|
+
}): Money;
|
|
47
|
+
/** `yearly / 12` half-up to a storage unit (a cent); whole-dollar headlines use `roundToUnit(..., { unit: 'major' })`. */
|
|
48
|
+
export declare function monthlyEquivalent(yearly: Money): Money;
|
|
49
|
+
type Coupon = {
|
|
50
|
+
readonly percentOff: number | DecimalText;
|
|
51
|
+
} | {
|
|
52
|
+
readonly amountOff: Money;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* `m` after `coupons`. `'sequential'` (default, Stripe): in order, each on what the previous
|
|
56
|
+
* left, a percent taking `round(amount x pct / 100)` half-up, clamped at 0 after each.
|
|
57
|
+
* `'additive'`: the percents summed and taken in one rounding, plus the amounts, clamped at 0.
|
|
58
|
+
* An amount-off coupon in another currency throws `currency_mismatch`.
|
|
59
|
+
*/
|
|
60
|
+
export declare function applyCoupons(m: Money, coupons: readonly Coupon[], options?: {
|
|
61
|
+
mode?: 'sequential' | 'additive';
|
|
62
|
+
}): Money;
|
|
63
|
+
/**
|
|
64
|
+
* The line amount that is still `m` after the subscription's percent coupons take their cut
|
|
65
|
+
* (Stripe discounts a negative invoice line too). A verbatim port of orch `grossUpCredit`,
|
|
66
|
+
* float arithmetic included, because a credit already granted must not change by a cent:
|
|
67
|
+
* `Math.round(m / prod(1 - p / 100))` with the product taken in float64, so a tie is resolved
|
|
68
|
+
* by the float product (9 cents at [10, 20] gives 12, since 0.9 x 0.8 is 0.7200000000000001);
|
|
69
|
+
* when the kept share is not positive (100% off), `m` unchanged.
|
|
70
|
+
* It inverts `applyCoupons` exactly for one percent coupon. For stacked percents it can miss
|
|
71
|
+
* by a cent (about 4% of amounts at [10, 20] or [30, 15]): Stripe rounds each coupon's cut,
|
|
72
|
+
* while this divides by the unrounded product (11 cents at [10, 20] grosses up to 15, which
|
|
73
|
+
* the coupons take to 10).
|
|
74
|
+
*/
|
|
75
|
+
export declare function grossUpForPercentOffs(m: Money, percents: readonly (number | DecimalText)[]): Money;
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/billing.d.ts
|
|
78
|
+
/** USD cents: Money in USD (USD stored amounts are cents). */
|
|
79
|
+
export type UsdCents = Money;
|
|
80
|
+
//#endregion
|
|
81
|
+
export type { Coupon, UsdAmount, UsdCostLine };
|
package/dist/billing.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { $ as describeValue, B as decimalOf, Q as MoneyError, dt as roundToScale, et as addDecimal, it as formatDecimal, n as fromMajor, pt as toDecimalText, rt as divRound, s as storedDecimal, st as multiplyDecimal, t as boundedMoney } from "./money-WhnGiI00.js";
|
|
2
|
+
import { n as hundredPercent, r as percentOfUnits } from "./percent-D1VB9xXG.js";
|
|
3
|
+
//#region src/billing/credits.ts
|
|
4
|
+
function wholeNumber(value, name, min) {
|
|
5
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < min) throw new MoneyError("amount_invalid", `${name} must be an integer >= ${min}, got ${describeValue(value)}`);
|
|
6
|
+
return BigInt(value);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Fraction digits a float USD cost is held to before the ceil: 1e-8 USD, the granularity of
|
|
10
|
+
* orch `costUsdToCredits`' 1e-6-credit drift epsilon. Coarse enough to absorb float noise
|
|
11
|
+
* (`0.07 * 3` is 0.21000000000000002), fine enough to keep sub-micro costs (1e-7 gives 1).
|
|
12
|
+
*/
|
|
13
|
+
const FLOAT_USD_DIGITS = 8;
|
|
14
|
+
/**
|
|
15
|
+
* Credits for a USD cost: `ceil(usd x 100)` (0.07 gives 7, 1.1 gives 110, 0.0049 and 1e-7
|
|
16
|
+
* give 1). Text and Money are exact. A number is first rounded half-even to 8 decimals, so
|
|
17
|
+
* float noise never adds a credit (`0.07 * 3` gives 21, `1.1 * 1.1` gives 121). USD Money is
|
|
18
|
+
* cents already. A negative cost throws `amount_invalid`.
|
|
19
|
+
*/
|
|
20
|
+
function usdToCredits(usd) {
|
|
21
|
+
let cents;
|
|
22
|
+
if (typeof usd === "object" && usd !== null) {
|
|
23
|
+
const { info } = storedDecimal(usd);
|
|
24
|
+
if (info.code !== "USD") throw new MoneyError("currency_mismatch", `Credits are USD cents, got ${info.code}`);
|
|
25
|
+
cents = usd.amount;
|
|
26
|
+
} else {
|
|
27
|
+
const parsed = decimalOf(usd, true, "usd");
|
|
28
|
+
const exact = formatDecimal(typeof usd === "number" ? roundToScale(parsed, FLOAT_USD_DIGITS, "half-even") : parsed);
|
|
29
|
+
cents = fromMajor(exact, "USD", {
|
|
30
|
+
precision: "round",
|
|
31
|
+
rounding: "ceil",
|
|
32
|
+
field: "usd"
|
|
33
|
+
}).amount;
|
|
34
|
+
}
|
|
35
|
+
if (cents < 0) throw new MoneyError("amount_invalid", `A USD cost must not be negative, got ${describeValue(usd)}`);
|
|
36
|
+
return cents;
|
|
37
|
+
}
|
|
38
|
+
/** The USD a number of credits is worth: `credits / 100`, exact. */
|
|
39
|
+
function creditsToUsd(credits) {
|
|
40
|
+
return toDecimalText({
|
|
41
|
+
units: wholeNumber(credits, "credits", Number.MIN_SAFE_INTEGER),
|
|
42
|
+
scale: 2
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The credits charged for a share (0..1) of a quoted run: `min(quoted, ceil(quoted x share))`,
|
|
47
|
+
* with the product held to 6 decimals (half-up) before the ceil, as the quote does, so a float
|
|
48
|
+
* share such as 0.28000000000000003 never adds a credit.
|
|
49
|
+
*/
|
|
50
|
+
function prorateCredits(quoted, share) {
|
|
51
|
+
const q = wholeNumber(quoted, "quoted", 0);
|
|
52
|
+
const s = decimalOf(share, false, "share");
|
|
53
|
+
const product = roundToScale({
|
|
54
|
+
units: q * s.units,
|
|
55
|
+
scale: s.scale
|
|
56
|
+
}, 6, "half-up");
|
|
57
|
+
const charged = roundToScale(product, 0, "ceil").units;
|
|
58
|
+
return Number(charged < q ? charged : q);
|
|
59
|
+
}
|
|
60
|
+
/** Credits rounded up to a whole `step` (default 50) for a quote: `ceil(credits / step) x step`. */
|
|
61
|
+
function bucketCredits(credits, step = 50) {
|
|
62
|
+
const c = wholeNumber(credits, "credits", Number.MIN_SAFE_INTEGER);
|
|
63
|
+
const s = wholeNumber(step, "step", 1);
|
|
64
|
+
return Number(divRound(c, s, "ceil") * s);
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/billing/plans.ts
|
|
68
|
+
function checkSame(m, other, verb) {
|
|
69
|
+
const info = storedDecimal(m).info;
|
|
70
|
+
const own = storedDecimal(other).info;
|
|
71
|
+
if (own.code !== info.code) throw new MoneyError("currency_mismatch", `Cannot ${verb} ${own.code} with ${info.code}`);
|
|
72
|
+
return info;
|
|
73
|
+
}
|
|
74
|
+
/** The yearly price: `yearly` when set, else `monthly x 12`. */
|
|
75
|
+
function annualPrice(input) {
|
|
76
|
+
const info = storedDecimal(input.monthly).info;
|
|
77
|
+
if (input.yearly !== null && input.yearly !== void 0) {
|
|
78
|
+
checkSame(input.monthly, input.yearly, "price");
|
|
79
|
+
return input.yearly;
|
|
80
|
+
}
|
|
81
|
+
return boundedMoney(BigInt(input.monthly.amount) * 12n, info);
|
|
82
|
+
}
|
|
83
|
+
/** `yearly / 12` half-up to a storage unit (a cent); whole-dollar headlines use `roundToUnit(..., { unit: 'major' })`. */
|
|
84
|
+
function monthlyEquivalent(yearly) {
|
|
85
|
+
const { info } = storedDecimal(yearly);
|
|
86
|
+
return boundedMoney(divRound(BigInt(yearly.amount), 12n, "half-up"), info);
|
|
87
|
+
}
|
|
88
|
+
/** A percent in 0..100, else `amount_invalid`. */
|
|
89
|
+
function couponPercent(value, name) {
|
|
90
|
+
const pct = decimalOf(value, false, name);
|
|
91
|
+
if (pct.units > hundredPercent(pct.scale)) throw new MoneyError("amount_invalid", `${name} must be at most 100, got ${describeValue(value)}`);
|
|
92
|
+
return pct;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* `m` after `coupons`. `'sequential'` (default, Stripe): in order, each on what the previous
|
|
96
|
+
* left, a percent taking `round(amount x pct / 100)` half-up, clamped at 0 after each.
|
|
97
|
+
* `'additive'`: the percents summed and taken in one rounding, plus the amounts, clamped at 0.
|
|
98
|
+
* An amount-off coupon in another currency throws `currency_mismatch`.
|
|
99
|
+
*/
|
|
100
|
+
function applyCoupons(m, coupons, options = {}) {
|
|
101
|
+
const { info } = storedDecimal(m);
|
|
102
|
+
let amount = BigInt(m.amount);
|
|
103
|
+
const percentCut = (base, value) => percentOfUnits(base, couponPercent(value, "percentOff"), "half-up");
|
|
104
|
+
if (options.mode === "additive") {
|
|
105
|
+
let percents = {
|
|
106
|
+
units: 0n,
|
|
107
|
+
scale: 0
|
|
108
|
+
};
|
|
109
|
+
let off = 0n;
|
|
110
|
+
for (const c of coupons) if ("amountOff" in c) {
|
|
111
|
+
checkSame(m, c.amountOff, "apply a coupon in");
|
|
112
|
+
off += BigInt(c.amountOff.amount);
|
|
113
|
+
} else percents = addDecimal(percents, couponPercent(c.percentOff, "percentOff"));
|
|
114
|
+
const cut = percentOfUnits(amount, percents, "half-up") + off;
|
|
115
|
+
return boundedMoney(amount > cut ? amount - cut : 0n, info);
|
|
116
|
+
}
|
|
117
|
+
for (const c of coupons) {
|
|
118
|
+
if ("amountOff" in c) {
|
|
119
|
+
checkSame(m, c.amountOff, "apply a coupon in");
|
|
120
|
+
amount -= BigInt(c.amountOff.amount);
|
|
121
|
+
} else amount -= percentCut(amount, c.percentOff);
|
|
122
|
+
if (amount < 0n) amount = 0n;
|
|
123
|
+
}
|
|
124
|
+
return boundedMoney(amount, info);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The line amount that is still `m` after the subscription's percent coupons take their cut
|
|
128
|
+
* (Stripe discounts a negative invoice line too). A verbatim port of orch `grossUpCredit`,
|
|
129
|
+
* float arithmetic included, because a credit already granted must not change by a cent:
|
|
130
|
+
* `Math.round(m / prod(1 - p / 100))` with the product taken in float64, so a tie is resolved
|
|
131
|
+
* by the float product (9 cents at [10, 20] gives 12, since 0.9 x 0.8 is 0.7200000000000001);
|
|
132
|
+
* when the kept share is not positive (100% off), `m` unchanged.
|
|
133
|
+
* It inverts `applyCoupons` exactly for one percent coupon. For stacked percents it can miss
|
|
134
|
+
* by a cent (about 4% of amounts at [10, 20] or [30, 15]): Stripe rounds each coupon's cut,
|
|
135
|
+
* while this divides by the unrounded product (11 cents at [10, 20] grosses up to 15, which
|
|
136
|
+
* the coupons take to 10).
|
|
137
|
+
*/
|
|
138
|
+
function grossUpForPercentOffs(m, percents) {
|
|
139
|
+
const { info } = storedDecimal(m);
|
|
140
|
+
const kept = percents.reduce((share, value) => {
|
|
141
|
+
return share * (1 - Number(formatDecimal(decimalOf(value, true, "percent"))) / 100);
|
|
142
|
+
}, 1);
|
|
143
|
+
if (!(kept > 0)) return m;
|
|
144
|
+
return boundedMoney(BigInt(Math.round(m.amount / kept)), info);
|
|
145
|
+
}
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region src/billing/usage.ts
|
|
148
|
+
const USD_DIGITS = 6;
|
|
149
|
+
/**
|
|
150
|
+
* The exact sum of `quantity x rate x multiplier` over `lines`, rounded half-up once to 6
|
|
151
|
+
* decimals. Replaces the token-cost formulas (`computeCost`, the unpriced-model fallback,
|
|
152
|
+
* the cloner's `estimateCost`). Each line needs exactly one of `usdPerMillion` / `usdPerUnit`.
|
|
153
|
+
*/
|
|
154
|
+
function usdCost(lines) {
|
|
155
|
+
let total = {
|
|
156
|
+
units: 0n,
|
|
157
|
+
scale: 0
|
|
158
|
+
};
|
|
159
|
+
for (let i = 0; i < lines.length; i++) {
|
|
160
|
+
const line = lines[i];
|
|
161
|
+
const perMillion = line?.usdPerMillion !== void 0;
|
|
162
|
+
if (perMillion === (line?.usdPerUnit !== void 0)) throw new MoneyError("amount_invalid", `lines[${i}] needs exactly one of usdPerMillion and usdPerUnit, got ${describeValue(line)}`);
|
|
163
|
+
const quantity = decimalOf(line.quantity, true, `lines[${i}].quantity`);
|
|
164
|
+
const rate = decimalOf(perMillion ? line.usdPerMillion : line.usdPerUnit, true, `lines[${i}].rate`);
|
|
165
|
+
const multiplier = decimalOf(line.multiplier ?? 1, true, `lines[${i}].multiplier`);
|
|
166
|
+
const cost = multiplyDecimal(multiplyDecimal(quantity, rate), multiplier);
|
|
167
|
+
total = addDecimal(total, perMillion ? {
|
|
168
|
+
units: cost.units,
|
|
169
|
+
scale: cost.scale + 6
|
|
170
|
+
} : cost);
|
|
171
|
+
}
|
|
172
|
+
return toDecimalText(roundToScale(total, USD_DIGITS, "half-up"));
|
|
173
|
+
}
|
|
174
|
+
//#endregion
|
|
175
|
+
export { annualPrice, applyCoupons, bucketCredits, creditsToUsd, grossUpForPercentOffs, monthlyEquivalent, prorateCredits, usdCost, usdToCredits };
|