mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 05:25:40 +09:00
feat(premium): let self-hosted instances sell premium and gifts (#3025)
This commit is contained in:
1 parent
192cec689a
commit
3736d94d73
159 files changed
+15802
-9067
No files matched your search
@@ -6,7 +6,7 @@ description: Instance configuration, branding, registration control, limits, and
|
||||
|
||||
import RouteHeader from '@/components/RouteHeader.astro';
|
||||
|
||||
Instance configuration is everything an operator can change at runtime. It covers single sign-on, Gateway rollout, registration policy, branding and legal links, instance policy, third party integrations, media retention, and the ordered limit configuration. The [Instance](/http-api/instance/) resource serves the subset published to unauthenticated clients.
|
||||
Instance configuration is everything an operator can change at runtime. It covers single sign-on, Gateway rollout, registration policy, branding and legal links, instance policy, third party integrations, premium billing, media retention, and the ordered limit configuration. The [Instance](/http-api/instance/) resource serves the subset published to unauthenticated clients.
|
||||
|
||||
Every write is a merge over the stored configuration, and an omitted key leaves the stored value unchanged. The limit configuration write is the one exception and replaces the stored document.
|
||||
|
||||
@@ -46,6 +46,7 @@ Missing settings use the defaults documented below. Invalid stored configuration
|
||||
| policy | [instance policy](#instance-policy-object) object | Community, direct message, premium, and gating policy |
|
||||
| integrations | [instance integrations](#instance-integrations-object) object | Third party provider settings and their resolved availability |
|
||||
| media | [instance media](#instance-media-object) object | Attachment retention settings |
|
||||
| billing | [instance billing](#instance-billing-object) object | Premium billing settings and the state resolved from them |
|
||||
|
||||
## SSO configuration object
|
||||
|
||||
@@ -381,6 +382,8 @@ Community, direct message, premium and gating policy for the whole deployment.
|
||||
| mirror | Resolve premium access from the account's own entitlement and premium flags |
|
||||
| everyone | Grant premium access to every account on a self-hosted deployment |
|
||||
|
||||
A self-hosted deployment issues gift codes and sells premium only in `mirror`.
|
||||
|
||||
## Deferred phone gate object
|
||||
|
||||
A rule for accounts whose phone verification requirement was deferred. When such an account joins a guild within `window_hours` of registration, and the guild has the `DISCOVERABLE` feature or more than `member_threshold` members, Fluxer refuses the join until the account verifies a phone.
|
||||
@@ -464,6 +467,58 @@ Client identity the deployment presents to Bluesky, and the number of signing ke
|
||||
|
||||
Signing keys are write-only and must have unique identifiers.
|
||||
|
||||
## Instance billing object
|
||||
|
||||
Stripe billing for the premium tier. Each stored field is nullable, and null falls back to the [deployment configuration](/operator/configuration/#payments).
|
||||
|
||||
### Structure
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| enabled | ?boolean | Operator override of the billing switch, or null for no override |
|
||||
| effective_enabled | boolean | Whether billing is switched on once the override and the deployment configuration are combined |
|
||||
| stripe_secret_key_set | boolean | Whether a Stripe secret key is stored or supplied by deployment configuration |
|
||||
| stripe_webhook_secret_set | boolean | Whether a Stripe webhook secret is stored or supplied by deployment configuration |
|
||||
| stripe_secret_key_stored | boolean | Whether a Stripe secret key is stored, not counting deployment configuration |
|
||||
| stripe_webhook_secret_stored | boolean | Whether a Stripe webhook secret is stored, not counting deployment configuration |
|
||||
| automatic_tax<sup>4</sup> | ?boolean | Stored switch for Stripe automatic tax at checkout, or null for the default |
|
||||
| tax_id_collection<sup>4</sup> | ?boolean | Stored switch for collecting a buyer tax ID at checkout, or null for the default |
|
||||
| terms_consent_required<sup>4</sup> | ?boolean | Stored switch for requiring terms of service consent at checkout, or null for the default |
|
||||
| effective_automatic_tax | boolean | Whether checkout uses Stripe automatic tax |
|
||||
| effective_tax_id_collection | boolean | Whether checkout collects a buyer tax ID |
|
||||
| effective_terms_consent_required | boolean | Whether checkout requires terms of service consent |
|
||||
| default_currency | ?string | Currency used when no country mapping applies, or null |
|
||||
| prices<sup>1</sup> | ?object | Stored [billing price sets](#billing-price-set-object) keyed by currency, or null to use the deployment configuration |
|
||||
| country_currencies | ?object | Stored map of two-letter country codes to currencies, or null |
|
||||
| legacy_prices<sup>2</sup> | ?object | Stored map of legacy price slots to arrays of Stripe price IDs, or null to use the deployment configuration |
|
||||
| billing_active<sup>3</sup> | boolean | Whether premium purchases are available |
|
||||
| stripe_serviceable<sup>5</sup> | boolean | Whether existing Stripe subscriptions can be managed, cancelled and billed |
|
||||
| catalog_mode | string | `operator` while `prices` is stored, otherwise `env` |
|
||||
| webhook_url | string | The URL to register as the Stripe webhook endpoint |
|
||||
|
||||
<sup>1</sup> Stored prices replace the deployment catalogue entirely and take any three-letter currency code. A buyer gets the currency mapped to their country, then `default_currency`, then the first configured currency. Fluxer's country checks for localised currencies do not apply to them
|
||||
|
||||
<sup>2</sup> A slot is `monthly`, `yearly`, `gift_1_month`, or `gift_1_year`, an underscore, and a currency, such as `monthly_GBP`. Existing subscriptions on a listed price keep renewing
|
||||
|
||||
<sup>3</sup> True when billing is switched on, a Stripe secret key is set, at least one currency has both a monthly and a yearly price, and premium tiering is in force. Premium tiering is always in force on a hosted deployment, and on a self-hosted one only in the `mirror` [premium mode](#premium-modes)
|
||||
|
||||
<sup>4</sup> A hosted deployment always turns all three on. A self-hosted deployment defaults all three to off. Automatic tax needs Stripe Tax set up, and terms consent needs a terms of service URL in the Stripe public details
|
||||
|
||||
<sup>5</sup> True while a Stripe secret key is set and premium tiering is in force. On a hosted deployment billing must also be switched on. Only the checkout routes follow `billing_active`
|
||||
|
||||
## Billing price set object
|
||||
|
||||
The Stripe price IDs for one currency. Each is nullable.
|
||||
|
||||
### Structure
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| monthly | ?string | Price ID of the monthly recurring plan |
|
||||
| yearly | ?string | Price ID of the yearly recurring plan |
|
||||
| gift_1_month | ?string | Price ID of the one-month gift |
|
||||
| gift_1_year | ?string | Price ID of the one-year gift |
|
||||
|
||||
## Instance media object
|
||||
|
||||
Attachment retention overrides the operator has set, and the values in force.
|
||||
@@ -642,10 +697,11 @@ The body has one optional object for each section. Fluxer leaves an absent secti
|
||||
| integrations?<sup>3</sup> | object | `gif`, `youtube`, `captcha`, `email`, and `bluesky` sub-objects, the last of which also has the `keys` array |
|
||||
| media? | object | `attachment_decay` overrides, each nullable to restore the deployment default |
|
||||
| policy? | [instance policy update](#instance-policy-update-structure) object | Community, direct message, premium, and gating policy |
|
||||
| billing?<sup>4</sup> | object | Stored [instance billing](#instance-billing-object) fields, with `stripe_secret_key` and `stripe_webhook_secret` for the secrets |
|
||||
|
||||
<sup>1</sup> The supplied fields are merged over the stored configuration and the result is validated as a whole. Every supplied endpoint URL uses `https`, has no credentials and no fragment, and resolves to a publicly routable address, otherwise the request returns 400 `INVALID_FORM_BODY` with `INVALID_URL_FORMAT` or `URL_NOT_PUBLICLY_ROUTABLE`
|
||||
|
||||
<sup>2</sup> `branding.product_name` is 1 to 80 characters, every branding and legal URL is at most 2048 characters and nullable, `branding.theme_color` is at most 64 characters and nullable, and `setup.configured` and `registration.collect_date_of_birth` are booleans. Every string is trimmed before it is stored
|
||||
<sup>2</sup> `branding.product_name` is 1 to 80 characters, every branding and legal URL is at most 2048 characters and nullable, `branding.theme_color` is at most 64 characters and nullable, and `setup.configured` and `registration.collect_date_of_birth` are booleans. `branding.premium_product_name` is 1 to 40 characters, and null restores the default. `branding.premium_info_url` is an absolute `http` or `https` URL. Every string is trimmed before it is stored
|
||||
|
||||
`voice_noise_suppression` takes every [voice noise suppression configuration](#voice-noise-suppression-configuration-object) field except `config_version`, each bound as documented there. Fluxer raises `config_version` by one on each request that supplies at least one of them. A section that is absent, or present with no field set, writes nothing and leaves `config_version` alone.
|
||||
|
||||
@@ -661,6 +717,8 @@ The body has one optional object for each section. Fluxer leaves an absent secti
|
||||
|
||||
<sup>3</sup> A secret such as `klipy_api_key`, `api_key`, `hcaptcha_secret_key`, `turnstile_secret_key`, or the SMTP `password` is written when supplied and left alone when absent. `integrations.bluesky.keys` is the only way to write the Bluesky signing keys counted as `bluesky.key_count`. It takes up to 8 entries of `kid` (1-255 characters) and nullable `private_key` (up to 10000 characters), and replaces the stored key set outright
|
||||
|
||||
<sup>4</sup> Accepted only on a self-hosted deployment. A hosted deployment rejects the section, and `app_public.branding.premium_product_name` and `premium_info_url`, with 400 `INVALID_FORM_BODY`. A Stripe secret, `enabled`, `automatic_tax`, `tax_id_collection` and `terms_consent_required` are written when supplied, cleared by null, and left alone when absent. A null `enabled` follows `FLUXER_STRIPE_ENABLED`. `prices`, `country_currencies`, and `legacy_prices` replace the stored value outright, a price slot left out is stored as null, and an empty `prices` object restores the deployment catalogue. A price ID is `price_` followed by letters and digits. A currency is three upper-case letters and a country code is two
|
||||
|
||||
:::note[Single sign-on URL validation is conditional]
|
||||
Fluxer skips URL validation while the merged configuration leaves single sign-on disabled. A configuration both enabled and enforced fails validation against `sso` with `SSO_MISCONFIGURED` unless it resolves an authorisation endpoint, a token endpoint, a client identifier, and a claims source, which is `jwks_url` or `userinfo_url`. A set `issuer` meets the endpoint and claims source requirements, because Fluxer can discover them from the issuer.
|
||||
:::
|
||||
@@ -685,6 +743,8 @@ Fluxer skips URL validation while the merged configuration leaves single sign-on
|
||||
|
||||
`direct_messages_locked` accepts only false, and a body that sets it to true fails with 400 `INVALID_FORM_BODY`.
|
||||
|
||||
On a self-hosted deployment, billing and the `everyone` premium mode exclude each other. A body that would leave billing switched on in `everyone` fails with 400 `INVALID_FORM_BODY` and writes nothing, whether it enables billing or switches the premium mode.
|
||||
|
||||
### Response
|
||||
|
||||
| Status | Body | Condition |
|
||||
@@ -693,12 +753,12 @@ Fluxer skips URL validation while the merged configuration leaves single sign-on
|
||||
| 400 | [error response](/admin-api/#error-response) | A policy transition is refused, returned as `INSTANCE_POLICY_TRANSITION_NOT_ALLOWED` |
|
||||
|
||||
:::caution[Sections are applied one after another]
|
||||
The order is `gateway_rollout`, `voice_noise_suppression`, `push_relay`, `domain_migration`, `altcha_captcha`, `profile_timezone`, `experiment_delivery`, `sso`, `registration`, `app_public` branding, legal, and registration fields, `integrations`, `media`, `policy`, and finally `app_public.setup`. A failure part way through leaves the earlier sections written.
|
||||
The order is `gateway_rollout`, `voice_noise_suppression`, `push_relay`, `domain_migration`, `altcha_captcha`, `profile_timezone`, `experiment_delivery`, `sso`, `registration`, `app_public` branding, legal, and registration fields, `integrations`, `media`, `policy`, `billing`, and finally `app_public.setup`. A failure part way through leaves the earlier sections written.
|
||||
:::
|
||||
|
||||
### Side effects
|
||||
|
||||
Fluxer publishes a `gateway_rollout` change to the Gateway cluster. A `push_relay` change reaches the push service without a restart. Premium mode changes affect the limits in force without replacing the saved limit configuration. On self-hosted deployments, `everyone` hides premium-filtered rules. Switching back to `mirror` restores them unless an Admin has replaced the limit configuration in the meantime. Enabling single community mode creates the community when none is designated, with the acting Admin as owner.
|
||||
Fluxer publishes a `gateway_rollout` change to the Gateway cluster. A `push_relay` change reaches the push service without a restart. A `billing` change applies without a restart. Premium mode changes affect the limits in force without replacing the saved limit configuration. On self-hosted deployments, `everyone` hides premium-filtered rules. Switching back to `mirror` restores them unless an Admin has replaced the limit configuration in the meantime. Enabling single community mode creates the community when none is designated, with the acting Admin as owner.
|
||||
|
||||
Initial setup completes on the first update that sets `app_public.setup.configured` to true from a session credential whose account holds neither `admin:authenticate` nor the wildcard. That update grants the account the wildcard Admin ACL and marks the deployment as bootstrapped.
|
||||
|
||||
|
||||
Reference in new issue
Block a user