@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/plans.d.ts
CHANGED
|
@@ -29,8 +29,8 @@ export declare const PLAN_ORDER: readonly PlanId[];
|
|
|
29
29
|
*/
|
|
30
30
|
export declare const planLimitKey: z.ZodEnum<{
|
|
31
31
|
apps: "apps";
|
|
32
|
-
robots: "robots";
|
|
33
32
|
seats: "seats";
|
|
33
|
+
robots: "robots";
|
|
34
34
|
app_users: "app_users";
|
|
35
35
|
live_video_ms_per_month: "live_video_ms_per_month";
|
|
36
36
|
asset_bytes_per_robot: "asset_bytes_per_robot";
|
|
@@ -139,8 +139,8 @@ export type PlanCatalogueEntry = z.infer<typeof planCatalogueEntry>;
|
|
|
139
139
|
*/
|
|
140
140
|
export declare const addonKey: z.ZodEnum<{
|
|
141
141
|
apps: "apps";
|
|
142
|
-
robots: "robots";
|
|
143
142
|
seats: "seats";
|
|
143
|
+
robots: "robots";
|
|
144
144
|
app_user_packs: "app_user_packs";
|
|
145
145
|
live_video_packs: "live_video_packs";
|
|
146
146
|
}>;
|
|
@@ -148,15 +148,15 @@ export type AddonKey = z.infer<typeof addonKey>;
|
|
|
148
148
|
export declare const addonCatalogueEntry: z.ZodObject<{
|
|
149
149
|
key: z.ZodEnum<{
|
|
150
150
|
apps: "apps";
|
|
151
|
-
robots: "robots";
|
|
152
151
|
seats: "seats";
|
|
152
|
+
robots: "robots";
|
|
153
153
|
app_user_packs: "app_user_packs";
|
|
154
154
|
live_video_packs: "live_video_packs";
|
|
155
155
|
}>;
|
|
156
156
|
raises: z.ZodEnum<{
|
|
157
157
|
apps: "apps";
|
|
158
|
-
robots: "robots";
|
|
159
158
|
seats: "seats";
|
|
159
|
+
robots: "robots";
|
|
160
160
|
app_users: "app_users";
|
|
161
161
|
live_video_ms_per_month: "live_video_ms_per_month";
|
|
162
162
|
asset_bytes_per_robot: "asset_bytes_per_robot";
|
|
@@ -260,8 +260,8 @@ export type PlanChangeKeep = z.infer<typeof planChangeKeep>;
|
|
|
260
260
|
* is locked (see `orgLock`) and must land on Basic.
|
|
261
261
|
*/
|
|
262
262
|
export declare const planChangeReason: z.ZodEnum<{
|
|
263
|
-
cancel: "cancel";
|
|
264
263
|
downgrade: "downgrade";
|
|
264
|
+
cancel: "cancel";
|
|
265
265
|
migration: "migration";
|
|
266
266
|
lock: "lock";
|
|
267
267
|
}>;
|
|
@@ -292,8 +292,8 @@ export declare const pendingPlanChange: z.ZodObject<{
|
|
|
292
292
|
enterprise: "enterprise";
|
|
293
293
|
}>;
|
|
294
294
|
reason: z.ZodEnum<{
|
|
295
|
-
cancel: "cancel";
|
|
296
295
|
downgrade: "downgrade";
|
|
296
|
+
cancel: "cancel";
|
|
297
297
|
migration: "migration";
|
|
298
298
|
lock: "lock";
|
|
299
299
|
}>;
|
|
@@ -391,8 +391,8 @@ export declare const orgPlan: z.ZodObject<{
|
|
|
391
391
|
enterprise: "enterprise";
|
|
392
392
|
}>;
|
|
393
393
|
reason: z.ZodEnum<{
|
|
394
|
-
cancel: "cancel";
|
|
395
394
|
downgrade: "downgrade";
|
|
395
|
+
cancel: "cancel";
|
|
396
396
|
migration: "migration";
|
|
397
397
|
lock: "lock";
|
|
398
398
|
}>;
|
|
@@ -455,8 +455,8 @@ export type PlanChangeRequest = z.infer<typeof planChangeRequest>;
|
|
|
455
455
|
*/
|
|
456
456
|
export declare const planOverrides: z.ZodObject<{
|
|
457
457
|
apps: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
458
|
-
robots: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
459
458
|
seats: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
459
|
+
robots: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
460
460
|
app_users: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
461
461
|
live_video_ms_per_month: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
462
462
|
asset_bytes_per_robot: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
@@ -490,8 +490,8 @@ export declare const adminPlanChangeRequest: z.ZodObject<{
|
|
|
490
490
|
}, z.core.$strict>>;
|
|
491
491
|
overrides: z.ZodOptional<z.ZodObject<{
|
|
492
492
|
apps: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
493
|
-
robots: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
494
493
|
seats: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
494
|
+
robots: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
495
495
|
app_users: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
496
496
|
live_video_ms_per_month: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
497
497
|
asset_bytes_per_robot: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
package/dist/realtime.d.ts
CHANGED
|
@@ -260,13 +260,13 @@ export type DatapointEvent = z.infer<typeof datapointEvent>;
|
|
|
260
260
|
*/
|
|
261
261
|
export declare const liveSessionEndReason: z.ZodEnum<{
|
|
262
262
|
unknown: "unknown";
|
|
263
|
+
expired: "expired";
|
|
263
264
|
plan_limit: "plan_limit";
|
|
264
265
|
publish_failed: "publish_failed";
|
|
265
266
|
robot_offline: "robot_offline";
|
|
266
267
|
config_changed: "config_changed";
|
|
267
268
|
released_by_peer: "released_by_peer";
|
|
268
269
|
revoked: "revoked";
|
|
269
|
-
expired: "expired";
|
|
270
270
|
robot_deleted: "robot_deleted";
|
|
271
271
|
}>;
|
|
272
272
|
export type LiveSessionEndReason = z.infer<typeof liveSessionEndReason>;
|
|
@@ -292,13 +292,13 @@ export declare const liveSessionEvent: z.ZodObject<{
|
|
|
292
292
|
state: z.ZodLiteral<"ended">;
|
|
293
293
|
reason: z.ZodEnum<{
|
|
294
294
|
unknown: "unknown";
|
|
295
|
+
expired: "expired";
|
|
295
296
|
plan_limit: "plan_limit";
|
|
296
297
|
publish_failed: "publish_failed";
|
|
297
298
|
robot_offline: "robot_offline";
|
|
298
299
|
config_changed: "config_changed";
|
|
299
300
|
released_by_peer: "released_by_peer";
|
|
300
301
|
revoked: "revoked";
|
|
301
|
-
expired: "expired";
|
|
302
302
|
robot_deleted: "robot_deleted";
|
|
303
303
|
}>;
|
|
304
304
|
detail: z.ZodNullable<z.ZodString>;
|
package/dist/rest.d.ts
CHANGED
|
@@ -1847,10 +1847,10 @@ export declare const USAGE_WINDOW_MAX_DAYS = 366;
|
|
|
1847
1847
|
* told to look at the wrong thing.
|
|
1848
1848
|
*/
|
|
1849
1849
|
export declare const usageMetric: z.ZodEnum<{
|
|
1850
|
+
asset_bytes: "asset_bytes";
|
|
1850
1851
|
api_calls: "api_calls";
|
|
1851
1852
|
live_session_ms: "live_session_ms";
|
|
1852
1853
|
retention_bytes: "retention_bytes";
|
|
1853
|
-
asset_bytes: "asset_bytes";
|
|
1854
1854
|
robot_online_ms: "robot_online_ms";
|
|
1855
1855
|
}>;
|
|
1856
1856
|
export type UsageMetric = z.infer<typeof usageMetric>;
|
|
@@ -1970,10 +1970,10 @@ export declare const usageRow: z.ZodObject<{
|
|
|
1970
1970
|
app_id: z.ZodNullable<z.ZodUUID>;
|
|
1971
1971
|
app_name: z.ZodNullable<z.ZodString>;
|
|
1972
1972
|
metric: z.ZodEnum<{
|
|
1973
|
+
asset_bytes: "asset_bytes";
|
|
1973
1974
|
api_calls: "api_calls";
|
|
1974
1975
|
live_session_ms: "live_session_ms";
|
|
1975
1976
|
retention_bytes: "retention_bytes";
|
|
1976
|
-
asset_bytes: "asset_bytes";
|
|
1977
1977
|
robot_online_ms: "robot_online_ms";
|
|
1978
1978
|
}>;
|
|
1979
1979
|
day: z.ZodString;
|
|
@@ -1986,10 +1986,10 @@ export declare const orgUsageResponse: z.ZodObject<{
|
|
|
1986
1986
|
app_id: z.ZodNullable<z.ZodUUID>;
|
|
1987
1987
|
app_name: z.ZodNullable<z.ZodString>;
|
|
1988
1988
|
metric: z.ZodEnum<{
|
|
1989
|
+
asset_bytes: "asset_bytes";
|
|
1989
1990
|
api_calls: "api_calls";
|
|
1990
1991
|
live_session_ms: "live_session_ms";
|
|
1991
1992
|
retention_bytes: "retention_bytes";
|
|
1992
|
-
asset_bytes: "asset_bytes";
|
|
1993
1993
|
robot_online_ms: "robot_online_ms";
|
|
1994
1994
|
}>;
|
|
1995
1995
|
day: z.ZodString;
|
package/dist/routes.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export type RouteAudience = 'developer' | 'client' | 'internal';
|
|
|
36
36
|
*/
|
|
37
37
|
export type RouteAuth = 'developer' | 'developer_or_client' | 'none' | 'robot_upload' | 'in_handler' | 'ops';
|
|
38
38
|
export type RouteTransport = 'http' | 'websocket';
|
|
39
|
-
export type RouteSection = 'health' | 'developer-auth' | 'client-auth' | 'org' | 'users' | 'apps' | 'robots' | 'config' | 'alerts' | 'commands' | 'cameras' | 'assets' | 'mcp' | 'transports';
|
|
39
|
+
export type RouteSection = 'health' | 'developer-auth' | 'client-auth' | 'org' | 'billing' | 'users' | 'apps' | 'robots' | 'config' | 'alerts' | 'commands' | 'cameras' | 'assets' | 'mcp' | 'transports';
|
|
40
40
|
export interface RouteParam {
|
|
41
41
|
readonly name: string;
|
|
42
42
|
/** One sentence: what the segment identifies and where a caller gets it. */
|
package/dist/routes.js
CHANGED
|
@@ -12,12 +12,14 @@ import { jobRunListResponse, jobRunQuery, jobRunSummary, jobRunSummaryQuery } fr
|
|
|
12
12
|
import { MCP_APP_PATHS, mcpRobotDatasheet, mcpRolePreviewResponse } from './mcp.js';
|
|
13
13
|
import { authorizationServerMetadata, dynamicClientRegistrationRequest, dynamicClientRegistrationResponse, oauthAuthorizeQuery, oauthRedirectResponse, oauthTokenRequest, oauthTokenResponse, protectedResourceMetadata, } from './oauth.js';
|
|
14
14
|
import { adminPlanChangeRequest, orgPlan, planChangeRequest } from './plans.js';
|
|
15
|
+
import { billingCancelRequest, billingChangeRequest, billingChangeResponse, billingDetailsUpdate, billingView, checkoutRequest, checkoutResponse, checkoutStatus, paymentMethodChangeRequest, vatIdCheckRequest, vatIdCheckResponse, } from './billing.js';
|
|
15
16
|
import { cameraListResponse, cancelRequest, configDraftResponse, configVersionResponse, configVersionsResponse, createRobotRequest, createRobotResponse, datapointListResponse, datapointValue, exposureListResponse, fetchTypesRequest, fetchTypesResponse, historyQuery, historyResponse, introspectionResponse, invokeOrServiceResponse, invokeRequest, jobResponse, liveSessionResponse, orgHealthQuery, orgLatencyQuery, orgLatencyResponse, orgQuotaUsage, orgUsageQuery, orgUsageResponse, patchRobotRequest, patchRobotResponse, publishConfigResponse, publishRequest, putConfigDraftRequest, putRobotDetailsRequest, putRobotDetailsResponse, robotTokenRotateResponse, jointStatePutRequest, jointStatePutResponse, releaseLiveQuery, renameSlugRequest, renameSlugResponse, robotDeleteQuery, resourceHealthListResponse, robotDeletionSummary, robotDetailResponse, robotJobsResponse, robotListResponse, slugUsageResponse, snapshotMetaResponse, typesResponse, } from './rest.js';
|
|
16
17
|
export const ROUTE_SECTIONS = [
|
|
17
18
|
{ id: 'health', title: 'Health' },
|
|
18
19
|
{ id: 'developer-auth', title: 'Developer auth' },
|
|
19
20
|
{ id: 'client-auth', title: 'App-user (client) auth' },
|
|
20
21
|
{ id: 'org', title: 'Org' },
|
|
22
|
+
{ id: 'billing', title: 'Billing' },
|
|
21
23
|
{ id: 'users', title: 'Team' },
|
|
22
24
|
{ id: 'apps', title: 'Apps' },
|
|
23
25
|
{ id: 'robots', title: 'Robots' },
|
|
@@ -92,6 +94,16 @@ export const IN_HANDLER_ROUTES = [
|
|
|
92
94
|
* than an undocumented one: a consumer branches on it and the branch is dead.
|
|
93
95
|
*/
|
|
94
96
|
const DEVELOPER_GUARD = ['unauthorized', 'token_expired', 'token_revoked'];
|
|
97
|
+
/**
|
|
98
|
+
* The billing section's shorthand for the two refusals that come from
|
|
99
|
+
* billing's own infrastructure rather than from what the caller sent
|
|
100
|
+
* (2026-10-04, fleetless/fleetless#104): `BILLING_OFF` is no payment
|
|
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.
|
|
104
|
+
*/
|
|
105
|
+
const BILLING_OFF = ['billing_unavailable'];
|
|
106
|
+
const MOLLIE = ['payment_provider_unavailable'];
|
|
95
107
|
/**
|
|
96
108
|
* The same, for `auth: 'developer_or_client'` — one guard resolving a developer
|
|
97
109
|
* bearer, an end-user bearer **or** a server key through a single token lookup.
|
|
@@ -2807,8 +2819,8 @@ export const ROUTES = [
|
|
|
2807
2819
|
params: [], query: null, request: planChangeRequest, response: orgPlan,
|
|
2808
2820
|
errors: [...DEVELOPER_GUARD, 'tier_required', 'validation_error', 'plan_limit', 'target_state_conflict'], transport: 'http',
|
|
2809
2821
|
notes: 'Owner tier, and **downward only**: this route moves the org to a lower plan or cancels it outright to Basic. It never moves the org ' +
|
|
2810
|
-
'up —
|
|
2811
|
-
'
|
|
2822
|
+
'up — an upgrade or an add-on goes through the billing routes instead (`POST /api/billing/checkout`, `POST /api/billing/change`). ' +
|
|
2823
|
+
'`409 target_state_conflict` names `target_plan` with rule `not_lower` when the ' +
|
|
2812
2824
|
'chosen plan is not below the org\'s current one; with rule `locked_basic_only` when the org is locked (`orgLock`) and the chosen ' +
|
|
2813
2825
|
'plan is anything but Basic; and with rule `migration_basic_only` when the org is still on the beta, awaiting the switch to priced ' +
|
|
2814
2826
|
'plans, and the chosen plan is anything but Basic — that choice is exactly what the org lands on at the switch. An owner is never ' +
|
|
@@ -2837,14 +2849,179 @@ export const ROUTES = [
|
|
|
2837
2849
|
audience: 'internal', auth: 'ops', rateLimited: true, ownerTier: false, status: 200,
|
|
2838
2850
|
params: [{ name: 'id', description: 'The org\'s uuid, whose plan, add-ons or overrides the operator is changing.' }],
|
|
2839
2851
|
query: null, request: adminPlanChangeRequest, response: orgPlan,
|
|
2840
|
-
errors: ['unauthorized', 'not_found', 'validation_error', 'plan_limit', 'rate_limited'], transport: 'http',
|
|
2852
|
+
errors: ['unauthorized', 'not_found', 'validation_error', 'plan_limit', 'rate_limited', 'target_state_conflict'], transport: 'http',
|
|
2841
2853
|
notes: 'Every public host answers `404 not_found` for every `/api/admin/*` path — this route is reachable only on the cloud\'s private ' +
|
|
2842
2854
|
'address — and that same address answers `404` here too while `OPS_API_TOKEN` is not configured, so a door with no key behind it ' +
|
|
2843
2855
|
'reads exactly like one nobody opened. An unknown `:id` is the same `404`. Applies at once, never queued as a `pending_change`, ' +
|
|
2844
2856
|
'and only when the org\'s current usage fits the result: `409 plan_limit` when a lower plan, a lowered override or a removed add-on ' +
|
|
2845
2857
|
'would leave the org over a limit — the operator never deletes an org\'s things, only the owner\'s own choice does. On success it ' +
|
|
2846
2858
|
'also withdraws any `pending_change`, ends a beta org\'s wait for the switch and lifts a lock. Audited as `org.plan_changed` with ' +
|
|
2847
|
-
'the `fleetless` actor, never a developer\'s — the row names what an operator did, not who in the org asked for it.'
|
|
2859
|
+
'the `fleetless` actor, never a developer\'s — the row names what an operator did, not who in the org asked for it. ' +
|
|
2860
|
+
'`409 target_state_conflict` names `currency` or `period_ends_at` with rule `billed` when the org has a billing account ' +
|
|
2861
|
+
'in `active` or `past_due` and the request names either field: both are fixed at the first payment and billing, not the operator, ' +
|
|
2862
|
+
'owns them from then on. The plan itself keeps working on a billed org — a plan, an add-on or an override the operator sets here is ' +
|
|
2863
|
+
'charged from the next renewal, except `enterprise` and `basic`, which the sweep cancels the billing account for instead.',
|
|
2864
|
+
},
|
|
2865
|
+
/* --- billing (2026-10-04, fleetless/fleetless#104) ----------------------
|
|
2866
|
+
*
|
|
2867
|
+
* Every route here but the webhook is owner-only (`ownerTier: true`), a
|
|
2868
|
+
* developer answers `403 tier_required` and `GET /api/billing`
|
|
2869
|
+
* is no exception — a developer reads #103's plan cards from
|
|
2870
|
+
* `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`.
|
|
2874
|
+
*/
|
|
2875
|
+
{
|
|
2876
|
+
method: 'GET', path: '/api/billing', section: 'billing',
|
|
2877
|
+
summary: "Reads the org's billing account, payment method and invoices.",
|
|
2878
|
+
audience: 'developer', auth: 'developer', rateLimited: false, ownerTier: true, status: 200,
|
|
2879
|
+
params: [], query: null, request: null, response: billingView,
|
|
2880
|
+
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 ' +
|
|
2883
|
+
'and its limits still come from `GET /api/org/plan` (#103) and are not repeated here.',
|
|
2884
|
+
},
|
|
2885
|
+
{
|
|
2886
|
+
method: 'POST', path: '/api/billing/checkout', section: 'billing',
|
|
2887
|
+
summary: 'Starts a Mollie checkout for a plan, or an upgrade paid at once.',
|
|
2888
|
+
audience: 'developer', auth: 'developer', rateLimited: true, ownerTier: true, status: 201,
|
|
2889
|
+
params: [], query: null, request: checkoutRequest, response: checkoutResponse,
|
|
2890
|
+
errors: [...DEVELOPER_GUARD, 'tier_required', 'validation_error', 'plan_limit', 'target_state_conflict', 'rate_limited', ...BILLING_OFF, ...MOLLIE],
|
|
2891
|
+
transport: 'http',
|
|
2892
|
+
notes: 'Rate limited on the `billing.checkout` bucket, same as `POST /api/billing/payment-method` and `POST /api/billing/invoices/:id/pay` ' +
|
|
2893
|
+
'— the three routes that mint a Mollie checkout. `400 validation_error` names who may not pay with these rules: ' +
|
|
2894
|
+
'`{ field: \'billing.address.country\', rule: \'eu_person\' }` for a person in another EU country, ' +
|
|
2895
|
+
'`{ field: \'billing.vat_id\', rule: \'vat_id_required\' }` for a company there with no VAT ID, ' +
|
|
2896
|
+
'`{ field: \'billing.vat_id\', rule: \'vat_id_invalid\' }` once VIES has said so, and ' +
|
|
2897
|
+
'`{ field: \'accept_withdrawal\', rule: \'required\' }` for a person who did not confirm it. `409 target_state_conflict` names ' +
|
|
2898
|
+
'`plan` with rule `already_billed` when the org already has an `active` or `past_due` billing account — checkout is for the first ' +
|
|
2899
|
+
'payment only, every later change is `POST /api/billing/change`. `checkout_url` is Mollie\'s hosted page; the return lands on ' +
|
|
2900
|
+
'`<console>/settings/billing?checkout=<checkout_id>`, which polls `GET /api/billing/checkout/:id` until the webhook — or the poll ' +
|
|
2901
|
+
'itself — has reconciled the payment. `409 plan_limit` is the org\'s own usage against the plan being bought.',
|
|
2902
|
+
},
|
|
2903
|
+
{
|
|
2904
|
+
method: 'GET', path: '/api/billing/checkout/:id', section: 'billing',
|
|
2905
|
+
summary: "Reads a checkout's status, for the return page's poll.",
|
|
2906
|
+
audience: 'developer', auth: 'developer', rateLimited: false, ownerTier: true, status: 200,
|
|
2907
|
+
params: [{ name: 'id', description: 'The checkout id from `checkoutResponse.checkout_id`, carried on the return URL.' }],
|
|
2908
|
+
query: null, request: null, response: checkoutStatus,
|
|
2909
|
+
errors: [...DEVELOPER_GUARD, 'tier_required', 'not_found'], transport: 'http',
|
|
2910
|
+
notes: 'Calls the same `reconcilePayment(deps, molliePaymentId)` the webhook calls, so a return page that lands before the ' +
|
|
2911
|
+
'webhook does still sees the payment applied — this route, not the webhook, is what the dev stack and the test-mode suite rely on, ' +
|
|
2912
|
+
'since Mollie refuses an unreachable webhook URL. `plan` is the org\'s plan after applying, unchanged unless `purpose` ' +
|
|
2913
|
+
'is `upgrade` and `status` is `paid`.',
|
|
2914
|
+
},
|
|
2915
|
+
{
|
|
2916
|
+
method: 'POST', path: '/api/billing/vat-id/check', section: 'billing',
|
|
2917
|
+
summary: 'Checks a VAT ID against VIES, for the checkout form and the details page.',
|
|
2918
|
+
audience: 'developer', auth: 'developer', rateLimited: true, ownerTier: true, status: 200,
|
|
2919
|
+
params: [], query: null, request: vatIdCheckRequest, response: vatIdCheckResponse,
|
|
2920
|
+
errors: [...DEVELOPER_GUARD, 'tier_required', 'validation_error', 'rate_limited'], transport: 'http',
|
|
2921
|
+
notes: 'Rate limited on its own `billing.vat_check` bucket, sized for a form checked on blur rather than for a checkout. Never refuses for ' +
|
|
2922
|
+
'an `unverified` VIES answer — the checkout itself accepts `unverified` and the hourly sweep re-checks it — this route ' +
|
|
2923
|
+
'only reports what VIES currently says, in a different place for the same ID, entered either at checkout or on `PATCH ' +
|
|
2924
|
+
'/api/billing/details`.',
|
|
2925
|
+
},
|
|
2926
|
+
{
|
|
2927
|
+
method: 'PATCH', path: '/api/billing/details', section: 'billing',
|
|
2928
|
+
summary: "Edits the billing account's invoice email or VAT ID.",
|
|
2929
|
+
audience: 'developer', auth: 'developer', rateLimited: false, ownerTier: true, status: 200,
|
|
2930
|
+
params: [], query: null, request: billingDetailsUpdate, response: billingView,
|
|
2931
|
+
errors: [...DEVELOPER_GUARD, 'tier_required', 'validation_error', 'not_found'], transport: 'http',
|
|
2932
|
+
notes: '`404 not_found` when the org has no billing account yet — there is nothing here to edit before the first checkout. A new `vat_id` ' +
|
|
2933
|
+
'is re-checked through VIES the same way `POST /api/billing/vat-id/check` does; a valid ID entered here lifts the block a definitive ' +
|
|
2934
|
+
'`invalid` answer placed on the next renewal.',
|
|
2935
|
+
},
|
|
2936
|
+
{
|
|
2937
|
+
method: 'POST', path: '/api/billing/change', section: 'billing',
|
|
2938
|
+
summary: "Moves the org's plan, cycle or add-ons, charging increases at once.",
|
|
2939
|
+
audience: 'developer', auth: 'developer', rateLimited: false, ownerTier: true, status: 200,
|
|
2940
|
+
params: [], query: null, request: billingChangeRequest, response: billingChangeResponse,
|
|
2941
|
+
errors: [...DEVELOPER_GUARD, 'tier_required', 'validation_error', 'plan_limit', 'target_state_conflict', ...BILLING_OFF, ...MOLLIE],
|
|
2942
|
+
transport: 'http',
|
|
2943
|
+
notes: 'The body names the **absolute** target — plan, cycle and add-ons — never a delta: the route compares it with the ' +
|
|
2944
|
+
'org\'s current state and splits the difference. The increasing part is charged now, through the same proration `changeNetCents` ' +
|
|
2945
|
+
'computes, and takes effect the moment Mollie accepts the `recurring` payment with anything but `failed`, `canceled` or `expired` ' +
|
|
2946
|
+
'— cards answer within seconds, SEPA stays `pending` for days; if Mollie refuses or does not answer, nothing is applied ' +
|
|
2947
|
+
'and this answers `502 payment_provider_unavailable` instead. The decreasing part — a lower plan, yearly → monthly, fewer add-ons — ' +
|
|
2948
|
+
'is stored as a pending change and applied at the period\'s end, same as `PUT /api/org/plan/change`; a lower plan over the target\'s ' +
|
|
2949
|
+
'limits answers `409 plan_limit` and the console opens #103\'s choose-what-stays page. Any increase first withdraws a pending ' +
|
|
2950
|
+
'downgrade or cancel, exactly as the admin route does. `409 target_state_conflict` names `billing` with rule `no_account` (no ' +
|
|
2951
|
+
'checkout yet — use `POST /api/billing/checkout`), `past_due` (an open invoice has to be paid first) or `no_valid_mandate` (the ' +
|
|
2952
|
+
'payment method needs renewing first, `POST /api/billing/payment-method`). `charged` in the response is the invoice from the part ' +
|
|
2953
|
+
'charged now, or `null` when the whole request was a decrease.',
|
|
2954
|
+
},
|
|
2955
|
+
{
|
|
2956
|
+
method: 'POST', path: '/api/billing/payment-method', section: 'billing',
|
|
2957
|
+
summary: 'Starts a Mollie checkout for a new payment method.',
|
|
2958
|
+
audience: 'developer', auth: 'developer', rateLimited: true, ownerTier: true, status: 201,
|
|
2959
|
+
params: [], query: null, request: paymentMethodChangeRequest, response: checkoutResponse,
|
|
2960
|
+
errors: [...DEVELOPER_GUARD, 'tier_required', 'validation_error', 'not_found', 'rate_limited', ...BILLING_OFF, ...MOLLIE],
|
|
2961
|
+
transport: 'http',
|
|
2962
|
+
notes: 'Rate limited on the `billing.checkout` bucket, same as `POST /api/billing/checkout`. Creates a `first` payment on the existing ' +
|
|
2963
|
+
'Mollie customer: with an open invoice, the new payment **is** that invoice\'s amount, for any method the currency ' +
|
|
2964
|
+
'allows, and pays it; without one, `card` and `paypal` use a zero amount and `sepa` is refused — Mollie allows a zero-amount first ' +
|
|
2965
|
+
'payment for card and PayPal only — `400 validation_error` with `{ field: \'method\', rule: \'sepa_needs_open_invoice\' }`. SEPA is ' +
|
|
2966
|
+
'also refused outside the org\'s EUR currency, `{ field: \'method\', rule: \'sepa_eur_only\' }`. `404 not_found` when the org has no ' +
|
|
2967
|
+
'billing account yet. When the new mandate turns `valid`, every other mandate of the customer is revoked.',
|
|
2968
|
+
},
|
|
2969
|
+
{
|
|
2970
|
+
method: 'POST', path: '/api/billing/cancel', section: 'billing',
|
|
2971
|
+
summary: "Schedules the org's plan to cancel to Basic at the period's end.",
|
|
2972
|
+
audience: 'developer', auth: 'developer', rateLimited: false, ownerTier: true, status: 200,
|
|
2973
|
+
params: [], query: null, request: billingCancelRequest, requestOptional: true, response: billingView,
|
|
2974
|
+
errors: [...DEVELOPER_GUARD, 'tier_required', 'validation_error', 'not_found', 'plan_limit'], transport: 'http',
|
|
2975
|
+
notes: 'A body is optional — `reason` alone, and nobody but Fleetless reads it. `404 not_found` when the org has no billing account to ' +
|
|
2976
|
+
'cancel. Takes effect at the period\'s end, nothing credited or refunded; over Basic\'s limits this is refused `409 ' +
|
|
2977
|
+
'plan_limit` and the console sends the owner to #103\'s choose-what-stays page instead, the same as a plan downgrade. "Cancel at ' +
|
|
2978
|
+
'period end" is not a flag here — it reads as `pending_change.target_plan === \'basic\'` on `GET /api/org/plan`.',
|
|
2979
|
+
},
|
|
2980
|
+
{
|
|
2981
|
+
method: 'POST', path: '/api/billing/resume', section: 'billing',
|
|
2982
|
+
summary: 'Withdraws a scheduled cancel, keeping the current plan.',
|
|
2983
|
+
audience: 'developer', auth: 'developer', rateLimited: false, ownerTier: true, status: 200,
|
|
2984
|
+
params: [], query: null, request: null, response: billingView,
|
|
2985
|
+
errors: [...DEVELOPER_GUARD, 'tier_required', 'not_found'], transport: 'http',
|
|
2986
|
+
notes: '`404 not_found` when nothing is pending — there is no scheduled cancel to withdraw. The org stays on its current plan, unchanged.',
|
|
2987
|
+
},
|
|
2988
|
+
{
|
|
2989
|
+
method: 'GET', path: '/api/billing/invoices/:id/pdf', section: 'billing',
|
|
2990
|
+
summary: "Downloads one invoice's rendered PDF.",
|
|
2991
|
+
audience: 'developer', auth: 'developer', rateLimited: false, ownerTier: true, status: 200,
|
|
2992
|
+
params: [{ name: 'id', description: 'The invoice id, from `billingInvoice.id` in `GET /api/billing`\'s `invoices`.' }],
|
|
2993
|
+
query: null, request: null, response: null, contentType: 'application/pdf',
|
|
2994
|
+
errors: [...DEVELOPER_GUARD, 'tier_required', 'not_found'], transport: 'http',
|
|
2995
|
+
notes: 'Rendered once, with `pdfkit`, when the invoice is issued, and stored as bytes — an issued invoice never changes, so this always ' +
|
|
2996
|
+
'answers the same PDF for the same id. `404 not_found` for an unknown id or one from another org.',
|
|
2997
|
+
},
|
|
2998
|
+
{
|
|
2999
|
+
method: 'POST', path: '/api/billing/invoices/:id/pay', section: 'billing',
|
|
3000
|
+
summary: 'Pays one open invoice, through a new Mollie payment.',
|
|
3001
|
+
audience: 'developer', auth: 'developer', rateLimited: true, ownerTier: true, status: 201,
|
|
3002
|
+
params: [{ name: 'id', description: 'The invoice id, from `billingInvoice.id`, of the open invoice to pay.' }],
|
|
3003
|
+
query: null, request: null, response: checkoutResponse,
|
|
3004
|
+
errors: [...DEVELOPER_GUARD, 'tier_required', 'not_found', 'target_state_conflict', 'rate_limited', ...BILLING_OFF, ...MOLLIE],
|
|
3005
|
+
transport: 'http',
|
|
3006
|
+
notes: 'Rate limited on the `billing.checkout` bucket, same as `POST /api/billing/checkout`. `409 target_state_conflict` names `invoice` ' +
|
|
3007
|
+
'with rule `not_open` when the invoice is already `paid` or `uncollectible` — there is nothing left to pay. Paying the open ' +
|
|
3008
|
+
'invoice of a locked org unlocks it and keeps the plan running to the period\'s end, the same as a renewal that ' +
|
|
3009
|
+
'succeeds on a retry. `404 not_found` for an unknown id or one from another org.',
|
|
3010
|
+
},
|
|
3011
|
+
{
|
|
3012
|
+
method: 'POST', path: '/api/billing/mollie/webhook', section: 'billing',
|
|
3013
|
+
summary: "Takes Mollie's payment-changed notification and reconciles the payment.",
|
|
3014
|
+
audience: 'internal', auth: 'none', rateLimited: true, ownerTier: false, status: 200,
|
|
3015
|
+
params: [], query: null, request: null, response: null,
|
|
3016
|
+
errors: [], transport: 'http',
|
|
3017
|
+
notes: '**The body is never trusted**: it names only a payment id (`id=tr_…`, form-encoded, Mollie\'s own shape), and this route ' +
|
|
3018
|
+
'does nothing with it but call `reconcilePayment(deps, molliePaymentId)` — the same function `GET /api/billing/checkout/:id` and ' +
|
|
3019
|
+
'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
|
+
'processing fault, which is `500`** so Mollie retries the notification rather than this cloud losing it. `auth: \'none\'` because ' +
|
|
3023
|
+
'Mollie signs nothing Fleetless checks here — the payment is only ever trusted once fetched back from Mollie\'s own API with the ' +
|
|
3024
|
+
'configured key.',
|
|
2848
3025
|
},
|
|
2849
3026
|
/* ------------------------------------------------- assets (robot upload) */
|
|
2850
3027
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fleetless/contracts",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0-next.1",
|
|
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",
|