feat(web): inFakt FE plugin — connection setup + invoice detail + KOR correction#1300
Conversation
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>
…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>
…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>
Live sandbox walkthrough — inFakt connect → issue → KSeF clearance → correctionFull 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)2. Create the connection in OpenLinker3. Wire up the webhook (inFakt dashboard, manual)4. An order arrives from PrestaShop5. Issue the invoiceOrder gross total PLN 259.00 matches inFakt's own record exactly ( 6. Verify in inFakt7. Full invoice detail page8. Issue a correction (KOR)Correction combines the original + corrected line correctly: PLN 259.00 + 99.99 = 358.99 PLN, verified against the sandbox ( Three real backend gaps surfaced by this walkthrough, all fixed and re-verified on #1293:
Screenshots committed at |
Tech-review (senior-level,
|
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>
Tech-review findings addressed (23ae0ab)Both IMPORTANT findings and all four suggestions from the tech-review comment are fixed: IMPORTANT
Suggestions
Verified: Branch is up to date with |
|
Added the "Default payment method" field this PR was missing for #1303 to be fully usable without editing raw JSON config:
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
left a comment
There was a problem hiding this comment.
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-selectsdefaultPaymentMethod: '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 (defaultPaymentMethoddefaults 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 correctly49, 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__referenceand.reg-card__actionsare 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>
0012af0 to
d441dbe
Compare
|
Addressed all findings from the tech-review: IMPORTANT
SUGGESTION
Also rebased onto #1293 (which is itself rebased onto latest Verified: |
piotrswierzy
left a comment
There was a problem hiding this comment.
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.tscorrected to "49 total / 10 plugin routes"; matches theEXPECTED_LAZY_ROUTE_COUNT = 49constant and the passing assertion. - Dead CSS
.reg-card__reference/.reg-card__actionsremoved (confirmed zero references), and.reg-card__notemigrated12px→0.75rem.
Verified
- The
.reg-cardchange 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.)
…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>
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
|
Follow-up: the "Download PDF" button introduced in this PR's E2E walkthrough was found to be non-functional during that same verification pass — Filed as #1321, fixed and merged on #1323 (stacked on this branch):
Description above updated to reflect this. |
piotrswierzy
left a comment
There was a problem hiding this comment.
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/workerplugins.ts+ webhook ingestion wiring (decoder / translator / connection-tester adapters + specs). Noapps/webchanges. - #1323 (
356b293e) — real PDF download viaRegulatoryDocumentReader: newuseInvoiceRenderedDocumentDownloadhook, one barrel export, and a "Download PDF" button in the inFaktinvoiceDetailSectionslot. - Main merges: demo-banner,
/v1versioning, 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 throughapiClient.invoicing.downloadDocument→ sharedrequestBlob(withencodeURIComponent). - Correct blob-download pattern (object URL + programmatic
<a download>,revokeObjectURLdeferred past the tick). - Dep direction
plugins/infakt→features/invoicingbarrel (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)
useInvoiceRenderedDocumentDownloadis an imperative one-shot (not a TanStack mutation) — deliberate, mirrorsuseKsefUpoDownload; 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.
…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>



















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./connections/new/infakt— name + API key + optional sandboxbaseUrloverride, 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 wiringStructuredConfigSectioninto the generic inline create form — verified against the realPlatformPicker/CreateConnectionFormsource that every plugin with asetupCardroutes to its own guided page instead.InfaktCredentialsPanel) and post-createbaseUrlediting (InfaktStructuredSection) — near-identical ports of the Erli / WooCommerce equivalents.InfaktInvoiceDetailSection) — renderssubmitted/accepted/rejectedregulatory states with the new.reg-cardtreatment (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.InfaktInvoiceCorrectionFlow) — near-1:1 port ofKsefInvoiceCorrectionFlow(same line-row model, same genericuseIssueCorrectionMutation+POST /invoices/:invoiceId/correctcontract). 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 backendCorrectionIssuercapability was confirmed real (PR feat(integrations): production-harden Infakt plugin - validators, retry classifier, NestJS module, tests #1292)..reg-cardredesign: a severity-stripe card treatment for theinvoiceDetailSectionprovider 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-c962be23dadbFull 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:
ConnectionTesterPortregistration forinfakt.accounting.v1successstatus mapped to the neutralclearedinstead ofaccepted, so the terminal-success UI (clearance reference chip) never renderedTest plan
pnpm --filter @openlinker/web lint— 0 errors (pre-existing warnings only)pnpm --filter @openlinker/web type-check— 0 errorspnpm --filter @openlinker/web test— all new + existing suites green (one pre-existing, unrelated local-env-only failure insettings-page.test.tsxcaused by a stale local.env.localVITE_API_BASE_URLoverride — not present in CI, not touched by this diff)EXPECTED_LAZY_ROUTE_COUNT48 → 49 for the newinfakt-setup.route.tsxFollow-up fix merged into this branch — PDF download (#1321 / #1323)
The E2E walkthrough's "Download PDF" affordance turned out to be non-functional:
InfaktInvoicingAdapterwas reading apdf_urlfield that doesn't exist on inFakt's invoice resource (verified live against the sandbox), soInvoiceRecord.pdfUrlwas alwaysnull. Filed as #1321, fixed and merged on #1323:InfaktInvoicingAdapternow implementsRegulatoryDocumentReader.getRegulatoryDocumentforkind: 'rendered', calling the realGET /invoices/{uuid}/pdf.json?document_type=original&invoice_type={kind}endpoint (confirmed live: returns a valid PDF binary directly). The deadpdf_url/print_urlfields are removed from the Infakt wire type.useInvoiceRenderedDocumentDownloadhook + a working "Download PDF" button inInfaktInvoiceDetailSection, routed through the existingGET /invoices/:invoiceId/document?kind=renderedcontroller route.pnpm --filter @openlinker/integrations-infakt build/lint/test— 115/115 passing, new adapter coverage;pnpm --filter @openlinker/web type-checkclean;pnpm check:invariantsclean; live sandbox verification of the real PDF endpoint before implementation.Closes #1282