Single and bulk payment initiation, status tracking, cancellation, and lookup, per the Payment Lifecycle documentation.
Every function accepts a client name (defaults to :default) as its
last argument, matching the multi-client design in
BankingCircle.Config.
Idempotency caveat
Only Direct Debit Collections (BankingCircle.DirectDebit) carries
documented idempotency-key support at time of writing. create_single/2
and create_bulk/2 do not — if a request times out, check
get_by_transaction_reference/2 (or get_bulk/2) before blindly
resubmitting, to avoid a duplicate payment. See the moduledoc on
BankingCircle.HTTP.Middleware.Retry for how this shapes our retry
policy: neither of these POSTs is auto-retried on a 5xx/429.
Summary
Functions
Attempts to cancel every cancellable payment in a batch (same
pre-processing rule as cancel_single/2). Supports partial cancellation
— returns a summary of which rows succeeded.
Cancels a payment. Only succeeds if the payment hasn't already entered
processing — returns {:error, %BankingCircle.Error{kind: :client_error}}
if it's too late.
Cancels a trace request while it's still in Processing status.
Submits a batch of payments as a single bulk request. rows is a list
of attribute maps, each validated the same way as create_single/2; if
any row is invalid the whole batch is rejected client-side with
{:error, [{index, changeset}, ...]} before any network call, using
1-based indices matching Banking Circle's elementIndex error semantics.
Initiates a Correspondent or Agency Banking payment from your own BIC
and IBANs (POST /api/v1/payments/fi-to-fi-customer-credit-transfer-initiation).
Requires special permissions on your API user — contact your
Relationship Manager if you get an authorization error here.
Submits a single payment. attrs is validated via
BankingCircle.Schemas.Payment.changeset/1 before it ever reaches the
network — invalid input returns {:error, %Ecto.Changeset{}} without a
request being made.
Detailed stats for a bulk batch: overall status, per-status counts, total payment count, aggregated amount.
Looks up a single payment by its caller-assigned transaction reference.
Fetches the MT103 confirmation message for a completed payment.
Lightweight status-only check for a recall — cheaper than get_recall/2 when you just need to know Received/Successful/Unsuccessful.
Full payment detail, including processing history.
Lightweight status check — intended for polling, cheaper than get_single/2.
Requests a recall of a processed payment (you sent it to the wrong
place, or with wrong details). reason_code must be one of "AC03"
(invalid creditor account), "AM09" (wrong amount), or "CUST"
(requested by customer).
Initiates a trace to investigate a delayed/missing payment by contacting
the beneficiary bank. Requires the payment to be in a final status
(Processed or Rejected), under 6 months old, without an existing active
trace. Faster Payments/SEPA Instant traces go straight to Unsuccessful.
Paginated list of bulk payment batches.
Lists recall requests from the past 6 months, paginated.
Paginated list of single payments, optionally filtered by status/date range.
Rejects an incoming direct debit payment.
Types
Functions
@spec cancel_bulk(String.t(), client()) :: {:ok, map()} | {:error, BankingCircle.Error.t()}
Attempts to cancel every cancellable payment in a batch (same
pre-processing rule as cancel_single/2). Supports partial cancellation
— returns a summary of which rows succeeded.
@spec cancel_single(payment_id(), client()) :: {:ok, map()} | {:error, BankingCircle.Error.t()}
Cancels a payment. Only succeeds if the payment hasn't already entered
processing — returns {:error, %BankingCircle.Error{kind: :client_error}}
if it's too late.
@spec cancel_trace(String.t(), client()) :: {:ok, map()} | {:error, BankingCircle.Error.t()}
Cancels a trace request while it's still in Processing status.
@spec create_bulk([map()], client()) :: {:ok, map()} | {:error, BankingCircle.Error.t() | [{pos_integer(), term()}]}
Submits a batch of payments as a single bulk request. rows is a list
of attribute maps, each validated the same way as create_single/2; if
any row is invalid the whole batch is rejected client-side with
{:error, [{index, changeset}, ...]} before any network call, using
1-based indices matching Banking Circle's elementIndex error semantics.
@spec create_fi_to_fi(map(), client()) :: {:ok, map()} | {:error, BankingCircle.Error.t()}
Initiates a Correspondent or Agency Banking payment from your own BIC
and IBANs (POST /api/v1/payments/fi-to-fi-customer-credit-transfer-initiation).
Requires special permissions on your API user — contact your
Relationship Manager if you get an authorization error here.
This is a materially richer payload than a single payment: full
structured debtor/creditor addresses, and — for the "on behalf of"
scenario — separate debtor vs. ultimate debtor fields (provide
either; if both, the ultimate debtor is carried forward as the debtor
name on the wire). Given the shape variance we don't run this through
the BankingCircle.Schemas.Payment changeset — pass the raw
API-shaped-but-snake_case attrs and we camelCase top-level keys for you;
nested structures (addresses, amount) should already be maps matching
the API's field names.
FX
Three ways to convert currency on an outbound Correspondent/Agency
payment, combining this endpoint with BankingCircle.FX:
- Embedded/live rate — set
currency_of_transferdifferent fromamount.currency; conversion happens automatically at the live rate. - RFQ — get a quote via
BankingCircle.FX.request_quotes/2(30-second lock), then pass its id asfx_quote_id. - Held rate — get one via
BankingCircle.FX.held_rate/5, then pass its id asfx_quote_id.
@spec create_single(map(), client()) :: {:ok, map()} | {:error, BankingCircle.Error.t() | Ecto.Changeset.t()}
Submits a single payment. attrs is validated via
BankingCircle.Schemas.Payment.changeset/1 before it ever reaches the
network — invalid input returns {:error, %Ecto.Changeset{}} without a
request being made.
On success, returns {:ok, %{"id" => payment_id, ...}}.
@spec get_bulk(String.t(), client()) :: {:ok, map()} | {:error, BankingCircle.Error.t()}
Detailed stats for a bulk batch: overall status, per-status counts, total payment count, aggregated amount.
@spec get_by_transaction_reference(String.t(), client()) :: {:ok, map()} | {:error, BankingCircle.Error.t()}
Looks up a single payment by its caller-assigned transaction reference.
@spec get_mt103(payment_id(), client()) :: {:ok, binary()} | {:error, BankingCircle.Error.t()}
Fetches the MT103 confirmation message for a completed payment.
@spec get_recall(String.t(), client()) :: {:ok, map()} | {:error, BankingCircle.Error.t()}
@spec get_recall_status(String.t(), client()) :: {:ok, map()} | {:error, BankingCircle.Error.t()}
Lightweight status-only check for a recall — cheaper than get_recall/2 when you just need to know Received/Successful/Unsuccessful.
@spec get_single(payment_id(), client()) :: {:ok, map()} | {:error, BankingCircle.Error.t()}
Full payment detail, including processing history.
@spec get_status(payment_id(), client()) :: {:ok, map()} | {:error, BankingCircle.Error.t()}
Lightweight status check — intended for polling, cheaper than get_single/2.
@spec get_trace(String.t(), client()) :: {:ok, map()} | {:error, BankingCircle.Error.t()}
@spec get_trace_status(String.t(), client()) :: {:ok, map()} | {:error, BankingCircle.Error.t()}
@spec initiate_recall(String.t(), String.t(), client()) :: {:ok, map()} | {:error, BankingCircle.Error.t()}
Requests a recall of a processed payment (you sent it to the wrong
place, or with wrong details). reason_code must be one of "AC03"
(invalid creditor account), "AM09" (wrong amount), or "CUST"
(requested by customer).
Requires the payment to be processed, under 6 months old, through a
non-instant scheme (Faster Payments/SEPA Instant recalls go straight to
Unsuccessful), and without an existing active/successful recall.
Banking Circle makes up to 3 attempts (initial + 2 chasers, 5 working
days apart) — there's no cancel-a-recall operation; contact Client
Services if you need one stopped.
@spec initiate_trace(String.t(), client()) :: {:ok, map()} | {:error, BankingCircle.Error.t()}
Initiates a trace to investigate a delayed/missing payment by contacting
the beneficiary bank. Requires the payment to be in a final status
(Processed or Rejected), under 6 months old, without an existing active
trace. Faster Payments/SEPA Instant traces go straight to Unsuccessful.
@spec list_bulks( keyword(), client() ) :: {:ok, map()} | {:error, BankingCircle.Error.t()}
Paginated list of bulk payment batches.
@spec list_recalls( keyword(), client() ) :: {:ok, map()} | {:error, BankingCircle.Error.t()}
Lists recall requests from the past 6 months, paginated.
@spec list_singles( keyword(), client() ) :: {:ok, map()} | {:error, BankingCircle.Error.t()}
Paginated list of single payments, optionally filtered by status/date range.
@spec list_traces( keyword(), client() ) :: {:ok, map()} | {:error, BankingCircle.Error.t()}
@spec reject_single(payment_id(), String.t() | nil, client()) :: {:ok, map()} | {:error, BankingCircle.Error.t()}
Rejects an incoming direct debit payment.