npm package: @saga-pay/sdk
Description: Saga SDK
Author: Saga
Latest version: 1.0.8

# Saga SDK

A TypeScript/JavaScript client library for integrating USDC payments into your applications with minimal code. The SDK handles payment orchestration while merchants control wallet integration.

**🌐 [Visit Saga Website](https://saga.onl)**

## Features

- **Single call payment** - `saga.pay()` handles everything
- **Automatic USDC allowance** - No manual approval steps required
- **Network validation** - Validates wallet network
- **Merchant-controlled wallet** - Pass in any ethers.js provider
- **Environment-based** - sandbox (Sepolia) / production (Mainnet)
- **TypeScript-first** with full type safety
- **Browser wallet support** - Works with MetaMask and other browser wallets
- **Error handling** - Comprehensive error handling with user-friendly messages

## Installation

```bash
npm install @saga-pay/sdk
```

## Quick Start

```typescript
import { Saga } from '@saga-pay/sdk';
import { ethers } from 'ethers';

// Get wallet provider (merchant controls this)
const provider = new ethers.BrowserProvider(window.ethereum);

// Initialize SDK (wallet must be connected first)
const saga = new Saga({
  environment: 'sandbox', // or 'production'
  walletProvider: provider
});

// Single payment call - returns PENDING status initially
const result = await saga.pay({
  merchantAddress: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
  amount: '10.00',
  callbackUrl: 'https://myapp.com/webhook' // Your backend webhook
});

console.log('Payment initiated:', result.paymentId, result.status); // Status will be 'PENDING'

// Check status (optional - backend webhook is recommended)
const status = await saga.getPaymentStatus(result.paymentId);
console.log('Current status:', status.status);
```

## API Reference

### Saga

The main SDK class that provides payment functionality.

#### Constructor

```typescript
new Saga(config: SagaConfig)
```

**Config Options:**
- `environment: 'sandbox' | 'production'` - Environment to use
- `walletProvider: ethers.Provider` - Ethers.js provider instance
- `allowanceConfig?: AllowanceConfig` - Optional allowance configuration

**AllowanceConfig Options:**
- `strategy: 'exact' | 'max'` - Allowance strategy (default: 'max')
- `autoPromptApproval: boolean` - Automatically prompt for approval (default: true)
- `approvalAmount?: string` - Custom approval amount (optional)

#### Methods

##### `pay(request: CreatePaymentRequest): Promise<PaymentResult>`

Execute a payment. This is the main method that handles the complete payment flow.

**Parameters:**
- `merchantAddress: string` - Merchant's wallet address
- `amount: string` - USDC amount (e.g., "10.00")
- `metadata?: Record<string, any>` - Optional payment metadata
- `callbackUrl?: string` - Optional webhook callback URL
- `callbackSecret?: string` - Optional webhook secret for signature verification

**Returns:**
- `paymentId: string` - Unique payment identifier
- `transactionHash: string` - Blockchain transaction hash
- `status: 'PENDING' | 'COMPLETED' | 'FAILED'` - Payment status
- `amount: string` - Payment amount
- `merchantAddress: string` - Merchant address
- `payerAddress: string` - Payer address

##### `getPaymentStatus(paymentId: string): Promise<PaymentStatusResponse>`

Get the current status of a payment.

##### `connectWallet(): Promise<WalletAccount>`

Connect to the wallet and return account information.

##### `getWalletAccount(): Promise<WalletAccount>`

Get current wallet account information.

##### `isConnected(): Promise<boolean>`

Check if wallet is connected.

##### `disconnect(): Promise<void>`

Disconnect from the wallet.

##### `getUSDCAddress(): string`

Get the USDC contract address for the current network.

##### `getEnvironment(): string`

Get the current environment.

## Best Practices

**Wallet Connection:**
- **SDK assumes wallet is already connected** by the merchant
- **Merchant controls wallet connection** (MetaMask, WalletConnect, etc.)

**Payment Flow:**
1. **Frontend**: Call `saga.pay()` → returns `PENDING` status
2. **Backend**: Handle webhook callbacks for final confirmation
3. **Frontend**: Optional polling for user feedback

**Why Backend Webhooks?**
- Reliable (won't fail if user closes browser)
- Secure (backend controls business logic)
- Authoritative (backend is source of truth)

## Examples

### Basic Integration

```typescript
import { Saga } from '@saga-pay/sdk';
import { ethers } from 'ethers';

const saga = new Saga({
  environment: 'sandbox',
  walletProvider: new ethers.BrowserProvider(window.ethereum)
});

// Payment returns PENDING status initially
const result = await saga.pay({
  merchantAddress: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
  amount: '10.00',
  callbackUrl: 'https://myapp.com/webhook'
});

console.log('Payment initiated:', result.paymentId, result.status); // 'PENDING'
```

### Allowance Configuration

```typescript
import { Saga } from '@saga-pay/sdk';
import { ethers } from 'ethers';

// Configure automatic allowance approval
const saga = new Saga({
  environment: 'sandbox',
  walletProvider: new ethers.BrowserProvider(window.ethereum),
  allowanceConfig: {
    strategy: 'max', // or 'exact' for exact amount approval
    autoPromptApproval: true, // Automatically prompt for approval
    approvalAmount: '1000.00' // Custom approval amount (optional)
  }
});

// SDK will automatically handle USDC allowance approval
const result = await saga.pay({
  merchantAddress: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
  amount: '10.00',
  callbackUrl: 'https://myapp.com/webhook'
});
```

### React Integration

```typescript
import React, { useState } from 'react';
import { Saga } from '@saga-pay/sdk';
import { ethers } from 'ethers';

function PaymentButton() {
  const [isLoading, setIsLoading] = useState(false);
  const [paymentId, setPaymentId] = useState<string | null>(null);

  const saga = new Saga({
        environment: 'sandbox',
    walletProvider: new ethers.BrowserProvider(window.ethereum)
      });

  const handlePayment = async () => {
    setIsLoading(true);
    try {
      const result = await saga.pay({
        merchantAddress: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
        amount: '10.00',
        callbackUrl: 'https://myapp.com/webhook'
      });
      
      setPaymentId(result.paymentId);
      console.log('Payment initiated:', result.status); // 'PENDING'
    } catch (error) {
      console.error('Payment failed:', error);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div>
      <button onClick={handlePayment} disabled={isLoading}>
      {isLoading ? 'Processing...' : 'Pay with USDC'}
    </button>
      {paymentId && <p>Payment ID: {paymentId}</p>}
    </div>
  );
}
```

### Backend Webhook

```typescript
// Express.js webhook endpoint
app.post('/webhook', async (req, res) => {
  const { paymentId, status, amount, metadata } = req.body;

  // 🔒 SECURITY: Always validate with GET request for security
  const actualPayment = await fetch(`https://api.saga.onl/v1/pay/${paymentId}`);
  const actualStatus = actualPayment.status;
  
  if (actualStatus === 'COMPLETED') {
    // Handle successful payment
    console.log(`Payment completed: ${amount} USDC`);
    await updateOrderStatus(metadata.orderId, 'paid');
    await sendConfirmationEmail(metadata.customerEmail);
  }
  
  res.status(200).json({ received: true });
});
```

**🔒 Security Best Practice:**
Always validate webhook data with a GET request to `/pay/{paymentId}` to ensure you're processing the most current payment status.


### Error Handling

```typescript
import { Saga, WalletError, PaymentError, APIError } from '@saga-pay/sdk';

