> ## Documentation Index
> Fetch the complete documentation index at: https://docs.crypto-cash.world/llms.txt
> Use this file to discover all available pages before exploring further.

# Fiat Module

> CryptoCash Fiat Module: signed API requests, payment creation, widget, transaction statuses, and error handling for integrators.

# Fiat Module

> **Note:** all numeric values, amounts, rates, and identifiers in the examples are illustrative and provided for demonstration purposes only. Real values depend on market conditions, account configuration, and the parameters of a specific transaction.

## Overview

Fiat Module is a subsystem of the CryptoCash platform that allows a merchant to accept fiat payments from end users and receive cryptocurrency to their wallet.

Integration is performed through signed API requests. Payments are processed via a payment provider (Mercuryo). The end user completes the payment in the CryptoCash widget, where they select a payment method. After the payment is processed, the merchant receives a webhook with the final status.

**Supported features:**

* Payment by card, Apple Pay, Google Pay, SEPA, and other payment methods
* Signed requests (`ED25519` or `LEGACY`/`SHA256`) for merchant authentication
* Webhooks to the merchant on every transaction status change
* Automatic calculation of the merchant’s earnings based on `partner_fee`

## High-level flow

```text theme={null}
1. Merchant sends a signed API request → receives a payment URL
2. User opens the URL → sees the CryptoCash widget
3. User selects a payment method
4. Payment provider processes the payment
5. Merchant receives a webhook on every status change
6. After successful payment, the merchant’s earnings are calculated automatically
```

**Two payment flows in the widget:**

| Payment method       | Flow                                                                                |
| -------------------- | ----------------------------------------------------------------------------------- |
| Apple Pay (native)   | The user pays directly in the widget — without being redirected to an external page |
| Card / other methods | The user is redirected to the payment provider’s page to complete the payment       |

For a detailed description of the native Apple Pay flow, see the **Apple Native Pay** section.

## Merchant setup and Fiat API Key

### What the merchant configures

After the administrator has prepared the account, the merchant generates a Fiat API Key in the dashboard. When creating the key, the merchant sets parameters that apply to all payments associated with this key:

| Parameter              | Description                                                                                                  |
| ---------------------- | ------------------------------------------------------------------------------------------------------------ |
| `keyType`              | Signature algorithm: `ED25519` (recommended) or `LEGACY` (`SHA256`)                                          |
| `keyName`              | Custom key name                                                                                              |
| `partnerFee`           | Share of the provider fee that goes to the merchant; configured by the administrator on the provider account |
| `paymentMethods`       | Payment methods available in the widget, for example `["card", "apple_pay"]`                                 |
| `webhookUrl`           | URL for receiving payment status webhooks                                                                    |
| `logoUrl`              | Merchant logo displayed in the widget                                                                        |
| `ipRestrictionEnabled` | Whether to restrict API access by IP address                                                                 |
| `allowedIpAddresses`   | List of allowed IP addresses or CIDR ranges                                                                  |
| `expiresAt`            | Specific key expiration date                                                                                 |
| `neverExpires`         | If `true`, the key does not expire (`expiresAt` = `null`); by default, the key validity period is 1 year     |

The response returns a `publicKey` / `privateKey` pair. **The private key is stored on the platform in encrypted form and is shown to the merchant only once** — during creation. Save it immediately.

> If two-factor authentication is enabled on the merchant account, 2FA confirmation is required to generate the key.

### Fiat API Key fields

| Field                  | Type       | Description                                                   |                                          |
| ---------------------- | ---------- | ------------------------------------------------------------- | ---------------------------------------- |
| `publicKey`            | `string`   | Key identifier, passed in every request                       |                                          |
| `privateKey`           | `string`   | Secret for signing requests; stored encrypted on the platform |                                          |
| `keyType`              | `ED25519`  | `LEGACY`                                                      | Signature algorithm                      |
| `isActive`             | `boolean`  | Whether the key is active                                     |                                          |
| `expiresAt`            | `datetime` | `null`                                                        | Expiration time (`null` — no expiration) |
| `paymentMethods`       | `string[]` | Allowed payment methods                                       |                                          |
| `webhookUrl`           | `string`   | `null`                                                        | URL for webhook delivery                 |
| `logoUrl`              | `string`   | `null`                                                        | Logo URL                                 |
| `ipRestrictionEnabled` | `boolean`  | Whether IP restriction is enabled                             |                                          |
| `allowedIpAddresses`   | `string[]` | Allowed IP addresses                                          |                                          |

