JavaScript/TypeScript

Complete examples for accepting x402 payments with Interface402 using JavaScript and TypeScript.

Installation

# No special installation needed! Just use fetch or your favorite HTTP library
npm install node-fetch  # If using Node.js < 18
npm install ws          # For WebSocket support

Basic Payment Verification

Verify a Payment

async function verifyPayment(
  paymentProof: string,
  expectedAmount: number,
  recipientAddress: string
) {
  const response = await fetch('https://api.interface402.dev/v1/payments/verify', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${process.env.API_KEY}`
    },
    body: JSON.stringify({
      payment_proof: paymentProof,
      amount: expectedAmount,
      recipient: recipientAddress
    })
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Verification failed: ${error.message}`);
  }

  const data = await response.json();
  return data;
}

// Usage
const result = await verifyPayment(
  'BASE64_ENCODED_PAYMENT_PROOF',
  1000000,
  'YOUR_WALLET_ADDRESS'
);

if (result.verified) {
  console.log('Payment verified!');
  console.log('Transaction ID:', result.transaction_id);
  console.log('Amount:', result.amount);
} else {
  console.log('Payment invalid');
}

Express.js Integration

Protect an API Endpoint

import express from 'express';

const app = express();
app.use(express.json());

// Helper function to verify payments
async function verifyPaymentWithInterface402(
  paymentProof: string,
  amount: number,
  recipient: string
) {
  const response = await fetch('https://api.interface402.dev/v1/payments/verify', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${process.env.API_KEY}`
    },
    body: JSON.stringify({
      payment_proof: paymentProof,
      amount,
      recipient
    })
  });

  const data = await response.json();
  return data.verified;
}

// Protected endpoint
app.get('/api/premium-content', async (req, res) => {
  const paymentProof = req.headers['x-payment-proof'] as string;
  const REQUIRED_AMOUNT = 1000000; // 0.001 SOL in lamports
  const MY_WALLET = 'YOUR_WALLET_ADDRESS';

  if (!paymentProof) {
    return res.status(402).json({
      error: 'Payment Required',
      amount: REQUIRED_AMOUNT,
      recipient: MY_WALLET,
      message: 'Please provide x-payment-proof header'
    });
  }

  try {
    const verified = await verifyPaymentWithInterface402(
      paymentProof,
      REQUIRED_AMOUNT,
      MY_WALLET
    );

    if (verified) {
      // Payment verified, return the content
      return res.json({
        data: 'Your premium content here',
        message: 'Access granted'
      });
    } else {
      return res.status(402).json({
        error: 'Invalid payment',
        message: 'Payment verification failed'
      });
    }
  } catch (error) {
    console.error('Payment verification error:', error);
    return res.status(500).json({
      error: 'Internal server error',
      message: 'Failed to verify payment'
    });
  }
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Middleware for Payment Protection

import { Request, Response, NextFunction } from 'express';

interface PaymentRequirement {
  amount: number;
  recipient: string;
}

function requirePayment(config: PaymentRequirement) {
  return async (req: Request, res: Response, next: NextFunction) => {
    const paymentProof = req.headers['x-payment-proof'] as string;

    if (!paymentProof) {
      return res.status(402).json({
        error: 'Payment Required',
        amount: config.amount,
        recipient: config.recipient
      });
    }

    try {
      const response = await fetch('https://api.interface402.dev/v1/payments/verify', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${process.env.API_KEY}`
        },
        body: JSON.stringify({
          payment_proof: paymentProof,
          amount: config.amount,
          recipient: config.recipient
        })
      });

      const data = await response.json();

      if (data.verified) {
        // Attach payment info to request for later use
        req.payment = data;
        next();
      } else {
        return res.status(402).json({
          error: 'Invalid payment'
        });
      }
    } catch (error) {
      return res.status(500).json({
        error: 'Payment verification failed'
      });
    }
  };
}

// Usage
app.get(
  '/api/paid-content',
  requirePayment({
    amount: 1000000,
    recipient: 'YOUR_WALLET_ADDRESS'
  }),
  (req, res) => {
    res.json({ data: 'Your paid content' });
  }
);

WebSocket for Real-Time Notifications

Basic WebSocket Connection

import WebSocket from 'ws';

class PaymentStream {
  private ws: WebSocket | null = null;
  private apiKey: string;

  constructor(apiKey: string) {
    this.apiKey = apiKey;
  }

  connect() {
    this.ws = new WebSocket('wss://api.interface402.dev/v1/payments/stream');

    this.ws.on('open', () => {
      console.log('Connected to payment stream');

      // Authenticate
      this.send({
        type: 'authenticate',
        apiKey: this.apiKey
      });

      // Subscribe to your wallet's payments
      this.send({
        type: 'subscribe',
        wallet: 'YOUR_WALLET_ADDRESS'
      });
    });

    this.ws.on('message', (data: string) => {
      const payment = JSON.parse(data);
      this.handlePayment(payment);
    });

    this.ws.on('error', (error) => {
      console.error('WebSocket error:', error);
    });

    this.ws.on('close', () => {
      console.log('Disconnected, reconnecting in 5s...');
      setTimeout(() => this.connect(), 5000);
    });
  }

  private send(data: any) {
    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(data));
    }
  }

  private handlePayment(payment: any) {
    console.log('Payment received:', {
      amount: payment.amount,
      from: payment.sender,
      transactionId: payment.transaction_id
    });

    // Do something with the payment
    // e.g., unlock content, send confirmation email, etc.
  }

  disconnect() {
    this.ws?.close();
  }
}

