LarsaSub is launching soon. Purchases and account registration open shortly.

Documentation for larsasub v0.38.2. On an older version? Your release notes ship with the package (CHANGELOG.md).

Database schema

Every table the package creates, column by column. Use it when you write a report or a query against LarsaSub's tables, or when you need to check a development database against what the current release expects.

There are twenty-seven tables. All of them carry the larsasub.table_prefix (default larsasub_) and live on the larsasub.database.connection connection (default: your application's default connection). The published migrations are the authority — run php artisan vendor:publish --tag=larsasub-migrations to read them.

Conventions

The column tables below leave out what every table shares, so each row is a column that tells you something.

Seven tables use soft deletes (deleted_at), so the audit trail survives a cleanup: larsasub_subscriptions, larsasub_billable_subscriptions, larsasub_subscription_cost_lines, larsasub_recurring_charges, larsasub_single_payments, larsasub_mollie_customers and larsasub_mollie_mandates.

These columns are encrypted at rest through model casts, so they hold ciphertext — never query or compare them in SQL:

Table Columns Cast
larsasub_recurring_charges, larsasub_single_payments vat_customer, provider_payment_details, provider_chargeback_details, provider_refund_details encrypted:array
larsasub_mollie_mandates consumer_account encrypted
larsasub_mollie_mandates details encrypted:array
larsasub_mollie_connected_accounts access_token, refresh_token encrypted
larsasub_connect_flow_states code encrypted

An encrypted array sits on a longText column, not json: a ciphertext is not valid JSON, and MySQL rejects it in a JSON column.

Core billing

The registration, the plans and each subscriber's copy of a plan.

larsasub_billables

The registration linking your owner model (User, Team, Tenant) to everything else. Model: Billable — see Billable models.

Column Type Null Notes
profile_code string(24) no Unique. Eight random characters, prefixed with the tenant's reference code in Connect mode; shown on the dashboards
owner_id unsignedBigInteger no Your owner model's key
owner_type string no Your owner model's morph class
tenant_type string yes The Connect tenant
tenant_id unsignedBigInteger yes The Connect tenant
tenant_key string(191) no Default ''. Non-null mirror of the tenant, {morph class}:{id}, '' without a tenant
locale string(12) yes The billable's content locale. Money paths read this, never the owner
invoice_type string(16) no Default personal; personal or business. See The invoice profile
invoice_first_name string(128) yes
invoice_last_name string(128) yes
invoice_email string(255) yes
invoice_company_name string(255) yes
invoice_vat_number string(32) yes
invoice_coc_number string(32) yes Chamber of commerce number
invoice_address_line_1 string(255) yes
invoice_address_line_2 string(255) yes
invoice_city string(128) yes
invoice_postal_code string(16) yes
invoice_state string(128) yes
invoice_country string(2) yes ISO 3166-1 alpha-2 code
vat_check_status string(16) yes Enums\VatNumberStatus value of the VIES check; null = not applicable or never checked. See Validating VAT numbers
vat_check_number string(34) yes The normalized number the status belongs to (VIES country code + national number)
vat_check_reference string(255) yes VIES consultation number of the last valid answer
vat_checked_at timestamp yes Date of the last definitive VIES answer
anonymized_at timestamp yes Set by Billable::anonymize(). See Erasing a billable

The four vat_check_* columns are written by the package only and are not fillable, so a profile form cannot set them.

Indexes:

larsasub_subscriptions

Plan templates. Model: Subscription — see The subscription plan. Soft deletes.

Column Type Null Notes
tenant_id unsignedBigInteger yes Null = a global plan
tenant_type string yes
feature_list string(32) yes
features json yes
name text no Translatable: a plain string or a JSON locale map. See Multilingual plan content
description text yes Translatable
content mediumText yes Translatable
active boolean no Default false
start_date date yes Available from
end_date date yes Available until
duration string(12) no The contract term
billing_interval_months unsignedTinyInteger no Default 1. Whole months, 1–12; a fixed duration must hold a whole number of periods (validated on the model)
is_charged boolean no Default true
price_amount decimal(10,2) no The price per billing period, not per month
price_currency string(6) no Default currency.default
vat_rate decimal(5,2) yes Per-plan VAT rate; null = invoicing.vat_rate. Every subscription without an override of its own follows it from its next charge; a change through the model fires SubscriptionVatRateChangedEvent
introduction_duration string(12) no
introduction_price_amount decimal(10,2) no
cancellation_notice_period string(12) no
order_nr integer yes Sort order
internal_note string(510) yes
automatic_renewal boolean no Default false
renewal_cancellation_notice_period string(12) no
fee_type string(16) yes Connect application fee override: percentage or fixed; null = connect.fee
fee_value string(10) yes 5 (5%) or 0.25 (€0.25)
minimum_days_before_change unsignedInteger yes Null = subscription.minimum_days_before_change
deleted_at timestamp yes Soft delete

Indexes: (tenant_id, tenant_type).

larsasub_subscription_change_targets

Which plans a plan may be switched to. A pivot with no model of its own; it backs Subscription::changeTargets().

Column Type Null Notes
subscription_id unsignedBigInteger no FK larsasub_subscriptions, cascade on delete
target_subscription_id unsignedBigInteger no FK larsasub_subscriptions, cascade on delete

Indexes: unique (subscription_id, target_subscription_id).

larsasub_billable_subscriptions

One subscriber's instance of a plan. The price and terms are copied from the plan at creation, so a later plan edit never changes the price a running subscriber pays or how often. The VAT rate is the exception: it is not copied, and follows the plan (see vat_rate). Model: BillableSubscription — see The BillableSubscription instance. Soft deletes.

Column Type Null Notes
subscription_id unsignedBigInteger no FK larsasub_subscriptions — the plan
billable_id unsignedBigInteger no FK larsasub_billables
payment_method_id unsignedBigInteger no FK larsasub_payment_methods
provider_mandate_type, provider_mandate_id morph yes The mandate this subscription collects with
activated_at timestamp yes
deactivated_at timestamp yes
activated_status string(64) no Display and audit text. Never query it — key on the two enum columns below
deactivation_reason string(48) yes Enums\DeactivationReason value
activation_source string(48) yes Enums\ActivationSource value; null on host-forced activations without a known source
start_date date no The real start; anchors the billing day and the proration. Change it through BillableSubscriptionService::moveStartDate() — see Moving the start date
end_date date yes
first_charge_on date yes The first day the subscription may be collected on; null = bill from start_date. Set when the period before it was paid elsewhere, so an imported subscriber is not charged twice. See Migrating subscribers in
duration string(12) no Copied from the plan
billing_interval_months unsignedTinyInteger no Default 1. Copied from the plan
is_charged boolean no Default true
price_amount decimal(10,2) no Per billing period
price_currency string(6) no Default currency.default
vat_rate decimal(5,2) yes An override for this subscription; null = the plan's rate (a soft-deleted plan's too), then invoicing.vat_rate, read when each charge is composed. Never copied from the plan
introduction_duration string(12) no
introduction_price_amount decimal(10,2) no
cancellation_notice_period string(12) no
automatic_renewal boolean no Default false
renewal_cancellation_notice_period string(12) no
name text no Copied raw from the plan, full locale map included
description text yes Copied raw from the plan
feature_overrides json yes
canceled_at timestamp yes
credit_amount decimal(10,2) yes
renewal_announced_at timestamp yes
started_processed_at timestamp yes Set once the upcoming-to-active transition was handled
deleted_at timestamp yes Soft delete

