npm package: @orvion/sdk
Description: Orvion SDK for x402 V2 payment-protected APIs
Author: Orvion
Latest version: 0.5.3

# Orvion Node.js SDK

Node.js SDK for integrating x402 payment-protected APIs with Orvion.

## Installation

```bash
npm install @orvion/sdk
# or
yarn add @orvion/sdk
# or
pnpm add @orvion/sdk
```

## Quick Start

### Express Integration

```typescript
import express from 'express';
import { orvionInit, requirePayment } from '@orvion/sdk/express';

const app = express();

// Initialize Orvion
orvionInit({
  apiKey: process.env.ORVION_API_KEY!,
});

// Protect an endpoint with payment requirement
app.get('/api/premium/data', requirePayment(), (req, res) => {
  // Access payment info
  const payment = req.payment;
  res.json({
    data: 'Premium content!',
    paid: payment.amount,
    transactionId: payment.transactionId,
  });
});

// Override price in code
app.get('/api/reports/generate', requirePayment({ amount: '5.00', currency: 'USD' }), (req, res) => {
  res.json({ report: '...' });
});

// Require customer identification (no anonymous access)
app.get('/api/ai/chat', requirePayment({ allowAnonymous: false }), (req, res) => {
  const customerId = req.payment.customerRef;
  res.json({ response: '...' });
});

app.listen(3000);
```

### Standalone Client

```typescript
import { OrvionClient } from '@orvion/sdk';

const client = new OrvionClient({
  apiKey: 'your_api_key',
});

// Create a charge
const charge = await client.createCharge({
  amount: '0.10',
  currency: 'USD',
  customerRef: 'user_123',
  resourceRef: 'protected_route:abc-123',
});

console.log(`Charge ID: ${charge.id}`);
console.log(`Payment URL: ${charge.x402Requirements}`);

// Verify a payment
const result = await client.verifyCharge({
  transactionId: 'tx_abc123',
  resourceRef: 'protected_route:abc-123',
});

if (result.verified) {
  console.log('Payment verified!');
}
```

### Facilitator Payment (x402 V2) with web3.js

Build the EIP-3009 typed data, sign it in the browser, and send the
`paymentPayload` to your backend.

```typescript
import { OrvionClient, buildEip3009TypedData, buildEvmAuthorization, buildEvmPaymentPayload } from '@orvion/sdk';

const client = new OrvionClient({ apiKey: 'your_api_key' });

const authorization = buildEvmAuthorization({
  from: '0x...',
  to: '0x...',
  value: '1000000',
  validAfter: '1716150000',
  validBefore: '1716153600',
  nonce: '0x...',
});

const typedData = buildEip3009TypedData({
  tokenName: 'USDC',
  tokenVersion: '2',
  chainId: 8453,
  verifyingContract: '0x...',
  authorization,
});

const signature = await window.ethereum.request({
  method: 'eth_signTypedData_v4',
  params: [authorization.from, JSON.stringify(typedData)],
});

const paymentPayload = buildEvmPaymentPayload({
  signature,
  authorization,
  network: 'base',
});

await client.processPayment({
  transactionId: 'charge_xxx',
  paymentPayload,
});
```

## Configuration

### Environment Variables

