Float Lifecycle
A float is a short-term advance disbursed to a user. This page is the canonical reference for float statuses and every lifecycle operation outside automated collection: creation, manual payback, reactivation payback, requirements bypass, user bans, and terminal states. Automated collection is covered in Collections Engine.
Float Statuses
The ach_debit_status column on the float record tracks where a float is in its lifecycle.
| Status | Description | Set by |
|---|---|---|
|
Float created and disbursement submitted; awaiting its due date. |
Float creation (API) |
|
ACH debit submitted to the payment processor; awaiting a settlement callback from the |
ACH-submitting collection runs; manual payback (ACH option) |
|
Fully collected. Terminal. |
ACH Handler (settlement); any successful pinless debit |
|
A collection attempt failed; the float is overdue and targeted by retry collection. |
Due-date and retry runs; webhook worker; ACH Handler (non-bannable return) |
|
A float operation errored; treated as overdue. |
batch-worker |
|
Failed-ACH variant; treated as overdue. |
None (data value read by overdue queries) |
|
No valid Plaid account and no valid primary debit card; re-evaluated on every Daily Retry run. |
Daily Retry |
|
Terminal: more than 90 days past due, bannable ACH return, chargeback, or user ban. |
Daily Retry; ACH Handler; ban endpoint |
|
Collection paused; scheduled batch jobs skip the float. |
External tooling (checked in |
Source: pkg/rds/status.go (which also defines CHARGED_BACK — a payment-response value, not a float status: on a chargeback event the ACH Handler sets the float to DEFAULTED and records CHARGED_BACK as the collection outcome; see ACH Callbacks)
The overdue set — statuses targeted by Daily Retry and the webhook collection paths — is RETRY, FAILED, ACHFAILED, UNCOLLECTABLE. Source: pkg/collections/status.go.
Creation & Disbursement
POST /{user_id}/floats runs all checks synchronously; the float record is written to RDS only after the disbursement succeeds.
-
Validate the user (User Service): state not restricted (
floats.restricted_states), statusACTIVE, email verified where enforced — otherwise400. -
Validate subscription (Subscriptions Service):
PAST_DUEorSTALEreturns400. -
Acquire the user’s distributed lock (locks table).
-
Fetch the linked bank account (Transactions Service).
-
Check for an active requirements bypass (Requirements Bypass).
-
Run fraud checks — see Fraud Detection; a connectivity error returns
500. -
Fetch the float profile and fee (Underwriting Service); float not enabled for the user returns
400. -
Fetch next payday and payback date (Insight Service); a custom payback date is validated against the allowed window.
-
Underwriting eligibility check — skipped when a bypass is active; not approved returns
400. -
Submit the disbursement (Payments Service) as a
PINLESS,RTP, orACHcredit; an RTP rejection (reported by Payments as aP04error) retries asPINLESSonly whenfloats.can_fallback_pinlessis enabled for the app build. -
Write the float to RDS: status
SCHEDULING, the credit confirmation ID, the computed due date (ach_debit_date), andnext_payday_date(first payday after the due date). -
Send the confirmation email (Segment + Iterable), publish
user_float_created(Events), return201.
| If the disbursement succeeds but the RDS write fails, funds have moved with no float record. The confirmation email and event publish happen only after the RDS write. |
Manual Payback
POST /{user_id}/floats/{float_id}/payback with payback_option: "ACH" | "PINLESS" pays back a single float early.
-
Fetch the float; it must be at least 24 hours old and not
COMPLETEDorACHSENT— otherwise404. -
Acquire the user’s billing lock.
-
Calculate the fee (
PINLESS/RTPfloats). -
Submit the payment (Payments Service) per
payback_option.
Payback submissions set bypass_retry_limit, so member-initiated attempts are never blocked by the Payments Service pinless retry limit.
|
| Result | Float status | Response |
|---|---|---|
Error or |
|
|
ACH submitted |
|
|
Pinless collected |
|
|
Reactivation Payback
POST /{user_id}/floats/payback/reactivate pays off all past-due floats before a cancelled account is reactivated. Pinless debit only, regardless of loan type; no request body.
-
Fetch the user’s active floats (
RETRY,DEFAULTED,SCHEDULING,ACHSENT,UNCOLLECTABLE) and filter to past-due (ach_debit_date ⇐ now). -
Total responsibility = sum of amount + fee across the past-due floats; the expected amount is validated before each submission.
-
Per float, sequentially: submit the reactivation payback (Payments Service; retry-limit bypass applies).
ErrFloatNotPayablelogs a warning, subtracts the float from the total, and continues; any other error returns500. A result other thanCOMPLETEDstops the run with402 Payment Requiredand{AmountCollected, AmountRemaining, MaskedCardNumber}. -
All collected:
200with{AmountCollected, AmountRemaining, AmountTotal, MaskedCardNumber}.
A 402 means partial collection — at least one float was paid but another could not be; the caller uses the amounts to decide whether reactivation proceeds.
Requirements Bypass
A per-user record that lets float creation skip the underwriting eligibility check. Granted by internal operations tooling via the bypass endpoints (see API spec); stored in the requirements-bypass DynamoDB table keyed by user_id (schema).
While an unexpired bypass exists:
-
The underwriting eligibility check is skipped.
-
Fraud checks still run, with the
Bypassedflag passed to the Payments Service; fraud results are not saved back for bypassed users.
The record expires on its expiration_date but is not deleted — it remains in DynamoDB as inactive until an explicit DELETE.
User Ban
POST /{user_id}/floats/ban defaults any float that could still be collected. For each active float (RETRY, DEFAULTED, SCHEDULING, ACHSENT, UNCOLLECTABLE):
-
RETRYorSCHEDULING→ statusDEFAULTED(debit IDuser_banned); collection logprocess=SUPPORT,outcome=DEFAULTED. -
ACHSENT,DEFAULTED,UNCOLLECTABLE→ unchanged. An in-flight ACH debit is not cancelled; the ACH Handler finalizes it on settlement.
No active floats returns 200 as a no-op.
Default & Uncollectable
DEFAULTED is set on four conditions. The first three are reached by a collection run; the fourth is a support action outside the collections engine:
-
Age — the float is more than 90 days past due (Daily Retry).
-
Bannable ACH return — the debit is returned with a code NACHA prohibits re-initiating; the float defaults and the user is banned (ach_processing.adoc#bannable-return-codes).
-
Chargeback — a chargeback event on the
prod-paymentsstream; the float defaults and the user is banned. -
User ban — the ban endpoint defaults the user’s
RETRYandSCHEDULINGfloats directly, writing aSUPPORTcollection log; no collection run is involved. See User Ban.
UNCOLLECTABLE is set by Daily Retry when the user has neither a valid Plaid account nor a valid primary debit card. It is not terminal: each Daily Retry run re-evaluates these floats, and collection resumes if a valid payment method appears.