Indexes: billable_id; (started_processed_at, start_date); the provider_mandate morph pair.

larsasub_subscription_cost_lines

Recurring add-ons on a subscription instance, prorated per calendar day. Model: SubscriptionCostLine — see Cost lines. Soft deletes.

Column Type Null Notes
billable_subscription_id unsignedBigInteger no FK larsasub_billable_subscriptions
key string(64) no Your identifier for the line, e.g. module:agenda
description string no
amount decimal(10,2) no Monthly amount
starts_on date no Inclusive
ends_on date yes Inclusive; null = open-ended
billed_through date yes Last day covered by a generated charge
valued_from date yes First day of the stretch billed_through closes that was actually valued. Read it through SubscriptionCostLine::valuedFrom(), never as a raw attribute
deleted_at timestamp yes Soft delete

valued_from and starts_on are not each other's mirror: an opening charge clamps to its own period start and writes the days before it off. Null means "nothing valued yet" on a new line and "predates the column" on an old one, and the accessor answers both with starts_on. Lines marked before the column existed are repaired once with larsasub:backfill-valued-from — see the upgrade guide.

Indexes: (billable_subscription_id, key).

Payments

Payment methods, the two payment tables and what hangs off them: order lines, attached entities, reminder series and payouts.

larsasub_payment_methods

