{
  "slug": "invoice-payment-ux",
  "spec_file": "docs/specs/2026-05-31-invoice-payment-ux.md",
  "findings": [
    {
      "id": "invoice-payment-ux-001",
      "severity": "P1",
      "type": "missing",
      "summary": "No /pay/[invoiceToken] payment page in zync-www",
      "spec_ref": "Payment Page / Foundation Deltas",
      "code_ref": "NONE",
      "evidence": "Glob apps/zync-www/src/pages/pay/** returns 0 files; spec requires hybrid SSR page at apps/zync-www/src/pages/pay/[invoiceToken].astro.",
      "repro": "Open https://zync.is/pay/<token> — route does not exist."
    },
    {
      "id": "invoice-payment-ux-002",
      "severity": "P1",
      "type": "missing",
      "summary": "No /pay/[invoiceToken]/return return-state page",
      "spec_ref": "Return URL Page",
      "code_ref": "NONE",
      "evidence": "No return.astro under apps/zync-www/src/pages/pay; ReturnStateIsland component absent from codebase.",
      "repro": ""
    },
    {
      "id": "invoice-payment-ux-003",
      "severity": "P1",
      "type": "missing",
      "summary": "PaymentPageIsland and invoice preview UX not implemented",
      "spec_ref": "Payment Page — Invoice preview",
      "code_ref": "NONE",
      "evidence": "Grep PaymentPageIsland, InvoicePreview, NotPayableNotice across repo returns no matches.",
      "repro": ""
    },
    {
      "id": "invoice-payment-ux-004",
      "severity": "P1",
      "type": "missing",
      "summary": "POST /api/invoices/:id/payment/session endpoint absent",
      "spec_ref": "API Endpoints",
      "code_ref": "NONE",
      "evidence": "Grep payment/session across repo returns no API route; spec gateway session creation endpoint not present.",
      "repro": ""
    },
    {
      "id": "invoice-payment-ux-005",
      "severity": "P1",
      "type": "missing",
      "summary": "GET /api/invoices/:id/payment/status poll endpoint absent",
      "spec_ref": "API Endpoints / Return States pending",
      "code_ref": "NONE",
      "evidence": "Grep payment/status in apps/zync-api returns no matches.",
      "repro": ""
    },
    {
      "id": "invoice-payment-ux-006",
      "severity": "P1",
      "type": "missing",
      "summary": "GET /api/pay/:invoiceToken/invoice resolver absent",
      "spec_ref": "API Endpoints",
      "code_ref": "NONE",
      "evidence": "Grep /api/pay in apps/zync-api finds only /api/payouts contractor routes; no token-gated pay invoice resolver.",
      "repro": ""
    },
    {
      "id": "invoice-payment-ux-007",
      "severity": "P1",
      "type": "missing",
      "summary": "Portal invoice list has no Pay Now button",
      "spec_ref": "Entry Points — Portal My Invoices",
      "code_ref": "apps/zync-app/src/routes/portal/invoices.tsx:1",
      "evidence": "PortalInvoicesPage lists invoices with download only; grep Pay Now in portal routes returns no matches.",
      "repro": "Log into portal; TAX_ISSUED invoice shows no Pay Now CTA."
    },
    {
      "id": "invoice-payment-ux-008",
      "severity": "P1",
      "type": "missing",
      "summary": "Payment receipt email on invoice.paid not implemented",
      "spec_ref": "Receipt Email",
      "code_ref": "NONE",
      "evidence": "Grep sendPaymentReceiptEmail and apps/zync-api/src/email/payment-receipt.ts returns only plan docs, no handler.",
      "repro": ""
    },
    {
      "id": "invoice-payment-ux-009",
      "severity": "P1",
      "type": "missing",
      "summary": "verifyInvoiceToken helper absent; token cannot be resolved",
      "spec_ref": "Architecture Decisions — invoiceToken HMAC",
      "code_ref": "packages/payments/src/invoice-payment-token.ts:43",
      "evidence": "Module exports signInvoicePaymentToken + buildPaymentLinkUrl only; no verifyInvoiceToken or timingSafeEqual compare.",
      "repro": ""
    },
    {
      "id": "invoice-payment-ux-010",
      "severity": "P2",
      "type": "divergent",
      "summary": "Payment links point to app.zync.is not zync.is/pay",
      "spec_ref": "Entry Points — Direct Payment Link",
      "code_ref": "apps/zync-api/src/routes/invoices/payment-link.ts:177",
      "evidence": "buildPaymentLinkUrl uses APP_BASE_URL default https://app.zync.is; spec requires zync.is/pay/{invoiceToken} on zync-www.",
      "repro": "Generate payment link; URL host is app.zync.is/pay/... not zync.is/pay/..."
    },
    {
      "id": "invoice-payment-ux-011",
      "severity": "P2",
      "type": "divergent",
      "summary": "Token is raw HMAC digest not payload.mac format",
      "spec_ref": "invoiceToken signing (plan Task 1)",
      "code_ref": "packages/payments/src/invoice-payment-token.ts:51",
      "evidence": "signInvoicePaymentToken returns base64url(mac) only; plan/spec expect base64url(invoiceId:tenantId).base64url(mac) for stateless decode.",
      "repro": ""
    },
    {
      "id": "invoice-payment-ux-012",
      "severity": "P3",
      "type": "divergent",
      "summary": "zync-www uses output server not hybrid",
      "spec_ref": "Foundation Deltas — output hybrid",
      "code_ref": "apps/zync-www/astro.config.mjs:6",
      "evidence": "astro.config.mjs sets output:'server'; spec references output:'hybrid' for pay pages.",
      "repro": ""
    }
  ],
  "summary": { "P0": 0, "P1": 9, "P2": 2, "P3": 1, "total": 12 }
}
