⚡
Milan.dev
>Home>Projects>Experience>Blog
GitHubLinkedIn
status: building
>Home>Projects>Experience>Blog
status: building

Connect

Let's collaborate on infrastructure challenges

Open to discussing DevOps strategies, cloud architecture optimization, security implementations, and interesting infrastructure problems.

send a message→

Find me elsewhere

GitHub
@milandangol
LinkedIn
/in/milan-dangol
Email
milandangol57@gmail.com
Forged with& code

© 2026 Milan Dangol — All systems reserved

back to blog
system-designfeatured

Payment Processing System at Scale: Stripe/Adyen Integration with AWS EventBridge, Lambda, and DynamoDB

Building a payment processing system handling millions of daily transactions - featuring EventBridge for event-driven orchestration, Lambda for serverless processing, DynamoDB for transaction state, idempotency guarantees, and real-time fraud detection with Kinesis.

M

Milan Dangol

Sr DevOps & DevSecOps Engineer

Jun 8, 2025
13 min read

Introduction

Processing payments at scale isn't just about calling a payment gateway API - it's about building a resilient, event-driven system that guarantees exactly-once processing, handles failures gracefully, and provides real-time visibility. I built a payment platform processing 3M+ transactions daily across Stripe and Adyen.

The system delivers:

  • 99.99% transaction success rate with automatic retries
  • Exactly-once processing guarantees with idempotency
  • Sub-500ms payment initiation at P95
  • Real-time fraud detection preventing $2M+ monthly losses

Architecture Overview

flowchart TB subgraph Client["Client Applications"] WEB[Web Checkout] MOBILE[Mobile App] API[Partner APIs] end subgraph Gateway["API Gateway Layer"] APIGW[API Gateway] AUTH[Auth & Rate Limiting] VALIDATE[Request Validation] end subgraph Orchestration["Event Orchestration"] EB[EventBridge<br/>Central Event Bus] RULES[Event Rules & Routing] end subgraph Processing["Payment Processing"] LAMBDA_INIT[Lambda: Initiate Payment] LAMBDA_PROCESS[Lambda: Process Payment] LAMBDA_WEBHOOK[Lambda: Webhook Handler] SF[Step Functions<br/>Complex Workflows] end subgraph PaymentGateways["Payment Gateways"] STRIPE[Stripe API] ADYEN[Adyen API] PAYPAL[PayPal API] end subgraph State["State Management"] DDB[DynamoDB: Transactions] IDEMPOTENCY[DynamoDB: Idempotency] CACHE[ElastiCache: Session Data] end subgraph Analytics["Analytics & Fraud"] KINESIS[Kinesis Data Stream] FRAUD[Lambda: Fraud Detection] ANALYTICS[Kinesis Analytics] end subgraph Notification["Notifications"] SNS[SNS Topics] SQS[SQS: Dead Letter Queue] end Client --> APIGW APIGW --> AUTH AUTH --> VALIDATE VALIDATE --> EB EB --> RULES RULES --> LAMBDA_INIT LAMBDA_INIT --> DDB LAMBDA_INIT --> IDEMPOTENCY LAMBDA_INIT --> PaymentGateways PaymentGateways --> LAMBDA_WEBHOOK LAMBDA_WEBHOOK --> EB EB --> LAMBDA_PROCESS LAMBDA_PROCESS --> DDB LAMBDA_PROCESS --> KINESIS KINESIS --> FRAUD KINESIS --> ANALYTICS LAMBDA_PROCESS --> SNS FRAUD --> SNS style Gateway fill:#1a1a2e,stroke:#00d9ff,stroke-width:2px,color:#fff style Orchestration fill:#f77f00,stroke:#fff,stroke-width:2px,color:#fff style Processing fill:#264653,stroke:#2a9d8f,stroke-width:2px,color:#fff style State fill:#9b5de5,stroke:#fff,stroke-width:2px,color:#fff style Analytics fill:#e63946,stroke:#fff,stroke-width:2px,color:#fff

Payment Initiation with Idempotency

