Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

readme.md

Payment Gateway Module

Develop a payment gateway module that integrates with multiple Payment Service Providers (PSPs) via a standardized interface (PSP Adapter), enabling payment processing across arbitrary platforms. The module should support multiple payment methods, configurable through an external configuration file. Payments are processed on behalf of tenants, with tenant-specific PSP configurations stored in the database and linked to tenant IDs. For this scope, PSP Adapters will be implemented for Stripe, PayPal, and a dummy “No-Op Provider,” with the module designed to allow easy addition of new PSPs in the future. One-time payments must be fully supported end-to-end. Recurring payments are out of scope for execution in this freelance task, but the data model and API must be designed to support recurring payments later. All transactions must be fully traceable and auditable.


Prerequisites


Technical Requirements / Stack

  • Frameworks:
    • Backend: Java 25, Spring Boot (latest), Spring Framework (latest)
    • Frontend: Typescript, Vue (latest), Vite (latest), ESLint + Prettier, Vue-Router, Pinia, Vitest, Playwright, Bootstrap, Axios
  • Database: PostgreSQL (latest)
  • Caching: Redis
  • Messaging: RabbitMQ (latest)
  • Containerization: Docker / concurrent multi-container operations. Assume deployment in Kubernetes
    • Local development uses docker-compose (PostgreSQL + Redis + RabbitMQ).
  • Build Tool: Maven (latest)
  • API Documentation: OpenAPI 3.0 (YAML)
  • Authorization: JWT token verification (OIDC/OAuth2). Tokens are issued by a separate Auth service.

Functional Requirements

General Architecture

  • Structure: Module structure should follow the design of the existing checkout and auditflow modules.
  • Configuration: Module static configuration must be managed via Spring Boot, covering:
    • Payment methods / common PSP configuration
    • Currencies support
    • Retry behavior
    • etc.
  • Security:
    • Tokenization: The module must never store raw credit card numbers. It must store PSP tokens/IDs (e.g., Stripe customer_id, payment_method_id).
    • Resilience: The /pay endpoint (see below) must support an Idempotency-Key header to prevent duplicate charges during network failures.
    • API Security: All API endpoints must be secured via JWT token (m2m) verification.
    • Multi-tenancy: tenantId must be taken from the JWT (tenantId claim) and used to scope all tenant data .
  • Abstraction: Introduce a PSP Abstraction Layer so new PSPs can be added or maintained easily.
  • Payment States:
    • ACTIVE - payment is enabled and will be executed on schedule
    • INCOMPLETE - payment setup is incomplete; requires user action
    • PAUSED - payment is temporarily paused but can be resumed later
    • CLOSED - payment is closed and won't be executed anymore
  • Transaction States:
    • PENDING - payment is initiated but not completed yet
    • SUCCESS - payment completed successfully
    • FAILED - payment attempt failed; will be retried based on dunning logic
  • Money Rules (consistent with checkout):
    • All monetary amounts are stored in minor units (e.g., cents for USD/EUR).
    • Currency codes must use ISO-4217 (3 uppercase letters).
    • Calculated fields are read-only and computed server-side.

Authentication & tenant scoping

  • JWT claim names: the tenant identifier claim name is tenantId.
  • JWT scopes (space-separated, OAuth2-style):
Scope Description
payment-method:read Read available payment methods
payment-method:admin Configure tenant PSP settings
payment:read Read payment details and list payments
payment:write Create payments and execute pay
transaction:read Read transaction details
  • Recommended JWT payload example (user token):
{
  "aud": ["checkout", "payment-gateway"],
  "sub": "user-123",
  "tenantId": "tenant-abs",
  "scope": "payment-method:read payment:write payment:read transaction:read",
  "roles": [],
  "iat": 1707470000,
  "exp": 1707473600,
  "jti": "2f1c1b6a-2c1e-4f2c-9b6b-9e51cdb1c4a0"
}

Notes:

  • sub is the principal id (a user id or a service id depending on the token issuer and flow).
  • scope is a space-separated string (OAuth2-style).
  • roles may be empty (array).

Tenant handling (no Tenant entity in Payment Gateway)

Payment Gateway must NOT create or own a Tenant entity.

  • Do not auto-create tenants on first request.
  • Do not manage tenant lifecycle in Payment Gateway (create/update/delete/metadata).

