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.
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
| Key | Env | Default | What it does |
|---|---|---|---|
billing.enabled | BILLING_ENABLED | true | The master switch. 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. |
billing.default | BILLING_DRIVER | stripe | The payment driver used when none is named. |
billing.stripe.api_version | BILLING_STRIPE_API_VERSION | null | The dated Stripe API version every call and webhook runs against. Null uses the version the package pins and is tested against. Override it only deliberately: Stripe versions the shape of a webhook payload, so a newer version can silently change what a mapper reads. |
Webhooks
| Key | Env | Default | What it does |
|---|---|---|---|
billing.webhook_path | BILLING_WEBHOOK_PATH | billing/webhook | The path the provider posts to. The route carries no middleware group and no CSRF — the driver's verifier authenticates by signature instead. |
billing.webhooks.connection | BILLING_WEBHOOK_QUEUE_CONNECTION | null | Queue connection for webhook effects. Null uses the default connection. |
billing.webhooks.queue | BILLING_WEBHOOK_QUEUE | null | Queue name for webhook effects. Set it to keep billing work off the queue your app's other jobs share. |
billing.webhooks.tries | BILLING_WEBHOOK_TRIES | 5 | How 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
| Key | Env | Default | What it does |
|---|---|---|---|
billing.checkout.success_url | BILLING_CHECKOUT_SUCCESS_URL | null | Where a hosted checkout returns on success. Null uses the hub's checkout-return route, which reconciles the subscription onto the local row so a paying customer is never shown the free tier. |
billing.checkout.cancel_url | BILLING_CHECKOUT_CANCEL_URL | null | Where an abandoned checkout returns. Null uses the plan screen. |
billing.checkout.portal_return_url | BILLING_PORTAL_RETURN_URL | null | Where the hosted billing portal returns. Falls back to success_url, then the subscription screen. |
billing.checkout.promotion_codes | BILLING_CHECKOUT_PROMOTION_CODES | true | Whether the provider's promotion-code field is offered at checkout. |
Owner model and seats
| Key | Env | Default | What it does |
|---|---|---|---|
billing.owner | BILLING_OWNER | user | Who owns billing: user (each user pays for themselves) or team (the user's team pays, for seats). |
billing.team_relation | — | team | With a team owner, the relation on the acting user that returns the paying team. Ignored for a user owner. |
billing.seats.membership_relation | — | members | The relation on the team model that returns its members. HasSeats counts it. |
billing.seats.active_status_column | — | null | Column 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 | — | active | The 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. |
Customer and tier resolution
| Key | Env | Default | What it does |
|---|---|---|---|
billing.customer.model | BILLING_CUSTOMER_MODEL | null | The Eloquent model that owns a provider customer record. Set it before you take a real payment. |
billing.customer.column | — | stripe_id | The column holding the provider's customer reference. |
billing.zero_tier | BILLING_ZERO_TIER | free | The fail-safe no-entitlement tier every resolver falls back to. |
billing.tier_column | — | plan | The raw column ColumnTierResolver reads — never an accessor. |
billing.untouchable_tiers | — | [] | Tier keys the plan-sync webhook effect never flips, in either direction. Use it for an admin-comped grant a provider event must not overwrite. |
Tiers, add-ons and coupons
| Key | Env | Default | What it does |
|---|---|---|---|
billing.tiers | — | one free tier | The 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, 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, and an optional grants (meter plus units). An add-on grants either money credit or prepaid usage units. |
billing.coupons | — | [] | Package-owned discount codes keyed by the code the customer enters: either percent (1 to 100) or amount plus currency, with an optional expires_at. Add stripe_coupon to have the provider own the money math at checkout. |
billing.currency | BILLING_CURRENCY | EUR | Used 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
| Key | Env | Default | What 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_attempts | — | 8 | A 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. |
billing.metering.backoff_seconds | — | 60 | Base delay for the exponential backoff between flush attempts. |
billing.metering.stall_hours | — | 6 | How 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.usage.hold_seconds | BILLING_USAGE_HOLD_SECONDS | 900 | How long a hold on a metered allowance stands before it is handed back. Longer than your slowest metered request, shorter than you would tolerate an owner being short of allowance they never spent. |
billing.quota.status | — | 429 | The 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
| Key | Env | Default | What it does |
|---|---|---|---|
billing.dunning | — | three rungs, at 3, 7 and 14 days | The ladder, in order. Each rung: after_days, an optional fee (amount plus currency), an optional label. 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_status | — | 402 | The 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 | — | 30 | How 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. |
Account hub, admin and runtime
| Key | Env | Default | What it does |
|---|---|---|---|
billing.navigation | — | the full hub | The 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.runtime | BILLING_RUNTIME | web | web or native. On native, items flagged web_only are hidden — for flows an app store forbids from being completed in-app. |
billing.link_out | BILLING_LINK_OUT | null | The 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.realtime.enabled | BILLING_REALTIME | false | Opt-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.admin.ability | BILLING_ADMIN_ABILITY | billing-admin | The Gate ability every admin-console access is authorized against. Your app defines it; until you do, the Gate denies everyone. |
billing.admin.prefix | BILLING_ADMIN_PREFIX | admin/billing | The URL prefix the admin console mounts under. |
billing.admin.middleware | — | ['web', 'auth'] | The middleware stack the admin console runs through. |
billing.subscriptions.downgrade_timing | BILLING_DOWNGRADE_TIMING | period_end | When 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. The screen and the swap read this one value, so they cannot disagree. |
Trials
| Key | Env | Default | What it does |
|---|---|---|---|
billing.trial.days | BILLING_TRIAL_DAYS | 0 | Trial length in days; 0 disables trials. Per-tier override: tiers.<key>.trial.days. |
billing.trial.mode | BILLING_TRIAL_MODE | null | none, 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_tier | BILLING_TRIAL_GENERIC_TIER | null | The tier a generic trial unlocks. Null disables generic trials — without a tier to unlock there is nothing to grant. |
billing.trial.requires_payment_method | BILLING_TRIAL_REQUIRES_PM | true | Whether a subscription trial collects a card up front. |
billing.trial.ending_within_days | — | 3 | How many days before a trial ends the app-shell banner starts nudging. |
Tax and the invoice seller
| Key | Env | Default | What it does |
|---|---|---|---|
billing.tax | BILLING_TAX | none | provider (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_profile | BILLING_TAX_PROFILE | null | Which 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 below | Thresholds 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_days | BILLING_TAX_EU_REVALIDATE_AFTER_DAYS | 365 | How 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.on_year_change | BILLING_TAX_REATTEST_ON_YEAR_CHANGE | true | Whether a declaration expires at the year boundary. It has to: the threshold is a statement about a year that has not ended, so a declaration made in March says nothing about the following January — and the platform cannot answer for the merchant, because it only sees what was sold here. |
billing.tax_small_business.reattestation.grace_days | BILLING_TAX_REATTEST_GRACE_DAYS | 30 | How 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.previous_year_limit | BILLING_TAX_SB_PREVIOUS_YEAR_LIMIT | 2500000 | Prior-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_limit | BILLING_TAX_SB_CURRENT_YEAR_LIMIT | 10000000 | Running-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_limit | BILLING_TAX_SB_FOUNDING_YEAR_LIMIT | 2500000 | Turnover 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_enabled | BILLING_TAX_SB_AUTO_FLIP | true | Whether 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.name | BILLING_COMPANY_NAME | null | The seller party on an e-invoice. |
billing.company.vat_id | BILLING_COMPANY_VAT_ID | null | Your VAT identification number. Also the last fallback for the electronic address below. |
billing.company.address | BILLING_COMPANY_ADDRESS | null | Street address of the seller party. |
billing.company.postcode | BILLING_COMPANY_POSTCODE | null | Postal code of the seller party. |
billing.company.city | BILLING_COMPANY_CITY | null | City of the seller party. |
billing.company.country | BILLING_COMPANY_COUNTRY | DE | Two-letter country code of the seller party. |
billing.company.endpoint_id | BILLING_COMPANY_ENDPOINT_ID | null | The 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_scheme | BILLING_COMPANY_ENDPOINT_SCHEME | EM | The scheme code for that address (EM is email). |
Tax and the invoice surface: Taxes · Invoices and e-invoicing.
Data protection and retention
| Key | Env | Default | What it does |
|---|---|---|---|
billing.audit.level | BILLING_AUDIT_LEVEL | money | money 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_customer | BILLING_ERASURE_FORGET_CUSTOMER | false | Whether billing:erase also deletes the customer at the provider. Irreversible, and it cancels their live subscriptions there — off by default. |
billing.retention.webhook_payload_days | BILLING_RETENTION_WEBHOOK_PAYLOAD_DAYS | 90 | How 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_days | BILLING_RETENTION_ERASED_FINANCIAL_DAYS | 2920 | The 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_days | BILLING_RETENTION_AUDIT_DAYS | 3650 | The 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. |
billing.retention.allow_below_statutory_minimum | BILLING_RETENTION_ALLOW_BELOW_STATUTORY_MINIMUM | false | The 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
| Key | Env | Default | What it does |
|---|---|---|---|
billing.datev.consultant | BILLING_DATEV_CONSULTANT | null | The consultant number in the EXTF header. |
billing.datev.client | BILLING_DATEV_CLIENT | null | The client number in the EXTF header. |
billing.datev.account_length | BILLING_DATEV_ACCOUNT_LENGTH | 4 | The account-number length your chart uses. |
billing.datev.revenue_account | BILLING_DATEV_REVENUE_ACCOUNT | null | The 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_account | BILLING_DATEV_CUSTOMER_ACCOUNT | null | The receivables account every invoice books to when no chart is selected. |
billing.datev.chart | BILLING_DATEV_CHART | null | skr03, 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.datev.accounts | — | an SKR03 and an SKR04 map | The 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. 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.
| Key | Env | Default | What it does |
|---|---|---|---|
billing.marketplace.enabled | BILLING_MARKETPLACE_ENABLED | false | Off, the custody guard below has nothing to check and single-merchant behavior is byte-identical. |
billing.marketplace.custody.platform_held | BILLING_MARKETPLACE_PLATFORM_HELD | false | Whether 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_posture | BILLING_MARKETPLACE_POSTURE | platform_deemed_supplier | Who the seller is to the buyer. A liability and VAT decision the package enforces but never makes for you. |
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_electronic | BILLING_MARKETPLACE_SUPPLIES_ELECTRONIC | true | Default 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_asserted | BILLING_MARKETPLACE_ART9A_REBUTTAL | false | The 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 | — | false | Assert that you do not set the terms of the supply. |
billing.marketplace.seller_of_record.no_billing_authorization | — | false | Assert that you do not authorize the charge to the buyer. |
billing.marketplace.seller_of_record.no_supply_authorization | — | false | Assert that you do not authorize the delivery of the supply. |
billing.marketplace.seller_activity | — | the two keys below | When a seller is active enough to be asked to declare their standing. |
billing.marketplace.seller_activity.sales_threshold | BILLING_MARKETPLACE_SALES_THRESHOLD | 30 | Sales in the period that count as trading. |
billing.marketplace.seller_activity.proceeds_threshold_minor | BILLING_MARKETPLACE_PROCEEDS_THRESHOLD_MINOR | 200000 | Proceeds 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 under and its money comparison is inclusive. At exactly the figure they disagree, deliberately — the exemption's boundary is set by law, and moving it to match a preference over-reports. |
billing.marketplace.seller_data_escalation | — | the keys below | What happens when a seller does not supply the data a reporting duty needs. |
billing.marketplace.seller_data_escalation.first_reminder_after_days | BILLING_MARKETPLACE_FIRST_REMINDER_DAYS | 7 | Days incomplete before the first reminder. |
billing.marketplace.seller_data_escalation.second_reminder_after_days | BILLING_MARKETPLACE_SECOND_REMINDER_DAYS | 30 | Days before the second. |
billing.marketplace.seller_data_escalation.measure_after_days | BILLING_MARKETPLACE_MEASURE_AFTER_DAYS | 60 | Days before a measure applies — and only where the missing data is legally required of that seller. |
billing.marketplace.seller_data_escalation.measure | BILLING_MARKETPLACE_DATA_MEASURE | withhold_payout | suspend_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_gaps | BILLING_MARKETPLACE_MEASURE_PRECAUTIONARY | false | Whether 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_days | BILLING_MARKETPLACE_WITHHOLD_UP_TO_DAYS | 90 | How long a withholding may last. |
billing.marketplace.seller_data_escalation.payout_deadline_days | BILLING_MARKETPLACE_PAYOUT_DEADLINE_DAYS | 90 | The 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_record | — | the key below | What a platform asks a seller for at onboarding. |
billing.marketplace.seller_record.collect_precautionary | BILLING_MARKETPLACE_COLLECT_PRECAUTIONARY | true | Collect 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 below | What happens while a merchant's tax standing is unestablished. |
billing.marketplace.tax_status_hold.blocks_payouts | BILLING_MARKETPLACE_HOLD_BLOCKS_PAYOUTS | true | Hold 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_sales | BILLING_MARKETPLACE_HOLD_BLOCKS_SALES | true | Stop 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 below | Which 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.default | BILLING_MARKETPLACE_REGIME | commission_chain | The 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 below | Prefixes for the document series the platform numbers itself. |
billing.marketplace.numbering.series | — | the eight keys below | One 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 | — | F | Prefix for the buyer's receipt. |
billing.marketplace.numbering.series.self_billed_invoice | — | G | Prefix for a self-billed invoice to a merchant. |
billing.marketplace.numbering.series.settlement_note | — | A | Prefix for a settlement note to a private party. |
billing.marketplace.numbering.series.commission_invoice | — | P | Prefix for a commission invoice — drawn only in an intermediation sale, never in a commission chain. |
billing.marketplace.numbering.series.buyer_receipt_correction | — | KF | Prefix for a correction to a buyer receipt. |
billing.marketplace.numbering.series.self_billed_invoice_correction | — | KG | Prefix for a correction to a self-billed invoice. |
billing.marketplace.numbering.series.settlement_note_correction | — | KA | Prefix for a correction to a settlement note. |
billing.marketplace.numbering.series.commission_invoice_correction | — | KP | Prefix for a correction to a commission invoice. |
billing.marketplace.self_billing | — | the keys below | Whether the platform may settle a creator by self-billing, and on what precondition. |
billing.marketplace.self_billing.enabled | BILLING_MARKETPLACE_SELF_BILLING_ENABLED | true | Whether 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.self_billing.require_agreement | BILLING_MARKETPLACE_SELF_BILLING_AGREEMENT | true | Whether 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.fee.rounding | BILLING_MARKETPLACE_FEE_ROUNDING | platform_first | Which 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.consumer_rights | — | the two keys below | The 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.profile | BILLING_CONSUMER_RIGHTS_PROFILE | null | Which consumer-rights reading is in force. Null means off: no extra checkout step, no changed receipt, byte-identical. Set it and the gate is fail-closed — a digital work whose withdrawal right extinguishes on delivery is not provided until the buyer's double consent is recorded, because providing it first forfeits their refund right on their behalf. |
billing.consumer_rights.window_days | BILLING_CONSUMER_RIGHTS_WINDOW_DAYS | 14 | How many days a withdrawal remains a right rather than a courtesy. |
billing.tax_evidence | — | the key below | How the buyer's country is established from the signals available while the sale happens. |
billing.tax_evidence.required_signals | BILLING_TAX_EVIDENCE_SIGNALS | 2 | How 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_markets | — | null | The countries you are registered in and may sell into, as ISO code → open | planned | blocked. 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, because a sale into a country with no registration cannot be repaired by any later document. A market opened here that the local rates cannot price refuses the boot. 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.pricing | — | the key below | How a price behaves across markets with different tax rates. |
billing.marketplace.pricing.mode | BILLING_MARKETPLACE_PRICING_MODE | uniform_gross | Which 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 two keys below | Tips 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.enabled | BILLING_MARKETPLACE_TIPS | false | Whether tipping is on at all. |
billing.marketplace.tips.commission_bps | BILLING_MARKETPLACE_TIPS_COMMISSION_BPS | null | A tip-specific commission rate in basis points. Null means a tip takes the platform's ordinary commission. |
billing.marketplace.pwyw | — | the key below | Pay-what-you-want pricing, where the fan chooses the price. |
billing.marketplace.pwyw.minimum_minor | BILLING_MARKETPLACE_PWYW_MINIMUM_MINOR | 0 | The 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 below | A 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.enabled | BILLING_MARKETPLACE_BUYER_FEE | false | Whether a buyer fee is charged at all. |
billing.marketplace.buyer_fee.model | BILLING_MARKETPLACE_BUYER_FEE_MODEL | percent | percent (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.bps | BILLING_MARKETPLACE_BUYER_FEE_BPS | 0 | The percentage rate in basis points, for the percent model. |
billing.marketplace.buyer_fee.fixed_minor | BILLING_MARKETPLACE_BUYER_FEE_FIXED_MINOR | 0 | The flat gross amount in minor units, for the fixed model. |
billing.marketplace.buyer_fee.revenue_account | BILLING_MARKETPLACE_BUYER_FEE_ACCOUNT | 8510 | The 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.charge_type_by_posture | — | the two keys below | Which 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_bps | BILLING_MARKETPLACE_FEE_BPS | 0 | What 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_minor | BILLING_MARKETPLACE_FEE_FLAT_MINOR | 0 | A 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.fee.processing_fee.borne_by | BILLING_MARKETPLACE_PROCESSING_FEE_BORNE_BY | merchant | Who economically carries the payment provider's OWN fee — separate from your commission. merchant follows the market: they carry it as they would selling anywhere else. platform records that you absorb it. The package only records which side carries it; the provider bills you directly and the package never invoices it. |
billing.marketplace.webhook | — | the two keys below | The endpoint provider events about a MERCHANT arrive on. It is separate from the platform endpoint, with a separate secret. |
billing.marketplace.webhook.path | BILLING_MARKETPLACE_WEBHOOK_PATH | billing/webhook/marketplace | Where merchant events are received. |
billing.marketplace.webhook.secret | BILLING_MARKETPLACE_WEBHOOK_SECRET | null | The 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 below | Giving a merchant an account at the provider and driving its hosted identity flow. |
billing.marketplace.onboarding.account_type | BILLING_MARKETPLACE_ACCOUNT_TYPE | express | Which 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 below | The 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, and an entry naming one is reported as a failure 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.
config/account.php
| Key | Env | Default | What it does |
|---|---|---|---|
account.prefix | BILLING_ACCOUNT_PREFIX | account/billing | The URL prefix the hub mounts under. The whole hub is gated on billing.enabled. |
account.middleware | — | ['web', 'auth'] | The middleware stack the hub routes run through. The hub shows a signed-in owner their own billing. |
account.layout | BILLING_ACCOUNT_LAYOUT | billing::layouts.account | The Blade layout the full-page screens extend. Point it at your own layout to frame the hub in your chrome. |
account.csp.enabled | BILLING_ACCOUNT_CSP | true | The 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.stylesheet | BILLING_ACCOUNT_STYLESHEET | null | A 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. |
config/license.php
| Key | Env | Default | What it does |
|---|---|---|---|
license.tiers | — | [] | What each tier unlocks, keyed by tier key: features (boolean grants) and limits (numeric ceilings, where null means uncapped). 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.