Skip to content

feat(web): inFakt FE plugin — connection setup + invoice detail + KOR correction#1300

Merged
piotrswierzy merged 31 commits into
mainfrom
1282-infakt-fe-plugin
Jul 2, 2026
Merged

feat(web): inFakt FE plugin — connection setup + invoice detail + KOR correction#1300
piotrswierzy merged 31 commits into
mainfrom
1282-infakt-fe-plugin

Conversation

@norbert-kulus-blockydevs

@norbert-kulus-blockydevs norbert-kulus-blockydevs commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Frontend half of the inFakt accounting integration (epic #1279). Backend capability surface (InvoicingPort + RegulatoryStatusReader + CorrectionIssuer) is code-complete on PR #1292 (merged) and PR #1293 (registration + webhook, stacked on #1292) — this PR consumes that contract.

  • Guided connection setup: /connections/new/infakt — name + API key + optional sandbox baseUrl override, mirroring the Erli setup-flow pattern (single credential, "Test connection" affordance after create). This corrects issue feat(infakt): FE plugin – connection setup + invoice detail section #1282's original text, which implied wiring StructuredConfigSection into the generic inline create form — verified against the real PlatformPicker/CreateConnectionForm source that every plugin with a setupCard routes to its own guided page instead.
  • Credentials rotation (InfaktCredentialsPanel) and post-create baseUrl editing (InfaktStructuredSection) — near-identical ports of the Erli / WooCommerce equivalents.
  • Invoice detail section (InfaktInvoiceDetailSection) — renders submitted / accepted / rejected regulatory states with the new .reg-card treatment (see below). Based on Subiekt's simpler badge+KV structure rather than KSeF's UPO/FA3-owning one, since inFakt exposes no UPO/FA3 document endpoints of its own.
  • KOR correction flow (InfaktInvoiceCorrectionFlow) — near-1:1 port of KsefInvoiceCorrectionFlow (same line-row model, same generic useIssueCorrectionMutation + POST /invoices/:invoiceId/correct contract). This scope item was missing from the original issue feat(infakt): FE plugin – connection setup + invoice detail section #1282 text; added to the issue body once the backend CorrectionIssuer capability was confirmed real (PR feat(integrations): production-harden Infakt plugin - validators, retry classifier, NestJS module, tests #1292).
  • Shared .reg-card redesign: a severity-stripe card treatment for the invoiceDetailSection provider slot (progress bar for pending, copyable clearance chip for accepted, proper alert styling for rejected). Applied additively to KSeF's and Subiekt's existing sections too (their original root class stays, so no existing DOM-shape assertions break) — inFakt ships with it from day one, its siblings get it as a drive-by improvement. Validated in a design mockup approved by the user before implementation: https://claude.ai/code/artifact/2c542a0c-0719-4db1-b9d0-c962be23dadb

Full plan: docs/plans/implementation-plan-infakt-fe-plugin.md (research trail, alternatives considered, risk analysis).

E2E verification — complete

Two Playwright evidence scripts (apps/web/e2e/infakt-connection.mjs, apps/web/e2e/infakt-invoice.mjs) drove the full connect → issue → KSeF-clearance → correction flow against the real inFakt sandbox. Full walkthrough with screenshots posted here: #1300 (comment)

Screenshots committed at libs/integrations/infakt/docs/assets/ (mirrors the KSeF/Subiekt tutorial-asset convention from PR #1284).

This walkthrough surfaced three real backend bugs, all found, fixed, and re-verified live against the sandbox on #1293:

  1. Missing ConnectionTesterPort registration for infakt.accounting.v1
  2. Invoice amounts sent ~100x too low (decimal-string vs. integer-groszy format) — a compliance-relevant bug, since these amounts go to KSeF
  3. KSeF success status mapped to the neutral cleared instead of accepted, so the terminal-success UI (clearance reference chip) never rendered

Test plan

  • pnpm --filter @openlinker/web lint — 0 errors (pre-existing warnings only)
  • pnpm --filter @openlinker/web type-check — 0 errors
  • pnpm --filter @openlinker/web test — all new + existing suites green (one pre-existing, unrelated local-env-only failure in settings-page.test.tsx caused by a stale local .env.local VITE_API_BASE_URL override — not present in CI, not touched by this diff)
  • Bumped EXPECTED_LAZY_ROUTE_COUNT 48 → 49 for the new infakt-setup.route.tsx
  • Live sandbox E2E: connect → issue → real KSeF clearance → correction, confirmed with exact PLN amounts against inFakt's own API responses (see walkthrough comment)

Follow-up fix merged into this branch — PDF download (#1321 / #1323)

The E2E walkthrough's "Download PDF" affordance turned out to be non-functional: InfaktInvoicingAdapter was reading a pdf_url field that doesn't exist on inFakt's invoice resource (verified live against the sandbox), so InvoiceRecord.pdfUrl was always null. Filed as #1321, fixed and merged on #1323:

  • InfaktInvoicingAdapter now implements RegulatoryDocumentReader.getRegulatoryDocument for kind: 'rendered', calling the real GET /invoices/{uuid}/pdf.json?document_type=original&invoice_type={kind} endpoint (confirmed live: returns a valid PDF binary directly). The dead pdf_url / print_url fields are removed from the Infakt wire type.
  • FE: new useInvoiceRenderedDocumentDownload hook + a working "Download PDF" button in InfaktInvoiceDetailSection, routed through the existing GET /invoices/:invoiceId/document?kind=rendered controller route.
  • pnpm --filter @openlinker/integrations-infakt build/lint/test — 115/115 passing, new adapter coverage; pnpm --filter @openlinker/web type-check clean; pnpm check:invariants clean; live sandbox verification of the real PDF endpoint before implementation.

Closes #1282

@norbert-kulus-blockydevs norbert-kulus-blockydevs marked this pull request as draft July 1, 2026 19:56
norbert-kulus-blockydevs added a commit that referenced this pull request Jul 1, 2026
Found during a live E2E walkthrough of the FE plugin (PR #1300): "Test
connection" fails with "Connection testing is not supported for adapter
infakt.accounting.v1" because infakt-plugin.ts never registered a
ConnectionTesterPort. Every other adapter with a Test connection affordance
(Erli, WooCommerce, PrestaShop, InPost, Allegro, Subiekt) has one; Infakt was
the one gap.

Add InfaktConnectionTesterAdapter mirroring SubiektConnectionTesterAdapter's
pattern (Infakt's factory only exposes createInvoicingAdapter, not a bare
HTTP-client construction seam, so credentials are resolved and the client
built directly rather than via a factory method): probes GET clients.json
(cheap, side-effect-free, requires a valid key), maps InfaktApiError to the
neutral ConnectionTestResult, and never throws. Register it in
infakt-plugin.ts alongside the existing validator/retry-classifier/webhook
registrations.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
norbert-kulus-blockydevs added a commit that referenced this pull request Jul 1, 2026
…hots

Part of the E2E evidence trail for PR #1300 / issue #1282: inFakt
sandbox dashboard login, API key generation page, and the webhook
creation flow (list + new-webhook form). Captured manually against
the real inFakt sandbox dashboard, no secrets visible in frame.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
norbert-kulus-blockydevs and others added 9 commits July 2, 2026 00:00
…estion

Registers InfaktIntegrationModule in apps/api and apps/worker so the host
can resolve the Infakt 'Invoicing' capability, and wires Infakt's KSeF-relay
webhooks into OL's ADR-021/ADR-015 ingestion pipeline:

- InfaktInboundWebhookDecoderAdapter (provider-keyed): HMAC-SHA256 verify,
  subscription-verification handshake echo, envelope extraction
- InfaktWebhookEventTranslatorAdapter (adapterKey-keyed): maps
  send_to_ksef_success/error to the new `invoicing` inbound domain
- InboundWebhookDecoderPort grows an optional detectHandshake step (ADR-021)
  so a provider's subscription-verification ping can echo a body before
  signature verification — WebhookService/WebhookController thread the
  return value through
- InboundRoutingPolicyService routes `invoicing` events to the existing
  invoicing.regulatoryStatus.reconcile job (webhook as trigger, not source
  of truth — the scheduled reconcile still drains the full frontier)

Closes #1281

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Shorten the wrap-around import path in both webhook adapters
(infrastructure/adapters -> infrastructure/webhooks was going through
infrastructure twice), and replace the dummy-secret
`new InfaktWebhookTranslator({ secret: '' }, logger)` construction with
a named `InfaktWebhookTranslator.forParsing(logger)` factory that makes
the secret-independent-parsing-only intent explicit in the type itself.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…us found in manual QA

Manual E2E testing against the real Infakt sandbox surfaced 8 issues
blocking real invoice issuance/correction and webhook verification:

- upsertCustomer sent `name`/`post_code`; Infakt's v3 API wants
  `company_name`/`postal_code` (rejected/ignored otherwise)
- issueInvoice hardcoded payment_method: 'transfer', which 422s without
  a configured bank account; defaulted to 'cash' (proven live in the
  June 30 POC)
- issueInvoice/issueCorrection sent client_uuid; Infakt's invoices.json
  only accepts the numeric client_id
- issueCorrection never sent a client at all, so every correction 422'd
- empty InvoiceLine.taxRate (core's documented contract) cascaded into
  a rejection on every single invoice line; added a Polish-standard-VAT
  (23%) fallback
- InfaktInvoicingAdapter.sendToKsef() existed but was never called from
  issueInvoice/issueCorrection (only a standalone POC script called it
  directly) - every Infakt invoice sat in `draft` forever. Now called
  inline, matching how KSeF submits inline and Subiekt transmits
  natively at issuance.
- InfaktInboundWebhookDecoderAdapter.extractEnvelope always routed
  regardless of event name instead of using the `ignore` outcome for
  events OL doesn't act on
- The webhook handshake echo returned 202; Infakt's own verifier only
  accepts 200

Confirmed end-to-end against the real sandbox: issue -> auto KSeF
submission -> real Infakt webhook (real HMAC) -> reconcile job ->
regulatoryStatus=cleared with a real KSeF number, for both the
original invoice and its correction.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Address review feedback on #1293: gate getVerificationEcho on the
absence of the event envelope so a signed webhook that happens to
carry a verification_code field in its resource is never
mis-short-circuited into the handshake path. Also cap the echoed
verification_code length, since the handshake echo is returned
pre-signature-verification and shouldn't reflect an unbounded
attacker-controlled value.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Rebasing onto main (which already carries #1292's grossToNet/taxRateNumeric
module-level refactor) alongside this branch's own empty-taxRate fix produced
a duplicate: an unused private taxRateNumeric method plus the real,
still-unpatched module-level function actually called by grossToNet. Fold
the empty-string fallback into the module-level function that's on the real
call path and drop the dead duplicate; extend the two issueCorrection tests
exercising send_to_ksef.json to seed the response now that issueCorrection
always submits.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
- Document the retry-safety assumption behind the two-step issue+submit
  flow at both sendToKsef call sites: a retry re-POSTs invoices.json with
  the same external_id, relying on Infakt returning/reusing the same
  invoice uuid rather than creating a duplicate draft, so the follow-up
  sendToKsef becomes a safe re-attempt on the same document.
- Add error-path tests for issueInvoice and issueCorrection covering
  sendToKsef failing after a successful invoice creation, asserting the
  InfaktApiError/failureMode propagates.
- payment_method being hardcoded to 'cash' is tracked separately as #1303
  (no neutral paymentMethod field exists on IssueInvoiceCommand yet).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Found during a live E2E walkthrough of the FE plugin (PR #1300): "Test
connection" fails with "Connection testing is not supported for adapter
infakt.accounting.v1" because infakt-plugin.ts never registered a
ConnectionTesterPort. Every other adapter with a Test connection affordance
(Erli, WooCommerce, PrestaShop, InPost, Allegro, Subiekt) has one; Infakt was
the one gap.

Add InfaktConnectionTesterAdapter mirroring SubiektConnectionTesterAdapter's
pattern (Infakt's factory only exposes createInvoicingAdapter, not a bare
HTTP-client construction seam, so credentials are resolved and the client
built directly rather than via a factory method): probes GET clients.json
(cheap, side-effect-free, requires a valid key), maps InfaktApiError to the
neutral ConnectionTestResult, and never throws. Register it in
infakt-plugin.ts alongside the existing validator/retry-classifier/webhook
registrations.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…l strings

Critical fiscal bug found during live E2E against the real inFakt sandbox:
issued invoices landed on inFakt's dashboard (and were submitted to KSeF)
at ~1/100th of the real order total (e.g. a PLN 349.00 order → PLN 3.48 on
the resulting invoice). Confirmed against both the raw sandbox response
(net_price/tax_price/gross_price returned as plain integers, e.g. 283 for
PLN 2.83) and the official Infakt API schema (unit_net_price/net_price/
gross_price are `integer`, documented "w groszach") that inFakt's wire
format is a plain integer count of groszy (1 PLN = 100 groszy) everywhere
- not the "amount currency" decimal-string format (e.g. "283.74 PLN") the
adapter was previously sending, which the earlier #1292 review had
incorrectly confirmed against the schema.

- InfaktInvoice.gross_price/net_price/tax_price and
  InfaktInvoiceService.unit_net_price/net_price/tax_price/gross_price are
  now typed `number` (plain integer groszy), not `string`.
- Added toGroszy/fromGroszy helpers replacing the removed parseInfaktAmount;
  issueInvoice and issueCorrection now round PLN amounts to integer groszy
  when building the request payload, and convert the original invoice's
  groszy amounts back to PLN decimals before doing gross-to-net arithmetic.
- Updated all affected unit tests (fixtures + assertions) to the integer
  groszy format and to assert exact groszy values rather than decimal
  strings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Addresses the three outstanding SUGGESTIONs from the full-PR re-review
(2026-07-01T22:11:12Z), none of which were blocking:

- infakt-inbound-webhook-decoder.adapter.ts: drop the redundant
  parenthesization around parsed.resource['invoice_uuid'] flagged as a
  merge-artifact.
- webhook.controller.ts: add a one-line comment pointing at the
  res.status(HttpStatus.OK) call, since @res is the only place this
  controller reaches for the raw Response object.
- infakt-invoicing.adapter.ts: clarify sendToKsef's public-visibility
  rationale — it already has a real external caller
  (scripts/poc-sandbox-test.ts), not just a hypothetical future one.

Verified: pnpm --filter @openlinker/integrations-infakt test (108
passing), pnpm --filter @openlinker/api test -- webhook (608 passing),
type-check clean on both packages, targeted eslint clean on all three
files.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
@norbert-kulus-blockydevs

norbert-kulus-blockydevs commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

Live sandbox walkthrough — inFakt connect → issue → KSeF clearance → correction

Full E2E run against the real inFakt sandbox, confirming the FE plugin works end to end (not just that the components render). Order: 1 x product, gross total PLN 259.00.

(Updated — both backend bugs found during this walkthrough are now fixed and re-verified; screenshots below reflect the fixed behavior.)

1. Get your inFakt API key (inFakt dashboard, manual)

inFakt dashboard logged in
API settings — generate new key

2. Create the connection in OpenLinker

Platform picker with the inFakt card
Guided setup form, empty
Guided setup form filled in, sandbox base URL set
Connection created, Test connection button visible
Connection test passed against the real sandbox
Connections list filtered to inFakt

3. Wire up the webhook (inFakt dashboard, manual)

Webhooks list — Dodaj nowy webhook
New webhook form filled in

4. An order arrives from PrestaShop

Clean, filtered orders list

5. Issue the invoice

Order detail — not issued, ready to issue
Invoice issued, submitted to KSeF, progress bar
reg-card success tone — KSeF: ACCEPTED, real clearance reference

Order gross total PLN 259.00 matches inFakt's own record exactly (gross_price: 25900 groszy), KSeF number 8201194127-20260702-04CF63400000-1D, badge correctly shows ACCEPTED (not stuck on CLEARING).

6. Verify in inFakt

inFakt dashboard — both documents, correct PLN amounts

7. Full invoice detail page

Full invoice detail page — reg-card, issuance/clearance timeline

8. Issue a correction (KOR)

Correction dialog with line 1 filled in
KOR issued toast, new corrected document
Invoices list — original accepted + correction submitted

Correction combines the original + corrected line correctly: PLN 259.00 + 99.99 = 358.99 PLN, verified against the sandbox (gross_price: 35899 groszy).


Three real backend gaps surfaced by this walkthrough, all fixed and re-verified on #1293:

  1. Missing ConnectionTesterPort registration for infakt.accounting.v1findingfixed
  2. Invoice amounts sent ~100x too low (decimal-string vs. integer-groszy) — findingfixedconfirmed
  3. KSeF success status mapped to cleared instead of accepted, so the terminal state never rendered — findingfixedconfirmed

Screenshots committed at libs/integrations/infakt/docs/assets/ for reuse in the eventual operator tutorial (mirroring the KSeF/Subiekt convention from PR #1284). This branch is up to date with main (includes #1292).

@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator Author

Tech-review (senior-level, /tech-review)

Scope: full PR (holistic pass against main, all 19 changed .ts/.tsx/.css files, ~1737 insertions) plus an independent second pass scoped strictly to the diff, run by a separate subagent. Findings below are merged from both passes.

Summary

Clean, well-scoped port of the existing KSeF/Erli/Subiekt plugin patterns to a new infakt FE plugin. It correctly uses the OpenLinkerPlugin contract, resolves everything through the platform registry (no platformType === 'infakt' string-matching outside plugins/infakt/), imports only from feature public barrels, uses design tokens throughout the new CSS, and ships solid test coverage per component. Architecture boundaries (plugin dependency direction, capability registry usage, kebab-case.tsx / *.test.tsx / use-*.ts naming, TanStack Query + RHF/Zod state ownership, no raw fetch()) are all respected. No BLOCKING issues.

Two things are worth fixing before merge: a real client-side validation gap in the correction flow, and a helper this PR triples across three files.

Issues

[IMPORTANT] - apps/web/src/plugins/infakt/components/infakt-invoice-correction-flow.tsx (parseLineRows, handleSubmit)

parseLineRows builds a CorrectionLineInput for any row with a non-empty originalLineNumber, even if both newQuantity and newUnitPriceGross are blank. The sibling KsefInvoiceCorrectionFlow.parseLineRows explicitly guards this: it returns null when a filled row has neither delta, and handleSubmit shows a specific inline error ("Each line must specify a new quantity and/or a new price."). The backend enforces exactly this invariant (HasCorrectionDeltaConstraint in apps/api/src/invoicing/http/dto/issue-correction-request.dto.ts), so submitting a bare line number here doesn't silently succeed, it 400s - but the operator only sees a generic "KOR failed: {backend message}" toast instead of the pre-submit inline guidance KSeF gives. Since this component is documented as a "near-1:1 port" of KSeF's, this divergence looks unintentional. Port the same null-return + dedicated error-message pattern from ksef-invoice-correction-flow.tsx.

[IMPORTANT] - apps/web/src/plugins/infakt/components/infakt-invoice-detail-section.tsx (new resolveRegCardTone), apps/web/src/plugins/ksef/components/ksef-invoice-detail-section.tsx:54-59, apps/web/src/plugins/subiekt/components/subiekt-invoice-detail-section.tsx:22-27

resolveRegCardTone is now defined three times with byte-for-byte identical bodies. This PR is the one introducing the third copy and simultaneously retrofitting the other two with the same helper instead of sharing one. All three call sites already import from the features/invoicing barrel (which the barrel doc comment explicitly carves out as the cross-plugin exception for exactly this kind of shared invoicing UI logic), so exporting one regCardToneFor(status) from that barrel costs no new import surface and removes the triplication.

Suggestions (non-blocking)

  • infakt-invoice-detail-section.tsx: resolveRegCardTone returns a bare string instead of the narrow literal union ('reg-card--info' | 'reg-card--success' | 'reg-card--error' | ''). Worth tightening when the helper above gets extracted. Also takes InvoiceDetailSectionProps['invoice']['regulatoryStatus'] as its param type where importing the named RegulatoryStatus type (as Subiekt's copy does) reads clearer.
  • infakt-invoice-detail-section.tsx: inline style={{ fontSize: '12px', margin: 0 }} on two <p> elements instead of a class. This mirrors a pre-existing pattern in subiekt-invoice-detail-section.tsx so it's not a regression, but since this file is new and "ships with the redesigned look from day one" per its own header comment, it was a good opportunity to use a type-scale class instead.
  • InvoiceCorrectionFlowProps.connection is a required prop that neither InfaktInvoiceCorrectionFlow nor the pre-existing KsefInvoiceCorrectionFlow use. Not introduced by this PR, but with 2 of 3 implementers now ignoring a required prop it may be worth revisiting whether it should be optional on the interface.
  • The .textarea className used in the reason field has no dedicated CSS rule (only the bare textarea element selector applies) - same gap already present in ksef-invoice-correction-flow.tsx, so not new, but a good candidate for a follow-up cleanup pass across all correction flows.

Verdict

Approve with changes - the two IMPORTANT items are both small, mechanical fixes (port the existing KSeF validation branch; extract one 4-line helper to the invoicing barrel) that should land before merge given the PR's own "near-1:1 port" / "shared .reg-card treatment" framing. Everything else is optional polish.

Found during a live E2E walkthrough (letting the real scheduled
invoicing.regulatoryStatus.reconcile job pick up clearance, rather than
manually poking the DB): toRegulatoryStatus mapped Infakt's terminal
ksef_data.status: 'success' to the neutral 'cleared' instead of 'accepted'.

Per the core RegulatoryStatus contract, 'cleared' is reserved for
split-clearance regimes that no current provider emits; the FE's status
card only branches on submitted/accepted/rejected, so an invoice stuck at
'cleared' rendered as a permanently in-progress "CLEARING" badge with no
clearance-reference chip, even though the invoice had genuinely cleared on
the government side. KSeF's own adapter already maps its terminal 200
status to 'accepted' for the identical reason - Infakt now mirrors that.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator Author

Tech-review findings addressed (23ae0ab)

Both IMPORTANT findings and all four suggestions from the tech-review comment are fixed:

IMPORTANT

  • InfaktInvoiceCorrectionFlow.parseLineRows now returns null when a filled line has neither a new quantity nor a new price (ported 1:1 from KsefInvoiceCorrectionFlow), and handleSubmit shows the same inline "Each line must specify a new quantity and/or a new price." error instead of letting a no-op line reach the backend and 400. Added a regression test (blocks submit when a filled line has neither new quantity nor new price) and fixed the pre-existing does not include empty line rows in the submission test, which had accidentally been exercising the bug it was meant to guard against (it never gave the filled row a delta).
  • Extracted the triplicated resolveRegCardTone into a single regCardToneFor(status) helper in features/invoicing/lib/derive-invoice-display.ts, exported from the invoicing barrel. KSeF, Subiekt, and inFakt detail sections now import it instead of each carrying their own copy.

Suggestions

  • regCardToneFor returns the narrow RegCardTone union ('reg-card--info' | 'reg-card--success' | 'reg-card--error' | '') instead of a bare string.
  • infakt-invoice-detail-section.tsx's two inline style={{ fontSize: '12px', margin: 0 }} blocks are replaced with a new .reg-card__note CSS class (added to index.css alongside the other .reg-card__* rules).
  • InvoiceCorrectionFlowProps.connection is now optional — none of the three current implementers (KSeF, Subiekt, inFakt) read it.
  • .textarea now has an actual CSS rule (merged into the existing bare textarea selector) instead of relying only on the element selector.

Verified: pnpm --filter @openlinker/web lint (0 errors), pnpm --filter @openlinker/web type-check (0 errors), full apps/web test suite (1908/1909 passing — same pre-existing, unrelated settings-page.test.tsx local-env failure called out in the PR description).

Branch is up to date with main (0 commits behind), no rebase was needed.

@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator Author

Added the "Default payment method" field this PR was missing for #1303 to be fully usable without editing raw JSON config:

  • Connection wizard: new "Default payment method" select, defaulting to Transfer (the common case for online-shop invoicing), with Cash always available.
  • Edit-connection screen: same field, tucked behind a new InlineDisclosure primitive (shared/ui/inline-disclosure.tsx) so it reads as an inline fact ("Payment method for invoice: Cash") rather than a permanently-open control — most operators never need to touch it.

Design reference: https://claude.ai/code/artifact/ee14c14d-bfac-4d5f-9b0f-ca3dfddb47a0

A follow-up (bank-account picker for Transfer invoices, in progress) will extend this same disclosure.

@piotrswierzy piotrswierzy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tech Lead Review — inFakt FE plugin

Clean, faithful port of the KSeF/Subiekt slot precedents. Architecture holds across the board: plugin barrel wires all four platform slots, dependency direction (plugins → features → shared) is respected, all server state flows through TanStack Query hooks (no raw fetch), the correction mutation invalidates invoicingQueryKeys.all, the API key is write-only (password input, no readback), CSS uses design tokens (no raw hex), the .reg-card change is genuinely additive (KSeF/Subiekt keep their root classes + DOM shape, .trim() handles the empty-tone case), and the lazy-route count is correctly bumped to 49. The correction flow mirrors KsefInvoiceCorrectionFlow near-1:1.

IMPORTANT

  • infakt-setup.schema.ts:53 — default payment method contradicts its own help copy. The wizard pre-selects defaultPaymentMethod: 'transfer', but the description directly beneath it (and on the edit section) says: "Transfer 422s on inFakt unless a bank account is configured… Leave Cash unless you have confirmed that prerequisite." An operator who accepts the default hits a 422 on their first issuance. Please default to 'cash' (fiscal-safe, matches the copy and the adapter fallback) — this also aligns with the BE default landing in #1309 (defaultPaymentMethod defaults to 'cash'). In a feature otherwise built around fiscal-safety gates, defaulting to the value the UI warns against stands out.

SUGGESTION

  • route-lazy.test.ts:877-886 — constant is correctly 49, but the comment is stale: header says (46 total) and "9 plugin routes", whereas it's 10 plugin routes (Allegro = callback + setup) → 36 + 3 + 10 = 49. Keep the prose in sync per the "Bump this when…" note.
  • index.css.reg-card__reference and .reg-card__actions are defined but unused by any section; drop them or wire them.
  • index.css.reg-card__note { font-size: 12px; } uses raw px where neighbouring rules use rem; minor consistency nit.

Non-blocking notes: .infakt-correction root / __orig have no dedicated CSS, but that faithfully mirrors the KSeF precedent (same gap there), and the "inFakt · slot" provider tag mirrors "KSeF · slot" — both intentional, not leftovers.

Verdict: Approve with changes — resolve the payment-method default/copy contradiction before this leaves draft; the rest are trivial cleanups.

…on redesign

Covers issue #1282 (guided setup, credentials rotation, invoice detail
section, KOR correction flow) plus the OrderInvoicePanel regulatory-section
host-chrome redesign validated in the approved mockup. Corrects the issue's
original setup-flow description against verified codebase precedent (Erli's
guided-route pattern, not the generic inline create form).

Closes #1282

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Adds Phase 4: two Playwright walkthrough scripts (connection setup,
invoice issuance through real KSeF clearance + a correction) mirroring
the existing Subiekt/Erli screenshot-evidence convention, plus posting
the resulting docs/assets/infakt/*.png screenshots inline in a PR
comment as end-to-end proof.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
… correction

Implements the frontend half of the inFakt accounting integration
(epic #1279): guided connection setup (name + API key + optional
sandbox baseUrl, mirroring the Erli pattern), credentials rotation,
post-create baseUrl editing, and the invoice-surfacing slots
(regulatory status + KSeF number, KOR correction flow) driven by the
existing generic InvoicingPort/CorrectionIssuer backend contract
(PR #1292/#1293).

Also introduces a shared `.reg-card` severity-stripe treatment for the
invoiceDetailSection provider slot (additive alongside each section's
existing root class, so KSeF/Subiekt keep their current DOM shape and
tests) — inFakt ships with it from day one, KSeF/Subiekt adopt it as a
drive-by improvement.

Closes #1282

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Playwright walkthroughs (apps/web/e2e/infakt-connection.mjs,
infakt-invoice.mjs) mirroring the existing Subiekt/Erli proof-capture
convention. Not part of pnpm test / CI — manual-run evidence scripts
against a live stack + the real inFakt sandbox, producing
docs/assets/infakt/*.png for the PR comment and the future operator
setup guide.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…hots

Part of the E2E evidence trail for PR #1300 / issue #1282: inFakt
sandbox dashboard login, API key generation page, and the webhook
creation flow (list + new-webhook form). Captured manually against
the real inFakt sandbox dashboard, no secrets visible in frame.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…KOR, list)

Captured against the real inFakt sandbox on a temporary paired stack
(this branch's web build + the 1281-infakt-plugin-registration-webhook
backend branch), confirming the full connect -> issue -> KSeF-clearance
-> correction flow works end to end through OpenLinker:

- 00-05: guided connection setup, Test connection (passing, after the
  ConnectionTesterPort fix landed on PR #1293), connections list
- 12: invoice accepted with real KSeF clearance number
  (8201194127-20260701-A5797F400000-ED)
- 14-16: KOR correction flow, also cleared by the real sandbox
- 17: invoices list showing the original + correction, both accepted
- if1-if4: manual inFakt-dashboard screenshots (API key, webhooks)

Known gaps (tracked, not blocking):
- 13-invoice-detail-page was captured via the /invoices/:invoiceId page,
  but that GET route doesn't exist yet on the 1281 backend branch's base
  main snapshot — will be trivial to recapture once #1292/#1293 land on
  current main.
- The not-issued / submitted (pending) order-detail states aren't
  captured cleanly yet — the seeded test order is now terminal
  (accepted) for this connection; a second seeded order would be needed
  for a clean before/during capture.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…ng on this backend branch, not blocking)

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…rified

Re-ran the full sandbox walkthrough on a fresh order (PLN 189.00) after
the groszy/decimal-string fix landed on PR #1293 (651cac2). Confirmed
against inFakt's raw API response that amounts now round-trip exactly
(gross_price: 18900 groszy = PLN 189.00) and the KOR correction combines
original + corrected lines correctly (189.00 + 99.99 = 288.99 PLN).

Also adds the invoice-detail-page screenshot (13) that 404'd on a stale
branch snapshot last time — the route works fine now.

Replaces the earlier PLN 349.00 test order's screenshots, which
(correctly, at the time) showed the ~100x-low amounts that led to the
money-format bug report on PR #1293.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…ssets/

Aligns with the convention established in PR #1284 (KSeF/Subiekt
tutorials) — screenshot assets live inside the integration package's
own docs/assets/, not a root-level docs/assets/<provider>/ directory.
Updates the two e2e scripts' output path accordingly.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
… 6 confirmation

- 08-orders-list.png: clean, filtered (sourceConnectionId + createdFrom)
  view of just the seeded test orders — the unfiltered list is too
  cluttered with other sessions' dev-DB fixtures to be tutorial-usable.
- 10/11: genuinely clean not-issued -> submitted transition, captured
  without a page reload wiping the connection-picker selection (fixed
  infakt-invoice.mjs to shoot the submitted state before any reload).
- if5-infakt-invoice-confirmed.png: fresh manual inFakt-dashboard
  screenshot confirming the money-format fix (9/07/2026 = PLN 189.00,
  10/07/2026 correction = PLN 288.99), replacing the pre-fix evidence.
- 13/17 updated: also documents the KSeF cleared-vs-accepted mapping bug
  found this run (flagged on PR #1293) — two rows show "KSeF: CLEARING"
  (the real reconcile job's output, unpatched) alongside two "KSeF:
  ACCEPTED" rows from earlier runs where I'd manually corrected the DB
  while chasing the money bug. Will re-capture once the accepted-mapping
  fix lands.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Ports the KSeF correction flow's line-delta validation guard into the
inFakt correction flow so a filled line number without a quantity or
price change is rejected client-side (matching the backend's
HasCorrectionDeltaConstraint) instead of silently 400ing. Extracts the
`.reg-card` tone mapping duplicated across KSeF, Subiekt, and inFakt
detail sections into a single `regCardToneFor` helper on the invoicing
feature barrel. Also applies the review's non-blocking suggestions:
narrows the helper's return type, makes InvoiceCorrectionFlowProps.connection
optional (no implementer reads it), and gives the `.textarea` class and
`.reg-card` note paragraphs real CSS rules instead of relying on bare
element selectors / inline styles.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…erified

Fourth fresh sandbox order (PLN 259.00), confirming both backend fixes
together: gross_price round-trips exactly (25900 groszy = PLN 259.00),
and the invoice now shows "KSeF: ACCEPTED" with a real clearance
reference chip instead of getting stuck on "KSeF: CLEARING" forever.
Correction verified too (259.00 + 99.99 = 358.99 PLN exact, gross_price
35899 groszy).

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…sure)

Adds a "Default payment method" field to the inFakt connection wizard
(defaults to Transfer, the common case for online-shop invoicing) and
the equivalent field to the edit-connection screen, tucked behind a
new InlineDisclosure primitive (shared/ui) so it reads as an inline
fact ("Payment method for invoice: Cash") rather than a permanently-
open control. Wires apps/web/src/features/connections/components/
infakt-setup-form.tsx, infakt-setup.schema.ts, edit-connection.schema.ts,
EditConnectionForm.tsx and plugins/infakt/components/infakt-structured-
section.tsx to the config.defaultPaymentMethod field introduced by #1303.

Related to #1303.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…view (order PLN 899.00)

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…ethod field

Rebuilt the web bundle after pulling the default-payment-method picker
feature (#1303) and re-ran the connection walkthrough — the wizard,
created/test/list states now show the new "Default payment method"
field. Live-verified against the real sandbox (Test connection passes).

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Default the inFakt setup wizard's payment method to cash - transfer 422s
on inFakt unless a bank account is configured, contradicting the help
copy and the adapter's own fallback. Also syncs the stale lazy-route
count comment (49 total, 10 plugin routes) and drops two unused
.reg-card CSS classes plus a raw-px rule in favor of the rem convention.

Signed-off-by: Norbert Kulus <norbert.kulus@silksh.pl>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator Author

Addressed all findings from the tech-review:

IMPORTANT

SUGGESTION

  • route-lazy.test.ts — fixed the stale comment header (46 total49 total, 9 plugin routes10 plugin routes); the EXPECTED_LAZY_ROUTE_COUNT = 49 constant itself was already correct.
  • index.css — dropped the unused .reg-card__reference and .reg-card__actions classes (no section references either).
  • index.css.reg-card__note now uses 0.75rem instead of the raw 12px, matching the neighbouring rem-based rules.

Also rebased onto #1293 (which is itself rebased onto latest main), so this branch is now current with both.

Verified: pnpm --filter @openlinker/web lint (0 errors), pnpm --filter @openlinker/web type-check (0 errors), pnpm --filter @openlinker/web test (all green except the pre-existing, unrelated local-env-only settings-page.test.tsx failure noted in the PR description).

@piotrswierzy piotrswierzy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at head d441dbe7 (was 0012af00). All prior findings addressed — the new commits are a tight, on-target response.

Resolved

  • IMPORTANT — wizard now defaults defaultPaymentMethod: 'cash' (was 'transfer'), consistent with its own help copy ("Transfer 422s unless a bank account is configured — leave Cash unless confirmed"), the adapter-side fallback, and the #1309 BE default. Tests updated across the board; the transfer path is retained as an explicit-override case.
  • Route-count comment in route-lazy.test.ts corrected to "49 total / 10 plugin routes"; matches the EXPECTED_LAZY_ROUTE_COUNT = 49 constant and the passing assertion.
  • Dead CSS .reg-card__reference / .reg-card__actions removed (confirmed zero references), and .reg-card__note migrated 12px0.75rem.

Verified

  • The .reg-card change is safe for the KSeF/Subiekt detail sections — the removed subclasses were unused, and the base .reg-card + all live modifiers remain intact.
  • Affected suites pass locally: 3 files / 69 tests green (infakt-setup-form, route-lazy, infakt-invoice-detail-section).

No new findings. Ready to un-draft from a review perspective. (Merge-order: this is base for #1307 and #1310, so land it before those.)

norbert-kulus-blockydevs added a commit that referenced this pull request Jul 2, 2026
…estion (#1293)

* feat(infakt): register Infakt plugin in API/worker + wire webhook ingestion

Registers InfaktIntegrationModule in apps/api and apps/worker so the host
can resolve the Infakt 'Invoicing' capability, and wires Infakt's KSeF-relay
webhooks into OL's ADR-021/ADR-015 ingestion pipeline:

- InfaktInboundWebhookDecoderAdapter (provider-keyed): HMAC-SHA256 verify,
  subscription-verification handshake echo, envelope extraction
- InfaktWebhookEventTranslatorAdapter (adapterKey-keyed): maps
  send_to_ksef_success/error to the new `invoicing` inbound domain
- InboundWebhookDecoderPort grows an optional detectHandshake step (ADR-021)
  so a provider's subscription-verification ping can echo a body before
  signature verification — WebhookService/WebhookController thread the
  return value through
- InboundRoutingPolicyService routes `invoicing` events to the existing
  invoicing.regulatoryStatus.reconcile job (webhook as trigger, not source
  of truth — the scheduled reconcile still drains the full frontier)

Closes #1281

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(infakt): address PR #1293 tech review suggestions

Shorten the wrap-around import path in both webhook adapters
(infrastructure/adapters -> infrastructure/webhooks was going through
infrastructure twice), and replace the dummy-secret
`new InfaktWebhookTranslator({ secret: '' }, logger)` construction with
a named `InfaktWebhookTranslator.forParsing(logger)` factory that makes
the secret-independent-parsing-only intent explicit in the type itself.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(infakt): correct field names, KSeF submission, and handshake status found in manual QA

Manual E2E testing against the real Infakt sandbox surfaced 8 issues
blocking real invoice issuance/correction and webhook verification:

- upsertCustomer sent `name`/`post_code`; Infakt's v3 API wants
  `company_name`/`postal_code` (rejected/ignored otherwise)
- issueInvoice hardcoded payment_method: 'transfer', which 422s without
  a configured bank account; defaulted to 'cash' (proven live in the
  June 30 POC)
- issueInvoice/issueCorrection sent client_uuid; Infakt's invoices.json
  only accepts the numeric client_id
- issueCorrection never sent a client at all, so every correction 422'd
- empty InvoiceLine.taxRate (core's documented contract) cascaded into
  a rejection on every single invoice line; added a Polish-standard-VAT
  (23%) fallback
- InfaktInvoicingAdapter.sendToKsef() existed but was never called from
  issueInvoice/issueCorrection (only a standalone POC script called it
  directly) - every Infakt invoice sat in `draft` forever. Now called
  inline, matching how KSeF submits inline and Subiekt transmits
  natively at issuance.
- InfaktInboundWebhookDecoderAdapter.extractEnvelope always routed
  regardless of event name instead of using the `ignore` outcome for
  events OL doesn't act on
- The webhook handshake echo returned 202; Infakt's own verifier only
  accepts 200

Confirmed end-to-end against the real sandbox: issue -> auto KSeF
submission -> real Infakt webhook (real HMAC) -> reconcile job ->
regulatoryStatus=cleared with a real KSeF number, for both the
original invoice and its correction.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(infakt): disambiguate verification handshake from signed events

Address review feedback on #1293: gate getVerificationEcho on the
absence of the event envelope so a signed webhook that happens to
carry a verification_code field in its resource is never
mis-short-circuited into the handshake path. Also cap the echoed
verification_code length, since the handshake echo is returned
pre-signature-verification and shouldn't reflect an unbounded
attacker-controlled value.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(infakt): resolve rebase conflict duplicating taxRateNumeric

Rebasing onto main (which already carries #1292's grossToNet/taxRateNumeric
module-level refactor) alongside this branch's own empty-taxRate fix produced
a duplicate: an unused private taxRateNumeric method plus the real,
still-unpatched module-level function actually called by grossToNet. Fold
the empty-string fallback into the module-level function that's on the real
call path and drop the dead duplicate; extend the two issueCorrection tests
exercising send_to_ksef.json to seed the response now that issueCorrection
always submits.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(infakt): address PR #1293 tech-review findings on KSeF submission

- Document the retry-safety assumption behind the two-step issue+submit
  flow at both sendToKsef call sites: a retry re-POSTs invoices.json with
  the same external_id, relying on Infakt returning/reusing the same
  invoice uuid rather than creating a duplicate draft, so the follow-up
  sendToKsef becomes a safe re-attempt on the same document.
- Add error-path tests for issueInvoice and issueCorrection covering
  sendToKsef failing after a successful invoice creation, asserting the
  InfaktApiError/failureMode propagates.
- payment_method being hardcoded to 'cash' is tracked separately as #1303
  (no neutral paymentMethod field exists on IssueInvoiceCommand yet).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(infakt): register ConnectionTesterPort for infakt.accounting.v1

Found during a live E2E walkthrough of the FE plugin (PR #1300): "Test
connection" fails with "Connection testing is not supported for adapter
infakt.accounting.v1" because infakt-plugin.ts never registered a
ConnectionTesterPort. Every other adapter with a Test connection affordance
(Erli, WooCommerce, PrestaShop, InPost, Allegro, Subiekt) has one; Infakt was
the one gap.

Add InfaktConnectionTesterAdapter mirroring SubiektConnectionTesterAdapter's
pattern (Infakt's factory only exposes createInvoicingAdapter, not a bare
HTTP-client construction seam, so credentials are resolved and the client
built directly rather than via a factory method): probes GET clients.json
(cheap, side-effect-free, requires a valid key), maps InfaktApiError to the
neutral ConnectionTestResult, and never throws. Register it in
infakt-plugin.ts alongside the existing validator/retry-classifier/webhook
registrations.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(infakt): send invoice amounts as plain-integer groszy, not decimal strings

Critical fiscal bug found during live E2E against the real inFakt sandbox:
issued invoices landed on inFakt's dashboard (and were submitted to KSeF)
at ~1/100th of the real order total (e.g. a PLN 349.00 order → PLN 3.48 on
the resulting invoice). Confirmed against both the raw sandbox response
(net_price/tax_price/gross_price returned as plain integers, e.g. 283 for
PLN 2.83) and the official Infakt API schema (unit_net_price/net_price/
gross_price are `integer`, documented "w groszach") that inFakt's wire
format is a plain integer count of groszy (1 PLN = 100 groszy) everywhere
- not the "amount currency" decimal-string format (e.g. "283.74 PLN") the
adapter was previously sending, which the earlier #1292 review had
incorrectly confirmed against the schema.

- InfaktInvoice.gross_price/net_price/tax_price and
  InfaktInvoiceService.unit_net_price/net_price/tax_price/gross_price are
  now typed `number` (plain integer groszy), not `string`.
- Added toGroszy/fromGroszy helpers replacing the removed parseInfaktAmount;
  issueInvoice and issueCorrection now round PLN amounts to integer groszy
  when building the request payload, and convert the original invoice's
  groszy amounts back to PLN decimals before doing gross-to-net arithmetic.
- Updated all affected unit tests (fixtures + assertions) to the integer
  groszy format and to assert exact groszy values rather than decimal
  strings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(infakt): polish remaining review suggestions on PR #1293

Addresses the three outstanding SUGGESTIONs from the full-PR re-review
(2026-07-01T22:11:12Z), none of which were blocking:

- infakt-inbound-webhook-decoder.adapter.ts: drop the redundant
  parenthesization around parsed.resource['invoice_uuid'] flagged as a
  merge-artifact.
- webhook.controller.ts: add a one-line comment pointing at the
  res.status(HttpStatus.OK) call, since @res is the only place this
  controller reaches for the raw Response object.
- infakt-invoicing.adapter.ts: clarify sendToKsef's public-visibility
  rationale — it already has a real external caller
  (scripts/poc-sandbox-test.ts), not just a hypothetical future one.

Verified: pnpm --filter @openlinker/integrations-infakt test (108
passing), pnpm --filter @openlinker/api test -- webhook (608 passing),
type-check clean on both packages, targeted eslint clean on all three
files.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(infakt): map KSeF success status to accepted, not cleared

Found during a live E2E walkthrough (letting the real scheduled
invoicing.regulatoryStatus.reconcile job pick up clearance, rather than
manually poking the DB): toRegulatoryStatus mapped Infakt's terminal
ksef_data.status: 'success' to the neutral 'cleared' instead of 'accepted'.

Per the core RegulatoryStatus contract, 'cleared' is reserved for
split-clearance regimes that no current provider emits; the FE's status
card only branches on submitted/accepted/rejected, so an invoice stuck at
'cleared' rendered as a permanently in-progress "CLEARING" badge with no
clearance-reference chip, even though the invoice had genuinely cleared on
the government side. KSeF's own adapter already maps its terminal 200
status to 'accepted' for the identical reason - Infakt now mirrors that.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

---------

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
@norbert-kulus-blockydevs norbert-kulus-blockydevs marked this pull request as ready for review July 2, 2026 11:48
Resolve conflicts in apps/web/src/features/invoicing/index.ts (keep both
regCardToneFor/RegCardTone and InvoicePdfLink exports) and
apps/web/src/index.css (keep both the Inline Disclosure and Demo Mode
sections, added independently on each branch).

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
…#1323)

fix(infakt): implement real PDF download via RegulatoryDocumentReader (#1323)

InfaktInvoicingAdapter read a pdf_url field that does not exist on Infakt's
invoice resource (verified live against the sandbox), so InvoiceRecord.pdfUrl
was always null and the "download PDF" affordance never worked. Infakt does
expose a real PDF at GET /invoices/{uuid}/pdf.json?document_type=original,
so the adapter now implements RegulatoryDocumentReader for kind 'rendered'
against that endpoint, and the FE Infakt invoice detail section gets a
working Download PDF button wired to the existing /document?kind=rendered
route.

Closes #1321
@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator Author

Follow-up: the "Download PDF" button introduced in this PR's E2E walkthrough was found to be non-functional during that same verification pass — InfaktInvoicingAdapter was reading a pdf_url field that inFakt's invoice resource doesn't actually expose, so InvoiceRecord.pdfUrl was always null.

Filed as #1321, fixed and merged on #1323 (stacked on this branch):

  • InfaktInvoicingAdapter now implements RegulatoryDocumentReader.getRegulatoryDocument for kind: 'rendered' against the real GET /invoices/{uuid}/pdf.json?document_type=original&invoice_type={kind} endpoint (verified live against the sandbox, returns a valid PDF binary).
  • Dead pdf_url / print_url fields removed from the Infakt wire type.
  • FE gets a working "Download PDF" button in InfaktInvoiceDetailSection via a new useInvoiceRenderedDocumentDownload hook, routed through the existing GET /invoices/:invoiceId/document?kind=rendered controller route.

Description above updated to reflect this.

@piotrswierzy piotrswierzy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review (head 356b293e) — Approve

Re-reviewed the delta since the previous pass (d441dbe7). Two real work commits absorbed, the rest is main merged in.

What's new

  • #1293 (61d5e5c0) — backend only: Infakt plugin registered in API/worker plugins.ts + webhook ingestion wiring (decoder / translator / connection-tester adapters + specs). No apps/web changes.
  • #1323 (356b293e) — real PDF download via RegulatoryDocumentReader: new useInvoiceRenderedDocumentDownload hook, one barrel export, and a "Download PDF" button in the inFakt invoiceDetailSection slot.
  • Main merges: demo-banner, /v1 versioning, Users pagination, erli — merge noise, not plugin work.

Prior findings — all still resolved

Every infakt FE file is byte-identical to d441dbe7 (git diff --stat empty): default→cash, route-lazy 49/10 comment, dead .reg-card__* CSS removal, .reg-card__note rem, KSeF/Subiekt-safe .reg-card. The index.css delta since the last pass is demo-banner CSS (merge #1264), not reg-card. No regression.

New code — clean

  • No raw fetch/axios; the hook goes through apiClient.invoicing.downloadDocument → shared requestBlob (with encodeURIComponent).
  • Correct blob-download pattern (object URL + programmatic <a download>, revokeObjectURL deferred past the tick).
  • Dep direction plugins/infaktfeatures/invoicing barrel (allowed); shared/ stays clean at value level.
  • Detail section reuses shared Button/useToast, i18n fallbacks, void-guarded promise, disabled+loading states, toast on failure. No raw hex tokens; route-lazy count test intact.

Suggestions (non-blocking)

  • useInvoiceRenderedDocumentDownload is an imperative one-shot (not a TanStack mutation) — deliberate, mirrors useKsefUpoDownload; downloads aren't cacheable server state, so this is fine.
  • Confirm the "Download PDF" button is offered in every regulatory state where inFakt has already rendered the PDF (it currently sits inside the clearance-number block).

CI

Please confirm the check-runs are green before merge (I couldn't read them from the legacy statuses API). Low risk (green-main merge + small additive FE). This is the base for #1307/#1310/#1323, so land it first.

Verdict: Approve.

@piotrswierzy piotrswierzy merged commit 5b3c363 into main Jul 2, 2026
7 checks passed
norbert-kulus-blockydevs added a commit that referenced this pull request Jul 3, 2026
…hots

Part of the E2E evidence trail for PR #1300 / issue #1282: inFakt
sandbox dashboard login, API key generation page, and the webhook
creation flow (list + new-webhook form). Captured manually against
the real inFakt sandbox dashboard, no secrets visible in frame.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(infakt): FE plugin – connection setup + invoice detail section

2 participants