> ## 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.

# One-Time Payments

> Create secure, single-use payment links for your customers.

A **one-time payment** (OTP) is a secure, single-use checkout link that allows your customers to complete a purchase through Compago's hosted payment page. This is ideal for non-recurring transactions, custom payment requests, or integrations where the payment flow is triggered externally.

## What You Can Do

With one-time payments, you can:

* **Generate Secure Payment Links**: Create unique, time-limited checkout URLs for each transaction
* **Accept Card Payments**: Process credit and debit card payments through Compago's PCI-compliant infrastructure
* **Customize the Experience**: Control where customers are redirected after payment completion
* **Track Payments**: Use your own external IDs to link Compago payments with your internal systems
* **Offer Installment Plans**: Support monthly installment payments (Meses Sin Intereses) when available
* **Set Custom Expiration Times**: Configure payment link expiration from 10 minutes to 48 hours based on your needs

## Use Cases

One-time payments are perfect for:

* **Invoicing**: Send payment links to customers via email or messaging
* **Custom Integrations**: Trigger payments from CRM, ERP, or internal tools
* **eCommerce**: Accept payments for online stores and marketplaces
* **Service Businesses**: Request deposits or full payments for appointments or bookings
* **Point of Sale**: Create payment links for in-person transactions (when integrated with terminal apps)

## How It Works

1. **Create**: Your backend calls the Compago API to create a payment link, providing the amount, customer details, and redirect URLs
2. **Receive**: Compago returns a secure checkout URL where your customer will complete the payment
3. **Redirect**: Send your customer to the checkout URL where they enter their payment details
4. **Process**: Compago processes the payment securely through its payment infrastructure
5. **Callback**: After successful payment, Compago redirects the customer back to your `redirectUrl` **with query parameters** `id` and `externalId` appended
6. **Verify**: On your callback page, extract the payment `id` from the URL and fetch the final payment status using the GET endpoint
7. **Fulfill**: Update your internal systems (mark order as paid, send confirmation email, etc.)

## Prerequisites

Before creating one-time payments, ensure you have:

* An active Compago account with API access
* A valid API key (generate one at **Configuraciones → Desarrollador** in your [Compago Dashboard](https://app.compago.com))
* Your integration configured to use **MXN currency**

<Note>
  One-time payments currently support **MXN (Mexican Peso)** only. Transactions in other currencies will be rejected.
</Note>

## Creating a One-Time Payment

### Endpoint

```
POST /api/one-time-payment
```

**API reference:** [`POST /one-time-payment`](/api-reference/one-time-payment/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": 1200,
  "currency": "MXN",
  "description": "Order #12345 - Blue Widget",
  "externalId": "order_12345_payment_1",
  "redirectUrl": "https://yourstore.com/order/12345/confirmation",
  "homepageUrl": "https://yourstore.com",
  "ttlMinutes": 1440,
  "displayMode": "LINK",
  "billingInformation": {
    "email": "customer@example.com",
    "firstName": "María",
    "lastName": "González",
    "phoneNumber": "+5215512345678",
    "address": {
      "addressLine": "Av. Insurgentes Sur 1234",
      "city": "Ciudad de México",
      "state": "CDMX",
      "zip": "03100"
    }
  }
}
```

### Field Descriptions

| Field                | Type   | Required | Description                                                                                                                                                                                                      |
| -------------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `amount`             | number | Yes      | Payment amount in MXN. Must meet the minimum amount configured for your account.                                                                                                                                 |
| `currency`           | string | Yes      | Currency code. Must be `"MXN"`.                                                                                                                                                                                  |
| `description`        | string | Yes      | Description of the payment shown to the customer during checkout.                                                                                                                                                |
| `externalId`         | string | Yes      | Your unique identifier for this payment. Must be unique per organization.                                                                                                                                        |
| `redirectUrl`        | string | Yes      | URL where customers are redirected after payment. Compago appends `id` (payment ID) and `externalId` (your identifier) as query parameters. See [Handling the Payment Callback](#handling-the-payment-callback). |
| `homepageUrl`        | string | Yes      | Your homepage URL, used if the customer cancels or wants to return to your site.                                                                                                                                 |
| `ttlMinutes`         | number | No       | Optional time-to-live in minutes for the payment link. Minimum 10 minutes, maximum 2,880 minutes (48 hours). Defaults to 48 hours if not specified. See [Custom Expiration Times](#custom-expiration-times).     |
| `displayMode`        | string | No       | `"LINK"` (default) for full-page redirect or `"EMBEDDED"` for iframe. See [Display Modes](#display-modes).                                                                                                       |
| `buttonText`         | string | No       | Custom text for the checkout button (EMBEDDED mode only). 1–40 characters.                                                                                                                                       |
| `buttonColor`        | string | No       | Custom hex color for the checkout button, e.g. `"#16A34A"` (EMBEDDED mode only).                                                                                                                                 |
| `billingInformation` | object | Yes      | Customer billing details (see below).                                                                                                                                                                            |

#### Billing Information

| Field         | Type   | Required | Description                                                    |
| ------------- | ------ | -------- | -------------------------------------------------------------- |
| `email`       | string | Yes      | Customer's email address.                                      |
| `firstName`   | string | Yes      | Customer's first name.                                         |
| `lastName`    | string | Yes      | Customer's last name.                                          |
| `phoneNumber` | string | Yes      | Customer's phone number (include country code, e.g., +521...). |
| `address`     | object | Yes      | Customer's billing address (see below).                        |

#### Address

| Field         | Type   | Required | Description                                                                                               |
| ------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------- |
| `addressLine` | string | Yes      | Street address including number and street name.                                                          |
| `city`        | string | Yes      | City name.                                                                                                |
| `state`       | string | Yes      | Mexican state code (e.g., `"CDMX"`, `"JAL"`, `"NLE"`). See [supported states](#supported-mexican-states). |
| `zip`         | string | Yes      | Postal code.                                                                                              |

### Response

```json theme={null}
{
  "redirectUrl": "https://app.compago.com/checkout/otp/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
}
```

Use the returned `redirectUrl` to redirect your customer to Compago's secure checkout page.

### Example with cURL

```bash theme={null}
curl -X POST https://api.harmony.compago.com/api/one-time-payment \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 1200,
    "currency": "MXN",
    "description": "Order #12345",
    "externalId": "order_12345_payment_1",
    "redirectUrl": "https://yourstore.com/order/12345/confirmation",
    "homepageUrl": "https://yourstore.com",
    "ttlMinutes": 1440,
    "billingInformation": {
      "email": "customer@example.com",
      "firstName": "María",
      "lastName": "González",
      "phoneNumber": "+5215512345678",
      "address": {
        "addressLine": "Av. Insurgentes Sur 1234",
        "city": "Ciudad de México",
        "state": "CDMX",
        "zip": "03100"
      }
    }
  }'
```

## Display Modes

Compago supports two display modes for the one-time payment checkout experience.

### LINK Mode (Default)

In LINK mode, you redirect the customer to the `redirectUrl` returned by the API. The customer completes the payment on a full-page Compago checkout page and is then redirected back to your `redirectUrl` with `id` and `externalId` query parameters.

This is the simplest integration. Just redirect the customer:

```javascript theme={null}
// After creating the one-time payment
window.location.href = checkoutUrl;
```

### EMBEDDED Mode

In EMBEDDED mode, you embed the checkout URL in an iframe on your own page. This keeps the customer on your site throughout the process. Customize the submit button using `buttonText` and `buttonColor`.

```json theme={null}
{
  "displayMode": "EMBEDDED",
  "buttonText": "Pay Now",
  "buttonColor": "#16A34A",
  "amount": 1200,
  "currency": "MXN",
  "description": "Order #12345",
  "externalId": "order_12345_payment_1",
  "redirectUrl": "https://yourstore.com/order/12345/confirmation",
  "homepageUrl": "https://yourstore.com",
  "billingInformation": { ... }
}
```

Embed the checkout URL in your page:

```html theme={null}
<div style="width: 100%; max-width: 600px; margin: 0 auto;">
  <iframe
    src="https://app.compago.com/checkout/otp/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
    width="100%"
    height="760"
    frameborder="0"
    allow="payment"
    style="border: none; border-radius: 8px;"
  ></iframe>
</div>
```

These dimensions are sized to comfortably contain the standard 3DS challenge window the buyer's bank may render during payment authorization. Smaller envelopes risk clipping the bank's verification dialog.

<Note>
  In EMBEDDED mode, the iframe handles the entire payment flow. Listen for iframe events to react to loading, success, and error states in real time. The iframe never self-redirects — your parent page decides what to render after a successful payment.
</Note>

### Iframe Events (EMBEDDED Mode)

In EMBEDDED mode, the checkout iframe sends `postMessage` events to the parent window so you can react without waiting for a redirect.

#### Events Reference

| Event Type                         | Payload                                                             | When Fired                                         |
| ---------------------------------- | ------------------------------------------------------------------- | -------------------------------------------------- |
| `COMPAGO_ONE_TIME_PAYMENT_LOADING` | `{ type }`                                                          | Checkout iframe loads                              |
| `COMPAGO_ONE_TIME_PAYMENT_SUCCESS` | `{ type, data: { paymentId, oneTimePaymentId, amount, currency } }` | Payment completed successfully                     |
| `COMPAGO_ONE_TIME_PAYMENT_ERROR`   | `{ type, error: { code, message } }`                                | Payment failed, link expired, or already processed |

#### Listening for Events

<CodeGroup>
  ```javascript JavaScript theme={null}
  window.addEventListener('message', (event) => {
    // Filter to only Compago one-time payment events
    if (!event.data?.type?.startsWith('COMPAGO_ONE_TIME_PAYMENT_')) {
      return;
    }

    switch (event.data.type) {
      case 'COMPAGO_ONE_TIME_PAYMENT_LOADING':
        console.log('Checkout is loading...');
        break;

      case 'COMPAGO_ONE_TIME_PAYMENT_SUCCESS':
        console.log('Payment completed!', event.data.data);
        // { paymentId, oneTimePaymentId, amount, currency }
        const { paymentId, oneTimePaymentId } = event.data.data;
        // Verify via API, then fulfill the order
        verifyAndFulfill(paymentId, oneTimePaymentId);
        break;

      case 'COMPAGO_ONE_TIME_PAYMENT_ERROR':
        console.error('Checkout error:', event.data.error);
        // { code, message }
        showErrorMessage(event.data.error.message);
        break;
    }
  });
  ```

  ```typescript TypeScript theme={null}
  type CompagoOtpEvent =
    | { type: 'COMPAGO_ONE_TIME_PAYMENT_LOADING' }
    | {
        type: 'COMPAGO_ONE_TIME_PAYMENT_SUCCESS';
        data: {
          paymentId: string;
          oneTimePaymentId: string;
          amount: number;
          currency: string;
        };
      }
    | {
        type: 'COMPAGO_ONE_TIME_PAYMENT_ERROR';
        error: { code: string; message: string };
      };

  window.addEventListener('message', (event: MessageEvent) => {
    const data = event.data as CompagoOtpEvent;

    if (!data?.type?.startsWith('COMPAGO_ONE_TIME_PAYMENT_')) {
      return;
    }

    switch (data.type) {
      case 'COMPAGO_ONE_TIME_PAYMENT_LOADING':
        console.log('Checkout is loading...');
        break;

      case 'COMPAGO_ONE_TIME_PAYMENT_SUCCESS':
        console.log('Payment completed!', data.data);
        verifyAndFulfill(data.data.paymentId, data.data.oneTimePaymentId);
        break;

      case 'COMPAGO_ONE_TIME_PAYMENT_ERROR':
        console.error('Checkout error:', data.error);
        showErrorMessage(data.error.message);
        break;
    }
  });
  ```
</CodeGroup>

#### Error Codes

The `error.code` field on `COMPAGO_ONE_TIME_PAYMENT_ERROR` events lets you distinguish failure modes:

| Code                                 | When                                      |
| ------------------------------------ | ----------------------------------------- |
| `ONE_TIME_PAYMENT_NOT_FOUND`         | The id is unknown or the link was deleted |
| `ONE_TIME_PAYMENT_EXPIRED`           | `expiresAt` has passed                    |
| `ONE_TIME_PAYMENT_ALREADY_PROCESSED` | Status is `CONFIRMED` or `CANCELLED`      |
| `UNSUPPORTED_CARD_BRAND`             | Customer used a non-VISA/Mastercard       |
| `CARD_EXPIRED`                       | Customer's card is expired                |
| `THREEDS_AUTH_FAILED`                | 3DS challenge failed or was aborted       |
| `PAYMENT_PROCESSING_FAILED`          | Bank-level decline or processor error     |
| `PAYMENT_TIMEOUT`                    | Step-up / processor timeout               |
| `UNEXPECTED_ERROR`                   | Fallback for any other failure            |

<Tip>
  Validate that `event.data.type` starts with `COMPAGO_ONE_TIME_PAYMENT_` before processing. This filters out unrelated `postMessage` events from other scripts or browser extensions.
</Tip>

<Warning>
  Even when using iframe events, always verify the payment status via the API before fulfilling the order. The `COMPAGO_ONE_TIME_PAYMENT_SUCCESS` event confirms the checkout completed, but your backend should call `GET /api/one-time-payment/{id}` to confirm the status is `CONFIRMED`.
</Warning>

## Handling the Payment Callback

When a customer successfully completes a payment, Compago redirects them back to your `redirectUrl` with two query parameters automatically appended:

* `id`: The Compago one-time payment ID
* `externalId`: The external ID you provided when creating the payment

This allows you to identify which payment completed and take appropriate action in your system.

### How the Redirect Works

If you created a payment with:

```json theme={null}
{
  "redirectUrl": "https://yourstore.com/order/confirmation",
  "externalId": "order_12345_payment_1"
  // ... other fields
}
```

And Compago returns payment ID `aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee`, then after the customer completes payment, they'll be redirected to:

```
https://yourstore.com/order/confirmation?id=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee&externalId=order_12345_payment_1
```

### Implementation Example

Here's how to handle the callback in your application:

<CodeGroup>
  ```javascript Node.js / Express theme={null}
  // Your callback route handler
  app.get('/order/confirmation', async (req, res) => {
    // 1. Extract query parameters
    const { id, externalId } = req.query;

    if (!id || !externalId) {
      return res.status(400).send('Missing payment parameters');
    }

    // 2. Fetch the payment status from Compago
    const response = await fetch(
      `https://api.harmony.compago.com/api/one-time-payment/${id}`,
      {
        headers: {
          'x-api-key': process.env.COMPAGO_API_KEY
        }
      }
    );

    const payment = await response.json();

    // 3. Update your internal systems
    await updateOrderStatus(externalId, 'paid');
    await sendConfirmationEmail(externalId);

    // 4. Show success page to customer
    res.render('order-success', {
      orderId: externalId,
      amount: payment.amount
    });
  });
  ```

  ```python Python / Flask theme={null}
  from flask import Flask, request, render_template
  import requests
  import os

  app = Flask(__name__)

  @app.route('/order/confirmation')
  def payment_callback():
      # 1. Extract query parameters
      payment_id = request.args.get('id')
      external_id = request.args.get('externalId')

      if not payment_id or not external_id:
          return 'Missing payment parameters', 400

      # 2. Fetch the payment status from Compago
      response = requests.get(
          f'https://api.harmony.compago.com/api/one-time-payment/{payment_id}',
          headers={'x-api-key': os.environ['COMPAGO_API_KEY']}
      )

      payment = response.json()

      # 3. Update your internal systems
      update_order_status(external_id, 'paid')
      send_confirmation_email(external_id)

      # 4. Show success page to customer
      return render_template('order_success.html',
          order_id=external_id,
          amount=payment['amount']
      )
  ```

  ```php PHP theme={null}
  <?php
  // Your callback page: /order/confirmation.php

  // 1. Extract query parameters
  $paymentId = $_GET['id'] ?? null;
  $externalId = $_GET['externalId'] ?? null;

  if (!$paymentId || !$externalId) {
      http_response_code(400);
      die('Missing payment parameters');
  }

  // 2. Fetch the payment status from Compago
  $ch = curl_init("https://api.harmony.compago.com/api/one-time-payment/{$paymentId}");
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'x-api-key: ' . getenv('COMPAGO_API_KEY')
  ]);

  $response = curl_exec($ch);
  $payment = json_decode($response, true);
  curl_close($ch);

  // 3. Update your internal systems
  updateOrderStatus($externalId, 'paid');
  sendConfirmationEmail($externalId);

  // 4. Show success page to customer
  include 'order_success.php';
  ?>
  ```

  ```ruby Ruby / Rails theme={null}
  class OrdersController < ApplicationController
    def confirmation
      # 1. Extract query parameters
      payment_id = params[:id]
      external_id = params[:externalId]

      if payment_id.blank? || external_id.blank?
        render plain: 'Missing payment parameters', status: :bad_request
        return
      end

      # 2. Fetch the payment status from Compago
      response = HTTParty.get(
        "https://api.harmony.compago.com/api/one-time-payment/#{payment_id}",
        headers: { 'x-api-key' => ENV['COMPAGO_API_KEY'] }
      )

      payment = response.parsed_response

      # 3. Update your internal systems
      update_order_status(external_id, 'paid')
      send_confirmation_email(external_id)

      # 4. Show success page to customer
      render :order_success, locals: {
        order_id: external_id,
        amount: payment['amount']
      }
    end
  end
  ```
</CodeGroup>

### Best Practices for Callback Handling

<AccordionGroup>
  <Accordion title="Always Verify Payment Status">
    Never trust the redirect alone. Always fetch the payment details from Compago's API to confirm the status. The redirect happens client-side and could be manipulated.

    ```javascript theme={null}
    // ❌ WRONG - Don't verify
    app.get('/order/confirmation', async (req, res) => {
      await markOrderAsPaid(req.query.externalId);
      res.send('Thank you for your payment!');
    });

    // ✅ CORRECT - Always verify for security
    app.get('/order/confirmation', async (req, res) => {
      const payment = await fetchPaymentStatus(req.query.id);
      await markOrderAsPaid(req.query.externalId);
      res.render('order-success', {
        orderId: req.query.externalId,
        amount: payment.amount
      });
    });
    ```
  </Accordion>

  <Accordion title="Handle Missing Query Parameters">
    Implement validation to handle cases where query parameters might be missing (user navigating directly to the URL, etc.).

    ```javascript theme={null}
    const { id, externalId } = req.query;

    if (!id || !externalId) {
      return res.redirect('/orders'); // or show error page
    }
    ```
  </Accordion>

  <Accordion title="Implement Idempotency">
    The callback URL might be accessed multiple times (user refreshing page, browser back button). Ensure your fulfillment logic is idempotent.

    ```javascript theme={null}
    // Check if order is already marked as paid
    const order = await getOrder(externalId);
    if (order.status === 'paid') {
      return res.render('already-processed');
    }

    // Only mark as paid if status changed
    const payment = await fetchPaymentStatus(id);
    if (payment.status === 'CONFIRMED' && order.status !== 'paid') {
      await markOrderAsPaid(externalId);
      await sendConfirmationEmail(externalId);
    }
    ```
  </Accordion>

  <Accordion title="Use HTTPS for Callback URLs">
    Always use HTTPS URLs for your `redirectUrl` to ensure query parameters are transmitted securely.

    ```javascript theme={null}
    // ✅ CORRECT
    redirectUrl: "https://yourstore.com/order/confirmation"

    // ❌ WRONG - Insecure
    redirectUrl: "http://yourstore.com/order/confirmation"
    ```
  </Accordion>

  <Accordion title="Preserve Existing Query Parameters">
    If your `redirectUrl` already contains query parameters, Compago will append `id` and `externalId` to them. Design your callback handler to work with multiple parameters.

    ```javascript theme={null}
    // If you create payment with:
    redirectUrl: "https://yourstore.com/order/confirmation?lang=es"

    // Customer will be redirected to:
    // https://yourstore.com/order/confirmation?lang=es&id=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee&externalId=order_123

    // Access all parameters normally:
    const { lang, id, externalId } = req.query;
    ```
  </Accordion>
</AccordionGroup>

### Security Considerations

<Warning>
  **Never skip verification!** The redirect URL is visible to the customer and could potentially be manipulated. Always fetch the payment status from Compago's API using the `id` parameter before fulfilling the order.
</Warning>

Your callback implementation should:

1. **Validate query parameters exist** before processing
2. **Fetch payment status from Compago API** - Don't trust the redirect alone
3. **Verify the `externalId` matches your records** - Ensure it corresponds to a real order in your system
4. **Check payment status is CONFIRMED** before fulfilling the order
5. **Log all callback attempts** for debugging and security monitoring
6. **Use rate limiting** on your callback endpoint to prevent abuse

### Testing Your Callback Handler

To test your callback implementation:

1. Create a one-time payment using your test API key
2. Complete the payment in the Compago checkout page
3. Verify your callback page receives the `id` and `externalId` parameters
4. Confirm your system correctly fetches and processes the payment status
5. Test the failure case by creating a payment and letting it expire

You can also manually test by navigating to your callback URL with test parameters:

```
https://yourstore.com/order/confirmation?id=test-payment-id&externalId=test-order-123
```

(Your code should handle this gracefully, likely returning a "payment not found" or similar error.)

## Important Business Rules

### Payment Expiration

<Warning>
  One-time payment links **expire 48 hours after creation by default**. If a customer does not complete the payment within this time, the link becomes invalid and cannot be used.
</Warning>

You can customize the expiration time using the `ttlMinutes` parameter (see [Custom Expiration Times](#custom-expiration-times) below). If your OTP expired and you need to create a new payment for the same transaction, generate a new OTP using a different `externalId`.

### Custom Expiration Times

You can set a custom expiration time for payment links using the optional `ttlMinutes` parameter. This allows you to create payment links that expire sooner or later based on your business needs.

**Configuration:**

* **Minimum**: 10 minutes
* **Maximum**: 2,880 minutes (48 hours)
* **Default**: 2,880 minutes (48 hours) if not specified

**Common use cases:**

<AccordionGroup>
  <Accordion title="Flash Sales (Short TTL)">
    For time-sensitive promotions or limited inventory:

    ```json theme={null}
    {
      "ttlMinutes": 30,  // 30-minute window
      "description": "Flash Sale - Limited Stock!"
      // ... other fields
    }
    ```

    Use short expiration times (10-60 minutes) to create urgency and ensure inventory isn't held indefinitely.
  </Accordion>

  <Accordion title="Standard Invoices (Default TTL)">
    For typical invoice payments:

    ```json theme={null}
    {
      // No ttlMinutes specified - defaults to 48 hours
      "description": "Invoice #12345"
      // ... other fields
    }
    ```

    The default 48-hour window gives customers plenty of time to complete payment at their convenience.
  </Accordion>

  <Accordion title="Event Tickets (Custom TTL)">
    For event registrations with registration deadlines:

    ```json theme={null}
    {
      "ttlMinutes": 720,  // 12 hours
      "description": "Concert Ticket - Register by 8 PM tonight"
      // ... other fields
    }
    ```

    Match the TTL to your event registration deadline to automatically close registration.
  </Accordion>

  <Accordion title="Quote Acceptance (Extended TTL)">
    For quotes that customers need time to review:

    ```json theme={null}
    {
      "ttlMinutes": 2880,  // Full 48 hours
      "description": "Quote #QT-5678 - Valid until [date]"
      // ... other fields
    }
    ```

    Give customers the maximum time to review and approve quotes before they expire.
  </Accordion>
</AccordionGroup>

**Best practices:**

* Balance security and convenience when setting expiration times
* Shorter TTLs (10-60 min) work well for in-person transactions or time-sensitive offers
* Longer TTLs (12-48 hours) are better for invoices and asynchronous payment flows
* Consider your customer's payment behavior and typical checkout time
* Communicate the expiration time to customers (e.g., "Complete payment within 30 minutes")

### Minimum Amount

Each Compago account has a configured minimum payment amount. Requests with amounts below this threshold will be rejected with a 400 error. If you receive this error, ensure your payment amount meets your account's minimum requirement.

### External ID Uniqueness

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

```json theme={null}
{
  "message": "One time payment with externalId order_12345_payment_1 already exists."
}
```

Use unique identifiers for each payment attempt. If a payment fails or expires, create a new one-time payment with a new `externalId`.

### Currency Restriction

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

### Payment Status

One-time payments have three statuses:

* **PENDING**: Payment link is active and waiting for customer to complete payment
* **CONFIRMED**: Customer has successfully completed the payment
* **CANCELLED**: Payment link has been cancelled and can no longer be used

Once a payment is marked as `CONFIRMED` or `CANCELLED`, the one-time payment link can no longer be used. See the [Cancel One-Time Payment](/one-time-payment/cancel) guide to learn how to cancel pending payments.

## Retrieving Payment Details

You can retrieve the status and details of a one-time payment using the GET endpoint.

**Common use case:** After receiving a payment callback at your `redirectUrl`, use the `id` query parameter to fetch the payment status and verify completion before fulfilling the order. See [Handling the Payment Callback](#handling-the-payment-callback) for complete implementation examples.

### Endpoint

```
GET /api/one-time-payment/{id}
```

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

### Response for Active Payments

If the payment is still `PENDING` and has not expired, you'll receive full payment details:

```json theme={null}
{
  "id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "status": "PENDING",
  "amount": 1200,
  "currency": "MXN",
  "description": "Order #12345",
  "externalId": "order_12345_payment_1",
  "expiresAt": "2025-10-30T12:45:00Z",
  "expired": false,
  "homepageUrl": "https://yourstore.com",
  "redirectUrl": "https://yourstore.com/order/12345/confirmation",
  "organization": {
    "name": "Your Business Name",
    "business": {
      "websiteURL": "https://yourstore.com"
    }
  },
  "billingInformation": {
    "phoneNumber": "+5215512345678"
  }
}
```

### Response for Completed or Expired Payments

If the payment is no longer `PENDING` or has expired, the response will contain limited information:

```json theme={null}
{
  "status": "CONFIRMED",
  "homepageUrl": "https://yourstore.com",
  "expired": false,
  "organization": {
    "name": "Your Business Name",
    "business": {
      "websiteURL": "https://yourstore.com"
    }
  },
  "billingInformation": {
    "phoneNumber": "+5215512345678"
  }
}
```

<Tip>
  The `expired` field indicates whether the payment link has passed its 48-hour expiration time, regardless of its status.
</Tip>

## Supported Mexican States

Use the following state codes in the `address.state` field:

| Code   | State               |
| ------ | ------------------- |
| `AGU`  | Aguascalientes      |
| `BCN`  | Baja California     |
| `BCS`  | Baja California Sur |
| `CAM`  | Campeche            |
| `CHP`  | Chiapas             |
| `CHH`  | Chihuahua           |
| `CDMX` | Ciudad de México    |
| `COA`  | Coahuila            |
| `COL`  | Colima              |
| `DUR`  | Durango             |
| `MEX`  | Estado de México    |
| `GUA`  | Guanajuato          |
| `GRO`  | Guerrero            |
| `HID`  | Hidalgo             |
| `JAL`  | Jalisco             |
| `MIC`  | Michoacán           |
| `MOR`  | Morelos             |
| `NAY`  | Nayarit             |
| `NLE`  | Nuevo León          |
| `OAX`  | Oaxaca              |
| `PUE`  | Puebla              |
| `QUE`  | Querétaro           |
| `ROO`  | Quintana Roo        |
| `SLP`  | San Luis Potosí     |
| `SIN`  | Sinaloa             |
| `SON`  | Sonora              |
| `TAB`  | Tabasco             |
| `TAM`  | Tamaulipas          |
| `TLA`  | Tlaxcala            |
| `VER`  | Veracruz            |
| `YUC`  | Yucatán             |
| `ZAC`  | Zacatecas           |

## Error Handling

### Common Errors

| Status Code | Error                        | Solution                                                     |
| ----------- | ---------------------------- | ------------------------------------------------------------ |
| 400         | Amount below minimum         | Ensure amount meets your account's minimum payment threshold |
| 400         | Duplicate externalId         | Use a unique externalId for each payment                     |
| 400         | Currency not supported       | Use `"MXN"` as the currency                                  |
| 401         | Unauthorized                 | Check your API key is valid and included in headers          |
| 400         | Missing organization context | Verify your organization settings are configured correctly   |

### Example Error Response

```json theme={null}
{
  "message": "One time payment amount must be at least $[minimum_amount]."
}
```

## Best Practices

1. **Generate Unique External IDs**: Use a combination of order ID and timestamp to ensure uniqueness
2. **Handle Expiration**: Implement logic to create new payment links if the 48-hour window expires
3. **Validate Amounts**: Ensure amounts meet your account's minimum payment threshold before calling the API
4. **Store Payment IDs**: Save the returned one-time payment ID to track and verify payments later
5. **Implement Webhooks**: Use webhooks (if available) to receive real-time payment status updates
6. **Test Thoroughly**: Use the demo environment to test your integration before going live
7. **Implement Robust Callback Handling**: Always verify payment status in your `redirectUrl` callback by fetching the payment using the `id` query parameter. Never assume payment success based on the redirect alone. See [Handling the Payment Callback](#handling-the-payment-callback).

## Support

If you encounter issues with one-time payments:

* Verify your API key is valid in **Configuraciones → Desarrollador** at [app.compago.com](https://app.compago.com)
* Ensure all required fields are included in your request
* Check that amounts meet the minimum threshold
* Verify your organization settings are configured correctly
* Contact Compago support at **[help@compago.com](mailto:help@compago.com)** for assistance
