Skip to content

[S08] payment-request.v1 contract restore and validation core #30

Description

@grantfox-oss

S08 — payment-request.v1 contract restore and validation core

Field Value
ID S08
ETA 2 — Auth and payment-request contract
Priority P0
Complexity Medium
Depends on S03
Atomic tasks SRV-031, SRV-032, SRV-033, SRV-034, SRV-035
Milestone Public validate endpoint
Blocker Yes — see Hard blockers section

Executive summary

This deliverable consolidates 5 atomic server tasks into one sprint-sized ticket for payment-request.v1 contract restore and validation core.
Success means: Public validate endpoint.

Product context

Ding Payments is a self-custodial Stellar P2P app: the server validates NFC payment requests, enforces hybrid Supabase + WebAuthn authorization, relays signed XDR to Horizon, and indexes history.

User stories

  • As a developer, I want S08 complete so I can run the next ETA without manual archaeology.
  • As a receiver, I want my NFC payment request validated consistently with the canonical contract.
  • As a sender, I want clear API errors when a payment request is malformed or expired.
  • As an operator, I want migrations, health checks, and logs sufficient to debug testnet payments.

Prerequisites

  • Deliverable S03 merged and deployed to dev
  • Node 20+, npm, Supabase project credentials, Postgres reachable via DATABASE_URL
  • Read Sections 3–6 of server-build-plan.md before coding

Atomic sub-task checklist

SRV-ID Title Key deliverable
SRV-031 Restore docs/payment-request.v1.md Restore docs/payment-request.v1.md
SRV-032 Port payment-request.v1.ts from git Port payment-request.v1.ts from git
SRV-033 Unit tests payment-request.v1 contract Unit tests payment-request.v1 contract
SRV-034 PaymentRequestsModule scaffold PaymentRequestsModule scaffold
SRV-035 POST /v1/payment-requests/validate POST /v1/payment-requests/validate

Scope — In

  • All atomic tasks SRV-031, SRV-032, SRV-033, SRV-034, SRV-035 as specified in server-build-plan.md
  • NestJS 11 patterns: modules, providers, DTOs with class-validator, Swagger decorators where applicable
  • Unit and/or E2E tests for new behavior; keep CI green (ci-server.yml)
  • Restore docs/payment-request.v1.md and port payment-request.v1.ts from git 5d4e9de^
  • Public validate endpoint with PAYMENT_REQUEST_* error codes

Scope — Out

  • Client UI or Expo changes (ding-payments repo)
  • Mainnet launch configuration (testnet MVP only unless explicitly toggled)
  • Push notifications on payment confirmation (post-MVP P3)
  • Custodial wallets or server-side key storage
  • Features not listed in atomic SRV IDs for this deliverable

Architecture & conventions

