Skip to content

Cancel Payment Link

Cancel a payment link to prevent further payments.

PATCH /v1/payment-links/{hashUrl}/cancel

Requires API key authentication via X-API-Key header.

ParameterTypeRequiredDescription
hashUrlstringYesThe hash URL of the payment link
Terminal window
curl -X PATCH "https://api-pay.zelta.dev/v1/payment-links/abc123/cancel" \
-H "X-API-Key: your-api-key-here" \
-H "Content-Type: application/json"
const hashUrl = 'abc123';
const response = await fetch(`https://api-pay.zelta.dev/v1/payment-links/${hashUrl}/cancel`, {
method: 'PATCH',
headers: {
'X-API-Key': 'your-api-key-here',
'Content-Type': 'application/json'
}
});
const result = await response.json();
console.log(result);
import requests
hash_url = 'abc123'
headers = {
'X-API-Key': 'your-api-key-here',
'Content-Type': 'application/json'
}
response = requests.patch(
f'https://api-pay.zelta.dev/v1/payment-links/{hash_url}/cancel',
headers=headers
)
result = response.json()
print(result)
$hashUrl = 'abc123';
$headers = [
'X-API-Key: your-api-key-here',
'Content-Type: application/json'
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api-pay.zelta.dev/v1/payment-links/{$hashUrl}/cancel");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$result = json_decode($response, true);
{
"success": true,
"data": {
"paymentLink": {
"id": "pl_1234567890abcdef",
"paymentLinkUrl": "https://pay.zelta.dev/abc123",
"customerName": "John Doe",
"concept": "Consulting Service",
"amount": 2500,
"status": "cancelled",
"createdAt": "2024-01-15T10:30:00.000Z",
"expiresAt": "2024-01-22T10:30:00.000Z",
"cancelledAt": "2024-01-15T12:00:00.000Z",
"completedAt": null,
"isTest": true,
"redirectUrl": "https://myapp.com/success",
"metadata": {
"orderId": "ORD-001",
"service": "consulting"
}
}
},
"message": "Payment link cancelled successfully",
"timestamp": "2024-01-15T12:00:00.000Z"
}
FieldTypeDescription
idstringUnique payment link identifier
paymentLinkUrlstringURL for the payment link
customerNamestringName of the customer
customerEmailstring|nullCustomer’s email address
conceptstringDescription of what’s being paid for
amountintegerAmount in cents
statusstringPayment link status (now “cancelled”)
createdAtstringISO 8601 timestamp of creation
expiresAtstringISO 8601 timestamp of expiration
cancelledAtstringISO 8601 timestamp of cancellation
completedAtstring|nullISO 8601 timestamp of completion
isTestbooleanWhether this is a test payment
redirectUrlstring|nullCustom redirect URL
metadataobject|nullCustom metadata
CodeDescription
200OK - Payment link cancelled successfully
400Bad Request - Invalid state for cancellation
401Unauthorized - Missing or invalid API key
403Forbidden - Account suspended
404Not Found - Payment link not found or not owned by account
429Too Many Requests - Rate limit exceeded
{
"success": false,
"message": "Payment link not found",
"error": {
"code": "ERR_PAYMENT_LINK_NOT_FOUND"
},
"timestamp": "2024-01-15T12:00:00.000Z"
}
{
"success": false,
"message": "Payment link cannot be cancelled",
"error": {
"code": "ERR_INVALID_STATE",
"details": "Payment link is already completed"
},
"timestamp": "2024-01-15T12:00:00.000Z"
}
{
"success": false,
"message": "Missing X-API-Key header",
"error": {
"code": "ERR_MISSING_API_KEY"
},
"timestamp": "2024-01-15T12:00:00.000Z"
}

Payment links can only be cancelled if they are in the following states:

  • pending - Waiting for payment
  • expired - Expired due to time limit

Payment links cannot be cancelled if they are in the following states:

  • completed - Payment successful
  • cancelled - Already cancelled
async function cancelPaymentLink(apiKey, hashUrl) {
try {
const response = await fetch(`https://api-pay.zelta.dev/v1/payment-links/${hashUrl}/cancel`, {
method: 'PATCH',
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json'
}
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.message);
}
return result.data.paymentLink;
} catch (error) {
console.error('Error cancelling payment link:', error);
throw error;
}
}
// Usage
const cancelledLink = await cancelPaymentLink('your-api-key', 'abc123');
console.log('Payment link cancelled:', cancelledLink);
import requests
def cancel_payment_link(api_key, hash_url):
try:
headers = {
'X-API-Key': api_key,
'Content-Type': 'application/json'
}
response = requests.patch(
f'https://api-pay.zelta.dev/v1/payment-links/{hash_url}/cancel',
headers=headers
)
result = response.json()
if not response.ok:
raise Exception(result['message'])
return result['data']['paymentLink']
except Exception as error:
print(f'Error cancelling payment link: {error}')
raise
# Usage
cancelled_link = cancel_payment_link('your-api-key', 'abc123')
print('Payment link cancelled:', cancelled_link)
async function safeCancelPaymentLink(apiKey, hashUrl) {
try {
// First, check the current status
const getResponse = await fetch(`https://api-pay.zelta.dev/v1/payment-links/${hashUrl}`, {
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json'
}
});
const getResult = await getResponse.json();
if (!getResponse.ok) {
throw new Error(getResult.message);
}
const paymentLink = getResult.data.paymentLink;
// Check if cancellation is allowed
if (paymentLink.status === 'completed') {
throw new Error('Cannot cancel completed payment link');
}
if (paymentLink.status === 'cancelled') {
console.log('Payment link is already cancelled');
return paymentLink;
}
// Proceed with cancellation
return await cancelPaymentLink(apiKey, hashUrl);
} catch (error) {
console.error('Error in safe cancellation:', error);
throw error;
}
}
// Usage
const result = await safeCancelPaymentLink('your-api-key', 'abc123');
console.log('Cancellation result:', result);

