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

# Save a Card

> Create a payment method and guide your customer through the card-saving checkout.

Saving a card starts by creating a **payment method** via the Compago API. This returns a checkout URL where your customer securely enters their card details. Once the card is verified, it's stored securely and you can [charge it on demand](/payment-method/charge).

## Creating a Payment Method

### Endpoint

```
POST /api/payment-method
```

**API reference:** [`POST /payment-method`](/api-reference/payment-method/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}
{
  "externalId": "customer_12345",
  "verificationAmount": 1,
  "billingInformation": {
    "email": "maria.gonzalez@example.com",
    "firstName": "María",
    "lastName": "González",
    "phoneNumber": "+5215512345678"
  },
  "displayMode": "LINK",
  "redirectUrl": "https://yourstore.com/cards/saved",
  "ttlMinutes": 2880
}
```

### Field Descriptions

| Field                | Type    | Required | Description                                                                                                              |
| -------------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------ |
| `externalId`         | string  | No       | Your unique identifier for this payment method (e.g., customer ID).                                                      |
| `verificationAmount` | number  | No       | Amount in MXN for the verification charge. Minimum 1 MXN. Defaults to 1 MXN.                                             |
| `billingInformation` | object  | Yes      | Customer billing details (see below).                                                                                    |
| `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).                                                                |
| `buttonColor`        | string  | No       | Custom hex color for the checkout button, e.g. `"#16A34A"` (EMBEDDED mode only).                                         |
| `redirectUrl`        | string  | No       | URL where the customer is redirected after saving their card. Compago appends `id` and `externalId` as query parameters. |
| `ttlMinutes`         | integer | No       | Time-to-live in minutes for the checkout link. Minimum 1 minute. Defaults to 2,880 (48 hours).                           |

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

### Response

```json theme={null}
{
  "id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "checkoutUrl": "https://app.compago.com/checkout/pm/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
}
```

| Field         | Type   | Description                                                                                |
| ------------- | ------ | ------------------------------------------------------------------------------------------ |
| `id`          | string | Unique identifier of the payment method. Use this to charge, retrieve, or revoke it later. |
| `checkoutUrl` | string | URL where the customer completes the card-saving process.                                  |

