Fees, reserves and contracts

What it is

Four related pricing concepts:

Funding particulars

The supplier-level funding record: facility limit, legal-signed flag, guarantor, covenants, and the current fee schedule.

Fee schedules and tiers

Reusable pricing definitions maintained centrally under Manage, assigned to a supplier or to a supplier-marketplace link.

Reserve

The percentage of incoming marketplace funds withheld — configured per marketplace link, together with minimum-fee overrides and opt-in periods.

Long-term contracts

The signed agreement, templated per marketplace, with a history.

Where it lives

Concern Location

Supplier fees tab

/suppliers/:supplierKey/fees/{purchase,payment,reserve,reserve/edit/:marketplaceKey} → suppliers/supplier/supplier-fees/

Fees service

supplier-fees/service/supplier-fees.service.ts

Fee models

supplier-fees/model/{purchase-fee,payment-fee,reserve}.model.ts

Funding particulars

suppliers/services/funding-particulars.service.ts (+ mock), suppliers/model/funding-particulars.model.ts

Per-marketplace pricing

suppliers/services/supplier-marketplace-funding.service.ts, supplier-marketplace-min-fee-overrides.service.ts, supplier-marketplace-opt-in-periods.service.ts

Central fee settings

/manage/fee-settings/{list,add,:id/edit} → manage/fee-settings/ with service/fee-settings.service.ts

Contracts

/suppliers/:supplierKey/contracts → suppliers/supplier/supplier-contracts/; /manage/contracts → manage/contracts/; shared/services/long-term-contracts/long-term-contracts.service.ts (+ abstract + mock)

Funding particulars

FundingParticularsModel is deliberately small:

export class FundingParticularsModel {
  legalSigned: boolean;
  fundPartStatus: string;
  fundFacilityLimit: number;
  guarantor: string;
  supplierCovenants: string;
}

It is loaded as part of the supplier aggregate forkJoin (and is null for ADVANCE_AUDITORS users), and is one of the two models that UPDATE_MODEL_PART_SUCCESS merges saved card values into — so a card editing fundFacilityLimit updates both this and SupplierDetailsModel. See partial editing.

Endpoints (internalApiUrl, via SupplierFeesService):

GET suppliers/:key/funding_particulars                        supplier-level record
PUT suppliers/:key/funding_particulars                        update
GET suppliers/:key/funding_particulars/:marketplaceKey        per-marketplace record
PUT suppliers/:key/funding_particulars/:marketplaceKey        { currentFeeScheduleKey }
PUT suppliers/:key                                            { loadNetEarnings }

Two things stand out:

  • Assigning a fee schedule to a marketplace is a PUT of a single field (currentFeeScheduleKey) on the per-marketplace funding record. The schedule itself is defined centrally.

  • loadNetEarnings — whether earnings are loaded gross or net — is a supplier-level PUT on the supplier resource, even though it appears under Fees.

Per-marketplace pricing

Everything below hangs off the marketplace link (mpSupKey), not the supplier. All under suppliers/:key/funding_particulars/:mpSupKey:

Endpoint Managed by

GET/PUT suppliers/:key/funding_particulars/:mpSupKey

SupplierMarketplaceFundingService — the marketplace funding record (SupplierMarketplaceFundingModel), including the reserve percentage.

GET/PUT …/:mpSupKey/min_fee_overrides

SupplierMarketplaceMinFeeOverridesService — overrides of the minimum fee for this link (SupplierMarketplaceMinFeeOverridesModel[]).

GET/PUT …/:mpSupKey/opt_in_periods, PUT …/:mpSupKey/opt_in_periods/:mpSupOptInPeriodKey

SupplierMarketplaceOptInPeriodsService — the date ranges in which this marketplace is opted into funding (SupplierMarketplaceOptInPeriodModel[]).

In the store these land in SupplierModel as three maps keyed by mpSupKey: marketplaceFundingModels, marketplaceOptInPeriodModels, marketplaceMinFeeOverridesModels.

Opt-in rows have dedicated actions in the supplier slice — ADD_OPT_IN_ROW, DELETE_OPT_IN_ROW (each with _SUCCESS / _ERROR) and ADD_OPT_IN_ROW_CLEAR — with addOptInProgress / deleteOptInProgress flags for button state.

SupplierDetailsModel.oldestOptIn is the earliest opt-in date across all marketplaces. It is a supplier-level rollup of link-level data, so it will not change until the supplier record is re-fetched after an opt-in edit.

