---
name: client-withdrawal-plan
overview: Merchant-demo payout form calling `POST /api/v1/payouts/` with `disburse_amount_usd`, plus API create/read/list notes (approval elsewhere).
---

## Goal
Deliver a **merchant-demo form** (in `payxea_customer/merchant-demo`) that creates client withdrawals by calling `POST /api/v1/payouts/`, and produce a clear API plan for the **payout create/read/list** contract.

**Out of scope for this plan:** merchant approval/rejection endpoints and the rest of the routing/agent pipeline (these are handled in merchant portal / other operational tooling).

## Current baseline (already implemented)
- **Create payout**: `POST /api/v1/payouts/` implemented in [/var/www/html/payxea_api/app/routers/psp/payouts.py](/var/www/html/payxea_api/app/routers/psp/payouts.py) (creates `PayoutRequest` with `status=PENDING_MERCHANT`).
- **Merchant controls** (out of scope here): approve/reject/list/tracking implemented in [/var/www/html/payxea_api/app/routers/merchant/client_withdrawals.py](/var/www/html/payxea_api/app/routers/merchant/client_withdrawals.py).
- **Core payout pipeline** (out of scope here): routing/agent task/settlement implemented in [/var/www/html/payxea_api/app/services/payout_service.py](/var/www/html/payxea_api/app/services/payout_service.py).
- **API contract doc**: payout section already exists in [/var/www/html/payxea_api/docs/deposit-withdrawal-api.md](/var/www/html/payxea_api/docs/deposit-withdrawal-api.md).

## Decisions captured
- **Approval not covered in this plan**: payout approval is handled under merchant portal; this plan focuses on payout creation and visibility.
- **Idempotency default (since not specified)**: document and implement **`client_ref` uniqueness per merchant** (duplicate create returns the existing payout). If you later prefer header-based idempotency, this becomes a follow-up enhancement.

## API surface (client withdrawal)
### 1) Create payout (client withdrawal request)
- **Path**: `POST /api/v1/payouts/`
- **Auth**: `X-PayXea-Key: <merchant_api_key>` (via `CurrentMerchant` dependency pattern)
- **Request** (existing): `client_ref`, `client_wallet_number`, **exactly one of** `amount_local` **or** `disburse_amount_usd`, `local_currency`, `payment_method`, `country_code`
  - **`amount_local`**: literal local payout amount (legacy path; agent FX spread).
  - **`disburse_amount_usd`**: USD notional for the rail; **`amount_local` is computed** from merchant FX for the `(country_code, local_currency)` corridor, with the admin `fx_rates` row snapshotted on create.
- **Behavior**:
  - Resolve FX for the corridor and validate a rate exists (see `FxService` / payout create flow in `payouts.py`).
  - Persist payout with `status=PENDING_MERCHANT`, corridor fields, computed `amount_usd` / `amount_local` (depending on which amount mode was used), and an exchange rate snapshot.
  - Return payout object.
- **Idempotency** (planned): enforce uniqueness on `(merchant_id, client_ref)` and return existing payout for duplicates.

### 2) Read/list endpoints
- `GET /api/v1/payouts/{payout_id}`
- `GET /api/v1/payouts?status=&limit=&offset=`

## Merchant-demo form (payxea_customer)
Add a simple form under `payxea_customer/merchant-demo` that collects the `POST /api/v1/payouts/` fields and submits them.

Planned behavior:
- **Inputs**: `client_ref`, `client_wallet_number`, `disburse_amount_usd` (USD), `local_currency`, `payment_method`, `country_code`
- **Config**: API base URL + merchant key supplied via environment (do not hardcode secrets in the repo).
- **Submit**: browser posts JSON to `POST /merchant-demo/create-payout`; the demo dev server forwards to `/api/v1/payouts/` with header `X-PayXea-Key` and **`disburse_amount_usd`** (not `amount_local`).
- **Output UI**: show the created payout `id` + `status` + any returned fields; show errors cleanly.
- **Note**: since approval is in merchant portal, the demo should explicitly display that new payouts are expected to be `PENDING_MERCHANT`.

## Lifecycle / state machine (payout)
States (source of truth): `PayoutRequest.status`
- **In scope**: creation results in `PENDING_MERCHANT`.
- **Out of scope** (handled elsewhere): `PENDING_MERCHANT` → `APPROVED` → `ASSIGNED_AGENT` → `TXID_SUBMITTED` → `VERIFYING` → `COMPLETED` (plus `REJECTED` / `FAILED` / `EXPIRED`).

```mermaid
flowchart LR
  Create[POST_api_v1_payouts] --> Pending[PENDING_MERCHANT]
  Pending --> Downstream[merchantPortal_and_agentPipeline]
```

## Callbacks, audit, and security
- **Callback event**: `payout.completed` (documented in `deposit-withdrawal-api.md`).
- **Audit** (out of scope here): merchant approve/reject and later lifecycle steps record `transaction_audit` entries.
- **PII**:
  - Mask wallet number in audit metadata/logs (already done via `mask_wallet_number`).

## Data model & constraints (to align on)
- `PayoutRequest` must store:
  - merchant + client reference
  - destination wallet number
  - amount (local + USD), FX rate snapshot
  - `payment_method`, `country_code`, `local_currency`
  - assignment fields (`assigned_agent_id`, `assigned_wallet_id`, timestamps)
  - txid + proof timestamps
  - fee snapshots and commission snapshots
  - routing retry metadata
- Add/confirm DB constraints for idempotency if chosen:
  - unique index on `(merchant_id, client_ref)`.

## Testing plan (API-level)
- Create payout returns `PENDING_MERCHANT`.
- Idempotent create: duplicate `client_ref` returns same payout (after uniqueness is enforced).
- Merchant-demo: submit form creates payout and displays response/error.

## Files to touch when implementing/documenting
- [/var/www/html/payxea_api/docs/deposit-withdrawal-api.md](/var/www/html/payxea_api/docs/deposit-withdrawal-api.md): confirm `/api/v1/payouts/` request/response + idempotency language.
- [/var/www/html/payxea_api/app/routers/psp/payouts.py](/var/www/html/payxea_api/app/routers/psp/payouts.py): enforce chosen idempotency behavior (if not already).
- Alembic migration under `alembic/versions/`: add unique constraint/index for `(merchant_id, client_ref)`.
- Integration tests (existing payout pipeline tests): extend to cover duplicate create semantics.
- `payxea_customer/merchant-demo/`: add a payout form page and submission handler.