### ED25519 vs LEGACY

|                  | `ED25519`                                                            | `LEGACY`                                                                       |
| ---------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| **Algorithm**    | Asymmetric cryptography (Ed25519)                                    | SHA256-based signature                                                         |
| **Key relation** | `publicKey` is mathematically derived from `privateKey`              | `publicKey` is an identifier; `privateKey` is a shared secret used for signing |
| **Verification** | Signature is verified using `publicKey` without knowing `privateKey` | The platform uses the stored `privateKey` to verify the signature              |
| **Recommended**  | Yes                                                                  | Only for compatibility with legacy integrations                                |

### What is checked on each request

When receiving a signed request, the platform checks:

* The key is active
* The key has not expired
* The **IP of the API caller** (merchant server) is included in the allowed list if IP restriction is enabled
* The signature is valid for the selected algorithm

> **Important:** IP restriction applies to the address of the server making the API request, not to the end user’s IP in the payment payload `ip` field.

## Signed requests

All merchant API requests must be signed. The signature algorithm is defined by the `keyType` value selected when creating the key.

### Request structure

```json theme={null}
{
  "data": "<base64-encoded JSON payload>",
  "signature": "<base64-encoded signature>"
}
```

`publicKey` is passed either in the `x-public-key` header or inside the `data` object. If both are present, the header takes priority.

### ED25519 (recommended)

```text theme={null}
1. payload    → JSON.stringify(payload)
2. json       → Buffer.from(json, 'utf8').toString('base64')  → data
3. sign       → Ed25519.sign(Buffer.from(data), privateKeyBytes) → signatureBytes
4. signature  → signatureBytes.toString('base64')
```

### LEGACY (SHA256)

```text theme={null}
1. payload    → JSON.stringify(payload)
2. json       → Buffer.from(json, 'utf8').toString('base64')  → data
3. hash       → SHA256(privateKey + data) → hashHex
4. signature  → Buffer.from(hashHex, 'utf8').toString('base64')
```

### Full example

Payload for `form/retrieve`:

```json theme={null}
{
  "publicKey": "a1b2c3d4...",
  "fiatCurrency": "SGD",
  "fiatAmount": "100",
  "currency": "USDT",
  "network": "TRC20",
  "address": "TXxxx...",
  "ip": "203.0.113.5"
}
```

Final body of the signed request:

```json theme={null}
{
  "data": "eyJwdWJsaWNLZXkiOiJhMWIyYzNkNC4uLiIsImZpYXRDdXJyZW5jeSI6IlNHRCIsImZpYXRBbW91bnQiOiIxMDAiLCJjdXJyZW5jeSI6IlVTRFQiLCJuZXR3b3JrIjoiVFJDMjAiLCJhZGRyZXNzIjoiVFh4eHguLi4iLCJpcCI6IjIwMy4wLjExMy41In0=",
  "signature": "base64-encoded-signature"
}
```

## Payment creation — `form/retrieve`

### Endpoint

```text theme={null}
POST https://rates.crypto-cash.world/merchant/api/v1/pay-fiat/form/retrieve
```

Signed request. The signature and IP are checked before the request is processed.

### Request

The `data` field of the signed request contains the following JSON:

| Field          | Type   | Required | Description                                                           |
| -------------- | ------ | -------- | --------------------------------------------------------------------- |
| `ticker`       | string | ✓\*      | Crypto asset and network ticker, for example `"USDTTRC20"`            |
| `currency`     | string | ✓\*      | Cryptocurrency code, for example `"USDT"`                             |
| `network`      | string | ✓\*      | Blockchain network, for example `"TRC20"`                             |
| `fiatCurrency` | string | ✓        | Fiat currency code, for example `"SGD"`                               |
| `fiatAmount`   | string | ✓        | Fiat amount as a string, for example `"100"`                          |
| `address`      | string | ✓        | Wallet address for cryptocurrency crediting                           |
| `ip`           | string | ✓        | End user’s IP address                                                 |
| `email`        | string | —        | Customer email                                                        |
| `redirectUrl`  | string | —        | URL to return the user to after payment                               |
| `lang`         | string | —        | Widget language, default `"en"`                                       |
| `externalId`   | string | —        | External order ID on the merchant side; must be unique within the key |

`*` To select a crypto asset, pass either `ticker` or the `currency` + `network` pair.

### Response

```json theme={null}
{
  "code": 200,
  "data": {
    "item": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "externalId": "order-123",
      "url": "https://buy.cryptocash.world/merchant/payment/550e8400-e29b-41d4-a716-446655440000"
    }
  }
}
```

| Field        | Description                                           |
| ------------ | ----------------------------------------------------- |
| `id`         | Internal transaction UUID                             |
| `externalId` | External ID from the request or `null`                |
| `url`        | CryptoCash widget URL — redirect the user to this URL |

### What happens after the request

1. Signature and IP are checked.
2. `externalId` uniqueness is checked within the merchant key.
3. The fiat amount is checked against the minimum allowed amount for the selected currency.
4. The `network` value is normalized case-insensitively.
5. A transaction is created with the `New` status.
6. The widget URL is generated.
7. A `payment::created` webhook is sent to the merchant.
8. `id` and `url` are returned.

### CryptoCash widget and provider page

|              | CryptoCash widget                                        | Payment provider page       |
| ------------ | -------------------------------------------------------- | --------------------------- |
| **URL**      | `https://buy.cryptocash.world/merchant/payment/{id}`     | Generated inside the widget |
| **Branding** | Merchant logo                                            | Provider styling            |
| **Purpose**  | Payment method selection, rate preview, native Apple Pay | Direct payment processing   |

**The merchant always receives the CryptoCash widget URL.** The provider page URL is generated inside the widget after the payment method is selected and is not returned to the merchant.

## Merchant Public API

### Retrieve payment

```text theme={null}
POST https://rates.crypto-cash.world/merchant/api/v1/pay-fiat/payments/retrieve
```

Signed request. The `data` field contains one of the following:

```json theme={null}
{ "internalId": "550e8400-..." }
```

or

```json theme={null}
{ "externalId": "order-123" }
```

Response:

```json theme={null}
{
  "code": 200,
  "data": {
    "item": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "externalId": "order-123",
      "status": "Paid",
      "fiatCurrency": "SGD",
      "fiatAmount": "100",
      "cryptoCurrency": "USDT",
      "network": "TRC20",
      "cryptoAmount": "73.21",
      "address": "TXxxx...",
      "hash": "blockchain-hash",
      "widgetUrl": "https://buy.cryptocash.world/merchant/payment/550e8400-...",
      "createdAt": "2026-04-16T10:00:00.000Z",
      "updatedAt": "2026-04-16T10:07:00.000Z"
    }
  }
}
```

### Payment list

```text theme={null}
POST https://rates.crypto-cash.world/merchant/api/v1/pay-fiat/payments/list
```

Signed request. The `data` field:

| Field        | Type     | Default | Description                      |
| ------------ | -------- | ------- | -------------------------------- |
| `page`       | number   | 1       | Page number                      |
| `pageSize`   | number   | 100     | Number of items per page         |
| `dateAfter`  | datetime | —       | Filter: created after this date  |
| `dateBefore` | datetime | —       | Filter: created before this date |

Response:

```json theme={null}
{
  "code": 200,
  "data": {
    "items": [ /* array of payments in the same structure as retrieve */ ],
    "pagination": {
      "page": 1,
      "pageSize": 100,
      "total": 42
    }
  }
}
```