tenantId (from JWT) is used only as a scoping key:

  • All tenant-scoped endpoints require tenantId (from JWT).
  • Database records must include tenantId.
  • Tenant-specific PSP configuration must be stored per tenantId.
  • Caching / locking keys must include tenantId.

Configuration conventions (YAML + DB split)

  • YAML (Spring Boot config) contains common/shared payment methods and capabilities.
  • Database stores tenant-specific PSP configuration (scoped by tenantId).
  • Configuration must be overridable in Kubernetes (e.g., ConfigMap / env overrides).

Example (Milestone 1):

payment-gateway:
  payment-methods:
    - id: "stripe"
      enabled: true
      name: "Stripe"
      description: "Credit/Debit Card via Stripe"
      recurring: true
      supported-currencies: ["USD", "EUR", "GBP"]
      supported-countries: ["US", "GB", "DE"]
    - id: "paypal"
      enabled: true
      name: "PayPal"
      description: "PayPal Checkout"
      recurring: false
      supported-currencies: ["USD", "EUR"]
      supported-countries: ["US", "DE"]
    - id: "noop"
      enabled: true
      name: "No-Op (Test)"
      description: "Test payment provider - always succeeds"
      recurring: false
      supported-currencies: ["USD", "EUR"]
      supported-countries: ["US", "DE"]
  retry:
    max-retries: 3
    retry-interval-seconds: 60
    backoff-multiplier: 2.0
  redis:
    idempotency-key-ttl-seconds: 86400    # 24h
    distributed-lock-ttl-seconds: 30

Conventions:

  • id must be unique and stable (internal identifier).
  • supported-currencies must use ISO-4217 currency codes (e.g., USD, EUR).
  • supported-countries must use ISO-3166-1 alpha-2 codes (e.g., US, DE).

Recurring payments scope (this task)

  • Recurring payment execution (scheduling, renewal engine) is out of scope.
  • Data model + API support for recurring payments is in scope.
  • One-time and recurring payments share the same core states.
  • In the future recurring flow, each successful renewal creates a new Transaction linked to the same Payment.
  • payment.finalized will be published for every successful renewal; only confirmed PSP transactions qualify and PSP webhook status is the source of truth.
  • Idempotency must be implemented to avoid duplicate events.

Retry behavior

  • Manual and automatic retries are required.
  • Manual retry: POST /payments/{paymentId}/retry endpoint.
  • Automatic retries must be configurable per PSP/payment method (see YAML retry section above).
  • Retry configuration: maxRetries, retryIntervalSeconds, backoffMultiplier.

Redis usage

Redis is used for:

  • Idempotency keys: Store processed Idempotency-Key values per tenantId + paymentId to prevent duplicate charges. TTL configurable via redis.idempotency-key-ttl-seconds.
  • Distributed locks: Coordinate concurrent /pay requests across multiple pods. TTL configurable via redis.distributed-lock-ttl-seconds.
  • Payment method config cache: Cache resolved payment method configurations (YAML + tenant DB config merge) with short TTL to reduce DB lookups.

PSP Abstraction Layer

The PSP Abstraction Layer uses the Strategy Pattern to enable easy addition of new payment providers.

Adding a new PSP should require at best:

  1. Implement PaymentProvider.
  2. Annotate with @Component.
  3. Add YAML configuration entry.

No changes to existing code should be required.

Integration & Messaging

  • API: Implement a RESTful API (via OpenAPI specification) to manage payment gateway methods.

    • Document all endpoints in OpenAPI YAML
    • Add request/response examples
    • Describe common error model for all endpoints (consistent with checkout and auditflow ErrorResponse schema)
    • Generate API stubs from the OpenAPI for both server and client generation (API-first approach)
  • Queueing: Final payment transactions should be published to a RabbitMQ queue for further processing by other ecosystem modules.

  • Correlation ID: The X-Correlation-ID header must be supported on every request.

    • The same correlation id must be included in logs, propagated to downstream HTTP calls, and included in RabbitMQ messages (headers and/or payload).
    • For webhook callbacks (which arrive without X-Correlation-ID), the gateway must restore the correlationId from the original payment record stored in the database.
  • Events: Publish domain events to RabbitMQ. Payload format must be JSON. Monetary values are integers in minor units (e.g., cents). Consumers must ignore unknown fields (payload evolution is additive).

