Skip to main content

Two-phase transfers

The name "two-phase transfer" is a reference to the two-phase commit protocol for distributed transactions.

Single-phase transfers post funds to accounts immediately when they are created. That is, they increase the debits_posted and credits_posted fields on respective accounts.

In contrast to single-phase transfers, a two-phase transfer moves funds in stages:

  1. First, the pending transfer reserves funds. While reserved, they cannot be used by either the payer or payee. Only the debits_pending and credits_pending fields are increased on the relevant accounts at this point.

  2. Later, the application creates another transfer — with either of the following flags:

Resolving a two-phase transfer

When the pending transfer is resolved (posted or voided), the debits_pending and credits_pending fields on the respective accounts are decreased by the amount of the pending transfer.

Post

When the pending transfer is posted, debits_posted and credits_posted fields on the respective accounts are increased by the posting transfer's amount (which cannot exceed the pending amount, but need not equal the pending amount either).

Post partial pending amount

Although an initial amount is reserved when a pending transfer is created, you can set the amount field to that amount or smaller than that initial amount when posting a pending transfer.

In the event that you post less than the amount you initially reserved, the rest of the amount not posted reverts back to the original account.

Void

When the pending transfer is voided, debits_posted and credits_posted are not modified.

Timeout

If a pending transfer is created with a timeout (which is optional), then if it has not been posted or voided by the time the timeout expires, the full amount will be voided.

Errors

A pending transfer can only be posted or voided once. It cannot be posted twice or voided then posted, etc.

Attempting to resolve a pending transfer more than once will return the applicable error result:

Interaction with account invariants

The pending transfer's amount is reserved in a way that the second step in a two-phase transfer will never cause the accounts' configured balance invariants (credits_must_not_exceed_debits or debits_must_not_exceed_credits) to be broken, whether the second step is a post or void.

Pessimistic pending transfers

If an account with debits_must_not_exceed_credits has credits_posted = 100 and debits_posted = 70 and a pending transfer is started causing the account to have debits_pending = 50, the pending transfer will fail. It will not wait to get to posted status to fail.

All transfers are immutable

To reiterate, completing a two-phase transfer (by either marking it void or posted) does not involve modifying the pending transfer. Instead you create a new transfer.

The first transfer that is marked pending will always have its pending flag set.

The second transfer will have a post_pending_transfer or void_pending_transfer flag set and a pending_id field set to the id of the first transfer. The id of the second transfer will be unique, not the same id as the initial pending transfer.

Examples

The following examples show the state of two accounts in three steps:

  1. Initially, before any transfers
  2. After a pending transfer
  3. And after the pending transfer is posted or voided

Post full pending amount

Account AAccount BTransfers
debitscredits
pendingpostedpendingposteddebit_account_idcredit_account_idamountflags
wxyz----
123 + wx123 + yzAB123pending
w123 + xy123 + zAB123post_pending_transfer

Post partial pending amount

Account AAccount BTransfers
debitscredits
pendingpostedpendingposteddebit_account_idcredit_account_idamountflags
wxyz----
123 + wx123 + yzAB123pending
w100 + xy100 + zAB100post_pending_transfer

Void pending transfer

Account AAccount BTransfers
debitscredits
pendingpostedpendingposteddebit_account_idcredit_account_idamountflags
wxyz----
123 + wx123 + yzAB123pending
wxyzAB123void_pending_transfer

Client documentation

Read more about how two-phase transfers work with each client.

Client samples

Or take a look at how it works with real code.