Skip to content

feat(infakt): consume invoice_marked_as_paid webhook + payment-status sync (#1354) - #1361

Merged
piotrswierzy merged 6 commits into
mainfrom
1354-infakt-marked-as-paid-webhook
Jul 6, 2026
Merged

feat(infakt): consume invoice_marked_as_paid webhook + payment-status sync (#1354)#1361
piotrswierzy merged 6 commits into
mainfrom
1354-infakt-marked-as-paid-webhook

Conversation

@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator

What changed

The inFakt invoice_marked_as_paid (+ _via_async_api) webhook was recognized but dropped, so OL never learned when an invoice became paid. This routes it onto the invoicing domain and refreshes payment status via an authoritative provider re-read (never trusting the webhook body), mirroring the KSeF-status reconciliation pattern.

  • CORE (invoicing): neutral PaymentStatus (unknown|unpaid|partially-paid|paid) + PaymentStatusResult; paymentStatus on InvoiceRecord (+ isPaid); PaymentStatusReader sub-capability + guard; PaymentStatusRefreshService (by-id, authoritative re-read, write-on-change, graceful no-op); ORM column + repo mapping + findByProviderInvoiceId; migration 1818000000004-add-invoice-payment-status.ts.
  • CORE (sync): new CanonicalInboundEvent domain invoice-payment; job type invoicing.paymentStatus.refreshByExternalId; InboundRoutingPolicy routes it to the refresh job, gated on Invoicing.
  • Worker: PaymentStatusRefreshHandler.
  • Integration (inFakt): adapter implements PaymentStatusReader (maps inFakt status/paid_date via authoritative GET); translator maps both payment events to invoice-payment; decoder derives externalId from the invoice uuid.

How to test

  • Unit: pnpm --filter @openlinker/core test (1450 pass), pnpm --filter @openlinker/integrations-infakt test (153 pass) - translator/routing/refresh-service/adapter/decoder specs.
  • Migration ordering check passes. Run pnpm --filter @openlinker/api migration:run on a test DB to apply the paymentStatus column.

Scope

  • Inbound half (consume the webhook + refresh status) is implemented - the must-have.
  • Outbound half (InvoicePaymentMarker -> POST /async/invoices/{uuid}/paid.json + endpoint/UI) is deferred; follow-up filed and linked below.
  • Part of the inFakt accounting epic feat(infakt): Infakt accounting integration [EPIC] #1279.

Closes #1354

🤖 Generated with Claude Code

… sync (#1354)

The inFakt invoice_marked_as_paid (+ _via_async_api) webhook was
recognized but dropped, so OL never learned when an invoice became paid.
Route it onto the invoicing domain and refresh payment status via an
authoritative provider re-read (never trusting the webhook body), mirroring
the KSeF-status reconciliation pattern.

- CORE (invoicing): neutral PaymentStatus (unknown|unpaid|partially-paid|
  paid) + PaymentStatusResult; paymentStatus on InvoiceRecord (+ isPaid);
  PaymentStatusReader sub-capability + guard; PaymentStatusRefreshService
  (by-id, authoritative re-read, write-on-change, graceful no-op); ORM
  column + repo mapping + findByProviderInvoiceId; migration.
- CORE (sync): CanonicalInboundEvent domain invoice-payment; job type
  invoicing.paymentStatus.refreshByExternalId; InboundRoutingPolicy routes
  it to the refresh job, gated on Invoicing.
- Worker: PaymentStatusRefreshHandler.
- Integration: adapter implements PaymentStatusReader (maps inFakt status/
  paid_date via authoritative GET); translator maps both payment events to
  invoice-payment; decoder derives externalId from the invoice uuid.

Outbound InvoicePaymentMarker (mark-paid to inFakt) is deferred to a
follow-up.

Closes #1354

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

@piotrswierzy piotrswierzy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

/pr-review — systematic review

Textbook-clean feature — the only thing standing between it and merge is a cross-PR migration-timestamp collision.

🔴 BLOCKING — 1818000000004 collides with PR #1341

Both PRs claim the same 13-digit prefix on an invoice_records migration:

  • this PR: 1818000000004-add-invoice-payment-status.ts
  • #1341 (open, closes #1338): 1818000000004-backfill-ksef-provider-invoice-number.ts

That's the #374 collision class (docs/migrations.md § Timestamp uniqueness invariant). The class names differ so there's no duplicate-migrations-row hazard, but the shared prefix violates the uniqueness + strictly-greater invariants. check-migration-timestamps.mjs compares each branch only against origin/main, so both pass lint in isolation and the second-to-merge fails pnpm lint. Latest prefix on main is 1818000000003, so …004 is otherwise the correct next step. Fix: whichever lands second re-prefixes to 1818000000005 + updates its class suffix — recommend this PR pre-empt to 1818000000005, since #1341 is a smaller self-contained bugfix. (I've flagged the same on #1341.)

✅ Everything else verified clean

  • Migration safety: up()/down() present; ADD COLUMN IF NOT EXISTS "paymentStatus" text NOT NULL DEFAULT 'unknown' — idempotent, metadata-only in PG11+, existing rows backfill to unknown (never falsely unpaid); ORM @Column default matches the DDL (no migration:show drift).
  • ADR-026: PaymentStatusValues (unknown|unpaid|partially-paid|paid), PaymentStatusResult, PaymentStatusReader, and the sync invoice-payment domain carry zero inFakt/PL vocabulary — all status/paid_date mapping confined to toPaymentStatus() in the adapter.
  • Authoritative re-read: refreshByExternalId re-reads via adapter.getPaymentStatus(record) and never touches the webhook body (payload carries only externalInvoiceId); write-on-change only; graceful no-ops (unsupported warn when !isPaymentStatusReader, not-found when no record) — only a transport failure propagates for retry. Mirrors the webhook=trigger / poll=source-of-truth principle and the RegulatoryStatusReader precedent.
  • Sub-capability + routing: payment-status-reader.capability.ts + co-located isPaymentStatusReader guard, barrel-exported; InboundRoutingPolicy gates invoice-payment on capability Invoicing (not platformType), switch stays exhaustive; runtime-detected (correctly not in supportedCapabilities), so no manifest/routing-int-spec ripple.
  • Layers/types: no any; service implements IPaymentStatusRefreshService in a separate interface file; Symbol token in invoicing.tokens.ts (export *'d); worker handler registered in both the module and handler-registration.service.ts; cross-context imports barrel-only; findByProviderInvoiceId backed by the pre-existing partial index, newest-first.
  • Tests: colocated and branch-complete (refresh-service updated/unchanged/not-found/unsupported; adapter mapping + 503 propagation; decoder; translator both events + dead-letter).

✅ Positives

Clean orthogonal modelling — paymentStatus is separate from issuance status and regulatoryStatus, and invoice-payment is deliberately split from the invoicing domain so a paid document nudges the by-id refresh rather than the regulatory sweep. Fiscal-safe unknown default applied consistently across type doc, entity, ORM, and migration.

Verdict: 🔄 Approve with changes — re-prefix the migration to 1818000000005 to clear the #1341 collision; the code is otherwise merge-ready.

norbert-kulus-blockydevs added a commit that referenced this pull request Jul 6, 2026
Re-prefix the backfill migration from 1818000000004 to 1818000000005 -
PR #1361 also claims 1818000000004 for a different invoice_records
migration, and TypeORM 0.3.17 has no tie-breaker for a shared prefix.
Per docs/migrations.md's ordering rule, whichever PR lands second
re-prefixes; doing it here avoids leaving the collision to be caught
reactively by CI after one of the two merges.

Addresses Piotr's review on #1341.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Resolves the migration-timestamp collision flagged in review: PR #1341
(closes #1338) already claims the 1818000000004 prefix on an
invoice_records migration. Re-prefix this PR's migration to the next
free slot (1818000000005) so both branches satisfy the timestamp
uniqueness + strictly-greater invariant regardless of merge order.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator Author

Thanks for the review, @piotrswierzy. All findings addressed - in this case the single 🔴 BLOCKING item (there were no inline comments or open suggestions to fold in):

🔴 Migration-timestamp collision with #1341 - fixed (bc7bb058)

Re-prefixed the migration 1818000000004 -> 1818000000005 as recommended, so this PR pre-empts and #1341 keeps the …004 slot:

  • File: 1818000000004-add-invoice-payment-status.ts -> 1818000000005-add-invoice-payment-status.ts
  • Class: AddInvoicePaymentStatus1818000000004 -> AddInvoicePaymentStatus1818000000005
  • name property updated to match.

…005 is the correct next step over main's latest …003 regardless of which of the two PRs merges first, so the timestamp uniqueness + strictly-greater invariant now holds either way.

Verification:

  • node scripts/check-migration-timestamps.mjs -> OK (ordering vs origin/main: checked)
  • pnpm --filter @openlinker/api type-check -> clean

No functional change - up()/down() DDL, the ORM column default, and every other reviewed aspect are untouched.

@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator Author

/tech-review - independent pass

Independent tech-lead review of PR #1361, formed from scratch against the diff and the OpenLinker docs. Not an echo of any prior review.

Summary

Solid, well-scoped work. The inbound-half implementation is faithful to the documented patterns: a distinct invoice-payment canonical domain, capability-gated routing to a by-id refresh job, and an authoritative provider re-read (PaymentStatusReader) that never trusts the webhook body - a clean mirror of the RegulatoryStatusReader reconcile pattern (#1121). CORE/Integration boundaries hold (no libs/integrations import in core; no paid_date/faktura vocabulary leaks past the adapter, honouring ADR-026), the service codes against ports only, and the Symbol-token + separate-interface conventions are respected. The migration timestamp collision that was flagged earlier is correctly fixed (1818000000005, strictly greater than main's tail ...003, class suffix matches, node scripts/check-migration-timestamps.mjs is green). I found no BLOCKING issues and no NEW correctness defects; the remainder are minor.

Issues

[SUGGESTION] - apps/worker/src/sync/handlers/payment-status-refresh.handler.ts (line 145)

getPayload carries non-trivial branching validation (schemaVersion, non-empty externalInvoiceId, SyncJobExecutionError on both invalid-payload and downstream failure) but has no unit spec. Handler specs are not the norm here (only 2 of 23 handlers have one), so this is not blocking - but the direct sibling this PR mirrors, regulatory-status-reconcile.handler.spec.ts, does have one, and the core service/routing/adapter paths are all otherwise well covered. A short spec pinning the reject path and the success delegation would match that sibling.

[SUGGESTION] - libs/core/src/invoicing/application/services/payment-status-refresh.service.ts (line 502)

Payment status is persisted through repo.updateOutcome(record.id, { paymentStatus }). Reusing the generic InvoiceOutcomePatch update works and avoids method proliferation, but updateOutcome is a slightly misleading name for a payment-lifecycle write that is orthogonal to the issuance outcome. Not worth churn on its own; if the patch surface grows, consider renaming to a neutral applyPatch/update. engineering-standards.md naming guidance, informational only.

[SUGGESTION] - libs/core/src/invoicing/application/services/payment-status-refresh.service.interface.ts (line 234)

PaymentStatusRefreshOutcomeValues (a runtime as const array) and PaymentStatusRefreshResult live in the .service.interface.ts file rather than a dedicated *.types.ts, which is the literal engineering-standards.md rule ("all types must be defined in separate files"). I am flagging it only as a SUGGESTION because there is an established in-context precedent - RegulatoryStatusReconcileResult is defined the same way in its service-interface file - so this is consistent with the surrounding code. Worth a note, not a change.

[SUGGESTION] - libs/integrations/infakt/src/infrastructure/adapters/infakt-invoicing.adapter.ts (line 165)

toPaymentStatus classifies "partial" via status.includes('partial') || status.includes('partly') and treats every other non-paid status as unpaid. This is a reasonable, tested heuristic and correctly contained in the adapter (ADR-026), but substring matching is fragile if inFakt introduces a new status string. Acceptable for now; a comment enumerating the known inFakt status vocabulary (or matching against a known set) would make future drift obvious. The paid_date-present fallback to paid is a sensible defensive belt.

Notes verified (no action)

  • Migration correctness: up/down are symmetric and idempotent (ADD COLUMN IF NOT EXISTS / DROP COLUMN IF EXISTS); NOT NULL DEFAULT 'unknown' backfills existing rows to a state that never falsely asserts "unpaid", matching the documented intent. No ORM drift - the ORM column (text, non-null, default 'unknown') matches the DDL, and no new index is required because IDX_invoice_records_provider_invoice_id (partial, providerInvoiceId IS NOT NULL) already exists on main and backs findByProviderInvoiceId. The repo comment referencing that index is accurate.
  • Backward compatibility: the new paymentStatus constructor parameter on InvoiceRecord is last and defaulted (= 'unknown'), so existing constructor call sites are unaffected.
  • Authoritative re-read / retry-safety: the service resolves the adapter, narrows with isPaymentStatusReader (clean unsupported no-op before any DB hit), looks up the record (benign not-found no-op), then re-reads provider state and writes only on change. A transport failure propagates for job retry - correct.
  • Exhaustiveness: adding invoice-payment to the closed domain union keeps the default: never guard in the routing policy intact; the translator refactor (domain === null gate) preserves the existing send_to_ksef_* -> invoicing behaviour.
  • Security: no secrets, capability-gated routing, webhook body treated as trigger only.

Verdict

Approve - ready to merge. All open items are SUGGESTIONs; none block. If any is worth acting on, the handler unit spec is the highest-value follow-up.

… classifier

Address tech-review SUGGESTIONs on PR #1361:

- Add unit spec for PaymentStatusRefreshHandler pinning the getPayload
  reject branches (missing/invalid payload, wrong schemaVersion, empty
  externalInvoiceId), the success delegation, and the OL-shaped error
  wrapping. Mirrors regulatory-status-reconcile.handler.spec.ts.
- Make toPaymentStatus classify against a documented, known inFakt token
  set (verified against the invoice_statuses + payment_statuses meta
  dictionaries) instead of a bare === 'paid' check; behaviour unchanged.
  Extend the adapter spec with partial_payment / draft / printed cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator Author

Tech-review follow-up (all 4 SUGGESTIONs addressed)

Thanks for the pass. Addressed in 4f309826.

Fixed

#1 - Missing handler unit spec (apps/worker/src/sync/handlers/payment-status-refresh.handler.ts)
Added payment-status-refresh.handler.spec.ts, mirroring the sibling regulatory-status-reconcile.handler.spec.ts. It pins:

  • every getPayload reject branch (undefined / null / non-object payload, wrong schemaVersion, missing / non-string / empty externalInvoiceId) throwing a SyncJobExecutionError and never calling the service;
  • the success path delegating to refreshByExternalId(connectionId, externalInvoiceId) and returning { outcome: 'ok' };
  • a thrown downstream error re-wrapped in an OL-shaped SyncJobExecutionError carrying jobId / jobType / connectionId.

9 tests, all green.

#4 - toPaymentStatus substring heuristic (libs/integrations/infakt/src/infrastructure/adapters/infakt-invoicing.adapter.ts)
Reworked the classifier to match against documented known-token sets (INFAKT_PAID_TOKENS / INFAKT_PARTIAL_TOKENS) instead of a bare === 'paid' plus ad-hoc substring. Added an enumerating comment listing both inFakt vocabularies, verified live against the meta dictionaries:

  • invoice status (invoice_statuses): draft | sent | printed | paid
  • payment status (payment_statuses): paid | unpaid | partial_payment | payment_not_applicable

Behaviour is unchanged (paid -> paid; partial/partly token -> partially-paid; paid_date belt -> paid; else unpaid). Extended the adapter spec table with partial_payment (the payment_statuses-dictionary token), draft, and printed (no paid_date) cases so future drift is caught. 51 tests, all green. Vocabulary stays entirely in the adapter (ADR-026 clean).

Deliberately kept (with reasons)

#2 - updateOutcome naming (payment-status-refresh.service.ts)
Kept. updateOutcome is the shared InvoiceRecordRepositoryPort write used by 10+ call sites across three services (InvoiceService, RegulatoryStatusReconciliationService, this one), the port, the repo impl, the SourceDocumentImmutableError, and the type docs. Renaming to a neutral applyPatch would be exactly the kind of sweeping ripple across unrelated files the review flagged as "not worth churn on its own". If the patch surface grows we can revisit then.

#3 - result types in .service.interface.ts (payment-status-refresh.service.interface.ts)
Kept. Moving PaymentStatusRefreshOutcomeValues + PaymentStatusRefreshResult to a dedicated *.types.ts would diverge from the direct in-context precedent - RegulatoryStatusReconcileResult lives in its own service-interface file the same way. Splitting only this one would make the invoicing context less internally consistent for a marginal literal-rule gain. Consistency with the established sibling pattern wins here.

Checks (scoped to touched packages)

  • pnpm --filter @openlinker/integrations-infakt type-check - clean
  • pnpm --filter @openlinker/worker type-check - clean
  • pnpm --filter @openlinker/integrations-infakt test -- infakt-invoicing.adapter.spec - 51 passed
  • pnpm --filter @openlinker/worker test -- payment-status-refresh.handler.spec - 9 passed

No migrations touched.

@piotrswierzy piotrswierzy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

/pr-review — delta re-review (9b1cac404f309826)

The non-migration delta is straight approve-quality. The sole remaining blocker is the migration collision — which moved but wasn't resolved.

🔴 CROSS-PR BLOCKING — the …004 collision became a …005 collision (still unresolved)

The migration was re-prefixed 18180000000041818000000005 (class + name both → AddInvoicePaymentStatus1818000000005) — but #1341 also independently re-prefixed to 1818000000005. So both now share …005 (and …004 is an unused gap). origin/main contains neither, so it's invisible in each isolated diff, but the second-to-merge still fails pnpm lint. Fix (needs coordination with #1341): exactly one PR owns …005; the other takes a distinct slot — …004 is now free and is the correct next step after main's …003 tail. Flagged the same on #1341.

✅ Non-migration delta — approve-quality

  • Adapter (+27): a behavior-preserving refactor, not a correctness change — status === 'paid'INFAKT_PAID_TOKENS.includes(status) (tokens ['paid'], identical), the partial branch → INFAKT_PARTIAL_TOKENS.some(...) (logically identical to the prior includes('partial')||includes('partly')), paid_date fallback + unpaid default unchanged. Now driven by named constants + a doc comment citing the verified live inFakt dictionaries. Neutrality holds — inFakt tokens stay in the adapter; only the neutral PaymentStatus crosses to core; getPaymentStatus still returns unknown when providerInvoiceId is absent and never trusts a webhook body.
  • New payment-status-refresh.handler.spec.ts (+91): real handler test, colocated, AAA — 7 it.each payload-validation branches all assert SyncJobExecutionError and the service is never called; success asserts delegation to refreshByExternalId('conn-1','inv-1') + {outcome:'ok'}; error asserts OL-shaped wrapping with jobId/jobType/connectionId. Correctly leaves the unsupported/not-found branching to the refresh service's own spec (the handler is a thin delegate). Closes the worker-handler test gap.
  • Adapter spec (+5): exercises the new token constants (partial_payment → partially-paid, draft/printed → unpaid).

🟢 Minor (non-blocking, pre-existing)

The doc comment attributes partial_payment to inFakt's payment_statuses dictionary, but toPaymentStatus reads invoice.status (dictionary draft|sent|printed|paid, no partial token), and InfaktInvoice carries only a single status field — so the partial branch may be unreachable against real invoice.status data. It's fiscally conservative (partial safer than a false "paid") and unchanged from the prior head — worth a note, not a gate.

✅ No regressions

Authoritative re-read still never trusts the webhook body; write-on-change owned by the (untouched) service; sub-capability PaymentStatusReader + isPaymentStatusReader guard barrel-exported; fiscal-safe unknown default preserved; no any. Migration body still idempotent (ADD COLUMN IF NOT EXISTS … DEFAULT 'unknown') with a symmetric down().

Verdict: 🔄 Approve with changes — resolve the 1818000000005 collision with #1341 (one PR takes a distinct slot, e.g. …004); everything else is merge-ready.

norbert-kulus-blockydevs added a commit that referenced this pull request Jul 6, 2026
…ct with #1361

Both #1341 and #1361 landed on 1818000000005, sharing the same TypeORM
timestamp prefix on invoice_records (the #374 collision class). Move this
PR's backfill to the free 1818000000004 slot — the correct next step after
main's 1818000000003 tail — leaving 1818000000005 uniquely to #1361. Updates
the class-name suffix and the controller comment that references the filename.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator Author

Migration timestamp collision with #1341 — resolved on the #1341 side

Heads-up on the 1818000000005 migration collision @piotrswierzy flagged (both this PR and #1341 added a 1818000000005-* migration on invoice_records).

I've re-prefixed #1341's backfill down to the free 1818000000004 slot. That leaves 1818000000005-add-invoice-payment-status.ts uniquely to this PR — so #1361 needs no change, just keep …005 as-is. Once both land, ordering is …003 (main) → …004 (#1341 backfill) → …005 (#1361 payment status), all distinct. check-migration-timestamps.mjs passes.

…rd (#1354)

Piotr's re-review flagged that the token doc attributed `partial_payment` to
the field `toPaymentStatus` reads, but that function reads `InfaktInvoice.status`
(the `invoice_statuses` dictionary: draft|sent|printed|paid — no partial token),
so the partial branch is unreachable against real `status` data. Clarify in the
comment that the `partial`/`partly` match is an intentional forward-looking guard
against future vocabulary drift, not a currently-reachable classification.
Comment-only; behavior unchanged (fiscally conservative).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
@norbert-kulus-blockydevs

Copy link
Copy Markdown
Collaborator Author

🟢 Minor doc finding addressed (4f30982643406b8e)

Also actioned the last-pass 🟢 Minor: the token doc attributed partial_payment to the field toPaymentStatus reads, but that function reads InfaktInvoice.status (the invoice_statuses dictionary — draft|sent|printed|paid, no partial token), so the partial/partly branch is unreachable against real status data.

Fix is comment-only (behavior unchanged, still fiscally conservative): the doc now states plainly that the partial match is an intentional forward-looking guard against future Infakt vocabulary drift, not a currently-reachable classification — kept so a part-settled document would classify as partially-paid rather than silently unpaid if Infakt ever surfaces a settlement token on status.

All findings on this PR (blocker + minor) are now addressed.

@piotrswierzy piotrswierzy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

/pr-review — delta re-review (4f3098265aac870c)

The migration collision is resolved — the sole remaining blocker. Approving.

Verified locally: this PR keeps 1818000000005-add-invoice-payment-status.ts and #1341 moved to 1818000000004 — two distinct, sequential prefixes above main's 1818000000003 tail. No more collision.

The only author change since my last review (cutting through the merge-of-main noise from #1284/#1342/#1335 landing on main) is one commit — docs(infakt): clarify partial-payment branch is a forward-looking guard — which addresses my minor note that the partial mapping branch may be unreachable against real invoice.status data by documenting it as an intentional forward-looking guard. Good.

Everything else was already verified clean at the prior head: authoritative re-read (never trusts the webhook body), write-on-change only, PaymentStatusReader sub-capability + barrel-exported guard, routing gated on Invoicing capability, fiscal-safe unknown default, the 91-line handler spec, idempotent ADD COLUMN IF NOT EXISTS … DEFAULT 'unknown' migration with a symmetric down(), no any.

Verdict: ✅ Approve.

piotrswierzy pushed a commit that referenced this pull request Jul 6, 2026
…ord (#1341)

* fix(ksef): persist FA(3) P_2 document number on the issued InvoiceRecord

KsefInvoicingAdapter.issueInvoice stamped the FA(3) P_2 number into the
XML (invoiceNumber: cmd.orderId) but returned the InvoiceRecord with
providerInvoiceNumber = null, and nothing ever backfilled it. Since the
correction precondition landed (#1289), every KSeF KOR was rejected with
"missing document number / issue date" even for fully cleared invoices.

Persist the same P_2 value on the record. Verified live on the KSeF test
environment during the 2026-07-03 E2E run: with this change (plus a
backfill for pre-existing records) the full KOR flow issues and clears.

Existing rows need a one-off backfill, e.g.:
  update invoice_records set "providerInvoiceNumber" = "orderId"
  where "providerType" = 'ksef' and "providerInvoiceNumber" is null;

Closes #1338

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

* refactor(ksef): single documentNumber source for FA(3) P_2 stamp and persisted record

Review follow-up on #1341: hoist the P_2 value into one const consumed by
both the FA(3) builder input and the InvoiceRecord constructor, so the
persisted providerInvoiceNumber can never drift from the number stamped
in the XML when the #1118 sequential-numbering follow-up replaces the
orderId placeholder.

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

* test(ksef): assert providerInvoiceNumber on correction record; clarify 422 for legacy rows

Review follow-up on #1341:
- Add an explicit providerInvoiceNumber assertion to the issueCorrection
  happy-path test. The correction path is the primary consumer of the
  #1289 precondition #1338 unblocks; guarding it directly (not only via
  the issueInvoice delegation) protects against a future refactor that
  special-cases correction-record construction.
- Extend the correction 422 message so an operator hitting a pre-fix
  KSeF row (fully issued/cleared, but null providerInvoiceNumber) gets a
  signal that a one-off backfill is needed, instead of the misleading
  'may not be fully issued yet' alone.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(ksef): ship provider invoice number backfill as an idempotent migration

Review follow-up on #1341 (piotrswierzy): a manual deployment-note SQL
step is easy to forget and, as flagged, easy to get wrong without care
around the camelCase column quoting. Ship it as a committed, idempotent
TypeORM migration instead, per docs/migrations.md — it runs
automatically via migration:run, is reviewable, and the IS NULL guard
makes re-running it a no-op. Scoped to status = 'issued' so failed/
pending rows (which legitimately have no stamped P_2) are untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(ksef): flag correction P_2 collision surfaced by #1338's persistence fix

Review follow-up on #1341 (piotrswierzy): issueCorrection delegates to
issueInvoice with the same orderId, so a KOR stamps the same P_2 as the
original document it corrects - invalid for FA(3), where P_2 must be
unique per document. Pre-existing (the orderId-as-P_2 placeholder
predates this fix) but now visible since providerInvoiceNumber is
actually persisted and read by the correction precondition. Left as an
explicit code note pending the real per-seller sequential FA(3)
numbering source.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(ksef): resolve migration-timestamp collision with #1361

Re-prefix the backfill migration from 1818000000004 to 1818000000005 -
PR #1361 also claims 1818000000004 for a different invoice_records
migration, and TypeORM 0.3.17 has no tie-breaker for a shared prefix.
Per docs/migrations.md's ordering rule, whichever PR lands second
re-prefixes; doing it here avoids leaving the collision to be caught
reactively by CI after one of the two merges.

Addresses Piotr's review on #1341.

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

* fix(ksef,api): address independent tech-review suggestions on #1341

Move the internal issue reference out of the operator-facing
UnprocessableEntityException message in the correction precondition
check (moved into a code comment instead, matching the existing
message-style convention elsewhere in the controller family).

File and cross-reference #1364 as the dedicated tracking issue for the
KOR-shares-original-document-number FA(3) semantics gap flagged in the
issueCorrection NOTE comment, so it's discoverable going forward.

No action needed on the migration down() no-op per reviewer.

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

* fix(ksef): re-prefix backfill migration to 1818000000004 to de-conflict with #1361

Both #1341 and #1361 landed on 1818000000005, sharing the same TypeORM
timestamp prefix on invoice_records (the #374 collision class). Move this
PR's backfill to the free 1818000000004 slot — the correct next step after
main's 1818000000003 tail — leaving 1818000000005 uniquely to #1361. Updates
the class-name suffix and the controller comment that references the filename.

Co-Authored-By: Claude Opus 4.8 (1M context) <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 Fable 5 <noreply@anthropic.com>
@piotrswierzy

Copy link
Copy Markdown
Collaborator

@norbert-kulus-blockydevs resolve conflicts

…-paid-webhook

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

# Conflicts:
#	libs/integrations/infakt/src/infrastructure/adapters/__tests__/infakt-invoicing.adapter.spec.ts
#	libs/integrations/infakt/src/infrastructure/adapters/infakt-invoicing.adapter.ts
@piotrswierzy
piotrswierzy merged commit 861680f into main Jul 6, 2026
8 checks passed
norbert-kulus-blockydevs added a commit that referenced this pull request Jul 14, 2026
…the 4 sub-capabilities shipped since #1307's last update

InfaktInvoicingAdapter now implements 10 Invoicing sub-capabilities;
this branch's docs only covered 6 (missing RegulatoryResubmitter #1356,
PaymentStatusReader #1354, PaymentMarker #1362, InvoiceEmailSender #1353).
Also fixes a since-stale claim that invoice_marked_as_paid webhooks are
ignored (they now drive PaymentStatusReader via #1354/#1361), documents
the dedicated corrective_invoices.json endpoint (#1342), and adds the
per-connection shipping-line label override (#1517/#1562).

docs/capabilities.md and architecture-overview.md's InvoicingPort
sub-capability list had the same 6-of-10 staleness independent of this
branch - fixed alongside since they're the same underlying gap.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
norbert-kulus-blockydevs added a commit that referenced this pull request Jul 22, 2026
…ord (#1341)

* fix(ksef): persist FA(3) P_2 document number on the issued InvoiceRecord

KsefInvoicingAdapter.issueInvoice stamped the FA(3) P_2 number into the
XML (invoiceNumber: cmd.orderId) but returned the InvoiceRecord with
providerInvoiceNumber = null, and nothing ever backfilled it. Since the
correction precondition landed (#1289), every KSeF KOR was rejected with
"missing document number / issue date" even for fully cleared invoices.

Persist the same P_2 value on the record. Verified live on the KSeF test
environment during the 2026-07-03 E2E run: with this change (plus a
backfill for pre-existing records) the full KOR flow issues and clears.

Existing rows need a one-off backfill, e.g.:
  update invoice_records set "providerInvoiceNumber" = "orderId"
  where "providerType" = 'ksef' and "providerInvoiceNumber" is null;

Closes #1338

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

* refactor(ksef): single documentNumber source for FA(3) P_2 stamp and persisted record

Review follow-up on #1341: hoist the P_2 value into one const consumed by
both the FA(3) builder input and the InvoiceRecord constructor, so the
persisted providerInvoiceNumber can never drift from the number stamped
in the XML when the #1118 sequential-numbering follow-up replaces the
orderId placeholder.

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

* test(ksef): assert providerInvoiceNumber on correction record; clarify 422 for legacy rows

Review follow-up on #1341:
- Add an explicit providerInvoiceNumber assertion to the issueCorrection
  happy-path test. The correction path is the primary consumer of the
  #1289 precondition #1338 unblocks; guarding it directly (not only via
  the issueInvoice delegation) protects against a future refactor that
  special-cases correction-record construction.
- Extend the correction 422 message so an operator hitting a pre-fix
  KSeF row (fully issued/cleared, but null providerInvoiceNumber) gets a
  signal that a one-off backfill is needed, instead of the misleading
  'may not be fully issued yet' alone.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(ksef): ship provider invoice number backfill as an idempotent migration

Review follow-up on #1341 (piotrswierzy): a manual deployment-note SQL
step is easy to forget and, as flagged, easy to get wrong without care
around the camelCase column quoting. Ship it as a committed, idempotent
TypeORM migration instead, per docs/migrations.md — it runs
automatically via migration:run, is reviewable, and the IS NULL guard
makes re-running it a no-op. Scoped to status = 'issued' so failed/
pending rows (which legitimately have no stamped P_2) are untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(ksef): flag correction P_2 collision surfaced by #1338's persistence fix

Review follow-up on #1341 (piotrswierzy): issueCorrection delegates to
issueInvoice with the same orderId, so a KOR stamps the same P_2 as the
original document it corrects - invalid for FA(3), where P_2 must be
unique per document. Pre-existing (the orderId-as-P_2 placeholder
predates this fix) but now visible since providerInvoiceNumber is
actually persisted and read by the correction precondition. Left as an
explicit code note pending the real per-seller sequential FA(3)
numbering source.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(ksef): resolve migration-timestamp collision with #1361

Re-prefix the backfill migration from 1818000000004 to 1818000000005 -
PR #1361 also claims 1818000000004 for a different invoice_records
migration, and TypeORM 0.3.17 has no tie-breaker for a shared prefix.
Per docs/migrations.md's ordering rule, whichever PR lands second
re-prefixes; doing it here avoids leaving the collision to be caught
reactively by CI after one of the two merges.

Addresses Piotr's review on #1341.

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

* fix(ksef,api): address independent tech-review suggestions on #1341

Move the internal issue reference out of the operator-facing
UnprocessableEntityException message in the correction precondition
check (moved into a code comment instead, matching the existing
message-style convention elsewhere in the controller family).

File and cross-reference #1364 as the dedicated tracking issue for the
KOR-shares-original-document-number FA(3) semantics gap flagged in the
issueCorrection NOTE comment, so it's discoverable going forward.

No action needed on the migration down() no-op per reviewer.

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

* fix(ksef): re-prefix backfill migration to 1818000000004 to de-conflict with #1361

Both #1341 and #1361 landed on 1818000000005, sharing the same TypeORM
timestamp prefix on invoice_records (the #374 collision class). Move this
PR's backfill to the free 1818000000004 slot — the correct next step after
main's 1818000000003 tail — leaving 1818000000005 uniquely to #1361. Updates
the class-name suffix and the controller comment that references the filename.

Co-Authored-By: Claude Opus 4.8 (1M context) <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 Fable 5 <noreply@anthropic.com>
norbert-kulus-blockydevs added a commit that referenced this pull request Jul 22, 2026
… sync (#1354) (#1361)

* feat(infakt): consume invoice_marked_as_paid webhook + payment-status sync (#1354)

The inFakt invoice_marked_as_paid (+ _via_async_api) webhook was
recognized but dropped, so OL never learned when an invoice became paid.
Route it onto the invoicing domain and refresh payment status via an
authoritative provider re-read (never trusting the webhook body), mirroring
the KSeF-status reconciliation pattern.

- CORE (invoicing): neutral PaymentStatus (unknown|unpaid|partially-paid|
  paid) + PaymentStatusResult; paymentStatus on InvoiceRecord (+ isPaid);
  PaymentStatusReader sub-capability + guard; PaymentStatusRefreshService
  (by-id, authoritative re-read, write-on-change, graceful no-op); ORM
  column + repo mapping + findByProviderInvoiceId; migration.
- CORE (sync): CanonicalInboundEvent domain invoice-payment; job type
  invoicing.paymentStatus.refreshByExternalId; InboundRoutingPolicy routes
  it to the refresh job, gated on Invoicing.
- Worker: PaymentStatusRefreshHandler.
- Integration: adapter implements PaymentStatusReader (maps inFakt status/
  paid_date via authoritative GET); translator maps both payment events to
  invoice-payment; decoder derives externalId from the invoice uuid.

Outbound InvoicePaymentMarker (mark-paid to inFakt) is deferred to a
follow-up.

Closes #1354

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* fix(infakt): re-prefix payment-status migration to 1818000000005 (#1354)

Resolves the migration-timestamp collision flagged in review: PR #1341
(closes #1338) already claims the 1818000000004 prefix on an
invoice_records migration. Re-prefix this PR's migration to the next
free slot (1818000000005) so both branches satisfy the timestamp
uniqueness + strictly-greater invariant regardless of merge order.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* test(infakt): add payment-refresh handler spec, harden inFakt payment classifier

Address tech-review SUGGESTIONs on PR #1361:

- Add unit spec for PaymentStatusRefreshHandler pinning the getPayload
  reject branches (missing/invalid payload, wrong schemaVersion, empty
  externalInvoiceId), the success delegation, and the OL-shaped error
  wrapping. Mirrors regulatory-status-reconcile.handler.spec.ts.
- Make toPaymentStatus classify against a documented, known inFakt token
  set (verified against the invoice_statuses + payment_statuses meta
  dictionaries) instead of a bare === 'paid' check; behaviour unchanged.
  Extend the adapter spec with partial_payment / draft / printed cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(infakt): clarify partial-payment branch is a forward-looking guard (#1354)

Piotr's re-review flagged that the token doc attributed `partial_payment` to
the field `toPaymentStatus` reads, but that function reads `InfaktInvoice.status`
(the `invoice_statuses` dictionary: draft|sent|printed|paid — no partial token),
so the partial branch is unreachable against real `status` data. Clarify in the
comment that the `partial`/`partly` match is an intentional forward-looking guard
against future vocabulary drift, not a currently-reachable classification.
Comment-only; behavior unchanged (fiscally conservative).

Co-Authored-By: Claude Opus 4.8 (1M context) <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 Opus 4.8 (1M context) <noreply@anthropic.com>
norbert-kulus-blockydevs added a commit that referenced this pull request Jul 22, 2026
…the 4 sub-capabilities shipped since #1307's last update

InfaktInvoicingAdapter now implements 10 Invoicing sub-capabilities;
this branch's docs only covered 6 (missing RegulatoryResubmitter #1356,
PaymentStatusReader #1354, PaymentMarker #1362, InvoiceEmailSender #1353).
Also fixes a since-stale claim that invoice_marked_as_paid webhooks are
ignored (they now drive PaymentStatusReader via #1354/#1361), documents
the dedicated corrective_invoices.json endpoint (#1342), and adds the
per-connection shipping-line label override (#1517/#1562).

docs/capabilities.md and architecture-overview.md's InvoicingPort
sub-capability list had the same 6-of-10 staleness independent of this
branch - fixed alongside since they're the same underlying gap.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
norbert-kulus-blockydevs added a commit that referenced this pull request Jul 22, 2026
…the 4 sub-capabilities shipped since #1307's last update

InfaktInvoicingAdapter now implements 10 Invoicing sub-capabilities;
this branch's docs only covered 6 (missing RegulatoryResubmitter #1356,
PaymentStatusReader #1354, PaymentMarker #1362, InvoiceEmailSender #1353).
Also fixes a since-stale claim that invoice_marked_as_paid webhooks are
ignored (they now drive PaymentStatusReader via #1354/#1361), documents
the dedicated corrective_invoices.json endpoint (#1342), and adds the
per-connection shipping-line label override (#1517/#1562).

docs/capabilities.md and architecture-overview.md's InvoicingPort
sub-capability list had the same 6-of-10 staleness independent of this
branch - fixed alongside since they're the same underlying gap.

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
piotrswierzy added a commit that referenced this pull request Jul 23, 2026
…1307)

* 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>

* 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>

* docs(infakt): ADR-030 KSeF indirection model + operator setup guide

Documents why InfaktInvoicingAdapter implements RegulatoryStatusReader
(not RegulatoryTransmitter) — inFakt auto-submits to KSeF on its own,
so OL only ever reads clearance status back. Adds the operator setup
guide (connection creation, webhook configuration, troubleshooting),
the package README the infakt adapter was missing, and the
architecture-overview.md provider entry.

Part of #1279. Closes #1283.

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

* docs(infakt): payment method, bank-account picker, PDF download in setup guide

Live-E2E-verified against the inFakt sandbox (2026-07-03):
- section 1: Default payment method wizard field + Transfer bank-account
  behaviour (live picker on the Edit form, eager persist, inFakt default
  sync) with fresh wizard + edit-form screenshots
- section 3: Download the invoice PDF step (rendered PDF via
  RegulatoryDocumentReader, #1321)
- corrections: issuance-time line snapshot note (#1297)
- README: BankAccountsReader / BankAccountDefaultSetter /
  RegulatoryDocumentReader notes + implementation details
- capability-panel screenshot (post-#1320)

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

* docs(infakt): address #1307 review - accepted mapping, webhook-secret reality check

- ADR-030: fix ksef_data.status mapping (success -> accepted, not cleared) -
  the ADR was re-canonizing exactly the bug the shipped adapter comment
  warns against
- bank-picker docs (BankAccountsReader/BankAccountDefaultSetter, #1310)
  now describe a MERGED feature - #1310 landed to main 2026-07-03,
  after Piotr's review; no doc changes needed, kept as-is post branch update
- setup-guide step 5: replace the nonexistent "Rotate webhook secret" FE
  button with the actual API call (curl snippet against
  POST /v1/connections/:id/webhooks/secret/rotate); invert the
  secret-exchange framing to lead with what if4's screenshot actually
  shows (no secret field) and mark the paste-into-inFakt direction
  explicitly unverified
- README: add defaultPaymentMethod + bankAccount to the Config JSON
  example (InfaktConnectionConfig carries both since #1309/#1310)
- plan: retarget addendum noting the branch now targets main with all
  prereqs merged
- merged origin/main (branch was behind #1297/#1329/#1310/#1320/#1331)

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

* fix(docs): fix inFakt ADR-030's misleading no-submit-primitive framing

The earlier review-fix commit (999dda5) addressed the /pr-review's four
numbered findings but missed the tech-lead draft review's BLOCKING finding:
ADR-030, the setup guide, and the README all claimed OL "has no submit
primitive to call" and that inFakt "auto-triggers" KSeF submission on its
own. The shipped adapter's own docstring says otherwise: an inFakt draft
does NOT auto-submit on its own, so issueInvoice/issueCorrection call
send_to_ksef.json explicitly and inline - verified live 2026-07-01.

Reworded throughout to the framing the reviewer suggested: OL retains an
out-of-port sendToKsef trigger, not surfaced as RegulatoryTransmitter,
because clearance timing and status ownership stay with inFakt - not
because there is nothing for OL to call. Also disambiguates the three
webhook classes in the README (InfaktWebhookTranslator,
InfaktInboundWebhookDecoderAdapter, InfaktWebhookEventTranslatorAdapter),
per the same draft review's suggestion, which the prior commit also left
unaddressed.

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

* docs(infakt): move setup-guide.md to libs/integrations/infakt/docs/ for convention parity

Every other integration (allegro, dpd-polska, erli, inpost, ksef, subiekt,
woocommerce) already lives at libs/integrations/<name>/docs/setup-guide.md
after the recent doc-location convention change on main. inFakt's guide had
already moved its screenshots there but left the markdown file behind at the
old top-level docs/integrations/infakt/ location. Move the file alongside its
assets and fix every cross-reference (ADR-030, package README,
architecture-overview.md).

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

* docs(infakt): catch up ADR-030/README/setup-guide/capabilities.md to the 4 sub-capabilities shipped since #1307's last update

InfaktInvoicingAdapter now implements 10 Invoicing sub-capabilities;
this branch's docs only covered 6 (missing RegulatoryResubmitter #1356,
PaymentStatusReader #1354, PaymentMarker #1362, InvoiceEmailSender #1353).
Also fixes a since-stale claim that invoice_marked_as_paid webhooks are
ignored (they now drive PaymentStatusReader via #1354/#1361), documents
the dedicated corrective_invoices.json endpoint (#1342), and adds the
per-connection shipping-line label override (#1517/#1562).

docs/capabilities.md and architecture-overview.md's InvoicingPort
sub-capability list had the same 6-of-10 staleness independent of this
branch - fixed alongside since they're the same underlying gap.

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

* docs(infakt): correct webhook-secret injection to env-var mechanism

The webhook-secret exchange direction is now confirmed by the merged
ingestion integration test (#1555): inFakt auto-generates the secret per
subscription and the operator injects it into OL via
OPENLINKER_WEBHOOK_SECRET__INFAKT[__<CONNECTION_ID>]. The secret/rotate
endpoint is the wrong tool (generates a random secret inFakt never sees).

Removes the unverified "known gap" framing; keeps the accurate residual
limitation (no set-arbitrary-secret endpoint / FE affordance yet).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

* docs(invoicing): document DocumentNumberConsumer capability + note ADR-030 roster

- capabilities.md: add the DocumentNumberConsumer (#1575) invoicing
  sub-capability row and bump the InvoicingPort count 12 -> 13 to match
  the 13 capability files on disk (the row was missing on main too; this
  PR owns the count since it re-tallied the section).
- ADR-030: add a scope note that the adapter accreted further
  sub-capabilities post-decision, pointing at the README / architecture
  -overview for the full code-synced roster instead of restating it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>

---------

Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Signed-off-by: Peter Swierzy <123735851+piotrswierzy@users.noreply.github.com>
Co-authored-by: Peter Swierzy <123735851+piotrswierzy@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Integration — Infakt: handle invoice_marked_as_paid webhook + payment-status sync

2 participants