# Payment Flow — Edge Cases

Not every payment follows the [happy path](/docs/payment-flows/std-flow). This page covers non-standard outcomes — underpayment, overpayment, expiration, failed on-chain transactions, and refunds — including what Fluxis does internally, which webhooks you receive, and how to handle each scenario in your integration.

## Overview

```mermaid
flowchart TD
    PR[Payment Request] --> Exp{Expired before payment?}
    Exp -->|Yes| E[expired]
    Exp -->|No| Pay{Customer pays}
    Pay --> Proc[processing]
    Proc --> Final{After finality}
    Final -->|Amount matches| Comp[completed → confirmed]
    Final -->|Too little| Under[underpaid]
    Final -->|Too much| Over[overpaid → confirmed]
    Final -->|Tx failed on-chain| Fail[stuck in processing]
```

| Scenario | Final status | Internal transaction | Webhook sent |
| -------- | ------------ | -------------------- | ------------ |
| Exact payment | `completed` → `confirmed` | Yes | `processing`, `completed` |
| Underpayment | `underpaid` | **No** | `processing`, `underpaid` |
| Overpayment | `overpaid` → `confirmed` | Yes (full received amount) | `processing`, `overpaid` |
| No payment in time | `expired` | No | No |
| On-chain tx reverts / fails | `processing` (stuck) | No | `processing` only |

## Webhook delivery matrix

Fluxis sends account-level webhooks for these payment request status changes only:

| Status | Webhook delivered | Typical trigger |
| ------ | ----------------- | --------------- |
| `processing` | Yes | Cron detected on-chain transfer (hop 1) |
| `completed` | Yes | Confirmed after finality; amount matches (hop 2) |
| `underpaid` | Yes | Confirmed after finality; amount too low (hop 2) |
| `overpaid` | Yes | Confirmed after finality; amount too high (hop 2) |
| `confirmed` | **No** | Internal ledger transaction created (hop 3) |
| `expired` | **No** | Expiration cron marks request as expired |
| `failed` | **No** | Not currently set by the worker |
| `pending` / `created` | **No** | Initial creation — poll or use your own UI state |

::::info
**Poll for statuses without webhooks**

Use `GET /v1/pos/{posID}/payment-request/{paymentRequestID}` to detect `expired` and `confirmed` states. Register webhooks at `POST /v1/account/{accountID}/webhook` — see [Webhooks](/docs/developers/webhooks).
::::

---

## Underpaid

An **underpaid** payment occurs when the customer sends less than the expected amount and the on-chain transaction is confirmed after finality.

### What Fluxis does

```mermaid
sequenceDiagram
    participant W as Transaction Worker
    participant DB as Database
    participant WH as Webhook

    W->>W: Compare received vs expected
    Note over W: received < expected
    W->>DB: status → underpaid
    W->>WH: Webhook (underpaid)
    W->>W: Release vault
    Note over W: Hop 3 skipped — no internal transaction
```

1. Hop 2 compares `ReceivedAmount` (from cron detection) against `ExpectedAmount`
2. Status is set to **`underpaid`**
3. A webhook is sent with status `underpaid`
4. The vault is released and removed from Redis cache
5. **Hop 3 is skipped** — no internal `Transaction` record is created, status does **not** reach `confirmed`

::::warning
**No ledger transaction for underpaid**

