mirror of
https://github.com/Sea-Haven-Industries/payments-dashboard.git
synced 2026-09-30 05:23:12 +00:00
Add CashPro check issue/cancel on CSV upload
- CSV processor detects new checks and submits add_issue to CashPro - Checks updated to voided/cancelled trigger cancel_issue to CashPro - Added SSM params for boa-api-token, boa-account-number, boa-company-id to both processPaymentCsv and fetchBoaTransactions Lambdas - Increased CSV processor timeout to 120s for API calls
This commit is contained in:
parent
8e262c3b88
commit
33cd9759b4
2 changed files with 130 additions and 6 deletions
|
|
@ -1,11 +1,29 @@
|
|||
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
|
||||
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
||||
import { DynamoDBDocumentClient, PutCommand, UpdateCommand } from "@aws-sdk/lib-dynamodb";
|
||||
import { DynamoDBDocumentClient, GetCommand, PutCommand, UpdateCommand } from "@aws-sdk/lib-dynamodb";
|
||||
import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm";
|
||||
import { parse } from "csv-parse/sync";
|
||||
|
||||
const s3 = new S3Client();
|
||||
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;
|
||||
}
|
||||
|
||||
// Convert MM/DD/YYYY to YYYY-MM-DD
|
||||
function toISODate(mdyDate) {
|
||||
const parts = String(mdyDate).split("/");
|
||||
if (parts.length !== 3) return null;
|
||||
const [mm, dd, yyyy] = parts;
|
||||
return `${yyyy}-${mm.padStart(2, "0")}-${dd.padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
export const handler = async (event) => {
|
||||
const record = event.Records[0];
|
||||
|
|
@ -36,12 +54,23 @@ export const handler = async (event) => {
|
|||
return isNaN(num) ? 0 : num;
|
||||
};
|
||||
|
||||
// Upsert each payment, preserving clear_status/bank_reference/cleared_date if they exist
|
||||
const cancelStatuses = ["voided", "cancelled"];
|
||||
const newChecks = [];
|
||||
const cancelChecks = [];
|
||||
|
||||
// Upsert each payment, tracking new and cancelled checks
|
||||
let count = 0;
|
||||
for (const row of normalizedRows) {
|
||||
const checkNumber = (row["Check Number"] || "").trim();
|
||||
const method = (row["Method"] || "").trim();
|
||||
const status = (row["Status"] || "").trim();
|
||||
const pk = `payment#${checkNumber}`;
|
||||
|
||||
// Check if record already exists (for detecting new vs updated)
|
||||
const { Item: existing } = await ddb.send(
|
||||
new GetCommand({ TableName: TABLE_NAME, Key: { pk } })
|
||||
);
|
||||
|
||||
await ddb.send(
|
||||
new UpdateCommand({
|
||||
TableName: TABLE_NAME,
|
||||
|
|
@ -61,18 +90,92 @@ export const handler = async (event) => {
|
|||
"#status": "status",
|
||||
},
|
||||
ExpressionAttributeValues: {
|
||||
":method": (row["Method"] || "").trim(),
|
||||
":method": method,
|
||||
":payee": (row["Payee"] || "").trim(),
|
||||
":check_number": checkNumber,
|
||||
":invoice_numbers": (row["Invoice Numbers"] || "").trim(),
|
||||
":send_payment_on": (row["Send Payment On"] || "").trim(),
|
||||
":amount_usd": parseAmount(row["Amount in USD"]),
|
||||
":status": (row["Status"] || "").trim(),
|
||||
":status": status,
|
||||
":company_subsidiary": (row["Company/Subsidiary"] || "").trim(),
|
||||
},
|
||||
})
|
||||
);
|
||||
count++;
|
||||
|
||||
// Only process checks for CashPro
|
||||
if (method !== "Check" || !checkNumber) continue;
|
||||
|
||||
if (!existing) {
|
||||
// New check → issue
|
||||
newChecks.push({
|
||||
checkNumber,
|
||||
amount: parseAmount(row["Amount in USD"]).toFixed(2),
|
||||
issueDate: toISODate(row["Send Payment On"] || ""),
|
||||
});
|
||||
} else if (
|
||||
cancelStatuses.includes(status.toLowerCase()) &&
|
||||
!cancelStatuses.includes((existing.status || "").toLowerCase())
|
||||
) {
|
||||
// Existing check now voided/cancelled → cancel
|
||||
cancelChecks.push({
|
||||
checkNumber,
|
||||
amount: parseAmount(row["Amount in USD"]).toFixed(2),
|
||||
issueDate: toISODate(row["Send Payment On"] || ""),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Submit to CashPro if there are any new issues or cancels
|
||||
if (newChecks.length || cancelChecks.length) {
|
||||
const [apiToken, accountNumber, companyId] = await Promise.all([
|
||||
getSSMParam(process.env.BOA_API_TOKEN_PARAM),
|
||||
getSSMParam(process.env.BOA_ACCOUNT_NUMBER_PARAM),
|
||||
getSSMParam(process.env.BOA_COMPANY_ID_PARAM),
|
||||
]);
|
||||
|
||||
const submitToBoA = async (items, action) => {
|
||||
const issueList = items.map((item) => ({
|
||||
accountNumber,
|
||||
checkNumber: item.checkNumber,
|
||||
amount: item.amount,
|
||||
issueAction: action,
|
||||
issueDate: item.issueDate,
|
||||
}));
|
||||
|
||||
const res = await fetch(
|
||||
`${BOA_BASE_URL}/cashpro/checkmanagement/v1/check-issues`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
companyId,
|
||||
},
|
||||
body: JSON.stringify({ issueList }),
|
||||
}
|
||||
);
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
console.error(`BoA ${action} error ${res.status}:`, JSON.stringify(data));
|
||||
throw new Error(`BoA ${action} failed: ${res.status}`);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`BoA ${action}: ${data.processedItems}/${data.totalItems} processed, ${data.unprocessedItems} failed`
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
if (newChecks.length) {
|
||||
await submitToBoA(newChecks, "add_issue");
|
||||
}
|
||||
|
||||
if (cancelChecks.length) {
|
||||
await submitToBoA(cancelChecks, "cancel_issue");
|
||||
}
|
||||
}
|
||||
|
||||
// Update metadata record
|
||||
|
|
@ -88,6 +191,11 @@ export const handler = async (event) => {
|
|||
})
|
||||
);
|
||||
|
||||
console.log(`Upserted ${count} payments from ${key}`);
|
||||
return { statusCode: 200, body: `Upserted ${count} payments` };
|
||||
console.log(
|
||||
`Upserted ${count} payments, ${newChecks.length} issued, ${cancelChecks.length} cancelled`
|
||||
);
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: `Upserted ${count}, issued ${newChecks.length}, cancelled ${cancelChecks.length}`,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -131,6 +131,13 @@ Resources:
|
|||
Properties:
|
||||
FunctionName: payments-processPaymentCsv
|
||||
Handler: src/processPaymentCsv.handler
|
||||
Timeout: 120
|
||||
Environment:
|
||||
Variables:
|
||||
BOA_BASE_URL: https://sandbox.cashpro.bankofamerica.com
|
||||
BOA_API_TOKEN_PARAM: /payments-dashboard/boa-api-token
|
||||
BOA_ACCOUNT_NUMBER_PARAM: /payments-dashboard/boa-account-number
|
||||
BOA_COMPANY_ID_PARAM: /payments-dashboard/boa-company-id
|
||||
VpcConfig:
|
||||
SubnetIds:
|
||||
- !Ref PrivateSubnet
|
||||
|
|
@ -152,6 +159,12 @@ Resources:
|
|||
BucketName: !Sub seahaven-payments-csv-${AWS::AccountId}
|
||||
- DynamoDBCrudPolicy:
|
||||
TableName: !Ref DashboardTable
|
||||
- SSMParameterReadPolicy:
|
||||
ParameterName: payments-dashboard/boa-api-token
|
||||
- SSMParameterReadPolicy:
|
||||
ParameterName: payments-dashboard/boa-account-number
|
||||
- SSMParameterReadPolicy:
|
||||
ParameterName: payments-dashboard/boa-company-id
|
||||
- Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
|
|
@ -210,6 +223,7 @@ Resources:
|
|||
BOA_BASE_URL: https://sandbox.cashpro.bankofamerica.com
|
||||
BOA_API_TOKEN_PARAM: /payments-dashboard/boa-api-token
|
||||
BOA_ACCOUNT_NUMBER_PARAM: /payments-dashboard/boa-account-number
|
||||
BOA_COMPANY_ID_PARAM: /payments-dashboard/boa-company-id
|
||||
Events:
|
||||
DailySchedule:
|
||||
Type: Schedule
|
||||
|
|
@ -224,6 +238,8 @@ Resources:
|
|||
ParameterName: payments-dashboard/boa-api-token
|
||||
- SSMParameterReadPolicy:
|
||||
ParameterName: payments-dashboard/boa-account-number
|
||||
- SSMParameterReadPolicy:
|
||||
ParameterName: payments-dashboard/boa-company-id
|
||||
- Version: "2012-10-17"
|
||||
Statement:
|
||||
- Effect: Allow
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue