Skip to content

Backend : Ench - Adds Yesbiz PG reconcillation report parser#1198

Open
kavya-shree-s wants to merge 1 commit intomainfrom
Backend-ench-adds-yesbiz-pg-reconcillation
Open

Backend : Ench - Adds Yesbiz PG reconcillation report parser#1198
kavya-shree-s wants to merge 1 commit intomainfrom
Backend-ench-adds-yesbiz-pg-reconcillation

Conversation

@kavya-shree-s
Copy link
Copy Markdown
Contributor

@kavya-shree-s kavya-shree-s commented Mar 27, 2026

Type of Change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring
  • Dependency updates

Description

Additional Changes

  • This PR modifies the database schema (database migration added)
  • This PR modifies dhall configs/environment variables

Motivation and Context

How did you test it?

Checklist

  • I formatted the code and addressed linter errors ./dev/format-all-files.sh
  • I reviewed submitted code
  • I added unit tests for my changes where possible
  • I added a CHANGELOG entry if applicable

Summary by CodeRabbit

  • New Features

    • Added YesBiz as a supported settlement service provider for payment and settlement imports.
    • Enhanced SFTP file retrieval with optional key-based authentication using private key paths, in addition to existing password-based access.
  • Tests

    • Expanded test utilities to support YesBiz settlement file operations and SFTP key-based authentication workflows.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

Walkthrough

YesBiz payment settlement support was introduced through new type definitions, two new CSV parsing modules, routing logic in the settlement interface, SFTP authentication refactoring to support key-based access, and test harness extensions for remote file retrieval and parsing.

Changes

Cohort / File(s) Summary
Type System & Cabal Exposure
lib/mobility-core/mobility-core.cabal, lib/mobility-core/src/Kernel/External/Settlement/Types.hs
Extended SettlementService enum with YesBiz constructor; added YesBizConfig variant to SettlementServiceConfig; added optional privateKeyPath field to SFTPConfig for key-based SFTP authentication. Exposed new YesBiz modules in cabal file.
YesBiz Parsing Implementation
lib/mobility-core/src/Kernel/External/Settlement/YesBiz/PaymentParser.hs, lib/mobility-core/src/Kernel/External/Settlement/YesBiz/PaymentTypes.hs
Defined YesBizRow record with CSV field mappings and implemented FromNamedRecord/ToJSON instances for CSV parsing. Implemented parseYesBizCsv and parseYesBizRow to decode CSV, normalize fields (transaction types, status codes, dates, amounts), and convert rows to PaymentSettlementReport with conditional refund-amount population.
Settlement Interface Routing
lib/mobility-core/src/Kernel/External/Settlement/Interface.hs
Extended parsePaymentSettlementCsv to route YesBiz cases to parseYesBizCsv; extended parsePayoutSettlementCsv to return unsupported error for YesBiz.
SFTP Protocol Refactoring
lib/mobility-core/src/Kernel/External/Settlement/Sources/SFTP.hs
Refactored fetchSettlementFile to branch on config.privateKeyPath: when present, use new fetchWithKey helper with sftp+identity-file; when absent, use new fetchWithPassword helper with legacy sshpass+scp. Unified post-processing (read, cleanup) applied after either branch succeeds.
Test CLI Expansion & Cabal
test-settlement-parser/src/Main.hs, test-settlement-parser/test-settlement-parser.cabal
Extended CLI with sftp command pattern accepting key-path, user@host, remote-path, optional port and file-pattern; implemented testSftpFetch to list remote files via sftp CLI, filter by extension/pattern, download each file, conditionally unzip, and parse with parsePaymentSettlementCsv. Added extractSftpFileList parser for sftp ls output. Extended parsePG and pgDirName to support yesbiz. Updated cabal other-modules to include YesBiz parser modules.

Sequence Diagram

