Configuration
After publishing the config file with php artisan vendor:publish --tag=tashil-config, you'll find
everything in config/tashil.php. This page walks through each section in plain language. You can
ignore all of it to start — the defaults are production-sensible — and return when you need to
change something specific.
Database
Controls where Tashil's tables live and what they're called.
'database' => [
// null = use your app's default connection. Set a named connection
// (defined in config/database.php) to isolate Tashil on its own DB.
'connection' => env('TASHIL_DB_CONNECTION', null),
// Prepended to every table name below.
'prefix' => 'tashil_',
// Rename individual tables only if you have a naming conflict.
'tables' => [
'packages' => 'packages',
'features' => 'features',
'package_feature' => 'package_feature',
'subscriptions' => 'subscriptions',
'subscription_features' => 'subscription_features',
'feature_usages' => 'feature_usages',
'usage_logs' => 'usage_logs',
'subscription_events' => 'subscription_events',
'invoices' => 'invoices',
'transactions' => 'transactions',
],
],Isolating Tashil on its own database
Set TASHIL_DB_CONNECTION=tashil and define a matching connection in config/database.php. Every
model, repository, and the migration honor this connection — so you can keep billing data on a
separate schema or server without touching any code.
Billing model
The single most important setting. activate_on_payment decides whether a newly created priced
plan grants access immediately or only after its first invoice is paid.
'billing' => [
// The CREATION-TIME default for a package's `requires_payment` flag.
// true (default) → new priced plans subscribe as `Pending` and gain
// access only when the first invoice is paid.
// false → new plans use the legacy "access first, bill later".
'activate_on_payment' => env('TASHIL_ACTIVATE_ON_PAYMENT', true),
// Due window (days) for the initial invoice of a gated subscription.
'initial_invoice_due_days' => env('TASHIL_INITIAL_INVOICE_DUE_DAYS', 1),
// Skip issuing a proration invoice below this amount (avoids dust invoices).
'min_proration_amount' => env('TASHIL_MIN_PRORATION', 0.50),
],This is a creation-time default, not a runtime switch
activate_on_payment only seeds a package's requires_payment flag when the package is created
and you didn't set it explicitly. At runtime, the package's own stored flag is authoritative.
Flipping this config later affects only packages created afterward — existing rows keep their flag.
See Billing Lifecycle for the full reasoning.
Trials
'trial' => [
// How many days before trial_ends_at to dispatch the TrialEnding event.
'warn_days' => env('TASHIL_TRIAL_WARN_DAYS', 3),
// Optional grace window after a trial expires (your app enforces it).
'grace_days' => env('TASHIL_TRIAL_GRACE_DAYS', 0),
],See Trials for the full lifecycle.
Renewal
What happens when a renewal comes due but a previous invoice is still unpaid.
'renewal' => [
// Policy when an unpaid invoice already exists at renewal time:
// 'cancel' → grace-cancel the subscription (default)
// 'skip' → do nothing, retry next run
// 'extend_grace' → push current_period_end forward and try again
'on_pending_invoice' => env('TASHIL_RENEWAL_ON_PENDING_INVOICE', 'cancel'),
// Days added when policy is 'extend_grace'.
'grace_days' => env('TASHIL_RENEWAL_GRACE_DAYS', 3),
// Cap on 'extend_grace' pushes — stops an unpaid invoice from
// extending access forever.
'max_grace_extensions' => env('TASHIL_RENEWAL_MAX_GRACE_EXTENSIONS', 3),
],Dunning
The failed-payment recovery cycle. Tashil owns the state machine and schedule; your app performs the actual retry charge by listening to the events it fires.
'dunning' => [
// Run the dunning lifecycle (tashil:process-dunning).
'enabled' => env('TASHIL_DUNNING_ENABLED', true),
// Days after due_date at which each retry milestone fires.
'retry_days' => [1, 3, 5],
// Suspend (cut access) once the attempt count reaches this.
'suspend_after_attempts' => env('TASHIL_DUNNING_SUSPEND_AFTER', 3),
// Expire a still-unpaid suspended subscription this many days later.
'cancel_after_suspend_days' => env('TASHIL_DUNNING_CANCEL_AFTER', 7),
// Soft dunning: keep access during the PastDue retry window.
// Suspended never has access regardless of this flag.
'keep_access_while_past_due' => env('TASHIL_PASTDUE_KEEP_ACCESS', true),
],The escalation path:
Read the full walkthrough in Billing Lifecycle.
Scheduler
'schedule' => [
// Set false to skip auto-registration and wire commands yourself.
'enabled' => env('TASHIL_SCHEDULE_ENABLED', true),
// Override the cron expression per command.
'overrides' => [
// 'tashil:expire-trials' => '*/15 * * * *',
],
],See Scheduler & Jobs for every command and its default cadence.
Events
'events' => [
// When true (default), domain events dispatch AFTER the DB commit using
// DB::afterCommit(), so listeners never observe a half-written state.
'async' => env('TASHIL_EVENTS_ASYNC', true),
],Invoice & transaction numbering
Both invoice numbers and transaction ids are stamped automatically by an observer. The format uses a small token vocabulary.
'invoice' => [
'prefix' => 'INV',
'format' => '#-YYMMDD-NNNNNN',
'generator' => Foysal50x\Tashil\Services\Generators\InvoiceNumberGenerator::class,
],
'transaction' => [
'prefix' => 'TXN',
'format' => '#-YYMMDD-NNNNNNAA',
'generator' => Foysal50x\Tashil\Services\Generators\TransactionIdGenerator::class,
],Format tokens:
| Token | Meaning |
|---|---|
# | The prefix above |
YY / MM / DD | Year / month / day, 2 digits each |
N | Random digit (0–9) |
S | Random letter (A–Z) |
A | Random alphanumeric (0–9, A–Z) |
For example #-YYMMDD-NNNNNN renders to INV-260522-849021. To plug in your own scheme, see
Extending Tashil.
Currency
// ISO 4217 code used when creating packages and issuing invoices.
'currency' => env('TASHIL_CURRENCY', 'USD'),Middleware aliases
The names under which Tashil's three route middleware register on boot. Set one to null to skip
registering it (e.g. if the name collides with an existing alias in your app).
'middleware' => [
'aliases' => [
'subscribed' => 'subscribed',
'plan' => 'plan',
'feature' => 'feature',
],
],See Middleware & Blade.
Caching
A dedicated Redis store named tashil is auto-registered so cache traffic is isolated from your
app's main store.
'redis' => [
'host' => env('TASHIL_REDIS_HOST', env('REDIS_HOST', '127.0.0.1')),
'password' => env('TASHIL_REDIS_PASSWORD', env('REDIS_PASSWORD', null)),
'port' => env('TASHIL_REDIS_PORT', env('REDIS_PORT', 6379)),
'database' => env('TASHIL_REDIS_DB', 5),
],
'cache' => [
'prefix' => env('TASHIL_CACHE_PREFIX', 'tashil_cache:'),
'ttl' => 3600, // seconds
'enabled' => env('TASHIL_CACHE_ENABLED', true),
],Only the cold catalog (plans, features) and analytics aggregates flow through the cache. Hot, frequently-mutating tables — the event log, feature snapshots, and usage counters — always bypass it for correctness.
Environment variable reference
Every tunable, in one place:
| Variable | Default | Controls |
|---|---|---|
TASHIL_DB_CONNECTION | null | Database connection name |
TASHIL_ACTIVATE_ON_PAYMENT | true | Activate-on-payment creation default |
TASHIL_INITIAL_INVOICE_DUE_DAYS | 1 | Initial invoice due window |
TASHIL_MIN_PRORATION | 0.50 | Minimum proration invoice amount |
TASHIL_TRIAL_WARN_DAYS | 3 | Days before expiry to warn |
TASHIL_TRIAL_GRACE_DAYS | 0 | Post-trial grace window |
TASHIL_RENEWAL_ON_PENDING_INVOICE | cancel | Renewal-with-unpaid-invoice policy |
TASHIL_RENEWAL_GRACE_DAYS | 3 | extend_grace push length |
TASHIL_RENEWAL_MAX_GRACE_EXTENSIONS | 3 | extend_grace cap |
TASHIL_DUNNING_ENABLED | true | Run dunning |
TASHIL_DUNNING_SUSPEND_AFTER | 3 | Attempts before suspension |
TASHIL_DUNNING_CANCEL_AFTER | 7 | Days suspended before expiry |
TASHIL_PASTDUE_KEEP_ACCESS | true | Soft vs hard dunning |
TASHIL_SCHEDULE_ENABLED | true | Auto-register scheduled jobs |
TASHIL_EVENTS_ASYNC | true | Dispatch events after commit |
TASHIL_CURRENCY | USD | Default currency |
TASHIL_CACHE_ENABLED | true | Enable the cache layer |
TASHIL_CACHE_PREFIX | tashil_cache: | Cache key prefix |
TASHIL_REDIS_HOST / _PORT / _DB | 127.0.0.1 / 6379 / 5 | Redis connection |