Supplier payments and deposit accounts

What it is

Two directions, two concepts:

Outgoing payment configuration (paymentConfigKey)

How Payability pays the supplier — the method (ACH, wire, prepaid card, …) and its parameters. A supplier may have several.

Deposit account (depositAccountKey)

The supplier’s bank account. SupplierDetailsModel.depositAccountKey points at the primary one.

The tab also carries payment features (auto-payment settings), velocity limits and, for card programmes, JIT (just-in-time funding) simulation.

Where it lives

Concern Location

Routes

/suppliers/:supplierKey/payment/{list,new,edit/:paymentConfigKey,deposit-account-edit/:depositAccountKey}

Components

suppliers/supplier/supplier-payment/ — supplier-payment-list/, outgoing-payment-add/, incoming-payment-add/

Services

suppliers/services/payment-config/payment-config.service.ts (+ mock), suppliers/services/deposit-account/deposit-account.service.ts (+ mock), suppliers/services/auto-payment.service.ts, supplier-payment/outgoing-payment-add/payment-history.service.ts, supplier-payment/supplier-payment-list/supplier-payment-plaid-information/supplier-plaid.service.ts (+ mock)

Models

suppliers/model/payment-config.model.ts, deposit-account.model.ts, supplier-manual-bank-account.model.ts, transfer-payment-details.model.ts

Store

The paymentConfigsPending map in the supplier slice

Both PaymentConfigService and DepositAccountService are provided by explicit token in AppModule ({ provide: X, useClass: X }) so specs can substitute them.

Routing note

new, edit/:paymentConfigKey and deposit-account-edit/:depositAccountKey each carry their own NgxPermissionsGuard with the standard deny clause, in addition to the parent’s. Editing a payment method is therefore double-gated. list is not.

Also note edit/:paymentConfigKey and new share OutgoingPaymentAddComponent — the component distinguishes create from edit by the presence of the route parameter.

Payment configuration endpoints

All on internalApiUrl:

Endpoint Purpose

GET suppliers/:key/payment/configs

List a supplier’s payment configurations.

GET suppliers/:key/payment/configs/:paymentConfigKey

One configuration.

POST suppliers/:key/payment/configs

Create (body serialised with cerialize).

PUT suppliers/:key/payment/configs/:paymentConfigKey

Update.

DELETE suppliers/:key/payment/configs/:paymentConfigKey

Delete.

GET/PUT suppliers/:key/velocity/limits

Spending velocity limits for the supplier.

POST suppliers/:key/payment/payment_features

Auto-payment / payment feature flags (AutoPaymentService, via saveItemPart — it implements the partial-update interface).

JIT simulation

PaymentConfigService exposes five simulation endpoints used to exercise just-in-time card funding without real transactions:

POST suppliers/:key/jit/simulate/pending
POST suppliers/:key/jit/simulate/rejected
POST suppliers/:key/jit/simulate/charge
POST suppliers/:key/jit/simulate/cleared
POST suppliers/:key/jit/simulate/refund

These are operator/QA tools for the card programmes — see cards. They post null bodies and are safe only on non-production environments; the backend enforces that, not the client.

Deposit account endpoints

Endpoint Purpose

GET deposit_accounts/:depositAccountKey

Read.

POST deposit_accounts

Create.

PUT deposit_accounts/:depositAccountKey

Update.

POST deposit_accounts/:depositAccountKey

A second POST on the keyed URL — used for a document/action submission rather than creation.

DepositAccountService is one of the few services that also reaches documentsApiUrl — deposit-account supporting documents live in the documents Lambda, not the internal API.

Manual bank accounts can be bulk imported: BatchImportService.addNewMBAccounts posts a CSV to deposit_accounts/csv_upload. Similarly pre-funded cards go to payment/configs/csv_upload. See bulk actions.

Routing-number validation

Bank routing numbers are validated with a status vocabulary rather than a boolean.

suppliers/enums/routing-number-validation-status.enum.ts:

