@fleetless/contracts 6.3.0-next.1 → 6.3.0-next.2

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.
@@ -9605,7 +9605,7 @@
9605
9605
  }
9606
9606
  }
9607
9607
  },
9608
- "description": "`available: false` when `MOLLIE_API_KEY` is not configured — this cloud takes no payments, and every mutating route on this page answers `503 billing_unavailable` instead of acting. `account` is `null` before the org has ever checked out; the plan and its limits still come from `GET /api/org/plan` (#103) and are not repeated here."
9608
+ "description": "`available: false` when `MOLLIE_API_KEY` is not configured — this cloud takes no payments, and every billing route that charges or opens a Mollie checkout answers `503 billing_unavailable` instead of acting; cancel, resume, the details and the VAT-ID check need no Mollie and answer normally. `account` is `null` before the org has ever checked out; the plan and its limits still come from `GET /api/org/plan` (#103) and are not repeated here."
9609
9609
  }
9610
9610
  },
9611
9611
  "/api/billing/checkout": {
@@ -6495,7 +6495,7 @@
6495
6495
  "tier_required"
6496
6496
  ],
6497
6497
  "transport": "http",
6498
- "notes": "`available: false` when `MOLLIE_API_KEY` is not configured — this cloud takes no payments, and every mutating route on this page answers `503 billing_unavailable` instead of acting. `account` is `null` before the org has ever checked out; the plan and its limits still come from `GET /api/org/plan` (#103) and are not repeated here."
6498
+ "notes": "`available: false` when `MOLLIE_API_KEY` is not configured — this cloud takes no payments, and every billing route that charges or opens a Mollie checkout answers `503 billing_unavailable` instead of acting; cancel, resume, the details and the VAT-ID check need no Mollie and answer normally. `account` is `null` before the org has ever checked out; the plan and its limits still come from `GET /api/org/plan` (#103) and are not repeated here."
6499
6499
  },
6500
6500
  {
6501
6501
  "method": "POST",
@@ -6789,9 +6789,11 @@
6789
6789
  "query": null,
6790
6790
  "request": null,
6791
6791
  "response": null,
6792
- "errors": [],
6792
+ "errors": [
6793
+ "rate_limited"
6794
+ ],
6793
6795
  "transport": "http",
6794
- "notes": "**The body is never trusted**: it names only a payment id (`id=tr_…`, form-encoded, Mollie's own shape), and this route does nothing with it but call `reconcilePayment(deps, molliePaymentId)` — the same function `GET /api/billing/checkout/:id` and the hourly sweep call — which fetches the payment from Mollie itself and applies what Mollie says, idempotently. Rate limited on the `billing.webhook` bucket, per ip, 600/min — generous, because this is Mollie's own infrastructure calling, not a browser. **Every answer is `200`**, including an id this cloud does not recognise, which is logged and otherwise ignored, **except a processing fault, which is `500`** so Mollie retries the notification rather than this cloud losing it. `auth: 'none'` because Mollie signs nothing Fleetless checks here — the payment is only ever trusted once fetched back from Mollie's own API with the configured key."
6796
+ "notes": "**The body is never trusted**: it names only a payment id (`id=tr_…`, form-encoded, Mollie's own shape), and this route does nothing with it but call `reconcilePayment(deps, molliePaymentId)` — the same function `GET /api/billing/checkout/:id` and the hourly sweep call — which fetches the payment from Mollie itself and applies what Mollie says, idempotently. Rate limited on the `billing.webhook` bucket, per ip, 600/min — generous, because this is Mollie's own infrastructure calling, not a browser; over it the answer is `429 rate_limited`, and Mollie retries the notification later. **Every other answer is `200`**, including an id this cloud does not recognise, which is logged and otherwise ignored, **except a processing fault, which is `500`** so Mollie retries the notification rather than this cloud losing it. `auth: 'none'` because Mollie signs nothing Fleetless checks here — the payment is only ever trusted once fetched back from Mollie's own API with the configured key."
6795
6797
  },