### Code Examples

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.harmony.compago.com/api/payment-method \
    -H "x-api-key: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "externalId": "customer_12345",
      "verificationAmount": 1,
      "billingInformation": {
        "email": "maria.gonzalez@example.com",
        "firstName": "María",
        "lastName": "González",
        "phoneNumber": "+5215512345678"
      },
      "redirectUrl": "https://yourstore.com/cards/saved",
      "ttlMinutes": 2880
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch('https://api.harmony.compago.com/api/payment-method', {
    method: 'POST',
    headers: {
      'x-api-key': process.env.COMPAGO_API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      externalId: 'customer_12345',
      verificationAmount: 1,
      billingInformation: {
        email: 'maria.gonzalez@example.com',
        firstName: 'María',
        lastName: 'González',
        phoneNumber: '+5215512345678'
      },
      redirectUrl: 'https://yourstore.com/cards/saved',
      ttlMinutes: 2880
    })
  });

  const { id, checkoutUrl } = await response.json();
  console.log('Payment method ID:', id);
  console.log('Checkout URL:', checkoutUrl);
  ```

  ```python Python theme={null}
  import requests
  import os

  response = requests.post(
      'https://api.harmony.compago.com/api/payment-method',
      headers={
          'x-api-key': os.environ['COMPAGO_API_KEY'],
          'Content-Type': 'application/json'
      },
      json={
          'externalId': 'customer_12345',
          'verificationAmount': 1,
          'billingInformation': {
              'email': 'maria.gonzalez@example.com',
              'firstName': 'María',
              'lastName': 'González',
              'phoneNumber': '+5215512345678'
          },
          'redirectUrl': 'https://yourstore.com/cards/saved',
          'ttlMinutes': 2880
      }
  )

  data = response.json()
  print('Payment method ID:', data['id'])
  print('Checkout URL:', data['checkoutUrl'])
  ```

  ```php PHP theme={null}
  <?php
  $ch = curl_init('https://api.harmony.compago.com/api/payment-method');
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_POST, true);
  curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'x-api-key: ' . getenv('COMPAGO_API_KEY'),
      'Content-Type: application/json'
  ]);
  curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
      'externalId' => 'customer_12345',
      'verificationAmount' => 1,
      'billingInformation' => [
          'email' => 'maria.gonzalez@example.com',
          'firstName' => 'María',
          'lastName' => 'González',
          'phoneNumber' => '+5215512345678'
      ],
      'redirectUrl' => 'https://yourstore.com/cards/saved',
      'ttlMinutes' => 2880
  ]));

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

  echo 'Payment method ID: ' . $data['id'] . PHP_EOL;
  echo 'Checkout URL: ' . $data['checkoutUrl'] . PHP_EOL;
  ?>
  ```

  ```ruby Ruby theme={null}
  require 'net/http'
  require 'json'
  require 'uri'

  uri = URI('https://api.harmony.compago.com/api/payment-method')
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true

  request = Net::HTTP::Post.new(uri)
  request['x-api-key'] = ENV['COMPAGO_API_KEY']
  request['Content-Type'] = 'application/json'
  request.body = {
    externalId: 'customer_12345',
    verificationAmount: 1,
    billingInformation: {
      email: 'maria.gonzalez@example.com',
      firstName: 'María',
      lastName: 'González',
      phoneNumber: '+5215512345678'
    },
    redirectUrl: 'https://yourstore.com/cards/saved',
    ttlMinutes: 2880
  }.to_json

  response = http.request(request)
  data = JSON.parse(response.body)

  puts "Payment method ID: #{data['id']}"
  puts "Checkout URL: #{data['checkoutUrl']}"
  ```
</CodeGroup>

## Display Modes

Compago supports two display modes for the card-saving checkout experience.

### LINK Mode (Default)

In LINK mode, you redirect the customer to the `checkoutUrl` returned by the API. The customer completes the card-saving flow on a full-page Compago checkout page and is then redirected back to your `redirectUrl`.

This is the simplest integration. Just redirect the customer:

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

### EMBEDDED Mode

In EMBEDDED mode, you embed the `checkoutUrl` in an iframe on your own page. This keeps the customer on your site throughout the process. You can customize the checkout button appearance using `buttonText` and `buttonColor`.

```json theme={null}
{
  "displayMode": "EMBEDDED",
  "buttonText": "Save My Card",
  "buttonColor": "#16A34A",
  "billingInformation": { ... },
  "redirectUrl": "https://yourstore.com/cards/saved"
}
```

Embed the checkout URL in your page:

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

<Note>
  In EMBEDDED mode, the iframe handles the entire card-saving flow. You can listen for iframe events to react to success and error states in real time, or use a `redirectUrl` to handle the result via a page redirect. For more details, see [Handling the Result](#handling-the-result).
</Note>

## Verification Charge

When a customer saves their card, Compago places a small **verification charge** to validate that the card is real and has sufficient funds. This charge is automatically **voided** (reversed). The customer is not actually billed.

* **Default amount**: 1 MXN
* **Minimum amount**: 1 MXN
* **What happens**: The charge appears temporarily on the customer's statement and is then voided

You can customize the verification amount:

```json theme={null}
{
  "verificationAmount": 5,
  "billingInformation": { ... }
}
```

<Tip>
  Set the verification amount close to the actual amount you plan to charge the customer later. Issuer banks are more likely to automatically reject future charges that differ significantly from the verification amount, so a closer match reduces the risk of payment declines.
</Tip>

<Tip>
  Some banks may show the verification charge as a pending transaction for a few days before it disappears. Consider notifying your customers about this.
</Tip>

## Handling the Result

After a customer completes the card-saving checkout, you need to handle the result. There are two approaches depending on your integration.

### Using the Redirect URL

When a customer successfully saves their card, Compago redirects them back to your `redirectUrl` with two query parameters:

* `id`: The Compago payment method ID
* `externalId`: The external ID you provided when creating the payment method

For example, if you created a payment method with:

```json theme={null}
{
  "redirectUrl": "https://yourstore.com/cards/saved",
  "externalId": "customer_12345"
}
```

The customer will be redirected to:

```
https://yourstore.com/cards/saved?id=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee&externalId=customer_12345
```

#### Redirect Implementation

<CodeGroup>
  ```javascript Node.js / Express theme={null}
  app.get('/cards/saved', async (req, res) => {
    const { id, externalId } = req.query;

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

    // Fetch the payment method to confirm it's ACTIVE
    const response = await fetch(
      `https://api.harmony.compago.com/api/payment-method/${id}`,
      {
        headers: { 'x-api-key': process.env.COMPAGO_API_KEY }
      }
    );

    const paymentMethod = await response.json();

    if (paymentMethod.status === 'ACTIVE') {
      // Card saved successfully - store the payment method ID
      await savePaymentMethodForCustomer(externalId, id);
      res.render('card-saved-success', {
        lastFour: paymentMethod.card?.lastFourDigits,
        network: paymentMethod.card?.networkType
      });
    } else {
      res.render('card-save-pending', { status: paymentMethod.status });
    }
  });
  ```

  ```python Python / Flask theme={null}
  @app.route('/cards/saved')
  def card_saved():
      payment_method_id = request.args.get('id')
      external_id = request.args.get('externalId')

      if not payment_method_id or not external_id:
          return 'Missing parameters', 400

      # Fetch the payment method to confirm it's ACTIVE
      response = requests.get(
          f'https://api.harmony.compago.com/api/payment-method/{payment_method_id}',
          headers={'x-api-key': os.environ['COMPAGO_API_KEY']}
      )

      payment_method = response.json()

      if payment_method['status'] == 'ACTIVE':
          save_payment_method_for_customer(external_id, payment_method_id)
          return render_template('card_saved_success.html',
              last_four=payment_method.get('card', {}).get('lastFourDigits'),
              network=payment_method.get('card', {}).get('networkType')
          )
      else:
          return render_template('card_save_pending.html',
              status=payment_method['status']
          )
  ```

  ```php PHP theme={null}
  <?php
  $paymentMethodId = $_GET['id'] ?? null;
  $externalId = $_GET['externalId'] ?? null;

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

  $ch = curl_init("https://api.harmony.compago.com/api/payment-method/{$paymentMethodId}");
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'x-api-key: ' . getenv('COMPAGO_API_KEY')
  ]);

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

  if ($paymentMethod['status'] === 'ACTIVE') {
      savePaymentMethodForCustomer($externalId, $paymentMethodId);
      include 'card_saved_success.php';
  } else {
      include 'card_save_pending.php';
  }
  ?>
  ```

  ```ruby Ruby / Rails theme={null}
  class CardsController < ApplicationController
    def saved
      payment_method_id = params[:id]
      external_id = params[:externalId]

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

      response = HTTParty.get(
        "https://api.harmony.compago.com/api/payment-method/#{payment_method_id}",
        headers: { 'x-api-key' => ENV['COMPAGO_API_KEY'] }
      )

      payment_method = response.parsed_response

      if payment_method['status'] == 'ACTIVE'
        save_payment_method_for_customer(external_id, payment_method_id)
        render :card_saved_success, locals: {
          last_four: payment_method.dig('card', 'lastFourDigits'),
          network: payment_method.dig('card', 'networkType')
        }
      else
        render :card_save_pending, locals: { status: payment_method['status'] }
      end
    end
  end
  ```
</CodeGroup>

### Using Iframe Events (EMBEDDED Mode)

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

#### Events Reference

| Event Type                       | Payload                                                                        | When Fired              |
| -------------------------------- | ------------------------------------------------------------------------------ | ----------------------- |
| `COMPAGO_PAYMENT_METHOD_LOADING` | `{ type }`                                                                     | Checkout iframe loads   |
| `COMPAGO_PAYMENT_METHOD_SUCCESS` | `{ type, data: { paymentMethodId, cardLastFour, cardNetwork, billingEmail } }` | Card saved successfully |
| `COMPAGO_PAYMENT_METHOD_ERROR`   | `{ type, error: { code, message } }`                                           | Card verification fails |

#### Listening for Events

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

    switch (event.data.type) {
      case 'COMPAGO_PAYMENT_METHOD_LOADING':
        console.log('Checkout is loading...');
        // Show a loading indicator in your UI
        break;

      case 'COMPAGO_PAYMENT_METHOD_SUCCESS':
        console.log('Card saved!', event.data.data);
        // { paymentMethodId, cardLastFour, cardNetwork, billingEmail }
        const { paymentMethodId, cardLastFour } = event.data.data;
        // Verify via API, then store the payment method
        verifyAndStoreCard(paymentMethodId, cardLastFour);
        break;

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

  ```typescript TypeScript theme={null}
  type CompagoEvent =
    | { type: 'COMPAGO_PAYMENT_METHOD_LOADING' }
    | {
        type: 'COMPAGO_PAYMENT_METHOD_SUCCESS';
        data: {
          paymentMethodId: string;
          cardLastFour: string | null;
          cardNetwork: string | null;
          billingEmail: string;
        };
      }
    | {
        type: 'COMPAGO_PAYMENT_METHOD_ERROR';
        error: { code: string; message: string };
      };

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

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

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

      case 'COMPAGO_PAYMENT_METHOD_SUCCESS':
        console.log('Card saved!', data.data);
        const { paymentMethodId, cardLastFour } = data.data;
        verifyAndStoreCard(paymentMethodId, cardLastFour);
        break;

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

