Skip to content

Payments Ledger with Paystack Webhook Verification & Double-Entry Reconciliation #41

Closed

Description

@meshackyaro

Description

This issue aims to address the implementation of Payments Ledger with Paystack Webhook Verification & Double-Entry Reconciliation.

The core objective is to: Turn the half-built Paystack integration into a trustworthy money path — signature-verified webhooks, an append-only double-entry ledger, an explicit payout state machine, and a reconciliation job that proves the platform's books match the provider's.

By completing this feature, we will ensure that the GuildWorkman protocol maintains its high standards for security, usability, and decentralized logic. This issue requires careful attention to the Backend architecture and adherence to the existing project conventions.

When picking up this issue, please ensure you document any new dependencies or architectural decisions made during development.

Component

Backend (Spring Boot / Java 17)

Difficulty

⚫ Very Hard

Current state

The payment path is currently a shell:

  • controllers/PaymentController.java is entirely commented out — there is no payment endpoint and no webhook receiver at all.
  • services/implmentations/TransactionServiceImpl.java and TransactionHistoryServiceImpl are empty @Service stubs, despite Transaction, TransactionHistory, and TransactionStatus models existing.
  • Payment state today therefore depends on the client calling back after a redirect, which is not a source of truth — a dropped callback silently loses a payment.

Money that can be lost by closing a browser tab is the problem this issue closes. Note that this is the fiat leg; the on-chain leg is tracked separately, and the reconciliation model here must leave room for both.

Tasks

  • Review the existing Spring Boot structure under backend-api/src/main/java
  • Implement the service and REST endpoints for: Payments Ledger with Paystack Webhook Verification & Double-Entry Reconciliation
  • Restore PaymentController as a real controller and add a webhook endpoint that verifies the Paystack x-paystack-signature HMAC over the raw request body, rejecting unverified payloads before any parsing
  • Make webhook handling idempotent by provider event id — Paystack retries, and a retry must never credit twice
  • Implement an append-only double-entry ledger (balanced debit/credit entries, no destructive updates) as the authoritative record, with Transaction/TransactionHistory derived from it
  • Model the payout lifecycle as an explicit state machine with documented legal transitions, covering success, failure, reversal, and refund
  • Implement a scheduled reconciliation job that pulls provider-side state and flags any divergence rather than silently correcting it
  • Ensure the webhook path is reachable without authentication but is protected by signature verification, and account for it explicitly in config/SecurityConfig.java
  • Add or adjust JPA entities, repositories, and schema migrations as needed
  • Write unit and integration tests (Spring Boot Test; MockWebServer for external RPC calls), including forged-signature, replayed-event, and out-of-order-event cases
  • Add caching for Maven dependencies in CI
  • Run ./mvnw verify and ./mvnw test to validate build and coverage

Acceptance Criteria

  • Feature accurately implements the objective: Turn the half-built Paystack integration into a trustworthy money path — signature-verified webhooks, an append-only double-entry ledger, an explicit payout state machine, and a reconciliation job that proves the platform's books match the provider's.
  • A webhook with a missing or forged signature is rejected and never mutates ledger state
  • The same provider event delivered twice produces exactly one ledger effect
  • Every ledger account balances: total debits equal total credits, asserted by test
  • A payment completes correctly even when the client never returns from the redirect
  • Illegal state transitions are rejected rather than silently applied
  • Reconciliation surfaces divergence between platform and provider state as an actionable signal
  • Any PR that introduces failing tests or compilation errors is automatically blocked
  • Endpoints/services are covered by tests including failure and concurrency paths
  • Public API is documented (OpenAPI) and errors follow a consistent response contract
  • Code is properly reviewed and approved by codeowners

Estimated Time

4-6 days

