npm package: @marmopay/node-sdk
Description: Official Node.js SDK for Marmopay Payment Gateway
Author: Marmopay
Latest version: 1.0.5

# Marmopay Node.js SDK

Official Node.js SDK for Marmopay Payment Gateway. Accept payments across Kenya and Uganda with M-Pesa, Airtel Money, and card payments.

## Features

- 💳 **Payment Processing** - Create and manage payments
- 🔍 **Transaction Management** - Query and track transactions
- 💰 **Refund Processing** - Process full and partial refunds
- 🔐 **Webhook Verification** - Secure webhook signature validation
- 📘 **TypeScript Support** - Full type definitions included
- 🔄 **Automatic Retries** - Built-in retry logic with exponential backoff
- ⚡ **Promise-based API** - Modern async/await support

## Installation

```bash
npm install @marmopay/node-sdk
```

```bash
yarn add @marmopay/node-sdk
```

```bash
pnpm add @marmopay/node-sdk
```

## Quick Start

```typescript
import { Marmopay } from '@marmopay/node-sdk';

// Initialize the SDK
const marmopay = new Marmopay({
  apiKey: process.env.MARMOPAY_API_KEY,
  environment: 'live', // or 'test'
  webhookSecret: process.env.MARMOPAY_WEBHOOK_SECRET,
});

// Create a payment
const payment = await marmopay.payments.create({
  amount: 1000, // Amount in cents (1000 = 10.00 KES)
  currency: 'KES',
  customer: {
    email: 'customer@example.com',
    phone: '+254712345678',
  },
  callbackUrl: 'https://your-domain.com/payment/callback',
  description: 'Order #12345',
});

console.log('Payment URL:', payment.paymentUrl);
// Redirect customer to payment.paymentUrl
```

## Configuration

### Required

- `apiKey` - Your Marmopay API key (get from dashboard)

### Optional

- `environment` - `'test'` or `'live'` (default: `'test'`)
- `webhookSecret` - Your webhook secret for signature verification
- `baseUrl` - Custom API base URL (default: auto-selected based on environment)
- `timeout` - Request timeout in milliseconds (default: `30000`)
- `retries` - Number of retry attempts (default: `3`)

## API Reference

### Payments

#### Create Payment

```typescript
const payment = await marmopay.payments.create({
  amount: 1000, // IMPORTANT: Must be a number (1000 = 10.00 KES in cents)
  currency: 'KES',
  customer: {
    email: 'customer@example.com',
    phone: '+254712345678',
    firstName: 'John', // optional
    lastName: 'Doe', // optional
  },
  callbackUrl: 'https://your-domain.com/callback', // optional
  reference: 'ORD-12345', // optional, auto-generated if not provided
  description: 'Order payment', // optional
  metadata: { // optional
    orderId: '12345',
    customField: 'value',
  },
});
```

**⚠️ Important Note About Amount:**
- `amount` **must be a number**, not a string
- Specify amounts in the smallest currency unit (cents/kobo)
- Examples: 1000 = 10.00 KES, 50000 = 500.00 KES

#### Get Payment

```typescript
const payment = await marmopay.payments.get('TXN-20240115-ABC123');
console.log(payment.status); // 'pending', 'success', 'failed'
```

#### List Payments

```typescript
const payments = await marmopay.payments.list({
  limit: 20,
  page: 1,
});
```

### Transactions

#### Get Transaction

```typescript
const transaction = await marmopay.transactions.get('TXN-20240115-ABC123');

if (transaction.status === 'success') {
  console.log('Payment confirmed!');
  // Fulfill order
}
```

#### List Transactions

```typescript
const transactions = await marmopay.transactions.list({
  status: 'success',
  startDate: '2024-01-01',
  endDate: '2024-01-31',
  limit: 50,
  page: 1,
});
```

### Refunds

#### Create Refund

```typescript
// Full refund
const refund = await marmopay.refunds.create({
  transactionId: 123,
  reason: 'Customer request',
});

// Partial refund
const partialRefund = await marmopay.refunds.create({
  transactionId: 123,
  amount: '500.00', // Refund half
  reason: 'Partial refund',
});
```

#### Get Refund

```typescript
const refund = await marmopay.refunds.get(456);
console.log(refund.status); // 'pending', 'completed', 'failed'
```

### Webhooks

Webhooks are the recommended way to receive real-time payment notifications.

**📌 Getting Your Webhook Secret:**
1. Log in to your Marmopay dashboard at https://pay.marmope.com
2. Navigate to **Settings → Webhooks**
3. Click **"Rotate Secret"** to generate a new webhook secret
4. Copy the secret and store it securely in your environment variables

**🔐 Security Note:**
- Each merchant has a **unique webhook secret**
- Marmopay signs all webhooks using **your merchant-specific secret**
- The SDK verifies signatures using the same secret you provide
- **Never** share your webhook secret publicly or commit it to version control

**⚠️ Important: Use raw body for webhook routes**

Signature verification uses the **exact raw request body** (the bytes sent over the wire). You must use `express.raw({ type: 'application/json' })` for your webhook route and **not** `express.json()`. If the body is parsed as JSON first, `req.body` becomes an object; re-stringifying it can produce a different string than the original (e.g. key order), so verification fails with "Invalid signature". Apply `express.raw()` only to the webhook route, and ensure no global `express.json()` runs before that route.

#### Express.js Example

```typescript
import express from 'express';
import { Marmopay } from '@marmopay/node-sdk';

const app = express();
const marmopay = new Marmopay({
  apiKey: process.env.MARMOPAY_API_KEY,
  webhookSecret: process.env.MARMOPAY_WEBHOOK_SECRET, // ⚠️ REQUIRED for webhook verification
});

// Use express.raw() for webhook routes to preserve signature
app.post(
  '/webhooks/marmopay',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.headers['x-marmopay-signature'] as string;

    try {
      // Verify and parse webhook using YOUR merchant-specific secret
      const event = marmopay.webhooks.parse(req.body, signature);

      // Process event
      switch (event.event) {
        case 'payment.success':
          console.log('Payment successful:', event.data);
          // Fulfill order
          break;

        case 'payment.failed':
          console.log('Payment failed:', event.data);
          // Notify customer
          break;

        case 'refund.processed':
          console.log('Refund processed:', event.data);
          // Update order
          break;
      }

      // Always respond quickly (within 5 seconds)
      res.json({ received: true });
    } catch (error) {
      console.error('Webhook verification failed:', error);
      res.status(401).json({ error: 'Invalid signature' });
    }
  }
);
```

#### Manual Verification

```typescript
const isValid = marmopay.webhooks.verify(req.body, signature);

if (isValid) {
  const event = JSON.parse(req.body);
  // Process event
}
```

## Error Handling

The SDK throws specific error types for different scenarios:

```typescript
import {
  AuthenticationError,
  ValidationError,
  NotFoundError,
  RateLimitError,
  NetworkError,
  WebhookSignatureError,
} from '@marmopay/node-sdk';

try {
  const payment = await marmopay.payments.create(paymentData);
} catch (error) {
  if (error instanceof AuthenticationError) {
    console.error('Invalid API key');
  } else if (error instanceof ValidationError) {
    console.error('Invalid data:', error.details);
  } else if (error instanceof NotFoundError) {
    console.error('Resource not found');
  } else if (error instanceof RateLimitError) {
    console.error('Rate limit exceeded');
  } else if (error instanceof NetworkError) {
    console.error('Network error:', error.message);
  } else {
    console.error('Unexpected error:', error);
  }
}
```

## TypeScript Support

The SDK is written in TypeScript and includes full type definitions:

```typescript
import { Marmopay, Payment, Transaction, Refund } from '@marmopay/node-sdk';

const marmopay = new Marmopay({ apiKey: 'your-key' });

// Types are automatically inferred
const payment: Payment = await marmopay.payments.create({...});
const transaction: Transaction = await marmopay.transactions.get('ref');
```

## Environment Variables

Create a `.env` file:

```env
MARMOPAY_API_KEY=mmp_test_your_key_here
MARMOPAY_WEBHOOK_SECRET=whsec_your_secret_here
MARMOPAY_ENVIRONMENT=test
```

Load with dotenv:

```typescript
import 'dotenv/config';
import { Marmopay } from '@marmopay/node-sdk';

const marmopay = new Marmopay({
  apiKey: process.env.MARMOPAY_API_KEY!,
  environment: process.env.MARMOPAY_ENVIRONMENT as 'test' | 'live',
  webhookSecret: process.env.MARMOPAY_WEBHOOK_SECRET,
});
```

## Testing

The SDK works seamlessly with test API keys:

```typescript
const marmopay = new Marmopay({
  apiKey: 'mmp_test_your_test_key', // Test key
  environment: 'test',
});

// Use test phone numbers and amounts
const payment = await marmopay.payments.create({
  amount: '100.00', // Test amount
  currency: 'KES',
  customer: {
    email: 'test@example.com',
    phone: '+254700000000', // Test phone
  },
});
```

## Best Practices

### 1. Use Environment Variables

Never hardcode API keys in your code.

### 2. Always Verify Webhooks

```typescript
if (!marmopay.webhooks.verify(req.body, signature)) {
  return res.status(401).send('Invalid signature');
}
```

### 3. Respond to Webhooks Quickly

```typescript
app.post('/webhooks/marmopay', async (req, res) => {
  // Verify signature
  
  // Respond immediately
  res.json({ received: true });
  
  // Process async
  processWebhookAsync(event);
});
```

### 4. Implement Idempotency

```typescript
const processedWebhooks = new Set();

if (processedWebhooks.has(event.data.reference)) {
  console.log('Already processed');
  return;
}

processedWebhooks.add(event.data.reference);
// Process webhook
```

### 5. Handle Errors Gracefully

```typescript
try {
  await marmopay.payments.create(data);
} catch (error) {
  if (error instanceof ValidationError) {
    // Show user-friendly message
  } else {
    // Log and alert developers
  }
}
```

## Support

- 📧 Email: dev@pay.marmope.com
- 📚 Documentation: https://pay.marmope.com
- 💬 Discord: https://discord.gg/marmopay
- 🐛 Issues: https://github.com/marmopay/node-sdk/issues

## License

MIT License - see LICENSE file for details

## Changelog

### v1.0.0 (2024-01-15)

- Initial release
- Payment creation and retrieval
- Transaction management
- Refund processing
- Webhook verification
- Full TypeScript support
- Automatic retries
- Comprehensive error handling