The available payment methods. Model: PaymentMethod — see Enabling methods.

Column Type Null Notes
name string(64) no
description string(510) no
provider string(32) no e.g. mollie
provider_id string(12) no The provider's method id
active boolean no Default false
tenant_usable boolean no Default false. Whether a Connect tenant may offer it
recurring boolean no Default false
single boolean no Default false
first_payment_method_id unsignedBigInteger yes The method that opens a mandate for this one. No foreign key
internal_note string(510) yes

Indexes: unique (provider, provider_id).

larsasub_tenant_payment_methods

Per-tenant enablement of those methods in Connect mode. Model: TenantPaymentMethod — see Tenant availability.

Column Type Null Notes
tenant_id unsignedBigInteger no
tenant_type string(255) no
payment_method_id unsignedBigInteger no FK larsasub_payment_methods, cascade on delete
active boolean no Default true

Indexes: unique (tenant_id, tenant_type, payment_method_id).

larsasub_recurring_charges

One payment in a subscription's billing history — and also every charge without a subscription: standalone charges, the €0.01 mandate setup and chargeback follow-ups. Model: RecurringCharge — see Recurring charges. Soft deletes.

Column Type Null Notes
reference string(16) yes Unique payment reference
billable_id unsignedBigInteger yes FK larsasub_billables
billable_subscription_id unsignedBigInteger yes FK larsasub_billable_subscriptions, set null on delete; null on a standalone charge
payment_method_id unsignedBigInteger no FK larsasub_payment_methods. A manual payment overwrites it with the method the customer picks
amount decimal(10,2) no The amount collected, after credit
original_amount decimal(10,2) yes Before credit
credit_applied decimal(10,2) yes
platform_fee_amount decimal(10,2) yes The Connect application fee charged
platform_fee_type string(16) yes The fee rule it came from
platform_fee_percentage decimal(5,2) yes
currency string(6) no Default currency.default
notify boolean no Default false. Advance-notice bookkeeping
description string yes
amount_breakdown longText yes Composition snapshot (base price + cost lines), taken at planning
vat_rate decimal(5,2) yes The effective rate after the country rules; 0.00 = zero-rated, null = no snapshot
vat_treatment string(16) no Default excluded. Enums\VatTreatment value — see the table
vat_reverse_charge boolean no Default false. Frozen, because the invoice prints it
vat_customer longText yes Encrypted. The customer fields the VAT decision rested on, frozen when the money moved
planned_on date no The day the charge is due
notified_at dateTime yes
charged_at dateTime yes When it was claimed for collection, written before the provider call
paid_on dateTime yes
status_update_at dateTime yes
provider_payment_id string(32) yes
provider_mandate_type, provider_mandate_id morph yes
provider_payment_status string(32) yes
provider_payment_status_raw string(32) yes Reserved for the provider's own status word; nothing writes it yet
settlement_id string(32) yes The provider payout this payment was paid out in — see below
provider_url string yes
provider_payment_details longText yes Encrypted
provider_chargeback_details longText yes Encrypted
provider_refund_details longText yes Encrypted
needs_attention boolean no Default false
attention_message string(128) yes Opens with a marker constant — see Public API
needs_attention_since dateTime yes
payable boolean no Default false. Claim and webhook bookkeeping, not "the customer may pay this"
mandate_setup boolean no Default false. The €0.01 verification charge; never invoiced
superseded_by_charge_id unsignedBigInteger yes FK to this table, set null on delete
follows_charge_id unsignedBigInteger yes On a chargeback follow-up: the charge it collects again. FK to this table, set null on delete
origin_chargeback_id string(32) yes Unique. On a follow-up: the chargeback it answers
origin_reversed_at dateTime yes On a follow-up: when the bank reversed that chargeback
reversal_checked_at dateTime yes On a charged-back original: when the package last asked the provider whether the chargeback was reversed
deleted_at timestamp yes Soft delete

Indexes:

larsasub_single_payments

One-time payments. Model: SinglePayment — see Single payments. Soft deletes.

