Source: https://fincobra.com/docs/checkout/invoices.html


# Invoices

Invoices are the core checkout object. Each invoice represents one USD payment request.

Use the dashboard to configure payment methods and create an API key. Use the API key from your server to create invoices and read invoice state.

## Create an Invoice

```
POST /api/checkout/invoices
```

Creates an invoice using the payment methods configured in the dashboard. The response includes an invoice `id` and `paymentUrl`.

Store both values with your order. Send the customer to `paymentUrl` to complete payment on the hosted payment page.

```bash
curl -X POST https://fincobra.com/api/checkout/invoices \
  -H "X-Api-Key: fc_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "amountUsd": 49.99,
    "productName": "Pro Plan - Monthly",
    "merchantReference": "order_123",
    "customerEmail": "buyer@example.com",
    "metadata": {
      "orderId": "order_123"
    }
  }'
```

### Request Body

| Field               | Type     | Required | Description                                                                                                      |
| ------------------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
| `amountUsd`         | `number` | Yes      | Amount in USD. Minimum `0.01`.                                                                                   |
| `paymentTiming`     | `object` | No       | Override the dashboard default timing for this invoice.                                                          |
| `productName`       | `string` | No       | Product or service description. Max 200 chars.                                                                   |
| `issuedBy`          | `string` | No       | Organization issuing the invoice. Defaults to the dashboard issuer name. Max 120 chars.                          |
| `billTo`            | `string` | No       | Customer or company name shown on the invoice. Max 120 chars.                                                    |
| `merchantReference` | `string` | No       | Your order or internal reference. Max 200 chars.                                                                 |
| `customerId`        | `string` | No       | Your internal customer identifier. Max 200 chars.                                                                |
| `customerEmail`     | `string` | No       | Customer email for reconciliation. Max 320 chars.                                                                |
| `redirectUrl`       | `string` | No       | Override the dashboard redirect URL for this invoice.                                                            |
| `metadata`          | `object` | No       | Arbitrary JSON object attached to the invoice. `metadata.notes`, when present, must be a string up to 140 chars. |

### Invoice Timing

Immediate invoices expire after a configured number of minutes:

```json
{
  "paymentTiming": {
    "mode": "immediate",
    "expiresAfterMinutes": 20
  }
}
```

Due-date invoices remain payable through the due date plus grace period:

```json
{
  "paymentTiming": {
    "mode": "due_date",
    "dueAfterDays": 7,
    "gracePeriodDays": 14
  }
}
```

Due-date invoices require an enabled USDT or USDC payment method. Bitcoin payment methods are only used for immediate invoices.

### Response

FinCobra returns HTTP `201` with the invoice object. Most integrations only need these fields:

```json
{
  "id": "a1b2c3d4-1111-4222-8333-abcdefabcdef",
  "paymentUrl": "https://fincobra.com/pay/a1b2c3d4-1111-4222-8333-abcdefabcdef",
  "amountUsd": 49.99,
  "status": "awaiting_payment",
  "paymentCoverage": "no_payment",
  "receivedAmountUsd": 0,
  "confirmedAmountUsd": 0,
  "productName": "Pro Plan - Monthly",
  "merchantReference": "order_123",
  "customerEmail": "buyer@example.com",
  "metadata": {
    "orderId": "order_123"
  },
  "paymentTiming": {
    "mode": "immediate",
    "expiresAfterMinutes": 20,
    "payableUntilAt": "2026-04-29T10:20:00.000Z"
  },
  "paymentSummary": {
    "paymentCoverage": "no_payment",
    "receivedAmountUsd": 0,
    "confirmedAmountUsd": 0,
    "remainingAmountUsd": 49.99,
    "overpaymentAmountUsd": 0
  }
}
```

The full response also includes payment options and timestamps. See [API Reference](./api-reference.md#create-invoice) for the complete contract.

## Get Invoice Detail

```
GET /api/checkout/invoices/:id
```

Returns a known invoice by `id`. Use this when you need the current invoice state outside a webhook handler.

```bash
curl https://fincobra.com/api/checkout/invoices/a1b2c3d4-1111-4222-8333-abcdefabcdef \
  -H "X-Api-Key: fc_live_..."
```

Short example:

```json
{
  "id": "a1b2c3d4-1111-4222-8333-abcdefabcdef",
  "paymentUrl": "https://fincobra.com/pay/a1b2c3d4-1111-4222-8333-abcdefabcdef",
  "status": "payment_detected",
  "paymentCoverage": "exact_payment",
  "receivedAmountUsd": 49.99,
  "confirmedAmountUsd": 0,
  "confirmations": 1,
  "paymentDetectedAt": "2026-04-29T10:05:00.000Z",
  "confirmedAt": null,
  "merchantReference": "order_123",
  "paymentSummary": {
    "paymentCoverage": "exact_payment",
    "receivedAmountUsd": 49.99,
    "confirmedAmountUsd": 0,
    "remainingAmountUsd": 0,
    "overpaymentAmountUsd": 0
  },
  "payments": []
}
```

## Get Invoice Status

```
GET /api/checkout/invoices/:id/status
```

Use this endpoint when you need a smaller payload for status polling.

```bash
curl https://fincobra.com/api/checkout/invoices/a1b2c3d4-1111-4222-8333-abcdefabcdef/status \
  -H "X-Api-Key: fc_live_..."
```

```json
{
  "status": "payment_detected",
  "paymentCoverage": "exact_payment",
  "receivedAmountUsd": 49.99,
  "confirmedAmountUsd": 0,
  "confirmations": 1,
  "paymentDetectedAt": "2026-04-29T10:05:00.000Z",
  "confirmedAt": null,
  "paidOutOfBandAt": null,
  "paidOutOfBandNote": null,
  "paymentSummary": {
    "paymentCoverage": "exact_payment",
    "receivedAmountUsd": 49.99,
    "confirmedAmountUsd": 0,
    "remainingAmountUsd": 0,
    "overpaymentAmountUsd": 0
  },
  "lastPaymentObservedAt": "2026-04-29T10:05:00.000Z"
}
```

Webhooks are preferred over polling for production order updates. Polling is useful for admin screens, fallback checks, and local testing.

## Status Handling

| Status             | Meaning                                                                         | Recommended merchant action                                                               |
| ------------------ | ------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `awaiting_payment` | No sufficient payment has been detected yet.                                    | Keep the order pending.                                                                   |
| `partially_paid`   | Some funds were detected, but the invoice is not fully paid.                    | Keep the order pending and review `paymentSummary`.                                       |
| `payment_detected` | Sufficient funds were detected but have not reached the confirmation threshold. | Keep the order pending unless your business accepts detected-but-unconfirmed payment.     |
| `confirmed`        | Sufficient funds reached the confirmation threshold.                            | Mark the order paid.                                                                      |
| `paid_out_of_band` | The merchant recorded an external payment for the invoice.                      | Mark the order paid and keep the recorded note for reconciliation.                        |
| `expired`          | The final payable deadline passed before sufficient payment was detected.       | Stop accepting payment for this invoice and prompt the customer to create a new checkout. |
| `voided`           | The invoice was voided before payment was detected.                             | Stop accepting payment for this invoice.                                                  |

## Payment Timing Model

The invoice payable window decides whether payment is on time. A payment first observed at or before `paymentTiming.payableUntilAt` is on time; a payment first observed after that timestamp is late. Payment events can still be observed and confirmed after the payable window, but a late payment does not automatically make an `expired` or `voided` invoice paid.

On-time full payment moves the invoice to `payment_detected` and then `confirmed` after the configured confirmation threshold. For Bitcoin, the post-expiry confirmation grace applies only when the full quoted Bitcoin amount was observed at or before `payableUntilAt`; it gives that on-time Bitcoin payment more time to confirm. It does not make late first payments or late top-ups valid.

If the payable deadline passes while only a partial payment has been observed, the invoice becomes `expired` with `paymentCoverage: "partial_payment"` and opens a `partial_payment` exception. If a full or extra payment arrives after the payable window, the invoice stays `expired` or `voided`, `paymentCoverage` reflects the observed amount, and a `late_payment` exception opens for merchant review.

Exception actions have fixed invoice outcomes:

| Action                  | When available                                                                                          | Final invoice state                                                                |
| ----------------------- | ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `accept_late_payment`   | `late_payment` exception on an `expired` or `voided` invoice after full payment confirmation.           | `confirmed`; the exception is closed.                                              |
| `mark_paid_out_of_band` | Open `late_payment` or `partial_payment` exception when the merchant received payment outside FinCobra. | `paid_out_of_band`; the exception is closed and the note is stored on the invoice. |
| `close_unpaid`          | Open exception on an `expired` or `voided` invoice that should remain unpaid.                           | The invoice stays `expired` or `voided`; the exception is closed.                  |

Overpayment during the payable window does not open an exception. The invoice follows the normal payment lifecycle and shows `paymentCoverage: "overpayment"` plus the excess amount in `paymentSummary.overpaymentAmountUsd`.

## Void an Invoice

```
POST /api/checkout/invoices/:id/void
```

Voids an `awaiting_payment` invoice. Voided invoices are terminal and are no longer payable.

```bash
curl -X POST https://fincobra.com/api/checkout/invoices/a1b2c3d4-1111-4222-8333-abcdefabcdef/void \
  -H "X-Api-Key: fc_live_..." \
  -H "Content-Type: application/json" \
  -d '{}'
```

## Record Payment

```
POST /api/checkout/invoices/:id/record-payment
```

Records an external payment for an `awaiting_payment`, `partially_paid`, or `expired` invoice.

```bash
curl -X POST https://fincobra.com/api/checkout/invoices/a1b2c3d4-1111-4222-8333-abcdefabcdef/record-payment \
  -H "X-Api-Key: fc_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "note": "Paid by wire transfer"
  }'
```

The `note` field is required and can be up to 2000 characters. The updated invoice has `status: "paid_out_of_band"`, `paidOutOfBandAt`, and `paidOutOfBandNote`.

`status` is the fulfillment state for the invoice. `paymentCoverage` explains whether the invoice has no payment, a partial payment, an exact payment, or an overpayment. Overpayment does not open an exception; the invoice remains in its payment lifecycle state and the excess amount is shown in `paymentSummary.overpaymentAmountUsd`. `receivedAmountUsd`, `confirmedAmountUsd`, and `paymentSummary` are USD-denominated.

Use `payments[]` when you need payment-level details such as transaction hash, observed amount, confirmations, and `confirmedAt`.

## Payment Options

Invoices include `paymentOptions[]` for hosted and custom payment pages. A standard hosted-page integration can ignore this field and redirect customers to `paymentUrl`.

Solana wallet payment options use `railType: "address_transfer"` and `network: "solana"` because the connected wallet signs a direct SPL-token transfer to your configured recipient wallet. The payer opens the hosted page in a browser with Phantom, Solflare, Backpack, or another compatible injected Solana wallet, reviews the quoted USDT or USDC transfer, and approves the payment from that wallet. FinCobra records invoice volume and commission internally for billing.

Use `paymentOptions[]` only when you are building custom payment UI. See [API Reference](./api-reference.md#payment-option-object) for the field contract.