try {
  const result = await saga.pay({
    merchantAddress: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
    amount: '10.00'
  });
} catch (error) {
  if (error instanceof WalletError) {
    console.error('Wallet error:', error.message);
  } else if (error instanceof PaymentError) {
    console.error('Payment error:', error.message);
  } else if (error instanceof APIError) {
    console.error('API error:', error.message);
  } else {
    console.error('Unexpected error:', error.message);
  }
}
```

### Payment with Metadata

```typescript
const result = await saga.pay({
  merchantAddress: '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6',
  amount: '25.50',
  metadata: {
    orderId: 'ORDER-123',
    description: 'Premium subscription',
    customerEmail: 'customer@example.com',
    productId: 'PROD-456'
  },
  callbackUrl: 'https://myapp.com/webhook'
});
```

## Supported Wallets

The SDK works with any ethers.js compatible wallet:

- **MetaMask** - Browser extension
- **Coinbase Wallet** - Browser extension
- **Any ethers.js provider** - Custom wallet implementations

## Network Support

- **Sandbox (Sepolia)** - Testnet for development
  - Chain ID: 11155111
  - USDC: `0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238`
  - Router: `0xf7F058f7d65a7C799cc2AF7A4d202B9A690fAeA9`
- **Production (Mainnet)** - Live Ethereum network
  - Chain ID: 1
  - USDC: `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48`
  - Router: `0x88849eE1DABB713D240c34A365D71F0AEF50c218`

## Error Types

The SDK provides specific error types for different failure scenarios:

- **WalletError** - Wallet-related errors (user rejection, network issues)
- **PaymentError** - Payment-related errors (invalid amounts, addresses)
- **APIError** - API-related errors (network failures, server errors)
- **SagaError** - Generic SDK errors

## Development

### Building

```bash
npm run build
```

### Testing

```bash
npm test
npm run test:coverage
```

### Linting

```bash
npm run lint
npm run lint:fix
```

## License

Apache-2.0 - See [LICENSE](./LICENSE) file for details