Skip to main content

Configuration reference

Every key the package publishes, its environment variable and its shipped default. The tables follow the order of the published files, so you can read this beside your own config/billing.php.

billing:install publishes all three files; vendor:publish --tag=billing-config re-publishes them. A key you never touch keeps the default listed here — the package merges its own config underneath yours, so a published file missing a key is not an error.

A key with an environment variable can be set from .env or edited in the published file. A key without one is edited in the file, because its value is a structure rather than a scalar. In .env, a switch reads true/false and 1/0 alike, and a number reads as the number it names.

Set billing.customer.model. It is the one key with no useful default: without it no subscription webhook can resolve its owner. See Choosing your setup.

config/billing.php​

Master switch and driver​

KeyEnvDefaultWhat it does
billing.enabledBILLING_ENABLEDtrueThe master switch, off with false or 0. Off, the manager resolves the no-op NullDriver, no account routes are registered and no billing surface exists — a clone without billing boots and schedules cleanly. Usage is still metered for the application's quotas, and none of it is queued for a provider. Nothing is scheduled while it is off, so an application that meters usage schedules billing:usage:flush itself, which then only hands back expired holds.
billing.modeBILLING_MODEfullWhat the package runs while it is enabled. full is the billing of a SaaS application. payments keeps only what takes payments into payment accounts: the two tables of the webhook deliveries and the runs of their effects, no account hub, no admin console, no Cashier routes, no default effects, and of the scheduled runs only billing:prune, which keeps the webhook records within their retention. Any other value is refused at boot.
billing.defaultBILLING_DRIVERstripeThe payment driver used when none is named. One install runs one driver. Changing providers is a migration of this value, not a mixed mode: the driver seams a screen resolves — webhook verifier and mapper, payment methods, invoices, subscription actions, proration — are bound once at boot by the active driver, so every screen gets that driver's implementation whatever row it renders. A marketplace does not need per-owner drivers: its sellers are merchants at the same provider, scoped by MerchantScope, charged through the platform's driver against their own connected account. An application that charges into the accounts of many shops hands each account to BillingManager::forAccount() instead, see Payment accounts.
billing.stripe.api_versionBILLING_STRIPE_API_VERSIONnullThe dated Stripe API version the package sends on every call. Null uses the version the package pins and is tested against. A webhook payload is rendered in the version of the endpoint that receives it, a setting at Stripe, and billing:doctor checks every endpoint against this value where Stripe is the active driver. Override it only deliberately: Stripe versions the shape of a webhook payload, so a newer version can silently change what a mapper reads.
billing.stripe.return_urlBILLING_STRIPE_RETURN_URLnullWhere Stripe sends the customer after a payment step that leaves the page, for a charge confirmed on-session: 3-D Secure outside Stripe.js, PayPal, Klarna. Stripe requires one for such a confirmation since its automatic payment methods became the default. Null rules redirecting methods out of the charge, so a card that needs no redirect confirms and one that does comes back as requires-action.
billing.adyen.hmac_keyBILLING_ADYEN_HMAC_KEYnullThe HMAC key from the Adyen notification configuration, read only when billing.default is adyen. Required there, and its absence refuses the whole batch rather than skipping verification. Adyen differs from the other two drivers in a way that decides the shape: the signature is not a request header over the whole body but one field inside each NotificationRequestItem, so a batch carries as many signatures as items. Every item is checked, and one bad item refuses the request — accepting the rest would mean a caller who gets one item right decides what else travels with it.
billing.adyen.notification_userBILLING_ADYEN_NOTIFICATION_USERnullThe basic-auth user Adyen sends with a notification, when the account is configured to send one. Optional: left null, basic auth is not required and the HMAC alone authenticates. Set it, and a request without matching credentials is refused before any signature is read — which is the cheap check and the one that costs an attacker a round trip.
billing.adyen.notification_passwordBILLING_ADYEN_NOTIFICATION_PASSWORDnullThe password half of the above. Compared with hash_equals, and both halves are compared even when the first already failed — short-circuiting on the user name would make the response time an oracle for whether a guessed user exists. Setting one half without the other is a configuration this package treats as basic auth being on, so the missing half can never match.
billing.mollie.api_keyBILLING_MOLLIE_API_KEYnullThe Mollie API key, read only when billing.default is mollie. Required there: the driver refuses to build a client without one, and a blank value counts as missing — a set-but-empty variable is what a half-finished deployment leaves behind and reads as configured to anybody looking at the file. A key that is present but malformed is refused with the same setting named, rather than letting the SDK's own exception through with a stack trace from a third-party library.
billing.mollie.webhook_urlBILLING_MOLLIE_WEBHOOK_URLnullWhere Mollie posts its status pings. A customer returns to the billing.checkout.* URLs instead. Must be absolute and reachable from the internet. It is configuration rather than a generated route URL because neither thing route() needs is reliably present: a package cannot know the public host, and the scheduled billing run creates payments from the CLI, where there is no request to take a host from. Left null it falls back to app.url joined with billing.webhook_path — right for a single-host install, wrong behind a development tunnel, where it must be the tunnel's address or Mollie posts to a host it cannot reach.
billing.mollie.methods—nullThe payment methods this Mollie account offers at checkout. Configurable because Mollie enables methods per account, so a fixed list would be wrong for most installs in both directions at once — offering what the account cannot take and hiding what it can. Left unset, the driver falls back to the methods a mandate can exist for, rather than to an empty list: empty reads as "this account can take no payments" and every screen that asks would render nothing.
billing.mollie.webhook_secretBILLING_MOLLIE_WEBHOOK_SECRETnullThe signing secret for Mollie's next-generation webhooks, which carry an HMAC-SHA256 in X-Mollie-Signature. Accepts a list as well as a single string, because rotation is a period where both secrets are live — without that an operator has to choose between rotating and losing webhooks, which is not a choice but a reason not to rotate. Left null, nothing is checked: every ping is authenticated by the fetch, since an attacker cannot invent a status Mollie will confirm. Set it, and a request that carries a signature has to carry a valid one, and an unsigned request shaped like a next-generation event (a type or an entityId) is refused, because that generation signs every delivery. The classic ping Mollie sends for each payment, which names the payment's id and nothing else, is never signed, whatever the dashboard says, so it is accepted either way and authenticated by the fetch.
billing.mollie.terminal_countries—[]Where each point-of-sale terminal stands, as an ISO 3166 country code. A sale at the counter is taxed in the country it is made in, and Mollie reports no address for a terminal, so the country is yours to state. Key an entry by a terminal's id (term_…) or by a profile's id (pfl_…) for every terminal of that profile; a terminal's own entry wins, even when it is unusable. A terminal with no usable entry takes no sale. See Payment in person.
billing.mollie.account_http.request.timeoutBILLING_MOLLIE_ACCOUNT_REQUEST_TIMEOUT10Seconds until a request of a payment account's client gives up inside a web request.
billing.mollie.account_http.request.connect_timeoutBILLING_MOLLIE_ACCOUNT_REQUEST_CONNECT_TIMEOUT3Seconds until its connection gives up inside a web request.
billing.mollie.account_http.request.retriesBILLING_MOLLIE_ACCOUNT_REQUEST_RETRIES0How often a request that never reached Mollie is sent again inside a web request. With 0 a caller hears at once that Mollie cannot answer.
billing.mollie.account_http.request.retry_delay_ms—0The pause before the first such retry, in milliseconds, growing by the same step before each further one.
billing.mollie.account_http.background.timeoutBILLING_MOLLIE_ACCOUNT_BACKGROUND_TIMEOUTnullSeconds until a request of a payment account's client gives up in a queue worker, the scheduler or Artisan. Null keeps the application's own HTTP default.
billing.mollie.account_http.background.connect_timeoutBILLING_MOLLIE_ACCOUNT_BACKGROUND_CONNECT_TIMEOUTnullSeconds until its connection gives up there. Null keeps the application's own HTTP default.
billing.mollie.account_http.background.retriesBILLING_MOLLIE_ACCOUNT_BACKGROUND_RETRIES5How often a request that never reached Mollie is sent again there, the SDK's own number. Every retry carries the request's idempotency key.
billing.mollie.account_http.background.retry_delay_ms—1000The pause before the first such retry, in milliseconds, growing by the same step before each further one.

Webhooks​

KeyEnvDefaultWhat it does
billing.webhook_pathBILLING_WEBHOOK_PATHbilling/webhookThe path the provider posts to. The route carries no middleware group and no CSRF — the driver's verifier authenticates by signature instead.
billing.webhook_middleware—[]Middleware added to every webhook endpoint: the install's, the marketplace's and the address of each payment account. The package reads no network address, so it ships no throttle per address; name one here if you want it, for instance throttle:billing-webhooks with a limiter your application defines. Stripe and Mollie answer a 429 by sending the delivery again later.
billing.webhooks.connectionBILLING_WEBHOOK_QUEUE_CONNECTIONnullQueue connection for webhook effects. Null uses the default connection.
billing.webhooks.queueBILLING_WEBHOOK_QUEUEnullQueue name for webhook effects. Set it to keep billing work off the queue your app's other jobs share.
billing.webhooks.triesBILLING_WEBHOOK_TRIES5How often a failing effect is retried before the job is marked failed. It stays re-driveable after that — the raw payload is stored, so billing:webhooks:replay --failed can run it long after the provider stopped redelivering.

Each effect runs in its own queued job, so a slow or failing effect can neither hold the provider's request open nor take the other effects down with it. See Webhooks.

Checkout​

KeyEnvDefaultWhat it does
billing.checkout.success_urlBILLING_CHECKOUT_SUCCESS_URLnullWhere a hosted checkout returns on success. Null uses the hub's checkout-return route for a subscription, which reconciles the subscription onto the local row so a paying customer is never shown the free tier, and the add-ons on the plan screen for an add-on purchase and for an on-session PaymentRails::charge() on Mollie. A tip checkout on Stripe needs it set.
billing.checkout.cancel_urlBILLING_CHECKOUT_CANCEL_URLnullWhere an abandoned checkout returns. Null uses the plan screen. A tip checkout on Stripe needs it set.
billing.checkout.purchase_success_urlBILLING_CHECKOUT_PURCHASE_SUCCESS_URLnullWhere a completed add-on purchase returns, and an on-session PaymentRails::charge() on Mollie, for an application that sends a subscription and a one-time purchase to different pages. Null uses success_url, then the add-ons on the plan screen.
billing.checkout.purchase_cancel_urlBILLING_CHECKOUT_PURCHASE_CANCEL_URLnullWhere an abandoned add-on purchase returns. Null uses cancel_url, then the plan screen.
billing.checkout.portal_return_urlBILLING_PORTAL_RETURN_URLnullWhere the hosted billing portal returns, unless the caller of HostedPortal::url() names its own. Falls back to the subscription screen, never to success_url.
billing.checkout.payment_methods_return_urlBILLING_PAYMENT_METHODS_RETURN_URLnullWhere a hosted "add a card" page returns. Falls back to the payment-methods screen.
billing.tax_counters.reversal_attributionBILLING_TAX_COUNTER_REVERSAL_ATTRIBUTIONoriginal_periodWhich window a reversal reduces: the one it corrects, or the one it happened in (reversal_period). One key governs both counters, so the threshold figure and the reported figure always agree about WHERE a reversal belongs — they still differ about its size. Only safe together with the rule that a completed threshold crossing is final; see the notes above the key in the published config.
billing.tax_counters.dac7—(section)The reporting counter's own settings.
billing.tax_counters.dac7.enabledBILLING_TAX_COUNTER_DAC7_ENABLEDtrueWhether this installation carries the reporting gross-inflow counter at all. Turn it off on a platform outside the regime. Off refuses rather than answering zero — a zero is itself a reportable figure, and a return built on one would state that every seller received nothing, with nothing red anywhere. It gates the reporting basis only: the section 19 threshold counter answers a different question and keeps running.
billing.marketplace.reporting—(section)Where a produced seller-reporting record is copied to.
billing.marketplace.reporting.export_diskBILLING_REPORTING_EXPORT_DISKnullThe disk a copy of a produced reporting record is written to. Null is a supported answer, not a missing setting: the record itself keeps the exact bytes, so an operator whose accounting reads them off the record needs no second copy — and writing one anyway would put a document containing sellers' figures somewhere nobody asked for it.
billing.marketplace.reporting.export_pathBILLING_REPORTING_EXPORT_PATHreportingThe directory on that disk. Files are named <year>-<format>-<currency>.txt, year first, so a directory sorts into filing order rather than alphabetically by format.
billing.tax_counters.us_state_gmv—(section)The subdivision gross-sales counter's own settings.
billing.tax_counters.us_state_gmv.enabledBILLING_TAX_COUNTER_US_STATE_GMVfalseWhether this installation counts buyer gross per subdivision of a destination country — the early warning for an obligation reached per state rather than nationally. Off by default, unlike the reporting counter: that one is on because it is what the package has always done, while most installations have no subdivision-level obligation anywhere. Independent of dac7.enabled in both directions, and independent of any geoblock — a counter that started when a market opened would produce its first useful figure after the year that could have breached. Off refuses rather than answering zero, because zero here reads as "we sold nothing into that state".
billing.checkout.promotion_codesBILLING_CHECKOUT_PROMOTION_CODEStrueWhether the provider's promotion-code field is offered at checkout.
billing.checkout.tax_id_collectionBILLING_CHECKOUT_TAX_ID_COLLECTIONtrueWhether a checkout taxed by the provider asks the buyer for a tax ID. Stripe reverse-charges on the ID's format, not its validity: it verifies the ID only after the session, so a well-formed invalid ID buys without VAT. A platform that sells to consumers can set it to false and charge every buyer their country's tax. It is the default for every checkout: subscribe(…, collectTaxId: true) and purchase(…, collectTaxId: true) ask on one checkout where it is false, and false leaves the field out where it is true. Read only while the provider computes tax.

