# Utilities

Helper functions for common operations like formatting and webhook verification.

---

## Formatting

### formatSatoshis

Convert satoshis to readable decimal string:

+++ TypeScript
```typescript
import { formatSatoshis } from '@hivepay/client';

console.log(formatSatoshis(10500n));     // "10.500"
console.log(formatSatoshis(10500n, 2));  // "10.50"
console.log(formatSatoshis(158n));       // "0.158"
console.log(formatSatoshis(1n));         // "0.001"
```
+++

### Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `satoshis` | `bigint` | - | Amount in satoshis |
| `precision` | `number` | `3` | Decimal places |

---

## Webhook Verification

### verifyWebhook

Verify webhook signature and parse payload:

+++ TypeScript
```typescript
import { HivePay } from '@hivepay/client';

const hivepay = new HivePay({
  apiKey: 'sk_live_xxx',
  webhookSecret: 'whsec_xxx'
});

async function handleWebhook(req: Request) {
  const result = await hivepay.verifyWebhook({
    payload: await req.text(),
    signature: req.headers.get('X-HivePay-Signature')!,
    timestamp: req.headers.get('X-HivePay-Timestamp')!,
    maxAge: 300000  // 5 minutes (optional)
  });

  if (!result.valid) {
    console.error('Invalid webhook:', result.error);
    return new Response('Unauthorized', { status: 401 });
  }

  // Access verified event
  const { event } = result;
  console.log('Event type:', event.type);
  console.log('Payment ID:', event.data.paymentId);
  console.log('Status:', event.data.status);

  return new Response('OK');
}
```
+++

### Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `payload` | `string` | Yes | Raw request body |
| `signature` | `string` | Yes | `X-HivePay-Signature` header |
| `timestamp` | `string` | Yes | `X-HivePay-Timestamp` header |
| `maxAge` | `number` | No | Max age in ms (default: 300000) |

### Returns

```typescript
// Success
{ valid: true, event: WebhookEvent }

// Failure
{ valid: false, error: string }
```

---

### createTestWebhook

Generate test webhook signatures for development:

+++ TypeScript
```typescript
import { HivePay } from '@hivepay/client';

const hivepay = new HivePay({
  apiKey: 'sk_test_xxx',
  webhookSecret: 'whsec_xxx'
});

// Create test webhook
const { signature, timestamp, body } = await hivepay.createTestWebhook({
  type: 'payment.status_changed',
  data: {
    paymentId: 'pay_test_123',
    status: 'completed'
  }
});

// Send to your endpoint
const response = await fetch('http://localhost:3000/webhooks/hivepay', {
  method: 'POST',
  headers: {
    'X-HivePay-Signature': signature,
    'X-HivePay-Timestamp': timestamp,
    'Content-Type': 'application/json'
  },
  body
});
```
+++

---

## Type Guards

### isHivePayError

Check if an error is a HivePay error:

+++ TypeScript
```typescript
import { isHivePayError } from '@hivepay/client';

try {
  await hivepay.payments.get('invalid-id');
} catch (error) {
  if (isHivePayError(error)) {
    // TypeScript knows this is HivePayError
    console.log(error.code);
    console.log(error.statusCode);

    // Use type guards
    if (error.isNotFound()) {
      // Handle 404
    }
  }
}
```
+++

---

## Examples

### Webhook Handler with Full Validation

+++ TypeScript
```typescript
import { HivePay, isHivePayError } from '@hivepay/client';

const hivepay = new HivePay({
  apiKey: process.env.HIVEPAY_API_KEY!,
  webhookSecret: process.env.HIVEPAY_WEBHOOK_SECRET!
});

export async function POST(req: Request) {
  // Extract headers
  const signature = req.headers.get('X-HivePay-Signature');
  const timestamp = req.headers.get('X-HivePay-Timestamp');

  if (!signature || !timestamp) {
    return new Response('Missing headers', { status: 400 });
  }

  // Verify webhook
  const result = await hivepay.verifyWebhook({
    payload: await req.text(),
    signature,
    timestamp
  });

  if (!result.valid) {
    return new Response(result.error, { status: 401 });
  }

  // Handle event
  const { event } = result;

  if (event.type === 'payment.status_changed') {
    const { paymentId, status } = event.data;

    try {
      // Fetch full payment details
      const payment = await hivepay.payments.get(paymentId);

      if (status === 'completed') {
        await fulfillOrder(payment);
      }
    } catch (error) {
      if (isHivePayError(error) && error.isNotFound()) {
        console.warn('Payment not found:', paymentId);
      }
      throw error;
    }
  }

  return new Response('OK');
}
```
+++
