feat(pax8): add safe ordering and billing-truth workflow#2501
Merged
Conversation
Deploying breeze with
|
| Latest commit: |
78bda43
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://21647eb2.breeze-9te.pages.dev |
| Branch Preview URL: | https://toddhebebrand-pax8-ordering.breeze-9te.pages.dev |
ToddHebebrand
marked this pull request as ready for review
July 16, 2026 04:08
ToddHebebrand
force-pushed
the
ToddHebebrand/pax8-ordering
branch
from
July 16, 2026 04:17
d9a6036 to
0a93eb8
Compare
Adds the write path to the Pax8 integration: place, change, and cancel subscriptions from a customer's org page or as the fulfillment step of an approved quote. Establishes Breeze as the source of truth for licensing and billing — Pax8's Subscription.quantity is stale and does not match what Pax8 invoices, so the existing nightly quantity push into contract_lines is demoted to drift detection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ten tasks, TDD throughout. Also corrects the spec: quote-acceptance stages the order INSIDE the accept transaction (as Phase 5, alongside contract creation), not in the post-commit tail — the tail exists only for Redis/BullMQ side effects, and a staged order referencing rolled-back contract lines would be unfixable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pax8 subscription quantity can differ from what Pax8 invoices. Keep the Breeze order ledger authoritative, persist quantity evidence, and expose read-only tenant-scoped drift instead of mutating contract billing during nightly sync.
Treat legacy snapshots as unknown, preserve the last genuine observation when Pax8 omits quantity, and expose the evidence bit to subscription consumers.
added 13 commits
July 15, 2026 22:21
ToddHebebrand
force-pushed
the
ToddHebebrand/pax8-ordering
branch
from
July 16, 2026 04:21
0a93eb8 to
78bda43
Compare
ToddHebebrand
added a commit
that referenced
this pull request
Jul 16, 2026
…rtion (#2563) ## Summary Fixes the red `Test API` CI job on `main` (`quoteService.clone.test.ts`). ## Root cause **Not a product bug** — a stale mock in `quoteService.clone.test.ts` after two independently-merged PRs interacted: - #2501 (pax8 ordering) added two DB selects inside `getQuote()`: a `pax8OrderSummary` lookup and a conditional `pax8OrderLineSummary` lookup, inserted between the existing `lines` select and `getQuote()`'s return. - #2535 (quote cloning) added `cloneQuote()`, which calls `getQuote()` and then issues its own `quoteImages` select immediately after. `quoteService.clone.test.ts` mocks `db.select()` with a shared FIFO queue of canned results, and pushed exactly 4 entries (`quote`, `blocks`, `lines`, `images`) — the correct count *before* #2501 landed. With the extra `pax8OrderSummary` select now spliced into `getQuote()`, the queue's "images" entry was consumed by the `pax8OrderSummary` destructure instead, so the real `quoteImages` select fell through to the queue's empty-array default. `imageIds` ended up an empty `Map`, so remapping the image block's `imageId` legitimately found nothing and `cloneQuote` correctly threw `QuoteServiceError('Quote image could not be cloned', 409, 'IMAGE_NOT_FOUND')`. Confirmed this can't happen in production: real Postgres queries are scoped by real `WHERE` clauses per table and can't cross-contaminate the way the mock's shared queue does. ## Fix `apps/api/src/services/quoteService.clone.test.ts`: push one extra empty result for the `pax8OrderSummary` select (i.e. "no staged Pax8 order for this quote"), which also correctly short-circuits the conditional `pax8OrderLineSummary` select exactly as the real code does. This makes the mock's select order match `getQuote()`'s actual current select order. ## Verification - `pnpm vitest run src/services/quoteService.clone.test.ts src/services/quoteService.test.ts src/services/quoteLifecycle.test.ts` — 31/31 passing (was 1 failing). - `pnpm vitest run src/services/` (full suite, 5825 tests) — all green except one unrelated pre-existing timing flake in `encryptedColumnRegistry.test.ts` (5000ms scrypt timeout), confirmed to pass in isolation and untouched by this change. - Node 22.20.0, matching `.nvmrc`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Todd Hebebrand <todd@lanternops.io> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Why
Pax8's reported subscription quantity can be stale and may not match what Pax8 invoices. Automatically copying it into Breeze contract lines can bill customers incorrectly. This change routes money-bearing actions through an auditable order ledger, preserves approved quote membership, fails closed on ambiguous vendor outcomes, and keeps Pax8 quantity data as drift evidence only.
Validation
E2E note
Authenticated live browser/API E2E was unavailable in this worktree because the required E2E credentials and matching web/API/Redis stack were not present. No real Pax8 vendor write was attempted. The scoped component/API and fresh-database evidence is recorded in
docs/testing/FEATURE_TEST_LOG.md.