# Voids

Cancel transactions before they settle.

---

## Overview

Voiding cancels a transaction before it's been settled (batched). Unlike refunds, voids prevent the transaction from ever appearing on the customer's statement.

**Key Differences: Void vs Refund**

| Feature | Void | Refund |
|---------|------|--------|
| **Timing** | Before settlement (`status: approved`) | After approval or settlement |
| **Statement** | Never appears | Appears as separate credit |
| **Fees** | None (transaction cancelled) | Processing fees may apply |
| **Speed** | Instant cancellation | 3-10 business days |
| **Amount** | Whole transaction only | Full or partial |
| **Record** | Original transaction's `status` becomes `voided` | A new `refund` transaction (`REF-…`) |

---

## API Route Options

| Auth Type | Endpoint | Use Case |
|-----------|----------|----------|
| **API Key** (`x-api-key`) | `POST /api/v1/public/void` | ERP / server integration |
| **JWT Bearer** | `POST /api/v1/transactions/:id/void` | Dashboard / admin (requires the `transactions.void` permission) |
| **SDK / API key** | `POST /api/v1/auxvault/void` | Official v2.0 SDK namespace; server-side `x-api-key`, SDK public keys get `403` |

For ERP and server-to-server integrations, see Void a Transaction — Public API in the [Transactions API](./transactions.md).

---

## Void Transaction (Admin API)

### Endpoint

```http
POST /api/v1/transactions/:transactionId/void
```

`:transactionId` is the transaction UUID or its `TXN-…` / `ACH-…` ID. No request body is needed.

```bash
curl -X POST https://uat.auxcore.net/api/v1/transactions/7c9e6679-7425-40de-944b-e07fc1f90ae7/void \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "X-Tenant-ID: your-tenant-id"
```

**Response (200):**
```json
{
  "success": true,
  "data": {
    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "transactionId": "TXN-1769605200000-A1B2C3D4",
    "type": "sale",
    "status": "voided",
    "amount": "150.00",
    "currency": "USD",
    "updatedAt": "2026-01-28T13:00:00.000Z"
  },
  "gatewayVoided": true
}
```

---

## Request Parameters

The void endpoints take no parameters besides the transaction ID. A `reason` sent in the body is accepted but not stored.

---

## Response Fields

| Field | Type | Description |
|-------|------|-------------|
| `data` | object | The voided transaction record (`status: "voided"`) |
| `gatewayVoided` | boolean | `true` when the gateway confirmed the reversal |
| `warning` | string | Present when `gatewayVoided` is `false`; explains that the card hold will expire on its own |

---

## What Can Be Voided

| Transaction | Voidable? |
|-------------|-----------|
| Approved sale or authorization (`status: approved`) | Yes — reversed at the gateway |
| Pending ACH debit | Yes — cancelled before it is sent to the bank (Payliance is asked first and must confirm) |
| TSYS capture waiting for its batch | Yes — removed from the batch locally |
| Settled transaction | No — use refund |
| Cash transaction | No — use refund |
| Declined, voided or refunded transaction | No |
| Transaction lost to a chargeback | No |

Uncaptured authorizations can be voided; voiding releases the hold rather than waiting for it to expire.

---

## When to Use Voids

### ✅ Use Void For:

1. **Customer cancellations (same day)**
   ```bash
   # Customer calls minutes after purchase to cancel
   curl -X POST https://uat.auxcore.net/api/v1/transactions/TXN-1769605200000-A1B2C3D4/void \
     -H "Authorization: Bearer YOUR_TOKEN" -H "X-Tenant-ID: your-tenant-id"
   ```

2. **Duplicate charges**
   ```bash
   # Accidentally charged customer twice
   curl -X POST https://uat.auxcore.net/api/v1/transactions/TXN-1769605260000-E5F6A7B8/void \
     -H "Authorization: Bearer YOUR_TOKEN" -H "X-Tenant-ID: your-tenant-id"
   ```

3. **Order errors** — wrong product or price charged
4. **Suspected fraud** — fraudulent transaction detected before settlement
5. **Releasing an authorization hold** — the order will not be fulfilled

### ❌ Don't Use Void For:

1. **Settled transactions** → Use refund instead
2. **Partial cancellations** → Void is all-or-nothing; use a partial refund
3. **Cash payments** → Use refund

---

## Settlement Windows

A transaction can be voided while its `status` is `approved`. Once settlement marks it `settled`, a void is refused and you must refund. Settlement timing depends on the merchant's gateway and batch schedule.

⚠️ **Important:** The gateway is the final word. If the processor has already settled the transaction, the void is rejected even if AuxCORE still shows `approved`; the error message then suggests issuing a refund.

---

## Validation Rules

Void errors are returned as HTTP `400` with `error.code: "VALIDATION_ERROR"` (or `404 NOT_FOUND` for an unknown transaction). The reason is in `error.message`:

| Situation | `error.message` |
|-----------|-----------------|
| Already settled | `This transaction has already been settled and cannot be voided. Please issue a refund instead.` |
| Not approved (declined, voided, refunded, …) | `Only approved transactions can be voided` |
| Cash transaction | `Void is not supported for Cash transactions. Please issue a refund instead.` |
| Lost chargeback | `This transaction has already been lost to a chargeback and cannot be voided. …` |
| Gateway rejected the reversal | `Gateway reversal was unsuccessful (code: …). …` — with `This transaction may have already been settled — please issue a refund instead.` when the gateway reports it settled |

```json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Only approved transactions can be voided"
  }
}
```

When the gateway rejects the reversal, the transaction is **not** marked voided — the charge is still active.

---

## Void vs Refund Decision Tree