sequenceDiagram
    participant CLI as Test CLI
    participant SFTP as SFTP Module
    participant Local as Local Filesystem
    participant Parser as YesBiz Parser
    participant Report as Settlement Report

    CLI->>SFTP: fetchSettlementFile(config with privateKeyPath)
    alt Key-Based Auth
        SFTP->>SFTP: fetchWithKey (sftp -i identity_file)
    else Password Auth
        SFTP->>SFTP: fetchWithPassword (sshpass + scp)
    end
    SFTP->>Local: Download remote file to localPath
    SFTP->>Local: Read localPath contents
    SFTP->>Local: Remove temporary file
    SFTP-->>CLI: Return file contents Right
    CLI->>Parser: parsePaymentSettlementCsv (YesBiz routing)
    Parser->>Parser: parseYesBizCsv (decode CSV, extract rows)
    loop For each YesBizRow
        Parser->>Parser: parseYesBizRow (normalize types, amounts, dates)
        Parser->>Report: Convert to PaymentSettlementReport
    end
    Parser-->>CLI: Return ParseResult with reports and errors
    CLI->>Local: Write parsed output to output/yesbiz/...
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A YesBiz has hopped into the settlement fray,
With CSV rows parsed both night and day,
SFTP keys now dance with passwords old,
New pathways branching, more stories told! 🛣️✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding YesBiz payment gateway reconciliation report parser support across multiple modules and configuration files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch Backend-ench-adds-yesbiz-pg-reconcillation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
lib/mobility-core/src/Kernel/External/Settlement/YesBiz/PaymentParser.hs (1)

108-112: parseAmount silently returns 0 on parse failure, potentially masking data issues.

If a CSV contains malformed amount values (e.g., "N/A", currency symbols), they'll be silently converted to 0, which could cause incorrect settlement calculations.

Consider logging a warning or propagating the parse failure to the row-level errors.

♻️ Alternative: Return Either to surface parse failures
parseAmount' :: Text -> Either Text HighPrecMoney
parseAmount' t =
  case readMaybe (T.unpack $ T.strip t) of
    Just v -> Right v
    Nothing -> Left $ "Invalid amount: " <> T.strip t
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/mobility-core/src/Kernel/External/Settlement/YesBiz/PaymentParser.hs`
around lines 108 - 112, parseAmount currently swallows parse failures by
returning 0 which can mask bad CSV data; change parseAmount to surface failures
(e.g., return Either Text HighPrecMoney or Maybe HighPrecMoney) instead of
defaulting to 0, update callers to handle and propagate the Left/Nothing as a
row-level error (or log a warning including the raw Text) so malformed values
(like "N/A" or currency symbols) are not silently treated as zero; refer to the
parseAmount function and any call sites that consume its result to implement the
propagation/logging changes.
lib/mobility-core/src/Kernel/External/Settlement/Sources/SFTP.hs (1)

45-50: Minor: Temp file may remain if readFile fails.

If LBS.readFile localPath throws an exception, the removeFile cleanup won't execute. Consider using bracket or a finally pattern for cleanup.

♻️ Suggested improvement
   case result of
     Right _ -> do
-      contents <- liftIO $ LBS.readFile localPath
-      liftIO $ removeFile localPath
-      pure $ Right contents
+      liftIO $ do
+        contents <- LBS.readFile localPath `finally` removeFile localPath
+        pure $ Right contents
     Left err -> pure $ Left err

This requires importing Control.Exception (finally).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/mobility-core/src/Kernel/External/Settlement/Sources/SFTP.hs` around
lines 45 - 50, The Right branch of the case on result reads the temp file with
LBS.readFile localPath then removes it, but if readFile throws the temp file
won't be deleted; change the Right branch to ensure cleanup always runs by
wrapping the read+remove sequence with a finally or bracket (import
Control.Exception (finally)) so removeFile localPath is executed even on
exceptions—i.e., use finally (or bracket) around LBS.readFile localPath to
guarantee removeFile localPath is called and return the file contents as Right
when successful.
lib/mobility-core/src/Kernel/External/Settlement/YesBiz/PaymentTypes.hs (1)

71-95: Consider deriving ToJSON instead of manual implementation.

