Skip to content

Latest commit

 

History

History
782 lines (654 loc) · 18.1 KB

File metadata and controls

782 lines (654 loc) · 18.1 KB

Integration Guide

This guide provides step-by-step instructions for integrating the AffixIO Verification SDK into various payment platforms and agentic workflows.

Table of Contents

  1. Express.js / Node.js REST API
  2. AWS Lambda + API Gateway
  3. Payment Processor (Stripe-like)
  4. Agentic Commerce Platform
  5. Compliance & Audit

Express.js / Node.js REST API

Setup

npm install express @affix-io/verification-sdk

Create Middleware

// middleware/affixio.ts
import { Request, Response, NextFunction } from 'express';
import {
  AffixioClient,
  AffixioValidationError,
  AffixioApiError,
} from '@affix-io/verification-sdk';

export const affixioClient = AffixioClient.fromEnv();

export interface VerificationRequest extends Request {
  verificationResult?: {
    eligible: boolean;
    proof: string;
    circuitId: string;
  };
}

export async function requireIssuerAuth(
  req: VerificationRequest,
  res: Response,
  next: NextFunction
) {
  try {
    const { agentId, accountId, consentRef, amount, currency, merchantCat } =
      req.body;

    const result = await affixioClient.verifyIssuerAgentAuthorization({
      agentId,
      accountId,
      consentReference: consentRef,
      amount,
      currency,
      merchantCategory: merchantCat,
    });

    if (!result.eligible) {
      return res.status(403).json({
        error: 'Agent not authorized',
        circuitId: result.circuit_id,
        proof: result.proof,
      });
    }

    req.verificationResult = {
      eligible: result.eligible,
      proof: result.proof,
      circuitId: result.circuit_id,
    };

    next();
  } catch (error) {
    if (error instanceof AffixioValidationError) {
      return res.status(400).json({
        error: 'Invalid verification request',
        field: error.field,
      });
    }
    if (error instanceof AffixioApiError) {
      return res.status(502).json({
        error: 'Verification service error',
        requestId: error.requestId,
      });
    }
    return res.status(500).json({ error: 'Internal server error' });
  }
}

Use in Routes

// routes/payments.ts
import express from 'express';
import { requireIssuerAuth } from '../middleware/affixio.js';

const router = express.Router();

router.post('/authorize-payment', requireIssuerAuth, async (req, res) => {
  const { agentId, amount } = req.body;
  const { proof, circuitId } = req.verificationResult!;

  // Save transaction with proof
  const tx = await saveTransaction({
    agentId,
    amount,
    verificationProof: proof,
    circuitId,
    timestamp: new Date(),
  });

  res.json({
    status: 'authorized',
    transactionId: tx.id,
    proof,
  });
});

export default router;

AWS Lambda + API Gateway

Setup

npm install @affix-io/verification-sdk

Lambda Handler

// handlers/verifyPayment.ts
import {
  APIGatewayProxyHandler,
  APIGatewayProxyResult,
} from 'aws-lambda';
import {
  AffixioClient,
  AffixioApiError,
  AffixioTransportError,
} from '@affix-io/verification-sdk';

const client = AffixioClient.fromEnv();

export const handler: APIGatewayProxyHandler = async (
  event
): Promise<APIGatewayProxyResult> => {
  try {
    const body = JSON.parse(event.body || '{}');
    const {
      agentId,
      accountId,
      consentRef,
      amount,
      currency,
      merchantCategory,
    } = body;

    // Verify with AffixIO
    const result = await client.verifyIssuerAgentAuthorization({
      agentId,
      accountId,
      consentReference: consentRef,
      amount,
      currency,
      merchantCategory,
    });

    if (!result.eligible) {
      return {
        statusCode: 403,
        body: JSON.stringify({
          error: 'Not eligible',
          proof: result.proof,
        }),
      };
    }

    // Log to DynamoDB for audit trail
    await dynamoDb.putItem({
      TableName: 'VerificationAudit',
      Item: {
        requestId: event.requestContext.requestId,
        agentId,
        eligible: result.eligible,
        proof: result.proof,
        timestamp: new Date().toISOString(),
      },
    });

    return {
      statusCode: 200,
      body: JSON.stringify({
        eligible: result.eligible,
        proof: result.proof,
        latency: result.latency_ms,
      }),
    };
  } catch (error) {
    console.error('Error:', error);

    if (error instanceof AffixioTransportError) {
      // Transient error, Lambda will retry
      return {
        statusCode: 503,
        body: JSON.stringify({ error: 'Service temporarily unavailable' }),
      };
    }

    if (error instanceof AffixioApiError) {
      // Log for investigation
      console.error('AffixIO API error', {
        status: error.status,
        requestId: error.requestId,
      });

      return {
        statusCode: 502,
        body: JSON.stringify({
          error: 'Verification service error',
          requestId: error.requestId,
        }),
      };
    }

    return {
      statusCode: 500,
      body: JSON.stringify({ error: 'Internal server error' }),
    };
  }
};