Owner model and seats​

KeyEnvDefaultWhat it does
billing.ownerBILLING_OWNERuserWho owns billing: user (each user pays for themselves) or team (the user's team pays, for seats).
billing.team_relation—teamWith a team owner, the relation on the acting user that returns the paying team. Ignored for a user owner. Point it at a relation that is the membership — see the note below.
billing.seats.membership_relation—membersThe relation on the team model that returns its members. HasSeats counts it.
billing.seats.active_status_column—nullColumn to filter the member count to active members. Null when the relation already returns only active ones — a pending invite is not a paid seat.
billing.seats.active_status_value—activeThe value that column must hold to count.
billing.seats.membership_events—[]Your join, leave and remove events. A queued listener is registered on each, so a membership change re-syncs the billed quantity. Empty means nothing fires until you opt in.
billing.seats.owner_properties—['team', 'owner']For an event that does not implement AffectsSeats, the first of these properties holding the team model is read.

The owner model receives the billing notices​

The shipped notifier sends every billing notice to the billing owner through Laravel's notification stack: a failed payment, a suspension warning, a receipt, a trial about to end. The mail channel asks the owner for its address with routeNotificationFor(), and the database channel asks it for its notifications() relation. Laravel's Notifiable trait provides both, so the owner model needs it, or those methods of its own. A team model often has neither. Without them each notice fails in its queued job and never arrives, and billing:doctor names every owner model in your billing records that a notice cannot reach. To send a team's notices to someone else, its owner for example, bind notifiers of your own (see Contracts).

If team_relation points at a pointer, implement VerifiesBillingMembership​

The team owner is whatever billing.team_relation returns off the acting user, and for a relation that is the membership — a belongsTo, a belongsToMany — that answer carries its own proof: the row exists because the user belongs to the team.

A great many applications point at a mutable column the user controls instead, current_team_id being the usual one. There the two questions come apart: the pointer keeps answering after the user has been removed from the team, and billing would follow it.

This package cannot tell the two apart from the outside, and probing for method names on your team model would be guesswork. So your model declares it:

use Illuminate\Database\Eloquent\Model;
use Pushery\Billing\Contracts\VerifiesBillingMembership;

class Team extends Model implements VerifiesBillingMembership
{
public function hasBillingMember(Model $actor): bool
{
return $this->hasMember($actor);
}
}

A team that implements it is asked before it is billed, and a false answer falls back to the acting user — the same direction the resolver already takes when team mode yields no team at all, so a removed member is neither billed through the team nor left without an owner.

A team model that does not implement it is trusted, exactly as before. This is a seam you opt into, not a behavior change you are handed.

Customer and tier resolution​

KeyEnvDefaultWhat it does
billing.customer.modelBILLING_CUSTOMER_MODELnullThe Eloquent model that owns a provider customer record. Set it before you take a real payment.
billing.customer.column—stripe_idThe column holding the provider's customer reference.
billing.schema.host_key_typeBILLING_HOST_KEY_TYPEnullWhat your models are keyed by: int, uuid or ulid. Left null it follows the framework: Schema::morphUsingUuids() or morphUsingUlids() in your application makes it uuid or ulid, and without either it is int. Every column referencing one of your models is declared with this type when its table is created, as Laravel's own uuidMorphs() and ulidMorphs() declare it: on PostgreSQL a uuid key is the native uuid type, so a table of yours keyed by $table->uuid('id') can be joined to the package's tables, which is what whereHas() compiles to. Set it before you migrate — get it wrong and the rows cannot be written at all, and changing it afterwards is a data migration rather than a settings change. billing:doctor reports a setting that disagrees with the tables, and a value that is none of the three: the migrations read such a value as int, so write it in lowercase.
billing.zero_tierBILLING_ZERO_TIERfreeThe fail-safe no-entitlement tier every resolver falls back to.
billing.tier_column—planThe raw column ColumnTierResolver reads — never an accessor.
billing.untouchable_tiers—[]Tier keys granted by hand and kept out of the billing flow: the plan screen does not offer them, a checkout refuses them, and the plan-sync webhook effect never flips them, in either direction. Use it for an admin-comped grant a provider event must not overwrite. A tier's own untouchable: true counts the same.
billing.order_item_preprocessors—[]Steps that may reshape a billing cycle's lines before its order is written, run in the order listed. Each implements OrderItemPreprocessor and is resolved through the container. Only a driver with a local engine reaches this — a provider that meters remotely returns an amount that is already correct. Empty is the right default: what a cycle costs beyond its plan price is a question only your application can answer. A step that throws aborts that cycle before it is claimed, so it is retried rather than billed at a partial total.

A record of what already happened finds its owner past your global scopes. Usage that was recorded, a share held for a sale, a settlement document and a webhook about a customer the provider still bills stand in the name of an owner, and a soft delete or a tenant scope on your model hides the row without settling what that owner owes or is owed. These paths look the owner up with your model's global scopes removed, as the seller reporting period does, and find nothing only where the row itself is gone. A webhook asks your own view first, so a row you show wins over a hidden one with the same customer reference.

Tiers, add-ons and coupons​

KeyEnvDefaultWhat it does
billing.tiers—one free tierThe tier catalog, keyed by tier key; the order is the upgrade ranking. Per tier: label, an optional provider_price (an id, or a per-driver map), price_display, interval, legacy_prices, metered components, trial, an archetype for the consumer-rights profile (unset, a tier is a subscription), buyers (business to sell the tier only to an owner IdentifiesBusinessBuyers names as a business; unset, anyone), and the presentation-only features, highlight and badge. A tier with no price_display is not purchasable. The client submits a tier key, never a price.
billing.addons—[]One-time purchasable add-ons, keyed by add-on key: label, provider_price, price_display, an optional grants (meter plus units), an optional buyers (business to sell the add-on only to an owner IdentifiesBusinessBuyers names as a business; unset, anyone), and an optional consignment for goods sold for a merchant (imported with intrinsic_value_minor in euro cents, or goods_located_in, beside new_means_of_transport, installed_or_assembled, subject_to_excise and basis, and the description and quantity a deemed supply needs), which the hosted purchase classifies a deemed supply from. An add-on grants prepaid usage units when it has grants, money credit when it has neither grants nor an archetype or has the voucher archetype, and nothing when it has any other archetype or is a work the register of works maps.
billing.coupons—[]Package-owned discount codes keyed by the code the customer enters, which matches whatever its case and the whitespace around it: either percent (1 to 100) or amount plus currency, with an optional expires_at: a date the code still resolves on, through the end of that day, or a moment with a time of day at which it stops. Add stripe_coupon to have the provider own the money math at checkout. The map carries no issuer, so a code declared here applies on every sale, a merchant's included; a code only one seller should honor is a billing_coupons row issued by that seller, and a live row answers before this map.
billing.currencyBILLING_CURRENCYEURUsed where an amount carries no explicit currency, such as a zero dunning fee.

The shape of a tier is worked through in Tiers and pricing; add-ons and prepaid units in Usage-based billing.

Usage and metering​

KeyEnvDefaultWhat it does
billing.dimensions—[]Extra dimensions a custom UsageProvider reports: label, unit, period, warn_threshold. The default provider ignores this — it derives its dimensions from each tier's metered components, so an owner sees exactly what they are billed for.
billing.metering.max_attemptsBILLING_METERING_MAX_ATTEMPTS8A deadline, not a limit: past it the usage is marked failed and logged as an error, because it is revenue that will not be collected unless someone acts. Do not raise it to hide a persistent failure. A value that is not a number falls back to 8.
billing.metering.backoff_secondsBILLING_METERING_BACKOFF_SECONDS60Base delay for the exponential backoff between flush attempts.
billing.metering.stall_hoursBILLING_METERING_STALL_HOURS6How long usage may sit unreported before billing:usage:reconcile calls it a stall rather than a passing outage. Keep it under your provider's back-dated acceptance window.
billing.metering.closed_period_grace_minutesBILLING_METERING_CLOSED_PERIOD_GRACE_MINUTES60How long after a billing period ends its usage can still reach that period's invoice. Stripe keeps a cycle invoice as a draft for an hour by default, and an account can set up to 72 hours. Usage of an ended period that reaches the flush later than that would be counted in the provider's meter and billed on no invoice, so it is marked failed and logged as an error instead. Set it to your account's grace. A value that is not a whole number of zero or more falls back to 60.
billing.usage.hold_secondsBILLING_USAGE_HOLD_SECONDS900How long a hold on a metered allowance stands before billing:usage:flush hands it back. Longer than your slowest metered request, shorter than you would tolerate an owner being short of allowance they never spent.
billing.usage.spend_cap.enabledBILLING_USAGE_SPEND_CAPfalseWhether metered usage is measured against a spend cap per owner. See A spend cap per customer.
billing.usage.spend_cap.defaultBILLING_USAGE_SPEND_CAP_DEFAULT0The cap of an owner who never set one, in minor units of billing.currency. 0 allows no usage past the included allowance.
billing.quota.status—429The HTTP status the billing.quota:<meter> middleware aborts a blocked request with. Only a blocking meter is gated; a degrade or fair-use meter never is.

Dunning, suspension and notifications​

KeyEnvDefaultWhat it does
billing.dunning—three rungs, at 3, 7 and 14 daysThe ladder, in order. Each rung: after_days, an optional fee (amount plus currency), an optional label. after_days and the fee's amount are integers, or strings of digits as env() delivers them. billing:dunning:advance sends each rung's warning once and charges its fee if one is set. The delinquency clock is a timestamp, never a gateway status.
billing.dunning_cure_window_daysBILLING_DUNNING_CURE_WINDOW_DAYS7How long a subscription in arrears can still be rescued. Access to that merchant is withdrawn immediately when the payment fails; this window is a chance to cure it, not a grace period with the service still running. billing:dunning:remind sends one reminder per day inside it, and the subscription expires when it runs out. It counts calendar days in the application's zone: with a window of 7, a payment that failed on the 1st is reminded through the 7th and expires on the 8th, whatever time it failed and whenever the two commands run. A sibling of the ladder above rather than a rung of it: the ladder decides which surfaces are withdrawn as time passes, this decides how long the relationship survives. Floors at one day.
billing.dunning_status—402The status a non-browser request from a delinquent owner gets. A browser request is redirected to the recovery screen instead.
billing.suspension—[]Per-surface lockout thresholds keyed by surface name; the value is the dunning level at which that surface locks. A surface with no threshold never locks.
billing.cards.warn_within_days—30How far ahead billing:cards:warn nudges an owner whose default card is expiring — the biggest preventable cause of involuntary churn.
billing.notifications.channels—['mail']The transport only, never whether the customer is told: billing notices are transactional and non-suppressible. Add database for an in-app feed. An unusable value falls back to mail rather than sending nothing. The ten notices to a billing owner carry a call-to-action button, and each links into the account hub unless you bind BillingActionUrls to send an owner you manage elsewhere to a screen of your own (contracts) — the trial notice to the payment-methods screen or the plan screen depending on whether a card is already on file, a failed payment and a suspension warning to recovery, a receipt to the invoice history. Those links appear only where the hub's routes are registered, which needs Livewire installed; without it the mail renders exactly as it did before, with its text and no button rather than a dead link. The tax-standing notice deliberately carries none: it reports a decision somebody else recorded and no hub screen edits it.
billing.notifications.tries—3How many times the queue tries a billing notice before it gives up. A mail server that turns a message away for a passing reason, a timeout or a temporary refusal, has not taken it, so another try does not deliver it twice, and the queue sends each channel as a job of its own, so a retry repeats only the channel that failed. 1 sends once and leaves a failed notice failed. A value that is not a positive whole number reads as 3.
billing.notifications.backoff—[60, 300]The seconds the queue waits before each retry of a billing notice: the first entry before the second try, the next before the third, and the last one before every try after it. A list with anything but positive whole numbers in it reads as the shipped one.

Account hub, admin and runtime​

KeyEnvDefaultWhat it does
billing.navigation—the full hubThe hub's sections, keyed by item key: label (an i18n key or a literal), route, and the optional group, icon, order and web_only. Malformed items are dropped. Remove an entry and the section is gone.
billing.runtimeBILLING_RUNTIMEwebweb or native. On native, items flagged web_only are hidden — for flows an app store forbids from being completed in-app.
billing.link_outBILLING_LINK_OUTnullThe external billing portal to link out to when an external merchant of record owns billing. Scheme-restricted: anything but an absolute http or https URL with a host is ignored.
billing.redirect_hosts—[]Hosts the application adds to the ones its payment driver serves pages from. A checkout, the page that adds a card, the customer portal and a merchant's onboarding are handed out only over https and on such a host: checkout.stripe.com, billing.stripe.com and connect.stripe.com for Stripe, www.mollie.com for Mollie. The install's Stripe account lists its custom domain here, such as payments.example.com, or its Checkout and portal open nowhere. A payment account names its own in PaymentAccount::$redirectHosts, which only that account's checkout accepts. Each refusal is logged with the host.
billing.provider_budget.callsBILLING_PROVIDER_BUDGET_CALLS30How many calls to the payment provider one payer may make in each window. The account screens spend from it before every render or action that asks the provider, and a screen of your own spends from the same budget with ProviderCallBudget::spend($owner), so the two share one count. A request past it is answered 429 before the provider is asked. null switches the budget off for an application that limits these requests itself.
billing.provider_budget.secondsBILLING_PROVIDER_BUDGET_SECONDS10The length of that window, in seconds.
billing.realtime.enabledBILLING_REALTIMEfalseOpt-in live refresh for the hub. Events broadcast only when this is on and a broadcaster is configured; otherwise the screens fall back to a bounded poll.
billing.realtime.render_toast_regionBILLING_REALTIME_TOAST_REGIONfalseWhether this package renders somewhere for the realtime toasts to land. The bridge dispatches a wirekit-toast browser event; something has to listen, and by default that is not this package. A WireKit host already renders a region reading exactly this event, which is why the default is off — a second one would show every toast twice, and that duplicate is visible only in a browser. Turn it on if your application has no toast region of its own: you get two aria-live containers (polite for info and success, assertive for warning and danger) and a small inline listener that appends the message and dismisses it after a few seconds, with no UI kit and no build step. The third option is neither — leave it off and write a one-line listener on wirekit-toast reading detail.message and detail.variant. Has no effect at all unless billing.realtime.enabled is also on, because the region follows the bridge.
billing.admin.enabledBILLING_ADMIN_ENABLEDtrueWhether the admin console is mounted at all. Turn it off when your back office has billing screens of its own: the console's route and its Livewire component are then not registered.
billing.admin.abilityBILLING_ADMIN_ABILITYbilling-adminThe Gate ability every admin-console access is authorized against. Your app defines it; until you do, the Gate denies everyone.
billing.admin.prefixBILLING_ADMIN_PREFIXadmin/billingThe URL prefix the admin console mounts under.
billing.admin.middleware—['web', 'auth']The middleware stack the admin console runs through.
billing.admin.layoutBILLING_ADMIN_LAYOUTbilling::layouts.adminThe Blade layout the admin console renders in. The default is a page of its own; point it at your back office's layout and the console takes its place in that shell. The account hub has the same lever in account.layout.
billing.subscriptions.warn_ending_within_days—3How far ahead billing:subscriptions:warn reminds an owner that a subscription they canceled ends, while resuming still keeps the plan without a gap. Override per run with --days.
billing.subscriptions.downgrade_timingBILLING_DOWNGRADE_TIMINGperiod_endWhen a downgrade lands. An upgrade is always immediate. period_end avoids owing a refund or taking away paid-for access mid-cycle; immediate downgrades at once, and any other value reads as period_end. Whether a change is a downgrade is the tier's rank in billing.tiers, not its price, so a tier listed higher and priced lower is an upgrade. The screen and the swap read this one value and this one ranking, so they cannot disagree.

Trials​

KeyEnvDefaultWhat it does
billing.trial.daysBILLING_TRIAL_DAYS0Trial length in days; 0 disables trials. Per-tier override: tiers.<key>.trial.days.
billing.trial.modeBILLING_TRIAL_MODEnullnone, subscription (collected at checkout) or generic (no subscription, granted by Trials::grant()). Null derives it: a configured generic_tier implies generic, otherwise a positive length implies subscription.
billing.trial.generic_tierBILLING_TRIAL_GENERIC_TIERnullThe tier a generic trial unlocks. Null disables generic trials — without a tier to unlock there is nothing to grant.
billing.trial.requires_payment_methodBILLING_TRIAL_REQUIRES_PMtrueWhether a subscription trial collects a card up front.
billing.trial.ending_within_days—3How many days before a trial ends the app-shell banner starts nudging.

Tax and the invoice seller​

KeyEnvDefaultWhat it does
billing.taxBILLING_TAXnoneprovider (defer to a provider that supports it), eu_oss (the bundled static EU-OSS VAT table) or none. A driver-capability decision, not a checkout option.
billing.tax_matrix—nullRates keyed by destination country and supply category, for jurisdictions that tax some supplies at a reduced rate: an array with a valid_from date and a rates map of country => category => basis points (1900 = 19%). Null means the bundled standard-rate table answers alone, exactly as before. A country in the matrix with no reduced band charges its standard rate — an absence there means "not granted here", which is an answer — while a country the matrix omits is still priced by the bundled table, so a partial matrix is a smaller table rather than a smaller world. Any audio or video part of a supply closes the reduced band for the whole of it, with no majority test and no threshold; splitting the product is how a reduced band is reached. A malformed value is refused at build rather than ignored, because a wrong rate looks exactly like a right one on an invoice. Country codes may be written in either case; a band that is not a rate category, a rate that is not a whole number and two spellings of one country count as malformed. A sibling of billing.tax, never a child — nesting under that scalar would turn it into an array and silently produce 0% tax on every invoice. An optional max_age_days entry inside it (default 180) sets how old the table may be before billing:doctor calls it out and exits non-zero — a diagnostic rather than a boot guard, because refusing to boot over an aged table takes an application down for something still mostly right. Leave this null and an active billing.tax_profile supplies its own country's rates, so an operator in a shipped jurisdiction does not hand-type them — a hand-typed rate looks exactly like a right one on an invoice. A value here wins over the profile, because an operator who priced their own table has a reason the package cannot know. It may also carry billing.tax_matrix.max_age_days (default 180) — how many days old the table may get before billing:doctor reports it. That sub-key is not settable on its own: it lives inside the matrix, because how long a table may sit depends on the table, and setting it alone would turn tax_matrix from null into an array and change which rates answer. An optional history entry holds the same rates as dated intervals — a list of valid_from / valid_to / source / source_version / fetched_at / rates, plus an optional approved_by — so a document written today about a supply taxed earlier can be priced at the rate that applied then, which is what the law binds it to. Absent, nothing changes and a tax point is simply ignored. Present, a country the history carries is answered from it and a tax point falling in a gap is refused rather than answered with the nearest rate, because an invented rate with a date on it cannot be told apart from a real one; a country the history omits keeps being priced by the table above. Intervals are append-only and an overlapping pair is refused, since one supply cannot have two rates.
billing.tax_oss—the three keys belowWhether the small-turnover threshold for cross-border consumer sales applies to you. A sibling of billing.tax, never a child — that key is a scalar, and an array there resolves to no tax mode at all.
billing.tax_oss.threshold_waivedBILLING_TAX_OSS_THRESHOLD_WAIVEDtrueThe default does not mean the package declared anything on your behalf: it means no origin-country fallback is applied, which is the direction that never under-charges. Charging your own rate where the buyer's was owed under-declares in a country nobody is registered in and surfaces as an assessment years later. Turn it off and bind a CrossBorderSalesCounter to have the threshold watched instead. Watched, a cross-border sale to a consumer that the package bills itself is taxed at the seller's rate while the year stays below the profile's limit, and the sale that takes it past the limit is taxed at the destination; the document keeps the customer's country, so the sale counts toward the limit.
billing.tax_oss.waived_sinceBILLING_TAX_OSS_WAIVED_SINCEnullWhen you actually declared it. Withdrawing the declaration inside its binding period is refused at boot — it would contradict a filing the revenue office holds, and silently reverting to origin taxation would under-declare in every destination country until somebody noticed.
billing.tax_oss.warning_levels—[0.80, 0.95]Fractions of the threshold at which you want to hear about it, so a registration does not begin on the day it is already needed.
billing.tax_profileBILLING_TAX_PROFILEnullWhich country's obligations the package enforces where it can — today, the points a jurisdiction adds to the marketplace go-live checklist. Null means none, and the checklist says so rather than reading as an all-clear. Shipped: de. A jurisdiction the package does not ship is supplied by binding your own JurisdictionProfile, which wins over this name; a name that is neither shipped nor bound is refused at boot.
billing.tax_small_business—the keys belowThresholds a jurisdiction profile with a small-business rule reads. Inert without such a profile.
billing.tax_small_business.warning_levels—[0.80, 0.95]How close to a threshold triggers a forced re-declaration. The percentages are configurable; the basis they are computed on is not — the same percentages against a different basis fire at a completely different moment, which would look like a configuration choice and behave like a different rule.
billing.tax_small_business.eu_revalidate_after_daysBILLING_TAX_EU_REVALIDATE_AFTER_DAYS365How long a confirmed small-business registration stands before it is checked again. A registration confirmed once is not confirmed forever — registers change, and a standing resting on a two-year-old lookup rests on nothing. Once it lapses the merchant is held exactly as if nobody had ever checked.
billing.tax_small_business.reattestation.grace_daysBILLING_TAX_REATTEST_GRACE_DAYS30How long somebody has to answer an obligation that arrives on the first day of the year. Added to the boundary, never replacing it.
billing.tax_small_business.reattestation.remind_daysBILLING_TAX_REATTEST_REMIND_DAYS14How many days before an attestation runs out its creator is reminded, on top of the notice when the renewal falls due at the year boundary.
billing.tax_small_business.previous_year_limitBILLING_TAX_SB_PREVIOUS_YEAR_LIMIT2500000Prior-year turnover ceiling, in minor units (the German §19 figure: €25,000). A count over it last year removes the exemption for the whole of this year, effective January 1.
billing.tax_small_business.current_year_limitBILLING_TAX_SB_CURRENT_YEAR_LIMIT10000000Running-year turnover ceiling, in minor units (€100,000). The transaction that breaks it ends the exemption from that transaction's own moment — not the day's start, not the year's.
billing.tax_small_business.founding_year_limitBILLING_TAX_SB_FOUNDING_YEAR_LIMIT2500000Turnover ceiling that applies in the founding year, in minor units (€25,000). A business in its first year has no prior year, so the current-year limit is replaced by this immediate one.
billing.tax_small_business.auto_flip_enabledBILLING_TAX_SB_AUTO_FLIPtrueWhether a broken limit automatically flips the creator from small-business to standard rating. The flip is one-directional by design — a count under the limit never flips back, because the platform sees only a lower bound of the real turnover. Turning this off leaves the flip to an operator.
billing.company.nameBILLING_COMPANY_NAMEnullThe seller party on an e-invoice.
billing.company.vat_idBILLING_COMPANY_VAT_IDnullYour VAT identification number. Also the last fallback for the electronic address below.
billing.company.addressBILLING_COMPANY_ADDRESSnullStreet address of the seller party.
billing.company.postcodeBILLING_COMPANY_POSTCODEnullPostal code of the seller party.
billing.company.cityBILLING_COMPANY_CITYnullCity of the seller party.
billing.company.countryBILLING_COMPANY_COUNTRYDETwo-letter country code of the seller party.
billing.company.endpoint_idBILLING_COMPANY_ENDPOINT_IDnullThe seller electronic address (EN 16931 BT-34). XRechnung makes it mandatory, so an endpoint must always resolve: set it, or the renderer falls back to a company email, then to vat_id.
billing.company.endpoint_schemeBILLING_COMPANY_ENDPOINT_SCHEMEEMThe scheme code for that address (EM is email).
billing.company.contact_nameBILLING_COMPANY_CONTACT_NAMEnullThe seller contact point (BT-41), such as a department. XRechnung requires it on every document, with the two below.
billing.company.contact_phoneBILLING_COMPANY_CONTACT_PHONEnullThe seller contact's telephone number (BT-42).
billing.company.contact_emailBILLING_COMPANY_CONTACT_EMAILnullThe seller contact's email address (BT-43).
billing.company.ibanBILLING_COMPANY_IBANnullThe account a document asks to be paid on by SEPA credit transfer (BT-84). XRechnung requires payment instructions, and they are written only when this is set.
billing.company.bicBILLING_COMPANY_BICnullThe bank of that account (BT-86).

Tax and the invoice surface: Taxes · Invoices and e-invoicing.

SEPA direct debit​

KeyEnvDefaultWhat it does
billing.direct_debit.notice_daysBILLING_DIRECT_DEBIT_NOTICE_DAYS14How many calendar days before a direct debit the payer is told its amount and its day. The SEPA core rulebook sets 14 where nothing else is agreed. The mandate text this package shows agrees the number set here, and the consent keeps it, so a mandate given on your form is announced by the period it was given with. A mandate a first payment established is announced by this number too: iDEAL, Bancontact and the other bank methods are collected as direct debits afterwards, and a shorter period has to be agreed in your terms for them. A value that is not a whole number of at least 1 reads as 14.
billing.direct_debit.mandate_wordings—AT, DE, LI and LU read the German wordingThe mandate text for payers in each country, keyed by the ISO country code. The rulebook asks for a language of the payer's country, and a country without an entry gets no mandate on your form. Another language is a class of your own implementing SepaMandateWording.

The mandates, the text and the notices: Subscriptions.

Data protection and retention​

KeyEnvDefaultWhat it does
billing.audit.levelBILLING_AUDIT_LEVELmoneymoney records every money movement and entitlement or state change — the events an auditor, or a "why is this customer on free?" question, needs. all adds the high-volume navigational and read-side events.
billing.erasure.forget_customerBILLING_ERASURE_FORGET_CUSTOMERfalseWhether billing:erase also deletes the customer at the provider. Irreversible, and it cancels their live subscriptions there — off by default.
billing.erasure.merchant_subscriptionsBILLING_ERASURE_MERCHANT_SUBSCRIPTIONSnowHow the subscriptions fans hold with a merchant whose account is deleted end: now ends them immediately, period_end lets each run to the end of the period the fan paid for and renew no more. Any other value reads as now.
billing.retention.webhook_payload_daysBILLING_RETENTION_WEBHOOK_PAYLOAD_DAYS90How long a stored webhook payload is kept. Long past the provider's own redelivery window, which is the only reason it is kept at all.
billing.retention.erased_financial_daysBILLING_RETENTION_ERASED_FINANCIAL_DAYS2920The invoice window for an erased owner's retained invoices: eight years, counted from the end of the year of issue. A shorter value refuses to boot. A floor and a default — set your own for another jurisdiction.
billing.retention.audit_daysBILLING_RETENTION_AUDIT_DAYS3650The book window for the audit ledger: ten years. Deliberately longer than the invoice window above — two record classes, not a value that drifted. Do not unify them. The produced tax returns and seller-reporting records keep this window as well, counted from the end of the year a record was produced in. The filings of a reporting period keep it too, and go together once the youngest of them has had it.
billing.retention.allow_below_statutory_minimumBILLING_RETENTION_ALLOW_BELOW_STATUTORY_MINIMUMfalseThe escape hatch for a jurisdiction whose invoice minimum genuinely is shorter than the floor above. Left false, a shorter erased_financial_days refuses to boot rather than prune tax records early.

Erasure keeps invoices on purpose: a valid invoice has to carry the buyer's name and address and has to be kept for years, so the right to erasure yields to the retention obligation. Those rows are unlinked from the owner and removed by billing:prune once the window closes. See Data protection and Retention and erasure.

DATEV export​

KeyEnvDefaultWhat it does
billing.datev.consultantBILLING_DATEV_CONSULTANTnullThe consultant number in the EXTF header.
billing.datev.clientBILLING_DATEV_CLIENTnullThe client number in the EXTF header.
billing.datev.account_lengthBILLING_DATEV_ACCOUNT_LENGTH4The account-number length your chart uses.
billing.datev.business_year_startBILLING_DATEV_BUSINESS_YEAR_START01-01The first day of your business year, as MM-DD. The batch header names the start of the business year the batch falls in, so a business year that begins on 1 July is 07-01. Anything that is not a day of the year stops the export with the key named.
billing.datev.revenue_accountBILLING_DATEV_REVENUE_ACCOUNTnullThe revenue account every invoice books against when no chart is selected. Left empty, the export still produces a structurally valid file with blank account fields.
billing.datev.customer_accountBILLING_DATEV_CUSTOMER_ACCOUNTnullThe receivables account every invoice books to when no chart is selected.
billing.datev.chartBILLING_DATEV_CHARTnullskr03, skr04 or null. Null uses the two accounts above and the export is byte-identical. Selecting a chart changes only the values resolved per transaction, never the file's structure or field order.
billing.tax_oss.correction_window_yearsBILLING_TAX_OSS_CORRECTION_WINDOW_YEARS3How long a filed period may still be corrected, measured from its due date — a month later than the period end, and measuring from the wrong one lets through a correction that is already out of time. Past the window the export refuses rather than dropping the line.
billing.reporting.goods_de_minimis.max_salesBILLING_REPORTING_MAX_GOODS_SALES30Where a small-scale exemption for sales of goods stops on the count. Both edges must hold at once — one alone exempts nobody.
billing.reporting.goods_de_minimis.sales_operatorBILLING_REPORTING_SALES_OPERATOR<How the count is compared.
billing.reporting.goods_de_minimis.max_compensation_minorBILLING_REPORTING_MAX_GOODS_COMPENSATION_MINOR200000Where it stops on the amount.
billing.reporting.goods_de_minimis.compensation_operatorBILLING_REPORTING_COMPENSATION_OPERATOR<How the amount is compared — strict by default, as § 4 Abs. 5 PStTG words it: less than the figure. The directive reads "did not exceed", which would also exempt the seller sitting exactly on the figure; set <= only if your statute follows that wording.
billing.e_invoice.alwaysBILLING_E_INVOICE_ALWAYSnullWhether documents are issued electronically. Null asks the active jurisdiction profile — unset is not the same as false, because reading a consumer's silence as "no" would mean an operator whose jurisdiction requires it silently does not comply.
billing.invoices.pdf_diskBILLING_INVOICE_PDF_DISKnullThe disk your billing_invoices.pdf_path values address. Name it and the download route serves the PDF you kept instead of rendering a new one, under every driver — which matters because a re-render years later resembles the document your customer holds without being it. Under a local-engine driver, the invoice list offers the download of a kept file even without a PDF renderer. Left null, no disk is ever touched and the route behaves exactly as before. A recorded path whose file is gone falls back to a fresh render where a PDF renderer is bound, so an owner is not locked out of their own invoice, and it is logged as an error: a lost archive file is an incident, and the quiet version of it is the expensive one.
billing.invoices.number_prefixBILLING_INVOICE_NUMBER_PREFIXINVThe prefix on an invoice number the package mints itself: PREFIX-YYYY-0000001. A local engine's invoices carry it, and so does every receipt from the counter. The running part restarts each year, which is what keeps a number readable. A provider-driven driver's own invoices never reach this: they copy the number the provider already issued. Gaps in the sequence are harmless; a number issued twice is not, so the sequence locks rather than counting rows.
billing.card_present.small_amount_threshold_minorBILLING_CARD_PRESENT_SMALL_AMOUNT_MINOR25000The gross, in minor units, up to and including which a sale paid by card at the counter gets a simplified invoice (€250.00, the German § 33 UStDV figure). Above it the buyer is still anonymous, so the document is a payment record. A seller whose counter stands in a country with another limit sets that country's figure.
billing.tax_us.enabledBILLING_TAX_US_ENABLEDfalseWhether anything acts on the tax declarations sellers give (withholding, reporting). The declarations are collected either way — asked for at onboarding or chased a year later from sellers who have moved, under a filing deadline, which ends in withholding money from people who did nothing wrong. The identifying number itself is never stored.
billing.retention.place_evidence_daysBILLING_RETENTION_PLACE_EVIDENCE_DAYS3650How long the evidence for a sale's country is kept, counted from the end of the year of the sale. Deliberately longer than the document window and its own key — merging them would prune the evidence before the return it justifies stops being examinable. billing:prune keeps an erased person's evidence for this window, and never for less than erased_financial_days.
billing.tax_oss.binding_yearsBILLING_TAX_OSS_BINDING_YEARS2How many years a declaration that gave up the distance-sale threshold binds for. Checked at boot: withdrawing it early fails loudly rather than falling back to the seller's own rate on supplies that owe the destination's.
billing.tax_oss.export_diskBILLING_TAX_OSS_EXPORT_DISKnullFilesystem disk a produced tax-return file, and the recapitulative statement beside it, is copied to. Null writes nowhere — what was produced is recorded either way. The package files nothing with any authority and holds no portal credentials.
billing.tax_oss.export_pathBILLING_TAX_OSS_EXPORT_PATHtax-returnsDirectory on that disk, for both declarations.
billing.tax_us.activation_share_bpsBILLING_TAX_US_ACTIVATION_SHARE_BPS5000How close to a region's limit counts as approaching it, in basis points of that limit. Waiting for a limit to be crossed is waiting too long — registration takes weeks while the obligation starts at the crossing.
billing.tax_rate_probe.enabledBILLING_RATE_PROBEfalseWhether billing:rates:probe may reach the public source that publishes VAT rates. Off by default: a package should not contact a public service because it happened to be installed. Turn it on for a nightly job, never for the push gate — a network-dependent check there goes red on the first DNS hiccup and gets disabled, which is how an arm quietly disappears.
billing.tax_rate_probe.proposal_pathBILLING_RATE_PROPOSAL_PATHnullWhere billing:tax-rates:check writes its proposal. Null means beside the shipped snapshot, so the two share a dating convention and can be diffed side by side. Point it somewhere writable when the installed package is read-only, or when the next release would take the file with it — a proposal exists to be reviewed by a person, which can be days later. The snapshot itself is never written to either way: this package proposes rate changes and does not apply them.
billing.tax_exchange_rates.enabledBILLING_EXCHANGE_RATESfalseWhether exchange rates are read from the local billing_exchange_rates table. Off by default, and off means a refusal rather than a silence: every conversion is answered by saying the package ships none and what to do about it. It ships none because which rate is correct is jurisdiction knowledge and the rules contradict each other — the ministry's monthly average against the central bank's rate at the tax point against its rate at period end, on the same turnover. Turn it on and the numbers on your documents are the ones you imported, from a publisher you can name. A single-currency install never converts and never reaches any of this.
billing.marketplace.tax_status_hold.enforce_fromBILLING_MARKETPLACE_HOLD_ENFORCE_FROMnullThe day the creator tax-status hold starts refusing sales for merchants whose taxation nobody has established. Null until you choose one, and that is not the two switches beside it being ignored — they say what is held, this says from when. It needs a date because its default refuses everybody: a merchant nobody has declared for is Unclarified, which is exactly the standing that blocks, so switching it on with today's date stops every creator who has not yet declared. billing:marketplace:preflight reports an unset date as outstanding. A value that is not a date is refused outright rather than guessed at, and a jurisdiction profile that requires the hold overrides this.
billing.marketplace.tax_status_hold.warn_days_beforeBILLING_MARKETPLACE_HOLD_WARN_DAYS_BEFORE30How many days before the date above the merchants who have not declared are told it is coming. The line beside enforce_from says to tell them; this says when. Too early is forgotten by the time it matters, and too late is not a warning — producing a declaration takes longer than a checkout takes to fail. Nobody is warned while enforce_from is unset: with no deadline there is nothing to warn about, and inventing one to have something to say is worse than silence. Sent once per deadline, and again if you move the date, because a merchant told about March has not been told about June.
billing.reporting.filing_notice_daysBILLING_FILING_NOTICE_DAYS14How many days before a filing obligation falls due it is announced. Long enough to assemble the figures, short enough that the notice is still about something imminent — a reminder six weeks out is filed away and not seen again. Each obligation is announced once for its date, and the two that share the end-of-January deadline are announced separately: different law, different data, and whoever handles the one they thought of must not be able to consider the day dealt with.
billing.tax_exchange_rates.currencies—[]Which currencies billing:exchange-rates:import fetches, as the bank names them. Empty by default, so the switch above alone still contacts nobody: holding rates locally and knowing which currencies you settle in are two decisions, and a package that inferred the second would dial out for currencies nobody sells in. Rates are stored in the direction the bank publishes them — euro to each of these — and never turned around, so list the currencies you receive money in.
billing.tax_exchange_rates.max_age_days—3How many days old the newest imported rate may be before billing:doctor calls the series out. Three, deliberately well under the fourteen at which the lookup gives up and a document cannot be issued: three days is two missed daily imports plus a weekend, which is a warning, while fourteen is the incident already happening. The forward window stays the ceiling — a value above it would let the diagnostic report green while the money path is refusing documents, so the lower of the two applies. The check is silent when the store is switched off or no currency is listed.
billing.tax_exchange_rates.lookback_daysBILLING_EXCHANGE_RATES_LOOKBACK_DAYS10How far back a scheduled import re-fetches. More than a day on purpose: a publisher revises, a run is missed, a machine sleeps through a night. Importing is idempotent, so an overlap costs a few rows rewritten with the same figures — while a one-day window turns any missed run into a permanent hole in the series, and the reader answers a hole with the next publication day's rate.
billing.tax_point_on_receiptBILLING_TAX_POINT_ON_RECEIPTfalseWhether a prepayment is taxed when the money arrives rather than as the service is rendered. On a prepaid year the two are eleven months apart. Off by default — a silent change of tax period is the last thing an upgrade should do.
billing.datev.person_accounts.modeBILLING_DATEV_PERSON_ACCOUNTScollectiveHow merchant payables appear in the books. collective books every merchant against the one liabilities account and keeps the per-merchant detail in this package's sub-ledger — workable at any number of merchants. individual gives each merchant their own account, for an accountant who expects open items per creditor. The booking logic is identical; only the account the payable resolves to changes. Switching an install that has already booked is a documented migration, not a flag flip.
billing.datev.person_accounts.range_startBILLING_DATEV_CREDITOR_RANGE_START70000Where individual creditor numbers begin. A number is allocated on first use and kept — one that moved would leave earlier bookings pointing at an account that now means somebody else.
billing.datev.accounts—an SKR03 and an SKR04 mapThe per-transaction account map for each chart. Each entry is account plus automatic; an automatic account derives its VAT from the posting itself, so a tax key is never set alongside it. A reverse-charge entry may add reverse_charge_transaction_key, the three-digit key that says under which circumstance the reverse charge is assessed — confirm it with your accountant the same as the account number, and note that zero is not a permitted value. Configure none and the export keeps the fields it always wrote. The numbers are German-accountant defaults, not values the package invents — override them for a different frame without a code change.

These are specific to your chart of accounts and your tax advisor's setup. Confirm them before importing. See Accounting and DATEV.

Multi-merchant keys​

The package ships the single-merchant path. These keys exist because two guarantees have to be enforceable before a routed sale could ever exist, and both are enforced today: a posture cannot be resolved outside the list you opted into, and the platform cannot start holding other people's money by flipping a flag.

KeyEnvDefaultWhat it does
billing.content_ownership.enabledBILLING_CONTENT_OWNERSHIP_ENABLEDfalseTurns on the content-ownership register — what a buyer owns, as opposed to what their plan lets them do. The licensing side (config/license.php, reached through the License contract) answers "what may this owner do right now", and its answer changes the moment their tier does; this answers "what did this person buy, and is it still theirs", which outlives the plan, the creator's account and the work's own publication. Off, the read seam resolves to a reader that answers no to everything -- an answer rather than a resolution error, so "off" and "miswired" stay distinguishable at the call site. The three effects that would otherwise write or withdraw a grant (a completed purchase, a refund, a chargeback) read this key first and return before touching anything, so off is fail-closed on both sides rather than only on the read.
billing.content_ownership.default_update_policyBILLING_DEFAULT_UPDATE_POLICYlatestWhat a sale promises about later versions when neither the work nor the merchant says otherwise: latest, latest_with_revisions, windowed, or frozen. This is the enrichment axis -- new editions, added material -- and it is the creator's to choose. It is not the conformity axis: a defect fix or a security patch is owed regardless of what stands here, which is why no value switches those off. A misspelt value is refused at first use rather than defaulted, because falling back to latest would promise free updates forever on every sale the install ever makes. windowed needs a length, and the length comes from your catalog, not from here: implement SuppliesUpdateWindows beside your UpdatePolicyCatalog and answer windowForContent() / windowForMerchant() with a CarbonInterval. "Updates for twelve months" is a term of sale a creator sets per work, so a configured global would put a term on every sale nobody agreed to. Answer nothing and a windowed grant is written with no window, which the resolver reads fail-closed as the moment of purchase — the same result frozen gives.
billing.content_ownership.bundle_additive_defaultBILLING_BUNDLE_ADDITIVE_DEFAULTfalseWhether a work added to a bundle later reaches people who bought the bundle earlier. Off, because a bundle is normally what it was on the day it was bought -- and off costs nothing to enforce, since bundle grants are materialized at purchase and a work added next month simply has no row for an earlier buyer. On, a repeat grantBundle() call for the same buyer tops up what has since been added.
billing.content_ownership.revoke_on_refundBILLING_REVOKE_ON_REFUNDtrueWhether a refund also ends access to the work. Genuinely switchable, because both answers are somebody's deliberate policy: leaving access in place after a goodwill refund is common and often the point -- the work has already been read, so taking it back costs nothing to skip and turns a recovered customer into an angry one. Ending it is right where the refund was a return rather than a gesture.
billing.content_ownership.revoke_on_chargebackBILLING_REVOKE_ON_CHARGEBACKtrueThe same question for a lost dispute, and a different situation: a chargeback is involuntary, decided by somebody else, and the money is already gone. There is no version of it where the platform chose to give the work away, which is why the shipped answer is to end access. This switch also decides where a driver reports the lost dispute as a refund of the purchase, and the revocation then records chargeback as its reason.
billing.content_ownership.restore_on_chargeback_reversalBILLING_RESTORE_ON_CHARGEBACK_REVERSALtrueWhether access a chargeback ended comes back when the bank takes the chargeback back. On, because the customer has paid after all. Only a grant the chargeback revoked returns: one that ended for another reason stays revoked, and the revocation stays on the row beside reinstated_at. Only Mollie reports a reversal, from billing:money-back:reconcile
billing.consumer_rights.conformity_update_period_daysBILLING_CONFORMITY_UPDATE_PERIOD_DAYSnullHow long a seller owes conformity updates on a sale -- defect fixes, security fixes, staying compatible. A different axis from content_ownership.default_update_policy, which governs what the creator sells and may withhold; this governs what is owed afterwards regardless, so a frozen sale still receives them. Ships empty, and empty is not an oversight: the obligation runs for as long as a buyer may reasonably expect, that is a judgment about a kind of product, and no statute states a number -- a package shipping one would be inventing a legal answer and hiding it in a library. Empty means no end has been established and updates keep flowing.
billing.consumer_rights.allow_conformity_waiverBILLING_ALLOW_CONFORMITY_WAIVERfalseWhether a buyer can validly agree to give that obligation up at all in your jurisdiction. It switches nothing off: there is deliberately no install-wide off switch for conformity updates, because that is exactly the blanket arrangement the law refuses to recognize. It only makes a waiver possible one grant at a time, and recording one still needs a reference to an actual agreement made separately and before the contract -- a flag in a file cannot produce a declaration. Off, because whether security fixes can be waived at all is genuinely disputed; turning it on is your decision on your own advice.
billing.marketplace.enabledBILLING_MARKETPLACE_ENABLEDfalseOff, the custody guard below has nothing to check and single-merchant behavior is byte-identical.
billing.marketplace.custody.platform_heldBILLING_MARKETPLACE_PLATFORM_HELDfalseWhether the platform itself would hold other people's funds. Holding funds is a regulated activity in most jurisdictions, so this is refused at boot unless the host binds a PaymentServiceLicenseAttestation. A flag alone can never make an unaware consumer an unlicensed money holder. There is deliberately no interest or yield option.
billing.marketplace.seller_of_record.default_postureBILLING_MARKETPLACE_POSTUREplatform_deemed_supplierWho the seller is to the buyer. A liability and VAT decision the package enforces but never makes for you. It does not decide where a merchant-defined tier's price is minted: that is the platform account under every posture, because the checkout runs there for both charge types.
billing.marketplace.seller_of_record.allowed_postures—['platform_deemed_supplier']The postures you have deliberately opted into. Resolving one outside this list is refused.
billing.marketplace.seller_of_record.supplies_are_electronicBILLING_MARKETPLACE_SUPPLIES_ELECTRONICtrueDefault classification of what is sold. An electronically supplied service falls under the deemed-supplier presumption; physical goods do not.
billing.marketplace.seller_of_record.art9a_rebuttal_assertedBILLING_MARKETPLACE_ART9A_REBUTTALfalseThe rebuttal assertion. It and the three keys below must all be true before a seller-of-record posture is allowed for an electronic supply.
billing.marketplace.seller_of_record.no_agb_control—falseAssert that you do not set the terms of the supply.
billing.marketplace.seller_of_record.no_billing_authorization—falseAssert that you do not authorize the charge to the buyer.
billing.marketplace.seller_of_record.no_supply_authorization—falseAssert that you do not authorize the delivery of the supply.
billing.marketplace.deemed_supply_of_goods—the keys belowGoods whose sale makes the platform their supplier: an imported consignment worth up to 150 euros, whoever sells it, and goods in the Union sold by a seller established outside it to somebody who is not a business. A routed sale of goods is classified from the consignment its lane passes. Under a tax profile that documents such a sale it goes ahead as the platform's own supply; under any other it is refused. See the marketplace guide.
billing.marketplace.deemed_supply_of_goods.ioss_numberBILLING_MARKETPLACE_IOSS_NUMBERnullThe number the platform is registered under in the import scheme, IM followed by ten digits. An imported consignment the platform supplies is refused without a valid one.
billing.marketplace.deemed_supply_of_goods.union_schemeBILLING_MARKETPLACE_DEEMED_SUPPLY_UNION_SCHEMEfalseYour statement that the platform declares its supplies of goods already in the Union in the Union scheme. The receipt the package issues for such a supply is enough only there, so a deemed supply under Art. 14a(2) is refused until it is true.
billing.marketplace.seller_activity—the two keys belowWhen a seller is active enough to be asked to declare their standing.
billing.marketplace.seller_activity.sales_thresholdBILLING_MARKETPLACE_SALES_THRESHOLD30Sales in the period that count as trading.
billing.marketplace.seller_activity.proceeds_threshold_minorBILLING_MARKETPLACE_PROCEEDS_THRESHOLD_MINOR200000Proceeds in the period that count as trading, in minor units. Two thresholds read these numbers and they are not the same rule: the declaration is asked for once either measure is reached, the reporting exemption holds only while both stay below. At exactly the figure they agree, because the German statute exempts only less than it; they stay separate rules all the same, because the exemption's boundary is set by law and the declaration's by you.
billing.marketplace.seller_activity.sales_comparisonBILLING_MARKETPLACE_SALES_COMPARISON>=How the sales count is compared with its threshold: >= (reaching it is enough) or >.
billing.marketplace.seller_activity.proceeds_comparisonBILLING_MARKETPLACE_PROCEEDS_COMPARISON>=The same for the proceeds.
billing.marketplace.seller_activity.windowBILLING_MARKETPLACE_ACTIVITY_WINDOWcalendar_yearWhat the two figures are counted over: calendar_year, or rolling_year for the 365 days up to now.
billing.marketplace.seller_data_escalation—the keys belowWhat happens when a seller does not supply the data a reporting duty needs.
billing.marketplace.seller_data_escalation.first_reminder_after_daysBILLING_MARKETPLACE_FIRST_REMINDER_DAYS7Days incomplete before the first reminder.
billing.marketplace.seller_data_escalation.second_reminder_after_daysBILLING_MARKETPLACE_SECOND_REMINDER_DAYS30Days before the second.
billing.marketplace.seller_data_escalation.measure_after_daysBILLING_MARKETPLACE_MEASURE_AFTER_DAYS60Days before a measure applies — and only where the missing data is legally required of that seller.
billing.marketplace.seller_data_escalation.measureBILLING_MARKETPLACE_DATA_MEASUREwithhold_payoutsuspend_sales or withhold_payout. Two very different impositions, and neither is obviously the gentler one: stopping somebody selling ends their income, holding their money leaves them selling and unpaid.
billing.marketplace.seller_data_escalation.measure_precautionary_gapsBILLING_MARKETPLACE_MEASURE_PRECAUTIONARYfalseWhether a measure also applies where only precautionary data is missing. Off, because extending a sanction to data no law requires is a contract question between a platform and its sellers.
billing.marketplace.seller_data_escalation.withhold_up_to_daysBILLING_MARKETPLACE_WITHHOLD_UP_TO_DAYS90How long a withholding may last.
billing.marketplace.seller_data_escalation.payout_deadline_daysBILLING_MARKETPLACE_PAYOUT_DEADLINE_DAYS90The money rail's own deadline, read here and defined by the payout schedule. A withholding is capped by it: "held until they cooperate" is open-ended and the rail is not, and a hold that outran it would not be stricter — it would be a payment nobody can complete.
billing.marketplace.seller_data_escalation.on_withholding_exhaustedBILLING_MARKETPLACE_ON_WITHHOLDING_EXHAUSTEDsuspend_salesWhat follows when a withholding has run as long as it may without the data arriving. The money moves either way, because the rail's limit is not negotiable: suspend_sales turns the withholding into a suspension, release ends it with the money.
billing.marketplace.seller_record—the key belowWhat a platform asks a seller for at onboarding.
billing.marketplace.tax_standing_routeBILLING_MARKETPLACE_TAX_STANDING_ROUTEnullThe route of your screen where a creator declares or renews their tax standing. The package has none; named here, the reminder that an attestation is due links to it, and without it the reminder carries no button.
billing.marketplace.seller_record_routeBILLING_MARKETPLACE_SELLER_RECORD_ROUTEnullThe route of your screen where a seller completes their record. The record lives in your application, so the screen does too; named here, a reminder about missing seller details links to it, and without it the reminder carries no button.
billing.marketplace.seller_record.collect_precautionaryBILLING_MARKETPLACE_COLLECT_PRECAUTIONARYtrueCollect the fields a reporting duty adds from every seller, not only those the duty currently covers. A seller's classification changes the day they take on different work, and a platform that only asked the sellers it already knew about then has to chase the rest — after the year closed, under a deadline, from people who have gone quiet. Switch it off and those fields are asked for only once a duty applies; the fields needed to settle at all are never affected either way.
billing.marketplace.tax_status_hold—the two keys belowWhat happens while a merchant's tax standing is unestablished.
billing.marketplace.tax_status_hold.blocks_payoutsBILLING_MARKETPLACE_HOLD_BLOCKS_PAYOUTStrueHold the merchant's own earnings until their standing is recorded. Inert under a jurisdiction profile that requires the hold: turning it off there would amount to choosing a default standing for people whose standing nobody knows.
billing.marketplace.tax_status_hold.blocks_salesBILLING_MARKETPLACE_HOLD_BLOCKS_SALEStrueStop selling on their behalf too. Holding only the payout is the more dangerous half-fix: buyers' money keeps arriving and each transaction creates an obligation to settle that cannot be settled — a growing backlog instead of a stop. Equally inert under such a profile.
billing.marketplace.regime—the two keys belowWhich shape a routed sale has. The platform reselling in its own name, or the platform arranging somebody else's sale — they produce different documents, different turnover, and different parties on a receipt.
billing.marketplace.regime.defaultBILLING_MARKETPLACE_REGIMEcommission_chainThe shape a sale has when what is sold does not decide for itself. commission_chain or intermediation. An unreadable or unknown value is refused rather than defaulted: silently choosing here would pick which documents every sale produces, on the strength of a typo.
billing.marketplace.regime.allowed—['commission_chain']The regimes you have deliberately opted into. Resolving one outside this list is refused — a platform that has not said it arranges other people's sales must not begin to because a product was classified in a way nobody looked at.
billing.marketplace.numbering—the key belowPrefixes for the document series the platform numbers itself.
billing.marketplace.numbering.series—the eight keys belowOne visible prefix per document role. The number format is PREFIX-YYYY-####### — the prefix, the four-digit year, and a seven-digit running number that restarts at 1 each year within the series. A role with no configured prefix is refused at allocation rather than numbered with a blank, because a malformed number issued is itself a numbered event that would then need a correction. These are the German defaults; a consumer elsewhere maps the same roles to their own letters.
billing.marketplace.numbering.series.buyer_receipt—FPrefix for the buyer's receipt.
billing.marketplace.numbering.series.self_billed_invoice—GPrefix for a self-billed invoice to a merchant.
billing.marketplace.numbering.series.settlement_note—APrefix for a settlement note to a private party.
billing.marketplace.numbering.series.commission_invoice—PPrefix for a commission invoice — drawn only in an intermediation sale, never in a commission chain.
billing.marketplace.numbering.series.buyer_receipt_correction—KFPrefix for a correction to a buyer receipt.
billing.marketplace.numbering.series.self_billed_invoice_correction—KGPrefix for a correction to a self-billed invoice.
billing.marketplace.numbering.series.settlement_note_correction—KAPrefix for a correction to a settlement note.
billing.marketplace.numbering.series.commission_invoice_correction—KPPrefix for a correction to a commission invoice.
billing.marketplace.self_billing—the keys belowWhether the platform may settle a creator by self-billing, and on what precondition.
billing.marketplace.self_billing.enabledBILLING_MARKETPLACE_SELF_BILLING_ENABLEDtrueWhether the platform settles creators by self-billing at all. On by default. A consumer that does not self-bill turns this off and stays in the fallback lane — the creator submits their own invoice. It is a backstop, not the routing decision: check it before settling, and the engine refuses loudly if it is reached while off rather than issue a document a disabled platform never meant to.
billing.marketplace.receipts—the key belowWhat the buyer's receipt collects, and when.
billing.marketplace.receipts.small_amount_threshold_minorBILLING_MARKETPLACE_RECEIPT_SMALL_AMOUNT_MINOR25000The gross, in minor units, up to and including which a domestic purchase gets the simplified receipt (€250.00, the German § 33 UStDV figure). A larger or cross-border purchase gets a plain payment record; only a buyer who asks for a full invoice has their name and address collected. A consumer sale carries no invoicing duty, so the least document is issued and the least data collected.
billing.marketplace.self_billing.require_agreementBILLING_MARKETPLACE_SELF_BILLING_AGREEMENTtrueWhether a self-billed document requires a prior agreement with the creator. On by default; it opts out only explicitly. A self-billed document is an invoice only if both sides agreed to the arrangement before it — one issued without that agreement is not an invoice and cannot be repaired. A jurisdiction that does not demand the agreement turns this off, but a missing or non-boolean value keeps the requirement, because the fail-safe is to require it. The clause text and the onboarding screen belong to you; the package ships only the record and the guard.
billing.marketplace.fallback.tolerance_minorBILLING_MARKETPLACE_FALLBACK_TOLERANCE_MINOR0How far, in minor units, a fallback-lane invoice's net or tax may deviate from what the creator earned that period before it is a review finding. The default is exact: the platform pays out what the creator earned, not what they wrote, and a mismatch holds the payout. A creator submits their own invoice through the fallback lane after an objection or a terminated self-billing agreement; the reconciliation is what keeps a €300.00 invoice against €270.00 earned from being paid.
billing.marketplace.fee.roundingBILLING_MARKETPLACE_FEE_ROUNDINGplatform_firstWhich side of an uneven percentage split keeps the leftover minor unit. platform_first gives it to the fee; creator_first gives it to the net, the only order that hits an exact target payout. At volume that assignment is real money, so it is a documented contract choice rather than an accident of rounding.
billing.marketplace.fee.refund_policyBILLING_MARKETPLACE_FEE_REFUND_POLICYrefundWhat happens to the platform's own commission when a sale is unwound. refund gives back the part of it that what remains of the sale no longer carries, recomputed on the remainder because a fixed part is not halved by a half refund — the only value under which a refund nets to zero across all three parties. On a destination charge the package gives that amount back itself, after the refund, rather than letting Stripe share out the fee it first withheld. retain keeps it, which leaves the merchant short by that amount. retain is refused under the commission_chain regime, at preflight rather than at the first refund: there the platform buys and resells, so unwinding the sale unwinds both supplies and no commission document remains for a retained fee to sit on.
billing.subscribe_return_urlBILLING_SUBSCRIBE_RETURN_URLnullWhere a provider without a synchronous setup call returns the customer after the first payment that establishes their mandate. Falls back to billing.checkout.success_url. Set neither and starting a subscription is refused rather than redirected to nowhere — the alternative is a customer who completes a real payment and lands on an error page holding a mandate nothing told them about.
billing.mandate_verification_minorBILLING_MANDATE_VERIFICATION_MINOR1What that first payment charges, in minor units of the plan's currency. It appears on the customer's statement, which is why it is yours to choose. The default is the smallest unit the currency has rather than the plan price, on purpose: this payment exists to create a mandate, and the first cycle is billed by the engine on its own schedule — collecting it here as well would charge the customer twice for one period.
billing.collection_timingBILLING_COLLECTION_TIMINGin_advanceWhen a driver the package bills itself collects each period of a new subscription. in_advance charges a period when it begins, the first one right after the mandate exists or when the trial ends, and squares a swap or a seat change inside a paid period at once: a dearer change is charged for the time left, a cheaper one leaves credit. in_arrears charges a period when it ends and prices a swap or a seat change into that bill. Each subscription keeps the timing it started with, so a change here reaches new subscriptions only, and one started before the setting existed is collected in arrears. Any other value refuses to boot.
billing.consumer_rights—the keys belowThe buyer's statutory withdrawal rights. This is consumer law, not tax law, and deliberately its own profile — a single seller needs it too, and an operator may run one country's VAT and another's consumer regime.
billing.consumer_rights.profileBILLING_CONSUMER_RIGHTS_PROFILEnullWhich consumer-rights reading is in force. Unset, empty and false all mean off: no extra checkout step, no changed receipt, byte-identical. de selects the German reading the package ships. A name of your own needs readings of your own bound for ConsumerWithdrawalPolicy and ConformityUpdatePolicy; any other value, off included, is refused with an InvalidBillingConfig at the first question the regime is asked, and billing:doctor reports it. Set it and the gate is fail-closed for every work that carries a classified archetype — such a work is not provided until the buyer's double consent is recorded, because providing it first forfeits their refund right on their behalf. The profile is one of two conditions, not the only one: at provision, a work nobody classified has no withdrawal type to gate on. That is caught one step earlier instead — PurchaseDeclarations::assertMayCheckout() refuses to start a checkout for an unclassified work while a profile is active, so the purchase never happens rather than the work being handed over unconsented. The shipped account hub calls it; a checkout of your own has to call it too, and until it does, the provision path still hands such a work over. A subscription checkout of your own calls its twin, assertMaySubscribe(), which reads a tier without an archetype as a subscription. Classify your works (archetype on the add-on, or your own SuppliesProductArchetypes), and run billing:doctor — it names any work that this profile does not actually cover.
billing.consumer_rights.declaration_timezoneBILLING_DECLARATION_TIMEZONEEurope/BerlinThe time zone a declaration through the cancellation button states the moment it arrived in, on the confirmation mail and on every saved copy. That moment decides whether the declaration was in time, and a receipt time that shifts with whoever reads it is not one time, so the zone is named beside it. Set the zone of the business that receives the declaration; a value that names no zone states the moment in UTC.
billing.consumer_rights.declaration_receipt_routeBILLING_DECLARATION_RECEIPT_ROUTEnullA route of your own that shows the receipt of a declaration, given the declaration's token as its declaration parameter. When it is set and registered, the confirmation mail links to it. The package draws no receipt page of its own.
billing.ai—the keys belowWhat model calls cost, and the daily caps on it. See the AI costs page.
billing.ai.currency—USDThe currency the rates and the caps are written in, where a rate does not name its own.
billing.ai.rates—[]The price list, keyed by model id and read by array access, because a model id carries dots. Each model holds one entry or a list of entries with a valid_from date, priced per million tokens as decimal strings: input and output, and cache_read, cache_write and reasoning where the provider prices them apart. A call is priced at the entry of its own day.
billing.ai.daily_caps—[]What one provider's calls may cost per UTC day, keyed by provider name, as decimal strings in billing.ai.currency. A provider without a cap is counted and never refused.
billing.ai.owner_daily_cap—nullWhat one owner's calls may cost per UTC day, as a decimal string in billing.ai.currency. Null counts and never refuses.
billing.tax_evidence—the key belowHow the buyer's country is established from the signals available while the sale happens.
billing.tax_evidence.required_signalsBILLING_TAX_EVIDENCE_SIGNALS2How many sources must name a country before a sale can rest on them. The legal answer depends on turnover — one piece of evidence below a threshold, two non-contradicting ones above it — so the package ships the stricter setting: too much evidence costs a checkout question, too little costs a defensible position. A value that cannot be read is refused rather than defaulted, because silently becoming 1 would relax the standard.
billing.tax_evidence.collect_subdivisionBILLING_TAX_EVIDENCE_SUBDIVISIONtrueWhether a sale's subdivision — a US state — is recorded beside its country. On, because it cannot be turned on retroactively: a US sales-tax nexus is measured per state over a rolling window, the evidence is written once at the sale, and the raw IP behind it is deliberately discarded — so a state not captured then is gone, and a counter built afterwards can only fill an unknown bucket while looking as though it worked. It records nothing you did not already supply: this package has no input finer than the country and does not go looking for one, so a sale whose signals carry no subdivision writes none. And it is narrow by construction — only for the countries in subdivision_countries, only from the sources that named that country, only when they agree, and only the ISO 3166-2 suffix (CA, never US-CA), never a postcode, a city or a coordinate. Off, the rest of the evidence is untouched and a state counter runs honestly on unknown rather than quietly on a guess.
billing.tax_evidence.subdivision_countries—['US']Which countries' subdivisions are worth recording at all. A list rather than „everywhere one exists", because almost every country has subdivisions and almost none of them decide anything this package is asked about. The US is here because its sales tax is administered per state and the registration duty follows a per-state threshold; adding a country should meet the same test — a question the subdivision actually answers.
billing.tax_markets—nullThe countries you are registered in and may sell into, as ISO code → open | planned | blocked. Codes may be written in either case; two spellings of one code are refused. Absent means no gate at all — a check defaulting to closed would stop every existing install at its next sale, which is an outage rather than a guard. Once configured it is fail-closed: anything not explicitly open is refused, including a country the evidence could not resolve. Refused BEFORE the payment where the country is known, because a sale into a country with no registration cannot be repaired by any later document: a checkout handed buyerCountry checks it before the session opens. Under Stripe the country the provider taxed a sale in is checked again once it is reported, and a sale into a market that is not open is undone, the subscription ended, the payment refunded and SaleIntoClosedMarketReversed dispatched. A market opened here that the local rates cannot price refuses the boot. Monaco is priced, placed and reported as France, as VAT law treats it, so MC may be opened like any member state. A sibling of billing.tax, never a child — nesting under that scalar would turn it into an array and silently produce 0% tax on every invoice.
billing.marketplace.vouchers.enabledBILLING_VOUCHERS_ENABLEDfalseWhether vouchers exist at all. Off by default: a balance customers pay into is a supervised question the moment it can be recharged, cashed out or handed on. This one can do none of those — there is no method for any of them — but it is still something to switch on knowingly.
billing.marketplace.vouchers.instrument_typeBILLING_VOUCHER_INSTRUMENT_TYPEmulti_purposeWhen the tax on a voucher falls. Frozen on each voucher at issue — a supply already made cannot be re-decided by a later change here. multi_purpose where you sell into many countries at many rates (nothing taxed at issue); single_purpose where one country and one rate make the supply determined already.
billing.marketplace.vouchers.expire_after_daysBILLING_VOUCHER_EXPIRE_AFTER_DAYS1095How long a voucher stays spendable. Whether a given period is enforceable against a consumer is a legal question this package does not answer — it makes the period configurable and records it.
billing.marketplace.vouchers.volume_window_monthsBILLING_VOUCHER_VOLUME_WINDOW_MONTHS12The rolling window the volume counter measures over.
billing.marketplace.vouchers.volume_threshold_minorBILLING_VOUCHER_VOLUME_THRESHOLD_MINOR100000000The figure past which a filing is expected where you operate. The counter reports it; filing anything is a decision a person makes. The figure counts two sources, so do not read "we issue no vouchers" as "the counter is moot": vouchers issued in the window, plus paid credit top-ups — an add-on its catalog classifies as money credit credits the balance at face value, which is the same instrument in another table, and each purchase counts by what it was when it was made. Refunds come off both. Currencies are kept apart rather than converted, because a rate this package picked would put a figure nobody published under a supervisory number.
billing.marketplace.vouchers.volume_warn_at_percentBILLING_VOUCHER_VOLUME_WARN_AT_PERCENT80Where the early warning sits. A threshold you learn about on the day you cross it leaves no time to act.
billing.marketplace.charge_typeBILLING_MARKETPLACE_CHARGE_TYPEseparate_transferWhich money flow you use with your provider. It has to agree with your seller-of-record posture, and the pairing is checked before a routed payment is assembled — not after one has been sent and only the transfer is left to fail.
billing.marketplace.buyer_protection—the five keys belowA payout that waits until the buyer confirms, or until their silence has lasted long enough to count as consent. The money is never held by your application — it stays with the payment provider and a release is an instruction to it.
billing.marketplace.buyer_protection.enabledBILLING_BUYER_PROTECTIONfalseWhether the merchant's share waits instead of moving the moment the payment succeeds. Off by default, and that default is the whole safety of the switch: with it off the payment path is byte-identical to what it always was, so no installation changes behavior on an upgrade. Turning it on decides when a seller is paid, which is not a call a package may make for an operator. It applies to the separate_transfer lane only — on a destination charge the provider moves the money as the payment settles, and there is no moment in between to hold.
billing.marketplace.buyer_protection.account_typeBILLING_BUYER_PROTECTION_ACCOUNT_TYPEexpressThe connected-account type. Must be one that lets a payout be held back; an account that pays out on the provider's own schedule is refused rather than run as protection that only appears to work.
billing.marketplace.buyer_protection.confirm_after_daysBILLING_BUYER_PROTECTION_CONFIRM_AFTER_DAYS14How long the buyer's silence takes to become consent. An open dispute stops this clock.
billing.marketplace.buyer_protection.decide_after_daysBILLING_BUYER_PROTECTION_DECIDE_AFTER_DAYS60The deadline nothing stops. On a disputed hold it does not decide the dispute — it marks the hold as needing a human.
billing.marketplace.buyer_protection.provider_limit_daysBILLING_BUYER_PROTECTION_PROVIDER_LIMIT_DAYS90How long your provider will delay a payout at all. Past it the money goes out whatever the settings say.
billing.marketplace.buyer_protection.margin_daysBILLING_BUYER_PROTECTION_MARGIN_DAYS20How much room to leave before that wall. A decision deadline that does not finish inside it is refused.
billing.marketplace.negative_balance—the two keys belowWhat happens when a merchant owes the platform money — a clawback the provider had nothing left to take.
billing.marketplace.negative_balance.offset_against_payoutsBILLING_MARKETPLACE_OFFSET_DEBTtrueWhether the next settlement pays the debt down before anything leaves. Offsetting withholds a payment; it is not a reduction of consideration, so the settlement document still states its full amount and no correcting document is due. Switching it off is a commercial choice — the debt then stands as a claim to pursue rather than quietly disappearing.
billing.marketplace.negative_balance.claim_after_daysBILLING_MARKETPLACE_CLAIM_AFTER_DAYS90How long a debt may sit untouched before it counts as a receivable to chase. The clock starts when the balance crosses into debt and is cleared when it is settled, so a second debt starts a second clock rather than inheriting the first one's age.
billing.marketplace.dispute_rate—the key belowThe line at which a seller's dispute rate needs a look.
billing.marketplace.dispute_rate.warn_atBILLING_MARKETPLACE_DISPUTE_RATE_WARN_AT0.01The warning line as a share of payments: 0.01 is one dispute per hundred payments. DisputeRates::reachesWarningThreshold() compares a rate with it. The default is Mastercard's line for monitoring a seller and sits below the 1.5% at which both networks call a seller excessive, because the package counts disputes and no fraud reports. A value that is not a share above zero and at most one, such as 1.5 meant as a percentage, is refused with an exception.
billing.marketplace.pricing—the key belowHow a price behaves across markets with different tax rates.
billing.marketplace.pricing.modeBILLING_MARKETPLACE_PRICING_MODEuniform_grossWhich quantity stays fixed when the rate changes — something has to move. uniform_gross keeps one buyer price everywhere and lets the payout absorb the difference; uniform_payout keeps the payout predictable and moves the price. A creator names their target payout rather than the buyer's price, which is what makes a change in their own tax standing visible instead of leaving the price fixed while the payout moves underneath them. Note that under the default rounding order a target that does not divide evenly is paid one cent short — 50.00 at 15% is paid as 49.99 — so show the resulting payout rather than repeating the request back.
billing.marketplace.tips—the three keys belowTips and pay-what-you-want, run through the ordinary sale pipeline rather than a donation side path. A tip is consideration for the creator's supply and carries the same regime, commission and document chain.
billing.marketplace.tips.enabledBILLING_MARKETPLACE_TIPSfalseWhether tipping is on at all.
billing.marketplace.tips.commission_bpsBILLING_MARKETPLACE_TIPS_COMMISSION_BPSnullA tip-specific commission rate in basis points. Setting it replaces the ordinary commission entirely — both the rate and the flat per-transaction component, which becomes zero. That is deliberate: a flat fee is a fixed amount, so on a small voluntary payment it would take a large share of it, and on a small enough tip more than the tip. Null means a tip takes the platform's ordinary commission including that flat component. So the flat fee applies to tips exactly when you have said nothing about tips, and stops applying the moment you set a rate — set it to the same value as your ordinary rate if you want a tip charged like any other sale minus the flat part.
billing.marketplace.tips.minimum_minorBILLING_MARKETPLACE_TIPS_MINIMUM_MINORnullThe floor a buyer-chosen tip must clear, in minor units, refused on the server with FanPriceTooLow — the same refusal a pay-what-you-want price below its floor gets, and for the same reason: an amount the buyer picks is the one place the package's stance against price injection would otherwise lapse. Null inherits pwyw.minimum_minor, which is the honest default — an operator who has said a buyer-chosen amount below X is not worth a transaction has said something the provider's fee makes true of both entries. Set it to allow a voluntary payment lower than a purchase, or to 0 to carry no floor on tips where a sale carries one; null and zero are deliberately different answers. A tip of zero is still no sale rather than a refusal, and is answered before the floor.
billing.marketplace.tips.line_nameBILLING_MARKETPLACE_TIPS_LINE_NAMETipWhat the tip line is called on the buyer's hosted checkout page — the one string this package puts in front of a buyer on the provider's site. Configurable because a hardcoded English word would sit on a checkout in any other language, and the default names the act rather than the merchant: whose name belongs on that page is yours to decide, and a provider page is not where this package starts composing sentences about other people.
billing.marketplace.pwyw—the key belowPay-what-you-want pricing, where the fan chooses the price.
billing.marketplace.pwyw.minimum_minorBILLING_MARKETPLACE_PWYW_MINIMUM_MINOR0The floor a chosen price may not fall below, in minor units. Enforced on the server — a buyer-chosen price is the one place the package's anti-injection stance would otherwise lapse. A chosen amount of zero is refused as no sale regardless of this floor.
billing.marketplace.buyer_fee—the keys belowA fee charged to the buyer on a C2C sale — a separate supply from the seller-side commission, and the platform's own first supply in the intermediary posture. Off by default. Its place of supply is where the mediated sale happens, not where the buyer banks, and it is quoted gross. Kept on its own revenue account, because netting it into the item price or the seller's turnover would make a taxable supply of the platform's own disappear.
billing.marketplace.buyer_fee.enabledBILLING_MARKETPLACE_BUYER_FEEfalseWhether a buyer fee is charged at all.
billing.marketplace.buyer_fee.modelBILLING_MARKETPLACE_BUYER_FEE_MODELpercentpercent (a share of the sale) or fixed (a flat amount, charged on top of the item price rather than out of it).
billing.marketplace.buyer_fee.bpsBILLING_MARKETPLACE_BUYER_FEE_BPS0The percentage rate in basis points, for the percent model.
billing.marketplace.buyer_fee.fixed_minorBILLING_MARKETPLACE_BUYER_FEE_FIXED_MINOR0The flat gross amount in minor units, for the fixed model.
billing.marketplace.buyer_fee.place_of_supplyBILLING_MARKETPLACE_BUYER_FEE_PLACEnullThe country whose rate the fee carries. It is where the mediated sale happens, never where the buyer banks — a mediation is supplied where the transaction it mediates is, so the payer's own seat does not move it. Unset, the shipped checkout states the sale's currency region, which is what that lane actually knows about the transaction; set it, or supply your own checkout, when you can answer more precisely.
billing.marketplace.buyer_fee.revenue_accountBILLING_MARKETPLACE_BUYER_FEE_ACCOUNT8510The revenue account the fee is booked to. The number is config; that it is separate from the item and the seller's turnover is structural.
billing.marketplace.seller_fee.enabledBILLING_MARKETPLACE_SELLER_FEEfalseWhether the seller is charged a commission for having their sale arranged — the platform's second intermediation supply, and the mirror of the buyer fee above. Off by default, so a mediated sale still produces exactly the one document it produces today until you turn this on.
billing.marketplace.seller_fee.modelBILLING_MARKETPLACE_SELLER_FEE_MODELpercentpercent (a share of the sale) or fixed (a flat amount). Unlike the buyer fee, a fixed commission is capped by the sale: it comes out of the payout, and a fee larger than the sale would owe the seller a negative amount.
billing.marketplace.seller_fee.bpsBILLING_MARKETPLACE_SELLER_FEE_BPS0The percentage rate in basis points, for the percent model.
billing.marketplace.seller_fee.fixed_minorBILLING_MARKETPLACE_SELLER_FEE_FIXED_MINOR0The flat gross amount in minor units, for the fixed model, capped by the sale.
billing.marketplace.seller_fee.revenue_accountBILLING_MARKETPLACE_SELLER_FEE_ACCOUNT8511The revenue account the commission is booked to. Its own account rather than the buyer fee's, because it is its own supply — booking both to one would make them indistinguishable in an export that has to tell them apart.
billing.marketplace.charge_type_by_posture—the two keys belowWhich money routing is compatible with which declared seller. The charge type decides who the provider treats as the merchant of record; the posture decides who the documents name as the seller. Neither determines the other — for electronic services the seller is assigned by law regardless of how the money flows — and because they are independent they can be set to disagree. A pair that disagrees raises no error: it produces a receipt and a settlement describing different transactions, found in an audit. A missing or misshapen table permits nothing rather than everything, so a typo cannot open the combination the check exists to close.
billing.marketplace.charge_type_by_posture.destination—['seller_of_record', 'platform_intermediary']Postures a destination charge may be used with. It makes the connected account the merchant of record, so it fits a sale the merchant is the seller of.
billing.marketplace.charge_type_by_posture.separate_transfer—['platform_deemed_supplier', 'platform_intermediary']Postures a separate transfer may be used with. The platform takes the whole payment and carries the dispute, which is what a platform that is the deemed supplier must do whatever it would prefer.
billing.marketplace.fee.default_bpsBILLING_MARKETPLACE_FEE_BPS0What the platform keeps, in basis points — 250 is 2.5%. Zero is the neutral position, deliberately: shipping a take rate would be choosing your commercial terms for you. A value that cannot be read as an integer is refused rather than cast, because the cast of a broken rate is 0, and a zero commission is indistinguishable from a platform that deliberately takes nothing.
billing.marketplace.fee.default_flat_minorBILLING_MARKETPLACE_FEE_FLAT_MINOR0A fixed amount kept per transaction, in minor units. Supported alongside the rate because the provider's own pricing has the same shape, and a marketplace that could express only one would have to approximate the other. Taken first; the rate then applies to what remains.
billing.marketplace.webhook—the two keys belowThe endpoint provider events about a merchant arrive on. It is separate from the platform endpoint, with a separate secret.
billing.marketplace.webhook.pathBILLING_MARKETPLACE_WEBHOOK_PATHbilling/webhook/marketplaceWhere merchant events are received. The endpoint answers as the platform endpoint does, 200 for a handled delivery and 404 while the marketplace is off, and it carries the same billing.webhook_middleware.
billing.marketplace.webhook.secretBILLING_MARKETPLACE_WEBHOOK_SECRETnullThe signing secret for that endpoint. Required in production once the marketplace is on — the app refuses to boot without it, because a marketplace whose merchant events all fail verification reports no error at all: the capability flags simply never move, and a merchant who lost their payout capability keeps being paid. Never reuse the platform secret: a verifier accepting either would let the merchant key authenticate platform events, and those move the platform's own money.
billing.marketplace.onboarding—the key belowGiving a merchant an account at the provider and driving its hosted identity flow.
billing.marketplace.onboarding.account_typeBILLING_MARKETPLACE_ACCOUNT_TYPEexpressWhich kind of provider account a merchant gets. express leaves onboarding, identity and the merchant dashboard with the provider; standard gives the merchant a full account and a direct relationship with the provider. It decides who runs the identity checks and who absorbs a loss, and a provider will not change it once a merchant has onboarded — so an unsupported value is refused at boot.
billing.marketplace.preflight—the two keys belowThe go-live checklist that gates enabled. php artisan billing:marketplace:preflight prints it; with the marketplace on, an open blocking point refuses the boot and names itself.
billing.marketplace.preflight.attestations—[]Points nobody can check from here — terms published, a registration filed — recorded as your own statement, keyed by checkpoint key: version, attested_at (YYYY-MM-DD, mandatory) and an optional reference. The version is the expiry: when a release changes what has to be attested, the recorded version stops matching and the point goes red until somebody re-reads and re-attests.
billing.marketplace.preflight.waived—[]Blocking points you deliberately proceed without, by checkpoint key. A waived point is still evaluated and still prints why it did not hold; it is demoted to a warning, never to a pass. Structural points cannot be waived. An entry naming one, a key no checkpoint carries and a value that is not a list of keys are each reported as a blocking failure under configuration.waivers rather than ignored.

A platform that sets its own terms, authorizes billing or approves the supply cannot truthfully assert the three no_* keys. Leave them false.

Replacing the package's own models​

KeyEnvDefaultWhat it does
billing.models—[]Your subclasses of the package's own models, keyed by the package class. Inert until set; see below.

models replaces one of the package's own models with your subclass, keyed by the package class:

'models' => [
\Pushery\Billing\Models\Subscription::class => \App\Models\Subscription::class,
],

It is not customer.model, which names your billable model. Use models when you need your own relations, scopes or casts on an order, a subscription or an invoice record. The package then uses your class on every path: every query, every row it writes, and the relations between its models. In your own code, Subscription::model() returns the configured class and Subscription::resolve() a new instance of it.

  • Your subclass inherits the table and the guards, and it has to keep both. Several of these rows are append-only records of money and tax, and the package writes them only through its own paths.
  • A class that does not exist, or does not extend the package class, is ignored in favor of the package class. Obeying it would fail inside a webhook or a renewal run, long after boot.
  • Mapping a model that other rows point at polymorphically changes the type new rows record, unless you register a morph map for it. The package reads both names wherever it compares them, so older rows keep matching.

config/account.php​

account and license are this package's names in your application's config. A file of your own called config/account.php or config/license.php merges with the package's settings, and its values then steer the hub: an account.middleware of yours, for example, replaces the hub's auth. Keep your own settings under another name; billing:doctor names every key in either file that the package does not ship.

KeyEnvDefaultWhat it does
account.enabledBILLING_ACCOUNT_ENABLEDtrueWhether the hub is mounted at all. Turn it off when your app has account screens of its own: none of the hub's routes or Livewire screens are registered (the realtime bridge billing.account-realtime stays, for a layout of your own), and a checkout returns to billing.checkout.success_url and billing.checkout.cancel_url, which then have to be set. See The account hub.
account.prefixBILLING_ACCOUNT_PREFIXaccount/billingThe URL prefix the hub mounts under. The whole hub is gated on billing.enabled, and payments-only mode (billing.mode) leaves it unmounted.
account.middleware—['web', 'auth']The middleware stack the hub routes run through. The hub shows a signed-in owner their own billing.
account.cancel_requires_confirmationBILLING_CANCEL_CONFIRMfalseWhether canceling takes two clicks. Off by default: canceling is reversible, and a subscription that is hard to leave is a dark pattern rather than a safeguard. Turn it on when your own acceptance asks for a confirmation step — the first click arms the cancellation, the second carries it out, and there is a way back in between.
account.layoutBILLING_ACCOUNT_LAYOUTbilling::layouts.accountThe Blade layout the full-page screens extend. Point it at your own layout to frame the hub in your chrome.
account.csp.enabledBILLING_ACCOUNT_CSPtrueThe scoped Content-Security-Policy, so the driver's payment element loads on the billing screens only. Turn it off only if your app already sends its own CSP for these routes — browsers enforce every CSP header at once.
account.csp.additional—[]Extra origins to allow, keyed by directive (for example 'font-src' => ['https://fonts.example']). Whitelist here rather than turning the header off.
account.stylesheetBILLING_ACCOUNT_STYLESHEETnullA compiled stylesheet for the standalone layout. Unused in the recommended setup, where account.layout points at your own layout and the package's views are added to your Tailwind source scan. A stylesheet on another origin is added to the hub's style-src, so it needs no entry in account.csp.additional.

config/license.php​

KeyEnvDefaultWhat it does
license.tiers—[]What each tier unlocks, keyed by tier key: features (boolean grants) and limits (numeric ceilings, where null means uncapped; a string of digits, which is how env() delivers a number, counts as that number). A tier, feature or limit that is not listed is denied or uncapped by the safe defaults — never an error.

This file is licensing, billing.php is pricing, and they are orthogonal on purpose. Billing code never reads license.* — an architecture test enforces it — and the single bridge is the License contract. Neither ever blocks a public or marketing surface.


← Back to the documentation index