Package Structure

The pkg/ directory is organized into three tiers: shared infrastructure, the installment loan product, and the credit line product. Product packages import from shared infrastructure but never from each other.

pkg/
├── dynamo/              # Shared: DynamoDB client, ID struct, helpers, distributed lock
├── iterable/            # Shared: Iterable notification client
├── payments/            # Shared: PayEngine client (ACH, RTP, Pinless)
├── floats/              # Shared: Float service client
├── growthbook/          # Shared: Feature flag client
├── underwriting/        # Shared: Underwriting service client
├── users/               # Shared: User service client
├── txns/                # Shared: Transaction service client
├── notification/        # Shared: Notification abstraction
├── slack/               # Shared: Slack alerting
├── s3/                  # Shared: S3 helpers
├── sqs/                 # Shared: SQS helpers
├── lambdadispatch/      # Shared: Lambda-to-Lambda invocation
├── utils/               # Shared: Math and general utilities
├── version/             # Shared: Build version metadata
│
├── installmentloan/     # Product: installment loans
│   ├── api/             # OpenAPI handlers (oapi-codegen, chi router)
│   ├── collections/     # Collection engine, job, worker, webhook handler
│   ├── service/         # Repository interfaces
│   ├── dynamo/          # DynamoDB models: Loan, Payment, Installment, LoanApplication
│   ├── expiration/      # TTL expiry logic (LOAN# prefix)
│   ├── loanbalanceworker/ # Balance reconciliation handler
│   ├── log/             # Audit log DynamoDB stream handler
│   ├── latex/           # LaTeX PDF rendering for loan agreements
│   ├── receipts/        # Payment receipt types
│   ├── precheck/        # Eligibility pre-check logic
│   └── datadog/         # DataDog metric constants
│
└── creditline/          # Product: revolving credit line
    ├── api/             # OpenAPI handlers
    ├── billing/         # loc_billing: interest accrual, statement gen, MinPaymentDue
    ├── collections/     # loc_due, loc_overdue, loc_upcoming
    ├── statements/      # LocStatement struct, PDF invoke, GET endpoints
    ├── draws/           # Draw handler, RTP→Pinless→ACH disbursement
    ├── dynamo/          # DynamoDB models: LineOfCredit, LocApplication, LocStatement, LocPayment
    └── expiration/      # TTL expiry logic (LOCAPP# prefix)

Shared Infrastructure (pkg/dynamo)

pkg/dynamo contains only infrastructure that both products depend on:

  • dynamo.goClient interface, ID struct (PK/SK/GSI keys), ErrNotFound, BatchWriteItems, ExhaustiveQuery, DynamoDB type constants (LOAN, PAYMENT, LOANAPP, USER, STATUS, DUEDATE)

  • lock.goLocker interface and cirello.io/dynamolock/v2 implementation for distributed locking

  • mocks/Client and Locker mocks (shared by both products' tests)

Lock key format: loc-{process}:user_id:{userID}:item_id:{itemID}

Installment Loan Product (pkg/installmentloan)

dynamo/

Product-specific DynamoDB repositories. All models embed pkg/dynamo.ID for the composite key fields and import pkg/dynamo for shared helpers.

File Contents

loan.go

Loan struct, LoanRepository interface, loan status constants

loan_application.go

LoanApplication struct, LoanApplicationRepository interface

payment.go

Payment struct, PaymentRepository interface, payment method/status constants

payment_log.go

PaymentLog struct, PaymentLogRepository interface

installment.go

Installment struct, InstallmentRepository interface, InstallmentStatus constants

installment_log.go

InstallmentLog struct, InstallmentLogRepository interface

precheck.go

PreCheck struct, PreCheckRepository interface

collections/

Handles all automated payment collection for installment loans.

  • engine.goPaybackEngine interface + implementation; ACH and pinless debit payment execution

  • job.go — CloudWatch-triggered scheduler; queries installments and enqueues SQS messages

  • worker.go — SQS-triggered worker; dispatches to engine per collection process type

  • webhook_handler.go — Kinesis webhook handler for ACH returns and pinless chargebacks

  • latefee.go — Late fee calculation and application logic

expiration/

Handles DynamoDB TTL expiration events for loan applications (LOAN# SK prefix). On expiry of an APPROVED application without a loan, transitions status to ABANDONED. Wired into cmd/expiration-handler alongside the credit line expiration handler.

Credit Line Product (pkg/creditline)

billing/

The loc_billing job runs at 08:00 UTC on each billing cycle close date:

  1. Accrue interest on PrincipalBalance

  2. Generate LocStatement (snapshot of draws, payments, interest)

  3. Calculate NextMinPaymentDueInterest + Fees + (1%–5% × PrincipalBalance)

  4. Write LastBillingCycleDate to guard loc_due from running before billing completes

collections/

Three scheduled jobs, all separate CloudWatch rules:

Job Schedule Description

loc_billing

08:00 UTC

Cycle close — see billing/ above

loc_due

15:00 UTC

Reads MinPaymentDue from previous billing cycle; autopay or notify manual users with exact NextPaymentDue date

loc_upcoming

14:00 UTC

Notifies ~7 days before NextPaymentDue

loc_overdue

16:00 UTC

Daily; skips if today < NextPaymentDue (14-day grace window via loc.payment_grace_days GrowthBook flag)

draws/

Draw request handler with payment method priority:

  • Disbursement: RTP → Pinless → ACH

  • Repayment: Pinless → ACH (RTP not used for payback)

ACH balance timing: balances applied immediately on ACH submission (not on webhook). Webhook LOAN_DEBIT_COMPLETED marks COMPLETED only — no balance change. LOAN_DEBIT_RETURNED / CHARGED_BACK reverses balances and sends loc_payment_failed Iterable event.

expiration/

Handles TTL expiration events for credit line applications (LOCAPP# SK prefix). On expiry, transitions to ABANDONED, writes tombstone, and fires LocApplicationAbandonedEvent via Iterable. Wired into cmd/expiration-handler alongside the installment loan expiration handler.

Dependency Rules

pkg/installmentloan/* → pkg/dynamo          ✅ (shared infra)
pkg/installmentloan/* → pkg/iterable        ✅ (shared infra)
pkg/creditline/*      → pkg/dynamo          ✅ (shared infra)
pkg/creditline/*      → pkg/iterable        ✅ (shared infra)
pkg/installmentloan/* → pkg/creditline/*    ❌ (no cross-product imports)
pkg/creditline/*      → pkg/installmentloan/* ❌ (no cross-product imports)

Adding a New Product

  1. Create pkg/{product}/ with subdirectories mirroring the credit line layout

  2. Add product-specific DynamoDB models to pkg/{product}/dynamo/ — embed pkg/dynamo.ID, import pkg/dynamo for shared helpers

  3. Add the product’s interfaces to .mockery.yaml under github.com/floatme-corp/loc-service/pkg/{product}/…​

  4. Create cmd/{product}-api/ and any scheduler/worker entry points

  5. Extend cmd/expiration-handler if the product has TTL-expirable records (add SK prefix check)