The manual ToJSON instance duplicates the field names. Since the record field names already use camelCase (matching JSON conventions), you could derive it:

♻️ Suggested simplification
 data YesBizRow = YesBizRow
   { merchantId :: Text,
     ...
   }
-  deriving (Show, Eq, Generic)
+  deriving (Show, Eq, Generic)
+  deriving anyclass (A.ToJSON)
-
-instance A.ToJSON YesBizRow where
-  toJSON row =
-    A.object
-      [ "merchantId" A..= row.merchantId,
-        ...
-      ]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/mobility-core/src/Kernel/External/Settlement/YesBiz/PaymentTypes.hs`
around lines 71 - 95, The manual A.ToJSON instance for YesBizRow duplicates
every record field; remove that instance and derive the JSON instance instead by
adding Generic to the YesBizRow type (ensure GHC.Generics is imported) and
deriving A.ToJSON (or use DeriveAnyClass/DerivingStrategies as your style
requires), then delete the explicit instance ToJSON YesBizRow and rely on the
derived implementation for toJSON.
test-settlement-parser/src/Main.hs (1)

217-222: extractSftpFileList relies on fragile parsing of sftp ls output.

The function uses last . words which can fail on empty lines or unusual output formats. The filter for entries with . in the name is a reasonable heuristic but may miss edge cases.

This is acceptable for a test harness but worth noting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test-settlement-parser/src/Main.hs` around lines 217 - 222, The current
extractSftpFileList function uses unsafe last . words which can crash or
mis-parse unusual/empty lines; replace that with a safe extraction: for each
line (excluding those starting with "sftp>" or blank) split into words and take
the last element only if the list of words is non-empty (e.g., via a helper
safeLast or listToMaybe . reverse and mapMaybe), then keep the existing '.'
filter; update the function extractSftpFileList to use this safe parsing
approach so it never calls last on an empty list and more robustly ignores
malformed lines.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/mobility-core/src/Kernel/External/Settlement/YesBiz/PaymentParser.hs`:
- Around line 149-156: parseTxnStatus fails on lowercase values because it only
strips whitespace; update it to normalize case like parseTxnType does (e.g.,
apply T.toUpper to T.strip t) before pattern matching so inputs like "success"
or "failed" are handled; keep the same branches (matching "00", "0", "SUCCESS",
"FAILED") after normalization and return the same Right SUCCESS/FAILED or Left
on unknown values in the parseTxnStatus function.

In `@test-settlement-parser/src/Main.hs`:
- Around line 130-132: The current parsing of userAtHost sets both sftpUser and
sftpHost to the full string when there is no '@', producing misleading config;
update the logic around userAtHost (where sftpUser and sftpHost are derived) to
validate the format and fail fast: if break (== '@') userAtHost does not yield a
proper (user, '@':host) pair then produce a clear IO error (e.g., using error or
fail in IO with a descriptive message) or return an explicit validation failure
instead of silently using the same value for both sftpUser and sftpHost.
- Around line 27-32: The code uses read portStr in the command-line branch
(inside the Main.hs match that calls parsePG and testSftpFetch) which will throw
on non-numeric input; change this to use readMaybe (e.g., import
Text.Read.readMaybe) to parse portStr safely, handle the Nothing case by
returning a clear error message or exiting gracefully, and pass the successfully
parsed Int to testSftpFetch; update both places that call testSftpFetch (the two
patterns that bind portStr) so that testSftpFetch always receives a validated
Int and invalid input is reported to the user instead of crashing.

---

Nitpick comments:
In `@lib/mobility-core/src/Kernel/External/Settlement/Sources/SFTP.hs`:
- Around line 45-50: The Right branch of the case on result reads the temp file
with LBS.readFile localPath then removes it, but if readFile throws the temp
file won't be deleted; change the Right branch to ensure cleanup always runs by
wrapping the read+remove sequence with a finally or bracket (import
Control.Exception (finally)) so removeFile localPath is executed even on
exceptions—i.e., use finally (or bracket) around LBS.readFile localPath to
guarantee removeFile localPath is called and return the file contents as Right
when successful.

In `@lib/mobility-core/src/Kernel/External/Settlement/YesBiz/PaymentParser.hs`:
- Around line 108-112: parseAmount currently swallows parse failures by
returning 0 which can mask bad CSV data; change parseAmount to surface failures
(e.g., return Either Text HighPrecMoney or Maybe HighPrecMoney) instead of
defaulting to 0, update callers to handle and propagate the Left/Nothing as a
row-level error (or log a warning including the raw Text) so malformed values
(like "N/A" or currency symbols) are not silently treated as zero; refer to the
parseAmount function and any call sites that consume its result to implement the
propagation/logging changes.

In `@lib/mobility-core/src/Kernel/External/Settlement/YesBiz/PaymentTypes.hs`:
- Around line 71-95: The manual A.ToJSON instance for YesBizRow duplicates every
record field; remove that instance and derive the JSON instance instead by
adding Generic to the YesBizRow type (ensure GHC.Generics is imported) and
deriving A.ToJSON (or use DeriveAnyClass/DerivingStrategies as your style
requires), then delete the explicit instance ToJSON YesBizRow and rely on the
derived implementation for toJSON.

In `@test-settlement-parser/src/Main.hs`:
- Around line 217-222: The current extractSftpFileList function uses unsafe last
. words which can crash or mis-parse unusual/empty lines; replace that with a
safe extraction: for each line (excluding those starting with "sftp>" or blank)
split into words and take the last element only if the list of words is
non-empty (e.g., via a helper safeLast or listToMaybe . reverse and mapMaybe),
then keep the existing '.' filter; update the function extractSftpFileList to
use this safe parsing approach so it never calls last on an empty list and more
robustly ignores malformed lines.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c1ecb431-e1d5-4853-aa68-29f12aedbb5b

📥 Commits

Reviewing files that changed from the base of the PR and between 8a51c4f and d0448c5.

📒 Files selected for processing (8)
  • lib/mobility-core/mobility-core.cabal
  • lib/mobility-core/src/Kernel/External/Settlement/Interface.hs
  • lib/mobility-core/src/Kernel/External/Settlement/Sources/SFTP.hs
  • lib/mobility-core/src/Kernel/External/Settlement/Types.hs
  • lib/mobility-core/src/Kernel/External/Settlement/YesBiz/PaymentParser.hs
  • lib/mobility-core/src/Kernel/External/Settlement/YesBiz/PaymentTypes.hs
  • test-settlement-parser/src/Main.hs
  • test-settlement-parser/test-settlement-parser.cabal

Comment on lines +149 to +156
-- | Status "00" means success for YesBiz UPI
parseTxnStatus :: Text -> Either Text TxnStatus
parseTxnStatus t = case T.strip t of
"00" -> Right SUCCESS
"0" -> Right SUCCESS
"SUCCESS" -> Right SUCCESS
"FAILED" -> Right FAILED
other -> Left $ "Unknown transaction status: " <> other
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent case handling: parseTxnStatus doesn't normalize case, but parseTxnType does.

parseTxnType uses T.toUpper before matching, but parseTxnStatus only uses T.strip. If the CSV contains "success" (lowercase), it will fail.

🔧 Suggested fix for consistency
 parseTxnStatus :: Text -> Either Text TxnStatus
-parseTxnStatus t = case T.strip t of
+parseTxnStatus t = case T.toUpper (T.strip t) of
   "00" -> Right SUCCESS
   "0" -> Right SUCCESS
   "SUCCESS" -> Right SUCCESS
   "FAILED" -> Right FAILED
   other -> Left $ "Unknown transaction status: " <> other
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/mobility-core/src/Kernel/External/Settlement/YesBiz/PaymentParser.hs`
around lines 149 - 156, parseTxnStatus fails on lowercase values because it only
strips whitespace; update it to normalize case like parseTxnType does (e.g.,
apply T.toUpper to T.strip t) before pattern matching so inputs like "success"
or "failed" are handled; keep the same branches (matching "00", "0", "SUCCESS",
"FAILED") after normalization and return the same Right SUCCESS/FAILED or Left
on unknown values in the parseTxnStatus function.

