From bf3d73a5f737ce3cd6f0dda6528643f51bc3036f Mon Sep 17 00:00:00 2001 From: Hampus Date: Mon, 5 Oct 2026 13:36:33 +0200 Subject: [PATCH] fix(ci): drop removed preapproval docs and format a test (#3220) --- .../tests/AccountActionConsumer.test.ts | 7 +- fluxer_docs/scripts/VerifyDocsStyle.ts | 3 +- .../src/content/docs/admin-api/reports.mdx | 3 +- .../src/content/docs/http-api/billing.mdx | 157 +----------------- .../docs/http-api/deployment-availability.md | 2 - .../src/content/docs/http-api/index.md | 2 +- 6 files changed, 14 insertions(+), 160 deletions(-) diff --git a/fluxer_api/src/api/worker/tests/AccountActionConsumer.test.ts b/fluxer_api/src/api/worker/tests/AccountActionConsumer.test.ts index 4ad815d8a..03cba33e5 100644 --- a/fluxer_api/src/api/worker/tests/AccountActionConsumer.test.ts +++ b/fluxer_api/src/api/worker/tests/AccountActionConsumer.test.ts @@ -567,7 +567,12 @@ describe('account action apply', () => { user_id: USER_ID, }); expect(h.shreds).toEqual([ - {userId: BigInt(USER_ID), entries: 450, adminUserId: SYSTEM_USER_ID, auditLogReason: 'Automated action a:07:4242:0'}, + { + userId: BigInt(USER_ID), + entries: 450, + adminUserId: SYSTEM_USER_ID, + auditLogReason: 'Automated action a:07:4242:0', + }, ]); expect(h.audits).toEqual([ { diff --git a/fluxer_docs/scripts/VerifyDocsStyle.ts b/fluxer_docs/scripts/VerifyDocsStyle.ts index 5e5e18e75..0188a31ba 100644 --- a/fluxer_docs/scripts/VerifyDocsStyle.ts +++ b/fluxer_docs/scripts/VerifyDocsStyle.ts @@ -281,11 +281,10 @@ const ACCEPTED_TABLE_FINDINGS = new Map1 | string | The comment shown to the reporter (0-512 characters) | | notify_reporter? | boolean | Whether to notify the reporter by system DM and email (default true) | +| resolution? | string | How the report was resolved: `actioned`, `no_violation` or `duplicate` | 1 Stored as null when it is omitted or empty, and shown to the reporter in the resolution notice otherwise @@ -390,7 +391,7 @@ Resolving a report notifies the reporter unless `notify_reporter` is false. A re The report moves to RESOLVED and records `resolved_at`, `resolved_by_admin_id`, and the public comment. -Fluxer records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with target type `report`, target ID equal to the report ID, and action `resolve_report`. The metadata is `report_id`, `report_type`, `notify_reporter`, `reporter_dm_sent`, and `reporter_email_sent`. +Fluxer records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with target type `report`, target ID equal to the report ID, and action `resolve_report`. The metadata is `report_id`, `report_type`, `notify_reporter`, `reporter_dm_sent`, and `reporter_email_sent`, plus `resolution` when the request sets it. Unless `notify_reporter` is false, Fluxer notifies the reporter when the report has a reporter account that still exists. The reporter receives a system direct message, and an email when the account has an address. An anonymous Digital Services Act notice has no reporter account and is resolved with no notification. diff --git a/fluxer_docs/src/content/docs/http-api/billing.mdx b/fluxer_docs/src/content/docs/http-api/billing.mdx index 31a65bf77..42b62f3d4 100644 --- a/fluxer_docs/src/content/docs/http-api/billing.mdx +++ b/fluxer_docs/src/content/docs/http-api/billing.mdx @@ -1,14 +1,14 @@ --- # SPDX-License-Identifier: AGPL-3.0-or-later title: Billing -description: Checkout, card preapproval, gift purchase, age verification, refunds, and the Stripe webhook. +description: Checkout, gift purchase, age verification, refunds, and the Stripe webhook. --- import RouteHeader from '@/components/RouteHeader.astro'; -These routes take payment for premium, verify an account holder's age, and refund the most recent purchase. Checkout, card preapproval and age verification each finish on the payment provider's own pages, so those routes create the provider session and return its URL for the browser to open. [Receive Stripe webhook](#receive-stripe-webhook) takes the signed events the provider sends back. The [Premium resource](/http-api/premium/) owns entitlement state, mirrored billing data and subscription self-service. +These routes take payment for premium, verify an account holder's age, and refund the most recent purchase. Checkout and age verification each finish on the payment provider's own pages, so those routes create the provider session and return its URL for the browser to open. [Receive Stripe webhook](#receive-stripe-webhook) takes the signed events the provider sends back. The [Premium resource](/http-api/premium/) owns entitlement state, mirrored billing data and subscription self-service. -A self-hosted deployment never serves age verification or the refund routes, serves the checkout routes only while billing is active, and serves [receive Stripe webhook](#receive-stripe-webhook) while Stripe is serviceable, as [deployment availability](/http-api/deployment-availability/) describes. All of them are user-only except [receive Stripe webhook](#receive-stripe-webhook) and [continue localised card preapproval](#continue-localised-card-preapproval). +A self-hosted deployment never serves age verification or the refund routes, serves the checkout routes only while billing is active, and serves [receive Stripe webhook](#receive-stripe-webhook) while Stripe is serviceable, as [deployment availability](/http-api/deployment-availability/) describes. All of them are user-only except [receive Stripe webhook](#receive-stripe-webhook). :::note[Every response is a Fluxer object] The responses are Fluxer redirect, eligibility, refund and acknowledgement objects. They copy only a few payment provider values, such as the identifiers on a [refund](#refund-object) and the `invoice_id` of a [refund eligibility](#refund-eligibility-object) object. @@ -16,7 +16,7 @@ The responses are Fluxer redirect, eligibility, refund and acknowledgement objec ## Redirect URL object -One absolute URL that completes a billing operation in a browser. [Create subscription checkout](#create-subscription-checkout), [create localised card preapproval](#create-localised-card-preapproval), [create gift checkout](#create-gift-checkout) and [create age verification session](#create-age-verification-session) all answer with it. The object has no session identifier, so a client sends the browser to the returned value. +One absolute URL that completes a billing operation in a browser. [Create subscription checkout](#create-subscription-checkout), [create gift checkout](#create-gift-checkout) and [create age verification session](#create-age-verification-session) all answer with it. The object has no session identifier, so a client sends the browser to the returned value. ### Structure @@ -34,55 +34,6 @@ One absolute URL that completes a billing operation in a browser. [Create subscr } ``` -## Localised card preapproval result object - -The state of one card preapproval flow. A localised recurring price is offered only to a card issued in the matching country. Fluxer creates the paid session only after a separate setup mode session, which takes no payment, has shown that the card was issued in the matching country. `status` says which of the variants the object is, and each variant defines its own members. - -### Structure - -| Field | Type | Description | -| --- | --- | --- | -| status | string | [Preapproval status](#preapproval-statuses) | -| url1 | string | Paid checkout URL | -| reason2 | string | [Preapproval rejection reason](#preapproval-rejection-reasons) | -| actual_country?3 | ?string | Detected two-letter card issuing country | - -1 Present and required only when `status` is `ready`. It is stable for the lifetime of the continuation token - -2 Present and required only when `status` is `rejected` - -3 Defined only inside the `rejected` variant, where it is optional. It has a value only for `country_mismatch`, and is null there when the card reported no issuing country - -### Example - -```json -{ - "status": "rejected", - "reason": "country_mismatch", - "actual_country": "PT" -} -``` - -## Preapproval statuses - -| Value | Description | -| --- | --- | -| pending | Setup has not completed, or another [continue localised card preapproval](#continue-localised-card-preapproval) call for this token is still running | -| ready | The card was approved and the paid checkout URL is available | -| rejected | The card was refused and the paid checkout cannot continue | -| expired | The token is empty, unknown, or has passed its one-day lifetime | - -## Preapproval rejection reasons - -| Value | Description | -| --- | --- | -| country_mismatch | Card issuing country does not match the country recorded on the preapproval session | -| missing_customer | Preapproval session metadata had no payment provider customer | -| missing_payment_method | Completed setup intent resolved no payment method | -| missing_setup_intent | Completed session had no setup intent | -| payment_method_not_card | Approved payment method is not a card | -| unknown | Preapproval failed without a more specific public reason | - ## Refund eligibility object Whether the account's most recent purchase can still be refunded without operator involvement. When no refundable purchase was resolved, `eligible` and `cancels_subscription` are false. `invoice_id`, `invoice_amount_paid_cents`, `currency`, `paid_at` and `refund_window_expires_at` are null. `cooldown_expires_at` still reports an active cooldown unless the reason is `feature_unavailable`. A client reads `eligible` and `reason` before rendering any amount or timestamp. @@ -225,10 +176,6 @@ An account already holding an `active` or `trialing` subscription can submit a r The conversion requires a stored billing cycle on the account that differs from the submitted price's cycle. Any other blocking subscription status, no stored billing cycle, and a matching cycle all still produce 403 `PREMIUM_PURCHASE_BLOCKED`. When scheduling the change fails, the caller receives that failure. -:::note[Only this route converts a blocked purchase] -A blocked purchase always fails on [create localised card preapproval](#create-localised-card-preapproval) and [continue localised card preapproval](#continue-localised-card-preapproval). -::: - ### JSON body | Field | Type | Description | @@ -271,102 +218,6 @@ Premium entitlement otherwise changes only after the matching signed event reach 3 requests per minute for each authenticated user, on the `stripe:checkout:subscription` bucket. -## Create localised card preapproval - - - -Creates a setup mode session that captures and verifies a card before a localised recurring purchase, and returns a [redirect URL](#redirect-url-object) object. - -### Limitations - -- The claimed account, verified email and purchase flag requirements of [create subscription checkout](#create-subscription-checkout) apply unchanged, with the same codes. -- A request that Fluxer cannot geolocate and that omits `country_code` returns 400 `STRIPE_INVALID_PRODUCT_CONFIGURATION`. A resolved price that is not a recurring price in a currency other than USD and EUR also returns 400 `STRIPE_INVALID_PRODUCT_CONFIGURATION`. -- The submitted price is always recurring, so the lifetime block and the existing subscription block both apply and return 403 `PREMIUM_PURCHASE_BLOCKED` with the reason `lifetime` or `existing_subscription`. - -### JSON body - -| Field | Type | Description | -| --- | --- | --- | -| price_id | string | Registered localised recurring price ID (1-256 characters) | -| country_code?1 | string | Two-letter country used to select the regional price catalogue (2 characters) | -| client_geoip_country_code? | string | Two-letter country the client previously observed for itself (2 characters) | -| eu_withdrawal_waiver_accepted? | boolean | Whether the digital content withdrawal waiver was explicitly accepted | -| is_business? | boolean | Whether to require a billing address for tax invoicing (default false) | - -1 Declared optional by the shared request schema, and this operation refuses a request that resolves no country. The request-time geolocation country wins, so this value is read only when Fluxer cannot geolocate the request - -A submitted `payment_method` is validated against the enum and then discarded. The setup session is always restricted to cards. - -### Response - -| Status | Body | Condition | -| --- | --- | --- | -| 200 | [redirect URL](#redirect-url-object) object | The setup session was created | -| 4002 | [error response](/http-api/#error-response) | The country, the price, the account, or the payment provider blocks the setup session | -| 403 | [error response](/http-api/#error-response) | The email address is unverified, or the purchase is blocked | -| 404 | [error response](/http-api/#error-response) | The authenticated account record no longer exists | - -2 The country is absent, the price is unknown or not valid for the resolved catalogue, the account is unclaimed, the payment provider is not configured, or the payment provider rejected the request and returns `STRIPE_ERROR` - -### Side effects - -The returned URL opens a card-only setup flow. Its success callback includes the continuation token. The flow expires one day after its most recent state change. - -The flow stays pending until [receive Stripe webhook](#receive-stripe-webhook) processes the matching completion event. It is approved only when the card issuing country equals the pricing country. Otherwise the flow records a [preapproval rejection reason](#preapproval-rejection-reasons), and for `country_mismatch` the detected country. No entitlement changes. - -### Rate limit - -5 requests per minute for each authenticated user, on the `stripe:checkout:subscription:preapproval` bucket. - -## Continue localised card preapproval - - - -Reports the current state of a preapproval flow and creates the paid checkout session once the card is approved, returning a [localised card preapproval result](#localised-card-preapproval-result-object) object. - -The continuation token is the credential. Fluxer selects the flow from the token alone, and a credential supplied on the request selects no flow. - -An empty token, an unknown token, and a token whose one-day flow has expired are all reported as `expired`, so the operation never discloses whether a flow exists. - -### Limitations - -- A recorded price that is no longer registered returns 400 `STRIPE_INVALID_PRODUCT`. -- A recorded price that no longer belongs to the recorded country catalogue returns 400 `STRIPE_INVALID_PRODUCT_CONFIGURATION`. -- The claimed account, verified email, purchase flag, lifetime and existing subscription refusals all apply to the account that opened the flow. - -To create the paid session, Fluxer runs every check of [create subscription checkout](#create-subscription-checkout) and creates a checkout session the same way, using the account, price and country recorded on the flow. - -:::caution[Approval is resolved once] -Only one call at a time creates the paid session for an approved flow. While one call creates the paid session, another call for the same flow is reported as `pending`. -::: - -### JSON body - -| Field | Type | Description | -| --- | --- | --- | -| token | string | Continuation token issued by [create localised card preapproval](#create-localised-card-preapproval) (1-256 characters) | - -### Response - -| Status | Body | Condition | -| --- | --- | --- | -| 200 | [localised card preapproval result](#localised-card-preapproval-result-object) object | The current flow state was returned | -| 4001 | [error response](/http-api/#error-response) | The recorded price, the recorded account, or the payment provider blocks the flow | -| 403 | [error response](/http-api/#error-response) | The recorded account has an unverified email address, or its purchase is blocked | -| 404 | [error response](/http-api/#error-response) | The recorded account record no longer exists | - -1 The recorded price is unknown or is no longer valid for the recorded country catalogue, the recorded account is unclaimed, the payment provider is not configured, or the payment provider rejected the paid session and returns `STRIPE_ERROR` - -### Side effects - -A `pending`, `rejected` or `expired` result changes nothing. For an approved flow, Fluxer attempts to make the approved card the customer's default invoice payment method, then creates the paid checkout even when that update is unavailable. - -Fluxer runs the checks of [create subscription checkout](#create-subscription-checkout) and creates the checkout session with the approved price and country. The same token then always resolves to the same paid checkout URL. Entitlement changes only after the matching signed event reaches [receive Stripe webhook](#receive-stripe-webhook). - -### Rate limit - -30 requests per minute for each client IP address, or for the authenticated user when a credential happens to be supplied, on the `stripe:checkout:subscription:preapproval:continue` bucket. - ## Create gift checkout diff --git a/fluxer_docs/src/content/docs/http-api/deployment-availability.md b/fluxer_docs/src/content/docs/http-api/deployment-availability.md index 17b13f476..b956fcffa 100644 --- a/fluxer_docs/src/content/docs/http-api/deployment-availability.md +++ b/fluxer_docs/src/content/docs/http-api/deployment-availability.md @@ -48,8 +48,6 @@ When billing is switched off or no Stripe secret key is configured on a hosted d | Method | Route | Operation | | --- | --- | --- | | POST | /v1/stripe/checkout/subscription | [Create subscription checkout](/http-api/billing/#create-subscription-checkout) | -| POST | /v1/stripe/checkout/subscription/preapproval | [Create localised card preapproval](/http-api/billing/#create-localised-card-preapproval) | -| POST | /v1/stripe/checkout/subscription/preapproval/continue | [Continue localised card preapproval](/http-api/billing/#continue-localised-card-preapproval) | | POST | /v1/stripe/checkout/gift | [Create gift checkout](/http-api/billing/#create-gift-checkout) | | GET | /v1/premium/price-ids | [Get price IDs](/http-api/premium/#get-price-ids) | diff --git a/fluxer_docs/src/content/docs/http-api/index.md b/fluxer_docs/src/content/docs/http-api/index.md index ecfd2f240..dc29d108c 100644 --- a/fluxer_docs/src/content/docs/http-api/index.md +++ b/fluxer_docs/src/content/docs/http-api/index.md @@ -303,7 +303,7 @@ Fluxer produces at most one entry for each distinct pair of `path` and `code`, s | [Webhooks](/http-api/webhooks/) | Webhook management, message execution, GitHub, Slack, and Instatus callbacks | | [Search](/http-api/search/) | Authenticated global message search | | [Unfurl](/http-api/unfurl/) | Authenticated external URL metadata resolution | -| [Billing](/http-api/billing/) | Stripe checkout, card preapproval, gift purchase, age verification, refunds, the Stripe webhook | +| [Billing](/http-api/billing/) | Stripe checkout, gift purchase, age verification, refunds, the Stripe webhook | | [Premium](/http-api/premium/) | Premium pricing, entitlement state, subscription self-service, billing portal handoff | | [Gifts](/http-api/gifts/) | Public gift code lookup and authenticated redemption | | [Donations](/http-api/donations/) | Donation currencies and intervals, checkout sessions, the donor management link |