// lambda/payment-initiation/index.ts

import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { DynamoDB, EventBridge } from 'aws-sdk';
import Stripe from 'stripe';
import { v4 as uuidv4 } from 'uuid';
import crypto from 'crypto';

const dynamodb = new DynamoDB.DocumentClient();
const eventBridge = new EventBridge();
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2023-10-16'
});

const TRANSACTIONS_TABLE = process.env.TRANSACTIONS_TABLE!;
const IDEMPOTENCY_TABLE = process.env.IDEMPOTENCY_TABLE!;
const EVENT_BUS = process.env.EVENT_BUS_NAME!;

interface PaymentRequest {
  amount: number;
  currency: string;
  customerId: string;
  paymentMethodId: string;
  metadata: {
    orderId: string;
    productIds: string[];
  };
}

export const handler = async (
  event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {

  try {
    const body: PaymentRequest = JSON.parse(event.body || '{}');
    const idempotencyKey = event.headers['idempotency-key'] || generateIdempotencyKey(body);

    // Check idempotency
    const existingTransaction = await checkIdempotency(idempotencyKey);
    if (existingTransaction) {
      return {
        statusCode: 200,
        body: JSON.stringify({
          transactionId: existingTransaction.transactionId,
          status: existingTransaction.status,
          cached: true
        })
      };
    }

    // Validate request
    await validatePaymentRequest(body);

    // Check fraud
    const fraudScore = await checkFraudScore(body);
    if (fraudScore > 80) {
      throw new Error('Transaction flagged as high risk');
    }

    // Create transaction record
    const transactionId = uuidv4();
    await createTransaction({
      transactionId,
      ...body,
      status: 'pending',
      fraudScore,
      createdAt: Date.now()
    });

    // Store idempotency record
    await storeIdempotencyRecord(idempotencyKey, transactionId);

    // Publish event to EventBridge
    await publishPaymentEvent({
      eventType: 'payment.initiated',
      transactionId,
      amount: body.amount,
      currency: body.currency,
      customerId: body.customerId,
      metadata: body.metadata
    });

    // Initiate payment with Stripe
    const paymentIntent = await stripe.paymentIntents.create({
      amount: body.amount,
      currency: body.currency,
      customer: body.customerId,
      payment_method: body.paymentMethodId,
      confirm: true,
      metadata: {
        transactionId,
        orderId: body.metadata.orderId
      }
    }, {
      idempotencyKey
    });

    // Update transaction with payment intent ID
    await updateTransaction(transactionId, {
      paymentIntentId: paymentIntent.id,
      status: 'processing'
    });

    return {
      statusCode: 200,
      body: JSON.stringify({
        transactionId,
        paymentIntentId: paymentIntent.id,
        status: paymentIntent.status,
        clientSecret: paymentIntent.client_secret
      })
    };

  } catch (error: any) {
    console.error('Payment initiation failed:', error);

    return {
      statusCode: 500,
      body: JSON.stringify({
        error: 'Payment initiation failed',
        message: error.message
      })
    };
  }
};

async function checkIdempotency(key: string): Promise<any> {
  const result = await dynamodb.get({
    TableName: IDEMPOTENCY_TABLE,
    Key: { idempotencyKey: key }
  }).promise();

  if (result.Item && result.Item.expiresAt > Date.now()) {
    return result.Item;
  }

  return null;
}

async function storeIdempotencyRecord(
  key: string,
  transactionId: string
): Promise<void> {
  await dynamodb.put({
    TableName: IDEMPOTENCY_TABLE,
    Item: {
      idempotencyKey: key,
      transactionId,
      createdAt: Date.now(),
      expiresAt: Date.now() + (24 * 60 * 60 * 1000), // 24 hours
      ttl: Math.floor(Date.now() / 1000) + (24 * 60 * 60)
    }
  }).promise();
}

async function createTransaction(transaction: any): Promise<void> {
  await dynamodb.put({
    TableName: TRANSACTIONS_TABLE,
    Item: {
      ...transaction,
      ttl: Math.floor(Date.now() / 1000) + (90 * 24 * 60 * 60) // 90 days
    }
  }).promise();
}

async function updateTransaction(
  transactionId: string,
  updates: any
): Promise<void> {
  const updateExpression: string[] = [];
  const expressionAttributeValues: any = {};

  Object.keys(updates).forEach(key => {
    updateExpression.push(`${key} = :${key}`);
    expressionAttributeValues[`:${key}`] = updates[key];
  });

  await dynamodb.update({
    TableName: TRANSACTIONS_TABLE,
    Key: { transactionId },
    UpdateExpression: `SET ${updateExpression.join(', ')}, updatedAt = :updatedAt`,
    ExpressionAttributeValues: {
      ...expressionAttributeValues,
      ':updatedAt': Date.now()
    }
  }).promise();
}

async function publishPaymentEvent(event: any): Promise<void> {
  await eventBridge.putEvents({
    Entries: [{
      Source: 'payment.service',
      DetailType: event.eventType,
      Detail: JSON.stringify(event),
      EventBusName: EVENT_BUS
    }]
  }).promise();
}

function generateIdempotencyKey(request: PaymentRequest): string {
  const data = JSON.stringify({
    amount: request.amount,
    currency: request.currency,
    customerId: request.customerId,
    orderId: request.metadata.orderId
  });

  return crypto.createHash('sha256').update(data).digest('hex');
}

async function validatePaymentRequest(request: PaymentRequest): Promise<void> {
  if (request.amount <= 0) {
    throw new Error('Invalid amount');
  }

  if (!['usd', 'eur', 'gbp'].includes(request.currency.toLowerCase())) {
    throw new Error('Unsupported currency');
  }

  if (!request.customerId || !request.paymentMethodId) {
    throw new Error('Missing required fields');
  }
}

async function checkFraudScore(request: PaymentRequest): Promise<number> {
  // Simplified fraud check - in production, integrate with fraud detection service
  let score = 0;

  // High amount transactions
  if (request.amount > 10000_00) score += 20;

  // Multiple transactions from same customer
  const recentTransactions = await getRecentTransactions(request.customerId);
  if (recentTransactions.length > 5) score += 30;

  return score;
}

async function getRecentTransactions(customerId: string): Promise<any[]> {
  const result = await dynamodb.query({
    TableName: TRANSACTIONS_TABLE,
    IndexName: 'CustomerIdIndex',
    KeyConditionExpression: 'customerId = :customerId',
    FilterExpression: 'createdAt > :yesterday',
    ExpressionAttributeValues: {
      ':customerId': customerId,
      ':yesterday': Date.now() - (24 * 60 * 60 * 1000)
    }
  }).promise();

  return result.Items || [];
}

EventBridge Rules for Payment Events

# eventbridge/payment-rules.tf

resource "aws_cloudwatch_event_bus" "payments" {
  name = "payment-events"

  tags = {
    Service = "payments"
  }
}

# Rule: Payment initiated -> Process payment
resource "aws_cloudwatch_event_rule" "payment_initiated" {
  name           = "payment-initiated"
  event_bus_name = aws_cloudwatch_event_bus.payments.name

  event_pattern = jsonencode({
    source      = ["payment.service"]
    detail-type = ["payment.initiated"]
  })
}

resource "aws_cloudwatch_event_target" "process_payment" {
  rule           = aws_cloudwatch_event_rule.payment_initiated.name
  event_bus_name = aws_cloudwatch_event_bus.payments.name
  arn            = aws_lambda_function.payment_processor.arn

  retry_policy {
    maximum_retry_attempts = 3
    maximum_event_age      = 3600
  }

  dead_letter_config {
    arn = aws_sqs_queue.payment_dlq.arn
  }
}

# Rule: Payment succeeded -> Send confirmation
resource "aws_cloudwatch_event_rule" "payment_succeeded" {
  name           = "payment-succeeded"
  event_bus_name = aws_cloudwatch_event_bus.payments.name

  event_pattern = jsonencode({
    source      = ["payment.service"]
    detail-type = ["payment.succeeded"]
  })
}

resource "aws_cloudwatch_event_target" "send_confirmation" {
  rule           = aws_cloudwatch_event_rule.payment_succeeded.name
  event_bus_name = aws_cloudwatch_event_bus.payments.name
  arn            = aws_lambda_function.send_confirmation.arn
}

resource "aws_cloudwatch_event_target" "update_inventory" {
  rule           = aws_cloudwatch_event_rule.payment_succeeded.name
  event_bus_name = aws_cloudwatch_event_bus.payments.name
  arn            = aws_lambda_function.inventory_updater.arn
}

# Rule: Payment failed -> Retry or notify
resource "aws_cloudwatch_event_rule" "payment_failed" {
  name           = "payment-failed"
  event_bus_name = aws_cloudwatch_event_bus.payments.name

  event_pattern = jsonencode({
    source      = ["payment.service"]
    detail-type = ["payment.failed"]
    detail = {
      retryable = [true]
    }
  })
}

resource "aws_cloudwatch_event_target" "retry_payment" {
  rule           = aws_cloudwatch_event_rule.payment_failed.name
  event_bus_name = aws_cloudwatch_event_bus.payments.name
  arn            = aws_lambda_function.payment_retry.arn

  retry_policy {
    maximum_retry_attempts = 5
    maximum_event_age      = 7200
  }
}

# Rule: High-risk transaction -> Manual review
resource "aws_cloudwatch_event_rule" "high_risk_transaction" {
  name           = "high-risk-transaction"
  event_bus_name = aws_cloudwatch_event_bus.payments.name

  event_pattern = jsonencode({
    source      = ["payment.service"]
    detail-type = ["payment.initiated"]
    detail = {
      fraudScore = [{ numeric: [">", 70] }]
    }
  })
}

resource "aws_cloudwatch_event_target" "fraud_review" {
  rule           = aws_cloudwatch_event_rule.high_risk_transaction.name
  event_bus_name = aws_cloudwatch_event_bus.payments.name
  arn            = aws_lambda_function.fraud_review.arn
}

Webhook Handler with Signature Verification

// lambda/webhook-handler/index.ts

import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { EventBridge, DynamoDB } from 'aws-sdk';
import Stripe from 'stripe';
import crypto from 'crypto';

const eventBridge = new EventBridge();
const dynamodb = new DynamoDB.DocumentClient();
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2023-10-16'
});

const WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET!;
const EVENT_BUS = process.env.EVENT_BUS_NAME!;
const TRANSACTIONS_TABLE = process.env.TRANSACTIONS_TABLE!;

export const handler = async (
  event: APIGatewayProxyEvent
): Promise<APIGatewayProxyResult> => {

  try {
    // Verify webhook signature
    const signature = event.headers['stripe-signature'];
    if (!signature) {
      return {
        statusCode: 400,
        body: JSON.stringify({ error: 'Missing signature' })
      };
    }

    let stripeEvent: Stripe.Event;
    try {
      stripeEvent = stripe.webhooks.constructEvent(
        event.body!,
        signature,
        WEBHOOK_SECRET
      );
    } catch (err: any) {
      console.error('Webhook signature verification failed:', err.message);
      return {
        statusCode: 400,
        body: JSON.stringify({ error: 'Invalid signature' })
      };
    }

    // Process webhook event
    await processWebhookEvent(stripeEvent);

    return {
      statusCode: 200,
      body: JSON.stringify({ received: true })
    };

  } catch (error: any) {
    console.error('Webhook processing failed:', error);
    return {
      statusCode: 500,
      body: JSON.stringify({ error: error.message })
    };
  }
};

async function processWebhookEvent(event: Stripe.Event): Promise<void> {
  console.log(`Processing webhook: ${event.type}`);

  switch (event.type) {
    case 'payment_intent.succeeded':
      await handlePaymentSucceeded(event.data.object as Stripe.PaymentIntent);
      break;

    case 'payment_intent.payment_failed':
      await handlePaymentFailed(event.data.object as Stripe.PaymentIntent);
      break;

    case 'charge.refunded':
      await handleRefund(event.data.object as Stripe.Charge);
      break;

    case 'charge.dispute.created':
      await handleDispute(event.data.object as Stripe.Dispute);
      break;

    default:
      console.log(`Unhandled event type: ${event.type}`);
  }
}

