TheArchitect

FIFO Payments Ledger

AWS architecture template · Exactly-once payment processing with FIFO SQS deduplication, conditional DynamoDB writes for idempotency, CMK encryption, and a DLQ alert path.

FIFO Payments Ledger is a free AWS architecture template built with Lambda, SQS, API Gateway, CloudWatch, DynamoDB, IAM, KMS, Secrets Manager and SNS. Open it in TheArchitect to edit the diagram, estimate monthly cost, and export the CloudFormation or Terraform that deploys it.

Payments APIPayment IntakeLedger Queue (…Ledger WriterLedger TableLedger CMKPSP CredentialsDLQ (FIFO)DLQ AlarmPager AlertsWriter Role
FIFO Payments Ledger architecture diagram

AWS services in this template

  • Lambda×2
  • SQS×2
  • API Gateway
  • CloudWatch
  • DynamoDB
  • IAM
  • KMS
  • Secrets Manager
  • SNS

Deployment notes

Needs input

  • Store PSP API credentials in Secrets Manager before deploying.
  • Set SQS FIFO queue URL as Lambda event source mapping.
  • KMS CMK must be created and key ARN referenced in DynamoDB SSE config.

Generated CloudFormation — 13 resources

Logical IDResource type
PaymentsAPIapigatewayAWS::ApiGateway::RestApi
PaymentIntakelambdaAWS::Lambda::Function
LedgerQueueFIFOsqsAWS::SQS::Queue
LedgerWriterlambdaAWS::Lambda::Function
LedgerTabledynamodbAWS::DynamoDB::Table
LedgerCMKkmsAWS::KMS::Key
PSPCredentialssecretsmanagerAWS::SecretsManager::Secret
DLQFIFOsqsAWS::SQS::Queue
DLQAlarmcloudwatchAWS::CloudWatch::Alarm
PagerAlertssnsAWS::SNS::Topic
WriterRoleiamAWS::IAM::Role
PaymentIntakelambdaLedgerQueueFIFOsqsEventSourceMappingAWS::Lambda::EventSourceMapping
LedgerWriterlambdaLedgerQueueFIFOsqsEventSourceMappingAWS::Lambda::EventSourceMapping
View the full CloudFormation template
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "Generated by TheArchitect — https://www.thecloudarchitect.dev",
  "Metadata": {
    "TheArchitect": {
      "Edges": [
        {
          "From": "PaymentIntakelambda",
          "To": "LedgerQueueFIFOsqs",
          "Label": "enqueue ordered"
        },
        {
          "From": "LedgerQueueFIFOsqs",
          "To": "LedgerWriterlambda",
          "Label": "BatchSize=1"
        },
        {
          "From": "LedgerWriterlambda",
          "To": "LedgerTabledynamodb",
          "Label": "conditional PutItem"
        },
        {
          "From": "DLQAlarmcloudwatch",
          "To": "PagerAlertssns",
          "Label": "alarm action"
        },
        {
          "From": "WriterRoleiam",
          "To": "LedgerWriterlambda",
          "Label": "execution role"
        }
      ]
    }
  },
  "Resources": {
    "PaymentsAPIapigateway": {
      "Type": "AWS::ApiGateway::RestApi",
      "Properties": {
        "Name": "payments-api",
        "Description": "Payments Intake REST API",
        "EndpointConfiguration": {
          "Types": [
            "REGIONAL"
          ]
        }
      }
    },
    "PaymentIntakelambda": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "FunctionName": "payment-intake",
        "Runtime": "nodejs20.x",
        "Handler": "intake.handler",
        "MemorySize": 256,
        "Timeout": 10,
        "Description": "Validates payment request and enqueues to FIFO queue with MessageGroupId=merchant_id and client-generated MessageDeduplicationId."
      }
    },
    "LedgerQueueFIFOsqs": {
      "Type": "AWS::SQS::Queue",
      "Properties": {
        "QueueName": "ledger-queue.fifo",
        "VisibilityTimeout": 120,
        "MessageRetentionPeriod": 86400,
        "ReceiveMessageWaitTimeSeconds": 20
      }
    },
    "LedgerWriterlambda": {
      "Type": "AWS::Lambda::Function",
      "DependsOn": [
        "WriterRoleiam"
      ],
      "Properties": {
        "FunctionName": "ledger-writer",
        "Runtime": "nodejs20.x",
        "Handler": "writer.handler",
        "MemorySize": 512,
        "Timeout": 60,
        "Description": "Processes one message at a time (BatchSize=1). Performs conditional PutItem on DynamoDB using transaction_id as idempotency key. Fetches PSP key from Secrets Manager.",
        "Role": {
          "Fn::GetAtt": [
            "WriterRoleiam",
            "Arn"
          ]
        }
      }
    },
    "LedgerTabledynamodb": {
      "Type": "AWS::DynamoDB::Table",
      "Properties": {
        "TableName": "ledger",
        "BillingMode": "PAY_PER_REQUEST",
        "AttributeDefinitions": [
          {
            "AttributeName": "id",
            "AttributeType": "S"
          }
        ],
        "KeySchema": [
          {
            "AttributeName": "id",
            "KeyType": "HASH"
          }
        ],
        "PointInTimeRecoverySpecification": {
          "PointInTimeRecoveryEnabled": true
        }
      }
    },
    "LedgerCMKkms": {
      "Type": "AWS::KMS::Key",
      "Properties": {
        "Description": "Ledger CMK — encrypts DynamoDB and Secrets Manager",
        "EnableKeyRotation": true,
        "Enabled": true,
        "PendingWindowInDays": 30
      }
    },
    "PSPCredentialssecretsmanager": {
      "Type": "AWS::SecretsManager::Secret",
      "Properties": {
        "Name": "payments/psp-credentials",
        "Description": "PSP API key and webhook secret for payment processor integration",
        "GenerateSecretString": {
          "PasswordLength": 32
        }
      }
    },
    "DLQFIFOsqs": {
      "Type": "AWS::SQS::Queue",
      "Properties": {
        "QueueName": "ledger-dlq.fifo",
        "MessageRetentionPeriod": 1209600
      }
    },
    "DLQAlarmcloudwatch": {
      "Type": "AWS::CloudWatch::Alarm",
      "Properties": {
        "AlarmName": "ledger-dlq-not-empty",
        "AlarmDescription": "Payment message reached DLQ — investigate ledger writer errors immediately",
        "Namespace": "AWS/SQS",
        "MetricName": "ApproximateNumberOfMessagesVisible",
        "Statistic": "Sum",
        "Period": 60,
        "EvaluationPeriods": 1,
        "Threshold": 1,
        "ComparisonOperator": "GreaterThanOrEqualToThreshold",
        "TreatMissingData": "notBreaching"
      }
    },
    "PagerAlertssns": {
      "Type": "AWS::SNS::Topic",
      "Properties": {
        "TopicName": "payments-pager-alerts",
        "DisplayName": "Payments Pager Alerts"
      }
    },
    "WriterRoleiam": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "RoleName": "ledger-writer-role",
        "Description": "Lambda execution role for ledger writer — SQS + DynamoDB + Secrets Manager + KMS",
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": "lambda.amazonaws.com"
              },
              "Action": "sts:AssumeRole"
            }
          ]
        },
        "ManagedPolicyArns": [
          "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
          "arn:aws:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole"
        ]
      }
    },
    "PaymentIntakelambdaLedgerQueueFIFOsqsEventSourceMapping": {
      "Type": "AWS::Lambda::EventSourceMapping",
      "DependsOn": [
        "PaymentIntakelambda",
        "LedgerQueueFIFOsqs"
      ],
      "Properties": {
        "EventSourceArn": {
          "Fn::GetAtt": [
            "LedgerQueueFIFOsqs",
            "Arn"
          ]
        },
        "FunctionName": {
          "Ref": "PaymentIntakelambda"
        },
        "Enabled": true,
        "BatchSize": 10
      }
    },
    "LedgerWriterlambdaLedgerQueueFIFOsqsEventSourceMapping": {
      "Type": "AWS::Lambda::EventSourceMapping",
      "DependsOn": [
        "LedgerWriterlambda",
        "LedgerQueueFIFOsqs"
      ],
      "Properties": {
        "EventSourceArn": {
          "Fn::GetAtt": [
            "LedgerQueueFIFOsqs",
            "Arn"
          ]
        },
        "FunctionName": {
          "Ref": "LedgerWriterlambda"
        },
        "Enabled": true,
        "BatchSize": 10
      }
    }
  }
}

Generated by TheArchitect from the diagram above. Review values such as names, CIDR ranges and instance sizes before deploying to your account.

Related templates