Port the Slack reaction-driven expense routing workflow (receiver + processor) from expense-approval-bot into this stack as JavaScript ESM. Secrets copied to payments-dashboard/ prefix in Secrets Manager. |
||
|---|---|---|
| .claude | ||
| .github | ||
| scripts | ||
| src | ||
| .gitignore | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| template.yaml | ||
Payments Dashboard
AWS SAM application that ingests payment CSVs, syncs check data with Bank of America CashPro APIs, processes Gusto payroll confirmation emails into Slack notifications, surfaces an outstanding-payments dashboard in Slack, and routes expense approvals through a Slack reaction-driven workflow.
Architecture
-
ProcessPayrollEmail — Lambda triggered by S3 (inbound email) and SQS (batch timer). SES receives Gusto payroll emails at
payroll@int.seahaven.com, stores them to S3, and this Lambda parses the email body, extracts financial data, and posts a combined Slack notification (employee payroll + contractor payments) after a 10-minute batching window. Runs outside VPC. -
ProcessPaymentCsv — Lambda triggered by S3 CSV upload. Parses Stampli payment exports, upserts to DynamoDB, and submits new/cancelled checks to the CashPro Check Management API.
-
FetchBoaTransactions — Scheduled Lambda (weekdays 9am ET). Calls the CashPro Previous Day Transaction Inquiry API and matches cleared/returned checks back to DynamoDB records.
-
SlackAppHome — Lambda behind API Gateway. Renders the payments dashboard on the Slack App Home tab with outstanding aging buckets and drill-down modals.
-
ExpenseReceiver — Lambda behind API Gateway (
POST /slack/expense-events). Verifies the Slack signing secret (HMAC-SHA256), handles URL verification challenges, and async-invokes ExpenseProcessor. Runs outside VPC. -
ExpenseProcessor — Async Lambda invoked by ExpenseReceiver. Processes
:white_check_mark:reactions to advance expense messages through a four-stage Slack channel pipeline: Submitted → Processed → Authorized → Matched. Runs outside VPC.
ProcessPaymentCsv, FetchBoaTransactions, and SlackAppHome run inside a VPC with a NAT Gateway for a static outbound IP (required by BoA IP whitelisting). ProcessPayrollEmail, ExpenseReceiver, and ExpenseProcessor run outside the VPC.
Expense Approval Bot
Reaction-driven workflow that routes expense submissions through four Slack channels. A separate Slack app ("Expense Approval Bot") posts to a Submitted channel. Users react with ✅ to advance the message to the next stage.
Channel pipeline:
| Stage | Channel ID | Action on ✅ |
|---|---|---|
| Submitted | C0AQ2AWLNEN |
Thread reply on original, copy to Processed |
| Processed | C0APLSGABAB |
Delete from Processed, post to Authorized |
| Authorized | C0AQ09CDJH4 |
Delete from Authorized, post to Matched |
| Matched | C0APYUM1JFP |
Terminal stage (no further routing) |
Architecture: Two Lambdas — ExpenseReceiver (HTTP endpoint, signature verification, async invoke) and ExpenseProcessor (business logic). This is the same receiver/processor pattern used for Slack's 3-second timeout requirement.
Secrets (Secrets Manager):
| Secret | Purpose |
|---|---|
payments-dashboard/expense-slack-token |
Slack Bot token for the Expense Approval Bot app |
payments-dashboard/expense-slack-signing-secret |
Slack signing secret for request verification |
Payroll Email Pipeline
Gusto sends payroll confirmation emails when payroll is run. A Gmail filter on adam@seahavenind.com auto-forwards emails from automated@gusto.com and gustonoreply@gusto.com to payroll@int.seahaven.com.
Flow: Gmail forward → SES receipt rule → S3 bucket → Lambda parses email → DynamoDB (pending) → SQS delay queue (10 min) → Lambda batches all pending items for that date → single Slack message → DynamoDB (notified)
Deduplication: Each email is deduplicated by DynamoDB key (PAYROLL_EMAIL#employee#<date> or PAYROLL_EMAIL#contractor#<date>#<bank-suffix>). The batch post is deduplicated by PAYROLL_BATCH#<date>. All items have a 90-day TTL.
BoA CashPro API Integration
Two separate CashPro APIs are used, each with its own OAuth credentials:
| API | Purpose | Endpoint |
|---|---|---|
| Check Management | Issue and cancel checks | /cashpro/checkmanagement/v1/check-issues |
| Reporting (Transaction Inquiry) | Fetch previous-day transactions | /cashpro/reporting/v1/transaction-inquiries/previous-day |
Authentication flow:
- POST to
/authn/v1/client-authenticationwithapplicationID,client_id, andclient_secret - Receive a Bearer
access_token(valid 1 hour) - Pass the token in the
Authorizationheader for subsequent API calls
Base URLs:
- Production:
https://api.bofa.com - Sandbox:
https://api-sb.bofa.com
SSM Parameters
All BoA credentials and config are stored in AWS SSM Parameter Store (SecureString):
| Parameter | Description |
|---|---|
/payments-dashboard/boa-check-mgmt-app-id |
Check Management application ID |
/payments-dashboard/boa-check-mgmt-client-id |
Check Management client ID |
/payments-dashboard/boa-check-mgmt-token |
Check Management client secret |
/payments-dashboard/boa-reporting-app-id |
Reporting application ID |
/payments-dashboard/boa-account-info-client-id |
Reporting client ID |
/payments-dashboard/boa-account-info-token |
Reporting client secret |
/payments-dashboard/boa-account-number |
BoA account number |
/payments-dashboard/boa-company-id |
CashPro company ID (check management) |
/payments-dashboard/boa-bank-id |
BoA routing number |
/payments-dashboard/slack-bot-token |
Slack Bot OAuth token |
Scripts
| Script | Purpose |
|---|---|
scripts/test-boa-sandbox.js |
One-off sandbox connectivity test for both CashPro APIs |
scripts/seed-from-csv.js |
Seed DynamoDB from a local CSV file |
scripts/seed-bank-status.js |
Seed bank clear status data into DynamoDB |
Deployment
sam build
sam deploy --guided
The BOA_BASE_URL environment variable in template.yaml controls whether Lambdas hit production (https://api.bofa.com) or sandbox (https://api-sb.bofa.com). All other BoA config is read from SSM at runtime.