async function handlePaymentSucceeded(
  paymentIntent: Stripe.PaymentIntent
): Promise<void> {
  const transactionId = paymentIntent.metadata.transactionId;

  // Update transaction status
  await dynamodb.update({
    TableName: TRANSACTIONS_TABLE,
    Key: { transactionId },
    UpdateExpression: 'SET #status = :status, paymentCompletedAt = :completedAt, updatedAt = :updatedAt',
    ExpressionAttributeNames: {
      '#status': 'status'
    },
    ExpressionAttributeValues: {
      ':status': 'succeeded',
      ':completedAt': Date.now(),
      ':updatedAt': Date.now()
    }
  }).promise();

  // Publish success event
  await eventBridge.putEvents({
    Entries: [{
      Source: 'payment.service',
      DetailType: 'payment.succeeded',
      Detail: JSON.stringify({
        transactionId,
        paymentIntentId: paymentIntent.id,
        amount: paymentIntent.amount,
        currency: paymentIntent.currency,
        customerId: paymentIntent.customer,
        timestamp: Date.now()
      }),
      EventBusName: EVENT_BUS
    }]
  }).promise();
}

async function handlePaymentFailed(
  paymentIntent: Stripe.PaymentIntent
): Promise<void> {
  const transactionId = paymentIntent.metadata.transactionId;
  const errorMessage = paymentIntent.last_payment_error?.message || 'Payment failed';

  // Update transaction status
  await dynamodb.update({
    TableName: TRANSACTIONS_TABLE,
    Key: { transactionId },
    UpdateExpression: 'SET #status = :status, errorMessage = :errorMessage, updatedAt = :updatedAt',
    ExpressionAttributeNames: {
      '#status': 'status'
    },
    ExpressionAttributeValues: {
      ':status': 'failed',
      ':errorMessage': errorMessage,
      ':updatedAt': Date.now()
    }
  }).promise();

  // Determine if retryable
  const retryable = isRetryableError(paymentIntent.last_payment_error);

  // Publish failure event
  await eventBridge.putEvents({
    Entries: [{
      Source: 'payment.service',
      DetailType: 'payment.failed',
      Detail: JSON.stringify({
        transactionId,
        paymentIntentId: paymentIntent.id,
        errorMessage,
        retryable,
        timestamp: Date.now()
      }),
      EventBusName: EVENT_BUS
    }]
  }).promise();
}

