npm package: @sodasoft/meshtally
Description: Meshtally Node.js SDK — Smart Payment Routing & Orchestration
Author: Ratomir Jovanovic
Latest version: 2.6.1

# @sodasoft/meshtally

Official Node.js SDK for the **Meshtally** Smart Payment Routing & Orchestration API.

Zero dependencies. Full TypeScript support. Node.js 18+ (uses native `fetch`).

[![npm version](https://badge.fury.io/js/%40sodasoft%2Fmeshtally.svg)](https://www.npmjs.com/package/@sodasoft/meshtally)

## Install

```bash
npm install @sodasoft/meshtally
```

## Quick Start

```ts
import { Meshtally } from '@sodasoft/meshtally';

const meshtally = new Meshtally({
  apiKey: process.env.MESHTALLY_API_KEY!,
  environment: 'sandbox',
});

const session = await meshtally.checkoutSessions.create({
  amount: 99.99,
  currency: 'EUR',
  customer_email: 'customer@example.com',
  success_url: 'https://yoursite.com/success',
  cancel_url: 'https://yoursite.com/cancel',
});

// Redirect customer to hosted checkout
res.redirect(session.checkout_url);
```

## ML-Powered Routing Context

Pass transaction context to enable intelligent PSP routing. The more context you provide, the better the ML model optimizes approval rates:

```ts
const session = await meshtally.checkoutSessions.create({
  amount: 499.99,
  currency: 'EUR',

  // Routing context — improves ML predictions
  customer_country: 'DE',
  card_bin: '411111',             // First 6 digits (Visa/MC/Amex detection)
  channel: 'web',                 // 'web' | 'mobile' | 'api' | 'in_store'
  risk_level: 'low',              // 'low' | 'medium' | 'high'
  device_type: 'desktop',         // 'desktop' | 'mobile' | 'tablet'
  payment_type: 'subscription',   // 'one_time' | 'subscription' | 'recurring' | 'deposit' | 'withdrawal'

  // Product context — for analytics & routing
  product_category: 'trading_deposit',
  product_name: 'Pro Plan',
  price_tier: 'high',             // 'micro' | 'low' | 'medium' | 'high' | 'premium'
  subscription_interval: 'monthly',

  // Customer context
  customer_email: 'trader@example.com',
  is_returning_customer: true,
  customer_lifetime_payments: 12,
  billing_address: { country: 'DE', postal_code: '10115' },

  // URLs
  success_url: 'https://yoursite.com/success',
  cancel_url: 'https://yoursite.com/cancel',
  metadata: { order_id: 'ORD-123' },
});
```

## Multi-Item Checkout (Line Items)

For orders with multiple products (e-commerce, airline tickets, etc.):

```ts
const session = await meshtally.checkoutSessions.create({
  currency: 'EUR',
  line_items: [
    { name: 'Flight BEG-LHR', quantity: 1, unit_price: 289.00, currency: 'EUR' },
    { name: 'Extra Baggage 23kg', quantity: 2, unit_price: 35.00, currency: 'EUR' },
    { name: 'Seat Selection 14A', quantity: 1, unit_price: 12.00, currency: 'EUR' },
  ],
  customer_country: 'RS',
  payment_type: 'one_time',
  product_category: 'airline_ticket',
  success_url: 'https://airline.com/booking/success',
});
// Total: €371.00 (auto-calculated from line items)
```

## Configuration

```ts
const meshtally = new Meshtally({
  apiKey: process.env.MESHTALLY_API_KEY!,
  environment: 'sandbox',  // 'sandbox' | 'production'
  timeout: 30000,           // request timeout in ms (default: 30000)
  maxRetries: 3,            // retry count for 429/5xx (default: 3)
  debug: false,             // enable request/response logging (default: false)
});
```

## Resources

### Checkout Sessions

```ts
// Create
const session = await meshtally.checkoutSessions.create({ amount: 99.99, currency: 'EUR' });

// Retrieve
const detail = await meshtally.checkoutSessions.retrieve('cs_abc123');

// List
const { sessions, total } = await meshtally.checkoutSessions.list({ status: 'completed', limit: 20 });

// Expire
await meshtally.checkoutSessions.expire('cs_abc123');
```

### Transactions

```ts
// Retrieve
const txn = await meshtally.transactions.retrieve('txn_abc123');

// List with filters
const { transactions, total } = await meshtally.transactions.list({
  status: 'captured',
  currency: 'EUR',
  limit: 50,
});

// Update metadata
await meshtally.transactions.update('txn_abc123', {
  metadata: { order_id: 'order_456' },
});
```

### Refunds

```ts
// Create (full or partial)
const refund = await meshtally.refunds.create({
  transaction_id: 'txn_abc123',
  amount: 25.00,
  reason: 'Customer request',
});

// List refunds for a transaction
const { refunds } = await meshtally.refunds.list({ transaction_id: 'txn_abc123' });
```

### Payouts

```ts
// Create payout to beneficiary
const payout = await meshtally.payouts.create({
  beneficiary_id: 'ben_abc123',
  amount: 500.00,
  currency: 'EUR',
});

// List payouts
const { payouts } = await meshtally.payouts.list({ status: 'completed' });

// Approve / Cancel
await meshtally.payouts.approve('po_abc123');
await meshtally.payouts.cancel('po_abc123');

// Batch payouts
await meshtally.payouts.createBatch({
  instructions: [
    { beneficiary_id: 'ben_1', amount: 100, currency: 'EUR' },
    { beneficiary_id: 'ben_2', amount: 200, currency: 'EUR' },
  ],
});
```

### Beneficiaries

```ts
// Create
const beneficiary = await meshtally.beneficiaries.create({
  first_name: 'John',
  last_name: 'Doe',
  type: 'trader',
  email: 'john@example.com',
});

// Retrieve
const ben = await meshtally.beneficiaries.retrieve('ben_abc123');

// Update
await meshtally.beneficiaries.update('ben_abc123', { email: 'new@example.com' });

// List
const { beneficiaries, total } = await meshtally.beneficiaries.list({ limit: 50 });

// Delete (soft-delete — sets status to blocked)
await meshtally.beneficiaries.delete('ben_abc123');

// ─── Payout methods ─────────────────────────────────────────

// Add a bank account
await meshtally.beneficiaries.addPayoutMethod('ben_abc123', {
  method_type: 'bank',
  currency: 'EUR',
  iban: 'DE89370400440532013000',
  swift_bic: 'COBADEFFXXX',
  bank_name: 'Commerzbank',
  bank_country: 'DE',
});

// Add a crypto wallet
await meshtally.beneficiaries.addPayoutMethod('ben_abc123', {
  method_type: 'crypto',
  currency: 'USDT',
  crypto_network: 'tron',
  wallet_address: 'TEbatdMV3...',
});

// List payout methods for a beneficiary
const { payout_methods } = await meshtally.beneficiaries.listPayoutMethods({
  beneficiary_id: 'ben_abc123',
});

// Update label or set as default
await meshtally.beneficiaries.updatePayoutMethod('pm_xyz789', {
  label: 'Primary EUR account',
  is_default: true,
});

// Delete (soft-delete — sets status to inactive)
await meshtally.beneficiaries.deletePayoutMethod('pm_xyz789');
```

### Settlement

```ts
// Get all balances
const balances = await meshtally.settlement.getBalances();

// Get balance for specific currency
const eurBalance = await meshtally.settlement.getBalance('EUR');

// Get settlement ledger
const ledger = await meshtally.settlement.getLedger({ currency: 'EUR', limit: 100 });
```

### Account

```ts
const account = await meshtally.account.retrieve();
console.log(account.name, account.status);
```

## Webhook Verification

Verify incoming webhook signatures (HMAC-SHA256):

```ts
import { WebhooksResource } from '@sodasoft/meshtally';

app.post('/webhooks/meshtally', express.raw({ type: 'application/json' }), (req, res) => {
  try {
    const event = WebhooksResource.constructEvent(
      req.body,
      req.headers['x-meshtally-signature'] as string,
      process.env.MESHTALLY_WEBHOOK_SECRET!,
    );

    switch (event.payload.event_type) {
      case 'payment.succeeded':
        console.log('Payment captured:', event.payload.data.transaction);
        break;
      case 'payment.failed':
        console.log('Payment failed:', event.payload.data.transaction);
        break;
      case 'refund.processed':
        console.log('Refund completed');
        break;
      case 'payout.completed':
        console.log('Payout sent:', event.payload.data.payout);
        break;
    }

    res.json({ received: true });
  } catch (err) {
    res.status(400).send('Invalid signature');
  }
});
```

## Error Handling

```typescript
import { MeshtallyError, AuthenticationError, InvalidRequestError, RateLimitError, NetworkError } from '@sodasoft/meshtally';

try {
  await meshtally.checkoutSessions.create({ amount: 100, currency: 'EUR' });
} catch (err) {
  if (err instanceof AuthenticationError) {
    // Invalid or expired API key (401/403)
  } else if (err instanceof InvalidRequestError) {
    // Bad parameters — check err.message for details (400/422)
  } else if (err instanceof RateLimitError) {
    // Too many requests — SDK retries automatically with backoff
  } else if (err instanceof NetworkError) {
    // DNS failure, timeout, connection reset
  }
  // All errors have: err.message, err.status, err.code, err.requestId
}
```

### Error Properties

| Property | Type | Description |
|----------|------|-------------|
| `message` | `string` | Human-readable error message |
| `status` | `number` | HTTP status code |
| `code` | `string` | Machine-readable error code |
| `requestId` | `string` | Server request ID for support |

## Pagination

```typescript
// Manual pagination
const page1 = await meshtally.transactions.list({ limit: 50, offset: 0 });
const page2 = await meshtally.transactions.list({ limit: 50, offset: 50 });

// Auto-pagination (async generator)
for await (const txn of meshtally.paginate(meshtally.transactions, 'list', { status: 'captured' })) {
  console.log(txn.id, txn.amount);
}
```

## Environment

- **Runtime:** Node.js 18+ only (uses native `fetch` and `node:crypto`)
- **Not compatible with browsers** — use the hosted checkout redirect flow for browser-based integrations
- **TypeScript:** Full type definitions included, strict mode compatible
- **Dependencies:** Zero runtime dependencies

## Retry Behavior

Automatic retries with exponential backoff:

- **Retried:** `429` (rate limited), `5xx` (server errors), network errors, timeouts
- **Not retried:** `400`, `401`, `403`, `404`, `422` (client errors)
- **Backoff:** 500ms → 1s → 2s (with 0-25% jitter)
- **Max retries:** 3 (configurable)

## Idempotency

All write operations automatically include an `Idempotency-Key` header (UUID v4). You can also provide your own:

```ts
const session = await meshtally.checkoutSessions.create(
  { amount: 99.99, currency: 'EUR' },
  { idempotencyKey: 'order-123-checkout' },
);
```

## Industry Examples

### FX Broker — Trader Deposit
```ts
await meshtally.checkoutSessions.create({
  amount: 1000, currency: 'USD',
  payment_type: 'deposit',
  product_category: 'trading_deposit',
  customer_country: 'AE',
  is_returning_customer: true,
});
```

### iGaming — Player Top-Up
```ts
await meshtally.checkoutSessions.create({
  amount: 50, currency: 'EUR',
  payment_type: 'deposit',
  product_category: 'gaming_deposit',
  channel: 'mobile',
  risk_level: 'low',
});
```

### Prop Trading — Challenge Fee
```ts
await meshtally.checkoutSessions.create({
  amount: 499.99, currency: 'USD',
  payment_type: 'one_time',
  product_category: 'challenge_fee',
  product_name: '100K Funded Challenge',
  price_tier: 'high',
});
```

### SaaS — Monthly Subscription
```ts
await meshtally.checkoutSessions.create({
  amount: 29.99, currency: 'EUR',
  payment_type: 'subscription',
  subscription_interval: 'monthly',
  product_name: 'Pro Plan',
  is_returning_customer: true,
  customer_lifetime_payments: 6,
});
```

## Links

- [Documentation](https://meshtally.com/docs)
- [SDK Quickstart](https://meshtally.com/sdk)
- [Help Center](https://meshtally.com/help)
- [Contact](https://meshtally.com/contact)

## License

MIT — [Sodasoft LLC](https://sodasoft.com)