Column Type Null Notes
reference string(16) yes Unique payment reference
billable_id unsignedBigInteger yes FK larsasub_billables
payment_method_id unsignedBigInteger no FK larsasub_payment_methods. Overwritten with the method the customer picks when they start the payment
amount decimal(10,2) no The amount collected, as you supplied it
currency string(6) no Default currency.default
description string yes
vat_rate decimal(5,2) yes The payment-level rate; 0.00 = zero-rated, null = resolve live. With order lines carrying their own rate, the lines hold the authoritative split
vat_treatment string(16) no Default excluded. Enums\VatTreatment value
vat_reverse_charge boolean no Default false
vat_customer longText yes Encrypted, as on charges
platform_fee_amount decimal(10,2) yes Connect application fee
platform_fee_type string(16) yes
platform_fee_percentage decimal(5,2) yes
charged_at dateTime yes
paid_on dateTime yes
status_update_at dateTime yes
provider_payment_id string(32) yes
provider_payment_status string(32) yes
provider_payment_status_raw string(32) yes Reserved; nothing writes it yet
settlement_id string(32) yes The provider payout — see below
provider_url string yes
provider_payment_details longText yes Encrypted
provider_chargeback_details longText yes Encrypted
provider_refund_details longText yes Encrypted
needs_attention boolean no Default false
attention_message string(128) yes
needs_attention_since dateTime yes
payable boolean no Default true
deleted_at timestamp yes Soft delete

Indexes: unique reference; billable_id; provider_payment_id; settlement_id.

settlement_id on both payment tables is written by larsasub:sync-settlements and by nothing else. It is the provider's settlement id, not a foreign key: a payment is attached before, or without, its larsasub_settlements row. See Settlements.

larsasub_single_payment_entities

Pivot linking a single payment to your IsPayable business objects. Model: SinglePaymentEntity — see Attaching entities.

Column Type Null Notes
single_payment_id unsignedBigInteger no FK larsasub_single_payments, cascade on delete
entity_type, entity_id morph no Your IsPayable model
processed_at timestamp yes Set once onPaymentPaid() ran for this entity, so a redelivered webhook does not run it twice

Indexes: single_payment_id; the entity morph pair.

larsasub_single_payment_lines

Optional order lines on a single payment. Model: SinglePaymentLine — see Order lines.

Column Type Null Notes
single_payment_id unsignedBigInteger no FK larsasub_single_payments, cascade on delete
description string no
quantity decimal(8,2) no Default 1
unit_amount decimal(10,2) no
amount decimal(10,2) no Line total, in the payment amount's terms
vat_rate decimal(5,2) yes Per-line rate; null = the payment's own vat_rate, else invoicing.vat_rate
sort_order unsignedSmallInteger no Default 0

Indexes: single_payment_id.

larsasub_dunning_cases

One reminder series per unpaid charge. Written only through DunningService; no soft deletes — the model log is the trail. Model: DunningCase — see Reminder series, which has a table for every enum value below.

Column Type Null Notes
recurring_charge_id unsignedBigInteger no Unique. FK larsasub_recurring_charges
billable_id unsignedBigInteger no FK larsasub_billables, copied from the charge
origin string(32) no Enums\DunningOrigin value
schedule json no The steps as configured when the case opened
started_at dateTime no The moment the steps count from
active_since dateTime no Opening or the last resume; no step runs within a day of it
status string(32) no Enums\DunningStatus value
pause_reason string(32) yes Enums\DunningPauseReason value, while paused
paused_since dateTime yes
closed_reason string(32) yes Enums\DunningClosedReason value, when closed
resolved_at dateTime yes
assessed_at dateTime yes The last run that looked at the case
started_dispatched_at dateTime yes Outbox marker for the started event
resolution_dispatched_at dateTime yes Outbox marker for the resolved event

Indexes: unique recurring_charge_id; billable_id; status.

larsasub_dunning_steps

One row per step of a case's schedule that a run took up. Model: DunningStep.

Column Type Null Notes
dunning_case_id unsignedBigInteger no FK larsasub_dunning_cases
step_index unsignedSmallInteger no Position in the case's own schedule, from 0
action string(32) no Enums\DunningAction value
result string(32) no Enums\DunningStepResult value
executed_at dateTime no
dispatched_at dateTime yes Outbox marker: set once the step's events went out

Indexes: unique (dunning_case_id, step_index) — two overlapping runs cannot both take the same step.

larsasub_settlements

