# 

## When to cancel vs. refund

The correct reversal method depends on whether the transaction has settled.

![image](https://files.modern-mermaid.live/images/1776277139735-mermaid-diagram-1776277139819.png)

| Action | When | API call | Funds returned |
|  --- | --- | --- | --- |
| **Cancel** (void) | Before settlement | `POST /v1/transactions/{id}/cancel` | Hold released immediately |
| **Refund** (full) | After settlement | `POST /v1/transactions/{id}/cancel` with `type: card_refund` | 3–5 business days |
| **Refund** (partial) | After settlement | Same, with `amount` field | 3–5 business days |


## Step 1: Check the transaction state

Before attempting a reversal, confirm the current state.

curl
```shell
curl -X GET \
  https://api.dev.paradisegateway.net/v1/transactions/TRANSACTION-01KEW32V6YNV11T33XGDR7TGWC \
  -H "APIKEY: YOUR-API-KEY"
```

Python
```python
import os, requests

API_KEY = os.environ["PARADISE_API_KEY"]
BASE = os.environ.get("PARADISE_BASE_URL", "https://api.dev.paradisegateway.net")
HEADERS = {"Content-Type": "application/json", "APIKEY": API_KEY}

txn_id = "TRANSACTION-01KEW32V6YNV11T33XGDR7TGWC"
resp = requests.get(f"{BASE}/v1/transactions/{txn_id}", headers=HEADERS)
txn = resp.json()
print(f"State: {txn['transaction_state']}")
```

JavaScript
```javascript
const API_KEY = process.env.PARADISE_API_KEY;
const BASE = process.env.PARADISE_BASE_URL || "https://api.dev.paradisegateway.net";
const headers = { "Content-Type": "application/json", "APIKEY": API_KEY };

const txnId = "TRANSACTION-01KEW32V6YNV11T33XGDR7TGWC";
const resp = await fetch(`${BASE}/v1/transactions/${txnId}`, { headers });
const txn = await resp.json();
console.log(`State: ${txn.transaction_state}`);
```

C#
```csharp
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("APIKEY", Environment.GetEnvironmentVariable("PARADISE_API_KEY"));
var baseUrl = Environment.GetEnvironmentVariable("PARADISE_BASE_URL") ?? "https://api.dev.paradisegateway.net";

var txnId = "TRANSACTION-01KEW32V6YNV11T33XGDR7TGWC";
var resp = await client.GetAsync($"{baseUrl}/v1/transactions/{txnId}");
var json = await resp.Content.ReadAsStringAsync();
Console.WriteLine(json);
```

Java
```java
HttpClient client = HttpClient.newHttpClient();
String baseUrl = System.getenv().getOrDefault("PARADISE_BASE_URL", "https://api.dev.paradisegateway.net");
String apiKey = System.getenv("PARADISE_API_KEY");

String txnId = "TRANSACTION-01KEW32V6YNV11T33XGDR7TGWC";
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create(baseUrl + "/v1/transactions/" + txnId))
    .header("APIKEY", apiKey)
    .GET()
    .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
```

Go
```go
req, _ := http.NewRequest("GET", base+"/v1/transactions/"+txnId, nil)
req.Header.Set("APIKEY", apiKey)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
```

PHP
```php
$txnId = "TRANSACTION-01KEW32V6YNV11T33XGDR7TGWC";
$ch = curl_init("{$base}/v1/transactions/{$txnId}");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["APIKEY: {$apiKey}"],
]);
$response = curl_exec($ch);
echo $response;
```

Ruby
```ruby
uri = URI("#{base}/v1/transactions/#{txn_id}")
req = Net::HTTP::Get.new(uri)
req["APIKEY"] = api_key
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.body
```

## Step 2a: Cancel (before settlement)

Cancel releases the hold immediately. Works for `authorized` or `captured` (pre-settlement) transactions.

curl
```shell
curl -X POST \
  https://api.dev.paradisegateway.net/v1/transactions/TRANSACTION-01KEW32V6YNV11T33XGDR7TGWC/cancel \
  -H "Content-Type: application/json" \
  -H "APIKEY: YOUR-API-KEY"
```

Python
```python
cancel_resp = requests.post(
    f"{BASE}/v1/transactions/{txn_id}/cancel",
    headers=HEADERS,
)
result = cancel_resp.json()
print(f"State: {result['transaction_state']}")  # → "canceled"
```

JavaScript
```javascript
const cancelResp = await fetch(`${BASE}/v1/transactions/${txnId}/cancel`, {
  method: "POST",
  headers,
});
const result = await cancelResp.json();
console.log(`State: ${result.transaction_state}`);  // → "canceled"
```

C#
```csharp
var cancelResp = await client.PostAsync(
    $"{baseUrl}/v1/transactions/{txnId}/cancel",
    new StringContent("{}", System.Text.Encoding.UTF8, "application/json"));
var cancelJson = await cancelResp.Content.ReadAsStringAsync();
Console.WriteLine(cancelJson);
```

Java
```java
HttpRequest cancelReq = HttpRequest.newBuilder()
    .uri(URI.create(baseUrl + "/v1/transactions/" + txnId + "/cancel"))
    .header("Content-Type", "application/json")
    .header("APIKEY", apiKey)
    .POST(HttpRequest.BodyPublishers.ofString("{}"))
    .build();
HttpResponse<String> cancelResp = client.send(cancelReq, HttpResponse.BodyHandlers.ofString());
System.out.println(cancelResp.body());
```

Go
```go
req, _ := http.NewRequest("POST", base+"/v1/transactions/"+txnId+"/cancel", strings.NewReader("{}"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("APIKEY", apiKey)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
```

PHP
```php
$ch = curl_init("{$base}/v1/transactions/{$txnId}/cancel");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => ["Content-Type: application/json", "APIKEY: {$apiKey}"],
    CURLOPT_POSTFIELDS => "{}",
]);
echo curl_exec($ch);
```

Ruby
```ruby
uri = URI("#{base}/v1/transactions/#{txn_id}/cancel")
req = Net::HTTP::Post.new(uri)
req["Content-Type"] = "application/json"
req["APIKEY"] = api_key
req.body = "{}"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
puts res.body
```

## Step 2b: Refund (after settlement)

For settled transactions, send a refund. Include `type: card_refund` and optionally an `amount` for partial refunds.

### Full refund

curl
```shell
curl -X POST \
  https://api.dev.paradisegateway.net/v1/transactions/TRANSACTION-01KEW32V6YNV11T33XGDR7TGWC/cancel \
  -H "Content-Type: application/json" \
  -H "APIKEY: YOUR-API-KEY" \
  -d '{ "type": "card_refund" }'
```

Python
```python
refund_resp = requests.post(
    f"{BASE}/v1/transactions/{txn_id}/cancel",
    headers=HEADERS,
    json={"type": "card_refund"},
)
result = refund_resp.json()
print(f"State: {result['transaction_state']}")  # → "refunded"
```

JavaScript
```javascript
const refundResp = await fetch(`${BASE}/v1/transactions/${txnId}/cancel`, {
  method: "POST",
  headers,
  body: JSON.stringify({ type: "card_refund" }),
});
const result = await refundResp.json();
console.log(`State: ${result.transaction_state}`);  // → "refunded"
```

### Partial refund

```shell
curl -X POST \
  https://api.dev.paradisegateway.net/v1/transactions/TRANSACTION-01KEW32V6YNV11T33XGDR7TGWC/cancel \
  -H "Content-Type: application/json" \
  -H "APIKEY: YOUR-API-KEY" \
  -d '{ "type": "card_refund", "amount": 1500 }'
```

## Decision logic implementation

```python
def reverse_transaction(txn_id: str, partial_amount: int | None = None) -> dict:
    txn = requests.get(f"{BASE}/v1/transactions/{txn_id}", headers=HEADERS).json()
    state = txn["transaction_state"]

    if state in ("authorized", "captured"):
        resp = requests.post(f"{BASE}/v1/transactions/{txn_id}/cancel", headers=HEADERS)
    elif state == "settled":
        payload = {"type": "card_refund"}
        if partial_amount:
            payload["amount"] = partial_amount
        resp = requests.post(
            f"{BASE}/v1/transactions/{txn_id}/cancel",
            headers=HEADERS,
            json=payload,
        )
    else:
        raise ValueError(f"Cannot reverse transaction in state: {state}")

    return resp.json()
```

## Common mistakes

| Mistake | Result | Fix |
|  --- | --- | --- |
| Refunding a pre-settlement transaction | Error — not settled yet | Cancel instead |
| Canceling a settled transaction | Error — already settled | Refund instead |
| Refunding more than the original amount | Error — amount exceeds original | Check original `amount` first |
| Not checking state first | Unpredictable errors | Always `GET` the transaction before reversing |


## Further reading

* [Cancel an authorized transaction](/docs/how-tos/manage-transactions/cancel-auth)
* [Refund a settled transaction](/docs/how-tos/manage-transactions/refund-settled-auth)
* [Cancels and refunds concepts](/docs/concepts/transactions/cancels-and-refunds)
* [Transaction lifecycle](/docs/concepts/transactions/transaction-lifecycle)