Deployment

# Build
npm run build

# Deploy with CloudFormation or Serverless Framework
serverless deploy

Payment Processor (Stripe-like)

Payment Authorization Flow

// services/paymentProcessor.ts
import {
  AffixioClient,
  AffixioApiError,
  AffixioTransportError,
} from '@affix-io/verification-sdk';

interface PaymentRequest {
  agentId: string;
  accountId: string;
  customerId: string;
  amount: number;
  currency: string;
  merchantId: string;
  merchantCategory: string;
  consentReference: string;
}

interface PaymentResult {
  status: 'approved' | 'declined' | 'error';
  transactionId?: string;
  verificationProof?: string;
  reason?: string;
}

const affixioClient = AffixioClient.fromEnv();

export async function authorizeAgentPayment(
  req: PaymentRequest
): Promise<PaymentResult> {
  const startTime = Date.now();

  try {
    // Step 1: Verify issuer authorization
    const issuerAuthResult = await affixioClient.verifyIssuerAgentAuthorization({
      agentId: req.agentId,
      accountId: req.accountId,
      consentReference: req.consentReference,
      amount: req.amount,
      currency: req.currency,
      merchantCategory: req.merchantCategory,
      merchantId: req.merchantId,
    });

    if (!issuerAuthResult.eligible) {
      await logPaymentAttempt({
        ...req,
        status: 'declined',
        reason: 'issuer_auth_failed',
        proof: issuerAuthResult.proof,
        duration: Date.now() - startTime,
      });

      return {
        status: 'declined',
        reason: 'Agent not authorized by issuer',
        verificationProof: issuerAuthResult.proof,
      };
    }

    // Step 2: Check merchant eligibility
    const merchantCheckResult = await affixioClient.verifyMerchantAgentCheckout({
      agentId: req.agentId,
      customerId: req.customerId,
      basketTotalCents: req.amount,
      currency: req.currency,
      merchantId: req.merchantId,
      merchantCategory: req.merchantCategory,
    });

    if (!merchantCheckResult.eligible) {
      await logPaymentAttempt({
        ...req,
        status: 'declined',
        reason: 'merchant_check_failed',
        proof: merchantCheckResult.proof,
        duration: Date.now() - startTime,
      });

      return {
        status: 'declined',
        reason: 'Merchant checkout not eligible',
        verificationProof: merchantCheckResult.proof,
      };
    }

    // Step 3: Process payment with Stripe / payment processor
    const txId = await chargePaymentProcessor(req);

    await logPaymentAttempt({
      ...req,
      status: 'approved',
      transactionId: txId,
      issuerProof: issuerAuthResult.proof,
      merchantProof: merchantCheckResult.proof,
      duration: Date.now() - startTime,
    });

    return {
      status: 'approved',
      transactionId: txId,
      verificationProof: issuerAuthResult.proof,
    };
  } catch (error) {
    console.error('Payment authorization error:', error);

    if (error instanceof AffixioTransportError) {
      // Network issue; may retry or fallback
      await logPaymentAttempt({
        ...req,
        status: 'error',
        reason: 'verification_network_error',
        error: error.message,
        duration: Date.now() - startTime,
      });

      return {
        status: 'error',
        reason: 'Verification service unavailable; please retry',
      };
    }

    if (error instanceof AffixioApiError) {
      // API error; log for investigation
      console.error('AffixIO API error', {
        status: error.status,
        requestId: error.requestId,
      });

      return {
        status: 'error',
        reason: `Verification error (${error.status})`,
      };
    }

    return {
      status: 'error',
      reason: 'Internal error; please retry',
    };
  }
}

async function chargePaymentProcessor(req: PaymentRequest): Promise<string> {
  // Call Stripe / payment provider
  // ...
  return 'txn_12345';
}

async function logPaymentAttempt(data: Record<string, unknown>) {
  // Log to database / analytics
  console.log('[Payment Audit]', JSON.stringify(data));
}

Agentic Commerce Platform