Activity

  1. Emelie-Dev commented on Aug 19, 2026

    @Emelie-Dev

    Hi, I would love to work on this issue.

  2. Meet-hybrid commented on Aug 20, 2026

    @Meet-hybrid

    I would like to contribute to this issue. I have experience with JavaScript/TypeScript, React, backend APIs, testing, Git, and software architecture. I will follow the acceptance criteria, add tests, and provide a focused pull request.

  3. bbjiggy commented on Aug 25, 2026

    @bbjiggy
    Contributor

    Hi,

    I’d like to take ownership of this issue because it addresses one of the most important gaps in the current payment architecture: making the fiat payment path reliable even when clients, redirects, or external providers behave unexpectedly.

    My approach would be to treat the ledger—not the client callback—as the source of truth, and build the Paystack integration around strong invariants: authenticated webhook events, idempotency, append-only accounting, explicit state transitions, and reconciliation.

    Implementation Approach

    I would first review the existing backend-api architecture, including the current payment models, repositories, security configuration, migration strategy, service conventions, and CI setup. Before introducing new abstractions, I’d aim to fit the implementation into the project's existing patterns wherever possible.

    The implementation would be broken into a few clear areas:

    Webhook verification: Restore PaymentController and add a Paystack webhook endpoint that reads the raw request body, computes the expected HMAC using the configured Paystack secret, and compares it safely against x-paystack-signature. Verification would happen before JSON parsing or any state mutation.
    Idempotent event processing: Persist the provider event ID with an appropriate uniqueness constraint and process events transactionally, so Paystack retries cannot create duplicate ledger effects. I would also account for concurrent delivery of the same event.
    Append-only double-entry ledger: Introduce ledger accounts and immutable debit/credit entries with database-level constraints/invariants where practical. Every monetary operation would produce balanced entries, with no destructive modification of historical accounting records.
    Transaction/history compatibility: Treat the ledger as authoritative while deriving or maintaining Transaction and TransactionHistory consistently with the existing domain model, avoiding two competing sources of truth.
    Payout state machine: Define explicit legal transitions and reject invalid transitions. The state model would cover the required success, failure, reversal, and refund paths and would make repeated or out-of-order provider events safe to process.
    Reconciliation: Add a scheduled job that retrieves provider-side payment/payout state, compares it against the platform's recorded state, and records/raises actionable discrepancies rather than silently modifying the books.
    Security configuration: Explicitly allow the webhook route through authentication while keeping signature verification mandatory. I would document this exception in SecurityConfig so it is intentional and auditable.
    Testing: Add unit and Spring Boot integration tests covering forged signatures, missing signatures, duplicate/replayed events, concurrent processing, out-of-order events, invalid state transitions, ledger balancing, failed payments, and reconciliation divergence. MockWebServer would be used for deterministic provider interactions.
    CI and documentation: Add Maven dependency caching to CI, document any new dependencies and architectural decisions, and ensure public endpoints are represented in OpenAPI with a consistent error response format.
    Important Design Considerations

    For financial data, I would prioritize correctness and recoverability over convenience. In particular, I would avoid implementing reconciliation as an automatic "fix" mechanism. A mismatch should become an explicit, inspectable signal so that the system never hides an accounting discrepancy by mutating historical records.

    I would also make idempotency a database-backed guarantee rather than relying solely on in-memory checks. This is important because webhook delivery can be retried and multiple application instances may process events concurrently.

    For out-of-order events, I would base state changes on the defined transition rules and persisted provider event information rather than assuming webhook delivery order. Where an event cannot legally advance the state, the system should reject or flag it according to the documented state-machine policy instead of silently corrupting the transaction state.

    Validation

    Before submitting the PR, I would run:

    ./mvnw test
    ./mvnw verify

    I would also verify the key accounting invariant in tests: for every completed ledger transaction, total debits equal total credits, and that duplicate webhook delivery produces exactly one accounting effect.

    The goal would be a narrowly scoped implementation that follows the existing Spring Boot/Java 17 conventions while establishing clear financial invariants that future contributors can safely build on.

    I’m particularly interested in this issue because it is not just about adding a Paystack endpoint—it is about establishing a trustworthy boundary between an external payment provider and the protocol's internal accounting. I’d be happy to take this through implementation, testing, documentation, and review-ready PR status.

  4. grantfox-oss commented on Aug 25, 2026

    @grantfox-oss

    🦊 GrantFox — @bbjiggy has been assigned to this issue as part of the Third Campaign campaign!

    Next steps:

    1. Open a Pull Request referencing this issue (e.g., Closes #41)
    2. Your PR will be reviewed by the workman-labs maintainers

    Good luck! Track your progress on GrantFox.

  5. grantfox-oss commented on Aug 26, 2026

    @grantfox-oss

    🎉 This issue has been marked as completed on GrantFox as part of the Third Campaign campaign!

    @bbjiggy's PR #51 was approved and merged by @meshackyaro.

    🏆 @bbjiggy: You earned 35 FoxPoints for this contribution! Your current tier: Explorer (853 total points). Track your full progress on GrantFox.

    👏 Great work, @bbjiggy! Keep contributing to workman-labs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardOfficial Campaign | FWC26Campaign: Official Campaign | FWC26Third CampaignCampaign: Third CampaignbackendBackend API (Spring Boot) workdifficulty: very hard⚫ Very HardenhancementNew feature or requesthelp wantedExtra attention is neededpriority: high🔴 High priority — core / blocking

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions