# Captures

Capture previously authorized transactions.

---

## Overview

Authorization (`auth`) holds funds on a customer's card without actually charging them. Capturing processes the charge and transfers the funds.

**Use cases:**
- 📦 **E-commerce** - Authorize at checkout, capture at shipping
- 🏨 **Hotels** - Authorize at booking, capture at checkout
- 🚗 **Rentals** - Authorize for a deposit, capture when the rental ends

**Key Points:**
- Authorizations typically expire in 7-30 days (set by the card brand / issuer)
- A capture always takes the **full authorized amount** — partial capture is not supported
- One-time operation (cannot capture twice)
- To charge a different amount, void the authorization and run a new sale

---

## API Route Options

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

> The Public API (`/api/v1/public/*`) does not have a capture endpoint. Captures are performed via the Admin API or the AuxCORE SDK route.

---

## Capture an Authorization (Admin API)

### Endpoint

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

`:transactionId` is the transaction UUID or its `TXN-…` ID. The endpoint takes no request body.

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

**Response (200):** the updated transaction record.
```json
{
  "success": true,
  "data": {
    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "transactionId": "TXN-1769608800000-A1B2C3D4",
    "type": "capture",
    "status": "settled",
    "amount": "150.00",
    "settledDate": "2026-01-28T14:00:00.000Z",
    "gatewayName": "NMI"
  }
}
```

### How the capture is applied

| Gateway | Result |
|---------|--------|
| TSYS Sierra | `type` becomes `capture`, `status` stays `approved`; the capture is sent in the next settlement batch and the status becomes `settled` when TSYS confirms it |
| iPOS / NearPay | Captured at the provider; `type: capture`, `status: settled` |
| Other gateways | Captured through the gateway adapter; `type: capture`, `status: settled` |

---

## Common Scenarios

### E-Commerce: Authorize then Capture

```bash
# Step 1: Authorize at checkout (Public API)
curl -X POST https://uat.auxcore.net/api/v1/public/transaction \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "Amount": 150.00,
    "transactionType": "auth",
    "SuggestedMode": "Card",
    "pciToken": "pci_tok_8f2c1a9b7e4d"
  }'
# Response: data.TransactionId = 7c9e6679-7425-40de-944b-e07fc1f90ae7

# Step 2: Capture when item ships (Admin API or AuxCORE SDK)
curl -X POST https://uat.auxcore.net/api/v1/transactions/7c9e6679-7425-40de-944b-e07fc1f90ae7/capture \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "X-Tenant-ID: your-tenant-id"
```

### Hotel: Final Bill Differs From the Hold

Capture cannot change the amount. If the final bill differs from the authorized hold, void the authorization and process a new sale for the final amount:

```bash
# Release the $500 hold
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"}'

# Charge the actual $250
curl -X POST https://uat.auxcore.net/api/v1/public/transaction \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"Amount": 250.00, "SuggestedMode": "Card", "pciToken": "pci_tok_8f2c1a9b7e4d"}'
```

---

## Validation Rules

Every capture error is 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` |
|-----------|-----------------|
| Not an authorization (a sale, or already captured) | `Only auth transactions can be captured` |
| Authorization not approved (declined, voided, …) | `Only approved auth transactions can be captured` |
| Gateway rejected the capture | The gateway's reason, e.g. `iPOS capture was unsuccessful (code: …)` |
| No gateway credentials for the merchant | `No gateway credentials available to perform capture` |

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

---

## Authorization Expiration

| Card Brand | Expiration Period |
|------------|-------------------|
| Visa | 7 days |
| Mastercard | 7 days (30 days for some MCC codes) |
| American Express | 7 days |
| Discover | 10 days |

⚠️ **Best Practice:** Capture within 3-5 days to avoid expiration.

---

## Webhooks

No webhook event is sent when an authorization is captured. The authorization itself triggers `transaction.approved`; to confirm a capture, read the response or fetch the transaction (`GET /api/v1/transactions/:id`) and check `type: "capture"`.

---

## Best Practices

### ✅ DO:
- **Capture promptly** - Within 3-5 days
- **Notify customers** - When capture occurs
- **Handle expiration** - Check auth age before capture
- **Test thoroughly** - Verify capture logic

### ❌ DON'T:
- **Don't delay captures** - Risk expiration
- **Don't capture twice** - Will fail
- **Don't expect partial capture** - The full authorized amount is captured
- **Don't skip error handling** - Always check response

---

## Testing

### Test Captures in Sandbox

```bash
# 1. Create authorization (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,
    "transactionType": "auth",
    "SuggestedMode": "Card",
    "pciToken": "pci_tok_8f2c1a9b7e4d"
  }' | jq -r '.data.TransactionId')

# 2. Capture (Admin API)
curl -X POST https://uat.auxcore.net/api/v1/transactions/$TXNID/capture \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "X-Tenant-ID: your-tenant-id"

# 3. Capture again — should fail with VALIDATION_ERROR
curl -X POST https://uat.auxcore.net/api/v1/transactions/$TXNID/capture \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "X-Tenant-ID: your-tenant-id"
```

---

## Capture via AuxCORE SDK

```http
POST /api/v1/auxvault/capture
```

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

This adapter captures the full authorization; `Amount` is ignored. Tenant-scoped / ERP keys also need `X-Merchant-ID`. Create the authorization through `/api/v1/public/transaction` using `TransactionType: "auth"` (or `"2"`).

Use a server-side key in `x-api-key` and omit `Authorization`. SDK public keys are rejected on this namespace. Response:

```json
{
  "success": true,
  "Status": 1,
  "TransactionId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "message": "Transaction captured",
  "data": { "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "type": "capture", "status": "settled" }
}
```

A failure returns the transaction service's HTTP status with `{ "success": false, "Status": 9, "message": "Capture failed" }`.

---

## Cancel Capture

Reverse a capture, returning the transaction to an **approved authorization** (`type: auth`, `status: approved`). Useful when a capture was made in error.

### Endpoint

```http
POST /api/v1/transactions/:transactionId/cancel-capture
```

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

**Response (200):** the updated transaction record.
```json
{
  "success": true,
  "data": {
    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "transactionId": "TXN-1769608800000-A1B2C3D4",
    "type": "auth",
    "status": "approved"
  }
}
```

### When to Use Cancel Capture

| Scenario | Action |
|----------|--------|
| Captured wrong transaction | Cancel capture → re-capture correct one |
| Item out of stock after capture | Cancel capture → void |
| Capture made too early | Cancel capture → capture later |
| TSYS capture already in a settlement batch | Cannot cancel — use refund instead |

### Validation

Cancel capture is allowed when:
- the transaction has `status: settled`, or
- it is a TSYS Sierra capture (`type: capture`, `status: approved`) that has not yet been assigned to a settlement batch.

Otherwise it returns `400 VALIDATION_ERROR` with `Only captured transactions can have capture canceled` or `Cannot cancel capture — transaction is already assigned to a batch`. Cancel capture only changes AuxCORE's record — it does not call the gateway. Requires the `transactions.capture` permission.

### Via AuxCORE SDK

```http
POST /api/v1/auxvault/cancel-capture
```

```bash
curl -X POST https://uat.auxcore.net/api/v1/auxvault/cancel-capture \
  -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": "Capture cancelled", "data": { … } }`.

## Next Steps

- [Authorize transactions →](./transactions.md)
- [Void transactions →](./voids.md)
- [Refund captured transactions →](./refunds.md)

---

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