Cancel payment links when orders are cancelled:

async function cancelOrderPaymentLink(apiKey, orderId) {
try {
// Get the payment link for the order
const paymentLinks = await getAllPaymentLinks(apiKey);
const orderPaymentLink = paymentLinks.find(link =>
link.metadata?.orderId === orderId
);
if (!orderPaymentLink) {
throw new Error('Payment link not found for order');
}
// Cancel the payment link
const cancelledLink = await cancelPaymentLink(apiKey, orderPaymentLink.paymentLinkUrl.split('/').pop());
// Update order status
await updateOrderStatus(orderId, 'cancelled');
return cancelledLink;
} catch (error) {
console.error('Error cancelling order payment link:', error);
throw error;
}
}

Cancel expired payment links:

async function cleanupExpiredPaymentLinks(apiKey) {
try {
const allLinks = await getAllPaymentLinks(apiKey);
const expiredLinks = allLinks.filter(link =>
link.status === 'expired' ||
new Date(link.expiresAt) < new Date()
);
const results = await Promise.allSettled(
expiredLinks.map(link =>
cancelPaymentLink(apiKey, link.paymentLinkUrl.split('/').pop())
)
);
const successful = results.filter(result => result.status === 'fulfilled');
const failed = results.filter(result => result.status === 'rejected');
console.log(`Cancelled ${successful.length} expired payment links`);
if (failed.length > 0) {
console.log(`Failed to cancel ${failed.length} payment links`);
}
return { successful: successful.length, failed: failed.length };
} catch (error) {
console.error('Error cleaning up expired payment links:', error);
throw error;
}
}

Handle customer requests to cancel payments:

async function handleCustomerCancellationRequest(apiKey, customerId, paymentLinkId) {
try {
// Verify the payment link belongs to the customer
const paymentLink = await getPaymentLink(apiKey, paymentLinkId);
if (paymentLink.metadata?.customerId !== customerId) {
throw new Error('Payment link does not belong to customer');
}
// Check if cancellation is allowed
if (paymentLink.status === 'completed') {
throw new Error('Cannot cancel completed payment');
}
if (paymentLink.status === 'cancelled') {
return { message: 'Payment link is already cancelled', paymentLink };
}
// Cancel the payment link
const cancelledLink = await cancelPaymentLink(apiKey, paymentLinkId);
// Send cancellation confirmation
await sendCancellationConfirmationEmail(customerId, cancelledLink);
return { message: 'Payment link cancelled successfully', paymentLink: cancelledLink };
} catch (error) {
console.error('Error handling customer cancellation:', error);
throw error;
}
}

Always check the current status before attempting cancellation:

async function cancelPaymentLinkSafely(apiKey, hashUrl) {
try {
const paymentLink = await getPaymentLink(apiKey, hashUrl);
if (paymentLink.status === 'completed') {
throw new Error('Cannot cancel completed payment link');
}
if (paymentLink.status === 'cancelled') {
console.log('Payment link is already cancelled');
return paymentLink;
}
return await cancelPaymentLink(apiKey, hashUrl);
} catch (error) {
console.error('Error in safe cancellation:', error);
throw error;
}
}

Implement proper error handling for different scenarios:

async function cancelPaymentLinkWithErrorHandling(apiKey, hashUrl) {
try {
return await cancelPaymentLink(apiKey, hashUrl);
} catch (error) {
if (error.message.includes('not found')) {
return { success: false, error: 'Payment link not found' };
}
if (error.message.includes('invalid state')) {
return { success: false, error: 'Payment link cannot be cancelled' };
}
throw error;
}
}

Log all cancellation events for audit purposes:

async function cancelPaymentLinkWithLogging(apiKey, hashUrl, reason = 'Manual cancellation') {
try {
const paymentLink = await cancelPaymentLink(apiKey, hashUrl);
// Log the cancellation event
console.log('Payment link cancelled:', {
paymentLinkId: paymentLink.id,
hashUrl: hashUrl,
reason: reason,
cancelledAt: paymentLink.cancelledAt,
timestamp: new Date().toISOString()
});
return paymentLink;
} catch (error) {
console.error('Failed to cancel payment link:', {
hashUrl: hashUrl,
reason: reason,
error: error.message,
timestamp: new Date().toISOString()
});
throw error;
}
}

Implement rate limiting for batch cancellation operations:

async function cancelPaymentLinksWithRateLimit(apiKey, hashUrls, delay = 1000) {
const results = [];
for (const hashUrl of hashUrls) {
try {
const paymentLink = await cancelPaymentLink(apiKey, hashUrl);
results.push({
hashUrl,
success: true,
paymentLink
});
} catch (error) {
results.push({
hashUrl,
success: false,
error: error.message
});
}
// Rate limiting delay
await new Promise(resolve => setTimeout(resolve, delay));
}
return results;
}