mirror of
https://github.com/Sea-Haven-Industries/payments-dashboard.git
synced 2026-09-30 07:43:12 +00:00
Replace the SSM Parameters section with the new Secrets Manager secret structure (3 grouped secrets) and correct the runtime-config note. Refs: INFRA-5, #3
92 lines
6 KiB
Markdown
92 lines
6 KiB
Markdown
# 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 :white_check_mark: to advance the message to the next stage.
|
|
|
|
**Channel pipeline:**
|
|
|
|
| Stage | Channel ID | Action on :white_check_mark: |
|
|
|-------|-----------|------------------------------|
|
|
| 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:**
|
|
1. POST to `/authn/v1/client-authentication` with `applicationID`, `client_id`, and `client_secret`
|
|
2. Receive a Bearer `access_token` (valid 1 hour)
|
|
3. Pass the token in the `Authorization` header for subsequent API calls
|
|
|
|
**Base URLs:**
|
|
- Production: `https://api.bofa.com`
|
|
- Sandbox: `https://api-sb.bofa.com`
|
|
|
|
## Secrets
|
|
|
|
All BoA and Slack credentials are stored in AWS Secrets Manager (per `engineering-handbook/secrets-and-config.md`). The Slack token is a plaintext secret; the two BoA secrets are JSON grouping each API's credentials:
|
|
|
|
| Secret | Type | Contents |
|
|
|--------|------|----------|
|
|
| `payments-dashboard/slack-bot-token` | plaintext | Slack Bot OAuth token (used by `processPayrollEmail`, `slackAppHome`) |
|
|
| `payments-dashboard/boa-check-mgmt` | JSON | `appId`, `clientId`, `token`, `accountNumber`, `companyId` — Check Management API (`processPaymentCsv`) |
|
|
| `payments-dashboard/boa-reporting` | JSON | `appId`, `clientId`, `token`, `accountNumber`, `bankId` — Reporting API (`fetchBoaTransactions`) |
|
|
|
|
`boa-account-number` is duplicated into both BoA secrets. Each Lambda is granted `secretsmanager:GetSecretValue` scoped to only the secret it needs. The Expense Approval Bot uses two additional secrets (`payments-dashboard/expense-slack-token`, `payments-dashboard/expense-slack-signing-secret`).
|
|
|
|
## 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
|
|
|
|
```bash
|
|
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 Secrets Manager at runtime.
|