6796
6798
  {
6797
6799
  "method": "POST",
package/dist/billing.d.ts CHANGED
@@ -174,6 +174,11 @@ export interface CheckoutQuote extends ChargeAmounts {
174
174
  * The first charge of a checkout: one full period of `plan` + `addons`.
175
175
  * One line for the plan, then one line per add-on actually bought
176
176
  * (quantity 0 is omitted, not a zero-amount line).
177
+ *
178
+ * The VAT is rounded once, over the whole net amount, because that is what
179
+ * is charged. Rounding it per line can land a cent off that total, so the
180
+ * plan line absorbs the difference: the gross lines always add up to
181
+ * `gross_cents`, and the amount a person sees is the amount charged.
177
182
  */
178
183
  export declare function checkoutQuote(input: {
179
184
  plan: 'plus' | 'pro';
package/dist/billing.js CHANGED
@@ -201,6 +201,11 @@ export function changeNetCents(input) {
201
201
  * The first charge of a checkout: one full period of `plan` + `addons`.
202
202
  * One line for the plan, then one line per add-on actually bought
203
203
  * (quantity 0 is omitted, not a zero-amount line).
204
+ *
205
+ * The VAT is rounded once, over the whole net amount, because that is what
206
+ * is charged. Rounding it per line can land a cent off that total, so the
207
+ * plan line absorbs the difference: the gross lines always add up to
208
+ * `gross_cents`, and the amount a person sees is the amount charged.
204
209
  */
205
210
  export function checkoutQuote(input) {
206
211
  const { plan, addons, cycle, currency, rate_percent } = input;
@@ -218,7 +223,9 @@ export function checkoutQuote(input) {
218
223
  }
219
224
  }
220
225
  const netCents = lines.reduce((sum, line) => sum + line.net_cents, 0);
221
- return { ...chargeAmounts(netCents, rate_percent), lines, rate_percent, currency, cycle };
226
+ const total = chargeAmounts(netCents, rate_percent);
227
+ lines[0].gross_cents += total.gross_cents - lines.reduce((sum, line) => sum + line.gross_cents, 0);
228
+ return { ...total, lines, rate_percent, currency, cycle };
222
229
  }
223
230
  /** Dunning after a failed renewal: retries on these days after the charge's due date. */
224
231
  export const BILLING_RETRY_DAYS = [3, 7];
package/dist/errors.js CHANGED
@@ -1042,8 +1042,10 @@ export const ERROR_CODES = [
1042
1042
  // 2026-10-04 — billing through Mollie (#104).
1043
1043
  /**
1044
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
1045
+ * configured — `MOLLIE_API_KEY` is unset. Every billing route that
1046
+ * charges or opens a Mollie checkout answers it; the routes that need no
1047
+ * Mollie (cancel, resume, the details, the VAT-ID check) answer normally,
1048
+ * and `GET /api/billing` answers `200` with
1047
1049
  * `available: false` instead, so a caller can render "billing is off"
1048
1050
  * without parsing an error. An upgrade or an add-on is a request to
1049
1051
  * Fleetless instead, exactly as it is today (#103's Feedback request).
package/dist/routes.js CHANGED
@@ -99,8 +99,9 @@ const DEVELOPER_GUARD = ['unauthorized', 'token_expired', 'token_revoked'];
99
99
  * billing's own infrastructure rather than from what the caller sent
100
100
  * (2026-10-04, fleetless/fleetless#104): `BILLING_OFF` is no payment
101
101
  * provider configured at all, `MOLLIE` is Mollie itself not
102
- * answering. Every mutating billing route that talks to Mollie
103
- * lists both; a route that only reads or edits local state lists neither.
102
+ * answering. Every billing route that charges or opens a Mollie checkout
103
+ * lists both; a route that only reads or edits local state lists neither,
104
+ * and answers normally without a key.
104
105
  */
105
106
  const BILLING_OFF = ['billing_unavailable'];
106
107
  const MOLLIE = ['payment_provider_unavailable'];
@@ -2868,9 +2869,9 @@ export const ROUTES = [
2868
2869
  * developer answers `403 tier_required` and `GET /api/billing`
2869
2870
  * is no exception — a developer reads #103's plan cards from
2870
2871
  * `GET /api/org/plan` instead, without the payer, payment method or
2871
- * invoices (Offene Punkte 5). `billing_unavailable` and
2872
- * `payment_provider_unavailable` are declared in full on the two routes
2873
- * above as `BILLING_OFF` and `MOLLIE`.
2872
+ * invoices. The routes that list `billing_unavailable` and
2873
+ * `payment_provider_unavailable` spread them from the `BILLING_OFF` and
2874
+ * `MOLLIE` constants near the top of this file.
2874
2875
  */
2875
2876
  {
2876
2877
  method: 'GET', path: '/api/billing', section: 'billing',
@@ -2878,8 +2879,9 @@ export const ROUTES = [
2878
2879
  audience: 'developer', auth: 'developer', rateLimited: false, ownerTier: true, status: 200,
2879
2880
  params: [], query: null, request: null, response: billingView,
2880
2881
  errors: [...DEVELOPER_GUARD, 'tier_required'], transport: 'http',
2881
- notes: '`available: false` when `MOLLIE_API_KEY` is not configured — this cloud takes no payments, and every mutating route on ' +
2882
- 'this page answers `503 billing_unavailable` instead of acting. `account` is `null` before the org has ever checked out; the plan ' +
2882
+ notes: '`available: false` when `MOLLIE_API_KEY` is not configured — this cloud takes no payments, and every billing route that ' +
2883
+ 'charges or opens a Mollie checkout answers `503 billing_unavailable` instead of acting; cancel, resume, the details and the ' +
2884
+ 'VAT-ID check need no Mollie and answer normally. `account` is `null` before the org has ever checked out; the plan ' +
2883
2885
  'and its limits still come from `GET /api/org/plan` (#103) and are not repeated here.',
2884
2886
  },
2885
2887
  {
@@ -3013,12 +3015,12 @@ export const ROUTES = [
3013
3015
  summary: "Takes Mollie's payment-changed notification and reconciles the payment.",
3014
3016
  audience: 'internal', auth: 'none', rateLimited: true, ownerTier: false, status: 200,
3015
3017
  params: [], query: null, request: null, response: null,
3016
- errors: [], transport: 'http',
3018
+ errors: ['rate_limited'], transport: 'http',
3017
3019
  notes: '**The body is never trusted**: it names only a payment id (`id=tr_…`, form-encoded, Mollie\'s own shape), and this route ' +
3018
3020
  'does nothing with it but call `reconcilePayment(deps, molliePaymentId)` — the same function `GET /api/billing/checkout/:id` and ' +
3019
3021
  'the hourly sweep call — which fetches the payment from Mollie itself and applies what Mollie says, idempotently. Rate limited on ' +
3020
- 'the `billing.webhook` bucket, per ip, 600/min — generous, because this is Mollie\'s own infrastructure calling, not a browser. ' +
3021
- '**Every answer is `200`**, including an id this cloud does not recognise, which is logged and otherwise ignored, **except a ' +
3022
+ 'the `billing.webhook` bucket, per ip, 600/min — generous, because this is Mollie\'s own infrastructure calling, not a browser; ' +
3023
+ 'over it the answer is `429 rate_limited`, and Mollie retries the notification later. **Every other answer is `200`**, including an id this cloud does not recognise, which is logged and otherwise ignored, **except a ' +
3022
3024
  'processing fault, which is `500`** so Mollie retries the notification rather than this cloud losing it. `auth: \'none\'` because ' +
3023
3025
  'Mollie signs nothing Fleetless checks here — the payment is only ever trusted once fetched back from Mollie\'s own API with the ' +
3024
3026
  'configured key.',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetless/contracts",
3
- "version": "6.3.0-next.1",
3
+ "version": "6.3.0-next.2",
4
4
  "description": "Fleetless wire contracts: the bridge-cloud protocol, the REST API schemas and the error codes, as zod schemas with generated JSON Schema and OpenAPI artifacts.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Dehne Robotik GmbH",