payments-dashboard/template.yaml
Adam Moussa 3583e2fc11 Add payroll notification Lambda for Gusto payroll via Dataddo
Queries Aurora for new payroll runs and contractor payments, sends
formatted Slack message with gross pay breakdown and total bank
withdrawal amount. Runs weekdays at 2pm ET, tracks notified payrolls
in DynamoDB to avoid duplicates.
2026-04-24 16:39:50 -04:00

396 lines
12 KiB
YAML

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Payments Dashboard - S3 CSV ingestion to Slack App Home
Globals:
Function:
Runtime: nodejs20.x
Timeout: 30
MemorySize: 256
Environment:
Variables:
TABLE_NAME: !Ref DashboardTable
Resources:
# VPC with private subnet + NAT Gateway for static outbound IP
Vpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.20.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: payments-dashboard-vpc
PrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref Vpc
CidrBlock: 10.20.1.0/24
AvailabilityZone: !Select [0, !GetAZs ""]
Tags:
- Key: Name
Value: payments-dashboard-private
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref Vpc
CidrBlock: 10.20.2.0/24
AvailabilityZone: !Select [0, !GetAZs ""]
Tags:
- Key: Name
Value: payments-dashboard-public
InternetGateway:
Type: AWS::EC2::InternetGateway
VpcGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref Vpc
InternetGatewayId: !Ref InternetGateway
NatEip:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
NatGateway:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NatEip.AllocationId
SubnetId: !Ref PublicSubnet
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref Vpc
PublicRoute:
Type: AWS::EC2::Route
DependsOn: VpcGatewayAttachment
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet
RouteTableId: !Ref PublicRouteTable
PublicSubnetB:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref Vpc
CidrBlock: 10.20.3.0/24
AvailabilityZone: !Select [1, !GetAZs ""]
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: payments-dashboard-public-b
PublicSubnetBRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnetB
RouteTableId: !Ref PublicRouteTable
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref Vpc
PrivateRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway
PrivateSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet
RouteTableId: !Ref PrivateRouteTable
LambdaSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Payments Dashboard Lambda outbound access
VpcId: !Ref Vpc
SecurityGroupEgress:
- IpProtocol: "-1"
CidrIp: 0.0.0.0/0
# Aurora Serverless v2 PostgreSQL (Dataddo → payroll data)
AuroraSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Aurora PostgreSQL access
VpcId: !Ref Vpc
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
SourceSecurityGroupId: !Ref LambdaSecurityGroup
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
CidrIp: 0.0.0.0/0
AuroraSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: Public subnets for Aurora PostgreSQL
SubnetIds:
- !Ref PublicSubnet
- !Ref PublicSubnetB
AuroraCluster:
Type: AWS::RDS::DBCluster
Properties:
Engine: aurora-postgresql
EngineVersion: "16.4"
DatabaseName: payroll
MasterUsername: payroll_admin
ManageMasterUserPassword: true
ServerlessV2ScalingConfiguration:
MinCapacity: 0.5
MaxCapacity: 2
VpcSecurityGroupIds:
- !Ref AuroraSecurityGroup
DBSubnetGroupName: !Ref AuroraSubnetGroup
EnableHttpEndpoint: true
StorageEncrypted: true
AuroraInstance:
Type: AWS::RDS::DBInstance
Properties:
DBClusterIdentifier: !Ref AuroraCluster
DBInstanceClass: db.serverless
Engine: aurora-postgresql
PubliclyAccessible: true
PaymentsCsvBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub seahaven-payments-csv-${AWS::AccountId}
DashboardTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: PaymentsDashboard
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: pk
AttributeType: S
KeySchema:
- AttributeName: pk
KeyType: HASH
TimeToLiveSpecification:
AttributeName: ttl
Enabled: true
ProcessPaymentCsvFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: payments-processPaymentCsv
Handler: src/processPaymentCsv.handler
Timeout: 120
Environment:
Variables:
BOA_BASE_URL: https://api.bofa.com
BOA_CHECK_MGMT_APP_ID_PARAM: /payments-dashboard/boa-check-mgmt-app-id
BOA_CHECK_MGMT_CLIENT_ID_PARAM: /payments-dashboard/boa-check-mgmt-client-id
BOA_CHECK_MGMT_SECRET_PARAM: /payments-dashboard/boa-check-mgmt-token
BOA_ACCOUNT_NUMBER_PARAM: /payments-dashboard/boa-account-number
BOA_COMPANY_ID_PARAM: /payments-dashboard/boa-company-id
VpcConfig:
SubnetIds:
- !Ref PrivateSubnet
SecurityGroupIds:
- !Ref LambdaSecurityGroup
Events:
CsvUpload:
Type: S3
Properties:
Bucket: !Ref PaymentsCsvBucket
Events: s3:ObjectCreated:*
Filter:
S3Key:
Rules:
- Name: suffix
Value: .csv
Policies:
- S3ReadPolicy:
BucketName: !Sub seahaven-payments-csv-${AWS::AccountId}
- DynamoDBCrudPolicy:
TableName: !Ref DashboardTable
- SSMParameterReadPolicy:
ParameterName: payments-dashboard/boa-check-mgmt-app-id
- SSMParameterReadPolicy:
ParameterName: payments-dashboard/boa-check-mgmt-client-id
- SSMParameterReadPolicy:
ParameterName: payments-dashboard/boa-check-mgmt-token
- SSMParameterReadPolicy:
ParameterName: payments-dashboard/boa-account-number
- SSMParameterReadPolicy:
ParameterName: payments-dashboard/boa-company-id
- Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- ec2:CreateNetworkInterface
- ec2:DescribeNetworkInterfaces
- ec2:DeleteNetworkInterface
Resource: "*"
SlackAppHomeFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: payments-slackAppHome
Handler: src/slackAppHome.handler
VpcConfig:
SubnetIds:
- !Ref PrivateSubnet
SecurityGroupIds:
- !Ref LambdaSecurityGroup
Environment:
Variables:
SLACK_BOT_TOKEN_PARAM: /payments-dashboard/slack-bot-token
Events:
SlackEvent:
Type: HttpApi
Properties:
Path: /slack/events
Method: POST
Policies:
- DynamoDBReadPolicy:
TableName: !Ref DashboardTable
- SSMParameterReadPolicy:
ParameterName: payments-dashboard/slack-bot-token
- Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- ec2:CreateNetworkInterface
- ec2:DescribeNetworkInterfaces
- ec2:DeleteNetworkInterface
Resource: "*"
FetchBoaTransactionsFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: payments-fetchBoaTransactions
Handler: src/fetchBoaTransactions.handler
Timeout: 60
VpcConfig:
SubnetIds:
- !Ref PrivateSubnet
SecurityGroupIds:
- !Ref LambdaSecurityGroup
Environment:
Variables:
BOA_BASE_URL: https://api.bofa.com
BOA_REPORTING_APP_ID_PARAM: /payments-dashboard/boa-reporting-app-id
BOA_REPORTING_CLIENT_ID_PARAM: /payments-dashboard/boa-account-info-client-id
BOA_REPORTING_SECRET_PARAM: /payments-dashboard/boa-account-info-token
BOA_ACCOUNT_NUMBER_PARAM: /payments-dashboard/boa-account-number
BOA_BANK_ID_PARAM: /payments-dashboard/boa-bank-id
Events:
DailySchedule:
Type: Schedule
Properties:
Schedule: cron(0 13 ? * MON-FRI *)
Description: Fetch BoA previous day transactions at 9am ET (13:00 UTC)
Enabled: true
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref DashboardTable
- SSMParameterReadPolicy:
ParameterName: payments-dashboard/boa-reporting-app-id
- SSMParameterReadPolicy:
ParameterName: payments-dashboard/boa-account-info-client-id
- SSMParameterReadPolicy:
ParameterName: payments-dashboard/boa-account-info-token
- SSMParameterReadPolicy:
ParameterName: payments-dashboard/boa-account-number
- SSMParameterReadPolicy:
ParameterName: payments-dashboard/boa-bank-id
- Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- ec2:CreateNetworkInterface
- ec2:DescribeNetworkInterfaces
- ec2:DeleteNetworkInterface
Resource: "*"
NotifyPayrollFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: payments-notifyPayroll
Handler: src/notifyPayroll.handler
Timeout: 60
VpcConfig:
SubnetIds:
- !Ref PrivateSubnet
SecurityGroupIds:
- !Ref LambdaSecurityGroup
Environment:
Variables:
AURORA_CLUSTER_ARN: !GetAtt AuroraCluster.DBClusterArn
AURORA_SECRET_ARN: !GetAtt AuroraCluster.MasterUserSecret.SecretArn
SLACK_BOT_TOKEN_PARAM: /payments-dashboard/slack-bot-token
Events:
PayrollCheck:
Type: Schedule
Properties:
Schedule: cron(0 18 ? * MON-FRI *)
Description: Check for new payroll data at 2pm ET (18:00 UTC)
Enabled: true
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref DashboardTable
- SSMParameterReadPolicy:
ParameterName: payments-dashboard/slack-bot-token
- Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- ec2:CreateNetworkInterface
- ec2:DescribeNetworkInterfaces
- ec2:DeleteNetworkInterface
Resource: "*"
- Effect: Allow
Action:
- rds-data:ExecuteStatement
Resource: !GetAtt AuroraCluster.DBClusterArn
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource: !GetAtt AuroraCluster.MasterUserSecret.SecretArn
Outputs:
SlackEventUrl:
Description: URL to set as the Slack app Request URL
Value: !Sub https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/slack/events
CsvBucket:
Description: S3 bucket for CSV uploads
Value: !Ref PaymentsCsvBucket
StaticOutboundIp:
Description: Static IP for BoA API whitelist
Value: !Ref NatEip
AuroraEndpoint:
Description: Aurora PostgreSQL cluster endpoint
Value: !GetAtt AuroraCluster.Endpoint.Address
AuroraPort:
Description: Aurora PostgreSQL port
Value: !GetAtt AuroraCluster.Endpoint.Port
AuroraSecretArn:
Description: Secrets Manager ARN for Aurora master credentials
Value: !GetAtt AuroraCluster.MasterUserSecret.SecretArn