mirror of
https://github.com/Sea-Haven-Industries/sh-mcp.git
synced 2026-10-04 17:12:04 +00:00
123 lines
4.2 KiB
TypeScript
123 lines
4.2 KiB
TypeScript
|
|
/**
|
||
|
|
* PaymentsClient interface + stub implementation.
|
||
|
|
*
|
||
|
|
* The real implementation would use the AWS SDK DynamoDB DocumentClient
|
||
|
|
* targeting the PaymentsDashboard table. That call is clearly marked below
|
||
|
|
* and guarded behind the interface so tests can inject a mock without any
|
||
|
|
* AWS credentials or network access at import time.
|
||
|
|
*
|
||
|
|
* TODO(auth-layer): when the real DynamoDB client is wired, pull the table
|
||
|
|
* name and region from environment variables set by the CDK stack rather than
|
||
|
|
* hard-coding them here. Credentials must come from the Lambda execution
|
||
|
|
* role (no explicit key/secret in code or Secrets Manager for IAM-auth calls).
|
||
|
|
*/
|
||
|
|
|
||
|
|
export interface Payment {
|
||
|
|
paymentId: string;
|
||
|
|
vendor: string;
|
||
|
|
vendorContact?: string;
|
||
|
|
amount: number;
|
||
|
|
currency: string;
|
||
|
|
invoiceNumber?: string;
|
||
|
|
checkNumber?: string;
|
||
|
|
/** ISO-8601 date string */
|
||
|
|
paymentDate: string;
|
||
|
|
status: "pending" | "cleared" | "voided" | "failed";
|
||
|
|
/** Bank account number — MUST be redacted before leaving the server */
|
||
|
|
bankAccountNumber?: string;
|
||
|
|
/** Bank routing number — MUST be redacted before leaving the server */
|
||
|
|
bankRoutingNumber?: string;
|
||
|
|
/** Card number (last-four or full) — MUST be redacted before leaving the server */
|
||
|
|
cardNumber?: string;
|
||
|
|
/** ACH or wire memo */
|
||
|
|
memo?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface PaymentsQueryOptions {
|
||
|
|
limit?: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The contract every PaymentsDashboard client must satisfy.
|
||
|
|
* Tests inject a MockPaymentsClient; production injects DynamoPaymentsClient.
|
||
|
|
*/
|
||
|
|
export interface PaymentsClient {
|
||
|
|
/** Return all payments for a given vendor name (case-insensitive prefix match). */
|
||
|
|
getByVendor(
|
||
|
|
vendor: string,
|
||
|
|
opts?: PaymentsQueryOptions,
|
||
|
|
): Promise<Payment[]>;
|
||
|
|
|
||
|
|
/** Return the payment(s) matching an invoice number. */
|
||
|
|
getByInvoice(
|
||
|
|
invoiceNumber: string,
|
||
|
|
opts?: PaymentsQueryOptions,
|
||
|
|
): Promise<Payment[]>;
|
||
|
|
|
||
|
|
/** Return the payment matching a check number. */
|
||
|
|
getByCheck(
|
||
|
|
checkNumber: string,
|
||
|
|
opts?: PaymentsQueryOptions,
|
||
|
|
): Promise<Payment[]>;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Stub production implementation
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Thin wrapper around the DynamoDB PaymentsDashboard table.
|
||
|
|
*
|
||
|
|
* STUBBED: the actual DynamoDB calls are replaced with a thrown error so that
|
||
|
|
* this file is safe to import in any environment without AWS credentials. To
|
||
|
|
* activate the real implementation:
|
||
|
|
* 1. npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
|
||
|
|
* 2. Replace each `throw new Error("STUB")` block with the real query.
|
||
|
|
*
|
||
|
|
* TODO(real-impl): implement DynamoDB GSI queries:
|
||
|
|
* - VendorIndex (pk = vendor_normalized)
|
||
|
|
* - InvoiceIndex (pk = invoiceNumber)
|
||
|
|
* - CheckIndex (pk = checkNumber)
|
||
|
|
*/
|
||
|
|
export class DynamoPaymentsClient implements PaymentsClient {
|
||
|
|
private readonly tableName: string;
|
||
|
|
|
||
|
|
constructor(tableName = process.env["PAYMENTS_TABLE"] ?? "PaymentsDashboard") {
|
||
|
|
this.tableName = tableName;
|
||
|
|
// The DynamoDB DocumentClient is intentionally NOT instantiated here to
|
||
|
|
// avoid any AWS SDK import side-effects at module load time. Instantiate
|
||
|
|
// it lazily inside each method once the real implementation is added.
|
||
|
|
void this.tableName; // suppress unused-var lint until real impl lands
|
||
|
|
}
|
||
|
|
|
||
|
|
async getByVendor(
|
||
|
|
_vendor: string,
|
||
|
|
_opts?: PaymentsQueryOptions,
|
||
|
|
): Promise<Payment[]> {
|
||
|
|
// TODO(real-impl): query VendorIndex GSI with vendor_normalized = vendor.toLowerCase()
|
||
|
|
throw new Error(
|
||
|
|
"DynamoPaymentsClient is a stub — inject a real or mock client instead.",
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
async getByInvoice(
|
||
|
|
_invoiceNumber: string,
|
||
|
|
_opts?: PaymentsQueryOptions,
|
||
|
|
): Promise<Payment[]> {
|
||
|
|
// TODO(real-impl): query InvoiceIndex GSI with invoiceNumber = invoiceNumber
|
||
|
|
throw new Error(
|
||
|
|
"DynamoPaymentsClient is a stub — inject a real or mock client instead.",
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
async getByCheck(
|
||
|
|
_checkNumber: string,
|
||
|
|
_opts?: PaymentsQueryOptions,
|
||
|
|
): Promise<Payment[]> {
|
||
|
|
// TODO(real-impl): query CheckIndex GSI with checkNumber = checkNumber
|
||
|
|
throw new Error(
|
||
|
|
"DynamoPaymentsClient is a stub — inject a real or mock client instead.",
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|