# Best Practices

Production-ready best practices for AuxCORE integration. Examples use the `auxcore(method, path, body)` fetch helper from the [Node.js examples](../examples/nodejs.md).

---

## 🔐 Security

### Never Hardcode Credentials
```javascript
// ❌ BAD
const apiKey = 'api-3f9a...';

// ✅ GOOD
const apiKey = process.env.AUXCORE_API_KEY;
```

### Always Use HTTPS
```javascript
// ❌ BAD
const baseUrl = 'http://app.auxcore.net';

// ✅ GOOD
const baseUrl = 'https://app.auxcore.net';
```

### Verify Webhook Signatures

Copy `verifySignature()` from the [Node.js webhook example](../examples/nodejs.md#webhook-handler-express) into your server helper and use it for every delivery. It rejects missing or malformed headers, invalid timestamps and non-hex signatures without throwing.

```javascript
// ✅ ALWAYS verify signatures — against the RAW request body.
// X-Webhook-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, `${t}.${rawBody}`)>
if (!verifySignature(rawBody, req.headers['x-webhook-signature'], secret)) {
  return res.status(401).send('Invalid signature');
}
```

Preserve the raw body before JSON parsing. A valid signature does not prevent duplicate deliveries: atomically store each `event.id` in a durable inbox and enqueue work once. See the [integration guide handler](./integration-guide.md#phase-4-webhooks).

### Never Send Raw Card Data
Collect cards with the PCI SDK (`https://vault.auxcore.net/sdk/auxVault.js`) and send only the `pci_tok_…` token. Raw `CardNumber` is rejected on Sandbox and Production with `400 PCI_SCOPE_VIOLATION`.

---

## 💻 Code Quality

### Use Idempotency Keys

Create and persist a payment-attempt UUID before sending the first request. Reuse its key and the exact payload for every retry of that attempt, including recovery after a timeout or process restart. Create a new attempt only after the earlier outcome is known and a new payment is intentionally requested.

```javascript
// ✅ Prevent duplicate charges on the admin sale endpoint (key: 16–255 chars)
// paymentAttemptId is a UUID loaded from your database, not regenerated for a retry.
const idempotencyKey = `payment-${paymentAttemptId}`;
const requestBody = JSON.stringify({ merchantId, amount: 100.00, pciToken });

async function submitPaymentAttempt() {
  return fetch('https://uat.auxcore.net/api/v1/transactions/sale', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${jwt}`,
      'X-Tenant-ID': tenantId,
      'Idempotency-Key': idempotencyKey,
      'Content-Type': 'application/json',
    },
    body: requestBody,
  });
}

const response = await submitPaymentAttempt();
```

The middleware caches responses for 24 hours. `409 IDEMPOTENCY_REQUEST_IN_PROGRESS` means the attempt is still running; wait and retry with the same key and payload. `422 IDEMPOTENCY_KEY_REUSED` means the payload differs: reconcile the existing attempt instead of silently generating a new key. Cached responses carry `Idempotent-Replayed: true`.

> **Public API:** `POST /api/v1/public/transaction` does not pass `Idempotency-Key` through. Supply an `OrderId` for reconciliation, but it is not unique in the database and does not deduplicate charges. After a timeout or `5xx`, look up `GET /api/v1/public/transaction-list?orderId=…` and match the results to your attempt. An empty immediate lookup is not proof of failure; wait for the webhook or confirm with the processor before authorizing another charge.

### Handle All Errors
```javascript
// ✅ Comprehensive error handling
const { status, body } = await processPayment();
if (status === 402) {
  // Declined — show body.message, inspect body.data.ResponseCode
} else if (status === 429) {
  // Rate limited — retry with backoff (see RateLimit-Reset header)
} else if (status >= 400) {
  // Log body.error and alert
}
```

### Use Exponential Backoff

Use automatic backoff for read requests or writes protected by idempotency. For protected writes, preserve the key and payload. Do not wrap public payment, void or refund submissions in a generic retry loop when their outcome is unknown; follow the reconciliation steps above.

```javascript
// ✅ Smart retry logic
async function retryWithBackoff(fn, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (!isRetryable(error) || i === maxRetries - 1) {
        throw error;
      }
      await sleep(Math.min(1000 * Math.pow(2, i), 10000));
    }
  }
}
```

---

## 📊 Performance

### Cache Static Data
```javascript
// ✅ Cache payment methods, merchants, etc.
const cache = new Map();

async function getCustomer(id) {
  if (cache.has(id)) return cache.get(id);

  const { body } = await auxcore('GET', `/customer/${id}`);
  const customer = body.data;
  cache.set(id, customer);
  return customer;
}
```

### Use Webhooks Over Polling
```javascript
// ❌ BAD: Poll every second
setInterval(() => checkStatus(), 1000);

// ✅ GOOD: Process verified events from the durable webhook inbox.
// The receiver verifies signatures and deduplicates event.id before enqueueing.
async function handleQueuedEvent(event) {
  if (event.type === 'transaction.approved') {
    await handleApproval(event.data); // Make order updates and receipts idempotent too.
  }
}
```

### Page Through Lists Instead of Fetching One by One
```javascript
// ❌ BAD: 100 individual requests
for (const txn of transactions) {
  await auxcore('GET', `/transaction/${txn.id}`);
}

// ✅ GOOD: one filtered, paged list (up to `limit` rows per page)
const { body } = await auxcore('GET', '/transaction-list?dateFrom=2026-10-01&dateTo=2026-10-01&page=1&limit=100');
// body.data = rows, body.meta = { page, limit, total, totalPages, summary, statusCounts }
```

---

## 🎯 User Experience

### Show Clear Error Messages
```javascript
// ❌ BAD
alert('Error');

// ✅ GOOD
alert('Payment declined. Please try a different card or contact your bank.');
```

### Validate Before Submitting
```javascript
// ✅ Validate your own fields (amount, email, order id) before calling the API.
// Card number/expiry/CVV are validated inside the PCI SDK's hosted fields.
function validateOrder(order) {
  if (!(order.amount > 0)) return 'Amount must be greater than 0';
  if (order.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(order.email)) return 'Invalid email';
  return null;
}
```

### Provide Payment Confirmation
```javascript
// ✅ Always send receipt
if (status === 200 && body.status === 'success') {
  await sendReceipt(customer.email, transaction);
  showSuccessMessage('Payment successful!');
}
```

---

## 📈 Monitoring

### Log Important Events
```javascript
// ✅ Log all transactions
logger.info('Transaction created', {
  transactionId: body.data.TransactionId,
  amount: body.data.Amount,
  status: body.status,
  responseCode: body.data.ResponseCode
});
```

### Set Up Alerts
```javascript
// ✅ Alert on high error rates
if (errorRate > 0.05) {
  alertOps('High error rate detected');
}
```

### Track Key Metrics
- Success rate
- Average response time
- Decline rate
- Webhook delivery rate

---

## 💰 Financial

### Reconcile Daily
```javascript
// ✅ Daily reconciliation — meta.summary aggregates the whole filtered set, not just the page
async function reconcileDaily(day) {
  const { body } = await auxcore('GET', `/transaction-list?dateFrom=${day}&dateTo=${day}&limit=1`);
  const ourTotal = await database.getApprovedTotalForDay(day);

  if (Number(body.meta.summary.approvedVolume) !== ourTotal) {
    alertFinance('Reconciliation mismatch');
  }
}
```

### Handle Settlements Properly
- Understand settlement schedule
- Account for holds and reserves
- Track refunds separately

---

## 🧪 Testing

### Test in Sandbox First
```javascript
// ✅ Always test in sandbox
if (process.env.NODE_ENV === 'production') {
  // Production settings
} else {
  // Sandbox settings
}
```

### Test All Scenarios
- Approved transactions
- Declined transactions
- Network failures
- Timeout scenarios
- Webhook failures

---

## 📞 Support

### Keep Support Contacts Ready
- Technical support email
- Account manager contact
- Emergency hotline

### Document Your Integration
- API endpoints used
- Webhook configuration
- Error handling approach
- Deployment process

---

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