ACH Processing

The prod-float-service-ach-handler Lambda consumes the prod-payments Kinesis stream and processes ACH settlement callbacks for float disbursements and collection attempts. It is the primary mechanism by which float statuses transition to COMPLETED or back to RETRY after an ACH attempt.

Event Types

The ACH Handler filters for four event types from the prod-payments stream. Events for other payment types (e.g. subscription payments) are ignored.

Kinesis Event Type Category Description

FLOAT_DEBIT_COMPLETED

Collection

An ACH debit submitted for float collection has settled successfully. The float is fully collected.

FLOAT_DEBIT_RETURNED

Collection

An ACH debit was returned by the bank (e.g. insufficient funds, account closed). The float is placed back into retry.

FLOAT_CREDIT_COMPLETED

Disbursement

An ACH credit (disbursement) has settled. Logged but no float status change — the float was already in SCHEDULING when disbursement was submitted.

FLOAT_CREDIT_RETURNED

Disbursement

An ACH credit was returned — effectively a chargeback on the disbursement. The float is defaulted and the user is banned.

ACH Callback Flow

Kinesis event received (FLOAT_DEBIT_* or FLOAT_CREDIT_*)
        │
        ▼
Look up float in RDS by loan_id
        │
        ├── Not found ──► log and skip (no-op)
        │
        ▼
Determine new float status and outcome:
        │
        ├── payment.Status == COMPLETED
        │         float status ──► COMPLETED
        │         outcome ──────► "Accepted"
        │
        ├── payment.Status == FAILED
        │         float status ──► RETRY
        │         outcome ──────► ACH return code (e.g. "R01", "R05")
        │
        └── payment.Status == CHARGED_BACK
                  float status ──► DEFAULTED
                  outcome ──────► "CHARGED_BACK"
        │
        ▼
Update float in RDS (ach_debit_status + ach_debit_id)
        │
        ▼
Write collection log to DynamoDB (collection-history)
  process: "Check-ach-cleared" or "Chargeback-detector"
  outcome: "Accepted", ACH return code, or "CHARGED_BACK"
        │
        ▼
If COMPLETED: send digital receipt
  ├── Segment notification
  ├── Iterable in-app / email notification
  ├── AppsFlyer attribution event
  └── Trigger underwriting recalculation (Underwriting Service)
        │
        ▼
Is return code bannable? ──No──► Done
        │ Yes
        ▼
Ban user (User Service)
        │
        ▼
Add ban note (Admin Service)
All external calls after the RDS update (receipt notifications, underwriting recalculation, user ban, admin note) are best-effort — failures are logged and tracked in Segment but do not halt processing or roll back the float status update.

Status Transitions

Event Type payment.Status Resulting Float Status Notes

FLOAT_DEBIT_COMPLETED

COMPLETED

COMPLETED

ACH collection settled. Digital receipt sent. Underwriting recalculation triggered.

FLOAT_DEBIT_RETURNED

FAILED

RETRY

ACH debit returned by the bank. Float re-enters the retry cycle. ACH return code recorded as the outcome.

FLOAT_CREDIT_RETURNED

CHARGED_BACK

DEFAULTED

Disbursement ACH was returned. User is banned via the User Service and a note is added via the Admin Service.

FLOAT_CREDIT_COMPLETED

COMPLETED

(no change)

Disbursement confirmed. Logged but float status is not updated — it remains SCHEDULING until collection begins.

Collection Log Entry

Every processed event writes a record to the collection-history DynamoDB table regardless of outcome.

Field Value

loan_id (PK)

Float ID from the payment event

run_time (SK)

Current UTC time as Unix nanoseconds

user_id

From the float record in RDS

due_date

Float’s ach_debit_date formatted as YYYY-MM-DD

run_date

Today’s date (YYYY-MM-DD)

process

Check-ach-cleared for debit events; Chargeback-detector for chargeback events

outcome

Accepted (COMPLETED), ACH return code (FAILED), or CHARGED_BACK

confirmation_id

Payment processor confirmation ID from the event

Notifications on Collection Success

When an ACH debit settles (FLOAT_DEBIT_COMPLETED), the ACH Handler sends a digital receipt across three channels:

Channel Purpose

Segment

User event analytics — notification events with float and payment details

Iterable

In-app and email notification informing the user their float has been collected

AppsFlyer

Mobile attribution event for the collection outcome

After notifications are sent, the ACH Handler calls the Underwriting Service to trigger a recalculation of the user’s float eligibility, reflecting the now-cleared balance.

Bannable ACH Return Codes

When an ACH debit is returned with one of the following codes, the user is automatically banned via the User Service. Any chargeback (CHARGED_BACK) also triggers a ban.

Return Code Meaning

R05

Unauthorized debit to consumer account using corporate SEC code

R07

Authorization revoked by customer

R08

Payment stopped

R10

Customer advises not authorized

R11

Check truncation entry returned

R29

Corporate customer advises not authorized

R51

Item is ineligible, notice not provided, signature not genuine, or item altered

Non-bannable return codes (e.g. R01 — insufficient funds, R02 — account closed) result in a RETRY status without triggering a ban. The float re-enters the normal retry cycle.