The provider's payouts, one row per settlement, written by larsasub:sync-settlements. Model: Settlement — see Settlements.

Column Type Null Notes
tenant_type string(255) yes The connected account; null = the platform's own account. Never overwritten
tenant_id unsignedBigInteger yes
settlement_id string(32) no Unique. The provider's id (stl_…)
reference string(64) yes As printed on the bank statement; absent while the settlement is open
status string(32) no The provider's own word, stored unchanged
amount decimal(10,2) no The paid-out amount
currency string(6) no
settled_at dateTime yes
provider_created_at dateTime no
invoice_id string(32) yes The provider's invoice id
periods json yes The provider's revenue and costs breakdown
synced_at dateTime yes When every payment of the settlement was last attached; null until the first complete pass

Indexes: unique settlement_id; (tenant_type, tenant_id); reference; status.

Credit

The credit wallet and its ledger. Mutate them only through CreditService — see Credit.

larsasub_billable_credits

One credit account per billable. Model: BillableCredit.

Column Type Null Notes
billable_id unsignedBigInteger yes Unique. FK larsasub_billables
balance decimal(10,2) no Default 0.00
currency string(6) no Default currency.default. The one currency the balance is held in; credit is applied only to a charge in it

Indexes: unique billable_id.

larsasub_credit_transactions

The ledger: one row per credit mutation. Model: CreditTransaction — see Transaction types.

Column Type Null Notes
billable_credit_id unsignedBigInteger no FK larsasub_billable_credits, cascade on delete
billable_id unsignedBigInteger yes FK larsasub_billables, set null on delete. Denormalized for direct querying
amount decimal(10,2) no Positive for additions, negative for deductions
remaining_amount decimal(10,2) yes On additions only: how much of this credit is still unspent
balance_before decimal(10,2) no
balance_after decimal(10,2) no
type string(32) no One of the CreditTransaction::TYPE_* constants
description string(255) yes
source_type, source_id morph yes What caused the transaction
recurring_charge_id unsignedBigInteger yes FK larsasub_recurring_charges, set null on delete
expires_at timestamp yes
expired boolean no Default false
expiration_notified_at timestamp yes
performed_by_user_id unsignedBigInteger yes Your auth user. No foreign key: the user table may live in another database
meta json yes

Indexes: billable_credit_id, billable_id, type, recurring_charge_id, expires_at, expired, performed_by_user_id, the source morph pair.

Invoicing

Invoice and credit-note snapshots and their numbering. Invoicing is off by default — see Invoicing and VAT.

larsasub_invoices

Finalized invoice snapshots. Model: Invoice — see The invoice model.

Column Type Null Notes
invoice_number string(32) no Unique
status enum(finalized, voided) no Default finalized
billable_id unsignedBigInteger no FK larsasub_billables
invoiceable_type, invoiceable_id morph yes The payment: a RecurringCharge or a SinglePayment
tenant_type, tenant_id morph yes The seller in Connect mode
customer_name string no Customer snapshot
customer_email string yes
customer_company_name string yes
customer_vat_number string(32) yes
customer_coc_number string(32) yes
customer_address text yes
customer_country string(2) yes
customer_vat_check_reference string(255) yes The VIES consultation number behind a reverse charge. Never printed on the PDF
seller_name string no Seller snapshot
seller_vat_number string(32) yes
seller_coc_number string(32) yes
seller_address text yes
seller_country string(2) yes
subtotal decimal(10,2) no Net of VAT
vat_amount decimal(10,2) no Default 0. Always total − subtotal
total decimal(10,2) no The charge's full value including VAT, before credit (original_amount). total − credit_applied is what the customer was debited
currency string(3) no The currency the payment was collected in (before 0.38.1: currency.default for a charge and for a single payment without order lines)
vat_rate decimal(5,2) yes Default 0. Null = mixed per-line rates
reverse_charge boolean no Default false
credit_applied decimal(10,2) no Default 0
description string no
invoice_date date no
payment_method_label string yes
external_download_url string(512) yes Set when your own invoicing system produces the document

Indexes: unique invoice_number; unique (invoiceable_type, invoiceable_id) — at most one invoice per payment; billable_id; the tenant morph pair.

larsasub_invoice_lines

Immutable invoice lines, net of VAT, summing exactly to the invoice subtotal. Model: InvoiceLine — see Invoice lines.

