@fleetless/contracts 6.2.0 → 6.3.0-next.1
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/CHANGELOG.md +40 -0
- package/artifacts/openapi.json +4322 -1887
- package/artifacts/routes.json +326 -2
- package/artifacts/schema/billing-address.schema.json +49 -0
- package/artifacts/schema/billing-cancel-request.schema.json +12 -0
- package/artifacts/schema/billing-change-request.schema.json +60 -0
- package/artifacts/schema/billing-change-response.schema.json +765 -0
- package/artifacts/schema/billing-details-update.schema.json +25 -0
- package/artifacts/schema/billing-details.schema.json +176 -0
- package/artifacts/schema/billing-invoice.schema.json +76 -0
- package/artifacts/schema/billing-view.schema.json +671 -0
- package/artifacts/schema/checkout-request.schema.json +252 -0
- package/artifacts/schema/checkout-response.schema.json +22 -0
- package/artifacts/schema/checkout-status.schema.json +49 -0
- package/artifacts/schema/payment-method-change-request.schema.json +19 -0
- package/artifacts/schema/payment-method.schema.json +80 -0
- package/artifacts/schema/payment-provider-unavailable-details.schema.json +29 -0
- package/artifacts/schema/vat-id-check-request.schema.json +22 -0
- package/artifacts/schema/vat-id-check-response.schema.json +36 -0
- package/dist/billing.d.ts +778 -0
- package/dist/billing.js +441 -0
- package/dist/errors.d.ts +17 -5
- package/dist/errors.js +31 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +5 -1
- package/dist/plans.d.ts +9 -9
- package/dist/realtime.d.ts +2 -2
- package/dist/rest.d.ts +3 -3
- package/dist/routes.d.ts +1 -1
- package/dist/routes.js +181 -4
- package/package.json +1 -1
package/dist/billing.js
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { ADDONS, PLANS, PLAN_ORDER, addonKey, orgAddons, planCurrency, planId, } from './plans.js';
|
|
4
|
+
/**
|
|
5
|
+
* Countries, VAT and the money math for billing through Mollie
|
|
6
|
+
* (fleetless/fleetless#104).
|
|
7
|
+
*
|
|
8
|
+
* Every price here reads from `plans.ts`'s `PLANS` / `ADDONS` catalogue,
|
|
9
|
+
* never a cent amount of its own, so a price change in one place is a price
|
|
10
|
+
* change everywhere. Money is integer cents throughout; the one rounding a
|
|
11
|
+
* proration or a VAT amount is allowed is `Math.round`, applied exactly
|
|
12
|
+
* once (`prorateCents`, `vatCents`, and `changeNetCents`'s single
|
|
13
|
+
* proration).
|
|
14
|
+
*/
|
|
15
|
+
/** ISO 3166-1 alpha-2, upper case. Greece is `GR` here; VIES calls it `EL`. */
|
|
16
|
+
export const countryCode = z.string().regex(/^[A-Z]{2}$/);
|
|
17
|
+
/** The 27 EU member states, as the billing country. Greece is `GR`. */
|
|
18
|
+
export const EU_COUNTRIES = [
|
|
19
|
+
'AT', 'BE', 'BG', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GR', 'HR', 'HU', 'IE', 'IT', 'LT', 'LU', 'LV',
|
|
20
|
+
'MT', 'NL', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK',
|
|
21
|
+
];
|
|
22
|
+
/** The seller: Dehne Robotik GmbH. Fleetless never charges another EU country's VAT. */
|
|
23
|
+
export const SELLER_COUNTRY = 'DE';
|
|
24
|
+
/** Germany's VAT rate, in whole percent. */
|
|
25
|
+
export const DE_VAT_RATE_PERCENT = 19;
|
|
26
|
+
/** Whether `country` is one of the 27 EU member states. */
|
|
27
|
+
export function isEuCountry(country) {
|
|
28
|
+
return EU_COUNTRIES.includes(country);
|
|
29
|
+
}
|
|
30
|
+
/** EUR in the EU, USD elsewhere. */
|
|
31
|
+
export function currencyForCountry(country) {
|
|
32
|
+
return isEuCountry(country) ? 'eur' : 'usd';
|
|
33
|
+
}
|
|
34
|
+
/** Who is paying: a company (may hold a VAT ID) or a person (a full name, never a VAT ID). */
|
|
35
|
+
export const payerKind = z.enum(['company', 'person']);
|
|
36
|
+
/** The billing cycle: monthly, or yearly at twelve months for 15 % off (#103). */
|
|
37
|
+
export const billingCycle = z.enum(['monthly', 'yearly']);
|
|
38
|
+
/**
|
|
39
|
+
* How a charge is taxed: `de_standard` (German VAT), `reverse_charge` (0 %,
|
|
40
|
+
* an EU company whose VAT ID VIES confirms) or `outside_eu` (0 %, no
|
|
41
|
+
* reverse-charge note).
|
|
42
|
+
*/
|
|
43
|
+
export const vatTreatment = z.enum(['de_standard', 'reverse_charge', 'outside_eu']);
|
|
44
|
+
/**
|
|
45
|
+
* Why `vatFor` refused a payer: `eu_person` (a person outside Germany, in
|
|
46
|
+
* another EU country), `vat_id_required` (a company in another EU country
|
|
47
|
+
* gave no VAT ID) or `vat_id_invalid` (VIES said the given VAT ID is
|
|
48
|
+
* invalid).
|
|
49
|
+
*/
|
|
50
|
+
export const payerRefusalRule = z.enum(['eu_person', 'vat_id_required', 'vat_id_invalid']);
|
|
51
|
+
/**
|
|
52
|
+
* Who may pay, and the VAT they pay. `vatIdValid`: `null` = no VAT ID
|
|
53
|
+
* given; `true` = VIES valid **or unverified** (VIES unreachable is
|
|
54
|
+
* accepted); `false` = VIES said invalid.
|
|
55
|
+
*
|
|
56
|
+
* Germany is unconditional — company or person, VAT ID optional, 19 %
|
|
57
|
+
* regardless of whether a given ID checks out. A German VAT ID's validity
|
|
58
|
+
* never gates checkout, only the renewal sweep later (dunning);
|
|
59
|
+
* `vatFor` only ever prices the charge in hand.
|
|
60
|
+
*/
|
|
61
|
+
export function vatFor(input) {
|
|
62
|
+
const { kind, country, vatIdValid } = input;
|
|
63
|
+
if (country === SELLER_COUNTRY)
|
|
64
|
+
return { allowed: true, treatment: 'de_standard', rate_percent: DE_VAT_RATE_PERCENT };
|
|
65
|
+
if (isEuCountry(country)) {
|
|
66
|
+
if (kind === 'person')
|
|
67
|
+
return { allowed: false, rule: 'eu_person' };
|
|
68
|
+
if (vatIdValid === true)
|
|
69
|
+
return { allowed: true, treatment: 'reverse_charge', rate_percent: 0 };
|
|
70
|
+
if (vatIdValid === false)
|
|
71
|
+
return { allowed: false, rule: 'vat_id_invalid' };
|
|
72
|
+
return { allowed: false, rule: 'vat_id_required' };
|
|
73
|
+
}
|
|
74
|
+
return { allowed: true, treatment: 'outside_eu', rate_percent: 0 };
|
|
75
|
+
}
|
|
76
|
+
/** VIES's member-state code: `EL` for `GR`, the country otherwise. */
|
|
77
|
+
export function viesCountry(country) {
|
|
78
|
+
return country === 'GR' ? 'EL' : country;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Upper-case, without spaces, dots and dashes, and without a leading
|
|
82
|
+
* country prefix: the VIES code (`EL` for Greece) or, for Greece only, the
|
|
83
|
+
* address code `GR` as well, since a payer is as likely to type that. `null` for an
|
|
84
|
+
* empty string (trimmed).
|
|
85
|
+
*/
|
|
86
|
+
export function normalizeVatId(country, raw) {
|
|
87
|
+
const trimmed = raw.trim();
|
|
88
|
+
if (trimmed === '')
|
|
89
|
+
return null;
|
|
90
|
+
let cleaned = trimmed.toUpperCase().replace(/[\s.-]/g, '');
|
|
91
|
+
const vies = viesCountry(country);
|
|
92
|
+
const prefixes = country === 'GR' ? [vies, country] : [vies];
|
|
93
|
+
for (const prefix of prefixes) {
|
|
94
|
+
if (cleaned.startsWith(prefix)) {
|
|
95
|
+
cleaned = cleaned.slice(prefix.length);
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return vies + cleaned;
|
|
100
|
+
}
|
|
101
|
+
function priceKeyFor(cycle, currency) {
|
|
102
|
+
return `${currency}_${cycle === 'monthly' ? 'month' : 'year'}`;
|
|
103
|
+
}
|
|
104
|
+
function planPriceCentsFor(plan, cycle, currency) {
|
|
105
|
+
const prices = PLANS[plan].prices;
|
|
106
|
+
if (prices === null)
|
|
107
|
+
throw new Error(`billing: ${plan} has no catalogue price — sold by contract, never self-service`);
|
|
108
|
+
return prices[priceKeyFor(cycle, currency)];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Net price of one period: the plan plus each add-on × units, for the
|
|
112
|
+
* cycle and currency. Add-ons count only on Pro — `plans.ts`'s own rule for
|
|
113
|
+
* which plans may buy them at all (`planFeature.addons`). Throws for
|
|
114
|
+
* `enterprise`: its price is `null`, sold by contract, and self-service
|
|
115
|
+
* never charges it.
|
|
116
|
+
*/
|
|
117
|
+
export function periodNetCents(input) {
|
|
118
|
+
const { plan, addons, cycle, currency } = input;
|
|
119
|
+
const key = priceKeyFor(cycle, currency);
|
|
120
|
+
let total = planPriceCentsFor(plan, cycle, currency);
|
|
121
|
+
if (plan === 'pro') {
|
|
122
|
+
for (const addon of addonKey.options) {
|
|
123
|
+
const units = addons[addon];
|
|
124
|
+
if (units > 0)
|
|
125
|
+
total += ADDONS[addon].prices[key] * units;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return total;
|
|
129
|
+
}
|
|
130
|
+
/** `Math.round(priceCents * remainingDays / periodDays)` — the one rounding every proration in this module uses. */
|
|
131
|
+
export function prorateCents(priceCents, remainingDays, periodDays) {
|
|
132
|
+
return Math.round((priceCents * remainingDays) / periodDays);
|
|
133
|
+
}
|
|
134
|
+
/** `Math.round(netCents * ratePercent / 100)`. */
|
|
135
|
+
export function vatCents(netCents, ratePercent) {
|
|
136
|
+
return Math.round((netCents * ratePercent) / 100);
|
|
137
|
+
}
|
|
138
|
+
/** Net, VAT (`vatCents`) and gross for one charge. */
|
|
139
|
+
export function chargeAmounts(netCents, ratePercent) {
|
|
140
|
+
const vat = vatCents(netCents, ratePercent);
|
|
141
|
+
return { net_cents: netCents, vat_cents: vat, gross_cents: netCents + vat };
|
|
142
|
+
}
|
|
143
|
+
/** Milliseconds in a day. */
|
|
144
|
+
export const DAY_MS = 86_400_000;
|
|
145
|
+
/** Whole days between `start` and `end` (`Math.round`). */
|
|
146
|
+
export function periodDays(start, end) {
|
|
147
|
+
return Math.round((end.getTime() - start.getTime()) / DAY_MS);
|
|
148
|
+
}
|
|
149
|
+
/** Days left until `end`, as of `now` (`Math.ceil`), clamped to `[0, days]`. */
|
|
150
|
+
export function remainingDays(now, end, days) {
|
|
151
|
+
const left = Math.ceil((end.getTime() - now.getTime()) / DAY_MS);
|
|
152
|
+
return Math.min(Math.max(left, 0), days);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The net amount charged NOW for moving `from` → `to` within the period
|
|
156
|
+
* — increases are charged at once, decreases wait for the period's end,
|
|
157
|
+
* nothing is credited. Three branches, in this order:
|
|
158
|
+
*
|
|
159
|
+
* 1. **Monthly → yearly**: the full yearly price of `to` (plan plus
|
|
160
|
+
* add-ons, Pro only) minus the unused share of `from`'s monthly price,
|
|
161
|
+
* floored at 0. The period restarts today with a new anchor, so this
|
|
162
|
+
* is the only branch that prices `to` and `from` on different cycles.
|
|
163
|
+
* 2. **Yearly → monthly**: the reverse is a cycle *decrease* — charged 0
|
|
164
|
+
* now, stored as `next_cycle` and applied at the period's end, like
|
|
165
|
+
* every other decrease.
|
|
166
|
+
* 3. **Same cycle**: the increase only — the plan difference when `to` is
|
|
167
|
+
* the higher plan (`PLAN_ORDER`), plus each add-on's added units × its
|
|
168
|
+
* unit price, summed and prorated once over the remaining days. A plan
|
|
169
|
+
* decrease and fewer add-ons contribute 0; add-ons count only when
|
|
170
|
+
* `to.plan` is `pro`, and units held on a plan other than Pro count as
|
|
171
|
+
* none.
|
|
172
|
+
*/
|
|
173
|
+
export function changeNetCents(input) {
|
|
174
|
+
const { from, to, currency, periodStart, periodEnd, now } = input;
|
|
175
|
+
const days = periodDays(periodStart, periodEnd);
|
|
176
|
+
const remaining = remainingDays(now, periodEnd, days);
|
|
177
|
+
if (from.cycle === 'monthly' && to.cycle === 'yearly') {
|
|
178
|
+
const yearlyTo = periodNetCents({ plan: to.plan, addons: to.addons, cycle: 'yearly', currency });
|
|
179
|
+
const unusedFromShare = prorateCents(periodNetCents({ plan: from.plan, addons: from.addons, cycle: 'monthly', currency }), remaining, days);
|
|
180
|
+
return Math.max(0, yearlyTo - unusedFromShare);
|
|
181
|
+
}
|
|
182
|
+
if (from.cycle === 'yearly' && to.cycle === 'monthly')
|
|
183
|
+
return 0;
|
|
184
|
+
const planIncrease = PLAN_ORDER.indexOf(to.plan) > PLAN_ORDER.indexOf(from.plan)
|
|
185
|
+
? planPriceCentsFor(to.plan, to.cycle, currency) - planPriceCentsFor(from.plan, to.cycle, currency)
|
|
186
|
+
: 0;
|
|
187
|
+
let addonIncrease = 0;
|
|
188
|
+
if (to.plan === 'pro') {
|
|
189
|
+
const fromAddons = from.plan === 'pro' ? from.addons : null;
|
|
190
|
+
const key = priceKeyFor(to.cycle, currency);
|
|
191
|
+
for (const addon of addonKey.options) {
|
|
192
|
+
const base = fromAddons ? fromAddons[addon] : 0;
|
|
193
|
+
const added = Math.max(0, to.addons[addon] - base);
|
|
194
|
+
if (added > 0)
|
|
195
|
+
addonIncrease += added * ADDONS[addon].prices[key];
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return prorateCents(planIncrease + addonIncrease, remaining, days);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* The first charge of a checkout: one full period of `plan` + `addons`.
|
|
202
|
+
* One line for the plan, then one line per add-on actually bought
|
|
203
|
+
* (quantity 0 is omitted, not a zero-amount line).
|
|
204
|
+
*/
|
|
205
|
+
export function checkoutQuote(input) {
|
|
206
|
+
const { plan, addons, cycle, currency, rate_percent } = input;
|
|
207
|
+
const key = priceKeyFor(cycle, currency);
|
|
208
|
+
const lines = [];
|
|
209
|
+
const planNet = planPriceCentsFor(plan, cycle, currency);
|
|
210
|
+
lines.push({ item: 'plan', quantity: 1, net_cents: planNet, gross_cents: chargeAmounts(planNet, rate_percent).gross_cents });
|
|
211
|
+
if (plan === 'pro') {
|
|
212
|
+
for (const addon of addonKey.options) {
|
|
213
|
+
const quantity = addons[addon];
|
|
214
|
+
if (quantity > 0) {
|
|
215
|
+
const net = ADDONS[addon].prices[key] * quantity;
|
|
216
|
+
lines.push({ item: addon, quantity, net_cents: net, gross_cents: chargeAmounts(net, rate_percent).gross_cents });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const netCents = lines.reduce((sum, line) => sum + line.net_cents, 0);
|
|
221
|
+
return { ...chargeAmounts(netCents, rate_percent), lines, rate_percent, currency, cycle };
|
|
222
|
+
}
|
|
223
|
+
/** Dunning after a failed renewal: retries on these days after the charge's due date. */
|
|
224
|
+
export const BILLING_RETRY_DAYS = [3, 7];
|
|
225
|
+
/** The day, after the charge's due date, the org is locked when still unpaid. */
|
|
226
|
+
export const BILLING_LOCK_DAY = 14;
|
|
227
|
+
/*
|
|
228
|
+
* ---------------------------------------------------------------------------
|
|
229
|
+
* Billing shapes: checkout, the billing account, invoices and the owner's
|
|
230
|
+
* self-service changes (2026-10-04, fleetless/fleetless#104, I-2).
|
|
231
|
+
*
|
|
232
|
+
* The wire shapes for `src/routes.ts`'s `billing` section
|
|
233
|
+
* (`GET /api/billing`, `POST /api/billing/*`). Money is the same
|
|
234
|
+
* integer-cents discipline as everything above; every price still comes
|
|
235
|
+
* from `plans.ts`'s catalogue through `periodNetCents` / `changeNetCents` —
|
|
236
|
+
* nothing here invents a cent amount of its own.
|
|
237
|
+
* ---------------------------------------------------------------------------
|
|
238
|
+
*/
|
|
239
|
+
/** A billing address. `country` gates VAT (`vatFor`) and currency (`currencyForCountry`). */
|
|
240
|
+
export const billingAddress = z.object({
|
|
241
|
+
line1: z.string().trim().min(1).max(200).meta({ description: 'Street and number, or the first address line.' }),
|
|
242
|
+
line2: z.string().trim().max(200).nullable().meta({ description: 'A second address line, or `null` when there is none.' }),
|
|
243
|
+
postal_code: z.string().trim().min(1).max(20).meta({ description: 'Postal or ZIP code.' }),
|
|
244
|
+
city: z.string().trim().min(1).max(100).meta({ description: 'City or town.' }),
|
|
245
|
+
country: countryCode.meta({ description: "The billing country. Decides VAT (`vatFor`) and currency (`currencyForCountry`)." }),
|
|
246
|
+
}).strict();
|
|
247
|
+
/**
|
|
248
|
+
* Who is paying: a `company` (may hold a VAT
|
|
249
|
+
* ID, checked through VIES) or a `person` (full name, never a VAT ID — only
|
|
250
|
+
* a company can be VAT-registered). The discriminant decides which other
|
|
251
|
+
* fields exist at all, so a person cannot even send `vat_id` or
|
|
252
|
+
* `company_name` — there is no field for `.strict()` to refuse, the shape
|
|
253
|
+
* itself has none.
|
|
254
|
+
*/
|
|
255
|
+
export const billingDetails = z.discriminatedUnion('kind', [
|
|
256
|
+
z.object({
|
|
257
|
+
kind: z.literal('company').meta({ description: 'Billed as a company.' }),
|
|
258
|
+
company_name: z.string().trim().min(1).max(200).meta({ description: "The company's legal name, printed on the invoice." }),
|
|
259
|
+
vat_id: z.string().trim().max(20).nullable().meta({
|
|
260
|
+
description: "The company's VAT ID, or `null` for none. Required, and must check out through VIES, for a company outside Germany.",
|
|
261
|
+
}),
|
|
262
|
+
address: billingAddress.meta({ description: 'The billing address.' }),
|
|
263
|
+
invoice_email: z.email().meta({ description: 'Where invoices and billing mail are sent.' }),
|
|
264
|
+
}).strict().meta({ description: "A company: name and an optional VAT ID, never 'full name'." }),
|
|
265
|
+
z.object({
|
|
266
|
+
kind: z.literal('person').meta({ description: 'Billed as a person.' }),
|
|
267
|
+
full_name: z.string().trim().min(1).max(200).meta({ description: "The person's full name, printed on the invoice." }),
|
|
268
|
+
address: billingAddress.meta({ description: 'The billing address.' }),
|
|
269
|
+
invoice_email: z.email().meta({ description: 'Where invoices and billing mail are sent.' }),
|
|
270
|
+
}).strict().meta({ description: 'A person: a full name, never a VAT ID — only a company can be VAT-registered.' }),
|
|
271
|
+
]);
|
|
272
|
+
/**
|
|
273
|
+
* `POST /api/billing/checkout`'s body: the plan and cycle to buy — Basic is
|
|
274
|
+
* free and never checked out — optional add-ons (Pro only; named on `plus`,
|
|
275
|
+
* or without `planFeature.addons`, is `400 validation_error`), the payer,
|
|
276
|
+
* and the terms and withdrawal confirmations: `accept_terms` is
|
|
277
|
+
* always required; `accept_withdrawal` is required for a person and refused
|
|
278
|
+
* for a company, which has no consumer right of withdrawal to confirm.
|
|
279
|
+
*/
|
|
280
|
+
export const checkoutRequest = z.object({
|
|
281
|
+
plan: z.enum(['plus', 'pro']).meta({ description: 'The plan to buy.' }),
|
|
282
|
+
cycle: billingCycle.meta({ description: 'Monthly, or yearly at 15 % off.' }),
|
|
283
|
+
addons: orgAddons.partial().strict().optional().meta({
|
|
284
|
+
description: 'Add-on counts to buy alongside the plan. Pro only; named on `plus`, or without the feature, `400 validation_error`.',
|
|
285
|
+
}),
|
|
286
|
+
billing: billingDetails.meta({ description: 'Who is paying, and where the invoice goes.' }),
|
|
287
|
+
accept_terms: z.literal(true).meta({ description: "Confirms Fleetless's terms of service. Always required." }),
|
|
288
|
+
accept_withdrawal: z.literal(true).optional().meta({
|
|
289
|
+
description: "Confirms the plan starts at once and the 14-day right of withdrawal ends with it. Required for a person; a company sending it is `400 validation_error` — it has no withdrawal right to confirm.",
|
|
290
|
+
}),
|
|
291
|
+
}).strict();
|
|
292
|
+
/** `POST /api/billing/checkout`'s answer: where to send the caller's browser. */
|
|
293
|
+
export const checkoutResponse = z.object({
|
|
294
|
+
checkout_id: z.uuid().meta({ description: 'Identifies this checkout: polled by `GET /api/billing/checkout/:id` and carried on the return URL.' }),
|
|
295
|
+
checkout_url: z.url().meta({ description: "Mollie's hosted checkout page. The caller's browser is sent here." }),
|
|
296
|
+
});
|
|
297
|
+
/** `GET /api/billing/checkout/:id`'s answer: the return page's poll. */
|
|
298
|
+
export const checkoutStatus = z.object({
|
|
299
|
+
checkout_id: z.uuid().meta({ description: 'The checkout this status is for.' }),
|
|
300
|
+
status: z.enum(['pending', 'paid', 'failed', 'canceled', 'expired']).meta({
|
|
301
|
+
description: "Mollie's payment status, as `reconcilePayment` last read it.",
|
|
302
|
+
}),
|
|
303
|
+
purpose: z.enum(['upgrade', 'payment_method', 'invoice']).meta({
|
|
304
|
+
description: 'What this checkout paid for: a plan upgrade, a payment-method change, or an open invoice.',
|
|
305
|
+
}),
|
|
306
|
+
plan: planId.meta({ description: "The org's plan after applying — unchanged unless `purpose` is `upgrade` and `status` is `paid`." }),
|
|
307
|
+
});
|
|
308
|
+
/** VIES's answer to a VAT-ID check: `unverified` when VIES could not be reached in time. */
|
|
309
|
+
export const vatIdStatus = z.enum(['valid', 'unverified', 'invalid']);
|
|
310
|
+
/** `POST /api/billing/vat-id/check`'s body: the VAT-ID-on-blur check the checkout and `PATCH /api/billing/details` both use. */
|
|
311
|
+
export const vatIdCheckRequest = z.object({
|
|
312
|
+
country: countryCode.meta({ description: "The VAT ID's country." }),
|
|
313
|
+
vat_id: z.string().trim().min(1).max(20).meta({ description: 'The VAT ID as typed; normalized before the VIES lookup (`normalizeVatId`).' }),
|
|
314
|
+
}).strict();
|
|
315
|
+
/** `POST /api/billing/vat-id/check`'s answer. */
|
|
316
|
+
export const vatIdCheckResponse = z.object({
|
|
317
|
+
status: vatIdStatus.meta({ description: "VIES's answer." }),
|
|
318
|
+
vat_id: z.string().meta({ description: 'The normalized VAT ID that was checked.' }),
|
|
319
|
+
name: z.string().nullable().meta({ description: 'The registered holder, when VIES named one; `null` otherwise.' }),
|
|
320
|
+
});
|
|
321
|
+
/**
|
|
322
|
+
* `POST /api/billing/change`'s body: the **absolute** target state, never a
|
|
323
|
+
* delta — the route compares it with the org's current state
|
|
324
|
+
* and splits the difference into what is charged now and what is only
|
|
325
|
+
* scheduled. At least one of `plan`, `cycle` or `addons` must be named; the
|
|
326
|
+
* `.refine()` below says so in prose rather than in the JSON Schema this
|
|
327
|
+
* exports as, the same discipline `auditQuery`'s pair already follows —
|
|
328
|
+
* `.refine()` has no JSON Schema rendering.
|
|
329
|
+
*/
|
|
330
|
+
export const billingChangeRequest = z.object({
|
|
331
|
+
plan: z.enum(['plus', 'pro']).optional().meta({ description: 'The target plan. Omitted leaves the plan as it is.' }),
|
|
332
|
+
cycle: billingCycle.optional().meta({ description: 'The target cycle. Omitted leaves the cycle as it is.' }),
|
|
333
|
+
addons: orgAddons.partial().strict().optional().meta({
|
|
334
|
+
description: 'Absolute add-on counts to end up with, not a delta. Omitted leaves add-ons as they are.',
|
|
335
|
+
}),
|
|
336
|
+
}).strict().refine((r) => r.plan !== undefined || r.cycle !== undefined || r.addons !== undefined, { message: 'Name a plan, a cycle or add-ons.' });
|
|
337
|
+
/** `POST /api/billing/cancel`'s body — optional, hence `requestOptional` on the route entry. */
|
|
338
|
+
export const billingCancelRequest = z.object({
|
|
339
|
+
reason: z.string().trim().max(500).optional().meta({ description: 'An optional free-text reason. Shown to nobody but Fleetless.' }),
|
|
340
|
+
}).strict();
|
|
341
|
+
/** `PATCH /api/billing/details`'s body: the invoice email and the VAT ID, the two fields an owner edits after checkout. */
|
|
342
|
+
export const billingDetailsUpdate = z.object({
|
|
343
|
+
invoice_email: z.email().optional().meta({ description: 'Replaces the invoice email. Omitted leaves it as it is.' }),
|
|
344
|
+
vat_id: z.string().trim().max(20).nullable().optional().meta({
|
|
345
|
+
description: 'Replaces the VAT ID; `null` clears it. Omitted leaves it as it is. A new ID is re-checked through VIES.',
|
|
346
|
+
}),
|
|
347
|
+
}).strict();
|
|
348
|
+
/** `POST /api/billing/payment-method`'s body. */
|
|
349
|
+
export const paymentMethodChangeRequest = z.object({
|
|
350
|
+
method: z.enum(['card', 'paypal', 'sepa']).meta({
|
|
351
|
+
description: "The new mandate's method. `sepa` needs an open invoice to charge a real amount against; without one it is `400 validation_error`.",
|
|
352
|
+
}),
|
|
353
|
+
}).strict();
|
|
354
|
+
/** The billing account's own status, distinct from the org's plan: `billing_accounts` holds no plan, currency or period of its own. */
|
|
355
|
+
export const billingAccountStatus = z.enum(['pending', 'active', 'past_due', 'canceled']);
|
|
356
|
+
/** The payment method on file, read from the active Mollie mandate — never a card or bank number, only its display fields. */
|
|
357
|
+
export const paymentMethod = z.discriminatedUnion('kind', [
|
|
358
|
+
z.object({
|
|
359
|
+
kind: z.literal('card').meta({ description: 'A card mandate.' }),
|
|
360
|
+
brand: z.string().meta({ description: "The card network, as Mollie's mandate reports it, e.g. 'Visa'." }),
|
|
361
|
+
last4: z.string().regex(/^\d{4}$/).meta({ description: 'The last four digits of the card.' }),
|
|
362
|
+
expires: z.string().regex(/^\d{2}\/\d{2}$/).meta({ description: "Expiry as Mollie's mandate shows it, 'MM/YY'." }),
|
|
363
|
+
}),
|
|
364
|
+
z.object({
|
|
365
|
+
kind: z.literal('sepa').meta({ description: 'A SEPA direct-debit mandate.' }),
|
|
366
|
+
holder: z.string().meta({ description: "The account holder's name on the mandate." }),
|
|
367
|
+
iban_last4: z.string().regex(/^[0-9A-Z]{4}$/).meta({ description: 'The last four characters of the IBAN.' }),
|
|
368
|
+
}),
|
|
369
|
+
z.object({
|
|
370
|
+
kind: z.literal('paypal').meta({ description: 'A PayPal mandate.' }),
|
|
371
|
+
account: z.string().meta({ description: "The PayPal account Mollie's mandate names." }),
|
|
372
|
+
}),
|
|
373
|
+
]);
|
|
374
|
+
/** An invoice's status: `open` until paid, or `uncollectible` after a fallback to Basic with an unpaid balance. */
|
|
375
|
+
export const invoiceStatus = z.enum(['open', 'paid', 'uncollectible']);
|
|
376
|
+
/** One invoice, rendered by the cloud: the number is gapless, `FL-<year>-<seq>`. */
|
|
377
|
+
export const billingInvoice = z.object({
|
|
378
|
+
id: z.uuid().meta({ description: "The invoice's id." }),
|
|
379
|
+
number: z.string().regex(/^FL-\d{4}-\d{4,}$/).meta({
|
|
380
|
+
description: 'The invoice number: `FL-<year>-<seq>`, `seq` zero-padded to four digits, gapless per calendar year in `Europe/Berlin`.',
|
|
381
|
+
}),
|
|
382
|
+
issued_at: z.iso.datetime().meta({ description: 'When the invoice was issued.' }),
|
|
383
|
+
status: invoiceStatus.meta({ description: 'This invoice\'s own status.' }),
|
|
384
|
+
currency: planCurrency.meta({ description: "The org's billing currency." }),
|
|
385
|
+
net_cents: z.number().int().nonnegative().meta({ description: 'The charge, excluding VAT, in integer cents.' }),
|
|
386
|
+
vat_rate_percent: z.number().int().min(0).max(100).meta({ description: 'The VAT rate applied, in whole percent.' }),
|
|
387
|
+
vat_cents: z.number().int().nonnegative().meta({ description: 'VAT, in integer cents (`vatCents`).' }),
|
|
388
|
+
gross_cents: z.number().int().nonnegative().meta({ description: 'Net plus VAT, in integer cents.' }),
|
|
389
|
+
});
|
|
390
|
+
/**
|
|
391
|
+
* The org's billing account: the payer, the VAT treatment, the cycle and
|
|
392
|
+
* period, what is scheduled for the period's end and dunning
|
|
393
|
+
*, when a charge is overdue. `orgs.plan`, `orgs.currency` and
|
|
394
|
+
* `orgs.period_ends_at` stay the single source of truth for the plan itself
|
|
395
|
+
* (#103) — nothing here duplicates them.
|
|
396
|
+
*/
|
|
397
|
+
export const billingAccount = z.object({
|
|
398
|
+
payer: billingDetails.meta({ description: 'Who is paying, and the invoice address and email.' }),
|
|
399
|
+
vat_id_status: vatIdStatus.nullable().meta({ description: "The payer's VAT ID check, or `null` when no VAT ID was given." }),
|
|
400
|
+
vat: z.object({
|
|
401
|
+
treatment: vatTreatment.meta({ description: 'How the account is taxed.' }),
|
|
402
|
+
rate_percent: z.number().int().meta({ description: 'The VAT rate, in whole percent.' }),
|
|
403
|
+
}).meta({ description: 'The VAT treatment and rate this account charges at (`vatFor`).' }),
|
|
404
|
+
currency: planCurrency.meta({ description: "The org's billing currency, fixed at the first payment." }),
|
|
405
|
+
cycle: billingCycle.meta({ description: 'The current billing cycle.' }),
|
|
406
|
+
status: billingAccountStatus.meta({ description: "The account's own status." }),
|
|
407
|
+
period_starts_at: z.iso.datetime().nullable().meta({ description: "The current period's start. `null` before the first payment." }),
|
|
408
|
+
period_ends_at: z.iso.datetime().nullable().meta({ description: "The current period's end. `null` before the first payment." }),
|
|
409
|
+
next_charge: z.object({
|
|
410
|
+
at: z.iso.datetime().meta({ description: 'When the next charge is due.' }),
|
|
411
|
+
net_cents: z.number().int().meta({ description: 'The next charge, excluding VAT, in integer cents.' }),
|
|
412
|
+
vat_cents: z.number().int().meta({ description: 'VAT on the next charge, in integer cents.' }),
|
|
413
|
+
gross_cents: z.number().int().meta({ description: 'The next charge including VAT, in integer cents.' }),
|
|
414
|
+
}).nullable().meta({ description: '`null` while a cancel is pending or nothing else renews.' }),
|
|
415
|
+
scheduled: z.object({
|
|
416
|
+
cycle: billingCycle.nullable().meta({ description: "A cycle change queued for the period's end, or `null`." }),
|
|
417
|
+
addons: orgAddons.nullable().meta({ description: "Add-on counts queued for the period's end, or `null`." }),
|
|
418
|
+
}).meta({ description: "What takes effect at the period's end." }),
|
|
419
|
+
dunning: z.object({
|
|
420
|
+
invoice_id: z.uuid().meta({ description: 'The open invoice dunning is chasing.' }),
|
|
421
|
+
gross_cents: z.number().int().meta({ description: 'The amount owed, in integer cents.' }),
|
|
422
|
+
due_at: z.iso.datetime().meta({ description: "The charge's due date; retries and the lock count from here." }),
|
|
423
|
+
next_retry_at: z.iso.datetime().nullable().meta({ description: 'The next retry, or `null` once retries are exhausted.' }),
|
|
424
|
+
lock_at: z.iso.datetime().meta({ description: 'When the org is locked if still unpaid (`BILLING_LOCK_DAY`).' }),
|
|
425
|
+
failure: z.string().nullable().meta({
|
|
426
|
+
description: "Mollie's own reason, e.g. 'card_expired'; 'vat_id_invalid' when VIES turned definitive.",
|
|
427
|
+
}),
|
|
428
|
+
}).nullable().meta({ description: '`null` while nothing is overdue.' }),
|
|
429
|
+
});
|
|
430
|
+
/** `GET /api/billing`'s answer, and what every other billing route hands back after a change. */
|
|
431
|
+
export const billingView = z.object({
|
|
432
|
+
available: z.boolean().meta({ description: 'Whether this cloud takes payments at all — `false` when no Mollie key is configured.' }),
|
|
433
|
+
account: billingAccount.nullable().meta({ description: '`null` before the org has ever checked out.' }),
|
|
434
|
+
payment_method: paymentMethod.nullable().meta({ description: 'The payment method on file, or `null`.' }),
|
|
435
|
+
invoices: z.array(billingInvoice).meta({ description: 'Newest first, at most 24.' }),
|
|
436
|
+
});
|
|
437
|
+
/** `POST /api/billing/change`'s answer: the billing view afterwards, and what was charged right now, if anything. */
|
|
438
|
+
export const billingChangeResponse = z.object({
|
|
439
|
+
billing: billingView.meta({ description: 'The billing view after the change.' }),
|
|
440
|
+
charged: billingInvoice.nullable().meta({ description: 'The invoice charged now, or `null` when the change was only scheduled for the period\'s end.' }),
|
|
441
|
+
});
|
package/dist/errors.d.ts
CHANGED
|
@@ -88,8 +88,8 @@ export type InvalidCodeDetails = z.infer<typeof invalidCodeDetails>;
|
|
|
88
88
|
export declare const planLimitDetails: z.ZodObject<{
|
|
89
89
|
limit: z.ZodEnum<{
|
|
90
90
|
apps: "apps";
|
|
91
|
-
robots: "robots";
|
|
92
91
|
seats: "seats";
|
|
92
|
+
robots: "robots";
|
|
93
93
|
app_users: "app_users";
|
|
94
94
|
live_video_ms_per_month: "live_video_ms_per_month";
|
|
95
95
|
asset_bytes_per_robot: "asset_bytes_per_robot";
|
|
@@ -111,8 +111,8 @@ export declare const planLimitDetails: z.ZodObject<{
|
|
|
111
111
|
}>>;
|
|
112
112
|
addon: z.ZodNullable<z.ZodEnum<{
|
|
113
113
|
apps: "apps";
|
|
114
|
-
robots: "robots";
|
|
115
114
|
seats: "seats";
|
|
115
|
+
robots: "robots";
|
|
116
116
|
app_user_packs: "app_user_packs";
|
|
117
117
|
live_video_packs: "live_video_packs";
|
|
118
118
|
}>>;
|
|
@@ -142,8 +142,8 @@ export type PlanLimitDetails = z.infer<typeof planLimitDetails>;
|
|
|
142
142
|
export declare const assetPlanLimitDetails: z.ZodObject<{
|
|
143
143
|
limit: z.ZodEnum<{
|
|
144
144
|
apps: "apps";
|
|
145
|
-
robots: "robots";
|
|
146
145
|
seats: "seats";
|
|
146
|
+
robots: "robots";
|
|
147
147
|
app_users: "app_users";
|
|
148
148
|
live_video_ms_per_month: "live_video_ms_per_month";
|
|
149
149
|
asset_bytes_per_robot: "asset_bytes_per_robot";
|
|
@@ -165,8 +165,8 @@ export declare const assetPlanLimitDetails: z.ZodObject<{
|
|
|
165
165
|
}>>;
|
|
166
166
|
addon: z.ZodNullable<z.ZodEnum<{
|
|
167
167
|
apps: "apps";
|
|
168
|
-
robots: "robots";
|
|
169
168
|
seats: "seats";
|
|
169
|
+
robots: "robots";
|
|
170
170
|
app_user_packs: "app_user_packs";
|
|
171
171
|
live_video_packs: "live_video_packs";
|
|
172
172
|
}>>;
|
|
@@ -225,10 +225,22 @@ export declare const fileTooLargeDetails: z.ZodObject<{
|
|
|
225
225
|
size_bytes: z.ZodNullable<z.ZodNumber>;
|
|
226
226
|
}, z.core.$strip>;
|
|
227
227
|
export type FileTooLargeDetails = z.infer<typeof fileTooLargeDetails>;
|
|
228
|
+
/**
|
|
229
|
+
* The `details` of a `502 payment_provider_unavailable` refusal (2026-10-04,
|
|
230
|
+
* fleetless/fleetless#104): Mollie did not answer, or answered with an
|
|
231
|
+
* error, so nothing was charged and nothing changed. `status` is Mollie's
|
|
232
|
+
* own HTTP status, `null` for a timeout — the two ways "did not answer"
|
|
233
|
+
* happens, told apart for whoever reads a support ticket.
|
|
234
|
+
*/
|
|
235
|
+
export declare const paymentProviderUnavailableDetails: z.ZodObject<{
|
|
236
|
+
provider: z.ZodLiteral<"mollie">;
|
|
237
|
+
status: z.ZodNullable<z.ZodNumber>;
|
|
238
|
+
}, z.core.$strip>;
|
|
239
|
+
export type PaymentProviderUnavailableDetails = z.infer<typeof paymentProviderUnavailableDetails>;
|
|
228
240
|
/**
|
|
229
241
|
* The codes in use today. The wire deliberately allows any string — this
|
|
230
242
|
* list is the shared vocabulary, not a closed set, so a new refusal never
|
|
231
243
|
* needs a contracts release before it can be reported honestly.
|
|
232
244
|
*/
|
|
233
|
-
export declare const ERROR_CODES: readonly ["not_found", "validation_error", "bad_request", "unknown_datapoint", "invalid_token", "protocol_mismatch", "bridge_too_old", "invalid_frame", "duplicate_slug", "reserved_slug", "unknown_slug", "unknown_field_path", "unknown_type", "unknown_topic", "invalid_rate", "invalid_range", "config_conflict", "no_data", "robot_offline", "bridge_timeout", "unauthorized", "forbidden", "invalid_credentials", "token_expired", "token_revoked", "email_taken", "identifier_taken", "weak_password", "account_blocked", "busy", "parameter_invalid", "cancel_rejected", "job_lost", "job_unknown_to_bridge", "action_server_lost", "action_failed", "goal_rejected", "goal_send_failed", "result_failed", "goal_uncontrollable", "bridge_disconnected", "config_changed", "publisher_busy", "unknown_command", "not_subscribable", "camera_offline", "no_snapshot_yet", "live_unavailable", "wrong_kind", "not_recorded", "not_aggregatable", "quota_exceeded", "credential_in_use", "goal_timeout", "robot_in_use", "robot_deletion_partial", "job_queue_full", "invalid_uuid", "rate_limited", "tier_required", "token_spent", "service_timeout", "asset_missing", "dynamic_registration_disabled", "client_limit_reached", "idp_unavailable", "mcp_disabled", "tool_not_available", "capability_required", "last_owner", "role_name_taken", "role_in_use", "last_role", "target_state_conflict", "signup_closed", "draft_not_a_document", "internal_error", "not_cancellable", "unsupported_media_type", "wrong_browser", "invalid_yaml", "unstorable_yaml", "registration_closed", "domain_not_allowed", "email_unverified", "origin_not_allowed", "template_invalid", "provider_disabled", "provider_misconfigured", "invalid_redirect_uri", "interaction_expired", "invalid_code", "method_not_allowed", "plan_limit", "plan_required", "org_locked", "file_too_large"];
|
|
245
|
+
export declare const ERROR_CODES: readonly ["not_found", "validation_error", "bad_request", "unknown_datapoint", "invalid_token", "protocol_mismatch", "bridge_too_old", "invalid_frame", "duplicate_slug", "reserved_slug", "unknown_slug", "unknown_field_path", "unknown_type", "unknown_topic", "invalid_rate", "invalid_range", "config_conflict", "no_data", "robot_offline", "bridge_timeout", "unauthorized", "forbidden", "invalid_credentials", "token_expired", "token_revoked", "email_taken", "identifier_taken", "weak_password", "account_blocked", "busy", "parameter_invalid", "cancel_rejected", "job_lost", "job_unknown_to_bridge", "action_server_lost", "action_failed", "goal_rejected", "goal_send_failed", "result_failed", "goal_uncontrollable", "bridge_disconnected", "config_changed", "publisher_busy", "unknown_command", "not_subscribable", "camera_offline", "no_snapshot_yet", "live_unavailable", "wrong_kind", "not_recorded", "not_aggregatable", "quota_exceeded", "credential_in_use", "goal_timeout", "robot_in_use", "robot_deletion_partial", "job_queue_full", "invalid_uuid", "rate_limited", "tier_required", "token_spent", "service_timeout", "asset_missing", "dynamic_registration_disabled", "client_limit_reached", "idp_unavailable", "mcp_disabled", "tool_not_available", "capability_required", "last_owner", "role_name_taken", "role_in_use", "last_role", "target_state_conflict", "signup_closed", "draft_not_a_document", "internal_error", "not_cancellable", "unsupported_media_type", "wrong_browser", "invalid_yaml", "unstorable_yaml", "registration_closed", "domain_not_allowed", "email_unverified", "origin_not_allowed", "template_invalid", "provider_disabled", "provider_misconfigured", "invalid_redirect_uri", "interaction_expired", "invalid_code", "method_not_allowed", "plan_limit", "plan_required", "org_locked", "file_too_large", "billing_unavailable", "payment_provider_unavailable"];
|
|
234
246
|
export type ErrorCode = (typeof ERROR_CODES)[number];
|
package/dist/errors.js
CHANGED
|
@@ -154,6 +154,17 @@ export const fileTooLargeDetails = z.object({
|
|
|
154
154
|
description: 'The refused file\'s size, in bytes: the announced size, or the bytes that arrived when none (or a false one) was announced; `null` only when the body limit stopped the upload and nobody counted the bytes.',
|
|
155
155
|
}),
|
|
156
156
|
});
|
|
157
|
+
/**
|
|
158
|
+
* The `details` of a `502 payment_provider_unavailable` refusal (2026-10-04,
|
|
159
|
+
* fleetless/fleetless#104): Mollie did not answer, or answered with an
|
|
160
|
+
* error, so nothing was charged and nothing changed. `status` is Mollie's
|
|
161
|
+
* own HTTP status, `null` for a timeout — the two ways "did not answer"
|
|
162
|
+
* happens, told apart for whoever reads a support ticket.
|
|
163
|
+
*/
|
|
164
|
+
export const paymentProviderUnavailableDetails = z.object({
|
|
165
|
+
provider: z.literal('mollie').meta({ description: 'The payment provider. Always `mollie` today.' }),
|
|
166
|
+
status: z.number().int().nullable().meta({ description: "Mollie's own HTTP status, or `null` when the request timed out instead of answering." }),
|
|
167
|
+
});
|
|
157
168
|
/**
|
|
158
169
|
* The codes in use today. The wire deliberately allows any string — this
|
|
159
170
|
* list is the shared vocabulary, not a closed set, so a new refusal never
|
|
@@ -1028,4 +1039,24 @@ export const ERROR_CODES = [
|
|
|
1028
1039
|
* does not help, only a smaller file does.
|
|
1029
1040
|
*/
|
|
1030
1041
|
'file_too_large',
|
|
1042
|
+
// 2026-10-04 — billing through Mollie (#104).
|
|
1043
|
+
/**
|
|
1044
|
+
* `503`: this cloud takes no payments, because no payment provider is
|
|
1045
|
+
* configured — `MOLLIE_API_KEY` is unset. Every mutating
|
|
1046
|
+
* billing route answers it, and `GET /api/billing` answers `200` with
|
|
1047
|
+
* `available: false` instead, so a caller can render "billing is off"
|
|
1048
|
+
* without parsing an error. An upgrade or an add-on is a request to
|
|
1049
|
+
* Fleetless instead, exactly as it is today (#103's Feedback request).
|
|
1050
|
+
* Retrying does not help: nothing here changes until a key is
|
|
1051
|
+
* configured.
|
|
1052
|
+
*/
|
|
1053
|
+
'billing_unavailable',
|
|
1054
|
+
/**
|
|
1055
|
+
* `502`: Mollie did not answer, or answered with an error, so nothing was
|
|
1056
|
+
* charged and nothing changed. `details` is `paymentProviderUnavailableDetails`: Mollie's own
|
|
1057
|
+
* HTTP status, or `null` for a timeout. Distinct from `billing_unavailable`,
|
|
1058
|
+
* which is this cloud's own configuration and never Mollie's fault.
|
|
1059
|
+
* Retrying may work.
|
|
1060
|
+
*/
|
|
1061
|
+
'payment_provider_unavailable',
|
|
1031
1062
|
];
|
package/dist/index.d.ts
CHANGED
|
@@ -47,8 +47,8 @@ export { auditActor, auditEvent, auditQuery, auditListResponse, AUDIT_CSV_COLUMN
|
|
|
47
47
|
export type { AuditActor, AuditEvent, AuditQuery, AuditListResponse } from './audit.js';
|
|
48
48
|
export { alertRowCondition, alertSeverity, alertState, datapointAlertRow, alertListResponse, orgFiringAlertsResponse, orgAlertsQuery, datapointDisplay, putDatapointDisplayRequest, } from './alerts.js';
|
|
49
49
|
export type { AlertRowCondition, AlertSeverity, AlertState, DatapointAlertRow, AlertListResponse, OrgFiringAlertsResponse, OrgAlertsQuery, DatapointDisplay, PutDatapointDisplayRequest, } from './alerts.js';
|
|
50
|
-
export { apiError, parameterViolation, parameterInvalidDetails, cancelRejectedDetails, invalidCodeDetails, ERROR_CODES, planLimitDetails, assetPlanLimitDetails, planRequiredDetails, orgLockedDetails, fileTooLargeDetails, } from './errors.js';
|
|
51
|
-
export type { ApiError, ParameterViolation, ParameterInvalidDetails, CancelRejectedDetails, InvalidCodeDetails, ErrorCode, PlanLimitDetails, AssetPlanLimitDetails, PlanRequiredDetails, OrgLockedDetails, FileTooLargeDetails, } from './errors.js';
|
|
50
|
+
export { apiError, parameterViolation, parameterInvalidDetails, cancelRejectedDetails, invalidCodeDetails, ERROR_CODES, planLimitDetails, assetPlanLimitDetails, planRequiredDetails, orgLockedDetails, fileTooLargeDetails, paymentProviderUnavailableDetails, } from './errors.js';
|
|
51
|
+
export type { ApiError, ParameterViolation, ParameterInvalidDetails, CancelRejectedDetails, InvalidCodeDetails, ErrorCode, PlanLimitDetails, AssetPlanLimitDetails, PlanRequiredDetails, OrgLockedDetails, FileTooLargeDetails, PaymentProviderUnavailableDetails, } from './errors.js';
|
|
52
52
|
export { oauthErrorCode, oauthError, oauthRedirectResponse, oauthCodeTokenRequest, oauthRefreshTokenRequest, oauthTokenRequest, oauthTokenResponse, redirectUri, codeChallengeMethod, oauthAuthorizeQuery, dynamicClientRegistrationRequest, MCP_DCR_MAX_REDIRECT_URIS, dynamicClientRegistrationResponse, authorizationServerMetadata, protectedResourceMetadata, } from './oauth.js';
|
|
53
53
|
export type { OauthErrorCode, OauthError, OauthRedirectResponse, OauthCodeTokenRequest, OauthRefreshTokenRequest, OauthTokenRequest, OauthTokenResponse, RedirectUri, OauthAuthorizeQuery, DynamicClientRegistrationRequest, DynamicClientRegistrationResponse, AuthorizationServerMetadata, ProtectedResourceMetadata, } from './oauth.js';
|
|
54
54
|
export { ROUTES, ROUTE_SECTIONS, IN_HANDLER_ROUTES, developerSignInRoutes } from './routes.js';
|
|
@@ -57,3 +57,5 @@ export { planId, PLAN_ORDER, planLimitKey, planLimits, planFeature, planFeatures
|
|
|
57
57
|
export type { PlanId, PlanLimitKey, PlanLimits, PlanFeature, PlanFeatures, PlanPrices, PlanSupport, PlanCatalogueEntry, AddonKey, AddonCatalogueEntry, } from './plans.js';
|
|
58
58
|
export { planCurrency, orgAddons, orgPlanUsage, planChangeKeep, planChangeReason, pendingPlanChange, orgLock, orgPlan, planChangeRequest, planOverrides, adminPlanChangeRequest, } from './plans.js';
|
|
59
59
|
export type { PlanCurrency, OrgAddons, OrgPlanUsage, PlanChangeKeep, PlanChangeReason, PendingPlanChange, OrgLock, OrgPlan, PlanChangeRequest, PlanOverrides, AdminPlanChangeRequest, } from './plans.js';
|
|
60
|
+
export { countryCode, EU_COUNTRIES, SELLER_COUNTRY, DE_VAT_RATE_PERCENT, isEuCountry, currencyForCountry, payerKind, billingCycle, vatTreatment, payerRefusalRule, vatFor, viesCountry, normalizeVatId, periodNetCents, prorateCents, vatCents, chargeAmounts, DAY_MS, periodDays, remainingDays, changeNetCents, checkoutQuote, BILLING_RETRY_DAYS, BILLING_LOCK_DAY, billingAddress, billingDetails, checkoutRequest, checkoutResponse, checkoutStatus, vatIdStatus, vatIdCheckRequest, vatIdCheckResponse, billingChangeRequest, billingCancelRequest, billingDetailsUpdate, paymentMethodChangeRequest, billingAccountStatus, paymentMethod, invoiceStatus, billingInvoice, billingAccount, billingView, billingChangeResponse, } from './billing.js';
|
|
61
|
+
export type { PayerKind, BillingCycle, VatTreatment, PayerRefusalRule, VatDecision, ChargeAmounts, BillingState, QuoteLine, CheckoutQuote, BillingAddress, BillingDetails, CheckoutRequest, CheckoutResponse, CheckoutStatus, VatIdStatus, VatIdCheckRequest, VatIdCheckResponse, BillingChangeRequest, BillingCancelRequest, BillingDetailsUpdate, PaymentMethodChangeRequest, BillingAccountStatus, PaymentMethod, InvoiceStatus, BillingInvoice, BillingAccount, BillingView, BillingChangeResponse, } from './billing.js';
|
package/dist/index.js
CHANGED
|
@@ -56,7 +56,9 @@ export { apiError, parameterViolation, parameterInvalidDetails, cancelRejectedDe
|
|
|
56
56
|
// 2026-10-02 — plans (#103). The plan errors (I-3).
|
|
57
57
|
planLimitDetails, assetPlanLimitDetails, planRequiredDetails, orgLockedDetails,
|
|
58
58
|
// 2026-10-03 — the per-file asset limit (#136).
|
|
59
|
-
fileTooLargeDetails,
|
|
59
|
+
fileTooLargeDetails,
|
|
60
|
+
// 2026-10-04 — billing through Mollie (#104).
|
|
61
|
+
paymentProviderUnavailableDetails, } from './errors.js';
|
|
60
62
|
export { oauthErrorCode, oauthError, oauthRedirectResponse, oauthCodeTokenRequest, oauthRefreshTokenRequest, oauthTokenRequest, oauthTokenResponse, redirectUri, codeChallengeMethod, oauthAuthorizeQuery, dynamicClientRegistrationRequest, MCP_DCR_MAX_REDIRECT_URIS, dynamicClientRegistrationResponse, authorizationServerMetadata, protectedResourceMetadata, } from './oauth.js';
|
|
61
63
|
export { ROUTES, ROUTE_SECTIONS, IN_HANDLER_ROUTES, developerSignInRoutes } from './routes.js';
|
|
62
64
|
// 2026-10-02 — plans (#103). The catalogue (I-1) only: `PLANS` and `ADDONS`
|
|
@@ -67,3 +69,5 @@ export { planId, PLAN_ORDER, planLimitKey, planLimits, planFeature, planFeatures
|
|
|
67
69
|
// 2026-10-02 — plans (#103). The organization's plan, plan changes and the
|
|
68
70
|
// admin request (I-2).
|
|
69
71
|
export { planCurrency, orgAddons, orgPlanUsage, planChangeKeep, planChangeReason, pendingPlanChange, orgLock, orgPlan, planChangeRequest, planOverrides, adminPlanChangeRequest, } from './plans.js';
|
|
72
|
+
// 2026-10-04 — billing through Mollie (#104).
|
|
73
|
+
export { countryCode, EU_COUNTRIES, SELLER_COUNTRY, DE_VAT_RATE_PERCENT, isEuCountry, currencyForCountry, payerKind, billingCycle, vatTreatment, payerRefusalRule, vatFor, viesCountry, normalizeVatId, periodNetCents, prorateCents, vatCents, chargeAmounts, DAY_MS, periodDays, remainingDays, changeNetCents, checkoutQuote, BILLING_RETRY_DAYS, BILLING_LOCK_DAY, billingAddress, billingDetails, checkoutRequest, checkoutResponse, checkoutStatus, vatIdStatus, vatIdCheckRequest, vatIdCheckResponse, billingChangeRequest, billingCancelRequest, billingDetailsUpdate, paymentMethodChangeRequest, billingAccountStatus, paymentMethod, invoiceStatus, billingInvoice, billingAccount, billingView, billingChangeResponse, } from './billing.js';
|