From 957fb726ab92dad86bd827d620f248eac313b41d Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Mon, 13 Apr 2026 16:08:57 -0400 Subject: [PATCH] Add OAuth token exchange to BoA sandbox test script Use correct sandbox base URL (api-sb.bofa.com), proper OAuth client-credentials flow with applicationID, and routing number for transaction inquiries. Both APIs now return 200 in sandbox. --- scripts/test-boa-sandbox.js | 96 +++++++++++++++++++++++++++++++------ 1 file changed, 81 insertions(+), 15 deletions(-) diff --git a/scripts/test-boa-sandbox.js b/scripts/test-boa-sandbox.js index fbc3cb6..3e93e30 100644 --- a/scripts/test-boa-sandbox.js +++ b/scripts/test-boa-sandbox.js @@ -1,8 +1,11 @@ /** * 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. + * Reads credentials from SSM Parameter Store, exchanges them for + * OAuth Bearer tokens, then makes test API calls to both Check + * Management and Reporting APIs. + * + * Prints full responses so you can capture the client ID, timestamp, + * and transactionIdentification for BoA production onboarding. * * Usage: * node scripts/test-boa-sandbox.js @@ -11,7 +14,8 @@ import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm"; const ssm = new SSMClient(); -const BOA_BASE_URL = "https://developer.bankofamerica.com"; +const SANDBOX_BASE = "https://api-sb.bofa.com"; +const AUTH_URL = `${SANDBOX_BASE}/authn/v1/client-authentication`; async function getSSMParam(name) { const { Parameter } = await ssm.send( @@ -20,31 +24,88 @@ async function getSSMParam(name) { return Parameter.Value; } +async function getAccessToken(applicationID, clientId, clientSecret) { + console.log(` Requesting token for ${applicationID}...`); + + const res = await fetch(AUTH_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + applicationID, + authn: { + client_id: clientId, + client_secret: clientSecret, + }, + }), + }); + + const text = await res.text(); + console.log(` Auth response (${res.status}):`, text, "\n"); + + if (!res.ok) { + throw new Error(`Auth failed for ${applicationID}: ${res.status} - ${text}`); + } + + const data = JSON.parse(text); + return data.access_token; +} + async function main() { console.log("Loading credentials from SSM...\n"); - const [checkMgmtToken, accountInfoToken, accountNumber, companyId] = await Promise.all([ + const [ + checkMgmtClientId, + checkMgmtSecret, + accountInfoClientId, + accountInfoSecret, + accountNumber, + companyId, + ] = await Promise.all([ + getSSMParam("/payments-dashboard/boa-check-mgmt-client-id"), getSSMParam("/payments-dashboard/boa-check-mgmt-token"), + getSSMParam("/payments-dashboard/boa-account-info-client-id"), 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"); + console.log("Credentials loaded.\n"); - // --- Test 1: Check Issue (add_issue with a test check) --- + // --- Step 1: Get OAuth tokens for both APIs --- console.log("=".repeat(60)); - console.log("TEST 1: Check Issue (add_issue)"); + console.log("STEP 1: OAuth Token Exchange"); + console.log("=".repeat(60)); + + console.log("\n[Check Management]"); + const checkMgmtBearerToken = await getAccessToken( + "app_SeaHavenIndustries_Checkmanagement_SB", + checkMgmtClientId, + checkMgmtSecret + ); + + console.log("[Account Info / Reporting]"); + const accountInfoBearerToken = await getAccessToken( + "app_SeaHavenIndustries_Reporting_SB", + accountInfoClientId, + accountInfoSecret + ); + + console.log("Both tokens acquired.\n"); + + // --- Step 2: 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, + issueAction: "add_Issue", checkNumber: "999999", amount: "1.00", - issueAction: "add_issue", issueDate: new Date().toISOString().split("T")[0], + payee: "Sandbox Test", }, ], }; @@ -53,12 +114,12 @@ async function main() { try { const issueRes = await fetch( - `${BOA_BASE_URL}/cashpro/checkmanagement/v1/check-issues`, + `${SANDBOX_BASE}/cashpro/checkmanagement/v1/check-issues`, { method: "POST", headers: { "Content-Type": "application/json", - Authorization: `Bearer ${checkMgmtToken}`, + Authorization: `Bearer ${checkMgmtBearerToken}`, companyId, }, body: JSON.stringify(issuePayload), @@ -75,7 +136,7 @@ async function main() { console.error("Check Issue request failed:", err.message); } - // --- Test 2: Previous Day Transaction Inquiry --- + // --- Step 3: Previous Day Transaction Inquiry --- console.log("\n" + "=".repeat(60)); console.log("TEST 2: Previous Day Transaction Inquiry"); console.log("=".repeat(60)); @@ -85,21 +146,26 @@ async function main() { const dateStr = yesterday.toISOString().split("T")[0]; const inquiryPayload = { - accounts: [{ accountNumber, bankId: "BOFAFRPP" }], fromDate: dateStr, toDate: dateStr, + accounts: [ + { + accountNumber, + bankId: "021000322", + }, + ], }; console.log("Request:", JSON.stringify(inquiryPayload, null, 2), "\n"); try { const inquiryRes = await fetch( - `${BOA_BASE_URL}/cashpro/reporting/v1/transaction-inquiries/previous-day`, + `${SANDBOX_BASE}/cashpro/reporting/v1/transaction-inquiries/previous-day`, { method: "POST", headers: { "Content-Type": "application/json", - Authorization: `Bearer ${accountInfoToken}`, + Authorization: `Bearer ${accountInfoBearerToken}`, }, body: JSON.stringify(inquiryPayload), }