# Cancel Pending Transfer
> Source: /api-reference/members/me/transfers/cancel-pending
> Cancel a pending transfer and return points to sender
> Endpoint: POST /members/me/transfers/{transferId}/cancel

## Overview [#overview]

Cancels a pending transfer and returns the points to the sender's account. Only the sender can cancel their own pending transfers. This action is irreversible and immediately returns the points to your available balance.

## Authentication [#authentication]

Requires a valid Clerk JWT token in the Authorization header.

## Security [#security]

**Reverification Required**: This endpoint requires reverification if the user hasn't verified their credentials within the last 10 minutes. This is a sensitive action involving point transfers.

## Path Parameters [#path-parameters]

<ParamField path="transferId" type="string" required>
  Pending transfer ID to cancel
</ParamField>

<RequestExample>

```bash cURL
curl -X POST "http://localhost:3000/v1/members/me/transfers/123e4567-e89b-12d3-a456-426614174000/cancel" \
  -H "Authorization: Bearer YOUR_CLERK_JWT"
```

```typescript TypeScript
const cancelPendingTransfer = async (transferId: string) => {
  const response = await fetch(`http://localhost:3000/v1/members/me/transfers/${transferId}/cancel`, {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_CLERK_JWT'
    }
  });
  
  return response.json();
};
```

</RequestExample>

## Response [#response]

<ResponseField name="message" type="string" required>
  Success message confirming the cancellation
</ResponseField>

<ResponseExample>

```json 200 Response - Successfully Cancelled
{
  "message": "Transfer cancelled successfully"
}
```

```json 400 Response - Not Cancellable
{
  "statusCode": 400,
  "message": "Transfer has already been claimed and cannot be cancelled",
  "error": "TRANSFER_NOT_CANCELLABLE"
}
```

```json 400 Response - Expired Transfer
{
  "statusCode": 400,
  "message": "Transfer has expired and cannot be cancelled",
  "error": "TRANSFER_NOT_CANCELLABLE"
}
```

```json 403 Response - Not Sender
{
  "statusCode": 403,
  "message": "You are not authorized to cancel this transfer",
  "error": "UNAUTHORIZED"
}
```

```json 403 Response - Reverification Required
{
  "statusCode": 403,
  "message": "Reverification required for this sensitive action"
}
```

```json 404 Response - Transfer Not Found
{
  "statusCode": 404,
  "message": "Transfer not found",
  "error": "NOT_FOUND"
}
```

</ResponseExample>

## Cancellation Rules [#cancellation-rules]

### Who Can Cancel [#who-can-cancel]
- **Sender Only**: Only the member who created the transfer
- **No Recipient Cancellation**: Recipients cannot cancel incoming transfers
- **Authorization**: Verified through member ID matching

### When You Can Cancel [#when-you-can-cancel]
- **Status**: Only transfers with status "PENDING"
- **Not Claimed**: Transfer has not been claimed by recipient
- **Not Expired**: Transfer has not passed expiration date
- **Before Completion**: Cannot cancel completed transfers

### When You Cannot Cancel [#when-you-cannot-cancel]
| Status | Description | Reason |
|--------|-------------|--------|
| CLAIMED | Recipient has claimed the transfer | Points already transferred |
| EXPIRED | Transfer has passed expiration date | System auto-expired |
| CANCELLED | Already cancelled | Cannot cancel twice |
| COMPLETED | Direct transfer (non-pending) | Immediate completion |

## What Happens When You Cancel [#what-happens-when-you-cancel]

### Points Return [#points-return]
- **Immediate**: Points returned to sender's account instantly
- **Full Amount**: Complete original transfer amount returned
- **No Fees**: No cancellation fees or penalties
- **Balance Update**: Wallet balance updated in real-time

### Transfer Status [#transfer-status]
- **Status Change**: Transfer marked as CANCELLED
- **Timestamp**: `cancelledAt` field set to current time
- **Immutable**: Cannot uncanccel once cancelled
- **Audit Trail**: Cancellation recorded for both parties

### Notifications [#notifications]
- **Sender**: Confirmation of successful cancellation
- **Recipient**: Notification that gift was cancelled (if they have an account)
- **Email/SMS**: Notification sent to pending transfer contact

## Use Cases [#use-cases]

### Change of Mind [#change-of-mind]
```typescript
// Cancel a transfer you no longer want to send
const cancelGiftTransfer = async (transferId: string) => {
  try {
    const response = await fetch(`/api/members/me/transfers/${transferId}/cancel`, {
      method: 'POST'
    });
    
    if (response.ok) {
      const result = await response.json();
      console.log('Transfer cancelled:', result.message);
      
      // Refresh pending transfers list
      refreshPendingTransfers();
      
      // Refresh wallet balance
      refreshWalletBalance();
      
      // Show success message
      showNotification('Gift transfer cancelled. Points returned to your account.');
    }
  } catch (error) {
    console.error('Failed to cancel transfer:', error);
  }
};
```

### Bulk Cancellation [#bulk-cancellation]
```typescript
// Cancel multiple pending transfers
const cancelMultipleTransfers = async (transferIds: string[]) => {
  const results = [];
  
  for (const transferId of transferIds) {
    try {
      const response = await fetch(`/api/members/me/transfers/${transferId}/cancel`, {
        method: 'POST'
      });
      
      const result = await response.json();
      results.push({
        transferId,
        success: response.ok,
        message: result.message
      });
    } catch (error) {
      results.push({
        transferId,
        success: false,
        error: error.message
      });
    }
  }
  
  const successful = results.filter(r => r.success).length;
  console.log(`Cancelled ${successful}/${transferIds.length} transfers`);
  
  return results;
};
```

### Expiry Management [#expiry-management]
```typescript
// Cancel transfers that are about to expire
const cancelExpiringTransfers = async () => {
  const pendingTransfers = await getPendingTransfers('sent');
  const expiringThreshold = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
  
  const expiringTransfers = pendingTransfers.items.filter(transfer => {
    const expiryDate = new Date(transfer.expiresAt);
    return expiryDate <= expiringThreshold && transfer.status === 'PENDING';
  });
  
  if (expiringTransfers.length > 0) {
    const shouldCancel = confirm(
      `You have ${expiringTransfers.length} transfers expiring soon. Cancel them to recover points?`
    );
    
    if (shouldCancel) {
      const results = await cancelMultipleTransfers(
        expiringTransfers.map(t => t.transferId)
      );
      
      const totalRecovered = expiringTransfers
        .filter((_, index) => results[index].success)
        .reduce((sum, transfer) => sum + transfer.amount, 0);
      
      showNotification(`Recovered ${totalRecovered} points from ${results.filter(r => r.success).length} cancelled transfers`);
    }
  }
};
```

## Error Handling [#error-handling]

### Common Cancellation Scenarios [#common-cancellation-scenarios]
```typescript
const handleCancellationError = (error: any, transferId: string) => {
  switch (error.error) {
    case 'TRANSFER_NOT_CANCELLABLE':
      if (error.message.includes('claimed')) {
        showMessage('This transfer has already been claimed by the recipient.');
      } else if (error.message.includes('expired')) {
        showMessage('This transfer has expired and cannot be cancelled.');
      } else {
        showMessage('This transfer cannot be cancelled.');
      }
      break;
      
    case 'UNAUTHORIZED':
      showMessage('You can only cancel transfers that you sent.');
      break;
      
    case 'NOT_FOUND':
      showMessage('Transfer not found. It may have already been processed.');
      break;
      
    default:
      showMessage('Unable to cancel transfer. Please try again later.');
  }
};
```

### Retry Logic [#retry-logic]
```typescript
const cancelWithRetry = async (transferId: string, maxRetries = 2) => {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(`/api/members/me/transfers/${transferId}/cancel`, {
        method: 'POST'
      });
      
      if (response.ok) {
        return await response.json();
      }
      
      if (response.status === 403 && attempt < maxRetries) {
        // Might be reverification required - wait and retry
        await new Promise(resolve => setTimeout(resolve, 1000));
        continue;
      }
      
      throw new Error(`HTTP ${response.status}: ${await response.text()}`);
    } catch (error) {
      if (attempt === maxRetries) throw error;
      
      // Wait before retry
      await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
    }
  }
};
```

## Security Considerations [#security-considerations]

### Reverification [#reverification]
- **Requirement**: Must reverify within 10 minutes
- **Methods**: SMS OTP or email verification
- **Purpose**: Prevents unauthorized cancellations
- **Timeout**: Strict timeframe enforcement

### Authorization Checks [#authorization-checks]
- **Sender Verification**: Only transfer creator can cancel
- **Member ID Match**: Server validates ownership
- **No Delegation**: Cannot cancel on behalf of others
- **Audit Logging**: All attempts logged for security

### Rate Limiting [#rate-limiting]
- **No Specific Limits**: Actions are naturally limited by transfer count
- **Reverification**: Acts as natural rate limiting
- **Monitoring**: Unusual patterns flagged for review

## Impact on Other Systems [#impact-on-other-systems]

### Wallet Balance [#wallet-balance]
- **Immediate Update**: Balance increased by cancelled amount
- **Transaction Record**: Cancellation recorded as credit transaction
- **Available Points**: Immediately available for new transfers

### Notifications [#notifications-1]
- **Real-time**: Cancellation notifications sent immediately
- **Multiple Channels**: In-app, email, SMS (if configured)
- **Recipient Notice**: Recipient informed gift was cancelled

## Related Endpoints [#related-endpoints]

- [List Pending Transfers](/api-reference/members/me/transfers/list-pending) - View transfers available for cancellation
- [Create Transfer](/api-reference/members/me/transfers/create-transfer) - Create new transfers
- [Get Transfer Limits](/api-reference/members/me/transfers/get-limits) - Check available transfer capacity after cancellation
- [Get Wallet Balance](/api-reference/members/wallet/get-balance) - View updated balance after cancellation