Event Routing Key Trigger
payment.finalized payment.finalized Transaction reaches terminal state (SUCCESS or FAILED)
payment.created payment.created New payment instance created
payment.closed payment.closed Payment closed (manually or after successful one-time payment)

payment.finalized event example:

{
  "event": {
    "id": "01J0QW5JZ6R1J7K8YV9E3N2M1P",
    "type": "payment.finalized",
    "version": 1,
    "occurredAt": "2026-02-10T12:34:56Z"
  },
  "tenantId": "tenant-abs",
  "correlationId": "RSVCZ9NYY9GZNMPXI",
  "payment": {
    "id": "pay_123",
    "status": "CLOSED",
    "type": "ONE_TIME",
    "amount": 1299,
    "currency": "USD",
    "description": "Order #12345"
  },
  "transaction": {
    "id": "trx_456",
    "status": "SUCCESS",
    "failureCode": null,
    "failureMessage": null
  },
  "paymentMethod": {
    "id": "stripe"
  },
  "pspReferences": {
    "provider": "stripe",
    "chargeId": "ch_...",
    "paymentIntentId": "pi_..."
  }
}
  • PSP UI Actions: Handle PSP-specific user actions (redirects, pop-ups, inline payment forms, etc.) as part of Payment Gateway UI integration.
    • PSP specific frontend dependencies should be exported from the PSP Adapter.

User-Driven Payment Flow

sequenceDiagram
  autonumber

  participant Client as Client / Checkout UI
  participant API as Payment Gateway API
  participant CFG as Config (YAML)
  participant DB as PostgreSQL
  participant PSP as PSP Adapter
  participant MQ as RabbitMQ
  participant Subscriber as AuditFlow / Checkout / Invoice

  Client->>API: GET /payment-methods
  API->>CFG: Load payment methods (capabilities: currency, recurring)
  API->>DB: Load tenant PSP configs (by tenantId from X-Auth-Tenant, trusted gateway header)
  API-->>Client: List of available payment methods

  Client->>API: POST /payments (paymentMethodId, purchaseOrderRef, billingInfo, ...)
  API->>CFG: Load payment method metadata
  API->>DB: Load tenant PSP config for paymentMethodId
  API->>DB: Create Payment (INCOMPLETE)
  API-->>Client: Payment + optional nextAction (setup)

  Client->>API: POST /payments/{payment.id}/pay (Idempotency-Key)
  API->>Redis: Check Idempotency-Key (unique per tenant+payment)
  API->>DB: Create Transaction (PENDING)
  API->>PSP: executePayment() (tokenized data / references)
  PSP-->>API: PSP response (status, references, nextAction?)

  alt Sync completed (SUCCESS/FAILED)
    API->>DB: Update Transaction (SUCCESS/FAILED)
    API->>DB: Update Payment (CLOSED for one-time / ACTIVE for recurring)
    API->>MQ: Publish payment.finalized (transaction payload, correlationId)
    MQ-->>Subscriber: Consume event
    API-->>Client: Transaction status
  else Requires user action (3DS/redirect)
    API->>DB: Update Payment (INCOMPLETE) + store nextAction
    API-->>Client: nextAction (redirect / 3DS)
  else Async completion (webhook later)
    API-->>Client: 202 Accepted (Transaction PENDING)
    PSP-->>API: Webhook/notification (later)
    API->>DB: Update Transaction (SUCCESS/FAILED)
    API->>MQ: Publish payment.finalized (transaction payload, correlationId)
    MQ-->>Subscriber: Consume event
  end
Loading

API Proposal

This section outlines the proposed RESTful API endpoints for the Payment Gateway module.

Every endpoint supports correlation tracing via the X-Correlation-ID header. All endpoints return errors using the standard ErrorResponse schema (see below).

Base path: /api/v1

Common Error Model

Consistent with checkout and auditflow modules:

{
  "code": "NOT_FOUND",
  "message": "Payment with ID '550e8400-...' was not found.",
  "timestamp": "2026-02-10T12:34:56Z",
  "traceId": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}

Error codes:

Code HTTP Status Description
NOT_FOUND 404 Requested resource not found
VALIDATION_ERROR 400 Request payload failed validation
CONFLICT 409 Duplicate idempotency key or state conflict
INTERNAL_ERROR 500 Unexpected server error
PSP_ERROR 502 Upstream PSP communication failure
MISSING_TENANT_ID 400 Missing tenantId in JWT
UNAUTHORIZED 401 Invalid or missing JWT
FORBIDDEN 403 Insufficient scopes
IDEMPOTENCY_CONFLICT 409 Idempotency-Key reused with different request body
PAYMENT_NOT_PAYABLE 409 Payment is in a state that does not allow /pay (e.g., already CLOSED)

