diff --git a/.gitignore b/.gitignore index efc9465..24a0e44 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ .aws-sam/ samconfig.toml +data/ diff --git a/scripts/seed-bank-status.js b/scripts/seed-bank-status.js index 5ad1045..2e7449d 100644 --- a/scripts/seed-bank-status.js +++ b/scripts/seed-bank-status.js @@ -52,7 +52,7 @@ function toISODate(dateStr) { return null; } -async function updateStatus(checkNumber, status, paidDate) { +async function updateStatus(checkNumber, status, { paidDate, amount, issueDate, method } = {}) { const pk = `payment#${checkNumber}`; const expr = ["#status = :s"]; const names = { "#status": "status" }; @@ -62,6 +62,23 @@ async function updateStatus(checkNumber, status, paidDate) { expr.push("paid_date = :pd"); values[":pd"] = paidDate; } + if (amount != null) { + expr.push("amount_usd = :amt"); + values[":amt"] = amount; + } + if (issueDate) { + expr.push("send_payment_on = :sd"); + values[":sd"] = issueDate; + } + if (checkNumber) { + expr.push("check_number = :cn"); + values[":cn"] = checkNumber; + } + if (method) { + expr.push("#method = :m"); + names["#method"] = "method"; + values[":m"] = method; + } await ddb.send( new UpdateCommand({ @@ -87,7 +104,9 @@ async function main() { for (const row of rows) { const checkNumber = row[0]?.trim(); if (!checkNumber) continue; - statusMap.set(checkNumber, { status: "Issued", paidDate: null }); + const amount = parseFloat(String(row[4] || "0").replace(/,/g, "")); + const issueDate = row[1]?.trim() || null; + statusMap.set(checkNumber, { status: "Issued", paidDate: null, amount: isNaN(amount) ? 0 : amount, issueDate, method: "Check" }); } console.log(` ${rows.length} positive pay entries`); } @@ -99,7 +118,18 @@ async function main() { for (const row of rows) { const checkNumber = (row["Check Number"] || "").trim(); if (!checkNumber) continue; - statusMap.set(checkNumber, { status: "Outstanding", paidDate: null }); + const amount = parseFloat(String(row["Amount"] || "0").replace(/,/g, "")); + const rawDate = (row["Issue Date"] || "").trim(); + // Normalize 2-digit year to 4-digit: "12/10/24" → "12/10/2024" + let issueDate = rawDate || null; + if (rawDate) { + const parts = rawDate.split("/"); + if (parts.length === 3 && parts[2].length === 2) { + parts[2] = `20${parts[2]}`; + issueDate = parts.join("/"); + } + } + statusMap.set(checkNumber, { status: "Outstanding", paidDate: null, amount: isNaN(amount) ? 0 : amount, issueDate, method: "Check" }); } console.log(` ${rows.length} outstanding entries`); } @@ -120,15 +150,16 @@ async function main() { } seen.add(checkNumber); const paidDate = toISODate(row["Paid Date"] || row["CD Volume Number"] || ""); - statusMap.set(checkNumber, { status: "Cleared", paidDate }); + const amount = parseFloat(String(row["Amount"] || "0").replace(/,/g, "")); + statusMap.set(checkNumber, { status: "Cleared", paidDate, amount: isNaN(amount) ? 0 : amount }); } console.log(` ${seen.size} unique cleared checks (${deduped} duplicates skipped)`); } // Write to DynamoDB let count = 0; - for (const [checkNumber, { status, paidDate }] of statusMap) { - await updateStatus(checkNumber, status, paidDate); + for (const [checkNumber, { status, paidDate, amount, issueDate, method }] of statusMap) { + await updateStatus(checkNumber, status, { paidDate, amount, issueDate, method }); count++; } diff --git a/scripts/test-boa-sandbox.js b/scripts/test-boa-sandbox.js new file mode 100644 index 0000000..fbc3cb6 --- /dev/null +++ b/scripts/test-boa-sandbox.js @@ -0,0 +1,126 @@ +/** + * One-off script to test BoA CashPro sandbox API connectivity. + * Reads credentials from SSM Parameter Store (same params as production), + * makes test API calls, and prints the full responses so you can capture + * the client ID, timestamp, and transactionIdentification for BoA onboarding. + * + * Usage: + * node scripts/test-boa-sandbox.js + */ + +import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm"; + +const ssm = new SSMClient(); +const BOA_BASE_URL = "https://developer.bankofamerica.com"; + +async function getSSMParam(name) { + const { Parameter } = await ssm.send( + new GetParameterCommand({ Name: name, WithDecryption: true }) + ); + return Parameter.Value; +} + +async function main() { + console.log("Loading credentials from SSM...\n"); + + const [checkMgmtToken, accountInfoToken, accountNumber, companyId] = await Promise.all([ + getSSMParam("/payments-dashboard/boa-check-mgmt-token"), + getSSMParam("/payments-dashboard/boa-account-info-token"), + getSSMParam("/payments-dashboard/boa-account-number"), + getSSMParam("/payments-dashboard/boa-company-id"), + ]); + + console.log("Credentials loaded. Testing sandbox endpoints...\n"); + + // --- Test 1: Check Issue (add_issue with a test check) --- + console.log("=".repeat(60)); + console.log("TEST 1: Check Issue (add_issue)"); + console.log("=".repeat(60)); + + const issuePayload = { + issueList: [ + { + accountNumber, + checkNumber: "999999", + amount: "1.00", + issueAction: "add_issue", + issueDate: new Date().toISOString().split("T")[0], + }, + ], + }; + + console.log("Request:", JSON.stringify(issuePayload, null, 2), "\n"); + + try { + const issueRes = await fetch( + `${BOA_BASE_URL}/cashpro/checkmanagement/v1/check-issues`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${checkMgmtToken}`, + companyId, + }, + body: JSON.stringify(issuePayload), + } + ); + + const issueHeaders = Object.fromEntries(issueRes.headers.entries()); + const issueText = await issueRes.text(); + + console.log("Status:", issueRes.status); + console.log("Response Headers:", JSON.stringify(issueHeaders, null, 2)); + console.log("Response Body:", issueText); + } catch (err) { + console.error("Check Issue request failed:", err.message); + } + + // --- Test 2: Previous Day Transaction Inquiry --- + console.log("\n" + "=".repeat(60)); + console.log("TEST 2: Previous Day Transaction Inquiry"); + console.log("=".repeat(60)); + + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + const dateStr = yesterday.toISOString().split("T")[0]; + + const inquiryPayload = { + accounts: [{ accountNumber, bankId: "BOFAFRPP" }], + fromDate: dateStr, + toDate: dateStr, + }; + + console.log("Request:", JSON.stringify(inquiryPayload, null, 2), "\n"); + + try { + const inquiryRes = await fetch( + `${BOA_BASE_URL}/cashpro/reporting/v1/transaction-inquiries/previous-day`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${accountInfoToken}`, + }, + body: JSON.stringify(inquiryPayload), + } + ); + + const inquiryHeaders = Object.fromEntries(inquiryRes.headers.entries()); + const inquiryText = await inquiryRes.text(); + + console.log("Status:", inquiryRes.status); + console.log("Response Headers:", JSON.stringify(inquiryHeaders, null, 2)); + console.log("Response Body:", inquiryText); + } catch (err) { + console.error("Transaction Inquiry request failed:", err.message); + } + + console.log("\n" + "=".repeat(60)); + console.log("Done. Look for: client ID, timestamp, transactionIdentification"); + console.log("=".repeat(60)); +} + +main().catch((err) => { + console.error("Fatal error:", err); + process.exit(1); +});