function isRetryableError(error: any): boolean {
  const retryableErrorCodes = [
    'rate_limit',
    'processing_error',
    'api_connection_error'
  ];

  return error && retryableErrorCodes.includes(error.code);
}

Payment Flow Diagram

sequenceDiagram participant Client participant API as API Gateway participant Init as Initiate Lambda participant DDB as DynamoDB participant Stripe participant EB as EventBridge participant Process as Process Lambda participant Notify as Notification Service Client->>API: POST /payments Note over API: Validate + Rate Limit API->>Init: Process request Init->>DDB: Check idempotency DDB-->>Init: No duplicate Init->>DDB: Create transaction Init->>EB: Publish payment.initiated Init->>Stripe: Create payment intent Stripe-->>Init: Payment intent created Init-->>Client: 200 OK (clientSecret) Note over Client,Stripe: Client completes payment<br/>with clientSecret Stripe->>API: Webhook: payment_intent.succeeded API->>Init: Verify signature Init->>DDB: Update status = succeeded Init->>EB: Publish payment.succeeded EB->>Process: Trigger processors Process->>DDB: Update order Process->>Notify: Send confirmation Notify-->>Client: Email + SMS Note over Client,Notify: Total time: ~1.5s

DynamoDB Schema

# dynamodb/transactions.tf