- `ORVION_API_KEY` - Your Orvion API key
- `ORVION_BASE_URL` - API base URL (default: https://api.orvion.sh)

### Middleware Options

```typescript
orvionInit({
  apiKey: 'your_api_key',
  baseUrl: 'https://api.orvion.sh',  // Custom API URL
  cacheTtlSeconds: 60,  // Route config cache TTL
  transactionHeader: 'X-Transaction-Id',  // Custom header name
  customerHeader: 'X-Customer-Id',  // Custom header name
});
```

### Middleware Options

```typescript
requirePayment({
  amount: '0.10',  // Override dashboard price
  currency: 'USD',
  allowAnonymous: false,  // Require customer identification
  customerResolver: (req) => req.user?.id,  // Custom customer ID extraction
  hostedCheckout: true,  // Enable hosted checkout mode (redirects to pay.orvion.sh)
})
```

### Hosted Checkout Mode

For web applications, use hosted checkout mode for a seamless payment experience:

```typescript
// API endpoint - protected with payment
app.get('/api/premium',
  requirePayment({
    amount: '1.00',
    currency: 'USDC',
    hostedCheckout: true,  // Just add this!
  }),
  (req, res) => {
    res.json({ message: 'Premium content!' });
  }
);

// Frontend page - serves the UI
app.get('/premium', (req, res) => {
  res.sendFile('static/premium.html');
});
```

**Automatic URL Convention**: The SDK strips `/api` prefix automatically:
- API at `/api/premium` → Frontend at `/premium`
- After payment, users are redirected to `/premium?charge_id=xxx&status=succeeded`
- No `returnUrl` configuration needed!

## Testing

The SDK includes comprehensive testing utilities and is fully tested with Vitest.

### Testing Utilities

```typescript
import { mockOrvion, fakePayment } from '@orvion/sdk/testing';

// Mock all Orvion calls
const mock = mockOrvion({ alwaysApprove: true });
mock.addRoute('/api/premium/*', { amount: '0.10' });

// In your test
const response = await request(app).get('/api/premium/data');
expect(response.status).toBe(200);

mock.restore();

// Create fake payment for testing
req.payment = fakePayment({
  amount: '0.10',
  currency: 'USD',
  transactionId: 'test_tx_123',
});
```

### Running Tests

```bash
# Run all tests
npm test

# Run tests in watch mode
npm run test:watch

# Run tests with coverage
npm run test:coverage

# Run E2E tests (requires ORVION_STAGING_API_KEY)
npm run test:e2e
```

### Test Structure

The SDK uses a three-layer testing approach:

- **Unit Tests** (`tests/unit/`) - Pure logic tests for matcher, cache, and error classes
- **Integration Tests** (`tests/integration/`) - HTTP mocking with MSW for client and middleware
- **E2E Tests** (`tests/e2e/`) - Smoke tests against staging API

### HTTP Mocking with MSW

The SDK uses [MSW (Mock Service Worker)](https://mswjs.io/) for HTTP mocking in integration tests:

```typescript
import { server } from './tests/helpers/server';
import { http, HttpResponse } from 'msw';

// Override default handlers
server.use(
  http.post('https://api.orvion.sh/v1/charges', () => {
    return HttpResponse.json({
      id: 'custom_charge',
      amount: '1.00',
      currency: 'USD',
      status: 'pending',
    });
  })
);
```

### Express Middleware Testing

Test Express middleware with Supertest:

```typescript
import request from 'supertest';
import express from 'express';
import { orvionInit, requirePayment } from '@orvion/sdk/express';

const app = express();
app.use(orvionInit({ apiKey: 'test_key' }));

app.get('/api/premium/data', requirePayment(), (req, res) => {
  res.json({ data: 'premium', payment: req.payment });
});

// Test
const response = await request(app)
  .get('/api/premium/data')
  .set('X-Payment-Transaction', 'valid_tx_123')
  .expect(200);

expect(response.body.payment).toBeDefined();
```

### Telemetry

Opt-in telemetry is available for monitoring SDK usage:

```typescript
import { OrvionClient } from '@orvion/sdk';

const client = new OrvionClient({
  apiKey: 'your_key',
  telemetry: {
    enabled: true,
    serviceName: 'my-app',
    serviceVersion: '1.0.0',
  },
});
```

**Privacy**: Telemetry is opt-in and never collects PII (no customer refs, transaction IDs, or amounts).

## TypeScript

This package includes TypeScript declarations. The `Payment` type is available on Express requests:

```typescript
import { Request } from 'express';
import { PaymentInfo } from '@orvion/sdk';

app.get('/api/premium', requirePayment(), (req: Request, res) => {
  const payment: PaymentInfo = req.payment;
  // ...
});
```

## License

MIT