Because hop 3 only runs for `completed` and `overpaid` statuses, underpaid payments remain outside the standard settlement pipeline. Coordinate resolution with Fluxis support or initiate a [refund](#refunds-api) if returning funds to the customer.
::::

### Merchant handling

| Action | Recommendation |
| ------ | -------------- |
| **Do not fulfill** the order automatically | Treat `underpaid` as a partial payment requiring manual review |
| **Contact the customer** | Request the remaining balance or agree on a partial fulfillment |
| **Create a new payment request** | For the outstanding difference, if the customer agrees to pay more |
| **Request a refund** | Use the [Refunds API](#refunds-api) to return the partial amount to the customer's wallet |

Eligible refund statuses include `underpaid`. See [Refunds API](#refunds-api) below.

---

## Overpaid

An **overpaid** payment occurs when the customer sends more than the expected amount.

### What Fluxis does

```mermaid
sequenceDiagram
    participant W as Transaction Worker
    participant DB as Database
    participant WH as Webhook

    W->>W: Compare received vs expected
    Note over W: received > expected
    W->>DB: status → overpaid, save full received amount
    W->>WH: Webhook (overpaid)
    W->>W: Hop 3 — create internal transaction
    W->>DB: status → confirmed
```

1. Hop 2 sets status to **`overpaid`** and records the full received amount
2. A webhook is sent with status `overpaid`
3. Hop 3 creates an internal **Transaction** for the full received amount
4. Status transitions to **`confirmed`** (no additional webhook)

### Merchant handling

| Action | Recommendation |
| ------ | -------------- |
| **Fulfill the order** for the requested amount | The customer paid at least the expected price |
| **Refund the excess** | Use `POST /v1/refunds/payment-request/{paymentRequestID}` with an explicit `amount` for the overpaid portion |
| **Contact support** | For complex cases where automatic refund is insufficient |

When `amount` is omitted on a refund request for an overpaid payment, Fluxis defaults to refunding the **overpaid portion** (received minus expected).

---

## Expired

A payment request **expires** when no on-chain payment is detected before the expiration timestamp.

### Timing

| Setting | Value | Scope |
| ------- | ----- | ----- |
| Payment request expiration | **8 minutes** | From vault assignment (`created` status) |
| Payment intention expiration | **5 minutes** | Cashier PoS flow only, before PR creation |
| Expiration check interval | Every **30 seconds** | Background cron job |

### What Fluxis does

```mermaid
stateDiagram-v2
    created --> expired: expiration < now (cron)
    expired --> [*]
```

1. The expiration cron finds payment requests with `status = created` and `expiration < now`
2. Updates status to **`expired`**
3. **No webhook** is sent for expiration
4. The vault is **not immediately released** — it is recycled when the vault pool reassigns it (expired vault overwrite logic)

::::note
**Grace period for late payments**

Transactions broadcast just before expiration may still be detected if the vault address remains in the Redis cache (TTL = expiration + 60 seconds). Do not assume `expired` means the customer cannot pay — check for late-arriving `processing` webhooks or poll the status endpoint.
::::

### Merchant handling

| Action | Recommendation |
| ------ | -------------- |
| **Show expiration in UI** | Display a countdown based on the `expiration` field from the create response |
| **Create a new payment request** | Standard recovery — each request is single-use |
| **Poll for late payments** | If a customer claims they paid, check status before creating a duplicate request |

See [Payment Requests — Timing](/docs/key-concepts/payment-request#timing-and-expiration) for details.

---

## Failed transaction stuck in processing

When an on-chain transaction **fails or reverts** after being detected, the worker logs the failure but does **not** update the payment request status.

### What Fluxis does

In hop 2 (`transaction_check_update_payment_request`), when `GetTransaction` returns a **failed** status:

1. The worker logs `"Blockchain Transaction Failed"`
2. Returns without updating the payment request
3. The payment request **remains in `processing`** indefinitely
4. No additional webhook is sent beyond the initial `processing` notification

```mermaid
flowchart LR
    A[processing] --> B{GetTransaction status}
    B -->|confirmed| C[completed / underpaid / overpaid]
    B -->|pending| D[Re-enqueue with finality delay]
    B -->|failed| E[Log only — stays processing]
```

::::warning
**Known behavior**

This is current platform behavior. If a payment request is stuck in `processing` and the on-chain transaction has failed, contact [support@fluxis.us](mailto:support@fluxis.us) for manual resolution. Poll `GET /v1/pos/{posID}/payment-request/{paymentRequestID}` and verify the transaction hash on a block explorer.
::::

### Merchant handling

| Action | Recommendation |
| ------ | -------------- |
| **Verify on-chain** | Look up the transaction hash (if available) on a block explorer |
| **Do not fulfill** | A stuck `processing` status does not guarantee payment |
| **Contact support** | Provide the payment request ID and transaction hash |
| **Create a new payment request** | If the original transaction definitively failed |

---

## Refunds API

Fluxis provides a Refunds API to initiate refunds for eligible payment requests. Refunds are created at the API level and trigger a webhook notification — **on-chain refund execution is not yet automated**.

### Eligible statuses

Refunds can be created when the payment request is in one of:

- `completed`
- `confirmed`
- `overpaid`
- `underpaid`

### Create a refund

```
POST /v1/refunds/payment-request/{paymentRequestID}
Authorization: Bearer <token>
x-fluxis-api-key: <api_key_id>
```

```json
{
  "refund_to_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
  "amount": "5.00",
  "reason": "Customer overpaid — returning excess"
}
```

| Field | Required | Description |
| ----- | -------- | ----------- |
| `refund_to_address` | Yes | Customer wallet address to receive the refund |
| `amount` | No | Amount to refund. If omitted: full amount for `completed`/`confirmed`, overpaid portion for `overpaid` |
| `reason` | No | Free-text reason for audit trail |

Example response:

```json
{
  "status": "success",
  "data": {
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "refund_to_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
    "amount": "5.00",
    "status": "processing"
  }
}
```

### Check refund status

```
GET /v1/refunds/{refundID}
```

### Refund webhook

When a refund is created, Fluxis sends a webhook with `notification_type: refund_status_update`:

```json
{
  "data": {
    "notification_type": "refund_status_update",
    "refund_request_id": "123e4567-e89b-12d3-a456-426614174000",
    "payment_request_id": "aa5d6c80-0b0b-4a2a-bbc8-14a41e6245bf",
    "status": "processing",
    "amount": "5.00"
  }
}
```

::::warning
**On-chain execution pending**

The refund API creates a `processing` record and sends a webhook. Actual on-chain refund transaction processing is planned but not yet automated. Treat refund records as intent until `refund_transaction_hash` is populated on `GET /v1/refunds/{refundID}`.
::::

### Common refund scenarios

| Scenario | Suggested approach |
| -------- | ------------------ |
| **Overpaid** | Omit `amount` to refund the excess, or specify the exact overpaid portion |
| **Underpaid — return partial payment** | Specify `amount` equal to the received (partial) amount |
| **Completed — full refund** | Omit `amount` to refund the full payment |
| **Customer provided wrong address** | Contact support if the refund address needs correction after submission |

---

## Integration checklist for edge cases

Use this checklist when building production integrations:

```mermaid
flowchart TD
    A[Receive webhook] --> B{Verify HMAC signature}
    B -->|Invalid| X[Reject — 401]
    B -->|Valid| C{status?}
    C -->|processing| D[Show confirming UI — do not fulfill]
    C -->|completed| E[Fulfill order]
    C -->|underpaid| F[Manual review / refund / new PR]
    C -->|overpaid| G[Fulfill order / refund excess]
    H[Poll GET payment-request] --> I{status?}
    I -->|expired| J[Offer new payment request]
    I -->|processing + tx failed on-chain| K[Contact support]
```

1. **Handle all four webhook statuses** — `processing`, `completed`, `underpaid`, `overpaid`
2. **Never fulfill on `processing`** — wait for a terminal amount outcome
3. **Poll for `expired` and `confirmed`** — these do not trigger webhooks
4. **Display the 8-minute countdown** — reduce expired-payment support tickets
5. **Verify webhook signatures** — see [Webhook Signature Verification](/docs/developers/webhook-signature-verification)
6. **Use the Refunds API** for overpaid/underpaid resolution where appropriate
7. **Monitor stuck `processing`** — check block explorer if status does not progress

## Related documentation

- [Payment Flow — Happy Path](/docs/payment-flows/std-flow) — Standard create → detect → confirm flow
- [Payment Requests](/docs/key-concepts/payment-request) — State definitions and API examples
- [Creating Payment Requests](/docs/merchant-integration/create-payreq) — Integration guide
- [Webhooks](/docs/developers/webhooks) — Registration and payload format
- [Webhook Signature Verification](/docs/developers/webhook-signature-verification) — HMAC verification and test workflow
- [FAQ — Payment Monitoring](/docs/faq#payment-monitoring) — Common questions
- [API Reference — Refunds](/api/refunds) — Interactive endpoint documentation
