Protecting an API with payments

Gate your API endpoints behind on-chain payments. Users pay once, then access is automatically unlocked based on payment verification.

1

Create a payment endpoint

Create an API endpoint that generates a payment request. This endpoint should create a payment with metadata that identifies the user and the resource they're paying for.

typescript
// app/api/payments/create/route.ts
import { ZynPayClient, Environment } from '@zyntrialabs/zynpay-sdk'
import { NextRequest, NextResponse } from 'next/server'

export async function POST(request: NextRequest) {
  const { userId, resourceId, amount } = await request.json()
  
  const client = new ZynPayClient({
    merchantWallet: process.env.MERCHANT_WALLET_ADDRESS!,
    environment: Environment.TESTNET,
    defaultChain: 'base',
  })
  
  // Create payment with metadata
  const payment = client.createPayment(amount, 'base', {
    userId,
    resourceId,
    description: `Access to resource ${resourceId}`,
    metadata: {
      userId,
      resourceId,
      type: 'api_access',
    },
  })
  
  // Store payment in your database
  await db.payments.create({
    paymentId: payment.id,
    userId,
    resourceId,
    amount,
    status: 'pending',
  })
  
  return NextResponse.json({
    paymentId: payment.id,
    amount: payment.amount,
    merchantAddress: payment.merchantAddress,
  })
}
2

Create a payment verification middleware

Create middleware that checks if a user has a valid payment for the requested resource before allowing API access.

typescript
// middleware/payment-verification.ts
import { ZynPayClient, Environment } from '@zyntrialabs/zynpay-sdk'
import { JsonRpcProvider } from 'ethers'

export async function verifyPaymentAccess(
  userId: string,
  resourceId: string,
  txHash?: string
): Promise<boolean> {
  // Check database for completed payment
  const payment = await db.payments.findFirst({
    where: {
      userId,
      resourceId,
      status: 'completed',
    },
  })
  
  if (!payment) {
    return false
  }
  
  // Optionally verify on-chain if txHash provided
  if (txHash) {
    const provider = new JsonRpcProvider('https://sepolia.base.org')
    const receipt = await provider.getTransactionReceipt(txHash)
    
    if (!receipt || receipt.status !== 1) {
      return false
    }
  }
  
  return true
}

// Usage in API route
export async function GET(request: NextRequest) {
  const userId = request.headers.get('x-user-id')
  const resourceId = request.nextUrl.searchParams.get('resourceId')
  
  if (!userId || !resourceId) {
    return NextResponse.json({ error: 'Missing parameters' }, { status: 400 })
  }
  
  // Verify payment
  const hasAccess = await verifyPaymentAccess(userId, resourceId)
  
  if (!hasAccess) {
    return NextResponse.json(
      { error: 'Payment required', paymentUrl: '/pay?resourceId=' + resourceId },
      { status: 402 }
    )
  }
  
  // User has paid, grant access
  return NextResponse.json({ data: 'Your protected resource data' })
}
3

Handle payment webhooks

Set up a webhook endpoint that automatically updates payment status when transactions are confirmed on-chain.

typescript
// app/api/webhooks/payment/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { ZynPayClient, Environment } from '@zyntrialabs/zynpay-sdk'

export async function POST(request: NextRequest) {
  const { paymentId, txHash, status } = await request.json()
  
  // Verify webhook signature (implement your own verification)
  // const isValid = verifyWebhookSignature(request)
  // if (!isValid) return NextResponse.json({ error: 'Invalid signature' }, { status: 401 })
  
  // Update payment status in database
  await db.payments.update({
    where: { paymentId },
    data: {
      status: status === 'completed' ? 'completed' : 'failed',
      txHash,
      completedAt: status === 'completed' ? new Date() : null,
    },
  })
  
  // If payment completed, unlock access
  if (status === 'completed') {
    const payment = await db.payments.findUnique({ where: { paymentId } })
    
    // Unlock user access
    await unlockUserAccess(payment.userId, payment.resourceId)
    
    // Send notification
    await sendNotification(payment.userId, 'Payment confirmed! Access unlocked.')
  }
  
  return NextResponse.json({ success: true })
}

async function unlockUserAccess(userId: string, resourceId: string) {
  // Your business logic: update user permissions, grant API access, etc.
  await db.userAccess.create({
    userId,
    resourceId,
    grantedAt: new Date(),
  })
}

Example: Protecting a premium API endpoint

typescript
// app/api/premium/data/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { verifyPaymentAccess } from '@/middleware/payment-verification'

export async function GET(request: NextRequest) {
  const userId = request.headers.get('x-user-id')
  
  // Check if user has paid for premium access
  const hasAccess = await verifyPaymentAccess(userId, 'premium_api')
  
  if (!hasAccess) {
    return NextResponse.json(
      {
        error: 'Premium access required',
        message: 'Please complete payment to access this endpoint',
        paymentUrl: '/api/payments/create?resourceId=premium_api',
      },
      { status: 402 }
    )
  }
  
  // User has paid, return premium data
  return NextResponse.json({
    data: {
      // Your premium API response
      premiumFeature: true,
      exclusiveData: '...',
    },
  })
}