npm package: @moanapay/sdk
Description: MoanaPay TypeScript SDK for payment processing, split payments, and connected accounts
Latest version: 0.1.0

# @moanapay/sdk

Official TypeScript SDK for the MoanaPay API. Supports payments, connected accounts, split payments, refunds, payouts, and webhook verification.

## Installation

```bash
npm install @moanapay/sdk
```

## Quick Start

```typescript
import { MoanaPay } from '@moanapay/sdk';

const moanapay = new MoanaPay('tp_live_xxx');

// Create a payment
const payment = await moanapay.payments.create({
  amountMinor: '10000', // T$100.00 in seniti
  currency: 'TOP',
  returnUrl: 'https://example.com/success',
});

console.log(payment.id, payment.checkoutUrl);
```

## Configuration

```typescript
const moanapay = new MoanaPay('tp_live_xxx', {
  baseUrl: 'https://api.moanapay.io', // default
  timeout: 10000,                      // 10s default
  maxRetries: 3,                       // retries on 429/5xx
});
```

The SDK auto-detects the environment from the API key prefix:
- `tp_live_` → Live environment
- `tp_test_` → Sandbox environment

## Payments

```typescript
// Create
const payment = await moanapay.payments.create({
  amountMinor: '5000',
  currency: 'TOP',
  returnUrl: 'https://example.com/return',
  metadata: { orderId: 'order-123' },
}, { idempotencyKey: MoanaPay.generateIdempotencyKey() });

// Get
const payment = await moanapay.payments.get('pay_abc123');

// List
const { items, total, totalPages } = await moanapay.payments.list({
  status: 'SUCCEEDED',
  page: 1,
  pageSize: 20,
});
```

### Split Payments

Create a payment with inline splits:

```typescript
const payment = await moanapay.payments.create({
  amountMinor: '100000',
  currency: 'TOP',
  returnUrl: 'https://example.com/return',
  splits: [
    { connectedAccountId: 'ca_vendor1', amountMinor: 60000 },
    { connectedAccountId: 'ca_vendor2', amountMinor: 30000 },
  ],
}, { idempotencyKey: MoanaPay.generateIdempotencyKey() });
```

Or use split rules:

```typescript
const payment = await moanapay.payments.create({
  amountMinor: '100000',
  currency: 'TOP',
  returnUrl: 'https://example.com/return',
  applySplitRules: true,
  splitAccounts: ['ca_vendor1', 'ca_vendor2'],
}, { idempotencyKey: MoanaPay.generateIdempotencyKey() });
```

Release held splits:

```typescript
// Release all splits for a payment
await moanapay.payments.releaseSplits('pay_abc123', {
  idempotencyKey: MoanaPay.generateIdempotencyKey(),
});

// Release a single split
await moanapay.payments.releaseSplit('pay_abc123', 'split_xyz', {
  idempotencyKey: MoanaPay.generateIdempotencyKey(),
});
```

## Connected Accounts

```typescript
// Create a connection (invite a vendor)
const account = await moanapay.connectedAccounts.create({
  vendorEmail: 'vendor@example.com',
  platformLabel: 'Supplier A',
  commissionType: 'PERCENTAGE',
  commissionBps: 500, // 5%
}, { idempotencyKey: MoanaPay.generateIdempotencyKey() });

// List
const { items } = await moanapay.connectedAccounts.list({ status: 'ACTIVE' });

// Get
const account = await moanapay.connectedAccounts.get('ca_abc123');

// Update
const updated = await moanapay.connectedAccounts.update('ca_abc123', {
  commissionBps: 750,
});

// Approve / Reject
await moanapay.connectedAccounts.approve('ca_abc123', {
  idempotencyKey: MoanaPay.generateIdempotencyKey(),
});
await moanapay.connectedAccounts.reject('ca_abc123', 'Not a valid vendor');

// Revoke
await moanapay.connectedAccounts.revoke('ca_abc123', 'Contract ended');
```

### Split Rules

```typescript
// Create
const rule = await moanapay.connectedAccounts.createRule('ca_abc123', {
  name: 'Standard Split',
  splitType: 'PERCENTAGE',
  splitBps: 3000, // 30%
  priority: 1,
});

// List
const rules = await moanapay.connectedAccounts.listRules('ca_abc123');

// Update
await moanapay.connectedAccounts.updateRule('ca_abc123', 'rule_xyz', {
  splitBps: 3500,
});

// Delete
await moanapay.connectedAccounts.deleteRule('ca_abc123', 'rule_xyz');
```