Multi-Circuit Verification for High-Value Orders

// services/agentCommerceVerification.ts
import {
  AffixioClient,
  AgentPaymentCompositeResponse,
} from '@affix-io/verification-sdk';

interface OrderVerificationRequest {
  agentId: string;
  accountId: string;
  consentReference: string;
  orderItems: Array<{
    sku: string;
    quantity: number;
    priceCents: number;
    category: string;
  }>;
  merchantId: string;
  shippingCountry: string;
  totalCents: number;
  currency: string;
}

interface OrderDecision {
  approved: boolean;
  verificationDetails: AgentPaymentCompositeResponse;
  recommendation: 'approve' | 'review' | 'decline';
  reason?: string;
}

const affixioClient = AffixioClient.fromEnv();

export async function verifyAgentOrder(
  req: OrderVerificationRequest
): Promise<OrderDecision> {
  // For high-value orders, run composite verification
  const isHighValue = req.totalCents > 50000; // $500+

  const circuits = isHighValue
    ? [
        'agentic-payment-permission',
        'finance-account-standing',
        'finance-fraud-indicator',
      ]
    : ['agentic-payment-permission'];

  const result = await affixioClient.verifyAgentPaymentComposite({
    agentId: req.agentId,
    accountId: req.accountId,
    consentReference: req.consentReference,
    transactionContext: {
      amount_cents: req.totalCents,
      currency: req.currency,
      merchant_category: determineMerchantCategory(req.orderItems),
      merchant_id: req.merchantId,
      order_items_count: req.orderItems.length,
      shipping_country: req.shippingCountry,
      is_high_value: isHighValue,
    },
    circuits,
  });

  // Decide on order
  let recommendation: 'approve' | 'review' | 'decline' = 'approve';
  let reason: string | undefined;

  if (!result.overallEligible) {
    const failedCircuits = result.perCircuit.filter((c) => !c.eligible);
    recommendation = 'decline';
    reason = `Failed circuits: ${failedCircuits.map((c) => c.circuitId).join(', ')}`;
  } else if (isHighValue && result.perCircuit.length < 3) {
    // High-value but only partial verification
    recommendation = 'review';
    reason = 'High-value order with incomplete verification';
  }

  await logOrderVerification({
    agentId: req.agentId,
    totalCents: req.totalCents,
    recommendation,
    verificationProof: result.aggregatedProof,
    perCircuit: result.perCircuit,
    timestamp: new Date().toISOString(),
  });

  return {
    approved: result.overallEligible,
    verificationDetails: result,
    recommendation,
    reason,
  };
}

function determineMerchantCategory(
  items: Array<{ category: string }>
): string {
  // Infer primary category from items
  const categories = items.map((i) => i.category);
  const primary = categories[0] || 'general_merchandise';
  return primary;
}

async function logOrderVerification(data: Record<string, unknown>) {
  console.log('[Order Verification]', JSON.stringify(data));
  // Store in audit log / database
}

Compliance & Audit

Proof Verification and Audit Trail

// services/auditTrail.ts
import { VerifyResponse } from '@affix-io/verification-sdk';

interface AuditRecord {
  id: string;
  timestamp: Date;
  agentId: string;
  circuitId: string;
  eligible: boolean;
  proof: string;
  requestMetadata?: Record<string, unknown>;
  transactionId?: string;
}

export class AuditTrail {
  async recordVerification(
    tx: VerifyResponse,
    agentId: string,
    metadata?: Record<string, unknown>
  ): Promise<AuditRecord> {
    const record: AuditRecord = {
      id: generateAuditId(),
      timestamp: new Date(),
      agentId,
      circuitId: tx.circuit_id,
      eligible: tx.eligible,
      proof: tx.proof,
      requestMetadata: metadata,
    };

    // Store in immutable log (e.g., database, S3 with versioning)
    await this.store(record);

    return record;
  }

  async verifyProof(auditId: string, proof: string): Promise<boolean> {
    const record = await this.retrieve(auditId);
    return record && record.proof === proof;
  }

  async getAuditTrail(
    agentId: string,
    startDate: Date,
    endDate: Date
  ): Promise<AuditRecord[]> {
    return this.query({
      agentId,
      timestamp: { $gte: startDate, $lte: endDate },
    });
  }

  private async store(record: AuditRecord): Promise<void> {
    // TODO: Implement database storage
    // e.g., PostgreSQL INSERT, DynamoDB PutItem, etc.
    console.log('[Audit]', JSON.stringify(record));
  }

