feat(infakt): register Infakt plugin in API/worker + wire webhook ingestion#1293
Conversation
Tech review — PR #1293Scope reviewed: this PR's actual diff is the delta from Verified locally: SummarySolid, well-scoped increment. Plugin registration, jest-integration mapper entries, and the webhook decoder/translator pair all follow the exact pattern already established by InPost/Erli ( Issues[SUGGESTION] —
[SUGGESTION] —
[SUGGESTION] — PR description, "Manual" test-plan items
Architecture / standards compliance
Verdict✅ Approve — no blocking or important issues. The three suggestions above are optional polish; none affect correctness or architecture compliance. Standard caveat: this PR is a draft stacked on unmerged #1292/#1280, so merge order still applies as already noted in the description. |
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>
Tech review suggestions addressed (1363f91)Both [SUGGESTION]s from the tech review are fixed:
The third suggestion (unconfirmed handshake HTTP status) is a manual-verification item, not a code change — left as-is per the PR description's existing checklist. Verified: |
Manual QA — backend-only, before FE workRan the full manual checklist from the PR description against this branch's exact HEAD, plus a full live round-trip (issue → KSeF submission → real Infakt webhook → clearance) against the real Infakt sandbox. Found and fixed 8 bugs along the way — all pushed to this branch. Details below, in discovery order. Bugs found + fixed (chronological)
All 96 PR checklist results
Additional webhook coverageConnection lifecycle — create against real sandbox + validator negative pathsWebhook: valid HMAC signature → 202, publishedWebhook: invalid / missing signature → 401Webhook: malformed payload (valid signature over garbage JSON) → 400Webhook: replay / dedup — resending the identical signed payloadWebhook: capability-gated (ungated) + unhandled event typeFull live E2E confirmation (issue → KSeF → real webhook → cleared)This goes beyond the PR's own checklist — it's what #1293 actually unlocks (first time Infakt is reachable end-to-end via the API), so I verified it fully against the real sandbox rather than stopping at the wiring level. Step 1 — issue invoice (after all fixes)Step 2 — issue correction on that invoiceStep 3 — real webhook received (temporary cloudflared tunnel + the real HMAC secret from Infakt's own dashboard — NOT a synthetic payload)This confirms bug #8's fix too — Infakt's "Verify" button only went Aktywny after the handshake was changed to return 200 (see screenshot below). Step 4 — webhook-triggered reconcile job runs, clearanceReference populated
[SCREENSHOT 1 — Infakt dashboard, both invoices now
[SCREENSHOT 2 — webhook details + delivery history:
[SCREENSHOT 3 — invoice detail showing "Wysłano do KSeF" + the real KSeF number, matching Note on screenshot 2: the webhook shows Environment notes (not code issues — context for reproducing)
|
piotrswierzy
left a comment
There was a problem hiding this comment.
Reviewed the #1293-specific delta (25 files, isolated by diffing head against the #1280/#1292 base). Clean, well-tested, and consistent with the KSeF/Subiekt dual-registration precedent. Approve-in-spirit with one item to confirm before this leaves draft.
Must confirm before merge
- Handshake HTTP status —
webhook.controller.ts:44sets@HttpCode(HttpStatus.ACCEPTED)(202), so the subscription-verification echo returns 202. Infakt's verification ping commonly expects HTTP 200 to activate the webhook; if so, activation would silently fail. You already flagged this as unconfirmed in the test plan — please verify against Infakt's docs/sandbox. If 200 is required, return a distinct 200 for the handshake path rather than the shared 202.
Suggestions (non-blocking)
getVerificationEchotriggers on any JSON body with a stringverification_codeand runs before signature verification. A signed event carrying such a field would be mis-short-circuited and never processed. Low likelihood given Infakt's{event, resource}envelope, but consider gating on the absence of theeventenvelope (or a dedicated handshake header) to make detection unambiguous.- The handshake echo is returned pre-verification, reflecting an attacker-controlled
verification_code. Inherent to the subscription-verification pattern and gated on an active connection, so risk is minimal — noting for completeness only.
Verified clean
- #917 mapper guard: both
^@openlinker/integrations-infakt$and.../(.*)$entries present in both apps'jest-integration.cjs, matching the plugins.ts registration —check:invariantsshould pass. - Routing target
invoicing.regulatoryStatus.reconcileis real end-to-end: job-type union, payload type, worker handler + registration, and a DI int-spec all exist. No orphan enqueue. - Capability gating correct (
requiredCapability: 'Invoicing',ungatedwhen absent — covered by spec). - Port change is additive/optional (
detectHandshake?); DefaultWebhookDecoder + InPost unaffected. - HostServices exposes both registries; decoder keyed by platformType, translator by adapterKey (ADR-021/ADR-015).
- Dependency direction intact; no migrations; no WebhookProvisioningPort (UI-only setup, correctly out of scope).
Merge is also blocked on the base PRs #1280/#1292 landing first (draft-stacked).
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>
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>
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>
c8d8103 to
c446b43
Compare
|
Addressed all findings from the latest review: Must confirm — handshake HTTP status: already resolved in a prior commit ( Suggestion — ambiguous handshake detection: fixed in Suggestion — pre-verification reflection: added the length cap noted above as defense-in-depth. No further action taken beyond that — the echo is JSON-only (no HTML/script context) and gated on an active connection, so the residual risk matches the reviewer's own assessment ("minimal, noted for completeness"). Added 3 new unit tests covering the disambiguation and length-cap behavior (
Branch has also been rebased onto latest |
piotrswierzy
left a comment
There was a problem hiding this comment.
Re-review — both prior findings resolved ✅
Re-reviewed the new commits (head c446b431). Both items from the prior pass are addressed, with test coverage.
1. IMPORTANT — handshake HTTP status (202 → 200): ADDRESSED.
webhook.controller.ts now returns 200 for the handshake echo (res.status(HttpStatus.OK)) while keeping the route default 202 for the route/ignore paths. The inline comment cites live verification against Infakt's "Zweryfikuj" button (2026-07-01): 202 → "could not verify", 200 → accepted. Swagger documents both. Exactly what I asked for, backed by sandbox evidence.
2. SUGGESTION — disambiguate handshake from signed events: ADDRESSED (and hardened).
getVerificationEcho now gates on the absence of the event envelope (!('event' in parsed)), so a signed { event, resource } delivery carrying a verification_code field is no longer mis-short-circuited. Bonus: verification_code is length-capped at 256 chars — since the echo is returned pre-signature-verify, an oversized value is rejected rather than reflected verbatim. Covered by three new translator specs plus a service spec asserting the handshake short-circuits before verify/dedup/publish.
#917 jest mapper guard: intact. Both ^@openlinker/integrations-infakt$ and .../(.*)$ entries are present in both apps/api/test/jest-integration.cjs and apps/worker/test/jest-integration.cjs.
Wiring sanity: decoder registered under platformType='infakt' (matches the /webhooks/infakt/:connectionId path and the controller's ^[a-z]+$ provider regex); translator under the adapterKey. Registration mirrors the Subiekt/KSeF dual-app precedent.
No new blocking findings. Remaining gate before un-drafting is CI — run the full pnpm test:integration (not just the feature specs), since adding an adapter-manifest capability + webhook routing ripples into the routing int-specs. Also blocked mechanically on the base PRs #1280/#1292 landing first.
c446b43 to
0bd0a9b
Compare
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>
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>
Latest review addressedRe-reviewed the two prior findings (handshake HTTP status, handshake/signed-event disambiguation) — both already confirmed resolved in this review pass, no new blocking or important findings raised. The only remaining item noted was a CI gate: run the full Also rebased the branch cleanly onto latest No code changes were needed for this round; the branch is otherwise unchanged. |
… 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>
Tech Review — PR #1293SummaryRegisters the Infakt plugin ( Issues[IMPORTANT] —
[SUGGESTION] —
Verdict🔄 Approve with changes — no architecture/boundary violations found; the one IMPORTANT item is a reliability gap worth addressing (or explicitly accepting) before merge. |
0bd0a9b to
132bbe6
Compare
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>
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>
- 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>
Tech Review Follow-up — Findings AddressedBoth findings from the review above have been addressed: [IMPORTANT] Two-step issue+submit reliability — Fixed in
[SUGGESTION] Hardcoded Also fixed during rebase-onto-main: this branch was originally stacked on the now-merged #1292, so it needed rebasing to drop the two commits already incorporated into Quality gate (all green after fixes):
Branch has been rebased onto current |
Finding: no
|
Fixed:
|
…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>
…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>
c7fc44c to
651cac2
Compare
Tech Review — PR #1293 (delta since the last full review)Scope reviewed: only the two commits that landed after the previous full
SummaryBoth are correctness fixes found during live E2E against the real inFakt sandbox, not new features, and both are scoped tightly to the bug they fix. Architecture is clean and consistent with prior review passes — no new boundary violations. I traced the groszy-format bug through every reader of the affected fields ( Findings[SUGGESTION] —
[SUGGESTION] —
Both commits report a clean quality gate ( Verdict✅ Approve — both fixes are correct, narrowly scoped, well-tested, and consistent with the architecture established in prior review rounds. No blocking or important issues found in this delta. |
Tech Review — PR #1293 (full PR, all 8 commits)Scope: entire diff from SummaryThis PR does three things: (1) registers Issues[SUGGESTION] —
[SUGGESTION] —
[SUGGESTION] —
[SUGGESTION] —
[SUGGESTION] —
No BLOCKING or IMPORTANT findings. I checked for regressions the type change in the final commit ( Architecture & standards compliance
Verdict✅ Approve — no blocking or important issues across the full PR. The four SUGGESTIONs above are minor and mostly already tracked or self-acknowledged in the PR's own commit messages; none block merge. |
Confirmed fixed: amounts now correct against the real sandboxRe-ran the same live E2E check on a fresh seeded order after pulling Order: 1 x product, gross total PLN 189.00 (net PLN 153.66 @ 23% VAT, tax PLN 35.34). inFakt's own record for the resulting invoice ( "net_price": 15366, "tax_price": 3534, "gross_price": 18900
Correction, adding a second line at PLN 99.99 gross on top of the original 189.00: "net_price": 23495, "tax_price": 5404, "gross_price": 28899
OL's own UI reflects it end to end — order total PLN 189.00 shown identically on the order detail page, the invoice panel, and the dedicated invoice detail page (which also renders correctly now, for what it's worth — the earlier 404 I hit on that route must have been a stale branch snapshot on my end, not a real gap). Great fix, thanks for the quick turnaround. No further blockers from my side on the money-format front. |
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>
…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>
Remaining review suggestions addressed (0bca26e)Went through both submitted reviews plus the full re-review comment thread and confirmed all IMPORTANT/blocking items were already resolved in prior commits (handshake status 200, handshake/signed-event disambiguation, two-step issue+submit retry-safety documentation, groszy amount fix,
No behavior changes — pure polish, no new tests needed. Verified: Branch is even with |
Finding: KSeF "success" maps to `cleared` instead of `accepted` — terminal state never rendersWhile re-running the live sandbox E2E on a third fresh order, this time letting the real scheduled `invoicing.regulatoryStatus.reconcile` job pick up the clearance (rather than manually poking the DB like my earlier runs), the invoice ended up with: "regulatoryStatus": "cleared",
"clearanceReference": "8201194127-20260702-036663400000-EA"`toRegulatoryStatus` in `infakt-invoicing.adapter.ts`: case 'success':
return 'cleared';But per the core `RegulatoryStatusValues` contract (`apps/web/src/features/invoicing/api/invoicing.types.ts`):
KSeF's own `success` status is the terminal accepted state for inFakt too (same semantic as KSeF's own adapter's 200 -> `accepted` mapping) — there's no split-clearance regime here that would justify `cleared`. Because the FE's `InfaktInvoiceDetailSection` (and the shared `reg-card` treatment) only branch on `submitted` / `accepted` / `rejected`, an invoice stuck at `cleared` renders as:
This one's easy to miss in testing because my earlier runs happened to reach `accepted` — but that was from me manually UPDATE-ing the DB row while investigating the money-format bug, not from the real reconcile path. This run is the first time I let the actual scheduled job do it, and it exposed the mismatch. Suggested fix: change the mapping to case 'success':
return 'accepted';Happy to re-verify against the sandbox again once that's in — same seeded connection is still up. |
… 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>
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>
Fixed:
|
Confirmed fixed: accepted state renders correctly nowRe-ran on a fresh order (PLN 259.00) after pulling `c003797b`. This time the invoice reached KSeF acceptance and the panel shows exactly what it should:
Correction issued on top also checks out: PLN 259.00 + 99.99 = 358.99 PLN, confirmed against the sandbox (`gross_price: 35899`). Both the money-format fix and this status-mapping fix are holding up together. No further blockers from my side. |
|
Confirming once more, in reply to #1293 (comment) — this fix worked. Re-verified end to end on a fresh sandbox order (PLN 259.00) with both the money-format fix and this status-mapping fix together: gross amount round-trips exactly, and the invoice correctly shows KSeF: ACCEPTED with a real clearance reference chip instead of getting stuck on CLEARING. Screenshots posted on PR #1300. Thanks for the fast turnaround on both of these — no outstanding blockers from my side. |
piotrswierzy
left a comment
There was a problem hiding this comment.
Re-review (head c003797, rebased onto main) — APPROVE pending int-suite
Re-reviewed the non-draft, rebased head. Branch is cleanly rebased on current main (merge-base == main head); the #1292 grossToNet/taxRateNumeric refactor is now upstream and the rebase-conflict commit folded this branch's empty-taxRate fallback onto the real call path with the dead duplicate dropped.
Both prior findings remain resolved
- Handshake echo returns HTTP 200 (
webhook.controller.ts→res.status(HttpStatus.OK)), verified against Infakt's live verifier. getVerificationEchois gated on!('event' in parsed)and capsverification_codeat 256 chars.
Verification points
- #917 jest mappers: present in BOTH
apps/api/test/jest-integration.cjsandapps/worker/test/jest-integration.cjs. - Additive
detectHandshake?port + return-threading: method is optional;WebhookServicecalls it viadecoder.detectHandshake?.(...).DefaultWebhookDecoderand the InPost decoder don't declare it, so the optional-chain short-circuits toverify— no regression. New spec covers both the handshake-echo and the "proceeds to verify when null" paths. invoicingrouting:invoicing → invoicing.regulatoryStatus.reconcile, gated onInvoicingacross BOTHsupportedCapabilitiesandconnection.enabledCapabilities(pure reads). Payloadsatisfies RegulatoryStatusReconcilePayloadV1.- Plugin registration:
InfaktIntegrationModuleadded to both apps' plugin lists; decoder registered provider-keyed, translator adapterKey-keyed, plus the newInfaktConnectionTesterAdapter.
Notes (non-blocking)
- Merge gate: Type Check / Build / Lint / unit Test / PHP / Scaffolded builds are green; Integration Tests is still in-progress. Since this PR adds an
Invoicing-capability connection and a new inbound domain, the routing int-specs are load-bearing — please hold merge until that check is green. - Scope: the title understates several E2E-surfaced fiscal fixes bundled here — most importantly the integer-groszy wire-format correction (invoices were being issued at ~1/100th of the real total). All are correct and CI-green; a one-line note in the PR body would help future readers.
payment_method: 'cash'is correctly deferred to #1303/#1309. - The two-step issue→
sendToKsefretry-safety assumption is documented at both call sites with error-path tests — no change needed.
Verdict: APPROVE, contingent only on the in-progress Integration Tests check passing. No code changes requested.
Addressed remaining review notePiotr's latest (APPROVED) re-review had no blocking findings and no code changes requested — the only actionable item was documentation: the PR title/summary understated the E2E-surfaced fiscal fixes bundled alongside the plugin registration/webhook wiring. Change made: added a "Scope note" to the PR description calling out the two most significant fixes surfaced during review's live sandbox testing:
Both were already implemented, tested, and confirmed against the sandbox in prior commits — this only makes them visible to future readers of the PR description, per the review note. No source changes were needed for this pass. The only other open item from the review is mechanical (waiting for the |
… 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>
…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>
…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>
… 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>
piotrswierzy
left a comment
There was a problem hiding this comment.
Tech Lead re-review — head 31f0854 (was c003797)
Re-reviewed the two commits added since my approval. Both are pure Merge branch 'main' (271b0cb, 31f0854); they pulled in #1291 (KSeF default tax-rate) and #1312 (int-spec cleanup) from main. The c003797b..HEAD diff touches only KSeF and integration-test-helper files carried over from main — zero Infakt/webhook/routing files were re-touched, and the merges resolved with no conflict artifacts on this PR's own surface. No new hand-written code.
Prior-findings status (all intact at head):
- #917 jest-integration mappers for
@openlinker/integrations-infaktpresent in both apps/api and apps/workerjest-integration.cjs. - Plugin registered in both hosts'
plugins.ts. - Handshake echo returns HTTP 200 (
res.status(HttpStatus.OK)), route default stays 202. getVerificationEchogated on!('event' in parsed)+MAX_VERIFICATION_CODE_LENGTH = 256.- Additive optional
detectHandshake?port method — non-breaking forDefaultWebhookDecoder/InPost. invoicingrouting gated on theInvoicingcapability →invoicing.regulatoryStatus.reconcile.
No regressions, no new findings.
CI: Lint, Type Check, PHP Unit Tests, Scaffolded Builds are green; Integration Tests is in_progress and Test / Build are queued on the freshly-merged head (mergeable_state: unstable). The main-merges re-triggered the full pipeline — the Infakt surface is byte-identical to the approved state, so this is just a wait.
Verdict: Approve — contingent, as before, on Integration Tests (and Test/Build) completing green. Please don't merge until the pipeline lands green.
… correction (#1300) * 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> * docs(plans): implementation plan for inFakt FE plugin + invoice-section 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> * docs(plans): add E2E Playwright evidence phase to inFakt FE plan 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> * feat(web): inFakt FE plugin — connection setup + invoice detail + KOR 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> * test(web): add inFakt E2E screenshot-evidence scripts 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> * docs(infakt): add first 4 manual inFakt-dashboard walkthrough screenshots 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> * docs(infakt): add real inFakt E2E screenshots (connection, issuance, 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> * docs(infakt): drop broken invoice-detail-page screenshot (route missing on this backend branch, not blocking) Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * docs(infakt): re-capture E2E screenshots with the money-format fix verified 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> * docs(infakt): move E2E screenshots to libs/integrations/infakt/docs/assets/ 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> * docs(infakt): add clean not-issued/submitted screenshots + fresh Part 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> * fix(web): address inFakt FE plugin tech-review findings 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> * docs(infakt): re-capture screenshots with the cleared->accepted fix verified 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> * feat(web): Infakt default-payment-method picker (wizard + edit disclosure) 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> * docs(infakt): re-verify screenshots against current code post tech-review (order PLN 899.00) Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * docs(infakt): re-capture connection wizard screenshots with payment-method 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> * fix(web): address inFakt FE plugin PR #1300 tech-review findings 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> * fix(infakt): implement real PDF download via RegulatoryDocumentReader (#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 --------- Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> Signed-off-by: Norbert Kulus <norbert.kulus@silksh.pl> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.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>
…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>
… 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>



Summary
Stacked on #1292 (#1280, not yet merged) - this branch is based on
1280-infakt-plugin-hardening-testsso it carries that PR's diff until it merges. Opened as draft for that reason.Part of the Infakt epic (#1279). Closes #1281.
InfaktIntegrationModuleinapps/api/src/plugins.tsandapps/worker/src/plugins.ts(mirrors the KSeF/Subiekt dual-registration pattern) so the host can resolve theInvoicingcapability for Infakt connections@openlinker/integrations-infakttoapps/api/apps/workerpackage.json, and the jest-integration moduleNameMapper entriesInfaktInboundWebhookDecoderAdapter(provider-keyedinfakt): HMAC-SHA256 signature verify (no timestamp header, so no replay-window check for this provider), subscription-verification handshake detection, envelope extraction — wraps the already-testedInfaktWebhookTranslatorfrom the feat(infakt): production-harden Infakt plugin – validators, retry classifier, NestJS module, unit tests #1280 POC rather than duplicating its crypto/parsingInfaktWebhookEventTranslatorAdapter(adapterKey-keyedinfakt.accounting.v1): mapssend_to_ksef_success/send_to_ksef_errorto the newinvoicinginbound domain; other Infakt events dead-letter (null)InboundWebhookDecoderPortwith an optionaldetectHandshakestep (runs beforeverify) so a provider's subscription-verification ping can echo a body back —WebhookService/WebhookControllernow thread a return value through instead of always resolvingvoid. This is additive;DefaultWebhookDecoderand existing decoders (InPost) are unaffected.'invoicing'to the closedInboundEventDomainValuesunion and a newInboundRoutingPolicyServicecase: aninvoicingwebhook event enqueues the existinginvoicing.regulatoryStatus.reconcilejob (gated on theInvoicingcapability). No new by-id job — the webhook is a trigger, not the source of truth (mirrors the InPost/webhook philosophy already documented inarchitecture-overview.md); the scheduled reconcile still drains the full non-terminal frontier regardless.No
WebhookProvisioningPort(Infakt webhook setup is UI-only, per issue scope).Scope note: beyond the plugin registration/webhook wiring described above, this PR also carries several fiscal fixes surfaced by live sandbox E2E testing during review — most notably an integer-groszy wire-format correction (invoices were being issued at ~1/100th of the real total) and a KSeF
success->acceptedregulatory-status mapping fix (previously mapped tocleared, which the FE never renders as terminal). Both are covered by tests and confirmed against the real sandbox.Test plan
pnpm --filter @openlinker/integrations-infakt test- 9 suites, 93 tests passing (2 new spec files)pnpm --filter @openlinker/core test- 138 suites, 1410 tests passing (incl. newinvoicingrouting cases)pnpm --filter @openlinker/api test- 48 suites, 580 tests passing (incl. new handshake short-circuit cases)pnpm --filter @openlinker/worker test- 17 suites, 188 tests passingpnpm lint- 0 errors (only pre-existing warnings)pnpm type-check- 0 errors across all packagespnpm check:invariants- cross-context imports, service interfaces, jest-integration-mappers all passpnpm start:dev:apistarts without errors with Infakt module registeredGET /adapterslistsinfakt.accounting.v1(route isGET /adapters, not/integrations/adapters)POST /webhooks/infakt/{connectionId}with valid HMAC signature returns 202POST /webhooks/infakt/{connectionId}with invalid signature returns 401verification_codeback correctly — required a fix: Infakt's own verifier only accepts HTTP 200 for the echo, not 202 (see manual QA comment below for the full writeup, incl. a live E2E confirmation and 7 other bugs found + fixed against the real sandbox)