```
Transaction needs to be cancelled
    ↓
Is its status "settled"?
    ↓                    ↓
   YES                  NO ("approved")
    ↓                    ↓
Use REFUND          Use VOID
(appears on         (never appears
statement)          on statement)
```

**How to check if settled:**

```bash
curl https://uat.auxcore.net/api/v1/transactions/7c9e6679-7425-40de-944b-e07fc1f90ae7 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "X-Tenant-ID: your-tenant-id"
```

**Response:**
```json
{
  "success": true,
  "data": {
    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "transactionId": "TXN-1769605200000-A1B2C3D4",
    "status": "approved",
    "settledDate": null
  }
}
```

- `status: "approved"` → Use **void**
- `status: "settled"` → Use **refund**

---

## Common Scenarios

### Scenario 1: Customer Changes Mind

**Timeline:** 30 minutes after purchase  
**Action:** Void

```bash
curl -X POST https://uat.auxcore.net/api/v1/public/void \
  -H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"transactionId": "7c9e6679-7425-40de-944b-e07fc1f90ae7"}'
```

**Result:** Transaction cancelled, never appears on statement

### Scenario 2: Next-Day Cancellation

**Timeline:** 24 hours after purchase  
**Action:** Check if settled, then decide

```bash
# Check settlement status
curl https://uat.auxcore.net/api/v1/public/transaction/7c9e6679-7425-40de-944b-e07fc1f90ae7 \
  -H "x-api-key: YOUR_API_KEY"

# If status is "approved":
curl -X POST https://uat.auxcore.net/api/v1/public/void \
  -H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"transactionId": "7c9e6679-7425-40de-944b-e07fc1f90ae7"}'

# If status is "settled":
curl -X POST https://uat.auxcore.net/api/v1/public/refund \
  -H "x-api-key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"transactionId": "7c9e6679-7425-40de-944b-e07fc1f90ae7"}'
```

---

## Void via AuxCORE SDK

```http
POST /api/v1/auxvault/void
```

```bash
curl -X POST https://uat.auxcore.net/api/v1/auxvault/void \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"TransactionId": "7c9e6679-7425-40de-944b-e07fc1f90ae7"}'
```

Returns `{ "success": true, "Status": 1, "TransactionId": "…", "message": "Transaction voided" }`, or the error status with `{ "success": false, "Status": 9, "message": "Void failed" }`.

---

## Webhooks

### transaction.voided
```json
{
  "id": "9e3c5a17-2b6d-4f80-b1a4-7c8d2e6f0a93",
  "type": "transaction.voided",
  "created": "2026-01-28T13:00:00.000Z",
  "data": {
    "transactionId": "TXN-1769605200000-A1B2C3D4",
    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "type": "sale",
    "status": "voided",
    "amount": "150.00",
    "currency": "USD",
    "orderId": "ORDER-2026-001",
    "merchantId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "timestamp": "2026-01-28T13:00:00.000Z"
  },
  "metadata": {
    "tenantId": "your-tenant-id",
    "merchantId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  }
}
```

---

## Best Practices

### ✅ DO:
- **Void quickly** - Same day before settlement
- **Check settlement status** - Before attempting void
- **Notify customer** - Confirm cancellation
- **Use webhooks** - Automate void notifications
- **Fall back to refund** - If void fails because the transaction settled, refund instead

### ❌ DON'T:
- **Don't void after settlement** - Use refund instead
- **Don't forget to notify** - Customer expects confirmation
- **Don't void partially** - It's all or nothing
- **Don't skip error handling** - Always check if void succeeded

---

## Testing

### Test Voids in Sandbox

```bash
# 1. Create test transaction (Public API)
TXNID=$(curl -s -X POST https://uat.auxcore.net/api/v1/public/transaction \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "Amount": 100.00,
    "SuggestedMode": "Card",
    "pciToken": "pci_tok_8f2c1a9b7e4d"
  }' | jq -r '.data.TransactionId')

# 2. Void the transaction (Public API)
curl -X POST https://uat.auxcore.net/api/v1/public/void \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"transactionId": "'"$TXNID"'"}'

# 3. Verify voided (Public API)
curl "https://uat.auxcore.net/api/v1/public/transaction/$TXNID" \
  -H "x-api-key: YOUR_API_KEY"
# Should show status: "voided"

# 4. Try to void again (should fail)
curl -X POST https://uat.auxcore.net/api/v1/public/void \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"transactionId": "'"$TXNID"'"}'
# Returns 400 VALIDATION_ERROR — "Only approved transactions can be voided"
```

---

## Error Handling

### Void Failed, Use Refund

```javascript
async function cancelTransaction(transactionId) {
  const result = await voidTransaction(transactionId);
  if (result.success) return result;

  // Void errors share the VALIDATION_ERROR code — the message says why.
  const message = result.error?.message || '';
  if (/settled/i.test(message)) {
    return refundTransaction(transactionId);
  }
  throw new Error(message);
}
```

---

## Impact on Merchant Fees

### Void = No Fees

```
Original transaction: $100.00
Processing fee: $3.00
  ↓
Transaction voided
  ↓
Customer charged: $0.00
Merchant pays: $0.00 (fee refunded)
```

### Refund = Fees Apply (usually)

```
Original transaction: $100.00
Processing fee: $3.00
  ↓
Transaction refunded next day
  ↓
Customer refunded: $100.00
Merchant still pays: $3.00 (fee not refunded)
```

💡 **Tip:** Void quickly to save on processing fees!

---

## Next Steps

- [Process refunds →](./refunds.md)
- [Capture authorizations →](./captures.md)
- [View transaction details →](./transactions.md)

---

**Need help?** Contact support@auxcore.net
