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

# Payment Intents

> Create card-present payment intents for POS terminal integrations.

A **payment intent** represents a request to collect a card-present payment through a Compago POS terminal. Unlike one-time payments (which generate hosted checkout links for online use), payment intents are designed for in-person, terminal-based payment flows where your system orchestrates the transaction and a physical terminal handles card capture.

## What You Can Do

With payment intents, you can:

* **Initiate Terminal Payments**: Create a payment request that a POS terminal picks up and processes
* **Track Payment Status**: Poll for real-time status updates as the terminal processes the card
* **Cancel Pending Payments**: Cancel payment intents that haven't been completed yet
* **Link to Your Systems**: Use external IDs to correlate Compago payments with your internal records
* **Target Specific Terminals**: Optionally route payment requests to a specific terminal by its serial number
* **Return to Your App**: Optionally tell the Compago POS app which app to open once the payment finishes (same-device flow)

## Use Cases

Payment intents are designed for:

* **Custom POS Integrations**: Build your own point-of-sale system that uses Compago terminals for card processing
* **ERP Payment Modules**: Add card-present payment capabilities to ERP systems (e.g., Odoo, SAP)
* **Kiosk Applications**: Self-service kiosks that accept card payments through connected terminals
* **Restaurant/Retail Systems**: Any in-person checkout flow where your software manages the transaction

## How It Works

After creating a payment intent, there are two ways to handle the payment flow depending on your setup. Both start the same way (your backend creates the payment intent via the API), but differ in how the card payment is captured.

### Same-Device Flow (Deep Link)

Use this when the Compago POS app is installed on the **same device** as your integration (e.g., an Android tablet running both your POS app and the Compago app). Your app opens the Compago app via a deep link, and the Compago app handles the entire card capture flow. When the payment is complete, control returns to your app.

<Steps>
  <Step title="Your system creates a payment intent">
    `POST /api/payment-intent` with amount, currency, externalId, and optionally terminalCode. Include `redirectAppId` with your app's Android application id so the Compago app automatically returns to your app when the payment finishes. Compago returns the payment intent with `status: PENDING`.
  </Step>

  <Step title="Your app opens the Compago POS app">
    Use the deep link `compago-pos-app:///charge/payment-intent/{id}` to hand off to the Compago app. The Compago app displays the payment screen and handles card capture.
  </Step>

  <Step title="The Compago app returns to your app">
    When the payment finishes (or is cancelled), the Compago app opens the app specified in `redirectAppId`, handing control back to your app.
  </Step>

  <Step title="Your system polls for status">
    While the Compago app handles the payment, your system polls `GET /api/payment-intent/{id}` every 3 seconds to detect when the payment completes.
  </Step>

  <Step title="Your system fulfills the order">
    Once `paymentSummary.status` is `SUCCESS`, mark the order as paid, print a receipt, update inventory, etc.
  </Step>
</Steps>

### Separate-Device Flow (Polling Only)

Use this when the Compago POS terminal is a **separate physical device** from the one running your integration (e.g., your ERP runs on a desktop and the terminal sits on the counter). Your system simply polls for status while the terminal operator processes the card on the Compago device.

<Steps>
  <Step title="Your system creates a payment intent">
    `POST /api/payment-intent` with amount, currency, externalId, and terminalCode. Compago returns the payment intent with `status: PENDING`.
  </Step>

  <Step title="Compago notifies the POS terminal">
    The terminal automatically receives a notification alerting it that a new payment request is waiting.
  </Step>

  <Step title="The terminal processes the card">
    The terminal operator sees the incoming payment request on the Compago POS app and processes the customer's card.
  </Step>

  <Step title="Your system polls for status">
    `GET /api/payment-intent/{id}` every 3 seconds. Once the terminal completes the payment, the response includes `paymentSummary.status: "SUCCESS"`.
  </Step>

  <Step title="Your system fulfills the order">
    Mark the order as paid, print a receipt, update inventory, etc.
  </Step>
</Steps>

<Tip>
  Both flows use the same API endpoints. The only difference is whether your client-side app opens the Compago POS app via deep link before polling. Choose based on whether your app and the Compago POS app run on the same device.
</Tip>

## Prerequisites

Before creating payment intents, ensure you have:

* An active Compago merchant account with API access
* A valid API key (generate one at **Configuraciones -> Desarrollador** in your [Compago Dashboard](https://app.compago.com))
* At least one Compago POS terminal registered to your organization
* Your integration configured to use **MXN currency**

<Note>
  Payment intents currently support **MXN (Mexican Peso)** only. Transactions in other currencies will be rejected.
</Note>

## Creating a Payment Intent

### Endpoint

```
POST /api/payment-intent
```

**API reference:** [`POST /payment-intent`](/api-reference/payment-intent/create)

### Authentication

Include your API key in the request headers:

```
x-api-key: YOUR_API_KEY
Content-Type: application/json
```

### Request Body

```json theme={null}
{
  "amount": 350.50,
  "currency": "MXN",
  "externalId": "order_12345_pos",
  "terminalCode": "TERMINAL-001",
  "redirectAppId": "com.mycompany.pos"
}
```

### Field Descriptions

| Field           | Type   | Required | Description                                                                                                                                                                                                                                              |
| --------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `amount`        | number | Yes      | Payment amount in MXN.                                                                                                                                                                                                                                   |
| `currency`      | string | Yes      | Currency code. Must be `"MXN"`.                                                                                                                                                                                                                          |
| `externalId`    | string | Yes      | Your unique identifier for this payment intent. Must be unique per organization.                                                                                                                                                                         |
| `terminalCode`  | string | No       | Serial number of the target POS terminal. If provided, the payment intent is routed to that specific terminal. If omitted, the payment intent is available to any terminal in your organization.                                                         |
| `redirectAppId` | string | No       | Android application id of the app the Compago POS app returns to after the payment finishes. If omitted, the terminal falls back to the integration configured locally on the device. See [Returning to Your App](#returning-to-your-app-redirectappid). |

### Response

```json theme={null}
{
  "id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "status": "PENDING",
  "amount": 350.50,
  "currency": "MXN",
  "externalId": "order_12345_pos",
  "redirectAppId": "com.mycompany.pos",
  "organizationId": "org-uuid",
  "terminalId": "terminal-uuid",
  "createdAt": "2026-03-11T10:00:00.000Z",
  "updatedAt": "2026-03-11T10:00:00.000Z"
}
```

Save the returned `id`. You will need it to poll for status, open the Compago app, or cancel the payment intent.

### Example with cURL

```bash theme={null}
curl -X POST https://api.harmony.compago.com/api/payment-intent \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 350.50,
    "currency": "MXN",
    "externalId": "order_12345_pos",
    "terminalCode": "TERMINAL-001",
    "redirectAppId": "com.mycompany.pos"
  }'
```

## Opening the Compago App (Same-Device Flow)

If the Compago POS app is installed on the same device as your integration, you can open it directly using a deep link. The Compago app will display the payment screen for the given payment intent and handle the full card capture flow.

### Deep Link Format

```
compago-pos-app:///charge/payment-intent/{id}
```

Replace `{id}` with the payment intent ID returned from the create endpoint.

### Implementation Example

On Android-based POS devices, you can trigger the deep link by navigating to the URL. For web-based integrations running on the same device, a common approach is using a hidden iframe:

```javascript theme={null}
function openCompagoApp(paymentIntentId) {
  const url = `compago-pos-app:///charge/payment-intent/${paymentIntentId}`;

  const iframe = document.createElement('iframe');
  iframe.style.display = 'none';
  iframe.src = url;

  document.body.appendChild(iframe);

  // Clean up the iframe after it triggers the intent
  setTimeout(() => document.body.removeChild(iframe), 1000);
}
```

<Note>
  The deep link requires the Compago POS app to be installed on the device. If the app is not installed, the deep link will have no effect. Your integration should still poll for status regardless, so the payment can also be processed from a separate device.
</Note>

After opening the Compago app, your system should immediately begin polling for the payment status (see next section). The polling logic is identical for both flows.

## Returning to Your App (redirectAppId)

In the same-device flow, you usually want the terminal to hand control back to your app once the payment finishes. Pass your app's Android application id in the `redirectAppId` field when creating the payment intent:

```json theme={null}
{
  "amount": 350.50,
  "currency": "MXN",
  "externalId": "order_12345_pos",
  "redirectAppId": "com.mycompany.pos"
}
```

### Behavior

* When the payment **completes successfully**, the Compago POS app opens the app identified by `redirectAppId`.
* When the payment intent is **cancelled**, the Compago POS app also returns to that app.
* `redirectAppId` takes precedence over the integration configured locally on the terminal (Settings, Integraciones). If the field is omitted, the terminal keeps using its local configuration.
* The terminal's local "Regresar automáticamente" setting controls the timing: when enabled, the app opens immediately; when disabled, the operator sees a confirmation screen with a button to return to your app.
* If the app is not installed on the terminal device, the terminal shows its standard completion screen instead.

<Note>
  `redirectAppId` only has an effect in the same-device flow. In the separate-device flow there is no app to return to on the terminal, so the field is ignored unless the app happens to be installed on the terminal device.
</Note>

## Polling for Payment Status

After creating a payment intent (and optionally opening the Compago app via deep link), your system should poll the GET endpoint to detect when the terminal completes the payment.

### Endpoint

```
GET /api/payment-intent/{id}
```

**API reference:** [`GET /payment-intent/{id}`](/api-reference/payment-intent/get)

### Response

When the payment is still being processed:

```json theme={null}
{
  "id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "status": "PENDING",
  "expired": false
}
```

When the payment has been completed successfully:

```json theme={null}
{
  "id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "status": "CONFIRMED",
  "expired": false,
  "paymentSummary": {
    "status": "SUCCESS"
  }
}
```

### How to Poll

The recommended approach is:

1. **Wait 3 seconds** between each request
2. **Check the response** for a terminal state:
   * `paymentSummary.status` is `"SUCCESS"`: the payment was completed. Fulfill the order.
   * `expired` is `true`: the payment intent timed out. Prompt the user to retry.
   * `status` is `"CANCELLED"`: the payment intent was cancelled. Handle accordingly.
3. **Repeat** until one of the above conditions is met, or until a maximum timeout (e.g., 10 minutes)

### Example

```bash theme={null}
# Poll every 3 seconds until the status changes
curl -X GET https://api.harmony.compago.com/api/payment-intent/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee \
  -H "x-api-key: YOUR_API_KEY"
```

## Cancelling a Payment Intent

You can cancel a pending payment intent before it is completed or expires. This is useful when the user changes their mind, navigates away, or the order is voided.

### Endpoint

```
POST /api/payment-intent/{id}/cancel
```

**API reference:** [`POST /payment-intent/{id}/cancel`](/api-reference/payment-intent/cancel)

For the full cancel guide, see [Cancel Payment Intent](/payment-intent/cancel).

### Example

```bash theme={null}
curl -X POST https://api.harmony.compago.com/api/payment-intent/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cancel \
  -H "x-api-key: YOUR_API_KEY"
```

<Note>
  Cancelling a payment intent also notifies the POS terminal that the payment request has been withdrawn.
</Note>

## Important Business Rules

### Payment Intent Expiration

<Warning>
  Payment intents **expire 10 minutes after creation**. If the terminal does not process the payment within this window, the payment intent becomes invalid. Create a new payment intent if needed.
</Warning>

### Terminal Routing

When you provide a `terminalCode`:

* The payment intent is routed to that specific terminal
* Any other `PENDING` payment intents for that terminal are automatically cancelled
* This ensures only one active payment intent exists per terminal at any time

When you omit `terminalCode`:

* The payment intent is available to any terminal in your organization
* This is useful when you don't know which terminal will process the payment

### External ID Uniqueness

The `externalId` must be **unique within your organization**. Attempting to create a payment intent with a duplicate `externalId` will result in an error.

Use a combination of your internal identifiers with a timestamp or random suffix to ensure uniqueness across retries. For example: `order-789_1710150000_a1b2c3`.

### Currency Restriction

Currently, only **MXN (Mexican Peso)** is supported. Requests with other currencies will be rejected.

### Payment Intent Status

Payment intents have three statuses:

| Status      | Description                                                                                                           |
| ----------- | --------------------------------------------------------------------------------------------------------------------- |
| `PENDING`   | Payment intent is active, waiting for the terminal to process the card payment.                                       |
| `CONFIRMED` | The terminal has successfully processed the card payment. This is a final state.                                      |
| `CANCELLED` | The payment intent was cancelled (manually or by creating a new intent for the same terminal). This is a final state. |

## Error Handling

### Common Errors

| Status Code | Error                              | Solution                                                                     |
| ----------- | ---------------------------------- | ---------------------------------------------------------------------------- |
| 400         | User does not have an organization | Verify your API key is associated with a valid merchant organization         |
| 400         | Only MXN currency is supported     | Use `"MXN"` as the currency                                                  |
| 400         | Payment intent already confirmed   | The payment was already processed. Cannot cancel a confirmed intent.         |
| 401         | Unauthorized                       | Check your API key is valid and included in headers                          |
| 404         | Terminal not found                 | Verify the `terminalCode` matches a terminal registered to your organization |
| 404         | Payment intent not found           | The payment intent ID does not exist or does not belong to your organization |

### Example Error Response

```json theme={null}
{
  "message": "Only MXN currency is supported."
}
```

## Best Practices

<AccordionGroup>
  <Accordion title="Use Terminal Codes for Directed Payments">
    When you know which terminal should process the payment, always provide the `terminalCode`. This ensures the payment intent is routed to the correct terminal and automatically cancels any stale pending intents on that terminal.
  </Accordion>

  <Accordion title="Handle Expiration Gracefully">
    Payment intents expire after 10 minutes. Your integration should:

    1. Detect expiration in your polling loop (when `expired` is `true`)
    2. Offer the user the option to retry
    3. Create a new payment intent with a new `externalId` if they choose to retry
  </Accordion>

  <Accordion title="Generate Unique External IDs">
    Combine your internal identifiers with a timestamp or random suffix to avoid collisions. For example: `session-5_order-abc_1710150000`. Avoid reusing the same ID for retries, since duplicate `externalId` values within the same organization will be rejected.
  </Accordion>

  <Accordion title="Cancel on User Exit">
    If the user navigates away from the payment screen or cancels the order, send a cancel request for the pending payment intent. This frees up the terminal and prevents stale intents from blocking new payments.
  </Accordion>

  <Accordion title="Use redirectAppId in Same-Device Integrations">
    When your app runs on the same device as the Compago POS app, always send `redirectAppId` with your app's application id. This guarantees a seamless round trip (your app, Compago app, your app) without relying on the terminal's local integration configuration.
  </Accordion>
</AccordionGroup>

## Support

If you encounter issues with payment intents:

* Verify your API key is valid in **Configuraciones -> Desarrollador** at [app.compago.com](https://app.compago.com)
* Ensure you have at least one terminal registered to your organization
* Check that the `terminalCode` matches your terminal's serial number
* Contact Compago support at **[help@compago.com](mailto:help@compago.com)** for assistance
