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

# Cancel One-Time Payment

> Cancel active payment links before they expire or are used

## Overview

The Cancel One-Time Payment endpoint allows you to cancel an active payment link before it is used or expires. Once cancelled, the payment link cannot be completed by customers and cannot be reactivated.

**Common use cases:**

* Customer requests to cancel an order
* Duplicate payment links were created by mistake
* Order or service was cancelled before payment
* Payment details need to be corrected (cancel and create new link)

<Warning>
  Cancellation is **permanent and irreversible**. Once a payment link is cancelled, it cannot be reactivated. If you need to accept payment again, you must create a new payment link.
</Warning>

## Authentication

This endpoint requires API key authentication using the `x-api-key` header.

<Note>
  Your API key can be found in your organization settings. Keep it secure and never expose it in client-side code.
</Note>

## Cancellation Rules

You can only cancel a payment link if **all** of these conditions are met:

<Steps>
  <Step title="Payment status is PENDING">
    The payment must be in `PENDING` status. You cannot cancel payments that are already `CONFIRMED` or `CANCELLED`.
  </Step>

  <Step title="Payment has not expired">
    The payment link must not have passed its expiration date (`expiresAt`). Expired payments cannot be cancelled.
  </Step>

  <Step title="Payment belongs to your organization">
    You can only cancel payment links that belong to your organization.
  </Step>
</Steps>

## Request Format

<ParamField path="id" type="string" required>
  The unique identifier (UUID) of the one-time payment to cancel.
</ParamField>

## Response Format

The endpoint returns the cancelled payment object with `status: "CANCELLED"`:

```json theme={null}
{
  "id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "status": "CANCELLED",
  "description": "Payment for order #1234",
  "amount": 100,
  "currency": "MXN",
  "externalId": "order-1234",
  "expiresAt": "2026-01-11T10:30:00Z",
  "createdAt": "2026-01-09T10:30:00Z",
  "updatedAt": "2026-01-12T14:25:00Z"
}
```

<Note>
  Notice the `updatedAt` timestamp reflects when the cancellation occurred.
</Note>

## Examples

