📰 IT NachrichtenWhy It’s Difficult for Tech Companies to Rein In A.I.(12.09.2026 um 11:02 Uhr)
🔧 AI Nachrichten Etzioni on AI: What kids tell chatbots, but not you(04.09.2026 um 16:05 Uhr)
🔧 AI Nachrichten OpenAI Wants to Know if an AI Industry Slowdown Would Even Be Legal(11.09.2026 um 01:28 Uhr)
🔧 AI Nachrichten OpenAI puts Pro subscriptions on hold due to Astra demand(10.09.2026 um 22:59 Uhr)
📰 IT NachrichtenWhy It’s Difficult for Tech Companies to Rein In A.I.(12.09.2026 um 11:02 Uhr)
🔧 AI Nachrichten Etzioni on AI: What kids tell chatbots, but not you(04.09.2026 um 16:05 Uhr)
🔧 AI Nachrichten OpenAI Wants to Know if an AI Industry Slowdown Would Even Be Legal(11.09.2026 um 01:28 Uhr)
🔧 AI Nachrichten OpenAI puts Pro subscriptions on hold due to Astra demand(10.09.2026 um 22:59 Uhr)

🔧 Programmierung 🕛 vor 2 Monaten 3 Min Lesezeit
0

Stop Hardcoding Your Commercial Rules: Config-Driven Free Seats, Bulk Seats, and Tax

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht




TL;DR




  • Commercial rules — how many free seats a plan gets, whether you can buy seats in bulk, how tax is applied — drift constantly. Hardcoding them means a deploy every time Sales changes their mind.

  • I moved them behind a BillingProvider contract plus config/settings: the number is data, the behaviour is a swappable driver (manual vs Stripe).

  • Takeaway: if a value is a business decision, not an engineering one, it belongs in config or settings — not an if statement.



A "free tier of 3 seats" looks innocent until someone asks for 5. Then you grep the codebase, find the 3 in four places, miss one, and ship a billing bug. Today's work was about making sure that number — and a few friends — never live in code again.






The seam: one contract, two drivers



Billing has two modes: invoice-by-hand (early/enterprise customers) and Stripe (self-serve). Callers shouldn't care which. So everything goes through a contract:




CODE
interface BillingProvider
{
public function freeSeatDefault(): int;
public function purchaseSeats(Account $account, int $quantity): SeatPurchase;
public function appliesTax(): bool;
// ...
}






ManualBillingProvider and StripeBillingProvider implement it. The entitlement gate that asks "does this account have a seat available?" talks to the contract, never to Stripe directly. Same idea as a Filesystem disk — the caller says "store this", the driver decides where.






The free-seat default is config, not a constant






CODE
// config/billing.php
'seats' => [
'free_default' => env('BILLING_FREE_SEATS', 1),
'bulk_enabled' => true,
],






The manual provider reads the config; the Stripe provider can override per-plan from the subscription metadata. Customer-facing tweaks (a promo bumping free seats to 5) move to DB-backed settings so an admin flips it without a deploy. Code reads one path; the value's origin is an implementation detail.






Bulk seats: quantity is a first-class input



Adding seats one-by-one is fine until a customer onboards 40 people. Bulk just means purchaseSeats($account, 40) instead of a loop — the provider decides how to price and record it. The win is that the gate logic doesn't change: it still asks "seats_used < seats_total?".






Tax belongs to the provider, not the checkout view



Tax rules vary by region and change without warning. Putting tax math in a Blade view or controller is how you end up auditing checkout code. Instead, tax is a provider concern — the Stripe driver delegates to Stripe Tax (it already knows the customer's location and current rates), the manual driver returns "tax handled on the invoice". The app just asks appliesTax().




CODE
checkout ──► BillingProvider (contract)
├─ ManualBillingProvider ─► invoice handles tax
└─ StripeBillingProvider ─► Stripe Tax computes it









Test the rule, not the vendor



You don't need a live Stripe call to prove the policy. Bind a fake provider and assert behaviour:




CODE
it('grants the configured number of free seats', function () {
config(['billing.seats.free_default' => 3]);

$account = Account::factory()->create();

expect(app(BillingProvider::class)->freeSeatDefault())->toBe(3);
expect($account->availableSeats())->toBe(3);
});






The contract is the seam and the test boundary — swap in a manual provider, assert the entitlement gate counts seats correctly, done. No network, no flake.






Decision rule




























If the value… Put it in…
Never changes, is engineering-internal a constant
Changes per environment/deploy
config() + .env
A customer or admin should change live DB-backed settings
Is a swappable behaviour
a driver behind a contract


Free seats, bulk pricing, tax handling — every one of them is a business decision dressed up as a code value. Move them out, and the next "can we make it 5?" is a settings toggle, not a release.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
5 Quellen
Rockwell Automation FactoryTalk Activation Manager
3 Quellen
September-Patchday: Adobe schließt kritische Zero-Day-Lücke und 172 weitere
2 Quellen
Jetzt patchen! Angreifer attackieren JFrog Artifactory und machen sich zu Admins
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Stop Hardcoding Your Commercial Rules: Config-Driven Free Seats, Bulk Seats, and Tax

Thematisch verwandte Begriffe: Stop, Hardcoding, Your, Commercial · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...