Move a customer between plans

Move-plan preflight, then apply, one customer or in bulk.

View .md

Goal

Move Acme Corp from its current package to growth at its next renewal boundary, without an unplanned invoice.

A move is two writes: updateEndDate on the current contract at the cutover, then a successor contract on the target package with a RENEWAL transition from the old one. Balances carry over by each grant's rollover_fraction. See Contracts.

Preview

metro contract move-plan preflight --to=growth --ingest-alias=acme-corp \
  --cutover-mode=renewal-boundary --sandbox --out=move.json

The cutover is one of:

FlagCutover
--cutover-mode=renewal-boundary (default)The next monthly boundary after the last paid plan invoice, or the contract start
--cutover-mode=next-hourThe next UTC hour
--cutover-mode=current-hourThe current UTC hour. May finalize the plan fee immediately.
--cutover=<ISO>An exact UTC hour

Pass --from=<alias> to assert which package the customer is on now. Preflight then stops if the current package doesn't carry that alias.

metro contract move-plan preflight (sandbox, key sha256:80f0714120b8) - read-only, nothing written to Metronome.
  contract 00000000-0000-4000-8000-000000000002.ending_before: null -> "2027-01-01T00:00:00.000Z"
  successor contract: null -> {"package_id":"00000000-0000-4000-8000-000000000104","alias":"growth","starting_at":"2027-01-01T00:00:00.000Z","transition":"RENEWAL"}
Steps: POST /v1/contracts/updateEndDate ; POST /v1/contracts/create
Payload hash: sha256:...

Preflight needs one active contract to move from: the single open-ended one, or the only active one. An active trial qualifies with warning MTR-MOVE-TRIAL-001, because apply truncates it at the cutover. An expired customer doesn't; create a fresh contract instead. Preflight pins the target package live at the cutover, warns about balances that won't roll over, and warns MTR-TIME-002 when the cutover isn't in the future.

Credits on the new plan

move-plan no longer takes a target credit amount. Grant credits on the successor afterwards with metro ledger-adjustment preflight --grant=credit, through the same ladder. See Credits and commits.

Confirm

Show the human the cutover hour, the old contract's new end, the target package, any rollover warning and the hash. Then dry-run:

metro contract move-plan apply --file move.json --sandbox --confirm-hash sha256:...

Apply

metro contract move-plan apply --file move.json --sandbox --confirm-hash sha256:... \
  --apply --confirm-live-apply=I_UNDERSTAND_THIS_WILL_WRITE_LIVE_METRONOME_SANDBOX

In bulk

Write a roster: one customer ID per line. Lines starting with #, and a customer_id header, are skipped.

metro contract move-plan bulk-preflight --to=growth --roster-file=roster.csv \
  --cutover-mode=renewal-boundary --sandbox --out-dir=plans/bulk-move
Planned 40/40 moves (0 failed). Manifest: plans/bulk-move/manifest.json
Manifest hash: sha256:...
Canary: metro contract move-plan bulk-apply --file plans/bulk-move/manifest.json --sandbox --confirm-hash sha256:... --limit=1 --apply --confirm-live-apply=I_UNDERSTAND_THIS_WILL_WRITE_LIVE_METRONOME_SANDBOX

Run the canary line first: --limit=1 moves one customer. Read it back, then run the same command without --limit. bulk-apply stops at the first failure, waits --delay-ms (default 300) between moves, and refuses a manifest edited after preflight.

Readback

Applied (sandbox).
Read-back (expected vs actual):
  ok   predecessor.ending_before: expected "2027-01-01T00:00:00.000Z" actual "2027-01-01T00:00:00.000Z"
  ok   successor exists: expected true actual true
  ok   successor.starting_at: expected "2027-01-01T00:00:00.000Z" actual "2027-01-01T00:00:00.000Z"
  ok   successor.package_id: expected "00000000-0000-4000-8000-000000000104" actual "00000000-0000-4000-8000-000000000104"
  ok   alias growth resolves to successor package at cutover: expected "00000000-0000-4000-8000-000000000104" actual "00000000-0000-4000-8000-000000000104"

Illustrative IDs; the check names are exact. Any FAIL exits 3. Confirm from the customer's side with metro ops who "Acme Corp" --sandbox.

On this page