Skip to content

Feat/payouts - #11

Merged
jules-mahounou merged 2 commits into
mainfrom
feat/payouts
Oct 6, 2026
Merged

jules-mahounou merged 2 commits into
mainfrom
feat/payouts

Conversation

@jules-mahounou

Copy link
Copy Markdown
Collaborator

What & why

Checklist

  • Commits are signed off (git commit -s) — see DCO
  • Tests added or updated
  • If this touches routing/fallback: tests prove no double charge is possible
  • Docs and CHANGELOG.md (Unreleased) updated if behavior changed
  • No real secrets or personal data in code, tests, or fixtures

jules-mahounou and others added 2 commits October 6, 2026 14:50
payenv.payout({ amount, recipient, idempotencyKey, ... }) sends money
from the merchant's provider balance to a recipient. It runs through the
same engine as collections: idempotency (atomic, concurrent-safe), and
fallback to another connector only when it is proven that no money was
sent. A double payout is money lost, so the double-charge invariant
applies unchanged.

- Operation 'payout', PayoutRequest, optional Connector.payout; only
  connectors implementing it are used for payouts
- status lookups and refresh use the payment's operation
- new code INSUFFICIENT_BALANCE (the merchant's balance at a provider):
  safe to fall back, another provider's balance may suffice
- an idempotency key cannot be reused across collect and payout
- confirm is reserved to widget collections

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Jules Mahounou <234069818+jules-mahounou@users.noreply.github.com>
Uses the flow verified on live payouts: POST /payouts (payout modes mtn,
moov, sbin — not the collection slugs), then PUT /payouts/start. Money
can only leave the balance at `start`:
- errors before it are safe to fall back; "Solde insuffisant" on start
  maps to INSUFFICIENT_BALANCE (safe: another provider's balance may do)
- a timeout or 5xx on start returns `unknown` with the payout id, so
  Payenv checks GET /payouts/:id and never sends it again elsewhere
- payout statuses mapped (sent → succeeded; pending/started/... →
  pending; failed/declined; canceled), never guessed as failed

"Opération non autorisée" with HTTP 403 (the real sandbox answer when
payouts are not enabled) now maps to ROUTE_UNSUPPORTED instead of
AUTHENTICATION_FAILED; both are safe to fall back.

Kkiapay has no API to send money to customers (its "payouts" are the
merchant's own scheduled withdrawals): documented, not implemented.

docs: READMEs, quirks, getting started, connectors, roadmap, changeset.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Jules Mahounou <234069818+jules-mahounou@users.noreply.github.com>
@jules-mahounou
jules-mahounou merged commit c23b781 into main Oct 6, 2026
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.

1 participant