# Payment flows
Source: https://paylinky.vercel.app/docs/payment-flows

USSD push, Wave redirect, OTP by SMS and OTP by USSD.

# Mobile Money payment flows

The flow depends on the country, operator and provider. Do not hard-code it from the brand name: load `/countries`, call `/collect`, and react to the **response**.

| Flow | Initial response | Customer action |
| - | - | - |
| USSD push | `202`, `status: "pending"` | Approve the prompt on the phone |
| Redirect (Wave) | `202`, `flow: "wave"`, `wave_url` | Open `wave_url` and approve |
| OTP by SMS | `400`, `error: "otp_required"`, `reference` | Read the SMS code, re-send with `otp` |
| OTP by USSD | `400`, `error: "otp_required"`, `reference`, `ussd_code` | Dial `ussd_code`, enter the code, re-send with `otp` |

In every case the payment remains `pending` until confirmed.

## 1. USSD push

Call `/collect` → `202 pending`. Show a "waiting for approval" screen. Receive the webhook (or poll).

## 2. Wave and redirects

```json
{
  "status": "pending",
  "flow": "wave",
  "wave_url": "https://pay.wave.com/c/…",
  "transaction_id": "…",
  "reference": "PLK-…"
}
```

Open `wave_url` exactly as returned (do not build it yourself), then wait for the webhook.

## 3. OTP

First request — same body as a normal collect:

```json
{
  "error": "otp_required",
  "message": "OTP required. Dial the code shown to get your OTP, then retry with 'otp' and 'reference'.",
  "reference": "PLK-7XK2M9QPLA",
  "ussd_code": "#144*82#"
}
```

Display `ussd_code` (when present) to the customer. After they enter the OTP, **re-send the same request** with:

- `reference` = the `reference` from the `otp_required` response (replaces your order reference),
- `otp` = the code the customer received.

:::tabs
```bash cURL
curl -X POST https://paylinky.vercel.app/api/v1/collect \
  -H "Authorization: Bearer $PAYLINKY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 5000,
    "currency": "XOF",
    "country_code": "CI",
    "operator": "Orange Money",
    "phone": "+2250XXXXXXXX",
    "reference": "PLK-7XK2M9QPLA",
    "otp": "123456"
  }'
```
```javascript Node.js
try {
  await plk.collect({ amount: 5000, currency: "XOF", country_code: "CI", operator: "Orange Money", phone, reference: "order_1" });
} catch (e) {
  if (e.code === "otp_required") {
    // ask the customer for the OTP, then:
    await plk.collect({ amount: 5000, currency: "XOF", country_code: "CI", operator: "Orange Money", phone, reference: e.body.reference, otp });
  }
}
```
:::

If the OTP session expired you receive `400 otp_expired`/`invalid_reference` — start a new collection with a new `reference`.

## Handling responses

| Code | Meaning | What to do |
| - | - | - |
| `202` | Accepted, pending | Wait for webhook / poll |
| `200` | Final result immediately (sandbox) or idempotent replay | Read `status` |
| `400 otp_required` | Needs OTP | Show OTP form, retry |
| `400 invalid_otp` | Wrong OTP | Ask again |
| `409 reference_conflict` | Reference reused with different data | Use a unique reference |
