Skip to content

Revert "Revert "backend/feat: Stripe payout dynamic transfer top-up f…#1326

Open
karan2cmd wants to merge 1 commit into
mainfrom
backend/reverting/reverting/pr
Open

Revert "Revert "backend/feat: Stripe payout dynamic transfer top-up f…#1326
karan2cmd wants to merge 1 commit into
mainfrom
backend/reverting/reverting/pr

Conversation

@karan2cmd
Copy link
Copy Markdown
Member

@karan2cmd karan2cmd commented Jun 2, 2026

…or Fleet VA insufficient balance (#1320)""

This reverts commit ce882b3.

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
    • Payout orders now automatically compute adjusted transfer amounts when fleet balance is insufficient, with merchant top-up support to cover shortfalls.
    • Payout responses now include merchant top-up amount information when applicable.
    • Enhanced balance validation for payout operations to ensure sufficient funds before processing transfers.

…or Fleet VA insufficient balance (#1320)""

This reverts commit ce882b3.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

Walkthrough

This PR adds Stripe balance querying and merchant top-up logic to the payout flow. It introduces new Stripe balance types and API support, exports balance query helpers, extends the payout response to track top-up amounts, implements adjusted transfer computation when fleet balance is insufficient, and updates Juspay to populate the new field.

Changes

Stripe Merchant Top-Up Payout Flow

Layer / File(s) Summary
Stripe balance types and API endpoint
lib/mobility-core/src/Kernel/External/Payout/Stripe/Flow.hs
BalanceFund and BalanceResp types model Stripe balance structure; GetBalanceAPI defines the GET /v1/balance endpoint; getBalance calls Stripe and returns the balance response.
Balance query helpers in payout interface
lib/mobility-core/src/Kernel/External/Payout/Interface/Stripe.hs
getBalance decrypts the API key and delegates to the Stripe module; getAvailableForCurrency filters per-currency entries and sums their converted amounts.
Payout response schema extension
lib/mobility-core/src/Kernel/External/Payout/Interface/Types.hs
CreatePayoutOrderResp now includes merchantTopUpAmount :: Maybe HighPrecMoney field.
Stripe payout adjusted transfer computation
lib/mobility-core/src/Kernel/External/Payout/Interface.hs
createPayoutOrder Stripe branch computes adjustedTransferAmount by querying fleet and merchant balances, calculates shortfall, validates merchant sufficiency, and logs top-up; transfer request and response helpers propagate adjusted amount and top-up value; payoutOrderStatus sets top-up to Nothing.
Juspay payout response field population
lib/mobility-core/src/Kernel/External/Payout/Interface/Juspay.hs
createPayoutOrder and payoutOrderStatus populate merchantTopUpAmount = Nothing in payout responses.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • nammayatri/shared-kernel#1320: Makes the same core Stripe payout flow changes—computing adjusted transfers and wiring merchantTopUpAmount through the response.
  • nammayatri/shared-kernel#1325: Reverts/removes the merchant top-up functionality and merchantTopUpAmount field that this PR adds.
  • nammayatri/shared-kernel#1282: Related updates to Stripe payout transfer handling logic in the same Kernel.External.Payout.Interface.hs file.

Suggested reviewers

  • khuzema786
  • Vignesh-772

Poem

🐰 A balance check, a shortfall found,
Merchant top-ups come around,
Stripe balances now flow through,
Payout logic shiny new!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is incomplete and truncated, appearing to be cut off mid-phrase with an ellipsis. It describes a revert of a revert action for a Stripe payout feature, but the full context and specific change description are unclear due to the truncation. Complete the title by removing the ellipsis and providing the full feature name, or use a clear, complete description of what is being reverted back (e.g., 'Revert: Restore Stripe payout dynamic transfer top-up functionality').
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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/reverting/reverting/pr

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.

@coderabbitai coderabbitai Bot requested review from Vignesh-772 and khuzema786 June 2, 2026 12:27
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: 2

🧹 Nitpick comments (1)
lib/mobility-core/src/Kernel/External/Payout/Interface.hs (1)

47-74: 💤 Low value

Extra synchronous Stripe balance calls on every payout.

computeAdjustedTransfer always calls Stripe.getBalance for the fleet account (plus a second call for the platform account on shortfall) on the payout request path. This adds external-call latency and rate-limit pressure to every payout. Consider short-TTL caching of balances or gating the top-up logic behind a config flag if it isn't needed for all flows.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/mobility-core/src/Kernel/External/Payout/Interface.hs` around lines 47 -
74, The payout path currently calls Stripe.getBalance inside
computeAdjustedTransfer for every request (fleetBalance and potentially
merchantBalance), causing extra latency and rate-limit pressure; update
computeAdjustedTransfer to first consult a short-TTL in-memory cache (keyed by
connectedAccountId and currency) for fleetBalance and merchantBalance and only
call Stripe.getBalance when the cache is missing/stale, and also gate the top-up
check behind a config flag (eg. enableTransferTopUp) so when disabled
computeAdjustedTransfer returns (req.transferAmount, Nothing) without any Stripe
calls; reference computeAdjustedTransfer, Stripe.getBalance,
fleetBalance/fleetAvail, merchantBalance/merchantAvail and merchantTopUpAmount
when making these changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/mobility-core/src/Kernel/External/Payout/Interface.hs`:
- Around line 58-64: Replace the client-facing error that leaks merchantAvail
and adjustedAmt by logging the exact balances internally and throwing a generic
InvalidRequest message; specifically, in the branch that checks "when
(merchantAvail < adjustedAmt)" record merchantAvail and adjustedAmt to your
internal logger at an appropriate level (e.g., error/debug) and then call
"throwError $ InvalidRequest" with a non-sensitive message like "Merchant
platform account has insufficient balance" (omit numeric details).

In `@lib/mobility-core/src/Kernel/External/Payout/Interface/Stripe.hs`:
- Around line 152-155: getAvailableForCurrency is converting Stripe minor-unit
amounts with the hardcoded centsToUsd (/100), which breaks currencies like INR
that have different decimal accuracy; replace the hardcoded conversion by using
the currency's accuracy (e.g. getAccuracy or existing currency-decimal helper)
to compute scale = 10 ^ accuracy and convert each f.amount from minor units to
HighPrecMoney by dividing by that scale (instead of using centsToUsd), keeping
the currency filter on f.currency; update any helper conversions
(usdToCents/eurToCents) or replace them with a generic minorUnit <-> money
converter so fleetAvail and the transferAmount comparison use the correct scale.

---

Nitpick comments:
In `@lib/mobility-core/src/Kernel/External/Payout/Interface.hs`:
- Around line 47-74: The payout path currently calls Stripe.getBalance inside
computeAdjustedTransfer for every request (fleetBalance and potentially
merchantBalance), causing extra latency and rate-limit pressure; update
computeAdjustedTransfer to first consult a short-TTL in-memory cache (keyed by
connectedAccountId and currency) for fleetBalance and merchantBalance and only
call Stripe.getBalance when the cache is missing/stale, and also gate the top-up
check behind a config flag (eg. enableTransferTopUp) so when disabled
computeAdjustedTransfer returns (req.transferAmount, Nothing) without any Stripe
calls; reference computeAdjustedTransfer, Stripe.getBalance,
fleetBalance/fleetAvail, merchantBalance/merchantAvail and merchantTopUpAmount
when making these changes.
🪄 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: 2662eb71-b341-4695-83d8-22d4b60fa664

📥 Commits

Reviewing files that changed from the base of the PR and between ce882b3 and ae9eed3.

📒 Files selected for processing (5)
  • lib/mobility-core/src/Kernel/External/Payout/Interface.hs
  • lib/mobility-core/src/Kernel/External/Payout/Interface/Juspay.hs
  • lib/mobility-core/src/Kernel/External/Payout/Interface/Stripe.hs
  • lib/mobility-core/src/Kernel/External/Payout/Interface/Types.hs
  • lib/mobility-core/src/Kernel/External/Payout/Stripe/Flow.hs

Comment on lines +58 to +64
when (merchantAvail < adjustedAmt) $
throwError $
InvalidRequest $
"Merchant platform account has insufficient balance. Available: "
<> show merchantAvail
<> ", Required: "
<> show adjustedAmt
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 | 🟠 Major | ⚡ Quick win

Don't leak the merchant platform balance in a client-facing error.

InvalidRequest is surfaced to the API caller. Embedding the platform/merchant account's available balance (and exact required amount) in the message discloses internal financial state to whoever triggers the payout. Log the details internally and return a generic message.

🔒 Proposed fix
-              when (merchantAvail < adjustedAmt) $
-                throwError $
-                  InvalidRequest $
-                    "Merchant platform account has insufficient balance. Available: "
-                      <> show merchantAvail
-                      <> ", Required: "
-                      <> show adjustedAmt
+              when (merchantAvail < adjustedAmt) $ do
+                logError $
+                  "Merchant platform account has insufficient balance. Available: "
+                    <> show merchantAvail
+                    <> ", Required: "
+                    <> show adjustedAmt
+                throwError $
+                  InvalidRequest "Insufficient merchant platform account balance to complete payout."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/mobility-core/src/Kernel/External/Payout/Interface.hs` around lines 58 -
64, Replace the client-facing error that leaks merchantAvail and adjustedAmt by
logging the exact balances internally and throwing a generic InvalidRequest
message; specifically, in the branch that checks "when (merchantAvail <
adjustedAmt)" record merchantAvail and adjustedAmt to your internal logger at an
appropriate level (e.g., error/debug) and then call "throwError $
InvalidRequest" with a non-sensitive message like "Merchant platform account has
insufficient balance" (omit numeric details).

Comment on lines +152 to +155
getAvailableForCurrency :: Currency -> Stripe.BalanceResp -> HighPrecMoney
getAvailableForCurrency currency Stripe.BalanceResp {available} =
let currencyText = T.toLower $ show currency
in sum [centsToUsd f.amount | f <- available, f.currency == currencyText]
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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the cents/major-unit converters to confirm scale is currency-agnostic.
ast-grep --pattern 'centsToUsd $_ = $$$'
ast-grep --pattern 'eurToCents $_ = $$$'
ast-grep --pattern 'usdToCents $_ = $$$'
rg -nP --type=haskell -C2 '\b(centsToUsd|eurToCents|usdToCents)\b'

Repository: nammayatri/shared-kernel

Length of output: 12663


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the Interface.hs referenced by the review comment
fd -e hs -a Interface.hs lib/mobility-core/src | head -n 50

# Search for the specific comparison expression
rg -n --type=haskell "fleetAvail\s*\+\s*req\.transferAmount\s*>?=\s*req\.amount" lib/mobility-core/src || true

# If not found, search for fleetAvail usage near transferAmount/amount
rg -n --type=haskell "fleetAvail" lib/mobility-core/src | head -n 50

Repository: nammayatri/shared-kernel

Length of output: 2691


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="lib/mobility-core/src/Kernel/External/Payout/Interface.hs"

# Show the relevant block around the comparison
nl -ba "$FILE" | sed -n '1,140p'

# Find the request record type that contains transferAmount/amount
rg -n --type=haskell "transferAmount" lib/mobility-core/src/Kernel/External/Payout/Interface.hs
rg -n --type=haskell "data .*Transfer.*|data .*Payout.*|record.*Transfer|req\.transferAmount" lib/mobility-core/src/Kernel/External/Payout/Interface.hs

# Broader search for where req.amount / transferAmount are built for payout
rg -n --type=haskell "transferAmount\s*=\s*" lib/mobility-core/src/Kernel/External/Payout lib/mobility-core/src/Kernel/External -S
rg -n --type=haskell "req\.amount" lib/mobility-core/src/Kernel/External/Payout -S

Repository: nammayatri/shared-kernel

Length of output: 111


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="lib/mobility-core/src/Kernel/External/Payout/Interface.hs"

# Line-numbered view without nl
cat -n "$FILE" | sed -n '1,120p'

# Find request/type definitions and uses
rg -n --type=haskell "transferAmount" "$FILE"
rg -n --type=haskell "req\.amount|req\.transferAmount|amount\s*=|transferAmount\s*=" lib/mobility-core/src/Kernel/External/Payout -S
rg -n --type=haskell "data .*Payout|data .*Transfer|Create.*Payout|Transfer.*Req|Payout.*Req|record .*Payout" lib/mobility-core/src/Kernel/External/Payout -S

Repository: nammayatri/shared-kernel

Length of output: 25372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect payout request types
rg -n --type=haskell "data CreatePayoutOrderReq" lib/mobility-core/src/Kernel/External/Payout/Interface/Types.hs
cat -n lib/mobility-core/src/Kernel/External/Payout/Interface/Types.hs | sed -n '1,220p'

# Inspect Stripe payout conversion of req.amount/transferAmount to Stripe minor units
cat -n lib/mobility-core/src/Kernel/External/Payout/Interface/Stripe.hs | sed -n '1,220p'

# Find Currency definition and any decimal/minor-unit scaling logic
rg -n --type=haskell "data Currency|newtype Currency|type Currency" lib/mobility-core/src/Kernel | head -n 50
rg -n --type=haskell "Currency" lib/mobility-core/src/Kernel | rg -n "decim|minor|zero|scale|fraction" | head -n 50 || true
rg -n --type=haskell "zero[-_ ]?decimal|minor unit|decimal" lib/mobility-core/src/Kernel | head -n 50 || true

Repository: nammayatri/shared-kernel

Length of output: 19012


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n --type=haskell "HighPrecMoney" lib/mobility-core/src/Kernel/Types/Price.hs lib/mobility-core/src/Kernel | head -n 50
rg -n --type=haskell "newtype HighPrecMoney|type HighPrecMoney" lib/mobility-core/src/Kernel | head -n 50
rg -n --type=haskell "HighPrecMoney.*cents|cents.*HighPrecMoney|minor.*HighPrecMoney" lib/mobility-core/src/Kernel | head -n 50

Repository: nammayatri/shared-kernel

Length of output: 6407


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="lib/mobility-core/src/Kernel/Types/Price.hs"

# show around HighPrecMoney definition
sed -n '1,140p' "$FILE" | cat -n | sed -n '40,110p'
# show rest where cents/minor may be defined
sed -n '140,260p' "$FILE" | cat -n | head -n 160

Repository: nammayatri/shared-kernel

Length of output: 8353


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n --type=haskell "getAvailableForCurrency|centsToUsd|usdToCents|eurToCents" lib/mobility-core/src/Kernel/External/Payout/Interface/Stripe.hs lib/mobility-core/src/Kernel/External/Payout/Interface.hs lib/mobility-core/src/Kernel/External/Payment/Interface/Stripe.hs
rg -n --type=haskell "getAccuracy\s+INR|data Currency\s*=\s*INR\s*\|" lib/mobility-core/src/Kernel/Types/Price.hs

Repository: nammayatri/shared-kernel

Length of output: 4628


Fix currency minor-unit conversion in getAvailableForCurrency (INR is broken by hardcoded /100).

getAvailableForCurrency filters by f.currency == currencyText, but still converts every matching Stripe amount with centsToUsd:

getAvailableForCurrency :: Currency -> Stripe.BalanceResp -> HighPrecMoney
getAvailableForCurrency currency Stripe.BalanceResp {available} =
  let currencyText = T.toLower $ show currency
   in sum [centsToUsd f.amount | f <- available, f.currency == currencyText]

centsToUsd is hardcoded to / 100, and both usdToCents/eurToCents are hardcoded to * 100—so for Currency = INR (0-decimal in getAccuracy INR = 0), fleetAvail (and the top-up logic guarded by fleetAvail + req.transferAmount >= req.amount in Kernel/External/Payout/Interface.hs) will use the wrong scale.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/mobility-core/src/Kernel/External/Payout/Interface/Stripe.hs` around
lines 152 - 155, getAvailableForCurrency is converting Stripe minor-unit amounts
with the hardcoded centsToUsd (/100), which breaks currencies like INR that have
different decimal accuracy; replace the hardcoded conversion by using the
currency's accuracy (e.g. getAccuracy or existing currency-decimal helper) to
compute scale = 10 ^ accuracy and convert each f.amount from minor units to
HighPrecMoney by dividing by that scale (instead of using centsToUsd), keeping
the currency filter on f.currency; update any helper conversions
(usdToCents/eurToCents) or replace them with a generic minorUnit <-> money
converter so fleetAvail and the transferAmount comparison use the correct scale.

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.

2 participants