1. Retrieve Payment Methods

GET /payment-methods Returns the list of available payment methods for the tenant, filtered by YAML config and tenant PSP configuration.

  • Scopes required: payment-method:read

  • Query Parameters:

    • currency (string) (optional): Filter by supported currency code (ISO-4217).
    • country (string) (optional): Filter by supported country code (ISO-3166-1 alpha-2).
  • Response 200 OK:

  • Response:

  • id (string): Unique identifier for the payment method.

  • name (string): Name of the payment method.

  • description (string): Description of the payment method.

  • icon (base64) (optional): Data for the PSP icon image.

  • recurring (boolean): Indicates if the method supports recurring payments.

2. Configure PSP for a Tenant

PUT /payment-methods/{paymentMethodId}/config Configure or update PSP-specific settings for the tenant. The tenant is determined from the JWT token.

  • Scopes required: payment-method:admin

  • Request Body:

  • pspConfig (object): PSP-specific configuration parameters.

  • Response:

    • 204 No Content on success.
    • 400 if paymentMethodId is not a valid/known payment method.

GET /payment-methods/{paymentMethodId}/config Retrieve the current PSP configuration for the tenant. Sensitive fields (API keys) must be masked in the response.

  • Scopes required: payment-method:admin

  • Response 200 OK:

{
  "paymentMethodId": "stripe",
  "tenantId": "tenant-abs",
  "enabled": true,
  "config": {
    "apiKey": "sk_test_...****",
    "webhookSecret": "whsec_...****",
    "merchantId": "acct_..."
  },
  "createdAt": "2026-01-15T10:00:00Z",
  "updatedAt": "2026-02-01T14:30:00Z"
}

3. Initiate Payment Instance

POST /payments Initiate a new payment instance (one-time or recurring) and capture payment details. No actual charge is performed yet. The tenant is determined from the JWT token.

  • Scopes required: payment:write

  • Request Body:

  • paymentMethodId (string): id of the selected payment method.

  • purchaseOrder (object): Details from the checkout module.

    • ... including
    • recurring (boolean): Indicates if the payment is recurring.
  • billingInfo (object): Billing information.

  • shippingInfo (object) (optional): Shipping information.

  • extra (object) (optional): Additional metadata.

  • Response:

  • payment (object): The created Payment object.

  • nextAction (object): returns optional next action required to complete the payment details capture via specific PSP flow.

    • type (string): e.g., "none", "redirect", "3ds-challenge".
    • details (object): Metadata required for the next action.

4. Execute Payment

POST /payments/{payment.id}/pay Execute a payment via the external PSP using stored captured payment details and create a payment transaction. For non-recurring payments, this operation closes the payment on success.

  • Scopes required: payment:write
  • Headers:
    • Idempotency-Key (string, required): Unique key per tenantId + paymentId combination.

NOTE: PSP may require delayed completion with later webhook notification. The architecture must handle this by returning 202 Accepted and updating the transaction status asynchronously upon webhook receipt.

  • Response 200 OK (synchronous completion):

NOTE: PSP may imply delayed completion of the payment with later notification. Ensure architecture is prepared for handling this scenario by capturing the PSP notification and adjusting the transaction status in the background. Transaction status transitions to SUCCESS or FAILED are to be published in the queueing service.

  • Response:
  • transaction (object): Payment transaction details.
    • id (string): Unique identifier for the transaction.
    • status (string): Transaction status.
    • pspData (object): Raw data returned from the PSP.

5. Payment Status

GET /payments/{payment.id} Retrieve the status and details of an existing payment. Can be used to restore (possibly incomplete) nextAction processing

  • Response:
  • payment (object): The created Payment object.
  • nextAction (object):
    • type (string): e.g., "none", "redirect", "3ds-challenge".
    • details (object): Metadata required for the next action.

6. Close Payment

POST /payments/{payment.id}/close Close an existing payment. No further pay operations can be executed.

7. Transaction Status