// Usage
const stream = new PaymentStream(process.env.API_KEY!);
stream.connect();

React Hook for Payment Notifications

import { useEffect, useState } from 'react';

interface Payment {
  transaction_id: string;
  amount: number;
  sender: string;
  timestamp: number;
}

export function usePaymentStream(apiKey: string, walletAddress: string) {
  const [payments, setPayments] = useState<Payment[]>([]);
  const [connected, setConnected] = useState(false);

  useEffect(() => {
    const ws = new WebSocket('wss://api.interface402.dev/v1/payments/stream');

    ws.onopen = () => {
      setConnected(true);

      ws.send(JSON.stringify({
        type: 'authenticate',
        apiKey
      }));

      ws.send(JSON.stringify({
        type: 'subscribe',
        wallet: walletAddress
      }));
    };

    ws.onmessage = (event) => {
      const payment = JSON.parse(event.data);
      setPayments(prev => [payment, ...prev]);
    };

    ws.onclose = () => {
      setConnected(false);
    };

    return () => ws.close();
  }, [apiKey, walletAddress]);

  return { payments, connected };
}

// Usage in component
function PaymentDashboard() {
  const { payments, connected } = usePaymentStream(
    process.env.NEXT_PUBLIC_API_KEY!,
    'YOUR_WALLET_ADDRESS'
  );

  return (
    <div>
      <div>Status: {connected ? '🟢 Connected' : '🔴 Disconnected'}</div>
      <h2>Recent Payments</h2>
      <ul>
        {payments.map(payment => (
          <li key={payment.transaction_id}>
            Received {payment.amount} from {payment.sender}
          </li>
        ))}
      </ul>
    </div>
  );
}

Error Handling

Robust Error Handling

class PaymentVerificationError extends Error {
  code: string;
  details: any;

  constructor(code: string, message: string, details: any = {}) {
    super(message);
    this.code = code;
    this.details = details;
  }
}

async function verifyPaymentWithRetry(
  paymentProof: string,
  amount: number,
  recipient: string,
  maxRetries: number = 3
) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch('https://api.interface402.dev/v1/payments/verify', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${process.env.API_KEY}`
        },
        body: JSON.stringify({
          payment_proof: paymentProof,
          amount,
          recipient
        })
      });

      const data = await response.json();

      if (!response.ok) {
        throw new PaymentVerificationError(
          data.error?.code || 'UNKNOWN_ERROR',
          data.error?.message || 'Verification failed',
          data.error?.details
        );
      }

      return data;
    } catch (error) {
      if (error instanceof PaymentVerificationError) {
        // Don't retry on certain errors
        if (error.code === 'INVALID_PAYMENT_PROOF' || error.code === 'INVALID_AMOUNT') {
          throw error;
        }

        // Retry on network errors or rate limits
        if (attempt < maxRetries - 1) {
          const delay = Math.pow(2, attempt) * 1000;
          console.log(`Retrying in ${delay}ms...`);
          await new Promise(resolve => setTimeout(resolve, delay));
          continue;
        }
      }
      throw error;
    }
  }

  throw new Error('Max retries exceeded');
}

Complete Example: API with Payments

import express from 'express';
import { PaymentStream } from './payment-stream';

const app = express();
app.use(express.json());

// In-memory cache of verified payments (use Redis in production)
const verifiedPayments = new Set<string>();

// Start payment stream to track incoming payments
const paymentStream = new PaymentStream(process.env.API_KEY!);
paymentStream.on('payment', (payment) => {
  verifiedPayments.add(payment.transaction_id);
  console.log('Payment received:', payment.transaction_id);
});
paymentStream.connect();

// Verify payment endpoint
app.post('/api/verify-payment', async (req, res) => {
  const { payment_proof, amount } = req.body;

  try {
    const response = await fetch('https://api.interface402.dev/v1/payments/verify', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.API_KEY}`
      },
      body: JSON.stringify({
        payment_proof,
        amount,
        recipient: 'YOUR_WALLET_ADDRESS'
      })
    });

    const data = await response.json();

    if (data.verified) {
      // Store the payment ID
      verifiedPayments.add(data.transaction_id);

      return res.json({
        success: true,
        transaction_id: data.transaction_id
      });
    }

    return res.status(402).json({
      success: false,
      error: 'Payment verification failed'
    });
  } catch (error) {
    return res.status(500).json({
      success: false,
      error: 'Internal server error'
    });
  }
});

// Protected content endpoint
app.get('/api/content/:id', async (req, res) => {
  const transactionId = req.headers['x-transaction-id'] as string;

  if (!transactionId || !verifiedPayments.has(transactionId)) {
    return res.status(402).json({
      error: 'Payment Required',
      message: 'Valid payment required to access this content'
    });
  }

  // Return the content
  res.json({
    content: 'Your premium content here'
  });
});

app.listen(3000);

Best Practices

  1. Environment Variables: Always store API keys in environment variables

  2. Error Handling: Implement retry logic with exponential backoff

  3. Logging: Log all payment verifications for debugging and reconciliation

  4. Validation: Always validate payment amounts match what you expect

  5. Security: Never expose API keys in client-side code

  6. Testing: Test with small amounts first before going live

Next Steps

Last updated