CluPilotCloud/database/factories
nexxo 52b41bb0d5 fix(billing): a paid order opens a contract, and provisioning obeys it
The pipeline re-resolved config('provisioning.plans') by order.plan, so the
subscription snapshot protected a customer's price but not their machine:
shrinking a plan resized an existing customer's VM on its next run. Nothing
created a subscription either, so closing this meant opening the contract at
purchase and pointing provisioning at it.

- OpenSubscription freezes the catalogue onto a subscription when a checkout
  is paid; StartCustomerProvisioning calls it inside the order transaction.
- CustomerStep::plan() reads the frozen snapshot. ValidateOrder and
  ReserveResources fail closed with no_subscription rather than falling back
  to the catalogue, which is the bug itself.
- template_vmid joins the snapshot so a re-clone cannot pick up a blueprint
  published after the sale. Deliberately outside FROZEN: it is how we build
  the machine, not a term the customer is owed, and a dead template must be
  replaceable without cancelling a contract.
- TrafficMeter reads the allowance off the contract too — cutting a plan's
  traffic was otherwise enough to start throttling someone who bought more.
- The migration backfills contracts for orders that already bought something,
  reconstructed from what was actually delivered where an instance exists,
  and adopts an existing order-less contract instead of opening a second.
  Orders paid in a currency the catalogue cannot price get none, matching the
  checkout path.

price_cents stays the catalogue's NET price, which is what PlanChange
prorates against — not Order::amount_cents, which holds Stripe's GROSS total.
Reconciling the two belongs to the proof register and Stripe (phases 4/5).

Also pins STRIPE_WEBHOOK_SECRET blank in phpunit.xml: the operator's real
secret was reaching the suite from .env and rejecting every unsigned test
payload, which is why 7 webhook tests failed before any of this.

Verified in the browser: with team traffic cut from 3000 to 500 GB in the
catalogue, the customer's portal still shows 3 TB.

373 tests green. Codex review clean after three rounds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 11:10:00 +02:00
..
CustomerFactory.php fix: seat limit + suspend guard per Codex; harden factory email uniqueness 2026-07-25 14:53:15 +02:00
DatacenterFactory.php test: widen DatacenterFactory code space (fix faker unique() pool exhaustion) 2026-07-25 14:23:49 +02:00
HostFactory.php fix(admin): confirm maintenance delivery via MessageSent (retryable until sent); guard resend/cancel on derived state; fix flaky non-unique host wg_ip 2026-07-25 16:14:01 +02:00
InstanceFactory.php feat(engine-b): customer domain models + migrations 2026-07-25 11:38:01 +02:00
MaintenanceWindowFactory.php feat(admin): maintenance windows — schedule once, assign many hosts, notify 2026-07-25 15:53:50 +02:00
OrderFactory.php fix(billing): a paid order opens a contract, and provisioning obeys it 2026-07-26 11:10:00 +02:00
ProvisioningRunFactory.php feat(engine): core provisioning data model + hosts 2026-07-25 09:49:43 +02:00
SeatFactory.php fix: seat limit + suspend guard per Codex; harden factory email uniqueness 2026-07-25 14:53:15 +02:00
SubscriptionFactory.php feat(billing): immutable subscription snapshots and pro-rata plan changes 2026-07-26 09:49:48 +02:00
UserFactory.php feat(admin): staff RBAC (spatie) + admin settings page 2026-07-25 15:47:42 +02:00
VpnPeerFactory.php feat(vpn): ownership, a Developer role, and password-gated config retrieval 2026-07-25 22:31:54 +02:00