### Cancel a payment link

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PATCH 'https://api.harmony.compago.com/api/one-time-payment/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cancel' \
    -H 'x-api-key: YOUR_API_KEY'
  ```

  ```javascript Node.js theme={null}
  const paymentId = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee';

  const response = await fetch(
    `https://api.harmony.compago.com/api/one-time-payment/${paymentId}/cancel`,
    {
      method: 'PATCH',
      headers: {
        'x-api-key': 'YOUR_API_KEY'
      }
    }
  );

  if (response.ok) {
    const cancelledPayment = await response.json();
    console.log(`Payment ${cancelledPayment.id} cancelled successfully`);
    console.log(`Status: ${cancelledPayment.status}`); // "CANCELLED"
  } else {
    const error = await response.json();
    console.error('Failed to cancel payment:', error);
  }
  ```

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

  payment_id = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'

  response = requests.patch(
      f'https://api.harmony.compago.com/api/one-time-payment/{payment_id}/cancel',
      headers={'x-api-key': 'YOUR_API_KEY'}
  )

  if response.status_code == 200:
      cancelled_payment = response.json()
      print(f"Payment {cancelled_payment['id']} cancelled successfully")
      print(f"Status: {cancelled_payment['status']}")  # "CANCELLED"
  else:
      print(f"Failed to cancel payment: {response.text}")
  ```

  ```php PHP theme={null}
  <?php
  $paymentId = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee';

  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, "https://api.harmony.compago.com/api/one-time-payment/{$paymentId}/cancel");
  curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH");
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'x-api-key: YOUR_API_KEY'
  ]);

  $response = curl_exec($ch);
  $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  curl_close($ch);

  if ($httpCode === 200) {
      $cancelledPayment = json_decode($response, true);
      echo "Payment {$cancelledPayment['id']} cancelled successfully\n";
      echo "Status: {$cancelledPayment['status']}\n"; // "CANCELLED"
  } else {
      echo "Failed to cancel payment: {$response}\n";
  }
  ?>
  ```

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

  payment_id = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
  uri = URI("https://api.harmony.compago.com/api/one-time-payment/#{payment_id}/cancel")

  request = Net::HTTP::Patch.new(uri)
  request['x-api-key'] = 'YOUR_API_KEY'

  response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
    http.request(request)
  end

  if response.code == '200'
    cancelled_payment = JSON.parse(response.body)
    puts "Payment #{cancelled_payment['id']} cancelled successfully"
    puts "Status: #{cancelled_payment['status']}" # "CANCELLED"
  else
    puts "Failed to cancel payment: #{response.body}"
  end
  ```
</CodeGroup>

### Cancel with error handling

<CodeGroup>
  ```javascript Node.js theme={null}
  async function cancelPayment(paymentId) {
    try {
      const response = await fetch(
        `https://api.harmony.compago.com/api/one-time-payment/${paymentId}/cancel`,
        {
          method: 'PATCH',
          headers: { 'x-api-key': 'YOUR_API_KEY' }
        }
      );

      if (!response.ok) {
        if (response.status === 404) {
          throw new Error('Payment not found');
        } else if (response.status === 400) {
          const error = await response.json();
          throw new Error(`Cannot cancel payment: ${error.message}`);
        } else if (response.status === 401) {
          throw new Error('Invalid API key');
        }
        throw new Error(`Unexpected error: ${response.status}`);
      }

      const cancelledPayment = await response.json();
      return cancelledPayment;
    } catch (error) {
      console.error('Error cancelling payment:', error.message);
      throw error;
    }
  }

  // Usage
  try {
    const result = await cancelPayment('aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee');
    console.log('Successfully cancelled:', result);
  } catch (error) {
    console.error('Cancellation failed:', error.message);
  }
  ```

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

  def cancel_payment(payment_id):
      """Cancel a payment with proper error handling"""
      try:
          response = requests.patch(
              f'https://api.harmony.compago.com/api/one-time-payment/{payment_id}/cancel',
              headers={'x-api-key': 'YOUR_API_KEY'}
          )

          if response.status_code == 404:
              raise ValueError('Payment not found')
          elif response.status_code == 400:
              error_data = response.json()
              raise ValueError(f"Cannot cancel payment: {error_data.get('message')}")
          elif response.status_code == 401:
              raise ValueError('Invalid API key')
          elif response.status_code != 200:
              raise ValueError(f'Unexpected error: {response.status_code}')

          return response.json()
      except requests.exceptions.RequestException as e:
          print(f'Request error: {e}')
          raise

  # Usage
  try:
      result = cancel_payment('aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee')
      print(f"Successfully cancelled: {result}")
  except ValueError as e:
      print(f"Cancellation failed: {e}")
  ```
</CodeGroup>

### Check status before cancelling