Target architecture from server-build-plan.md Section 3: flat src/modules/* layout, ConfigModule validation, global exception filter, URI versioning /v1.

ding-server/
├── src/
│   ├── main.ts
│   ├── app.module.ts
│   ├── config/
│   ├── common/filters/
│   ├── database/
│   ├── auth/
│   ├── supabase/
│   ├── stellar/
│   ├── webauthn/
│   ├── contracts/
│   └── modules/
│       ├── users/
│       ├── payment-requests/
│       ├── payments/
│       └── transactions/
├── prisma/schema.prisma
├── docs/
└── test/

Endpoint reference (MVP):

Endpoints

Method Route Auth Module Description
POST /v1/payment-requests/validate Public payment-requests Validate NFC payload without persisting
POST /v1/payment-requests JWT payment-requests Register receiver request
GET /v1/payment-requests/:id JWT payment-requests Query request
POST /v1/payments JWT payments Create payment intent (sender)
POST /v1/payments/:id/authorize JWT + WebAuthn payments Verify passkey and authorize
POST /v1/payments/:id/submit JWT payments Receive signed XDR and relay
GET /v1/payments/:id JWT payments Status and details
POST /v1/transactions/simulate JWT transactions Simulate tx before signing
GET /v1/transactions JWT transactions Paginated history
GET /v1/users/me JWT users Profile and wallets
POST /v1/users/me/wallet JWT users Link Stellar pubkey
POST /v1/webauthn/register/options JWT webauthn Passkey registration options
POST /v1/webauthn/register/verify JWT webauthn Verify registration
POST /v1/webauthn/authenticate/options JWT webauthn Challenge for payment
GET /health Public Liveness
GET /health/ready Public Readiness (DB + Stellar)
GET /health/stellar Public Horizon/RPC status

Payment state machine (Section 6 excerpt):

6. Payment state machine

States

stateDiagram-v2
    [*] --> CREATED: POST payments
    CREATED --> AUTHORIZED: POST authorize WebAuthn OK
    AUTHORIZED --> SUBMITTED: POST submit XDR relay OK
    SUBMITTED --> CONFIRMED: Poll Stellar success
    SUBMITTED --> FAILED: Poll Stellar fail
    CREATED --> FAILED: Timeout without authorize
    AUTHORIZED --> FAILED: Timeout without submit
    CREATED --> FAILED: Request expired
    AUTHORIZED --> FAILED: Request expired
Loading

Transitions and rules

From To Trigger Validations
CREATED POST /v1/payments Valid request, not expired, sender ≠ receiver
CREATED AUTHORIZED POST /v1/payments/:id/authorize Valid WebAuthn assertion, payment not expired
AUTHORIZED SUBMITTED POST /v1/payments/:id/submit Valid XDR, matches intent, broadcast OK
SUBMITTED CONFIRMED Poll Stellar Tx included in ledger, success result
SUBMITTED FAILED Poll Stellar Tx failed or poll timeout
CREATED FAILED Cron/timeout PAYMENT_SUBMIT_TIMEOUT_MS without authorize
AUTHORIZED FAILED Cron/timeout Timeout without submit
* FAILED Expiration expiresAt passed

EventEmitter2 events

Event Payload Consumers
payment.created { paymentId, senderUserId, receiverUserId } AuditLog
payment.authorized { paymentId, userId } AuditLog
payment.submitted { paymentId, stellarTxHash } Poll worker, AuditLog
payment.confirmed { paymentId, stellarTxHash, ledger } Transaction indexer, AuditLog
payment.failed { paymentId, failureCode, failureReason } AuditLog

Configurable timeouts

Variable Default Description
PAYMENT_SUBMIT_TIMEOUT_MS 300000 (5 min) Max time in CREATED/AUTHORIZED before FAILED
PAYMENT_POLL_INTERVAL_MS 2000 Interval between confirmation polls
PAYMENT_POLL_MAX_ATTEMPTS 30 ~60s total polling

Appendix A — Error codes

Payment Request (PAYMENT_REQUEST_*):

  • PAYMENT_REQUEST_PAYLOAD_INVALID
  • PAYMENT_REQUEST_FIELD_REQUIRED
  • PAYMENT_REQUEST_FIELD_UNKNOWN
  • PAYMENT_REQUEST_TYPE_UNSUPPORTED
  • PAYMENT_REQUEST_VERSION_UNSUPPORTED
  • PAYMENT_REQUEST_RECIPIENT_INVALID
  • PAYMENT_REQUEST_ASSET_UNSUPPORTED
  • PAYMENT_REQUEST_AMOUNT_INVALID
  • PAYMENT_REQUEST_TIMESTAMP_INVALID
  • PAYMENT_REQUEST_TIMESTAMP_OUT_OF_WINDOW
  • PAYMENT_REQUEST_EXPIRES_AT_INVALID
  • PAYMENT_REQUEST_EXPIRES_AT_OUT_OF_WINDOW
  • PAYMENT_REQUEST_METADATA_INVALID

Payment (PAYMENT_*):

  • PAYMENT_NOT_FOUND
  • PAYMENT_EXPIRED
  • PAYMENT_INVALID_STATE
  • PAYMENT_SENDER_IS_RECEIVER
  • PAYMENT_UNAUTHORIZED
  • PAYMENT_XDR_MISMATCH
  • PAYMENT_XDR_INVALID
  • PAYMENT_ALREADY_SUBMITTED
  • PAYMENT_REQUEST_ID_REPLAY

Stellar (STELLAR_*):

  • STELLAR_ACCOUNT_NOT_FOUND
  • STELLAR_INSUFFICIENT_BALANCE
  • STELLAR_TX_FAILED
  • STELLAR_OP_UNDERFUNDED
  • STELLAR_NETWORK_ERROR
  • STELLAR_TIMEOUT

WebAuthn (WEBAUTHN_*):

  • WEBAUTHN_CHALLENGE_EXPIRED
  • WEBAUTHN_VERIFICATION_FAILED
  • WEBAUTHN_CREDENTIAL_NOT_FOUND

Files to create/modify

  • ding-server/docs/payment-request.v1.md
  • ding-server/src/contracts/payment-request.v1.ts
  • ding-server/src/modules/payment-requests/
  • See server-build-plan.md atomic files for SRV-031
  • See server-build-plan.md atomic files for SRV-032
  • See server-build-plan.md atomic files for SRV-033

Implementation guide

  1. Implement SRV-031 — Restore docs/payment-request.v1.md; cross-check Section 8 in server-build-plan.md for files and snippets.
  2. Implement SRV-032 — Port payment-request.v1.ts from git; cross-check Section 8 in server-build-plan.md for files and snippets.
  3. Implement SRV-033 — Unit tests payment-request.v1 contract; cross-check Section 8 in server-build-plan.md for files and snippets.
  4. Implement SRV-034 — PaymentRequestsModule scaffold; cross-check Section 8 in server-build-plan.md for files and snippets.
  5. Implement SRV-035 — POST /v1/payment-requests/validate; cross-check Section 8 in server-build-plan.md for files and snippets.
  6. Run npm run lint and fix any new violations.
  7. Run targeted unit tests for the module(s) touched.
  8. Run npm run test:e2e when HTTP surface changed.
  9. Update Swagger decorators if routes or DTOs changed.
  10. Verify error responses use { statusCode, message, code?, errors? } shape from Section 5.
  11. Document any new env vars in .env.example and README.
  12. Manual smoke test with npm run start:dev and curl/httpie against /v1 routes.

API examples — POST /v1/payment-requests/validate

Request (public, no JWT):

{
  "type": "payment-request",
  "version": 1,
  "recipient": "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
  "asset": "USDC",
  "amount": "25.00",
  "timestamp": "2026-06-17T12:00:00.000Z",
  "expiresAt": "2026-06-17T12:00:30.000Z",
  "requestId": "req_unique_123",
  "memo": "Coffee"
}

Response 200 (valid):

{
  "valid": true,
  "normalized": {
    "type": "payment-request",
    "version": 1,
    "recipient": "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
    "asset": "USDC",
    "amount": "25.00",
    "timestamp": "2026-06-17T12:00:00.000Z",
    "expiresAt": "2026-06-17T12:00:30.000Z"
  },
  "errors": []
}

Response 200 (invalid):

{
  "valid": false,
  "normalized": null,
  "errors": [
    { "code": "PAYMENT_REQUEST_ASSET_UNSUPPORTED", "field": "asset", "message": "Asset BTC is not supported" }
  ]
}

Acceptance criteria

  • SRV-031 — Restore docs/payment-request.v1.md: acceptance criteria in server-build-plan.md satisfied
  • SRV-032 — Port payment-request.v1.ts from git: acceptance criteria in server-build-plan.md satisfied
  • SRV-033 — Unit tests payment-request.v1 contract: acceptance criteria in server-build-plan.md satisfied
  • SRV-034 — PaymentRequestsModule scaffold: acceptance criteria in server-build-plan.md satisfied
  • SRV-035 — POST /v1/payment-requests/validate: acceptance criteria in server-build-plan.md satisfied
  • CI workflow passes lint, build, and test jobs
  • No secrets committed; .env gitignored
  • OpenAPI/Swagger reflects new or changed endpoints
  • Prisma client regenerated if schema changed (npm run prisma:generate)
  • Database migration applied locally without drift
  • Error codes align with Appendix A where applicable
  • Logs do not print JWTs, XDR secrets, or service role keys
  • Code review checklist: DTO validation, auth guard, idempotency where required
  • README or docs updated when setup steps change
  • Definition of done checklist below is complete

Test plan

  • Unit: Services, validators, and state machine pure functions for S08
  • Integration: Prisma against local Postgres or test container for repositories
  • E2E: Supertest against Nest app with JWT mock and Stellar SDK mocks
  • Contract: payment-request.v1 golden vectors (S08+) — invalid BTC/ETH assets rejected
  • Manual: curl examples from Section 5 and Appendices
  • Regression: Re-run auth suite when touching guards (S06/S07)
  • Performance: Smoke load on validate when approaching S20

Client coordination

Lock schema with client C10/C11 before changing validate rules.

Security notes

  • Never log SUPABASE_JWT_SECRET, SUPABASE_SERVICE_ROLE_KEY, or raw WebAuthn challenges
  • Validate all inbound DTOs; reject unknown fields where strict mode applies
  • Use parameterized Prisma queries only — no raw SQL unless documented
  • Validate endpoint is public — rate limit in S18; no PII in validate logs

Risks & pitfalls

  • Scope creep — stay within listed SRV atomic tasks for this sprint
  • Drift from client NFC contract — coordinate before changing validation rules
  • Stellar testnet instability — use health checks and retries (S12/S13)
  • Prisma migration conflicts — serialize DB changes with team
  • WebAuthn environment mismatch between dev client and server origins
  • Underestimating E2E flakiness — mock Horizon in tests where possible

Definition of done

  • All atomic SRV tasks implemented
  • Tests added/updated and passing locally
  • Swagger updated for HTTP changes
  • No regression in CI pipeline
  • Peer review completed
  • Linked client Issue updated if integration contract changed
  • Merged to main with migration deploy notes if applicable


Source: build plan

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions