Skip to content

feat: payout API — single, bulk, cancel, retry (#72)#129

Merged
0xdevcollins merged 1 commit intomainfrom
feat/payouts-api
Apr 5, 2026
Merged

feat: payout API — single, bulk, cancel, retry (#72)#129
0xdevcollins merged 1 commit intomainfrom
feat/payouts-api

Conversation

@0xdevcollins
Copy link
Copy Markdown
Owner

Summary

  • Schema: adds idempotencyKey String? @unique to Payout model plus DB indexes on merchantId, status, batchId, createdAt
  • POST /v1/payouts: create single payout with optional Idempotency-Key header; validates destination shape per type (BANK_ACCOUNT, MOBILE_MONEY, CRYPTO_WALLET, STELLAR)
  • POST /v1/payouts/bulk: up to 10,000 recipients in one request; row-level validation — each row independently succeeds or fails; returns per-index payoutId or error
  • GET /v1/payouts: paginated list filterable by status, destinationType, currency, dateFrom, dateTo, batchId
  • GET /v1/payouts/:id: payout detail
  • POST /v1/payouts/:id/cancel: cancels PENDING payouts only
  • POST /v1/payouts/:id/retry: resets FAILED payouts to PENDING and re-processes
  • Processing: STELLAR / CRYPTO_WALLET(network=stellar) destinations execute via StellarService.executePathPayment with auto path-finding and 1% slippage tolerance; BANK_ACCOUNT and MOBILE_MONEY remain PROCESSING for external disbursement system
  • Webhooks: payout.initiated, payout.completed, payout.failed dispatched via existing WebhooksService

Test plan

  • POST /v1/payouts with STELLAR destination → payout created, Stellar tx executed, status → COMPLETED
  • POST /v1/payouts with duplicate Idempotency-Key → returns same payout (no duplicate)
  • POST /v1/payouts/bulk with 3 valid + 1 invalid row → 3 accepted, 1 rejected with per-index error
  • POST /v1/payouts/bulk with 10,001 rows → 400 validation error
  • GET /v1/payouts?status=PENDING → filtered results
  • POST /v1/payouts/:id/cancel on PENDING → status CANCELLED
  • POST /v1/payouts/:id/cancel on PROCESSING → 400 error
  • POST /v1/payouts/:id/retry on FAILED → status resets to PENDING, processing re-triggered
  • Webhooks fire for payout.initiated, payout.completed, payout.failed

Closes #72

🤖 Generated with Claude Code

…ints

- Add idempotencyKey field to Payout model with unique constraint
- Add missing DB indexes on merchantId, status, batchId, createdAt
- DTOs: Zod schemas for create-payout (BANK_ACCOUNT, MOBILE_MONEY,
  CRYPTO_WALLET, STELLAR destinations), bulk-payout (up to 10,000),
  and payout-filters (status, type, currency, date range, pagination)
- Service: idempotency deduplication, async fire-and-forget processing,
  Stellar path payment execution for STELLAR/CRYPTO_WALLET destinations,
  row-level error capture for bulk payouts
- Controller: POST /v1/payouts, POST /v1/payouts/bulk,
  GET /v1/payouts, GET /v1/payouts/:id,
  POST /v1/payouts/:id/cancel, POST /v1/payouts/:id/retry
- Webhook events: payout.initiated, payout.completed, payout.failed
- Wire StellarModule and WebhooksModule into PayoutsModule

Closes #72

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@0xdevcollins 0xdevcollins merged commit 00fddab into main Apr 5, 2026
2 of 5 checks passed
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.

API: Single & Bulk Payout Endpoints

1 participant