  private async retrieve(id: string): Promise<AuditRecord | null> {
    // TODO: Implement database retrieval
    return null;
  }

  private async query(
    filter: Record<string, unknown>
  ): Promise<AuditRecord[]> {
    // TODO: Implement database query
    return [];
  }
}

function generateAuditId(): string {
  return `audit_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
}

Compliance Report

// services/complianceReport.ts
import { AuditTrail } from './auditTrail.js';

export async function generateComplianceReport(
  startDate: Date,
  endDate: Date
) {
  const auditTrail = new AuditTrail();

  // Collect all verification records
  const records = await auditTrail.getAuditTrail('all-agents', startDate, endDate);

  const summary = {
    period: {
      start: startDate.toISOString(),
      end: endDate.toISOString(),
    },
    totalVerifications: records.length,
    eligible: records.filter((r) => r.eligible).length,
    declined: records.filter((r) => !r.eligible).length,
    byCircuit: groupByCircuit(records),
    byAgent: groupByAgent(records),
    allProofsPresent: records.every((r) => r.proof),
  };

  return summary;
}

function groupByCircuit(records: Array<{ circuitId: string; eligible: boolean }>) {
  const groups: Record<string, { total: number; eligible: number }> = {};

  records.forEach((r) => {
    if (!groups[r.circuitId]) {
      groups[r.circuitId] = { total: 0, eligible: 0 };
    }
    groups[r.circuitId].total += 1;
    if (r.eligible) {
      groups[r.circuitId].eligible += 1;
    }
  });

  return groups;
}

function groupByAgent(records: Array<{ agentId: string; eligible: boolean }>) {
  const groups: Record<string, { total: number; eligible: number }> = {};

  records.forEach((r) => {
    if (!groups[r.agentId]) {
      groups[r.agentId] = { total: 0, eligible: 0 };
    }
    groups[r.agentId].total += 1;
    if (r.eligible) {
      groups[r.agentId].eligible += 1;
    }
  });

  return groups;
}

Retry & Fallback Strategy

// utils/retryStrategy.ts
import {
  AffixioTransportError,
  AffixioApiError,
  AffixioClient,
} from '@affix-io/verification-sdk';

export interface RetryOptions {
  maxAttempts: number;
  initialDelayMs: number;
  maxDelayMs: number;
  backoffMultiplier: number;
}

const defaultRetryOptions: RetryOptions = {
  maxAttempts: 3,
  initialDelayMs: 100,
  maxDelayMs: 5000,
  backoffMultiplier: 2,
};

export async function withRetry<T>(
  fn: () => Promise<T>,
  options: RetryOptions = defaultRetryOptions
): Promise<T> {
  let lastError: Error | undefined;
  let delay = options.initialDelayMs;

  for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error instanceof Error ? error : new Error(String(error));

      // Check if error is retryable
      if (!isRetryable(error)) {
        throw error;
      }

      if (attempt < options.maxAttempts) {
        console.warn(
          `Attempt ${attempt} failed, retrying in ${delay}ms...`,
          lastError.message
        );
        await new Promise((resolve) => setTimeout(resolve, delay));
        delay = Math.min(delay * options.backoffMultiplier, options.maxDelayMs);
      }
    }
  }

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

function isRetryable(error: unknown): boolean {
  if (error instanceof AffixioTransportError) {
    return true; // Network errors are retryable
  }

  if (error instanceof AffixioApiError) {
    // Retry 5xx errors
    return error.status >= 500;
  }

  return false;
}

// Usage:
const client = AffixioClient.fromEnv();

await withRetry(
  () =>
    client.verifyIssuerAgentAuthorization({
      agentId: 'agent:001',
      accountId: 'acct:123',
      consentReference: 'consent:abc',
      amount: 1000,
      currency: 'USD',
      merchantCategory: 'software_subscription',
    }),
  {
    maxAttempts: 3,
    initialDelayMs: 100,
    maxDelayMs: 5000,
  }
);

Summary

The SDK integrates seamlessly into:

  • REST APIs: Middleware for Express, etc.
  • Serverless: AWS Lambda, etc.
  • Payment systems: Authorization flows with Stripe, etc.
  • Agentic platforms: Multi-circuit verification for complex decisions
  • Compliance: Audit trails and reporting

Always:

  1. Log proofs for audit trails
  2. Distinguish between retryable (transport) and non-retryable (validation) errors
  3. Implement proper timeout and retry strategies
  4. Store verification results with transaction records