mirror of
https://github.com/Sea-Haven-Industries/payments-dashboard.git
synced 2026-10-01 11:33:12 +00:00
- New fetchBoaTransactions Lambda: daily 9am ET schedule (disabled until API key set) Calls CashPro Previous Day Transaction Inquiry, filters for codes 255/475, matches bankReference to check_number, updates clear_status in DynamoDB - CSV processor switched to UpdateCommand to preserve clearing data on re-upload - Slack dashboard now shows Scheduled, Outstanding, Cleared, and Returned sections - ACH payments auto-assumed cleared once past due date
123 lines
3.8 KiB
JavaScript
123 lines
3.8 KiB
JavaScript
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
import { DynamoDBDocumentClient, ScanCommand, UpdateCommand } from "@aws-sdk/lib-dynamodb";
|
|
import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm";
|
|
|
|
const ddb = DynamoDBDocumentClient.from(new DynamoDBClient());
|
|
const ssm = new SSMClient();
|
|
const TABLE_NAME = process.env.TABLE_NAME;
|
|
const BOA_BASE_URL = process.env.BOA_BASE_URL;
|
|
|
|
async function getSSMParam(name) {
|
|
const { Parameter } = await ssm.send(
|
|
new GetParameterCommand({ Name: name, WithDecryption: true })
|
|
);
|
|
return Parameter.Value;
|
|
}
|
|
|
|
export const handler = async () => {
|
|
const [apiToken, accountNumber] = await Promise.all([
|
|
getSSMParam(process.env.BOA_API_TOKEN_PARAM),
|
|
getSSMParam(process.env.BOA_ACCOUNT_NUMBER_PARAM),
|
|
]);
|
|
|
|
// Get yesterday's date in YYYY-MM-DD
|
|
const yesterday = new Date();
|
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
const dateStr = yesterday.toISOString().split("T")[0];
|
|
|
|
// Call CashPro Previous Day Transaction Inquiry
|
|
const res = await fetch(`${BOA_BASE_URL}/cashpro/reporting/v1/transaction-inquiries/previous-day`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${apiToken}`,
|
|
},
|
|
body: JSON.stringify({
|
|
accounts: [{ accountNumber, bankId: "BOFAFRPP" }],
|
|
fromDate: dateStr,
|
|
toDate: dateStr,
|
|
}),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const text = await res.text();
|
|
throw new Error(`BoA API error ${res.status}: ${text}`);
|
|
}
|
|
|
|
const data = await res.json();
|
|
const transactions = data.accountTransactions?.transactions || [];
|
|
|
|
// Filter for cleared checks (255) and returned checks (475)
|
|
const relevant = transactions.filter(
|
|
(t) => t.transactionCode === "255" || t.transactionCode === "475"
|
|
);
|
|
|
|
if (!relevant.length) {
|
|
console.log(`No check transactions (255/475) found for ${dateStr}`);
|
|
return { statusCode: 200, body: `No relevant transactions for ${dateStr}` };
|
|
}
|
|
|
|
// Load all check payments from DynamoDB to match against
|
|
const payments = [];
|
|
let lastKey;
|
|
do {
|
|
const result = await ddb.send(
|
|
new ScanCommand({
|
|
TableName: TABLE_NAME,
|
|
FilterExpression: "begins_with(pk, :prefix) AND #m = :method",
|
|
ExpressionAttributeNames: { "#m": "method" },
|
|
ExpressionAttributeValues: { ":prefix": "payment#", ":method": "Check" },
|
|
ExclusiveStartKey: lastKey,
|
|
})
|
|
);
|
|
payments.push(...result.Items);
|
|
lastKey = result.LastEvaluatedKey;
|
|
} while (lastKey);
|
|
|
|
// Build a map of check_number -> payment for matching
|
|
const checkMap = new Map();
|
|
for (const p of payments) {
|
|
checkMap.set(p.check_number, p);
|
|
}
|
|
|
|
let matched = 0;
|
|
|
|
for (const txn of relevant) {
|
|
const bankRef = txn.bankReference || "";
|
|
|
|
// BoA may append extra digits to the reference number
|
|
// Try to find a check_number that the bankReference starts with
|
|
const matchedPayment = payments.find((p) =>
|
|
p.check_number && bankRef.startsWith(p.check_number)
|
|
);
|
|
|
|
if (!matchedPayment) {
|
|
console.log(`No match for bankReference: ${bankRef}`);
|
|
continue;
|
|
}
|
|
|
|
const clearStatus = txn.transactionCode === "255" ? "Cleared" : "Returned";
|
|
|
|
await ddb.send(
|
|
new UpdateCommand({
|
|
TableName: TABLE_NAME,
|
|
Key: { pk: matchedPayment.pk },
|
|
UpdateExpression: "SET clear_status = :status, bank_reference = :ref, cleared_date = :date",
|
|
ExpressionAttributeValues: {
|
|
":status": clearStatus,
|
|
":ref": bankRef,
|
|
":date": txn.valueDate || dateStr,
|
|
},
|
|
})
|
|
);
|
|
|
|
matched++;
|
|
console.log(`${clearStatus}: check ${matchedPayment.check_number} (bankRef: ${bankRef})`);
|
|
}
|
|
|
|
console.log(`Processed ${relevant.length} transactions, matched ${matched} payments`);
|
|
return {
|
|
statusCode: 200,
|
|
body: `${matched} of ${relevant.length} transactions matched to payments`,
|
|
};
|
|
};
|