Skip to content

feat(payments): add inbound webhook handler with hmac verify and dedup - #226

Merged
tiana-code merged 1 commit into
mainfrom
feat/E-02-payments-webhook
Jun 18, 2026
Merged

feat(payments): add inbound webhook handler with hmac verify and dedup#226
tiana-code merged 1 commit into
mainfrom
feat/E-02-payments-webhook

Conversation

@tiana-code

Copy link
Copy Markdown
Owner

What

Seventh slice of E-02. The inbound payment-webhook handler service (variant B; the HTTP endpoint + security land with the REST slice #216) that takes a submitted payment to its final state.

  • WebhookSignatureVerifier: HMAC-SHA256 over the raw payload with a configured shared secret, MessageDigest.isEqual constant-time compare, hex trimmed + case-normalized, fail-closed on a blank secret or mismatch.
  • PaymentWebhookHandler (@Transactional, inbound-only, no external call): verify (throws before any DB write) -> correlate by provider_reference (unmatched -> Ignored, not deduped, retry-able) -> legal-transition pre-check (illegal/terminal -> Ignored, so no exception crosses the tx -> no rollback-only) -> dedup via native INSERT ... ON CONFLICT DO NOTHING (duplicate -> Duplicate) -> markSettled/markFailed. The dedup row + transition + outbox emit commit atomically.
  • Adds PaymentService.markSettled (SUBMITTED -> SETTLED, PaymentSettled), PaymentRepository.findByProviderReference, ProcessedWebhookRepository.insertIfAbsent, and registers PaymentWebhookProperties.
  • The @DataJpaTest dedup IT closes the F-02.2 Payments DB schema and persistence #209-deferred hard PK-conflict test (native double-insert -> 1 then 0).

A concurrent-race conflict self-heals: the optimistic-lock/illegal-transition exception propagates, the whole tx (dedup row included) rolls back, the provider retries, and the retry sees the terminal state and returns Ignored.

OSS boundary (§5.3): generic notification shape, neutral "provider reported failure", no real provider/payload/partner; the HMAC secret is config-only (no hardcoded literal; gitleaks-safe).

Gates

Local (-x integrationTest): :services:payments:{test,detekt,detektTest,spotlessCheck,assemble,compileIntegrationTestKotlin} green. critic GO (pre-check no-rollback-only + hex-normalize + fail-closed secret applied), security-auditor PASS (HMAC constant-time/fail-closed + gitleaks-safe + §5.3 clean, 7/7 ACs), evaluator 0.932. The dedup IT runs on CI.

Closes #214

Add the inbound payment-webhook handler service: verify the callback's
HMAC-SHA256 signature against a configured shared secret (constant-time
compare, fail-closed on a blank secret or mismatch), correlate to the payment
by its stored provider reference, dedup by the provider delivery id via an
INSERT-ON-CONFLICT-DO-NOTHING on the processed-webhooks table, and settle or
fail the payment. Signature verification runs before any database write; the
dedup row and the state transition commit in one transaction so a failure
rolls both back. A late or conflicting webhook for an already-terminal payment
is ignored as reconciliation rather than an error, guarded by a legal-transition
pre-check so no exception crosses the transaction boundary.

Add markSettled (SUBMITTED to SETTLED), findByProviderReference, and the native
dedup insert. The HTTP endpoint and security wiring are added with the REST
slice.

Closes #214
@tiana-code tiana-code added this to the v0.2.0 milestone Jun 18, 2026
@tiana-code
tiana-code merged commit 55819d8 into main Jun 18, 2026
8 checks passed
@tiana-code
tiana-code deleted the feat/E-02-payments-webhook branch June 18, 2026 14:40
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.

F-02.7 Inbound webhook handler (HMAC verify + dedup)

1 participant