GET /transactions/{transaction.id} Retrieve the status and details of a payment transaction.

  • Response:
  • transaction (object): Payment transaction details.
    • id (string): Unique identifier for the transaction.
    • status (string): Transaction status.
    • pspData (object): Raw data returned from the PSP.

Deliverables

  • OpenAPI Spec: Fully specified YAML file for the RESTful API, including all endpoints, schemas, error model, and examples.
  • Java Module: Implementing the functionality described above; including PayPal, Stripe and NoOp PSP adapters.
  • Unit Tests: Covering all major functionalities.
  • Configuration:
    • Structured application.yaml for integrated payment methods.
    • PSP-specific configuration instructions/templates/examples for a tenant.
  • Database: Flyway migration scripts in resources/db/migration.
  • Dockerfile: For containerizing the module.
    • Create docker-compose for local development (single instance) including PostgreSQL, Redis, and RabbitMQ.
  • Documentation: Setup, configuration, usage guide, contributor guide.
  • CI/CD: .github workflows pipeline to build, test.

Milestones

  • Total workload will be split into milestones defined as below.
  • At the beginning of each milestone, a new PR must be created targeting the main branch of the Payment Gateway repo.
  • Daily work progress must be frequently committed (one daily commit at minimum).
  • Early & timely communication via GitHub Issues and PRs only. Expect on-the-fly comments to the commits.
  • Each milestone must be approved and merged before proceeding to the next one.
  • Scope review and adjustments may be done at the end of each milestone if necessary. Additional work beyond the defined milestones will be treated as out-of-scope and must be agreed separately.
  • Approved and merged milestones PRs are considered as completed and entitled for payment.
  • Failure to complete a milestone within the agreed timeframe may result in project termination.

  • Milestone 1: Project Setup & Basic Architecture
    • ✅ (done) - Setup Spring Boot project with necessary dependencies.
    • ✅ (done) - Dockerfile and basic CI/CD pipeline.
    • Design OpenAPI specification with generated Java interface and create API stubs with log output.
  • Milestone 2: PSP Abstraction Layer & NoOp Implementation
    • Implement PSP Abstraction Layer using Strategy Pattern.
    • Create NoOp PSP adapter for testing.
    • Create needed database entities and Flyway migration scripts.
    • Implement all endpoints with basic logic.
  • Milestone 3: Stripe Integration
    • Implement Stripe PSP adapter.
    • Complete payment flow for Stripe.
    • Unit tests for Stripe integration.
  • Milestone 4: PayPal Integration
    • Implement PayPal PSP adapter.
    • Complete payment flow for PayPal.
    • Unit tests for PayPal integration.

Release Condition: The Pull Request to the Payment Gateway repo must be accepted and merged into the main branch to release the freelance payment feature.


Acceptance Criteria

  • Code follows standard Java best practices (consistent with checkout / auditflow modules) and is well-documented.
  • PSP Abstraction Layer is implemented using the Strategy Pattern (e.g., StripeProvider, PayPalProvider, and NoOpProvider implement a common PaymentProvider interface).
  • Integrated PSPs:
  • One-time payments can be demoed with all integrated PSPs using sandbox accounts (personal developer accounts). Recurring payment execution is out of scope for this task.
  • Async Messaging: RabbitMQ producer is implemented for payment.finalized, payment.created, and payment.closed events with transaction payload.
  • Webhook handling: PSP webhooks are received via POST /webhooks/{provider}, verified using PSP-specific signatures, and used to settle async transactions.
  • Distributed Environments / Kubernetes safety:
    • All functionality can run reliably on Kubernetes
    • Execution is idempotent to prevent duplicate processing
    • Components are safe to run in parallel across multiple pods
    • Coordination and state handling support distributed execution without race conditions
  • Traceability: Every log entry and RabbitMQ message must contain a correlationId traceable across ecosystem modules.
  • Docker container builds successfully and runs without errors.
  • All unit tests pass with >80% code coverage.
  • API endpoints function exactly as specified in the OpenAPI documentation.
  • Error model follows the standard ErrorResponse schema consistent with checkout and auditflow modules.
  • Logging Policy: no sensitive information in the logs, such as PAN/PII, credentials, etc.
  • Payment methods are configurable via YAML and retrievable via API.
  • Vulnerability Scanning: no High and Critical vulnerabilities (static code analysis, dependencies, docker, etc.) with GitHub Code scanning / CodeQL, Trivy for Docker