resource "aws_dynamodb_table" "transactions" {
  name           = "payment-transactions"
  billing_mode   = "PAY_PER_REQUEST"
  hash_key       = "transactionId"

  attribute {
    name = "transactionId"
    type = "S"
  }

  attribute {
    name = "customerId"
    type = "S"
  }

  attribute {
    name = "createdAt"
    type = "N"
  }

  attribute {
    name = "status"
    type = "S"
  }

  # GSI for querying by customer
  global_secondary_index {
    name            = "CustomerIdIndex"
    hash_key        = "customerId"
    range_key       = "createdAt"
    projection_type = "ALL"
  }

  # GSI for querying by status
  global_secondary_index {
    name            = "StatusIndex"
    hash_key        = "status"
    range_key       = "createdAt"
    projection_type = "ALL"
  }

  # Enable streams for real-time processing
  stream_enabled   = true
  stream_view_type = "NEW_AND_OLD_IMAGES"

  # TTL for automatic cleanup
  ttl {
    attribute_name = "ttl"
    enabled        = true
  }

  point_in_time_recovery {
    enabled = true
  }

  tags = {
    Service = "payments"
  }
}

# Idempotency table
resource "aws_dynamodb_table" "idempotency" {
  name           = "payment-idempotency"
  billing_mode   = "PAY_PER_REQUEST"
  hash_key       = "idempotencyKey"

  attribute {
    name = "idempotencyKey"
    type = "S"
  }

  ttl {
    attribute_name = "ttl"
    enabled        = true
  }

  tags = {
    Service = "payments"
  }
}

Real-Time Fraud Detection

// lambda/fraud-detection/index.ts

import { KinesisStreamEvent } from 'aws-lambda';
import { DynamoDB, SNS } from 'aws-sdk';

const dynamodb = new DynamoDB.DocumentClient();
const sns = new SNS();

interface Transaction {
  transactionId: string;
  customerId: string;
  amount: number;
  currency: string;
  ipAddress: string;
  deviceId: string;
  location: {
    country: string;
    city: string;
  };
}

export const handler = async (event: KinesisStreamEvent): Promise<void> => {
  const promises = event.Records.map(record => {
    const transaction: Transaction = JSON.parse(
      Buffer.from(record.kinesis.data, 'base64').toString()
    );
    return analyzeTransaction(transaction);
  });

  await Promise.all(promises);
};

async function analyzeTransaction(transaction: Transaction): Promise<void> {
  let riskScore = 0;
  const riskFactors: string[] = [];

  // Check 1: Unusual amount
  const avgAmount = await getCustomerAverageTransaction(transaction.customerId);
  if (transaction.amount > avgAmount * 3) {
    riskScore += 25;
    riskFactors.push('Unusual transaction amount');
  }

  // Check 2: Velocity - multiple transactions in short time
  const recentCount = await getRecentTransactionCount(
    transaction.customerId,
    5 * 60 * 1000 // Last 5 minutes
  );
  if (recentCount > 5) {
    riskScore += 30;
    riskFactors.push('High transaction velocity');
  }

  // Check 3: New device
  const isKnownDevice = await checkKnownDevice(
    transaction.customerId,
    transaction.deviceId
  );
  if (!isKnownDevice) {
    riskScore += 15;
    riskFactors.push('Unknown device');
  }

  // Check 4: Geographic anomaly
  const isAnomalousLocation = await checkLocationAnomaly(
    transaction.customerId,
    transaction.location
  );
  if (isAnomalousLocation) {
    riskScore += 20;
    riskFactors.push('Unusual location');
  }

  // Check 5: Card testing pattern
  const hasCardTestingPattern = await detectCardTesting(
    transaction.customerId,
    transaction.amount
  );
  if (hasCardTestingPattern) {
    riskScore += 40;
    riskFactors.push('Card testing pattern detected');
  }

  // Store fraud analysis
  await storeFraudAnalysis(transaction.transactionId, {
    riskScore,
    riskFactors,
    analyzedAt: Date.now()
  });

  // Alert if high risk
  if (riskScore >= 70) {
    await alertHighRiskTransaction(transaction, riskScore, riskFactors);
  }
}

