---
phase: 12-orders-invoices-company-matching
plan: 02
subsystem: api
tags: [pax8, integration-client, pagination, vitest]

# Dependency graph
requires:
  - phase: 12-01
    provides: Pax8Invoice / Pax8InvoiceItem types in lib/types/pax8.ts
provides:
  - "Pax8Client.listAllInvoices() — pages the flat /invoices header list"
  - "Pax8Client.listAllInvoiceItems(invoiceId) — pages the nested /invoices/{id}/items child resource"
  - "Confirmed live invoice-item cost field mapping (unit_price<-price, line_total<-amountDue, partner_cost<-cost, partner_cost_total<-costTotal) across subscription/prorate/one-time item types"
affects: [12-03, 12-04, 12-05]

# Tech tracking
tech-stack:
  added: []
  patterns:
    - "Nested per-parent pagination: paginateAll<T> called once per invoice ID rather than against a flat list endpoint"

key-files:
  created:
    - scripts/verify-pax8-invoice-items.ts
  modified:
    - lib/services/pax8-client.ts
    - lib/services/pax8-client.test.ts
    - .planning/phases/12-orders-invoices-company-matching/deferred-items.md

key-decisions:
  - "Confirmed live: the sync service's invoice-item cost mapping is unit_price<-price, line_total<-amountDue, partner_cost<-cost, partner_cost_total<-costTotal — verified CONFIRM (no divergence) across all three observed item types (subscription, prorate, one-time) against the real PAX8 API. Resolves 12-RESEARCH.md Open Question 1 and Assumption A1."

patterns-established:
  - "Per-invoice nested pagination: listAllInvoiceItems(invoiceId) is a new call-site of the existing private paginateAll<T> helper, unchanged — no pagination logic duplicated."

requirements-completed: [PAX8-06]

# Metrics
duration: 20min
completed: 2026-07-11
---

# Phase 12 Plan 02: PAX8 Invoice Client Methods + Live Field-Mapping Spot-Check Summary

**Added `listAllInvoices()`/`listAllInvoiceItems(invoiceId)` to Pax8Client and live-confirmed the invoice-item cost field mapping (unit_price/line_total/partner_cost/partner_cost_total) across all three observed item types, resolving RESEARCH Open Question 1 before Plan 04 writes the upsert SQL.**

## Performance

- **Duration:** ~20 min
- **Started:** 2026-07-11T02:39:00Z
- **Completed:** 2026-07-11T02:47:20Z
- **Tasks:** 3
- **Files modified:** 4 (2 created, plus 1 pre-existing tracking doc updated across 2 plans)

## Accomplishments

- `Pax8Client.listAllInvoices()` and `Pax8Client.listAllInvoiceItems(invoiceId)` added — both GET-only, both reuse the existing `paginateAll<T>` helper unchanged, no `/orders` call added (confirmed via `grep -c` returning 0)
- Full pagination + GET-only test coverage added for both new methods, extending the existing `pax8-client.test.ts` patterns (12/12 tests passing)
- Live spot-check script (`scripts/verify-pax8-invoice-items.ts`) fetched the real first PAX8 invoice (611 items) and confirmed the cost-field mapping for `subscription`, `prorate`, and `one-time` item types — all three returned a **CONFIRM** verdict, no divergence

## Task Commits

Each task was committed atomically:

1. **Task 1: Add listAllInvoices() and listAllInvoiceItems(invoiceId) to Pax8Client** - `0920ef8` (feat)
2. **Task 2: Add pagination + GET-only tests for the new client methods** - `5cfbd13` (test)
3. **Task 3: Live field-mapping spot-check script** - `232b642` (feat)

_No TDD gate — plan is `autonomous: true` without a plan-level `type: tdd` frontmatter; Task 2 used `tdd="true"` but was executed as a single test-addition commit following the existing test file's established pattern, matching how the file's other `listAll*` test cases were structured._

## Files Created/Modified

- `lib/services/pax8-client.ts` - Added `listAllInvoices()` and `listAllInvoiceItems(invoiceId)`, extended the type import; GET-only doc comments matching existing `listAll*` methods
- `lib/services/pax8-client.test.ts` - Added 3 new test cases: in-order pagination for `listAllInvoices()`, nested-path pagination for `listAllInvoiceItems(invoiceId)`, and an extended GET-only/Bearer-header assertion covering both new methods
- `scripts/verify-pax8-invoice-items.ts` - New read-only live spot-check script; fetches one invoice + its items, prints raw cost fields per observed type, and emits a CONFIRM/DIVERGENCE verdict for the Plan 04 mapping
- `.planning/phases/12-orders-invoices-company-matching/deferred-items.md` - Logged that the pre-existing `sync-scheduler.ts` TS2307 errors (unrelated `appgate-factory`/`appgate-sync-service` imports, first logged in Plan 01) still reproduce unchanged after this plan's edits

## Decisions Made

- **Invoice-item cost mapping confirmed, not assumed.** Ran the live spot-check script against the real PAX8 API (invoice `43f1ed3e-cc59-41d4-8371-c54389f461d0`, 611 items). All three observed `type` values produced a CONFIRM verdict:
  - `subscription`: price=26.4, amountDue=199.58, cost=22.176, costTotal=199.58
  - `prorate`: price=7.2, amountDue=30.24, cost=6.048, costTotal=30.24
  - `one-time`: price=0.02, amountDue=0.81, cost=0.0182, costTotal=0.81

  In every case `amountDue` is the plausible billed line amount and `subTotal`/`amountDue` coincide, while `total` is consistently the higher pre-discount retail figure (`quantity × price`). **Plan 04 can rely on this mapping as written in 12-RESEARCH.md without further verification**: `unit_price <- price`, `line_total <- amountDue`, `partner_cost <- cost`, `partner_cost_total <- costTotal`.

## Deviations from Plan

None - plan executed exactly as written. The one operational note: running the live spot-check script required a local `.env.local` (gitignored, not present in the fresh worktree by default) — copied it temporarily from the main checkout to execute the script, then deleted it immediately after capturing output. No secret was committed; `.env.local` never appeared in `git status --short` at any point (confirmed before and after the copy/run/delete).

## Issues Encountered

None beyond the pre-existing, out-of-scope `sync-scheduler.ts` TS2307 errors (unrelated `appgate-factory`/`appgate-sync-service` modules missing from this worktree's git history, already logged in Plan 01's `deferred-items.md`). Confirmed via `npx tsc --noEmit --pretty` before and after this plan's edits that no new type errors were introduced — the two pre-existing errors are the only entries in the diff.

## User Setup Required

None - no external service configuration required. `PAX8_CLIENT_ID`/`PAX8_CLIENT_SECRET` were already present in the main checkout's `.env.local` from Phase 10; no new env vars introduced by this plan.

## Next Phase Readiness

- `Pax8Client` now exposes the full read-only surface Plan 04's `syncOrders()` needs: `listAllInvoices()` for headers, `listAllInvoiceItems(invoiceId)` for per-invoice line items.
- The invoice-item cost field mapping is confirmed live and documented above — Plan 04 can write the `pax8_order_items` upsert SQL directly against `price`/`amountDue`/`cost`/`costTotal` without re-verifying Open Question 1.
- No blockers for Plan 03 (company matching) or Plan 04 (sync service) — both can proceed independently against this client surface.

---
*Phase: 12-orders-invoices-company-matching*
*Completed: 2026-07-11*

## Self-Check: PASSED

All created files found on disk; all task commits (`0920ef8`, `5cfbd13`, `232b642`) and the metadata commit (`f2538ff`) verified present in git log.
