2026-04-09 13:29:28 -04:00
|
|
|
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:
|
2026-04-09 14:27:34 -04:00
|
|
|
# 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
|
|
|
|
|
|
2026-04-24 15:05:43 -04:00
|
|
|
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
|
|
|
|
|
|
2026-04-09 14:27:34 -04:00
|
|
|
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
|
|
|
|
|
|
2026-04-24 15:05:43 -04:00
|
|
|
# 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
|
|
|
|
|
|
2026-04-09 13:29:28 -04:00
|
|
|
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
|
2026-04-20 17:40:55 -04:00
|
|
|
TimeToLiveSpecification:
|
|
|
|
|
AttributeName: ttl
|
|
|
|
|
Enabled: true
|
2026-04-09 13:29:28 -04:00
|
|
|
|
|
|
|
|
ProcessPaymentCsvFunction:
|
|
|
|
|
Type: AWS::Serverless::Function
|
|
|
|
|
Properties:
|
|
|
|
|
FunctionName: payments-processPaymentCsv
|
|
|
|
|
Handler: src/processPaymentCsv.handler
|
2026-04-09 15:14:51 -04:00
|
|
|
Timeout: 120
|
|
|
|
|
Environment:
|
|
|
|
|
Variables:
|
2026-04-13 16:24:20 -04:00
|
|
|
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
|
2026-04-09 15:14:51 -04:00
|
|
|
BOA_ACCOUNT_NUMBER_PARAM: /payments-dashboard/boa-account-number
|
|
|
|
|
BOA_COMPANY_ID_PARAM: /payments-dashboard/boa-company-id
|
2026-04-09 14:27:34 -04:00
|
|
|
VpcConfig:
|
|
|
|
|
SubnetIds:
|
|
|
|
|
- !Ref PrivateSubnet
|
|
|
|
|
SecurityGroupIds:
|
|
|
|
|
- !Ref LambdaSecurityGroup
|
2026-04-09 13:29:28 -04:00
|
|
|
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
|
2026-04-09 15:14:51 -04:00
|
|
|
- SSMParameterReadPolicy:
|
2026-04-13 16:24:20 -04:00
|
|
|
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
|
2026-04-09 15:14:51 -04:00
|
|
|
- SSMParameterReadPolicy:
|
|
|
|
|
ParameterName: payments-dashboard/boa-account-number
|
|
|
|
|
- SSMParameterReadPolicy:
|
|
|
|
|
ParameterName: payments-dashboard/boa-company-id
|
2026-04-09 14:27:34 -04:00
|
|
|
- Version: "2012-10-17"
|
|
|
|
|
Statement:
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action:
|
|
|
|
|
- ec2:CreateNetworkInterface
|
|
|
|
|
- ec2:DescribeNetworkInterfaces
|
|
|
|
|
- ec2:DeleteNetworkInterface
|
|
|
|
|
Resource: "*"
|
2026-04-09 13:29:28 -04:00
|
|
|
|
|
|
|
|
SlackAppHomeFunction:
|
|
|
|
|
Type: AWS::Serverless::Function
|
|
|
|
|
Properties:
|
|
|
|
|
FunctionName: payments-slackAppHome
|
|
|
|
|
Handler: src/slackAppHome.handler
|
2026-04-09 14:27:34 -04:00
|
|
|
VpcConfig:
|
|
|
|
|
SubnetIds:
|
|
|
|
|
- !Ref PrivateSubnet
|
|
|
|
|
SecurityGroupIds:
|
|
|
|
|
- !Ref LambdaSecurityGroup
|
2026-04-09 13:29:28 -04:00
|
|
|
Environment:
|
|
|
|
|
Variables:
|
2026-04-09 14:27:34 -04:00
|
|
|
SLACK_BOT_TOKEN_PARAM: /payments-dashboard/slack-bot-token
|
2026-04-09 13:29:28 -04:00
|
|
|
Events:
|
|
|
|
|
SlackEvent:
|
|
|
|
|
Type: HttpApi
|
|
|
|
|
Properties:
|
|
|
|
|
Path: /slack/events
|
|
|
|
|
Method: POST
|
|
|
|
|
Policies:
|
|
|
|
|
- DynamoDBReadPolicy:
|
|
|
|
|
TableName: !Ref DashboardTable
|
2026-04-09 14:27:34 -04:00
|
|
|
- SSMParameterReadPolicy:
|
|
|
|
|
ParameterName: payments-dashboard/slack-bot-token
|
|
|
|
|
- Version: "2012-10-17"
|
|
|
|
|
Statement:
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action:
|
|
|
|
|
- ec2:CreateNetworkInterface
|
|
|
|
|
- ec2:DescribeNetworkInterfaces
|
|
|
|
|
- ec2:DeleteNetworkInterface
|
|
|
|
|
Resource: "*"
|
2026-04-09 13:29:28 -04:00
|
|
|
|
2026-04-09 14:59:39 -04:00
|
|
|
FetchBoaTransactionsFunction:
|
|
|
|
|
Type: AWS::Serverless::Function
|
|
|
|
|
Properties:
|
|
|
|
|
FunctionName: payments-fetchBoaTransactions
|
|
|
|
|
Handler: src/fetchBoaTransactions.handler
|
|
|
|
|
Timeout: 60
|
|
|
|
|
VpcConfig:
|
|
|
|
|
SubnetIds:
|
|
|
|
|
- !Ref PrivateSubnet
|
|
|
|
|
SecurityGroupIds:
|
|
|
|
|
- !Ref LambdaSecurityGroup
|
|
|
|
|
Environment:
|
|
|
|
|
Variables:
|
2026-04-13 16:24:20 -04:00
|
|
|
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
|
2026-04-09 14:59:39 -04:00
|
|
|
BOA_ACCOUNT_NUMBER_PARAM: /payments-dashboard/boa-account-number
|
2026-04-13 16:24:20 -04:00
|
|
|
BOA_BANK_ID_PARAM: /payments-dashboard/boa-bank-id
|
2026-04-09 14:59:39 -04:00
|
|
|
Events:
|
|
|
|
|
DailySchedule:
|
|
|
|
|
Type: Schedule
|
|
|
|
|
Properties:
|
|
|
|
|
Schedule: cron(0 13 ? * MON-FRI *)
|
|
|
|
|
Description: Fetch BoA previous day transactions at 9am ET (13:00 UTC)
|
2026-04-14 17:43:13 -04:00
|
|
|
Enabled: true
|
2026-04-09 14:59:39 -04:00
|
|
|
Policies:
|
|
|
|
|
- DynamoDBCrudPolicy:
|
|
|
|
|
TableName: !Ref DashboardTable
|
|
|
|
|
- SSMParameterReadPolicy:
|
2026-04-13 16:24:20 -04:00
|
|
|
ParameterName: payments-dashboard/boa-reporting-app-id
|
|
|
|
|
- SSMParameterReadPolicy:
|
|
|
|
|
ParameterName: payments-dashboard/boa-account-info-client-id
|
|
|
|
|
- SSMParameterReadPolicy:
|
|
|
|
|
ParameterName: payments-dashboard/boa-account-info-token
|
2026-04-09 14:59:39 -04:00
|
|
|
- SSMParameterReadPolicy:
|
|
|
|
|
ParameterName: payments-dashboard/boa-account-number
|
2026-04-09 15:14:51 -04:00
|
|
|
- SSMParameterReadPolicy:
|
2026-04-13 16:24:20 -04:00
|
|
|
ParameterName: payments-dashboard/boa-bank-id
|
2026-04-09 14:59:39 -04:00
|
|
|
- Version: "2012-10-17"
|
|
|
|
|
Statement:
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action:
|
|
|
|
|
- ec2:CreateNetworkInterface
|
|
|
|
|
- ec2:DescribeNetworkInterfaces
|
|
|
|
|
- ec2:DeleteNetworkInterface
|
|
|
|
|
Resource: "*"
|
|
|
|
|
|
2026-04-24 16:39:50 -04:00
|
|
|
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
|
|
|
|
|
|
2026-04-09 13:29:28 -04:00
|
|
|
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
|
2026-04-09 14:27:34 -04:00
|
|
|
StaticOutboundIp:
|
|
|
|
|
Description: Static IP for BoA API whitelist
|
|
|
|
|
Value: !Ref NatEip
|
2026-04-24 15:05:43 -04:00
|
|
|
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
|