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; } async function getAccessToken(applicationID, clientId, clientSecret) { const res = await fetch(`${BOA_BASE_URL}/authn/v1/client-authentication`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ applicationID, authn: { client_id: clientId, client_secret: clientSecret }, }), }); if (!res.ok) { const text = await res.text(); throw new Error(`OAuth token exchange failed: ${res.status} - ${text}`); } const data = await res.json(); return data.access_token; } export const handler = async () => { const [appId, clientId, clientSecret, accountNumber, bankId] = await Promise.all([ getSSMParam(process.env.BOA_REPORTING_APP_ID_PARAM), getSSMParam(process.env.BOA_REPORTING_CLIENT_ID_PARAM), getSSMParam(process.env.BOA_REPORTING_SECRET_PARAM), getSSMParam(process.env.BOA_ACCOUNT_NUMBER_PARAM), getSSMParam(process.env.BOA_BANK_ID_PARAM), ]); const bearerToken = await getAccessToken(appId, clientId, clientSecret); // 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 ${bearerToken}`, }, body: JSON.stringify({ fromDate: dateStr, toDate: dateStr, accounts: [{ accountNumber, bankId }], }), }); if (!res.ok) { const text = await res.text(); throw new Error(`BoA API error ${res.status}: ${text}`); } const data = await res.json(); // Response shape: { accountTransactions: [{ accountNumber, bankId, currency, transactions: [...] }] } const allTransactions = (data.accountTransactions || []).flatMap( (acct) => acct.transactions || [] ); // Filter for cleared checks (475) and returned checks (255) const relevant = allTransactions.filter( (t) => t.transactionCode === "475" || t.transactionCode === "255" ); if (!relevant.length) { console.log(`No check transactions (475/255) 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); let matched = 0; for (const txn of relevant) { const custRef = (txn.customerReference || "").replace(/^0+/, ""); const bankRef = txn.bankReference || ""; // Match customerReference (check number with leading zeros stripped) to our check_number const matchedPayment = payments.find((p) => p.check_number && p.check_number === custRef ); if (!matchedPayment) { console.log(`No match for customerReference: ${txn.customerReference} (bankRef: ${bankRef})`); continue; } const clearStatus = txn.transactionCode === "475" ? "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`, }; };