## Connections (Vendor-side)

```typescript
// List incoming connections
const { items } = await moanapay.connections.list();

// Get connection details
const connection = await moanapay.connections.get('ca_abc123');

// Approve invitation
await moanapay.connections.approve('ca_abc123', {
  idempotencyKey: MoanaPay.generateIdempotencyKey(),
});

// Reject invitation
await moanapay.connections.reject('ca_abc123', 'Not interested');

// Disconnect
await moanapay.connections.disconnect('ca_abc123', 'No longer partnering');

// View split payment history
const { items: splits } = await moanapay.connections.listSplits('ca_abc123', {
  page: 1,
  pageSize: 50,
});
```

## Refunds

```typescript
// Create
const refund = await moanapay.refunds.create({
  paymentId: 'pay_abc123',
  amount: 5000,
  reason: 'Customer requested',
}, { idempotencyKey: MoanaPay.generateIdempotencyKey() });

// Get
const refund = await moanapay.refunds.get('ref_abc123');

// List
const { items } = await moanapay.refunds.list({ paymentId: 'pay_abc123' });
```

## Payouts

```typescript
// Create
const payout = await moanapay.payouts.create({
  amount: '50000',
  currency: 'TOP',
  bankDetails: {
    bankAccountName: 'Business Account',
    bankAccountNumber: '1234567890',
    bankName: 'BSP Tonga',
  },
  description: 'Weekly payout',
}, { idempotencyKey: MoanaPay.generateIdempotencyKey() });

// Get
const payout = await moanapay.payouts.get('po_abc123');

// List
const { items } = await moanapay.payouts.list({ status: 'COMPLETED' });
```

## Webhook Verification

```typescript
import { MoanaPay } from '@moanapay/sdk';

const moanapay = new MoanaPay('tp_live_xxx');

// In your webhook handler (e.g. Express)
app.post('/webhooks', (req, res) => {
  const signature = req.headers['x-webhook-signature'] as string;
  const webhookSecret = process.env.WEBHOOK_SECRET!;

  try {
    const event = moanapay.webhooks.constructEvent(
      req.body, // raw body string
      signature,
      webhookSecret,
    );

    switch (event.type) {
      case 'payment.succeeded':
        // Handle successful payment
        break;
      case 'payment.split.credited':
        // Handle split payment credited to vendor
        break;
      case 'refund.completed':
        // Handle refund
        break;
    }

    res.status(200).send('OK');
  } catch (error) {
    if (error instanceof MoanaPay.errors.SignatureVerificationError) {
      res.status(400).send('Invalid signature');
    } else {
      res.status(500).send('Server error');
    }
  }
});
```

Or verify without parsing:

```typescript
const isValid = moanapay.webhooks.verifySignature(rawBody, signature, secret);
```

## Error Handling

```typescript
import { MoanaPay, ApiError } from '@moanapay/sdk';

try {
  await moanapay.payments.create({ ... });
} catch (error) {
  if (error instanceof MoanaPay.errors.AuthenticationError) {
    // 401 — invalid API key
  } else if (error instanceof MoanaPay.errors.PermissionError) {
    // 403 — capability disabled or insufficient permissions
  } else if (error instanceof MoanaPay.errors.NotFoundError) {
    // 404 — resource not found
  } else if (error instanceof MoanaPay.errors.RateLimitError) {
    // 429 — rate limited
    console.log('Retry after:', error.retryAfter, 'seconds');
  } else if (error instanceof MoanaPay.errors.IdempotencyError) {
    // 409 — idempotency conflict
  } else if (error instanceof ApiError) {
    // Other API error
    console.log(error.status, error.code, error.message, error.requestId);
  }
}
```

## Idempotency

All state-changing operations (POST/PUT/PATCH) accept an optional `idempotencyKey` to ensure exactly-once processing:

```typescript
const key = MoanaPay.generateIdempotencyKey();

const payment = await moanapay.payments.create(params, {
  idempotencyKey: key,
});
```

## Requirements

- Node.js >= 18.0.0 (uses native `fetch` and `crypto`)
- TypeScript >= 5.0 (recommended)

## License

MIT