async function alertHighRiskTransaction(
  transaction: Transaction,
  riskScore: number,
  riskFactors: string[]
): Promise<void> {
  await sns.publish({
    TopicArn: process.env.FRAUD_ALERTS_TOPIC!,
    Subject: `High-risk transaction detected: ${transaction.transactionId}`,
    Message: JSON.stringify({
      transactionId: transaction.transactionId,
      customerId: transaction.customerId,
      amount: transaction.amount,
      riskScore,
      riskFactors
    }, null, 2)
  }).promise();
}

Performance & Scale

flowchart TB subgraph Throughput["Transaction Throughput"] T1["Peak: 1,500 TPS<br/>(transactions per second)"] T2["Daily: 3M transactions"] T3["Monthly: 90M transactions"] end subgraph Latency["Response Times"] L1["P50: 250ms"] L2["P95: 480ms"] L3["P99: 850ms"] end subgraph Reliability["Reliability"] R1["99.99% success rate"] R2["< 1min recovery time"] R3["Zero data loss"] end style Throughput fill:#2a9d8f,stroke:#fff,stroke-width:2px,color:#fff style Latency fill:#f77f00,stroke:#fff,stroke-width:2px,color:#fff style Reliability fill:#9b5de5,stroke:#fff,stroke-width:2px,color:#fff

Cost Breakdown

Component Monthly Cost Transactions
API Gateway $3,000 90M requests
Lambda executions $8,000 270M invocations
DynamoDB $12,000 Pay-per-request
EventBridge $1,800 90M events
Kinesis Data Streams $4,500 Real-time analytics
Data transfer $2,700 Cross-region
Total $32,000 ~$0.00036 per transaction

Best Practices

Practice Implementation Benefit
Idempotency Request-level idempotency keys Prevent duplicates
Event-driven EventBridge for orchestration Loose coupling
Async processing SQS for non-critical paths Better performance
Retry logic Exponential backoff Handle transient failures
Fraud detection Real-time analysis Prevent losses
Monitoring CloudWatch + X-Ray Observability

Conclusion

Building a payment processing system at scale requires careful attention to reliability, idempotency, and real-time processing. The combination of:

  • EventBridge for event-driven orchestration
  • Lambda for serverless processing
  • DynamoDB for state management with streams
  • Kinesis for real-time fraud detection
  • Step Functions for complex workflows

Creates a system that processes millions of daily transactions with 99.99% reliability while maintaining sub-500ms response times. The key is treating payments as events, implementing proper idempotency guarantees, and building comprehensive fraud detection into the flow.

Share this article

Tags

#aws#payments#eventbridge#lambda#dynamodb#stripe#adyen#event-driven

Related Articles

system-design12 min read

AI Chatbot System Architecture: WhatsApp Business API, Facebook Messenger, and AWS Bedrock Integration

Designing a multi-channel AI chatbot system handling 5M+ conversations monthly - featuring AWS Bedrock for conversational AI, SQS for message queuing, DynamoDB for conversation state, and Lambda for serverless processing across WhatsApp and Facebook Messenger.

cloud9 min read

Multi-Region AWS Infrastructure for Resilience: A Terraform Deep Dive

Learn how to architect highly available, multi-region AWS infrastructure using Terraform, Transit Gateway, Network Load Balancers, and intelligent routing strategies for enterprise-grade applications.

cloud11 min read

Cloud FinOps Framework: AWS Cost Intelligence Dashboard, Budgets, and Cost Anomaly Detection for Enterprise Cost Governance

Architecting a FinOps framework that reduced cloud costs by 30% and delivered predictable spend - featuring Cost Intelligence Dashboard, automated anomaly detection, chargeback mechanisms, and executive-level cost visibility.