- ClientFamilyMembersWritePlatformServiceImpl#updateFamilyMember: guard
maritalStatusId/genderId/professionId with != null instead of != 0 so a
payload that omits these keys doesn't NPE when the boxed Long is unboxed.
- application.properties: fix the broken property name
`fineract.security.cors.allowed-metho pas` -> `allowed-methods`. The typo
left CORS allowed-methods empty, which made Spring reject every browser
POST with "Invalid CORS request" 403 once the SPA sent an Origin header.
- application.properties: change the Hikari default password from 313863
to root so the local devRun matches the tenant-DB default at line 49.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PAN verify for non-individual clients was sending name="" + dob=null to LincPay because
the code path always read firstname/middlename/lastname (NULL on entities) and
client.dateOfBirth (also NULL on entities). LincPay then returned plain-text "Invalid
DOB format. Required format is dd/MM/yyyy", which Gson can't parse (MalformedJsonException
at line 1 col 10), surfacing as a useless 503. Now ClientKycWritePlatformServiceJpa-
RepositoryImpl.verifyPan branches on LegalForm:
- PERSON → name = firstname + middlename + lastname, dob = client.dateOfBirth
- ENTITY → name = client.fullname, dob = clientNonPerson.dateOfRegistration
Injected ClientNonPersonRepositoryWrapper for the entity branch.
ClientNonPerson got two missing getters (getConstitution, getDateOfRegistration) needed
by the above plus the family/nominee rules below.
Defensive JSON parse in LincPayKycVerificationServiceImpl: parseJson now returns null
when the body doesn't even start with '{' or '[' (i.e. vendor sent plain text or HTML),
logs the literal body at WARN, and every verify method (PAN, GSTIN, Aadhaar OTP gen,
Aadhaar OTP verify, CIN, DIN) checks for null and returns
"<X> verification failed — vendor returned non-JSON: <first 200 chars>"
So future vendor outages surface the actual response text instead of a MalformedJson-
Exception with no context.
Non-individual client rules:
- ClientFamilyMembersWritePlatformService: addFamilyMember / addClientFamilyMember /
updateFamilyMember now refuse with PlatformApiDataValidationException when the client
is an entity. Family members are an individual-only concept. Delete is intentionally
unguarded so legacy rows can be cleaned up.
- ClientNomineeWritePlatformService: addNominee / updateNominee refuse for non-individual
clients UNLESS the constitution normalises to PROPRIETORSHIP (matches both
"PROPRIETOR SHIP" and "PROPRIETORSHIP"). Proprietorships are operationally individuals
with a separate trade name, so they keep nominee support; companies / LLPs / trusts /
societies / HUFs / partnerships don't carry a nomination concept. Delete unguarded.
Verified the LincPay endpoints directly:
/validate-pan AQRPV8345E + "Aditya Vyas" + 19/12/1988 → valid (individual)
/validate-pan AAMCG5406B + "GIZMOGLADE..." + 01/12/2025 → valid (company)
/validate-pan AAMCG5406B + "" + "" → "Invalid DOB format" plain text (the failing path)
/validate-gstn 23AADCL9984A1ZH → 200 with full company data
/validate-gstn 21AAMCG5406BIZ6 → 400 "Invalid GSTIN pattern" (bad checksum, not our bug)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The second schema() builder used by the clients-list query had the JOIN
left join m_client gc on gc.id = c.guardian_client_id
and the SELECT
c.guardian_client_id as guardianClientId
but was missing
gc.display_name as guardianClientDisplayName
which the RowMapper still reads (rs.getString("guardianClientDisplayName")).
Result: PSQLException "The column name guardianClientDisplayName was not found
in this ResultSet" → BadSqlGrammarException → 500 on any client-list pull.
The matching first builder (~line 327) had both columns correctly. Added the
missing SELECT line to keep the pair consistent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- SearchParameters: added fromDate, toDate, asOnDate (LocalDate) fields with hasX() helpers
- SavingsAccountsApiResource: added status, fromDate, toDate, asOnDate query params
- SavingsAccountReadPlatformServiceImpl: resolveStatusEnum(), date filters on activatedon_date, default ORDER BY id DESC
- CurrentAccountsApiResource + service: same filters on ca.status_enum and ca.activatedon_date
- DepositAccountReadPlatformService (interface): new retrieveAllPaged overload with date/status params
- DepositAccountReadPlatformServiceImpl: resolveStatusEnum(), date filters, default ORDER BY id DESC for FD/RD
- FixedDepositAccountsApiResource + RecurringDepositAccountsApiResource: pass new params to service
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New CurrentAccountTransactionWritePlatformService interface with deposit,
withdrawal, holdAmount, releaseAmount, undoTransaction, reverseTransaction,
and adjustTransaction operations
- New CurrentAccountTransactionWritePlatformServiceImpl using m_current_account
tables directly (not m_savings_account), with GL journal entry posting
- Add holdAmount, releaseAmount, reversal factory methods to CurrentAccountTransaction
- Wire all 7 current account transaction command handlers to new write service
- Fix CurrentAccountTransactionsApiResource template SQL column: currency_multiplesof
- Fix account number search in SearchParameters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use FD/RD transaction ID prefixes for Fixed/Recurring Deposit GL entries
via ThreadLocal context set in Cash/AccrualBasedAccountingProcessorForSavings
- Add depositAccountType to SavingsDTO and pass from SavingsAccount bridge data
- Add savingsAccountNo field to JournalEntryData; populate via LEFT JOIN on
m_savings_account (SB/FD/RD) and m_current_account (CA prefix) in read query
- Fix JournalEntryMapper MapStruct: ignore savingsAccountNo (SQL-populated)
- Fix JournalEntryRunningBalanceUpdateServiceImpl constructor call (27 params)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pre-save validator (ClientWritePlatformServiceJpaRepositoryImpl.validateClientUniqueness)
rejects duplicate mobile / email / PAN / Aadhaar (masked) / GSTIN before saveAndFlush
and before the LincPay KYC API call. Avoids burning vendor credits on identity-duplicate
records and gives the UI a clean field-level 403 instead of waiting for the DB constraint.
Splits the new repository finders into "create" and "excluding-self" variants so a
nullable Long parameter never reaches PostgreSQL's parser (same bigint <> varchar trap
that broke the family-member POST earlier).
Also seeds the full set of 28 Indian States + 8 UTs as STATE code_values (migration
0297). Previously only MAHARASHTRA / MADHYA_PRADESH / DELHI existed and the names were
UPPER_SNAKE_CASE; the postal PIN-lookup service returns "Madhya Pradesh" so any state
other than Maharashtra/Delhi silently failed to resolve to m_address.state_province_id.
Renames the 3 existing rows in place (preserves their ids/FKs) and inserts the missing
33 with idempotent preConditions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The relationship-uniqueness query used (:excludeId IS NULL OR fm.id <> :excludeId).
On create, excludeId is null and PostgreSQL parses the parameter as varchar before
the IS NULL branch can short-circuit, so adding a family member returned 403 with
"operator does not exist: bigint <> character varying". Split into two queries so
neither has a nullable Long parameter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings TOTP / Google Authenticator MFA factor (device mapping at
/v1/mfa/totp/setup + /verify, login via existing twofactor pipeline),
DSC-2FA login UI fix, Individual-only filter on guardian/nominee/family
link pickers, Title Case normalization on client names, and migration
0296 unique nominee per account type.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts:
# .claude/settings.local.json
# fineract-provider/logs/fineract.log
Backend:
- New dependency dev.samstevens.totp:totp:1.7.1 (bundles ZXing for QR PNG).
- TotpService: secret/QR/otpauth-URL/verify primitives. SHA1, 6 digits, 30s —
the standard combo every consumer authenticator app accepts unconfigured.
Issuer label is configurable via FINERACT_MFA_TOTP_ISSUER, defaults to
"CBSnexora".
- Two-step device mapping driven by TotpEnrollmentService:
* POST /v1/mfa/totp/setup — generate secret, upsert m_user_mfa_factor row
as factor_type=TOTP/verified=false, return {secret, qrDataUri, otpAuthUrl,
issuer, factorId}. Refuses to overwrite an already-verified-and-active
factor so an accidental click can't unbind the user's working device.
* POST /v1/mfa/totp/verify — body {code}; verify against the stored secret
and flip verified=true on success.
- Login wiring in MfaFactorBackedTwoFactorServiceImpl:
* getDeliveryMethodsForUser surfaces verified TOTP factors as
{name:"totp", target:"Google Authenticator"} so the picker offers it
alongside SMS/EMAIL.
* createNewOTPToken("totp") creates a no-op OTPRequest (no LincPay/SMTP
round-trip) carrying the factor id in the token field so validate can
look up the secret.
* createAccessTokenFromOTP branches on channel — TOTP path verifies code
vs factor.secret with totp library (sliding 1-step window) and mints
a TFAccessToken with the same lifetime + extended-token logic as OTP.
* Defence-in-depth: re-checks at validate time that the factor still
belongs to the user, is verified, and not disabled.
- TwoFactorConstants.TOTP_DELIVERY_METHOD_NAME = "totp".
- SecurityConfig: /api/*/mfa/totp/** is fullyAuthenticated() (exempt from
the TFA gate so users can enrol before they can satisfy 2FA).
UI changes pending (see chat): add a "Google Authenticator" tile on the
MFA Factors settings page that calls /setup → shows QR + secret → calls
/verify; extend two-factor-authentication.component.ts iconFor() and copy
for the new "totp" delivery method. Existing requestOTP / validateOTP
pipeline handles the login flow transparently.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- DSC-as-2FA login: UI's claimAndValidate() now calls a new
AuthenticationService.applyTwoFactorToken(token, liveSeconds) instead of
validateOTP(). The /v1/login/dsc/{rn}/claim response is already a minted
TFAccessToken — POSTing it to /twofactor/validate fails because that path
expects an in-memory OTPRequest. applyTwoFactorToken runs only the
post-success bookkeeping (interceptor.setTwoFactorAccessToken, setCredentials,
Authentication Success alert that drives router.navigate(['/']), and
sessionStorage persist of {token, validFrom, validTo}). Fixes the
OTPTokenInvalidException users hit after a successful DSC verify+claim.
- Individual-only filter for link-client pickers: ClientTextSearch gains a
legalFormType field; SearchingClientRepository accepts an Integer legalForm
value and adds cb.equal(r.get("legalForm"), ...) to the spec.
ClientSearchService.parseLegalForm tolerates INDIVIDUAL/PERSON/1 and
NON_INDIVIDUAL/ENTITY/2; unknown = no filter so a typo doesn't blank results.
UI: clients.service.ts searchByText gains a 7th optional arg; six link
pickers (guardian, nominee, family-member across create flow and post-create
tabs) now pass 'INDIVIDUAL'. General client list and signatory picker are
intentionally unchanged.
- Title-case client names on persistence: ClientWritePlatformServiceJpa
RepositoryImpl now runs first/middle/last/full name fields through
toTitleCase() on both create and update paths. "aditya kumar vyas",
"ADITYA KUMAR VYAS", "Aditya Kumar Vyas" all persist as "Aditya Kumar Vyas".
Whitespace-split title case; preserves hyphens/apostrophes inside words
without re-capitalising after them so clerks verifying against ID documents
see predictable output.
- Migration 0296: unique constraint to prevent duplicate nominee per account
type (the underlying repository + service changes also persisted in this
commit complete the enforcement path).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- m_user_mfa_factor is now the single 2FA gate. DSC enroll callback writes a
factor row (factor_type=DSC), revoke flips disabled_on when the last cert
goes. AuthenticationApiResource and TwoFactorAuthenticationFilter drop the
direct m_bank_user_dsc check so disabling from the UI actually stops the
challenge at login.
- SecurityConfig exempts /api/*/dsc/** and /api/*/mfa/factors from the
TWOFACTOR_AUTHENTICATED gate. Without this a user with a verified factor
can't manage their own factors after they break the device.
- MfaFactorType gains a DSC enum value; SelfUserMfaFactorServiceImpl error
message lists it.
- dsc-helper: CBSnexora-branded header (logo, title, subtitle, accent palette),
8-column cert table (Holder Name, Alias, Organisation, DSC Type, Serial,
Issuer, Valid From, Expires), LdapName-based DN parsing, sign+verify proof-
of-possession that picks SHA256withRSA / SHA256withECDSA from the key's own
algorithm (works on RSA, RSA Signing-only, EC), pattern-matched JNLP age
check, dscType JNLP-arg override with KeyUsage fallback.
- generate-test-dsc.ps1 produces a realistic self-signed DSC with strong key
protection so dev testing sees real CN/O values and a PIN prompt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backend:
- m_bank_user_dsc + m_dsc_challenge tables (Liquibase 0292/0293/0294)
- DSC enrollment flow: start → JNLP → JWS helper → callback → upsert
- DSC login factor: challenge → sign → verify → claim minted TFA token
- 2FA gate now also accepts an active DSC mapping (AuthenticationApiResource
+ TwoFactorAuthenticationFilter), defensively wrapped so a missing DSC table
doesn't 500 the auth path
- TenantAwareBasicAuthenticationFilter exempts /api/v*/dsc/helper.jar and
strips Authorization header for that path so JWS's raw GET works without
the user seeing a browser Basic-auth prompt
- DSC helper JAR served at /api/v*/dsc/helper.jar from a configurable
filesystem path (default ~/.cbsnexora/dsc-helper.jar)
Java helper JAR (dsc-helper/):
- Standalone Gradle subproject, Java 8 target, fat jar with Gson shaded in
- Cross-platform token picker (Windows Cert Store / ePass2003 / eToken /
ProxKey / Other) — matches the JNLP arg contract emitted by the backend
- Sets Fineract-Platform-TenantId on its callback POST from the JNLP args
- JNLP age check now reads cbsnexora-dsc.jnlp (current name) AND
SignTender.jnlp (legacy) — picks the youngest, never blocks if none exist
Inline KYC upsert in updateClient (mirror of the create-flow path):
- ClientKycWritePlatformService.upsertKycInline() — creates or merges KYC
fields, bumps status to IN_PROGRESS on real changes; resets PAN verification
on PAN edit of a verified record
- ClientWritePlatformServiceJpaRepositoryImpl.updateClient() handles the
kyc:{} sub-object on PUT /clients/{id}, matching createClient
User uniqueness:
- m_appuser.email + m_appuser.mobile_no UNIQUE (Liquibase 0295)
- Pre-flight assertEmailAndMobileUnique() in create/update with self-exclusion
- handleDataIntegrityIssues maps the two new constraints to clean
field-scoped error messages (race-condition safety net)
Email OTP subject line now: "Your OTP for Login - <AppName> (dd-MM-yy-HH:mm)"
where AppName comes from fineract.email.from-name (default "CBSnexora").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Login 2FA via @Primary MfaFactorBackedTwoFactorServiceImpl (replaces legacy)
- LincPay SMS OTP and SMTP email OTP with auto-promote to m_user_mfa_factor
- Reusable EmailSenderService + EmailTemplateService (CBSnexora branding)
- Admin OTP audit viewer (GET /v1/otp-logs) with paging, search, masking
- Self-service MFA factor API at /v1/mfa/factors
- READ_OTP_LOG permission + OTP Log Auditor role + bankadmin direct grant
- JsonAuthenticationEntryPoint suppresses Basic-auth popup for SPA paths
- TwoFactorAuthenticationFilter grants TWOFACTOR_AUTHENTICATED when no challenge
- Soft-delete + audit columns + deletion audit + version on client tables
- CIF number generator (TTYYYYNNNNNN format)
- DocumentNotFoundExceptionMapper to silence noisy WARN logs on missing images
- Liquibase migrations 0278-0291 (MFA, OTP log, audit/soft-delete, role wiring)
Note: application.properties intentionally not committed — contains local DB
and Gmail App Password fallbacks. Set SPRING_MAIL_PASSWORD,
FINERACT_DEFAULT_TENANTDB_PWD, FINERACT_HIKARI_PASSWORD as env vars in prod.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- GET /ckyc/reports/summary — KPI cards (total, gender split, missing PAN/Aadhaar, dup mobile)
- GET /ckyc/reports/state-distribution — donut chart by state
- GET /ckyc/reports/branch-distribution — top-N branches bar chart
- GET /ckyc/reports/upload-trend — daily upload line chart
- GET /ckyc/customers — paginated, filterable customer list
- GET /ckyc/customers/search — search by name/mobile/pan/cif (capped 50)
- GET /ckyc/customers/{cif} — customer detail with addresses
- GET /ckyc/customers/export — CSV/TXT (pipe-delimited CERSAI format)
- POST /ckyc/files — parse inbound CKYC .txt file
- GET /ckyc/files — list historical uploads
- POST /ckyc/files/generate — generate outbound submission file
- GET /ckyc/branches, /states, /codes/id-types — master data
- Migration 0259: performance indexes on m_client_kyc and m_client
- Postman collection with all endpoints
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Migration 0256: EducationLevel & DegreeQualification codes with m_client FK columns
- Migration 0257: Religion & SocialCategory codes with m_client FK columns
- Migration 0258: agriculture_income & non_agriculture_income decimal columns on m_client
- Full API support: create, update, template options, and GET response for all new fields
- Postman collection for testing all new dropdowns
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Added accountTypeOptions and customerSegmentOptions fields to SavingsAccountData and RecurringDepositAccountData
- Added CodeValueReadPlatformService to SavingsAccountTemplateReadPlatformServiceImpl, CurrentAccountTemplateReadPlatformServiceImpl, and DepositAccountReadPlatformServiceImpl to fetch options from code values
- Added options to SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS and RD response parameters
- Updated SavingsConfiguration bean factory methods to pass new CodeValueReadPlatformService dependency
- Added validCheckSum for modified Liquibase changeset 0272
- Fixed AccountNumberGeneratorTest constructor to include GLAccountRepository and SavingsProductRepository
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>