Column Type Null Notes
invoice_id unsignedBigInteger no FK larsasub_invoices, cascade on delete
key string(64) yes The cost line key; null for base and single-payment lines
description string no
quantity decimal(8,2) no Default 1
unit_amount decimal(10,2) no
amount decimal(10,2) no Net of VAT
vat_rate decimal(5,2) yes Null = the invoice-level rate applies
vat_amount decimal(10,2) yes
period_start date yes Billed period of a subscription or cost line
period_end date yes
sort_order unsignedSmallInteger no Default 0

Indexes: invoice_id.

larsasub_credit_notes

Credit notes voiding an invoice after a refund or chargeback. Model: CreditNote — see Credit notes.

Column Type Null Notes
credit_note_number string(32) no Unique
invoice_id unsignedBigInteger no FK larsasub_invoices
billable_id unsignedBigInteger no FK larsasub_billables
tenant_type, tenant_id morph yes
total decimal(10,2) no
vat_amount decimal(10,2) no Default 0
currency string(3) no The invoice's currency
vat_rate decimal(5,2) yes Null = mixed per-line rates on the voided invoice
reverse_charge boolean no Default false
reason string yes
external_download_url string(512) yes
issued_at date no

Indexes: unique credit_note_number; invoice_id; billable_id; the tenant morph pair.

larsasub_invoice_sequences

Continuous numbering, one sequence per document type and tenant, never reset. Model: InvoiceSequence — see Numbering.

Column Type Null Notes
tenant_type, tenant_id morph yes
tenant_key string no Default ''. Non-null mirror of the tenant, as on larsasub_billables
type string(16) no invoice or credit_note
last_number unsignedInteger no Default 0

Indexes: unique (tenant_key, type) — the non-null tenant_key is what makes it bite in simple mode, where a unique over the nullable morph would let two first invoices mint the same number.

Provider (Mollie)

Customer and mandate references at Mollie, and the Connect tables. See Mollie models.

larsasub_mollie_customers

Customer references at Mollie, one per billable per tenant. Model: MollieCustomer. Soft deletes.

Column Type Null Notes
customer_id string(32) no Mollie's customer id (cst_…)
billable_id unsignedBigInteger yes FK larsasub_billables
tenant_id unsignedBigInteger yes
tenant_type string yes
deleted_at timestamp yes Soft delete

Indexes: unique (billable_id, tenant_id, tenant_type).

larsasub_mollie_mandates

Direct debit mandates. Model: MollieMandate — see Mandates. Soft deletes.

Column Type Null Notes
mandate_id string(32) no Mollie's mandate id (mdt_…)
status string(32) no
consumer_account text no Encrypted. Not null
billable_id unsignedBigInteger yes FK larsasub_billables
customer_id unsignedBigInteger no FK larsasub_mollie_customers — a mandate requires a customer
payment_method_id unsignedBigInteger no FK larsasub_payment_methods
details longText yes Encrypted
usable boolean no Default false
last_validated_at timestamp yes Resume cursor for larsasub:validate-mandates
deleted_at timestamp yes Soft delete

The tenant of a mandate is the tenant of its customer.

Indexes: unique (customer_id, mandate_id); (billable_id, payment_method_id); last_validated_at.

larsasub_mollie_connected_accounts

A tenant's OAuth connection to its own Mollie organization. Model: MollieConnectedAccount — see Mollie Connect.

Column Type Null Notes
reference_code string(4) yes Unique. The tenant's code in payment references, e.g. LCRA
tenant_id unsignedBigInteger no
tenant_type string no
access_token text no Encrypted
refresh_token text no Encrypted
expires_at timestamp yes Access token expiry
mollie_organization_id string yes
organization_name string yes
mollie_profile_id string yes The website profile payments are created on. See Choosing the website profile
mollie_profile_name string yes
mollie_profile_count unsignedInteger yes
profile_selected_at timestamp yes Set when the tenant chose the profile themselves
connected_at timestamp yes
disconnected_at timestamp yes
disconnect_requested_at timestamp yes A requested disconnect that is draining. See Disconnecting a tenant
disconnect_deadline_at timestamp yes Frozen at the request
settlements_synced_at timestamp yes The last successful settlement sync
settlements_sync_error string(64) yes Enums\SettlementSyncOutcome value: why the last pass could not run

Indexes: unique (tenant_id, tenant_type) — one connected account per tenant; unique reference_code; disconnected_at.

larsasub_connect_flow_states

A Connect authorization in progress. It lives in a table rather than the cache because the flow crosses origins — see The OAuth flow. Model: ConnectFlowState.

Column Type Null Notes
state_hash char(64) yes Unique. SHA-256 of the OAuth state; cleared when consumed
ticket_hash char(64) yes Unique. SHA-256 of the completion ticket; cleared when consumed
tenant_type string no The tenant being connected
tenant_id unsignedBigInteger no
user_key string(191) no The user who started the flow; only they can complete it
origin string no Where the initiating session lives
return_url text no
code text yes Encrypted. The authorization code, present only between the callback and completion
expires_at timestamp no

The rows are transient: a flow that completes or is declined is deleted on the spot, and expired rows are swept by the next authorization. That is why the table has no prune command.

Indexes: unique state_hash; unique ticket_hash; expires_at.

Supporting

Acceptance evidence, the notification delivery log and the activity trail. None of these tables stores a raw IP address, user agent, email address, subject or body — see Compliance.

larsasub_terms_acceptances

Append-only acceptance evidence. Model: TermsAcceptance — see The evidence record.

Column Type Null Notes
billable_id unsignedBigInteger no FK larsasub_billables
billable_subscription_id unsignedBigInteger yes FK larsasub_billable_subscriptions; linked after creation succeeds
terms_identifier string(128) no Your document version identifier, verbatim. Not a foreign key
terms_locale string(12) yes The language rendition shown
context string(32) no What was accepted: subscribe (the terms), or withdrawal-consent (the waiver of the withdrawal right) — see Withdrawal consent
accepted_at dateTime no
client_hash string(64) yes Keyed one-way hash of IP address and user agent — never the raw values

Indexes: (billable_id, terms_identifier).

larsasub_notification_deliveries

One row per (notification, channel), so an interrupted delivery job does not re-mail a whole list. Model: NotificationDelivery — see Delivery records and duplicates.

Column Type Null Notes
notification_id uuid no Laravel's notification id
channel string(191) no
delivery_key uuid yes The queued delivery that produced the row
notification string(191) no The notification class, never its subject or body
recipient_type string yes The recipient as a model reference, never an address
recipient_id unsignedBigInteger yes
recipient_hash char(64) yes Keyed one-way hash of a model-less recipient's address
billable_id unsignedBigInteger yes FK larsasub_billables, cascade on delete
tenant_type string yes
tenant_id unsignedBigInteger yes
status string(16) no Default sending
attempts unsignedTinyInteger no Default 0
claimed_until timestamp yes Lease of the current attempt
sent_at timestamp yes
failure text yes The exception class of a failed attempt

Indexes: unique (notification_id, channel) — the deduplication key; (delivery_key, status); (billable_id, created_at); (status, created_at); created_at.

larsasub_model_logs

The per-model activity trail, written through peters-development/laravel-model-logger. Model: ModelLog — see HasModelLogs. This table has created_at only.

Column Type Null Notes
user_id unsignedBigInteger yes The acting user. Indexed
message text no The line, rendered in the log language
loggable_type, loggable_id morph no The model the line is about
meta json yes Includes the translation key and parameters the line was written with
created_at timestamp yes

Indexes: user_id; the loggable morph pair.

Three daily prune commands keep the bounded tables bounded:

Command Table Retention key Default
larsasub:prune-model-logs larsasub_model_logs retention.model_logs_days 730 days
larsasub:prune-notification-deliveries larsasub_notification_deliveries retention.notification_deliveries_days 365 days
larsasub:prune-dunning-cases larsasub_dunning_cases and their steps retention.dunning_days kept (null)

For the first two, null or 0 disables pruning. Finished reminder series are kept unless you set retention.dunning_days; a series whose charge could still get one is never pruned.

Keeping the schema in step

Since 0.37.1, existing migrations never change: every schema change ships as a new migration, and php artisan migrate applies it. Up to 0.37.1 the migrations were edited in place, so a database created from an earlier release can have nothing pending while it misses columns: php artisan migrate reports success, and the next scheduled run dies on Unknown column.

php artisan larsasub:doctor

The doctor checks the columns the models expect, not just the tables. On a development database the fix is php artisan migrate:fresh, or a manual ALTER — see the upgrade guide for the per-release notes.