Audience: contributors and downstream integrators who need to know how QuickLendX treats a request that does not fully cover the invoice amount.
QuickLendX does not maintain a separate bid-level “partial fill” state machine. A bid is accepted and funded atomically through the invoice funding flow; there is no later contract path that partially consumes a bid amount in-place.
The behavior that people often call “partial fill” is the invoice settlement path: when a business calls process_partial_payment, the contract records a payment against the funded invoice and only applies the portion that is still due.
In practice, that means:
- a payment request is never applied beyond the invoice’s
remaining_due - duplicate transaction IDs are treated as idempotent replays
- once the accumulated
total_paidreaches the invoice amount, settlement is triggered automatically
For a payment request on a funded invoice, applied_amount = min(requested_amount, remaining_due).
The public entrypoint is:
pub fn process_partial_payment(
env: &Env,
invoice_id: &BytesN<32>,
payment_amount: i128,
transaction_id: String,
) -> Result<(), QuickLendXError>The normal call pattern in tests is:
client.process_partial_payment(
&invoice_id,
&400,
&String::from_str(&env, "pay-1"),
);Assume:
- invoice amount =
1_000 - current
total_paid=0 - the invoice is already
Funded
A first call:
client.process_partial_payment(&invoice_id, &400, &String::from_str(&env, "pay-1"));results in:
applied_amount = 400total_paid = 400remaining_due = 600- invoice status remains
Funded
A second call:
client.process_partial_payment(&invoice_id, &800, &String::from_str(&env, "pay-2"));The contract does not “over-apply” the request. It caps the payment to the outstanding balance:
remaining_duebefore the call =600requested_amount=800applied_amount = min(800, 600) = 600total_paidbecomes1_000remaining_duebecomes0
At that point, the settlement path is triggered automatically and the invoice moves to its terminal paid state.
The contract maintains three invariants for this flow:
total_paidnever exceeds the invoice amount.record_paymentis idempotent for the same(invoice_id, nonce)pair.- a transaction that would exceed the remaining balance is silently capped rather than rejected.
That last rule is the key part of the “partial fill” behavior: the system records the payment that fits, and it does not leave the invoice in a half-processed state.
The nonce is the caller’s deduplication key for a payment attempt.
If the same transaction_id is re-used on the same invoice:
- the contract returns the current invoice progress
- no new payment record is appended
total_paiddoes not increase a second time
This is important for downstream integrators because a retried payment request should behave like a harmless replay, not as a second partial match.
For contributors and support staff, the practical interpretation is:
- a “partial fill” of the invoice amount is not a separate state machine
- the payment request is capped to the remaining balance
- one payment attempt can finish the invoice if it reaches the final amount exactly
- the same nonce can be safely replayed without corrupting accounting