Skip to content
KAMOUniverse

Changelog

What's new.

Every improvement to the platform, as it ships.

  1. · kamo-shared-library

    API_SIGNATURE api-key scope + EsignGenericEnvelope.origin

    Additive, no version bump: - ApiKeyScope.API_SIGNATURE — gates the programmatic e-signature API (Business) - EsignGenericEnvelope.origin (EsignUsageOrigin, nullable) — records whether an envelope was created via API vs MANUAL, so completed signatures meter correctly (API counts toward the $0.20 overage; MANUAL never does). Column added to prod via idempotent ALTER beforehand.

  2. · kamo-internal

    Send-for-signature action + envelope status in document editor

    Add a 'Send for signature' button to the template editor header that opens a dialog to send the document to recipients and track signing status, calling the new ESigService /api/esig/envelopes engine. - Proxy routes (OTK via generateOTK, mirrors the templates proxy): POST/GET /api/esig/envelopes, GET [uid], POST [uid]/void, POST **************** - SendForSignatureDialog: Send tab (recipient rows, message, expiry) + Status tab (per-template envelope list, status chips, void/resend) - Button disabled while the template has unsaved changes - i18n via next-intl ****************

  3. · ESigService

    Generic envelope create/send engine (sender side)

    Add the OTK-authed /api/esig/envelopes API kamo-internal drives to send documents for signature, reusing the same shared-lib entities the public signing API reads — the additive, org-agnostic replacement for the loan- coupled KamoLOS/EsignClient create path. - POST create-and-send from a template: builds EsignGenericEnvelope (AD_HOC), one EsignEnvelopeRecipient per signer with a fresh hashed access token, and emails each the /s/{env}/{recipient}?t={token} signing link (best-effort) - GET list (envelope dashboard) + GET {uid} detail with recipient statuses - POST {uid}/void and {uid}/recipients/{rid}/resend (rotates the token) - Invite + delegate emails share EsignEmailClient.sendInvite; signer-route links corrected to /s/{env}/{recipient}

  4. · ESigService

    Public signing API engine for sign.sign.pink

    Implement the /api/esig/public/* signing API the kamo-signer app calls, turning the live signer from preview-stub into a working backend. - validate (recipient token -> signer session), session, consent, stepup/start+verify (email OTP), submit, decoder, delegate, document - Redis-backed signer sessions (X-Signer-Session), distinct from staff OTK - Document bytes via the real Img/MinIO pipeline (template imgId -> converted-PDF ImgDat -> MinIO), not the never-wired /api/imaging/blobs stub - PDFBox flatten: overlay field values + signature images at design coordinates, append E-SIGN/UETA audit certificate pages; signed PDF stored in MinIO under the envelope signedBlobId with recorded SHA-256 - Append-only EsignSignature audit rows + EsignFieldValue persistence - Emits **************** API-origin metering follows in #3/#4 - Branding resolved from Organization + OrgDomain for first-paint theming - OTKAuthFilter bypasses /api/esig/public (token-authed, not staff OTK) Additive only; existing /api/esig/templates/* untouched.

  5. · kamo-internal

    Platform product toggle in Organizations tab

    Surface the platform-product designation where it belongs — Platform → Organizations. Adds a gated Switch per org (disabled for the top-level org), a confirmation dialog explaining the entitlement effect, a 'Platform product' chip, a no-catalog warning, optimistic update, and error snackbar. All strings via next-intl.

  6. · BillingService

    Manage Organization.isPlatformProduct from Platform → Orgs

    PlatformAccountSummary exposes isPlatformProduct + hasSubscriptionMarket; new PUT **************** (platform-admin gated) sets the flag and invalidates the org's members' entitlements (their product root, and thus features, change). Top-level org is the implicit root and cannot be toggled.

  7. · kamo-shared-library

    Explicit Organization.isPlatformProduct as entitlement-root signal

    Replace the derived 'owns a SUBSCRIPTION market' heuristic with an explicit, admin-set ownership flag. A platform product (e.g. sign.pink) is a white-label product the platform operates and is its own entitlement root; a customer org — even one running its own SUBSCRIPTION market to resell to its customers — is not, and stays entitled to the platform. EntitlementRootResolver now reads the flag (plus the implicit top-level root) and no longer depends on market ownership. Adds is_platform_product column (BOOL DEFAULT FALSE) — requires KamoInitializer to create it before services that select Organization deploy.

  8. · SecurityService

    Scope member entitlements to product root, not KamoCRM

    EntitlementService now resolves each member's entitlement root via EntitlementRootResolver (nearest market-owning ancestor-or-self) instead of walking to the top-level org. The Free-tier floor and the product-access flag derive from that root, and isKamoCrmSubscription becomes subscriptionGrantsRootAccess (plan-org vs root, never the billing account). MemberEntitlements gains productRootOrgId so consumers gate access against the right product. BillingSetupService sources the market + Free plan from the new org's product root while keeping the billing Account on the top-level KamoCRM org — i.e. the parent owns billing, the child can own the product/market. Behaviour is unchanged until an org actually owns its own market (KamoCRM is the only market owner today, so every member still resolves to it).

  9. · kamo-shared-library

    Resolve entitlements by product-root org, not platform top-level

    Introduce EntitlementRootResolver: the nearest market-owning ancestor-or-self of an org (its product root), falling back to the platform top-level. This decouples *what product you're entitled to* (market/plan owner) from *who pays* (billing account) and from the single top-level org, so a white-label product org like sign.pink that owns its own market becomes its own entitlement root. **************** now sources the Free baseline from the org's product root (not findFirstByIsTopLevelTrue), and resolves plans by the product/market org rather than the billing-account org. Backwards compatible: KamoCRM is currently the only market owner, so every existing org resolves to it exactly as before — behaviour only diverges once a sub-tree owns its own market. Additive; no version bump.

  10. · kamo-shared-library

    Add envelope recipient, field value, recipient status entities

    Check in the in-progress esign model: EsignEnvelopeRecipient (+repo), EsignFieldValue (+repo), EsignRecipientStatus, and an ESignSignerType update. Additive; no version bump.

  11. · kamo-shared-library

    Add EsignUsageRecord for per-signature usage metering

    New append-only usage entity (mirrors AiUsageRecord) emitted at signature completion. Only API-origin signatures count toward a plan's monthly included quota + overage (sign.pink Business: 40/mo included, then $0.20 each); MANUAL single-user signatures are recorded but never billed. billing_period (YYYY-MM) supports monthly aggregation; reported_to_stripe makes Stripe usage submission idempotent. Additive only; no version bump.

  12. · ConversionService

    Preserve aspect ratio, never stretch non-square logos

    Favicon PNG/ICO frames were produced with keepAspectRatio(false), stretching any non-square logo into a distorted square. Center-fit the logo on a transparent square canvas instead, preserving aspect ratio, so favicons look correct for any uploaded logo shape without requiring a square source image.

  13. · KlusterServices

    Pre-seed sign.pink + www.sign.pink in STATIC_DOMAINS

    sign.pink is a new apex domain (consumer e-signature brand / white-label Kamo tenant org). Pre-seed apex + www so a Let's Encrypt cert exists before first HTTPS traffic; *.sign.pink subdomains get per-FQDN certs dynamically when first hit, same mechanism as *.priceturbo.com.

  14. · kamo-signer-monorepo

    New monorepo + mobile-first e-signature signing app

    Turborepo + pnpm workspaces monorepo housing reusable e-sign packages extracted from kamo-internal plus a new, ultra-polished kamo-signer app — the second-party SIGNING experience (the designer/authoring side stays in kamo-internal). Packages: - @kamo/esign-core types + DesignData wire contract, zod schema, runtime field validation, signStore (zustand) - @kamo/esign-ui interactive field inputs + read-only field/annotation renderers (forked from kamo-internal FieldControl) - @kamo/doc-render bundled pdf.js page renderer (self-hosted worker, no CDN), responsive, rotation-correct overlays - @kamo/theme per-tenant branding bootstrap (getThemePath, ThemeLogo, DynamicMetaLoader) - @kamo/api-client browser fetch + server Redis/forwarding helpers - @kamo/config shared tsconfig / tailwind preset / eslint App (apps/kamo-signer): - token-scoped signer auth (SIGNSESH httpOnly session, BFF route handlers) - welcome -> consent (ESIGN/UETA) -> eIDAS AES step-up OTP -> guided signing -> finish -> completion, all phases animated (framer-motion, reduced-motion aware), mobile-first equal to desktop, WCAG-minded - signature capture suite: draw / type (script fonts) / upload / adopt-all - multi-document, decoder, delegate/reassign, full i18n (next-intl, 22 locales) - runnable preview mode (no backend required) + /.forgejo + /k8s (HostRegexp sign.*, turbo-prune Dockerfile)

  15. · kamo-internal

    Full-width enterprise rewrite with live preview

    LoanProductForm was a single narrow column (maxWidth: 880) with hardcoded English everywhere. Now uses the same enterprise pattern as the offer page: Layout: - Full-width gradient header with breadcrumbs, back arrow, kind badge - Two-column grid (6fr/6fr) on lg+, single-column below - Form on the left, live "Product Preview" on the right Form side: - Kind selector as ToggleButtonGroup with descriptions (Installment vs LOC), not buried in a dropdown — this is the structural choice that reshapes the rest of the form, so it deserves prominence - Section blocks with icons (Basics · Pricing · Limits & Terms · Compliance) - Rates entered as %, converted to bps on the wire (no more "3600 = 36%") - All field labels via next-intl t() - Day-count tooltip explaining ACT_365 vs ACT_360 Preview side: - Hero card: kind chip, product code chip, name, headline, description - 4-tile stats grid: rate, origination, late fee, NSF fee - Limits & terms grid (kind-aware: range + frequency for installment, **************** for LOC) - Sample offer card (installment): monthly payment, total interest, total cost, sample origination at the midpoint of the principal range - Compliance chips: allowed states (cap 24 + overflow chip), autopay required + discount, promissory-note template binding - Footer note explaining how this product flows to the offer page LoanProductForm now wraps both the new and edit pages. Page wrappers pass marketTitle so the breadcrumb resolves cleanly.

  16. · kamo-internal

    Two-column layout with live preview pane

    The new-offer page was a tall narrow column on wide screens — wasted half the viewport. Now uses CSS Grid with the form on the left (5fr) and a live "Offer Preview" pane on the right (7fr) that updates as the operator types. Single-column on small screens (< lg). Form side: - Section blocks with icons (Product · Borrower · Amount · Custom Terms) - Borrower fields stack vertically (member id + state) in a more compact presentation now that horizontal space is reserved for the preview Preview side (only when a product is chosen): - Hero card with gradient background, kind chip, product name, and a human-readable headline ("$5,000 · 12-month installment · 14.99% APR" / "$25,000 line of credit · 9.99% rate") - 4-tile stats grid: principal, rate, monthly payment, term (installment) or principal, rate, draw period, repayment period (LOC) - Fee breakdown (origination/late/NSF/annual) with "custom" chip when an override is applied - Cost summary panel: net disbursed, total interest, total cost - Amortization schedule peek: first 3 + last 3 rows with a "{N} more payments…" gap row for terms > 6 months - TILA reminder note explaining the backend computes the true APR + builds the real schedule + renders disclosures on send All math is client-side straight-line amortization (P × r × (1+r)^n / ((1+r)^n - 1)) — a faithful preview, not the final disclosed APR. Backend still owns truth.

  17. · kamo-internal

    Polish new-offer page — empty state, LOC badge, custom terms

    Three UX gaps the offer page had after the prior refactor: 1. Empty state. When a market has no Loan Products yet, the dropdown used to silently render empty and the operator had no idea what to do. Now replaced with a hero empty state (icon, headline, primer on Installment vs LOC, and a "Create Loan Product" CTA routed to the products/new flow). 2. LOC vs Installment isn't visible upfront — both appear identically in the product dropdown. Added a colored chip (LOC = blue + repeat icon, Installment = green + wallet icon) rendered both in the selected value and inside each dropdown option, plus product description in the option. 3. The "Locked terms" block was confusingly named — it implied unchangeable without any way to override. Added a collapsible "Custom terms" panel that exposes per-offer overrides for interest rate, disclosed APR, and origination fee (relationship pricing, hardship workouts, promotional pricing). When set, the locked-terms card visually strikes through the product value and shows the override (e.g. 14.99% → 11.99%) so the operator sees exactly what'll be issued. Backend support pushed in KamoLOS 0e80393; types extended in losApi.ts so the override fields flow through createOfferDraft.

  18. · kamolos

    Per-offer overrides for rate, APR, and origination fee

    LoanOfferCreateRequest gains three optional fields — interestRateBpsOverride, aprBpsOverride, originationFeeAmountOverride. createDraft applies them when set and otherwise falls back to the product snapshot. Use cases: relationship pricing (rate kickback for repeat borrowers), hardship workouts (waived origination), one-off promotional offers. For installment products, only interestRateBpsOverride is in the normal path — APR is computed from rate + fees + schedule. aprBpsOverride is exposed for the rare case an explicitly disclosed APR must differ from the computation. Audit: writes OFFER_DRAFTED for every draft (uniform with OFFER_SENT/ACCEPTED). Logs explicit override values vs product defaults so the trail can reconstruct what was inherited vs. set per-offer.

  19. · kamo-internal

    Expose PERSONAL_LOANS in commerce-type & engagement pickers

    Adds PERSONAL_LOANS to the COMMERCE_TYPES and EngagementType lists so the new KamoLOS personal-loans market can be created from the markets/new and markets/edit pages, surfaces it in QuickActionsBar (with /commerce/personal-loans/new route), and includes it in the pipeline engagement-type filter and config.

  20. · kamo-internal

    T6+T7 frontend polish + org reports + auto-pay UI

    Frontend polish (T6): - New lib/format.ts: locale-aware Intl helpers — formatCurrency / formatDate / formatDateTime / formatPercentBps. Replaces hardcoded toLocaleString() + slice(0,10) patterns scattered across the codebase. - BorrowerAutoPayCard: full enroll / pause / resume / cancel / edit UI for the auto-pay state machine. Surfaces amount-mode dropdown (INSTALLMENT_SCHEDULED / MIN_PAYMENT / FULL_STATEMENT_BALANCE / FIXED_AMOUNT) + day-of-month + payment-method selector. Backend was already wired; this is the last piece for borrower self-service. - Charge-off queue: IRS 1099-C reason code dropdown (8 official letters A–H per IRC §6050P) replaces the free-text reason field; default **************** Notes field stays. Currency rendered via formatCurrency. Org reports (T7): - New **************** 5-bucket dashboard (Current / 1-29 / 30-59 / 60-89 / 90+) with %-of-total + color-coded top borders. Drill-through link to charge-off queue. losApi additions: - agingReport(orgId) + AgingReport type - auditSearch(filters) for the multi-filter regulator endpoint - LoanChargeOffReasonCode union type - confirmChargeOff signature accepts reasonCode

  21. · kamolos

    Tier 6+7+9+10 — frontend polish, org reports, OpenAPI, JaCoCo

    T6 (frontend polish): - New /lib/format.ts: locale-aware formatCurrency / formatDate / formatDateTime / formatPercentBps via Intl APIs (replaces hardcoded toLocaleString + slice(0,10) patterns). - BorrowerAutoPayCard: enroll / pause / resume / cancel / edit auto-pay with amount-mode dropdown (INSTALLMENT_SCHEDULED / MIN_PAYMENT / FULL_STATEMENT_BALANCE / FIXED_AMOUNT) + day-of-month + payment-method selector. - Charge-off queue: IRS 1099-C reason code dropdown (8 official letters A–H per IRC §6050P) replaces the free-text reason field; default G. Currency formatting via formatCurrency. T7 (org reports): - New LoanReportsController.aging: counts loans into Current / 1-29 / 30-59 / 60-89 / 90+ buckets via paged DELINQUENT scan + countByOrganizationAndStatus for ACTIVE. - **************** 5-bucket dashboard with %-of-total + color-coded bands + drill-through to charge-off queue. T9-T10 (DX + docs): - **************** /v3/api-docs + /swagger-ui.html auto-generated. OpenApiConfig sets title + description + version. - DevSeedLoader @Profile("dev"): boots two sample LoanProducts (12-mo installment + perpetual LOC). Idempotent (skips on existing productCode). - JaCoCo Maven plugin: prepare-agent + report on test phase. Coverage HTML at target/site/jacoco/index.html for CI consumption. losApi additions: - agingReport(orgId) + AgingReport type - auditSearch(filters) for the multi-filter regulator endpoint - confirmChargeOff signature accepts reasonCode 36 tests passing.

  22. · kamolos

    Tier 5 compliance batch

    - OfacSanctionsService: Jaro-Winkler fuzzy match @ ≥0.92 after exact-match pass. Hits surface similarity score. Configurable via los.ofac.fuzzy-threshold. - AdverseActionNoticeService: ECOA §1002.9 compliant PDF generator naming the CFPB as federal regulator. Renders reasons-for-decoder + standard ECOA non-discrimination language. Stored to MediaService. - **************** multi-filter audit query for regulator subpoenas + retention sweeps. Paged. - LoanChargeOffService.confirm accepts reasonCode parameter (IRS 1099-C box-6 letter), persisted to Loan.chargeOffReasonCode and threaded into YearEndTaxJob's 1099-C PDF render. Default G. - LoanProductValidationService warns when interestRateBps > MLA 3600bps cap so compliance can flag for offer-time MLA screening. - YearEndTaxJob: removed the produce1099Int branch — was inert (no borrower-credit-interest payment flow exists). Stub left with reactivation guidance once such a flow lands. All 30 unit tests pass.

  23. · kamo-shared-library

    LoanChargeOffReasonCode (IRS 1099-C box 6) + audit search

    - New LoanChargeOffReasonCode enum (A_BANKRUPTCY..H_OTHER) per IRC §6050P - Loan.chargeOffReasonCode column - **************** multi-filter @Query for regulator subpoenas (loanId / kind / actorType / actorMemberId / from / to) — paged

  24. · kamolos

    Tier 3 security + Tier 8 bug cleanup batch

    T3 — Security: - New RateLimitFilter (Bucket4j): 1000/min per IP for webhooks, 10/min per session for offer accept/decoder/rescind, 60/min general. Registered ahead of ProxyHmacFilter so abuse hits the bucket before cryptographic verification cost. - SecurityConfig adds HSTS + frameOptions(DENY) + contentTypeOptions via Spring Security HeaderWriter. - StripeConfig fails fast on prod profile with non-live key. T8 — Bug cleanup: - **************** looks up Loan.productIdSnapshot → LoanProduct.feeNsfFlat instead of hard-coded $25. $25 remains the fallback when product or fee is missing. - **************** gains an AdjustmentTarget field (PRINCIPAL / INTEREST / FEES). manualAdjust dispatches per target rather than always touching principal. Audit metadata now records which counter was touched. - **************** finds the most recent ACTIVE *or* PAUSED plan and reactivates it instead of creating a new row that orphans paused ones. - **************** typed FetchResult record with status (OK / NOT_FOUND / UNREACHABLE / UNCONFIGURED). fetchBlob(uid) preserved as legacy bytes-or-null adapter.

  25. · kamo-shared-library

    ****************

    Used by **************** to reactivate an existing PAUSED plan instead of creating a duplicate row that orphans the paused one.

  26. · kamo-internal

    /api/los proxy HMAC-signs identity headers

    When LOS_PROXY_HMAC_SECRET env var is set, the proxy now computes **************** secret) and sends X-Proxy-Signature + X-Proxy-Timestamp alongside the X-Org-Id / X-Member-Id headers. KamoLOS's new ProxyHmacFilter validates this (±60s skew, constant-time compare) so a client bypassing kamo-internal can no longer spoof identity headers. Unset secret = local-dev pass-through (filter disabled on the backend side too).

  27. · kamo-internal

    Org-side loan detail page + adjustments/promise API

    - losApi: manualAdjust, waiveFee, promiseToPay - **************** full org-operations page * 4-up balance cards (current, principal, interest, next due) * Manual adjustment form (CREDIT/DEBIT, required reason) — calls /api/los/adjustments/manual * Promise-to-pay form — calls **************** * Statements + payments tables * Embeds <LoanAuditTimeline> for the full event log - This resolves the broken link that MarketActiveLoansTab + PipelineActiveLoansTab were pointing to.

  28. · kamolos

    Final batch — compliance, packets, adjustments, Grafana, tests

    Compliance: - MlaCheckService — Military Lending Act covered-borrower lookup against DoD MDC single-record query. Returns NotCovered if unconfigured so dev doesn't hard-fail; production MUST configure los.mla.endpoint + api-key OR run manual MLA at the org's KYC step (PII access required). - OfacSanctionsService — Treasury SDN list cached in-memory, refreshed nightly from sdn.csv. Unicode NFD normalization + diacritic stripping. Wired into LoanOfferService.send: SDN hit blocks the send and writes a STATE_RULE_VIOLATION_DETECTED audit row. - LoanLifecycleService cooling-off now reads LoanStateRule.coolingOffDays for the borrower's state (replaces the LocalDateTime.now() placeholder). - Stripe account.updated webhook now syncs charges/payouts/details flags + default_currency + requirements_disabled_reason onto LoanOrgStripeConnect so cron jobs + the org settings UI see live capability state. Disclosure packet: - New DisclosurePacketBuilder bundles TILA box + GLBA privacy notice + ECOA non-discrimination notice + ACH mandate authorization into one PDF via PDFBox PDFMergerUtility + stores in MediaService. Each component is recorded as a LoanDisclosure row keyed to the offer. LoanOfferService.send builds the packet automatically before the ESign envelope is created. Servicing operations: - **************** — fees/principal credit-or-debit with required reason; emits LoanPayment(INTERNAL_ADJUSTMENT or REVERSAL_ADJUSTMENT) + MANUAL_ADJUSTMENT audit - LoanAdjustmentService.waiveFee — flips LoanFee.waivedAt + credits feesPaidToDate + emits FEE_WAIVED audit - **************** — non-financial commitment row used in D60+ workflow tracking, emits PROMISE_TO_PAY_LOGGED audit - LoanAdjustmentController exposes /api/los/adjustments/{manual, fees/{uid}/waive, promise-to-pay} Observability: - **************** — importable dashboard with status gauges, payment outcome rates, R-code returns by code, webhook p50/p90/p99, disbursements/hour, charge-offs/day, JVM heap, HTTP rate Tests (30 total, all green): - 7 AmortizationEngine - 9 InterestAccrualEngine (including the previously-fixed 100x bug) - 5 PaymentAllocator - 4 NachaReturnCode (retry classes + mandate-revoking codes + describe) - 5 OfacSanctionsService (clean match / hit / diacritics / alias / disabled via reflection-injected cache, no network or test container needed)

  29. · kamolos

    P2 batch — observability, tests, year-end tax, real Stripe IDs

    Observability: - micrometer-registry-prometheus on the classpath - /actuator/prometheus exposed via management config (both local + k8s configmap) - LoanMetrics service: **************** **************** los_charge_off_total, los_disbursement_total, los_loan_status_count{status} gauge, **************** histogram - Counters wired into LoanPaymentPostingService + LoanChargeOffService Math + tests: - 21 unit tests covering AmortizationEngine (level payment, APR Newton- Raphson, monthly due-date progression, principal+interest invariant, bad-input guards), InterestAccrualEngine (ACT_365 / ACT_360 DPR, one-day / thirty-day accrual, LOC ADB), PaymentAllocator (fees→interest →principal order, partials, overpayment, nulls) - **Fixes 100x interest-rate bug** in **************** — divisor was 36500/36000 instead of 3,650,000/3,600,000 (annualRate fraction needs both /10000 bps→fraction AND /365 days), so accrued interest was 100× too high. Test added that would have caught it. Real Stripe identifiers (no more placeholders): - StripeConnectController.onboard now persists LoanOrgStripeConnect on successful Stripe Account creation; .status returns onboarding state - New StripeIdResolver: connectedAccountIdForOrg() + lazy getOrCreateBorrowerCustomerId() (idempotency-keyed Customer.create under the connected account, persists to Loan.stripeCustomerId) - AutoDebitRunJob.fireAchDebit + .fireDisbursement now resolve real IDs through StripeIdResolver instead of "acct_placeholder_*" Reminder cadence: - ReminderSchedulingJob: nightly sweep emits PAYMENT_DUE_3_DAYS, PAYMENT_DUE_TODAY, PAYMENT_LATE_DAY_1/5/15, OFFER_EXPIRING (24h before expiresAt), STATEMENT_AVAILABLE (on generation). Dedup-keyed via **************** so reruns are idempotent. Year-end tax: - YearEndTaxJob (cron 06:00 Jan 5): produces 1099-C for charge-offs ≥$600 + 1099-INT for borrower-credit interest > $10. PDFBox-rendered form, stored to MediaService, recorded as LoanDisclosure with form/year metadata so reruns are idempotent. - Loan.tax1099CEligible auto-set on charge-off confirm when amount ≥ $600

  30. · kamo-shared-library

    LoanOrgStripeConnect + Loan tax/accrual fields + hot indexes

    - New LoanOrgStripeConnect entity (one-per-org, holds stripeConnectedAccountId + Connect onboarding capabilities) + repository - Loan: stripeCustomerId (lazy-created under the org's connected account), lastAccrualDate, tax1099IntEligible, tax1099CEligible - Hot composite indexes: * loans: (org_id,status), (borrower_member_id,status), (next_payment_due_at), (status,delinquency_tier) * loan_payments: (loan_id,initiated_at), (loan_id,status), (stripe_payment_intent_id), (statement_id) * loan_statements: (loan_id,statement_number DESC), (due_date,status) * loan_offers: (org_id,status), (borrower_member_id,status), (status,expires_at)

Your universe is waiting.

Start free in minutes. Add products whenever you are ready — no migration, no new logins.

Free forever — no credit card required