The fees tab

Child route Screen

fees/purchase

PurchaseFeesComponent — fees charged when Payability purchases the receivable.

fees/payment

PaymentFeesComponent — fees charged on paying the supplier.

fees/reserve

ReserveComponent — the reserve percentage per marketplace.

fees/reserve/edit/:marketplaceKey

EditMarketplaceComponent — edit one marketplace’s reserve.

Unlike most supplier tabs, the fees routes carry no extra permission restriction — the fees parent has no RESTRICTED_*_CONFIG spread. Access control for fee edits rests on the INT_FEE_UPD permission being enforced by the backend and on UI-level ngxPermissions directives inside the components.

Central fee settings

/manage/fee-settings maintains the reusable schedules. FeeSettingsService on internalApiUrl:

Endpoint Purpose

GET fee_schedule_types

The available schedule types (drives the add/edit form).

POST fee_schedules

Create a schedule.

PUT fee_schedules/:feeScheduleKey

Rename / retype ({ feeScheduleTypeCode, feeScheduleName }).

PUT fee_schedule_tier/:feeScheduleKey

Replace the tier table ({ tiers: […​] }).

PUT fee_schedules/:feeScheduleKey/extra

Toggle free access ({ freeAccess }).

DELETE fee_schedule_remove/:feeScheduleKey

Delete.

Endpoint naming is inconsistent — plural fee_schedules for CRUD, singular fee_schedule_tier for tiers, and a verb-style fee_schedule_remove for delete. Do not infer a URL; read the service.

Also note editFeeSetting replaces the entire tier array. A partial update is not supported; read, modify, write the whole set.

Adjusting a fee on an individual transaction is a different operation — the Adjust Payability Fee action, see lifecycle actions.

Long-term contracts

LongTermContractsService (in shared/services/, with an abstract base and a mock) on internalApiUrl under long_term_contract:

Endpoint Purpose

GET long_term_contract/template/:marketplaceKey/standard

The standard template for a marketplace.

GET long_term_contract/template

All templates.

GET long_term_contract/template/:templateGroupKey

One template group.

POST long_term_contract/template/:marketplaceKey/create

Create a template for a marketplace.

GET long_term_contract/:supplierKey

The supplier’s current contracts.

GET long_term_contract/:supplierKey/history

Contract history.

GET long_term_contract/:contractKey

One contract.

POST long_term_contract/:marketplaceSupplierKey/create

Create a contract for a marketplace link.

GET long_term_contract/:marketplaceSupplierKey/active

The active contract for a link.

Note the path-parameter ambiguity: long_term_contract/:x is used with a supplier key, a contract key and a marketplace-supplier key depending on the method. The distinction lives only in the method names.

Contracts are loaded as part of the supplier aggregate — both current and historical — which is why opening any supplier tab issues two contract calls (and none at all for ADVANCE_AUDITORS users). See suppliers.

Screens:

  • /suppliers/:key/contracts — SupplierContractsComponent, the per-supplier view.

  • /manage/contracts — ContractsComponent, template administration.

  • suppliers/supplier/add-contract/ — the "Add New Default Contract" action from the Actions menu, shown only when the supplier is active.

The CONTRACTS__REFRESH iframe message type exists so that an EVA-rendered contract view can ask the parent to refresh — see iframe messaging.

Business rules

Rule Detail

Pricing is layered

Central fee schedule → assigned to the supplier-marketplace funding record → optionally overridden per link by minimum-fee overrides. The most specific level wins, and the resolution happens in the backend.

Reserve lives on the link

There is no single supplier reserve percentage; each marketplace link has its own. The advance calculator’s reserveForRepayments is a separate concept — the repayment reserve on an offer, not the marketplace funding reserve. See advances.

Opt-in gates funding

A marketplace only contributes funding inside an opt-in period.

legalSigned and contracts are distinct

FundingParticularsModel.legalSigned is a flag on the funding record; the long-term contract records are separate documents. Both must be right.

Gotchas

  • MIN_TOTAL_FEE / MAX_TOTAL_FEE in the advance calculator (5 % / 40 %) are advance fee bounds, unrelated to fee schedules here.

  • isMpReserveForPaymentEmpty on the calculator state refers to the marketplace reserve being zero — the one place the two reserve concepts touch.

  • SupplierFeesService builds apiUrl as internalApiUrl + 'suppliers' (no trailing slash) and prefixes paths with /. The marketplace pricing services do the same. Other supplier services use a trailing slash. Read before appending.