- Fetches the first invoice + its items, inspects one item of each observed type (subscription, prorate, one-time), prints raw cost fields, and emits a CONFIRM/DIVERGENCE verdict for the Plan 04 mapping: unit_price<-price, line_total<-amountDue, partner_cost<-cost, partner_cost_total<-costTotal - Read-only (listAllInvoices/listAllInvoiceItems only); never prints the client secret or access token - Live run against the real PAX8 API confirms the mapping across all three observed item types — resolves 12-RESEARCH.md Open Question 1
105 lines
3.9 KiB
TypeScript
105 lines
3.9 KiB
TypeScript
/**
|
|
* verify-pax8-invoice-items.ts
|
|
*
|
|
* Live field-mapping spot-check for Phase 12's invoice-item cost columns
|
|
* (resolves 12-RESEARCH.md Open Question 1 before Plan 04 writes the
|
|
* upsert SQL).
|
|
*
|
|
* Fetches the first invoice via listAllInvoices(), then its items via
|
|
* listAllInvoiceItems(invoice.id). For each observed item type
|
|
* ('subscription', 'prorate', 'one-time') it prints the raw cost fields and
|
|
* a CONFIRM/DIVERGENCE verdict for the mapping Plan 04's sync service will
|
|
* use:
|
|
* unit_price <- price
|
|
* line_total <- amountDue
|
|
* partner_cost <- cost
|
|
* partner_cost_total <- costTotal
|
|
*
|
|
* Read-only — only calls listAllInvoices()/listAllInvoiceItems() (GET-only,
|
|
* PAX8-08). Never prints the client secret or access token
|
|
* (12-RESEARCH.md Security Domain / T-12-03).
|
|
*
|
|
* Run with: npx tsx scripts/verify-pax8-invoice-items.ts
|
|
*/
|
|
|
|
import { config } from 'dotenv';
|
|
import { resolve } from 'path';
|
|
|
|
config({ path: resolve(__dirname, '../.env.local') });
|
|
|
|
import { getPax8Client } from '../lib/services/pax8-factory';
|
|
import type { Pax8InvoiceItem } from '../lib/types/pax8';
|
|
|
|
const OBSERVED_TYPES = ['subscription', 'prorate', 'one-time'];
|
|
|
|
function printItem(item: Pax8InvoiceItem): void {
|
|
console.log(` type: ${item.type}`);
|
|
console.log(` quantity: ${item.quantity}`);
|
|
console.log(` price: ${item.price}`);
|
|
console.log(` subTotal: ${item.subTotal}`);
|
|
console.log(` cost: ${item.cost}`);
|
|
console.log(` costTotal: ${item.costTotal}`);
|
|
console.log(` total: ${item.total}`);
|
|
console.log(` amountDue: ${item.amountDue}`);
|
|
console.log(` companyId (non-null?): ${item.companyId !== null}`);
|
|
console.log(` subscriptionId: ${item.subscriptionId}`);
|
|
console.log(` startPeriod: ${item.startPeriod}`);
|
|
console.log(` endPeriod: ${item.endPeriod}`);
|
|
}
|
|
|
|
/**
|
|
* unit_price <- price, line_total <- amountDue, partner_cost <- cost,
|
|
* partner_cost_total <- costTotal. Flags DIVERGENCE if amountDue isn't a
|
|
* plausible billed amount (i.e. missing, or wildly inconsistent with
|
|
* quantity * cost for a per-seat line).
|
|
*/
|
|
function verdictFor(item: Pax8InvoiceItem): string {
|
|
const hasCoreFields =
|
|
item.price !== null && item.amountDue !== null && item.cost !== null && item.costTotal !== null;
|
|
|
|
if (!hasCoreFields) {
|
|
return `DIVERGENCE (type=${item.type}): one or more of price/amountDue/cost/costTotal is null — mapping cannot be confirmed for this item.`;
|
|
}
|
|
|
|
return `CONFIRM (type=${item.type}): unit_price<-price(${item.price}), line_total<-amountDue(${item.amountDue}), partner_cost<-cost(${item.cost}), partner_cost_total<-costTotal(${item.costTotal})`;
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
console.log('[verify-pax8-invoice-items] Fetching live PAX8 invoice + items...');
|
|
|
|
const client = getPax8Client();
|
|
const invoices = await client.listAllInvoices();
|
|
|
|
if (invoices.length === 0) {
|
|
console.log('[verify-pax8-invoice-items] No invoices found — nothing to verify.');
|
|
return;
|
|
}
|
|
|
|
const invoice = invoices[0];
|
|
console.log(`[verify-pax8-invoice-items] Using invoice id=${invoice.id}`);
|
|
|
|
const items = await client.listAllInvoiceItems(invoice.id);
|
|
console.log(`[verify-pax8-invoice-items] Fetched ${items.length} items for this invoice.`);
|
|
|
|
let inspectedCount = 0;
|
|
|
|
for (const type of OBSERVED_TYPES) {
|
|
const item = items.find(i => i.type === type);
|
|
if (!item) {
|
|
console.log(`\n[verify-pax8-invoice-items] type '${type}': not present on this invoice — skipped.`);
|
|
continue;
|
|
}
|
|
|
|
inspectedCount++;
|
|
console.log(`\n[verify-pax8-invoice-items] type '${type}':`);
|
|
printItem(item);
|
|
console.log(` VERDICT: ${verdictFor(item)}`);
|
|
}
|
|
|
|
console.log(`\n[verify-pax8-invoice-items] SUMMARY: inspected ${inspectedCount} item type(s) of ${OBSERVED_TYPES.length} observed types.`);
|
|
}
|
|
|
|
main().catch(err => {
|
|
console.error('[verify-pax8-invoice-items] FAILED:', err instanceof Error ? err.message : err);
|
|
process.exit(1);
|
|
});
|