feat(payments): add inbound webhook handler with hmac verify and dedup - #226
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.isEqualconstant-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 byprovider_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 nativeINSERT ... ON CONFLICT DO NOTHING(duplicate ->Duplicate) ->markSettled/markFailed. The dedup row + transition + outbox emit commit atomically.PaymentService.markSettled(SUBMITTED -> SETTLED,PaymentSettled),PaymentRepository.findByProviderReference,ProcessedWebhookRepository.insertIfAbsent, and registersPaymentWebhookProperties.@DataJpaTestdedup 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