export enum RoutingNumberValidationStatus {
  INCORRECT_ROUTING_NUM,
  VERIFIED,
  PENDING_VERIFICATION,
  INSUFFICIENT_FOR_NATIONAL_PAYMENT,
  INSUFFICIENT_FOR_INTERNATIONAL_PAYMENT,
  NONE,
}

Each status maps to a message and a severity via RoutingNumberValidationStatusConfig and RoutingNumberValidationStatusType (success / danger / warning).

The two INSUFFICIENT_FOR_* statuses are the interesting ones: the routing number is valid but the bank cannot receive that payment type. National and international are distinguished, so a supplier can be payable domestically and not by wire.

A form validator enforces verification (supplier-payment/routing-num.validator.ts):

export function routingNumberStatus(value): ValidatorFn {
  return (control) => {
    const isVerified = value === 'VERIFIED';
    return isVerified ? { routingNumNotVerified: { value: control.value } } : null;
  };
}

The logic reads inverted: the error routingNumNotVerified is returned when the status is 'VERIFIED'. Either the error key is misnamed or the condition is wrong. It also compares against the string 'VERIFIED' while the enum member is numeric. Investigate against the calling template before relying on this validator.

Plaid bank verification

supplier-payment-list/supplier-payment-plaid-information/supplier-plaid.service.ts (+ mock) surfaces Plaid-derived bank information for a payment method. The app does not embed the Plaid Link SDK — the data arrives through the internal API. The transaction-level Plaid data appears separately as an underwriting report tab (ExternalReports.PLAID). See underwriting.

Payment history

outgoing-payment-add/payment-history.service.ts provides the history shown alongside a payment configuration — prior payments made using that method.

Broader transaction views are separate tabs: /suppliers/:key/transaction-summary (suppliers/supplier/supplier-transaction-summary/) and /suppliers/:key/transaction-details (supplier-transaction-details/), with suppliers/supplier/transaction-modal/ for individual transaction detail.

Store integration

Payment configurations are the one area that uses the actionId idiom rather than per-operation flags. Actions in the supplier slice:

Action Effect

FETCH_SUPPLIER_PAYMENT_CONFIGS / _ERROR

Loads into SupplierModel.paymentConfigsModels, keyed by paymentConfigKey.

CREATE_SUPPLIER_PAYMENT_CONFIGS / _SUCCESS / _ERROR

Create.

UDPATE_SUPPLIER_PAYMENT_CONFIGS (sic) / UPDATE_…_SUCCESS / _ERROR

Update. Note the misspelled request constant.

DELETE_SUPPLIER_PAYMENT_CONFIGS / _SUCCESS / _ERROR

Delete.

PC_RESPONSE_SUCCESS / PC_RESPONSE_ERROR / PC_CLEAR_STATUS

Write and clear entries in paymentConfigsPending[actionId] with PaymentConfigStatus.SUCCESS | ERROR.

The caller generates an actionId, dispatches the operation, watches paymentConfigsPending[actionId], then dispatches PC_CLEAR_STATUS. Forgetting the clear leaves a stale status that the next render of the same actionId will pick up.

selectPaymentMethods is exported from supplier.actions.ts and is used by AppComponent to tell EVA whether the supplier has an EFS card (paymentType === 'EFS' or pcardAvailable) — see iframe messaging.

Money-moving operations that are not payment configuration live in the Actions menu and are documented in lifecycle actions and transactions: add receivable, add chargeback, add bad-debt recovery, add write-off, adjust the Payability fee, add a fund-transfer request, and the marketplace payment/statement adjustments.

Batch-level payment release is processes and batches, and the pre-release treasury check is balance approval.

Gotchas

  • MANAGE_LINKING_PAYMENT_GATEWAYS is a permission in the enum; gateway linking on the supplier’s marketplace record is separate from payment configuration — see marketplaces.

  • ADD_PAYMENT_GATEWAY in the supplier reducer resets the slice to initialState (keeping only the mutated supplierModel), discarding the multi-supplier cache and every part-edit map.

  • AutoPaymentService builds its URL as internalApiUrl + 'suppliers' and then concatenates /${supplierKey}/…, so the base has no trailing slash while other services in the same folder do include one. Match the file you are editing.