# Troubleshooting (/docs/payment-orchestration/troubleshooting)

## Precium API Troubleshooting Guide [#precium-api-troubleshooting-guide]

Solutions to common integration issues and debugging techniques.

## Quick Diagnostics [#quick-diagnostics]

### API Health Check [#api-health-check]

```bash
curl -X GET "https://gate.reviopay.com/api/v1/clients/" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

| Response | Meaning | Action |
|---|---|---|
| `200` with results | API working | Integration issue |
| `401 Unauthorized` | Invalid API key | Check credentials |
| `500 Server Error` | Platform issue | Contact support |
| Connection refused | Network issue | Check connectivity |

## Authentication Issues [#authentication-issues]

### Problem: 401 Unauthorized [#problem-401-unauthorized]

**Symptoms:**

```json
{
  "__all__": {
    "message": "Invalid or expired authentication token",
    "code": "invalid_token"
  }
}
```

**Common Causes:**

1. **Incorrect API Key**

```bash
    # Wrong
    -H "Authorization: your_api_key"

    # Correct
    -H "Authorization: Bearer your_api_key"
```

1. **Environment Mismatch**

- Using the sandbox key in production
- Using the production key in the sandbox

1. **Revoked Key**

- The key was regenerated in the dashboard
- The key was disabled

**Resolution:**

1. Verify key format includes "Bearer " prefix
2. Check that the key in the dashboard matches the configuration
3. Generate a new key if necessary
4. Ensure the environment matches

### Problem: Missing Authorization Header [#problem-missing-authorization-header]

**Symptoms:**

```json
{
  "__all__": {
    "message": "Authorization header is required",
    "code": "missing_authorization"
  }
}
```

**Resolution:**

Ensure every request includes the header:

```python
headers = {
    "Authorization": f"Bearer {api_key}",
    "Content-Type": "application/json"
}
```

## Purchase Creation Issues [#purchase-creation-issues]

### Problem: Invalid brand_id [#problem-invalid-brand_id]

**Symptoms:**

```json
{
  "brand_id": {
    "message": "Invalid pk \"xxx\" - object does not exist",
    "code": "does_not_exist"
  }
}
```

**Causes:**

- Incorrect brand ID
- Brand ID from a different environment
- Typo in UUID

**Resolution:**

1. Copy brand_id directly from the dashboard
2. Verify format: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
3. Ensure the use of the correct environment's brand

### Problem: checkout_url Not Generated [#problem-checkout_url-not-generated]

**Symptoms:**

- `checkout_url` is `null` in response
- `direct_post_url` is `null`

**Causes:**

- Missing `success_redirect` or `failure_redirect`
- `purchase.request_client_details` is not empty
- Purchase is not in a payable state

**Resolution:**

Include redirect URLs:

```json
{
  "success_redirect": "https://www.example.com/success",
  "failure_redirect": "https://www.example.com/failure",
  ...
}
```

### Problem: Total Amount Mismatch [#problem-total-amount-mismatch]

**Symptoms:**

- Calculated total doesn't match expected
- Checkout shows the wrong amount

**Diagnosis:**

```python
# Check how total is calculated
total = sum(
    product['price'] * int(product.get('quantity', '1'))
    for product in products
)
```

**Common Issues:**

- Quantity is a string, not applied correctly
- Discount/tax calculations
- Override fields not set properly

**Resolution:**

```json
{
  "products": [
    {"name": "Item", "price": 5000, "quantity": "2"}  // quantity is STRING
  ],
  "total_override": null  // Don't set unless intentional
}
```

## Payment Processing Issues [#payment-processing-issues]

### Problem: Card Declined [#problem-card-declined]

**Symptoms:**

- Payment fails at checkout
- Error message: "Card declined"

**Possible Causes:**

| Decline Reason | Meaning | Solution |
|---|---|---|
| `do_not_honour` | Generic decline | Try different card |
| `insufficient_funds` | Not enough balance | Use different card |
| `expired_card` | Card expired | Use valid card |
| `invalid_card` | Card number invalid | Check digits |
| `suspected_fraud` | Fraud detection | Contact bank |

**Debugging:**

```bash
# Check purchase for detailed error
GET https://gate.reviopay.com/api/v1/purchases/{id}/

# Response includes:
{
  "status": "failed",
  "payment_failure_reason": "do_not_honour"
}
```

### Problem: 3DS Authentication Failed [#problem-3ds-authentication-failed]

**Symptoms:**

- Customer fails 3D Secure challenge
- Redirect to failure URL
- Error: `3ds_authentication_failed`

**Causes:**

- The customer entered the wrong OTP/password
- 3DS timeout
- Customer cancelled
- Browser pop-up blocked

**Resolution:**

1. The customer should retry with the correct credentials
2. Ensure pop-ups are not blocked
3. Verify redirect URLs load quickly
4. Check browser compatibility

### Problem: Payment Stuck in Pending [#problem-payment-stuck-in-pending]

**Symptoms:**

- Status remains `pending_charge` indefinitely
- No success/failure redirect

**Diagnosis:**

```bash
# Check current status
GET https://gate.reviopay.com/api/v1/purchases/{id}/

# Response
{
  "status": "pending",
  "events": [...],  // Check for processing events
  "payment_method_details": {...}
}
```

**Causes:**

- 3DS flow not completed
- Customer abandoned payment
- Processing delay

**Resolution:**

1. Wait for processing (usually \< 60 seconds)
2. Check if the customer completed 3DS
3. Set up webhooks for async notification
4. For extended pending, contact support

## Webhook Issues [#webhook-issues]

### Problem: Webhooks Not Received [#problem-webhooks-not-received]

**Checklist:**

- Webhook URL is HTTPS
- URL is publicly accessible
- Firewall allows Precium IPs
- Server responds within 5 seconds
- Events are subscribed to

**Debug Steps:**

1. **Verify webhook configuration:**

```bash
GET https://gate.reviopay.com/api/v1/webhooks/

# Check your endpoints are listed
```

1. **Test URL accessibility:**

```bash
curl -X POST "https://www.example.com/webhooks/precium" \
  -H "Content-Type: application/json" \
  -d '{"test": true}'
```

1. **Check server logs for incoming requests**
2. **Verify the SSL certificate is valid**

### Problem: Webhook Signature Invalid [#problem-webhook-signature-invalid]

**Symptoms:**

- Signature verification fails
- Events appear legitimate, but don't validate

**Debug:**

```python
import base64
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding

# Signing is ASYMMETRIC RSA — there is no shared secret to compare against.
# The two most common causes of a failure here are verifying a re-serialised
# body instead of the raw bytes, and using the wrong public key.

def debug_signature(raw_body: bytes, received_sig: str, public_key_pem: str) -> bool:
    print(f"X-Signature: {received_sig}")
    print(f"Raw body length: {len(raw_body)} bytes")
    key = serialization.load_pem_public_key(public_key_pem.encode())
    try:
        key.verify(
            base64.b64decode(received_sig),
            raw_body,
            padding.PKCS1v15(),
            hashes.SHA256(),
        )
        print("Valid")
        return True
    except Exception as exc:
        print(f"Invalid: {exc}")
        print("Check: raw bytes (not json.dumps output)? correct key for this webhook?")
        return False
```

**Common Issues:**

1. **Payload modification:** Use raw request body

```python
    # Wrong - parsed JSON
    payload = json.dumps(request.json)

    # Correct - raw body
    payload = request.get_data(as_text=True)
```

1. **Encoding issues:** Ensure UTF-8
2. **Wrong secret:** Verify the secret from the dashboard

### Problem: Duplicate Webhook Events [#problem-duplicate-webhook-events]

**Cause:** Webhook retry on non-200 response

**Resolution:**

```python
# Implement idempotency
processed_events = set()

def handle_webhook(event):
    event_id = event.get('id')

    if event_id in processed_events:
        return  # Already processed

    processed_events.add(event_id)
    # Process event...
```

**Best Practice:** Store event IDs in the database:

```sql
CREATE TABLE webhook_events (
    event_id VARCHAR(50) PRIMARY KEY,
    event_type VARCHAR(50),
    processed_at TIMESTAMP DEFAULT NOW()
);
```

## Recurring Payment Issues [#recurring-payment-issues]

### Problem: Token Not Created [#problem-token-not-created]

**Symptoms:**

- `force_recurring: true` set but no token
- Empty token list for client

**Causes:**

- Payment not completed
- The payment amount was 0 without `skip_capture`
- Token creation is disabled for the payment method

**Debug:**

```bash
# Check if payment completed
GET https://gate.reviopay.com/api/v1/purchases/{id}/

# Verify status is "paid" or "hold"

# List client tokens
GET https://gate.reviopay.com/api/v1/clients/{client_id}/recurring_tokens/
```

**Resolution:**

For zero-amount tokenization:

```json
{
  "purchase": {
    "products": [{"name": "Auth", "price": 0}]
  },
  "skip_capture": true,
  "force_recurring": true
}
```

### Problem: Token Charge Failed [#problem-token-charge-failed]

**Symptoms:**

- `/charge/` endpoint returns error
- Token exists, but won't charge

**Error Types:**

| Error | Cause | Solution |
|---|---|---|
| `recurring_token_not_found` | Invalid token ID | Verify token exists |
| `recurring_token_expired` | Card expired | Re-tokenize |
| `card_declined` | Insufficient funds | Contact customer |

**Debug:**

```bash
# Verify token exists
GET https://gate.reviopay.com/api/v1/clients/{client_id}/recurring_tokens/{token_id}/

# Check original purchase
GET https://gate.reviopay.com/api/v1/purchases/{token_id}/
```

## Direct Post Issues [#direct-post-issues]

### Problem: direct_post_url is null [#problem-direct_post_url-is-null]

**Causes:**

- Missing redirect URLs
- `purchase.request_client_details` not empty
- Purchase not in created state

**Resolution:**

```json
{
  "success_redirect": "https://...",  // Required
  "failure_redirect": "https://...",  // Required
  "purchase": {
    "request_client_details": []  // Must be empty
  }
}
```

### Problem: Direct Post Returns to Failure [#problem-direct-post-returns-to-failure]

**Symptoms:**

- Valid card details
- Redirect to failure URL

**Debug Checklist:**

- Form fields match expected names exactly
- Card number has no spaces
- Expiry format is `MM/YY`
- CVC is 3-4 digits
- Cardholder name uses Latin characters only

**Field Requirements:**

```html
<!-- Exact field names required -->
<input name='cardholder_name'>  <!-- Latin, max 30 chars -->
<input name='card_number'>       <!-- Digits only, max 19 -->
<input name='expires'>           <!-- MM/YY format -->
<input name='cvc'>               <!-- 3-4 digits -->
<input name='pm' value='visa'>   <!-- Payment method -->
```

## Performance Issues [#performance-issues]

### Problem: Slow API Responses [#problem-slow-api-responses]

**Diagnosis:**

```python
import time

start = time.time()
response = requests.get(url, headers=headers)
elapsed = time.time() - start
print(f"Response time: {elapsed:.2f}s")
```

**Causes:**

- Network latency
- Large response payloads
- Server load

**Resolution:**

1. Implement connection pooling
2. Use pagination for list endpoints
3. Cache responses where appropriate
4. Implement timeout handling

### Problem: Rate Limit Exceeded [#problem-rate-limit-exceeded]

**Symptoms:**

```json
HTTP 429 Too Many Requests

Headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1619740860
```

**Resolution:**

```python
import time

def api_request_with_backoff(func, max_retries=3):
    for attempt in range(max_retries):
        response = func()

        if response.status_code == 429:
            reset_time = int(response.headers.get('X-RateLimit-Reset', 0))
            wait = max(reset_time - time.time(), 2 ** attempt)
            print(f"Rate limited. Waiting {wait:.0f}s...")
            time.sleep(wait)
            continue

        return response

    raise Exception("Rate limit exceeded after retries")
```

## Debugging Tools [#debugging-tools]

### Request Logging [#request-logging]

```python
import logging

logging.basicConfig(level=logging.DEBUG)

# Log all requests
import http.client
http.client.HTTPConnection.debuglevel = 1
```

### Response Inspection [#response-inspection]

```python
def debug_response(response):
    print(f"Status: {response.status_code}")
    print(f"Headers: {dict(response.headers)}")
    print(f"Body: {response.text}")
```

### cURL Verbose Mode [#curl-verbose-mode]

```bash
curl -v -X POST "https://gate.reviopay.com/api/v1/purchases/" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"..."}'
```

## Error Message Reference [#error-message-reference]

| Error | HTTP | Quick Fix |
|---|---|---|
| `invalid_token` | 401 | Check API key format |
| `required` | 400 | Add missing field |
| `does_not_exist` | 404 | Verify UUID |
| `duplicate_client` | 409 | Search existing client |
| `purchase_already_paid` | 409 | Check status first |
| `recurring_token_expired` | 409 | Re-tokenize card |
| `rate_limit_exceeded` | 429 | Implement backoff |

## Getting Support [#getting-support]

### Before Contacting Support [#before-contacting-support]

1. **Collect information:**

- Request ID (if available)
- Full error response
- Request payload (sanitized)
- Timestamp of issue

1. **Check dashboard:**

- Transaction history
- Webhook logs
- Event logs

1. **Review documentation:**

- API reference
- Error codes guide

### Contact Support [#contact-support]

**Email:** support@precium.com

**Include:**

- Merchant ID / Brand ID
- Environment (Sandbox/Production)
- Purchase ID(s) affected
- Steps to reproduce
- Error messages
- Timestamps