Comment on lines +27 to +32
["sftp", pgStr, keyPath, userAtHost, remotePath, portStr] -> do
pg <- parsePG pgStr
testSftpFetch pg keyPath userAtHost (read portStr) remotePath Nothing
["sftp", pgStr, keyPath, userAtHost, remotePath, portStr, pattern_] -> do
pg <- parsePG pgStr
testSftpFetch pg keyPath userAtHost (read portStr) remotePath (Just pattern_)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

read portStr can throw a runtime exception on invalid input.

If the user passes a non-numeric port argument, read will throw a parse exception. Consider using readMaybe with a fallback or error message.

🛡️ Suggested fix
+import Text.Read (readMaybe)
+
+parsePort :: String -> IO Int
+parsePort s = case readMaybe s of
+  Just p -> pure p
+  Nothing -> do
+    TIO.putStrLn $ "Invalid port number: " <> T.pack s
+    exitFailure
+
 -- Then in main:
-    ["sftp", pgStr, keyPath, userAtHost, remotePath, portStr] -> do
-      pg <- parsePG pgStr
-      testSftpFetch pg keyPath userAtHost (read portStr) remotePath Nothing
+    ["sftp", pgStr, keyPath, userAtHost, remotePath, portStr] -> do
+      pg <- parsePG pgStr
+      port <- parsePort portStr
+      testSftpFetch pg keyPath userAtHost port remotePath Nothing
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test-settlement-parser/src/Main.hs` around lines 27 - 32, The code uses read
portStr in the command-line branch (inside the Main.hs match that calls parsePG
and testSftpFetch) which will throw on non-numeric input; change this to use
readMaybe (e.g., import Text.Read.readMaybe) to parse portStr safely, handle the
Nothing case by returning a clear error message or exiting gracefully, and pass
the successfully parsed Int to testSftpFetch; update both places that call
testSftpFetch (the two patterns that bind portStr) so that testSftpFetch always
receives a validated Int and invalid input is reported to the user instead of
crashing.

Comment on lines +130 to +132
let (sftpUser, sftpHost) = case break (== '@') userAtHost of
(u, '@' : h) -> (T.pack u, T.pack h)
_ -> (T.pack userAtHost, T.pack userAtHost)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Edge case: Invalid user@host format results in misleading config.

If the input lacks @, both sftpUser and sftpHost are set to the full string, which will cause SFTP to fail with a confusing error. Consider validating the format.

🛡️ Suggested fix
   let (sftpUser, sftpHost) = case break (== '@') userAtHost of
         (u, '@' : h) -> (T.pack u, T.pack h)
-        _ -> (T.pack userAtHost, T.pack userAtHost)
+        _ -> error $ "Invalid user@host format: " <> userAtHost <> ". Expected format: user@hostname"

Or better, return an IO error with a clear message.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let (sftpUser, sftpHost) = case break (== '@') userAtHost of
(u, '@' : h) -> (T.pack u, T.pack h)
_ -> (T.pack userAtHost, T.pack userAtHost)
let (sftpUser, sftpHost) = case break (== '@') userAtHost of
(u, '@' : h) -> (T.pack u, T.pack h)
_ -> error $ "Invalid user@host format: " <> userAtHost <> ". Expected format: user@hostname"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test-settlement-parser/src/Main.hs` around lines 130 - 132, The current
parsing of userAtHost sets both sftpUser and sftpHost to the full string when
there is no '@', producing misleading config; update the logic around userAtHost
(where sftpUser and sftpHost are derived) to validate the format and fail fast:
if break (== '@') userAtHost does not yield a proper (user, '@':host) pair then
produce a clear IO error (e.g., using error or fail in IO with a descriptive
message) or return an explicit validation failure instead of silently using the
same value for both sftpUser and sftpHost.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant