> ## Documentation Index
> Fetch the complete documentation index at: https://devs.tapipay.la/llms.txt
> Use this file to discover all available pages before exploring further.

# Órdenes de pago

> Cobra una deuda en el momento con la cuenta bancaria que el usuario ya registró, en un solo request.

Una **orden de pago** cobra una deuda **en el momento** con la cuenta bancaria que el usuario registró en TapiPay. En un solo request, `POST /v2/debts/orders` registra la deuda y pide el cargo a su cuenta.

## Cuándo usar una orden de pago

> **Orden de pago**
>
> Un cobro único que quieres disparar ahora, con la cuenta que el usuario ya registró. Por ejemplo, un cargo extra o un pago que el usuario aprobó en tu app.

> **[Débito automático](/es/recursos/debito-automatico.md)**
>
> Si las deudas de un producto se tienen que cobrar solas cada vez que vencen.

> **[Deuda](/es/recursos/deudas.md)**
>
> Si el usuario paga cuando quiere, por su `paymentUrl`, con el medio que elija.

> [!NOTE]
>
> Una orden de pago **no** necesita que el usuario esté adherido a un producto (`POST /adhesions`). Solo necesita que tenga su cuenta bancaria registrada.

## Requisitos previos

* Tu compañía debe tener habilitadas las órdenes de pago y el débito automático (se configura con tu ejecutivo de Tapi).
* El usuario debe existir como [contacto](/es/recursos/contactos.md). Este endpoint no crea contactos en línea.
* El usuario debe haber registrado su cuenta bancaria en la [página de enrolamiento](/es/portal-de-pagos/sdk-adhesiones.md) (no por API). Puedes comprobarlo con [`GET /payment-methods`](/es/recursos/debito-automatico#consultar-los-medios-de-pago-de-un-usuario).

## Crear una orden de pago

```bash theme={null}
curl --request POST \
  --url 'https://tapipay-facade.homo.tapila.cloud/v2/debts/orders' \
  --header 'x-api-key: TU_API_KEY' \
  --header 'x-authorization-token: TU_TOKEN_TAPI' \
  --header 'Content-Type: application/json' \
  --data '{
    "externalRequestId": "CARGO-2026-00042",
    "externalClientId": "CLI-00042",
    "amount": 350.00,
    "paymentMethodType": "BANK_ACCOUNT",
    "externalPaymentMethodId": "b7e4a0c2-9f13-4e6d-8a25-71c3d9e0b4f8",
    "description": "Cargo por material escolar"
  }'
```

### Campos

**externalRequestId** `string` *required*

Tu identificador único de la orden, hasta 250 caracteres. Es la llave de idempotencia: si reintentas, usa el mismo valor. El `debtId` de la deuda resultante es `debt_<externalRequestId>`. Ver [Reintentos](#reintentos-e-idempotencia).

**externalClientId** `string` *required*

El identificador del usuario, el mismo que usó al registrar su cuenta. Tiene que corresponder a un contacto existente.

**amount** `number` *required*

Monto a cobrar en **pesos**, con hasta 2 decimales (mínimo `0.01`), igual que en [deudas](/es/recursos/deudas.md).

**paymentMethodType** `string` *required*

Con qué medio se cobra. Por ahora el único valor es `BANK_ACCOUNT` (la cuenta bancaria registrada).

**externalPaymentMethodId** `string`

La cuenta con la que se cobra, tal como la devuelve [`GET /payment-methods`](/es/recursos/debito-automatico#consultar-los-medios-de-pago-de-un-usuario). Tiene que ser una cuenta vigente del usuario y del tipo indicado en `paymentMethodType`. Si no lo envías, se cobra con la cuenta **predeterminada** del usuario.

**dueDate** `string`

Vencimiento de la deuda, en formato `YYYY-MM-DD`. Si no lo envías, se toma la fecha de hoy en Ciudad de México más 7 días.

**description** `string`

Texto libre de hasta 255 caracteres. No puede ser vacío.

**additionalData** `object`

Objeto plano con tus datos propios. Sigue las mismas reglas que en las deudas. Ver [Convenciones de la API](/es/conceptos/convenciones.md#additionaldata).

**companyCode** `string`

Solo si tu cliente tiene varias companies. Ver [Convenciones de la API](/es/conceptos/convenciones.md#companycode).

> [!WARNING]
>
> El endpoint solo acepta estos campos. Cualquier otro devuelve `400 INVALID_REQUEST`, con el campo rechazado en `details[].field`.

### La respuesta

```json Respuesta 201 theme={null}
{
  "data": {
    "debt": {
      "debtId": "debt_CARGO-2026-00042",
      "externalRequestId": "CARGO-2026-00042",
      "status": "PENDING",
      "amount": 350.00,
      "amountPaid": 0,
      "currency": "MXN",
      "dueDate": "2026-10-29",
      "createdAt": "2026-10-22T15:20:00.000Z",
      "allowOverduePayment": true,
      "contact": {
        "contactId": "con_cmruu1lx2000002l4ctm7b0et",
        "externalClientId": "CLI-00042",
        "createdInline": false
      }
    },
    "order": {
      "orderId": "6f1c2b9e-4d3a-4b8e-9a71-2c5d8e0f1a23",
      "status": "PROCESSING",
      "amount": 350.00,
      "currency": "MXN",
      "paymentMethod": {
        "externalPaymentMethodId": "b7e4a0c2-9f13-4e6d-8a25-71c3d9e0b4f8",
        "paymentMethodType": "BANK_ACCOUNT"
      },
      "estimatedProcessingDate": null
    }
  },
  "requestId": "req_a1b2c3d4e5f6"
}
```

La respuesta trae dos objetos:

* `debt`: la deuda que se registró. Es una deuda como cualquier otra: la puedes leer con `GET /debts/{debtId}`. No admite pagos parciales.
* `order`: el cargo a la cuenta del usuario. `estimatedProcessingDate` es la fecha estimada del cargo (`YYYY-MM-DD`). Por ahora siempre viene en `null`.

## Estados de la orden

El cargo se procesa de forma **asíncrona**: la orden nace en `PROCESSING` y cambia de estado cuando el banco responde.

| `status` | Qué significa |
| - | - |
| `PROCESSING` | El cargo se pidió y todavía no se confirmó. |
| `PAID` | El cargo se cobró. |
| `FAILED` | El cargo no se pudo cobrar. |
| `CHARGED_BACK` | El cargo se cobró y después se revirtió (contracargo). |

El resultado del cargo te llega por webhook: ver [Pago de una orden](/es/webhooks/pago-de-orden.md). También puedes consultar la orden con `GET /v2/debts/orders`.

## Consultar una orden

```bash theme={null}
curl --request GET \
  --url 'https://tapipay-facade.homo.tapila.cloud/v2/debts/orders?externalRequestId=CARGO-2026-00042' \
  --header 'x-api-key: TU_API_KEY' \
  --header 'x-authorization-token: TU_TOKEN_TAPI'
```

```json Respuesta 200 theme={null}
{
  "data": {
    "orderId": "6f1c2b9e-4d3a-4b8e-9a71-2c5d8e0f1a23",
    "status": "PAID",
    "amount": 350.00,
    "currency": "MXN",
    "paymentMethod": {
      "externalPaymentMethodId": "b7e4a0c2-9f13-4e6d-8a25-71c3d9e0b4f8",
      "paymentMethodType": "BANK_ACCOUNT"
    },
    "estimatedProcessingDate": null
  },
  "requestId": "req_a1b2c3d4e5f6"
}
```

`externalRequestId` es obligatorio y es el mismo que enviaste al crear la orden. `data` tiene la misma forma que `order` en la respuesta de creación. Si no existe una orden con ese `externalRequestId`, la respuesta es `404 RESOURCE_NOT_FOUND`.

## Reintentos e idempotencia

`externalRequestId` es la llave de idempotencia de la orden. Así se comporta un reintento con el mismo valor:

| Situación | Respuesta |
| - | - |
| El intento anterior no llegó a registrar la deuda | Se procesa como un request nuevo. |
| La deuda quedó registrada, sigue `PENDING` y el reintento trae el mismo `externalClientId`, `amount` y `externalPaymentMethodId` | Se reutiliza esa deuda y se pide el cargo. |
| La llave ya se usó con otros datos (incluido otro `externalPaymentMethodId`, o enviarlo en un intento y no en el otro), o ya tiene una orden creada | `409 RESOURCE_CONFLICT`. Consulta la orden con `GET /v2/debts/orders` para conocer su estado. |

> [!WARNING]
>
> Ante un `500 INTERNAL_ERROR` en `POST /v2/debts/orders`, **no** sabes si el cargo se pidió. Reintenta siempre con el **mismo** `externalRequestId`, nunca con uno nuevo: así evitas cobrarle dos veces al usuario. Si el reintento responde `409`, consulta la orden.

## Errores

| Causa | Status y `error.code` |
| - | - |
| Campo fuera del contrato, campo obligatorio faltante o campo inválido, `paymentMethodType` distinto de `BANK_ACCOUNT` | `400 INVALID_REQUEST` |
| Los datos de la deuda no cumplen una regla de negocio. No se creó nada | `400 INVALID_REQUEST` |
| `GET` sin `externalRequestId` | `400 INVALID_REQUEST` |
| Tu cliente tiene varias companies y no enviaste `companyCode` | `400 BAD_REQUEST` |
| `companyCode` que no pertenece a tu cliente | `403 RESOLUTION_ERROR` |
| El `externalClientId` no corresponde a un contacto | `404 CONTACT_NOT_FOUND` |
| `GET` de una orden que no existe | `404 RESOURCE_NOT_FOUND` |
| La llave ya se usó con otros datos (incluido el `externalPaymentMethodId`) o ya tiene una orden | `409 RESOURCE_CONFLICT` |
| Tu compañía no tiene habilitadas las órdenes de pago | `422 COMPANY_WITHOUT_DEBT_ID_MODALITY` |
| Tu compañía no tiene habilitado el débito automático | `422 PAYMENT_METHODS_NOT_ENABLED` |
| No enviaste `externalPaymentMethodId` y el usuario no tiene una cuenta predeterminada vigente | `422 NO_ACTIVE_PAYMENT_METHOD` |
| El `externalPaymentMethodId` no existe, no es del usuario, no está vigente o no es del tipo de `paymentMethodType`. No se creó nada | `422 PAYMENT_METHOD_NOT_AVAILABLE` |
| No se pudo crear el cargo. La deuda se canceló y no se cobró nada | `422 ORDER_NOT_CREATED` |
| `POST` con resultado incierto: no se sabe si el cargo se pidió. Reintenta con el mismo `externalRequestId` | `500 INTERNAL_ERROR` |
| `GET` que no se pudo resolver en este momento. Reintenta la consulta | `500 INTERNAL_ERROR` |

> [!NOTE]
>
> En estos dos endpoints, un `companyCode` ajeno responde `403` con el envelope de error (`error.code: RESOLUTION_ERROR` y `requestId`), y no `422` como en el resto de la API. Ver [Manejo de errores](/es/conceptos/errores.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.