<CodeGroup>
  ```javascript Node.js theme={null}
  async function cancelPaymentSafely(paymentId) {
    // First, get the current payment status
    const getResponse = await fetch(
      `https://api.harmony.compago.com/api/one-time-payment/${paymentId}`,
      {
        method: 'GET',
        headers: { 'x-api-key': 'YOUR_API_KEY' }
      }
    );

    if (!getResponse.ok) {
      throw new Error('Payment not found');
    }

    const payment = await getResponse.json();

    // Check if payment can be cancelled
    if (payment.status !== 'PENDING') {
      throw new Error(`Cannot cancel payment with status: ${payment.status}`);
    }

    if (payment.expired) {
      throw new Error('Cannot cancel expired payment');
    }

    // Payment can be cancelled, proceed
    const cancelResponse = await fetch(
      `https://api.harmony.compago.com/api/one-time-payment/${paymentId}/cancel`,
      {
        method: 'PATCH',
        headers: { 'x-api-key': 'YOUR_API_KEY' }
      }
    );

    if (!cancelResponse.ok) {
      throw new Error('Failed to cancel payment');
    }

    return await cancelResponse.json();
  }

  // Usage
  try {
    const cancelled = await cancelPaymentSafely('aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee');
    console.log('Payment cancelled:', cancelled);
  } catch (error) {
    console.error('Error:', error.message);
  }
  ```

  ```python Python theme={null}
  import requests
  from datetime import datetime

  def cancel_payment_safely(payment_id):
      """Check payment status before attempting to cancel"""
      # First, get the current payment status
      get_response = requests.get(
          f'https://api.harmony.compago.com/api/one-time-payment/{payment_id}',
          headers={'x-api-key': 'YOUR_API_KEY'}
      )

      if get_response.status_code != 200:
          raise ValueError('Payment not found')

      payment = get_response.json()

      # Check if payment can be cancelled
      if payment['status'] != 'PENDING':
          raise ValueError(f"Cannot cancel payment with status: {payment['status']}")

      if payment.get('expired', False):
          raise ValueError('Cannot cancel expired payment')

      # Payment can be cancelled, proceed
      cancel_response = requests.patch(
          f'https://api.harmony.compago.com/api/one-time-payment/{payment_id}/cancel',
          headers={'x-api-key': 'YOUR_API_KEY'}
      )

      if cancel_response.status_code != 200:
          raise ValueError('Failed to cancel payment')

      return cancel_response.json()

  # Usage
  try:
      cancelled = cancel_payment_safely('aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee')
      print(f"Payment cancelled: {cancelled}")
  except ValueError as e:
      print(f"Error: {e}")
  ```
</CodeGroup>

## Error Responses

<ResponseField name="400" type="error">
  **Cannot cancel payment** - The payment is not in `PENDING` status or has already expired.

  Common reasons:

  * Payment status is `CONFIRMED` (already paid)
  * Payment status is `CANCELLED` (already cancelled)
  * Payment has passed its `expiresAt` date
</ResponseField>

<ResponseField name="404" type="error">
  **Payment not found** - No payment with the specified ID exists in your organization.
</ResponseField>

<ResponseField name="401" type="error">
  **Unauthorized** - Missing or invalid API key.
</ResponseField>

## Best Practices

<Tip>
  **Check status first** - Before cancelling, fetch the payment details to verify it can be cancelled. This prevents unnecessary API calls and provides better error messages.
</Tip>

<Tip>
  **Log cancellations** - Keep audit logs of cancelled payments including the reason and who initiated the cancellation.
</Tip>

<Tip>
  **Notify customers** - If a customer initiated a payment link that you're cancelling, notify them via email that the payment link is no longer valid.
</Tip>

<Warning>
  **Handle race conditions** - If a customer completes a payment at the same time you cancel it, the cancellation will fail with a 400 error. The payment platform uses database locks to prevent race conditions.
</Warning>

## Common Use Cases

### Order Cancellation Workflow

```javascript theme={null}
async function handleOrderCancellation(orderId) {
  // 1. Find the payment link for this order
  const params = new URLSearchParams({
    status: 'PENDING',
    page: '1',
    pageSize: '100'
  });

  const listResponse = await fetch(
    `https://api.harmony.compago.com/api/one-time-payment?${params}`,
    { headers: { 'x-api-key': 'YOUR_API_KEY' } }
  );

  const { items } = await listResponse.json();
  const payment = items.find(p => p.externalId === orderId);

  if (!payment) {
    console.log('No pending payment found for this order');
    return;
  }

  // 2. Cancel the payment link
  try {
    const cancelResponse = await fetch(
      `https://api.harmony.compago.com/api/one-time-payment/${payment.id}/cancel`,
      {
        method: 'PATCH',
        headers: { 'x-api-key': 'YOUR_API_KEY' }
      }
    );

    if (cancelResponse.ok) {
      console.log(`Payment link for order ${orderId} cancelled`);

      // 3. Notify the customer
      await sendEmailNotification(payment.billingInformation.email, {
        subject: 'Order Cancelled',
        message: `Your order ${orderId} has been cancelled. The payment link is no longer valid.`
      });
    }
  } catch (error) {
    console.error('Failed to cancel payment:', error);
  }
}
```

### Bulk Cancellation

```javascript theme={null}
async function cancelExpiringSoonPayments() {
  // Find all pending payments
  const params = new URLSearchParams({
    status: 'PENDING',
    page: '1',
    pageSize: '500'
  });

  const listResponse = await fetch(
    `https://api.harmony.compago.com/api/one-time-payment?${params}`,
    { headers: { 'x-api-key': 'YOUR_API_KEY' } }
  );

  const { items } = await listResponse.json();

  // Filter payments expiring in next hour
  const oneHourFromNow = new Date(Date.now() + 60 * 60 * 1000);
  const expiringSoon = items.filter(payment => {
    const expiresAt = new Date(payment.expiresAt);
    return expiresAt <= oneHourFromNow;
  });

  console.log(`Found ${expiringSoon.length} payments expiring soon`);

  // Cancel them in parallel
  const cancellationPromises = expiringSoon.map(payment =>
    fetch(
      `https://api.harmony.compago.com/api/one-time-payment/${payment.id}/cancel`,
      {
        method: 'PATCH',
        headers: { 'x-api-key': 'YOUR_API_KEY' }
      }
    ).catch(error => {
      console.error(`Failed to cancel ${payment.id}:`, error);
      return null;
    })
  );

  const results = await Promise.all(cancellationPromises);
  const successful = results.filter(r => r?.ok).length;
  console.log(`Successfully cancelled ${successful}/${expiringSoon.length} payments`);
}
```

### Duplicate Detection and Cancellation

```javascript theme={null}
async function findAndCancelDuplicates(externalId) {
  // Find all pending payments with the same externalId
  const params = new URLSearchParams({
    status: 'PENDING',
    page: '1',
    pageSize: '500'
  });

  const listResponse = await fetch(
    `https://api.harmony.compago.com/api/one-time-payment?${params}`,
    { headers: { 'x-api-key': 'YOUR_API_KEY' } }
  );

  const { items } = await listResponse.json();
  const duplicates = items.filter(p => p.externalId === externalId);

  if (duplicates.length <= 1) {
    console.log('No duplicates found');
    return;
  }

  console.log(`Found ${duplicates.length} payments with externalId: ${externalId}`);

  // Keep the most recent, cancel the rest
  const sorted = duplicates.sort((a, b) =>
    new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
  );

  const toCancel = sorted.slice(1); // All except the first (most recent)

  for (const payment of toCancel) {
    try {
      await fetch(
        `https://api.harmony.compago.com/api/one-time-payment/${payment.id}/cancel`,
        {
          method: 'PATCH',
          headers: { 'x-api-key': 'YOUR_API_KEY' }
        }
      );
      console.log(`Cancelled duplicate: ${payment.id}`);
    } catch (error) {
      console.error(`Failed to cancel ${payment.id}:`, error);
    }
  }

  console.log(`Kept payment: ${sorted[0].id}`);
}
```

## Integration Tips

<Tip>
  **Webhook Integration** - If you use webhooks for payment notifications, ensure your system can handle cancellation events to keep your order status in sync.
</Tip>

<Tip>
  **Idempotency** - Attempting to cancel an already cancelled payment will return a 400 error. Check the error message to determine if the payment was previously cancelled.
</Tip>

<Tip>
  **External ID Tracking** - Use the `externalId` field to easily find and cancel payment links associated with specific orders or transactions in your system.
</Tip>

## What Happens After Cancellation

After a payment link is successfully cancelled:

1. **Status Change** - The payment status changes from `PENDING` to `CANCELLED`
2. **Customer Access** - Customers can no longer access or complete the payment using the payment link URL
3. **Irreversible** - The cancellation cannot be undone
4. **New Payment Required** - If payment is still needed, you must create a new payment link

<Note>
  Cancelled payments remain in your system for record-keeping and can be retrieved via the List or Get endpoints.
</Note>

## Next Steps

<CardGroup cols={2}>
  <Card title="Create Payment" icon="plus" href="/one-time-payment/create">
    Create a new payment link to replace the cancelled one
  </Card>

  <Card title="List Payments" icon="list" href="/one-time-payment/list">
    View all payment links including cancelled ones
  </Card>
</CardGroup>
