PaymentLoad
Record payments made against invoices. Upserts on external_source +
external_payment_id. A payment has a list of lines, each
applying part of the total amount to a specific invoice.
Import
from lib.objects.payment import PaymentLoad
Methods
| Method | What it does |
|---|---|
new() | Queue a new payment |
save_all() | POST each queued payment, one request per payment. Returns the API answers |
obj.save() | POST one payment |
get_by_external_id(source, external_id) | One payment by its key, or None. Any failed request (also a 403) returns None too |
get_by_invoice(invoice_token) | Every payment applied to an invoice |
Payments are not on the integration engine: there is no list of all
payments, no search, no full load and no delete. get(), get_all(),
search(), delete_where() and obj.delete() raise
NotImplementedError; read with get_by_external_id() or
get_by_invoice(). To cancel a payment, save it again with
status='CANCELED'.
Payment fields
| Field | Required | Notes |
|---|---|---|
id | (assigned) | BIGINT |
external_payment_id | yes | Your stable id from the source system |
external_source | e.g. 'SAP', 'NETSUITE'. Combined with external_payment_id as the upsert key. Strongly recommended: without it the API uses 'UNKNOWN', and two systems pushing the same id collide. | |
status | INITIATED, PROCESSING, SENT, SETTLED, RETURNED, FAILED, CANCELED, PAID | |
method | yes | ACH, WIRE, SEPA, SWIFT, RTP, CHECK, CARD, VIRTUAL_CARD, PAYPAL, OTHER |
total_amount | yes | float |
currency | yes | 3-letter ISO |
| Lifecycle timestamps | ||
scheduled_at / submitted_at / sent_at | ||
expected_settlement_at / settled_at / returned_at | ||
| References | ||
payment_reference | Free-form | |
bank_reference | ||
ach_trace_number | for ACH | |
sepa_end_to_end_id | for SEPA | |
rtp_trace_id | for RTP | |
| Check-specific | ||
check_number | ||
check_mailed_at / check_cleared_at | ||
check_stop_payment | bool | |
| Beneficiary (recipient) | ||
beneficiary_name | ||
beneficiary_bank_name / beneficiary_bank_country (2-char) | ||
beneficiary_swift_bic | up to 11 chars | |
beneficiary_iban / beneficiary_account / beneficiary_routing | ||
| Payer (sender) | ||
payer_bank_name / payer_bank_country | ||
payer_account / payer_routing | ||
| Remittance | ||
remittance_memo | ||
remittance_advice_url | ||
replace_lines | bool, default true. Only used when the payment carries lines: true makes the lines the payment's complete set of applications; false merges per invoice (a line replaces the application to its invoice, applications to other invoices stay). |
PaymentLine fields (the application to an invoice)
One line per invoice per request; the same invoice twice in one payment is refused. An unknown invoice token refuses the whole payment.
| Field | Required |
|---|---|
invoice_token | yes. UUID of the target invoice |
applied_amount | yes. Float |
applied_currency | |
fx_rate_line / fx_at_line | |
discount_amount / discount_currency | |
fee_amount_line / fee_currency_line | |
remittance_label / remittance_value | |
notes |
Example: ACH payment applied to two invoices
from lib.objects.payment import PaymentLoad
def run(context):
load = PaymentLoad(context)
p = load.new()
p.external_payment_id = 'PAY-2026-001'
p.external_source = 'SAP'
p.method = 'ACH'
p.total_amount = 1500.00
p.currency = 'USD'
p.status = 'SENT'
p.sent_at = '2026-04-25T14:00:00Z'
p.beneficiary_name = 'Acme Corporation'
p.ach_trace_number = 'T20260425001'
line = p.new_line()
line.invoice_token = 'aaaaaaaa-0001-...'
line.applied_amount = 1000.00
line = p.new_line()
line.invoice_token = 'aaaaaaaa-0002-...'
line.applied_amount = 500.00
answers = load.save_all() # one POST per payment, one answer each
return {'saved': len(answers)}
The raw call is context.api.upsert_payment(body). It also takes a list of
payments; the answer then has a result per payment (index,
external_payment_id, ok, payment_id, error), with HTTP 207 when some
failed.
Lookup
load = PaymentLoad(context)
# By external id
p = load.get_by_external_id('SAP', 'PAY-2026-001')
if p:
print(p.status, p.total_amount, p.currency)
# All payments touching an invoice
for p in load.get_by_invoice('aaaaaaaa-0001-...'):
for ln in p.lines:
print(ln.applied_amount)
Update an existing payment
Save again with the same external_source + external_payment_id:
fields you send are updated, fields you leave out are preserved. A
payment without lines leaves its applications as they are.
p = load.new()
p.external_payment_id = 'PAY-2026-001'
p.external_source = 'SAP'
p.method = 'ACH'
p.total_amount = 1500.00
p.currency = 'USD'
p.status = 'SETTLED'
p.settled_at = '2026-04-26T10:00:00Z'
load.save_all()
Status PAID or SETTLED without settled_at stamps it with the current
time; RETURNED does the same for returned_at.
By default, re-saving with a lines array replaces the applications.
With replace_lines = False each line replaces only the application to
its own invoice.