### Currency lists

```text theme={null}
POST https://rates.crypto-cash.world/merchant/api/v1/fiat-currencies/list
POST https://rates.crypto-cash.world/merchant/api/v1/pay-fiat/crypto-currencies/list
```

Both endpoints use signed requests. Minimum payload in `data`:

```json theme={null}
{ "publicKey": "..." }
```

`crypto-currencies/list` returns the cryptocurrencies available to the merchant together with networks and tickers. Values from `tickers` can be passed to `form/retrieve` instead of the `currency` + `network` pair.

### Payment response fields

| Field            | Type     | Nullable | Description                 |
| ---------------- | -------- | -------- | --------------------------- |
| `id`             | string   |          | Transaction UUID            |
| `externalId`     | string   | ✓        | Merchant external ID        |
| `status`         | string   |          | Current status              |
| `fiatCurrency`   | string   |          | Fiat currency code          |
| `fiatAmount`     | string   |          | Fiat amount                 |
| `cryptoCurrency` | string   |          | Cryptocurrency code         |
| `network`        | string   |          | Blockchain network          |
| `cryptoAmount`   | string   | ✓        | Received crypto amount      |
| `address`        | string   |          | Wallet address              |
| `hash`           | string   | ✓        | Blockchain transaction hash |
| `widgetUrl`      | string   | ✓        | Widget URL                  |
| `createdAt`      | datetime |          | Creation time               |
| `updatedAt`      | datetime |          | Last update time            |

### Transaction statuses

| Status     | Description                               |
| ---------- | ----------------------------------------- |
| `New`      | Transaction created, awaiting user action |
| `Waiting`  | Payment accepted and being processed      |
| `Paid`     | Cryptocurrency sent to the wallet address |
| `Fail`     | Payment failed                            |
| `Canceled` | Payment canceled                          |

Statuses are set by the platform based on payment provider events. The merchant cannot change the status via API.

## Error handling

All API errors are returned in one of two formats:

**Format A — coded error:**

```json theme={null}
{ "code": 2020, "message": "..." }
```

**Format B — error list** used for decoding errors:

```json theme={null}
{ "errors": [2010] }
```

### Error codes

| Code   | Description                                                                        |
| ------ | ---------------------------------------------------------------------------------- |
| `1000` | Bad request                                                                        |
| `1102` | Invalid request: unsupported network, amount below minimum, duplicate `externalId` |
| `1110` | Request validation error                                                           |
| `2010` | `data` decoding error                                                              |
| `2011` | `signature` decoding error                                                         |
| `2020` | Invalid signature                                                                  |
| `2021` | API key blocked                                                                    |
| `2030` | IP address forbidden                                                               |
| `3001` | Order not found                                                                    |
| `3002` | Transaction not found                                                              |

## Apple Pay

The CryptoCash widget supports the native Apple Pay flow: the iOS payment sheet opens directly in the widget without redirecting to an external page.

For a detailed description of native mode availability conditions, fallback behavior, and error handling, see the **Apple Native Pay** section.

## Diagrams

### General payment flow

```mermaid theme={null}
sequenceDiagram
    participant M as Merchant
    participant P as Platform
    participant W as Widget
    participant Pr as Payment Provider

    M->>P: POST /merchant/api/v1/pay-fiat/form/retrieve
    P->>P: Validate signature, IP, amount
    P->>P: Create transaction (status: New)
    P->>M: { data: { item: { id, url } } }
    P-->>M: Webhook: payment::created

    M->>W: Open widget URL
    W->>P: Get payment details
    P-->>W: Payment info
    W->>W: User selects payment method

    W->>P: Payment initiated
    P->>Pr: Process payment

    Pr->>P: Status update webhook
    P->>P: Update transaction status
    P-->>M: Webhook: payment::waiting / payment::paid / payment::failed

    Pr->>P: Withdrawal completed webhook
    P->>P: Calculate merchant earning
    P->>P: Update merchant balance
    P-->>M: Webhook: payment::paid
```
