CluPilotCloud/app/Provisioning/Steps/Customer
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
..
CloneVirtualMachine.php fix(engine-b): clean re-clone on lost task ref; idempotent firewall rules 2026-07-25 13:04:48 +02:00
CompleteProvisioning.php fix(engine-b): always scrub credentials once delivered; idempotent monitoring lookup 2026-07-25 12:46:34 +02:00
ConfigureCloudInit.php fix(engine-b): apply plan CPU/RAM; idempotent backup + monitoring registration 2026-07-25 12:42:10 +02:00
ConfigureDnsAndTls.php fix(engine-b): idempotent local DNS record (firstOrCreate on record_id) 2026-07-25 13:08:13 +02:00
ConfigureNetwork.php fix(engine-b): address Codex round 5 (capacity release, activate-last, guest-ip poll, email unique) 2026-07-25 12:19:31 +02:00
ConfigureNextcloud.php feat(engine-b): 15-step customer pipeline + DNS/Traefik services 2026-07-25 11:50:26 +02:00
CreateCustomerAdmin.php fix(engine-b): real backup/monitoring registration + OC_PASS scoping 2026-07-25 12:33:45 +02:00
CustomerStep.php fix(billing): a paid order opens a contract, and provisioning obeys it 2026-07-26 11:10:00 +02:00
DeployApplicationStack.php fix(engine-b): address Codex round 6 (crash-window idempotency) 2026-07-25 12:23:28 +02:00
RegisterBackup.php fix(engine-b): clone recovery waits for lock; register rows before breadcrumb 2026-07-25 12:44:22 +02:00
RegisterMonitoring.php fix(monitoring): cap monitoring retries at the run budget; dependency-free liveness probe 2026-07-25 20:19:32 +02:00
ReserveResources.php fix(billing): a paid order opens a contract, and provisioning obeys it 2026-07-26 11:10:00 +02:00
RunAcceptanceChecks.php feat(monitoring): Uptime Kuma support via a REST bridge + resilient monitoring policy 2026-07-25 20:04:58 +02:00
StartVirtualMachine.php feat(engine-b): 15-step customer pipeline + DNS/Traefik services 2026-07-25 11:50:26 +02:00
ValidateOrder.php fix(billing): a paid order opens a contract, and provisioning obeys it 2026-07-26 11:10:00 +02:00
WaitForGuestAgent.php fix(engine): don't reset started_at on poll so step deadlines accumulate 2026-07-25 12:15:26 +02:00