Production Deployment

This tutorial covers best practices for deploying T402 payment integration in production environments.

Deployment Checklist

Before going to production:

  • Security audit completed
  • Private keys secured in secret manager
  • Monitoring and alerting configured
  • Rate limiting enabled
  • Error handling tested
  • Backup and recovery procedures documented
  • Load testing performed

Infrastructure Architecture

                    ┌─────────────────────────────────┐
                    │         Load Balancer           │
                    │      (SSL Termination)          │
                    └───────────────┬─────────────────┘

              ┌─────────────────────┼─────────────────────┐
              │                     │                     │
              ▼                     ▼                     ▼
       ┌────────────┐        ┌────────────┐        ┌────────────┐
       │   API-1    │        │   API-2    │        │   API-3    │
       │  (t402)    │        │  (t402)    │        │  (t402)    │
       └─────┬──────┘        └─────┬──────┘        └─────┬──────┘
             │                     │                     │
             └─────────────────────┼─────────────────────┘

              ┌────────────────────┼────────────────────┐
              │                    │                    │
              ▼                    ▼                    ▼
       ┌────────────┐       ┌────────────┐       ┌────────────┐
       │   Redis    │       │ PostgreSQL │       │ Facilitator│
       │  (Cache)   │       │   (Data)   │       │  (t402.io) │
       └────────────┘       └────────────┘       └────────────┘

Secure Secret Management

Never hardcode secrets. Use a secret manager:

import { SecretsManager } from '@aws-sdk/client-secrets-manager';
 
const client = new SecretsManager({ region: 'us-east-1' });
 
async function getSecrets() {
  const response = await client.getSecretValue({
    SecretId: 't402-production-secrets',
  });
 
  return JSON.parse(response.SecretString);
}
 
// Usage
const secrets = await getSecrets();
const facilitator = createFacilitatorClient({
  url: 'https://facilitator.t402.io',
  apiKey: secrets.T402_API_KEY,
});

Configure Rate Limiting

Protect your API from abuse:

import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
import { createClient } from 'redis';
 
const redis = createClient({ url: process.env.REDIS_URL });
 
const limiter = rateLimit({
  store: new RedisStore({
    client: redis,
    prefix: 't402:rate-limit:',
  }),
  windowMs: 60 * 1000, // 1 minute
  max: 100, // 100 requests per minute
  standardHeaders: true,
  legacyHeaders: false,
  handler: (req, res) => {
    res.status(429).json({
      error: 'Too many requests',
      retryAfter: res.getHeader('Retry-After'),
    });
  },
});
 
app.use('/api/', limiter);

Implement Health Checks

app.get('/health', (req, res) => {
  res.json({ status: 'ok' });
});
 
app.get('/ready', async (req, res) => {
  try {
    // Check Redis
    await redis.ping();
 
    // Check Facilitator
    const supported = await facilitator.getSupported();
 
    res.json({
      status: 'ready',
      checks: {
        redis: 'ok',
        facilitator: 'ok',
        networks: supported.kinds.length,
      },
    });
  } catch (error) {
    res.status(503).json({
      status: 'not ready',
      error: error.message,
    });
  }
});

Set Up Monitoring

import promClient from 'prom-client';
 
// Metrics
const paymentCounter = new promClient.Counter({
  name: 't402_payments_total',
  help: 'Total payment requests',
  labelNames: ['network', 'status'],
});
 
const paymentDuration = new promClient.Histogram({
  name: 't402_payment_duration_seconds',
  help: 'Payment processing duration',
  labelNames: ['network'],
  buckets: [0.1, 0.5, 1, 2, 5, 10],
});
 
// Middleware to track payments
function trackPayments(req, res, next) {
  const start = Date.now();
 
  res.on('finish', () => {
    const duration = (Date.now() - start) / 1000;
    const network = req.paymentNetwork || 'unknown';
    const status = res.statusCode < 400 ? 'success' : 'error';
 
    paymentCounter.inc({ network, status });
    paymentDuration.observe({ network }, duration);
  });
 
  next();
}
 
// Metrics endpoint
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', promClient.register.contentType);
  res.send(await promClient.register.metrics());
});

Configure Alerting

