# BeezifiPay JavaScript SDK

Official Node.js SDK for the BeezifiPay REST API. Zero dependencies — uses Node's built-in `https` module.

## Installation

```bash
npm install beezifipay
```

Requires Node 18+.

## Authentication

| Key prefix | Used for |
|---|---|
| `sk_live_` / `sk_test_` | Server-side merchant API calls |
| `pk_live_` / `pk_test_` | Publishable key (client-safe, limited scope) |
| `pt_…` | Payer token — issued by `client.login()` |

Generate API keys in the merchant portal under **Organization → API Keys**.

---

## Merchant SDK

```js
const { MerchantClient } = require('beezifipay');

const client = new MerchantClient({
  apiKey:  'sk_live_…',           // required
  baseUrl: 'https://pay.beezifi.com', // optional — default
  timeout: 30_000,                // optional ms — default 30s
  maxRetries: 2,                  // optional — default 2 (retries 408/429/5xx)
});
```

### Organisation

```js
const org = await client.getOrg();
// { id, name, slug, status, createdAt }
```

### Checkouts

```js
// Create
const checkout = await client.createCheckout({
  payerEmail: 'alice@example.com',
  items: [
    { name: 'Widget', quantity: 2, unitPrice: 49.99, description: 'Blue widget' },
  ],
  taxAmount: 8.00,
  currency: 'USD',           // default USD
  merchantNotes: 'Order #1', // optional
  locationId: 42,            // optional
  idempotencyKey: 'order-1', // optional — safe to retry
});

// Deliver to payer's payment requests
await client.deliverCheckout(checkout.sessionCode);

// Poll status (prefer webhooks in production)
const { status, settledAt } = await client.getCheckoutStatus(checkout.sessionCode);

// Get full checkout
const full = await client.getCheckout(checkout.sessionCode);

// Cancel
await client.cancelCheckout(checkout.sessionCode);

// List with pagination
const page = await client.listCheckouts({ status: 'settled', limit: 20 });
console.log(page.data, page.hasMore);
const page2 = await page.nextPage(); // null if no more

// Iterate all pages with async iterator
for await (const c of page) {
  console.log(c.sessionCode, c.total);
}
```

### POS (Point of Sale)

```js
const pos = await client.createPosCheckout({
  items: [{ name: 'Coffee', quantity: 1, unitPrice: 4.50 }],
  taxAmount: 0.40,
  idempotencyKey: 'pos-txn-777',
});
console.log(pos.payUrl);   // QR URL to show payer
console.log(pos.sessionCode);
```

### Refunds

```js
const refund = await client.issueRefund({
  checkoutCode: 'CR1A2B3C4D',
  amount: 49.99,             // optional — omit for full refund
  reason: 'requested_by_customer', // duplicate | fraudulent | requested_by_customer | other
  notes: 'Return approved',
  idempotencyKey: 'refund-order-1',
});
```

### API Key Management

```js
const keys = await client.listKeys();
const { id, key } = await client.createKey({ name: 'CI/CD', environment: 'test' });
await client.revokeKey(id);
```

---

## Payer SDK

```js
const { PayerClient } = require('beezifipay');

const client = new PayerClient({
  apiKey:  'pk_live_…',            // publishable key for initial requests
  baseUrl: 'https://pay.beezifi.com',
});
```

### Auth

```js
// Register
await client.register({ fullName: 'Alice', email: 'alice@example.com', password: 'secret' });

// Login — token is stored on client and attached to all subsequent requests
const { token, expiresAt, payer } = await client.login({ email: 'alice@example.com', password: 'secret' });

// Pass an existing token instead of logging in each time:
const client2 = new PayerClient({ apiKey: 'pk_live_…', payerToken: token });

// Logout — revokes the token server-side
await client.logout();

// Verify email
await client.verifyEmail('token-from-email');
```

### Profile

```js
const profile = await client.getProfile();
// { id, email, phone, fullName, emailVerifiedAt, phoneVerifiedAt, createdAt }
```

### Payment Methods

```js
const methods = await client.listPaymentMethods();

// Add a Stripe PM ID (obtain via Stripe.js on the client side)
const method = await client.addPaymentMethod({ stripePaymentMethodId: 'pm_xxx', label: 'My Visa' });

await client.setDefaultPaymentMethod(method.id);
await client.removePaymentMethod(method.id);
```

### Payment Requests

```js
// List pending requests
const page = await client.listRequests({ status: 'delivered', limit: 10 });

// Get one
const request = await client.getRequest('CR1A2B3C4D');

// Approve (pay)
await client.approveRequest('CR1A2B3C4D', {
  methodId: 12,
  idempotencyKey: 'approve-CR1A2B3C4D',
});

// Decline
await client.declineRequest('CR1A2B3C4D', { reason: 'Not my order' });
```

---

## Webhooks

Verify incoming webhook deliveries using the `X-BeezifiPay-Signature` header:

```js
const { WebhookVerifier } = require('beezifipay');
const verifier = new WebhookVerifier(process.env.BEEZIFI_WEBHOOK_SECRET);

// Express — must use raw body
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
  const event = verifier.parse(req.body, req.headers['x-beezifipay-signature']);
  // { event: 'checkout.settled', timestamp: '…', data: { … } }
  res.json({ received: true });
});
```

### Event types

| Event | Fired when |
|---|---|
| `checkout.settled` | Payer payment succeeded |
| `checkout.declined` | Payer declined the request |
| `checkout.payment_failed` | Stripe charge failed |
| `checkout.refunded` | A refund was issued |

---

## Error handling

All API errors throw `ApiError`:

```js
const { ApiError } = require('beezifipay');

try {
  await client.createCheckout({ … });
} catch (err) {
  if (err instanceof ApiError) {
    console.error(err.code, err.message, err.status, err.requestId);
    if (err.code === 'PAYER_NOT_FOUND') { /* … */ }
  }
}
```

| Property | Type | Description |
|---|---|---|
| `message` | string | Human-readable description |
| `code` | string | Machine-readable error code |
| `status` | number | HTTP status code |
| `requestId` | string | `req_…` — include in support tickets |
| `details` | array \| null | Validation error messages |

### Common error codes

| Code | Status | Meaning |
|---|---|---|
| `UNAUTHORIZED` | 401 | Missing or invalid API key |
| `INVALID_API_KEY` | 401 | Key revoked or not found |
| `KEY_EXPIRED` | 401 | Key past expiry date |
| `VALIDATION_ERROR` | 400 | Request body failed validation |
| `PAYER_NOT_FOUND` | 404 | Payer email not registered |
| `CHECKOUT_NOT_FOUND` | 404 | Session code not found or wrong org |
| `ALREADY_REFUNDED` | 422 | Checkout already fully refunded |
| `INVALID_AMOUNT` | 422 | Refund exceeds settled amount |
| `INVALID_TRANSITION` | 422 | Status change not allowed |

---

## Idempotency

Pass `idempotencyKey` on any mutating call. Duplicate requests within 24 hours return the cached response with `Idempotency-Replayed: true`:

```js
await client.createCheckout({ …, idempotencyKey: `order-${orderId}` });
```

Use a stable, unique key per business operation (e.g. your internal order ID).

---

## Examples

See the [`examples/`](examples/) directory:

- [`merchant-quickstart.js`](examples/merchant-quickstart.js) — create, deliver, poll, refund
- [`payer-quickstart.js`](examples/payer-quickstart.js) — login, list requests, approve
- [`webhook-handler.js`](examples/webhook-handler.js) — receive and verify events
- [`express-integration.js`](examples/express-integration.js) — full Express server integration