#### Event Payloads

**Loading event:**

```json theme={null}
{
  "type": "COMPAGO_PAYMENT_METHOD_LOADING"
}
```

**Success event:**

```json theme={null}
{
  "type": "COMPAGO_PAYMENT_METHOD_SUCCESS",
  "data": {
    "paymentMethodId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
    "cardLastFour": "4242",
    "cardNetwork": "VISA",
    "billingEmail": "maria.gonzalez@example.com"
  }
}
```

**Error event:**

```json theme={null}
{
  "type": "COMPAGO_PAYMENT_METHOD_ERROR",
  "error": {
    "code": "CARD_DECLINED",
    "message": "The card was declined by the issuing bank."
  }
}
```

<Tip>
  Validate that `event.data.type` starts with `COMPAGO_PAYMENT_METHOD_` 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 method status via the API before trusting the result. The `COMPAGO_PAYMENT_METHOD_SUCCESS` event confirms the checkout completed, but your backend should call `GET /api/payment-method/{id}` to confirm the status is `ACTIVE` before storing the card for future charges.
</Warning>

## Best Practices

<AccordionGroup>
  <Accordion title="Always Verify Payment Method Status">
    After the customer is redirected back, always fetch the payment method from the API to confirm its status is `ACTIVE` before storing it for future charges.

    ```javascript theme={null}
    const paymentMethod = await fetchPaymentMethod(id);
    if (paymentMethod.status !== 'ACTIVE') {
      // Handle non-active status
    }
    ```
  </Accordion>

  <Accordion title="Store the Payment Method ID">
    Save the Compago payment method `id` in your database associated with the customer. You'll need this ID to charge the card later.

    ```javascript theme={null}
    await db.customers.update(externalId, {
      compagoPaymentMethodId: id,
      cardLastFour: paymentMethod.card?.lastFourDigits,
      cardNetwork: paymentMethod.card?.networkType
    });
    ```
  </Accordion>

  <Accordion title="Inform Customers About the Verification Charge">
    Let customers know that a small temporary charge will appear on their statement during the card verification process. This prevents confusion and support requests.
  </Accordion>

  <Accordion title="Handle Expiration Gracefully">
    If the checkout link expires before the customer saves their card, create a new payment method and send the customer the updated checkout URL.
  </Accordion>

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

## Error Handling

| Status Code | Error                        | Solution                                                    |
| ----------- | ---------------------------- | ----------------------------------------------------------- |
| 400         | Invalid billing information  | Ensure all required billing fields are provided and valid.  |
| 400         | Missing organization context | Verify your organization settings are configured correctly. |
| 401         | Unauthorized                 | Check your API key is valid and included in headers.        |

## Security Considerations

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

Your redirect implementation should:

1. **Validate query parameters exist** before processing
2. **Fetch payment method status from Compago API**. Don't trust the redirect alone
3. **Verify the `externalId` matches your records**. Ensure it corresponds to a real customer in your system
4. **Check status is `ACTIVE`** before allowing future charges
5. **Log all redirect attempts** for debugging and security monitoring

## Next Steps

<CardGroup cols={2}>
  <Card title="Charge a Saved Card" icon="money-bill" href="/payment-method/charge">
    Learn how to charge a customer's saved card on demand.
  </Card>

  <Card title="Manage Payment Methods" icon="list" href="/payment-method/manage">
    List, retrieve, and revoke saved payment methods.
  </Card>
</CardGroup>