# alertmanager.yml
groups:
  - name: t402
    rules:
      - alert: HighPaymentFailureRate
        expr: rate(t402_payments_total{status="error"}[5m]) > 0.1
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "High payment failure rate"
          description: "Payment failure rate is {{ $value }} per second"
 
      - alert: FacilitatorUnreachable
        expr: probe_success{job="facilitator"} == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Facilitator is unreachable"

Implement Error Handling

import { PaymentError, NetworkError, SettlementError } from '@t402/core';
 
// Global error handler
app.use((err, req, res, next) => {
  // Log error
  logger.error('Request error', {
    error: err.message,
    stack: err.stack,
    path: req.path,
    method: req.method,
  });
 
  // Handle specific errors
  if (err instanceof PaymentError) {
    return res.status(402).json({
      error: 'Payment failed',
      code: err.code,
      message: err.message,
    });
  }
 
  if (err instanceof NetworkError) {
    return res.status(503).json({
      error: 'Network error',
      message: 'Please try again later',
    });
  }
 
  // Generic error
  res.status(500).json({
    error: 'Internal server error',
    requestId: req.id,
  });
});
 
// Retry with exponential backoff
async function withRetry<T>(
  fn: () => Promise<T>,
  maxRetries = 3,
  baseDelay = 1000,
): Promise<T> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      const delay = baseDelay * Math.pow(2, i);
      await new Promise(r => setTimeout(r, delay));
    }
  }
  throw new Error('Max retries exceeded');
}

Database Schema

Store payment records:

CREATE TABLE payments (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    idempotency_key VARCHAR(64) UNIQUE,
    network VARCHAR(50) NOT NULL,
    amount VARCHAR(78) NOT NULL,
    payer_address VARCHAR(100) NOT NULL,
    recipient_address VARCHAR(100) NOT NULL,
    transaction_hash VARCHAR(100),
    status VARCHAR(20) NOT NULL DEFAULT 'pending',
    resource_path VARCHAR(255),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    settled_at TIMESTAMP,
    metadata JSONB
);
 
CREATE INDEX idx_payments_network ON payments(network);
CREATE INDEX idx_payments_status ON payments(status);
CREATE INDEX idx_payments_created_at ON payments(created_at);

Load Testing

Test your deployment under load:

// k6 load test script
import http from 'k6/http';
import { check, sleep } from 'k6';
 
export const options = {
  stages: [
    { duration: '1m', target: 50 },  // Ramp up
    { duration: '3m', target: 50 },  // Sustain
    { duration: '1m', target: 100 }, // Peak
    { duration: '1m', target: 0 },   // Ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'], // 95% under 500ms
    http_req_failed: ['rate<0.01'],   // <1% errors
  },
};
 
export default function () {
  const res = http.get('https://api.example.com/api/premium/data', {
    headers: {
      'Payment-Signature': VALID_PAYMENT_HEADER,
    },
  });
 
  check(res, {
    'status is 200': (r) => r.status === 200,
    'response time < 500ms': (r) => r.timings.duration < 500,
  });
 
  sleep(1);
}

Kubernetes Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: t402-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: t402-api
  template:
    metadata:
      labels:
        app: t402-api
    spec:
      containers:
        - name: api
          image: your-registry/t402-api:latest
          ports:
            - containerPort: 3000
          env:
            - name: NODE_ENV
              value: production
            - name: T402_FACILITATOR_URL
              value: https://facilitator.t402.io
          envFrom:
            - secretRef:
                name: t402-secrets
          resources:
            requests:
              memory: "256Mi"
              cpu: "200m"
            limits:
              memory: "512Mi"
              cpu: "500m"
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 10
            periodSeconds: 30
          readinessProbe:
            httpGet:
              path: /ready
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 10

Rollback Procedures

Document and test rollback:

#!/bin/bash
# rollback.sh
 
# Get previous deployment
PREVIOUS=$(kubectl rollout history deployment/t402-api -o jsonpath='{.metadata.annotations.deployment\.kubernetes\.io/revision}')
PREVIOUS=$((PREVIOUS - 1))
 
# Rollback
kubectl rollout undo deployment/t402-api --to-revision=$PREVIOUS
 
# Wait and verify
kubectl rollout status deployment/t402-api
curl -f https://api.example.com/health || exit 1
 
echo "Rollback complete"