Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c700d7a
feat(feature:send-money): add UPI QR code processor
biplab1 Aug 5, 2025
1aa831d
feat(feature:send-money): add screen for payment options
biplab1 Aug 13, 2025
3a25f79
feat(feature:send-money): fit names in one line
biplab1 Aug 14, 2025
bb02992
feat(feature:send-money): implement upi and non upi scanner navigation
biplab1 Aug 14, 2025
7143825
fix(feature:send-money): fetch UPI data in payee details screen
biplab1 Aug 15, 2025
6959a91
refactor(send-money): enhance payment details UI/UX
biplab1 Aug 15, 2025
60e97ac
feat(feature:send-money): add account selection, replace hardcoded st…
biplab1 Aug 21, 2025
ce7a94b
feat(feature:send-money): add payment processing screen
biplab1 Aug 21, 2025
cfd4db5
feat(feature:send-money): add payment success screen
biplab1 Aug 21, 2025
c4e3100
feat(feature:send-money): implement upi pin screen
biplab1 Aug 28, 2025
32f4603
feat(feature:send-money): add text in upi pin screen
biplab1 Sep 2, 2025
2b2d958
feat(feature:send-money): add text in upi pin screen
biplab1 Sep 2, 2025
d0bfbb4
feat(feature:send-money): implement share screenshot
biplab1 Sep 2, 2025
7e0cafa
feat(send-money): add user payment chat history
biplab1 Sep 2, 2025
fe2983f
feat(feature:send-money): implement payment details screen
biplab1 Sep 3, 2025
d39688a
feat(feature:send-money): implement screen shot in payment details sc…
biplab1 Sep 3, 2025
a792092
refactor(feature:send-money): improve ui ux in payment chat history a…
biplab1 Sep 4, 2025
2ad303b
feat(feature:send-money): implement transaction history section
biplab1 Sep 4, 2025
b72d6a1
feat(feature:send-money): implement transaction history screen with s…
biplab1 Sep 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,29 @@ import org.mifospay.feature.savedcards.createOrUpdate.addEditCardScreen
import org.mifospay.feature.savedcards.createOrUpdate.navigateToCardAddEdit
import org.mifospay.feature.savedcards.details.cardDetailRoute
import org.mifospay.feature.savedcards.details.navigateToCardDetails
import org.mifospay.feature.send.money.AmountUtils
import org.mifospay.feature.send.money.SendMoneyScreen
import org.mifospay.feature.send.money.navigation.PAYMENT_SUCCESS_ROUTE
import org.mifospay.feature.send.money.navigation.SEND_MONEY_BASE_ROUTE
import org.mifospay.feature.send.money.navigation.SEND_MONEY_OPTIONS_ROUTE
import org.mifospay.feature.send.money.navigation.navigateToPayeeDetailsScreen
import org.mifospay.feature.send.money.navigation.navigateToPaymentChatHistoryScreen
import org.mifospay.feature.send.money.navigation.navigateToPaymentDetailsScreen
import org.mifospay.feature.send.money.navigation.navigateToPaymentProcessingScreen
import org.mifospay.feature.send.money.navigation.navigateToPaymentSuccessScreen
import org.mifospay.feature.send.money.navigation.navigateToSendMoneyOptionsScreen
import org.mifospay.feature.send.money.navigation.navigateToSendMoneyScreen
import org.mifospay.feature.send.money.navigation.navigateToUpiPinScreen
import org.mifospay.feature.send.money.navigation.navigateToUpiTransactionHistoryScreen
import org.mifospay.feature.send.money.navigation.payeeDetailsScreen
import org.mifospay.feature.send.money.navigation.paymentChatHistoryScreen
import org.mifospay.feature.send.money.navigation.paymentDetailsScreen
import org.mifospay.feature.send.money.navigation.paymentProcessingScreen
import org.mifospay.feature.send.money.navigation.paymentSuccessScreen
import org.mifospay.feature.send.money.navigation.sendMoneyOptionsScreen
import org.mifospay.feature.send.money.navigation.sendMoneyScreen
import org.mifospay.feature.send.money.navigation.upiPinScreen
import org.mifospay.feature.send.money.navigation.upiTransactionHistoryScreen
import org.mifospay.feature.settings.navigation.settingsScreen
import org.mifospay.feature.standing.instruction.StandingInstructionsScreen
import org.mifospay.feature.standing.instruction.createOrUpdate.addEditSIScreen
Expand All @@ -98,6 +117,7 @@ internal fun MifosNavHost(
onBackClick = navController::navigateUp,
navigateToTransferScreen = navController::navigateToTransferScreen,
navigateToScanQrScreen = navController::navigateToScanQr,
navigateToPayeeDetails = navController::navigateToPayeeDetailsScreen,
showTopBar = false,
)
},
Expand Down Expand Up @@ -162,7 +182,7 @@ internal fun MifosNavHost(
onRequest = {
navController.navigateToShowQrScreen()
},
onPay = navController::navigateToSendMoneyScreen,
onPay = navController::navigateToSendMoneyOptionsScreen,
navigateToTransactionDetail = navController::navigateToSpecificTransaction,
navigateToAccountDetail = navController::navigateToSavingAccountDetails,
navigateToHistory = navController::navigateToHistory,
Expand Down Expand Up @@ -283,12 +303,143 @@ internal fun MifosNavHost(
navigateBack = navController::navigateUp,
)

sendMoneyOptionsScreen(
onBackClick = navController::popBackStack,
onScanQrClick = {
// This is now handled by the ViewModel using ML Kit scanner
},
onPayAnyoneClick = {
// TODO: Navigate to Pay Anyone screen
},
onBankTransferClick = {
// TODO: Navigate to Bank Transfer screen
},
onFineractPaymentsClick = {
navController.navigateToSendMoneyScreen()
},
onQrCodeScanned = { qrData ->
navController.navigateToSendMoneyScreen(
requestData = qrData,
navOptions = navOptions {
popUpTo(SEND_MONEY_OPTIONS_ROUTE) {
inclusive = true
}
},
)
},
onNavigateToPayeeDetails = { qrCodeData ->
navController.navigateToPayeeDetailsScreen(qrCodeData)
},
onPaymentHistoryClick = {
navController.navigateToPaymentChatHistoryScreen()
},
onUpiTransactionHistoryClick = {
navController.navigateToUpiTransactionHistoryScreen()
},
)

sendMoneyScreen(
onBackClick = navController::popBackStack,
navigateToTransferScreen = navController::navigateToTransferScreen,
navigateToPayeeDetailsScreen = navController::navigateToPayeeDetailsScreen,
navigateToScanQrScreen = navController::navigateToScanQr,
)

paymentChatHistoryScreen(
onBackClick = navController::popBackStack,
onPaymentClick = {
navController.navigateToSendMoneyOptionsScreen()
},
onTransactionClick = { transactionId ->
navController.navigateToPaymentDetailsScreen(transactionId)
},
)

upiTransactionHistoryScreen(
onBackClick = navController::popBackStack,
)

paymentDetailsScreen(
onBackClick = navController::popBackStack,
onPayAgainClick = {
navController.navigateToSendMoneyOptionsScreen()
},
onRetryClick = {
navController.popBackStack()
},
onShareScreenshot = {
// Screenshot functionality is handled by the Android-specific PaymentDetailsScreen implementation
// The actual screenshot and sharing is done within the screen itself
// This callback is used by the Android-specific implementation to trigger the screenshot
},
)

payeeDetailsScreen(
onBackClick = navController::popBackStack,
onNavigateToUpiPin = { state ->
navController.navigateToUpiPinScreen(
payeeName = state.payeeName,
amount = state.amount,
isUpiCode = state.isUpiCode,
bankName = state.selectedAccount?.bankName ?: "Bank",
accountNo = state.selectedAccount?.accountNumber ?: "1234567890123456",
refId = state.refId,
)
},
)

upiPinScreen(
onBackClick = navController::popBackStack,
onNavigateToPaymentProcessing = { payeeName, amount, isUpiCode ->
val amountInPaise = AmountUtils.rupeesToPaise(amount)
navController.navigateToPaymentProcessingScreen(
payeeName = payeeName,
amount = amountInPaise,
isUpiCode = isUpiCode,
)
},
)

paymentProcessingScreen(
onPaymentComplete = { payeeName, amount, upiName, transactionTimestamp ->
navController.navigateToPaymentSuccessScreen(
payeeName = payeeName,
amount = amount,
upiName = upiName,
transactionTimestamp = transactionTimestamp,
)
},
onPaymentFailed = { errorMessage ->
navController.popBackStack()
},
)

paymentSuccessScreen(
onShareScreenshot = {
// Screenshot functionality is handled by the Android-specific PaymentSuccessScreen implementation
// The actual screenshot and sharing is done within the screen itself
// This callback is used by the Android-specific implementation to trigger the screenshot
},
onDone = {
navController.navigate(HOME_ROUTE) {
popUpTo(HOME_ROUTE) {
inclusive = false
}
launchSingleTop = true
}
},
onNavigateToSendMoneyOptions = {
navController.navigateToSendMoneyOptionsScreen(
navOptions {
popUpTo(PAYMENT_SUCCESS_ROUTE) {
inclusive = true
}
launchSingleTop = true
},
)
},
)

transferScreen(
navigateBack = navController::popBackStack,
onTransferSuccess = {
Expand Down Expand Up @@ -326,6 +477,16 @@ internal fun MifosNavHost(
},
)
},
navigateToPayeeDetailsScreen = {
navController.navigateToPayeeDetailsScreen(
qrCodeData = it,
navOptions = navOptions {
popUpTo(SCAN_QR_ROUTE) {
inclusive = true
}
},
)
},
)

merchantTransferScreen(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.data.util

import org.mifospay.core.model.utils.PaymentQrData
import org.mifospay.core.model.utils.StandardUpiQrData

/**
* Standard UPI QR Code Processor
* Handles parsing of standard UPI QR codes according to UPI specification
*/
object StandardUpiQrCodeProcessor {

/**
* Checks if the given string is a valid UPI QR code
* @param qrData The QR code data string
* @return true if it's a valid UPI QR code, false otherwise
*/
fun isValidUpiQrCode(qrData: String): Boolean {
return qrData.startsWith("upi://") || qrData.startsWith("UPI://")
}

/**
* Parses a standard UPI QR code string
* @param qrData The QR code data string
* @return StandardUpiQrData object with parsed information
* @throws IllegalArgumentException if the QR code is invalid
*/
fun parseUpiQrCode(qrData: String): StandardUpiQrData {
if (!isValidUpiQrCode(qrData)) {
throw IllegalArgumentException("Invalid UPI QR code format")
}

val paramsString = qrData.substringAfter("upi://").substringAfter("UPI://")
val parts = paramsString.split("?", limit = 2)
val params = if (parts.size > 1) parseParams(parts[1]) else emptyMap()

val payeeVpa = params["pa"] ?: run {
throw IllegalArgumentException("Missing payee VPA (pa) in UPI QR code")
}
val payeeName = params["pn"] ?: "Unknown"

val vpaParts = payeeVpa.split("@", limit = 2)
val actualVpa = if (vpaParts.size == 2) payeeVpa else payeeVpa

return StandardUpiQrData(
payeeName = payeeName,
payeeVpa = actualVpa,
amount = params["am"] ?: "",
currency = params["cu"] ?: StandardUpiQrData.DEFAULT_CURRENCY,
transactionNote = params["tn"] ?: "",
merchantCode = params["mc"] ?: "",
transactionReference = params["tr"] ?: "",
url = params["url"] ?: "",
mode = params["mode"] ?: "02",
)
}

/**
* Parses URL parameters into a map
* @param paramsString The parameters string
* @return Map of parameter keys and values
*/
private fun parseParams(paramsString: String): Map<String, String> {
return paramsString
.split("&")
.associate { param ->
val keyValue = param.split("=", limit = 2)
if (keyValue.size == 2) {
keyValue[0] to keyValue[1]
} else {
param to ""
}
}
}

/**
* Converts StandardUpiQrData to PaymentQrData for compatibility with existing code
* @param standardData Standard UPI QR data
* @return PaymentQrData object
* Note: clientId and accountId not available in standard UPI
*/
fun toPaymentQrData(standardData: StandardUpiQrData): PaymentQrData {
return PaymentQrData(
clientId = 0,
clientName = standardData.payeeName,
accountNo = standardData.payeeVpa,
amount = standardData.amount,
accountId = 0,
currency = standardData.currency,
officeId = 1,
accountTypeId = 2,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ package org.mifospay.core.designsystem.icon

import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.ArrowForward
import androidx.compose.material.icons.automirrored.filled.Send
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
import androidx.compose.material.icons.filled.ArrowOutward
import androidx.compose.material.icons.filled.AttachMoney
Expand All @@ -22,20 +24,25 @@ import androidx.compose.material.icons.filled.ChevronLeft
import androidx.compose.material.icons.filled.ChevronRight
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.material.icons.filled.CurrencyRupee
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Description
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.ExpandLess
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material.icons.filled.FlashOff
import androidx.compose.material.icons.filled.FlashOn
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Photo
import androidx.compose.material.icons.filled.PhotoLibrary
import androidx.compose.material.icons.filled.QrCode
import androidx.compose.material.icons.filled.QrCode2
import androidx.compose.material.icons.filled.RadioButtonChecked
import androidx.compose.material.icons.filled.RadioButtonUnchecked
import androidx.compose.material.icons.filled.Send
import androidx.compose.material.icons.filled.Share
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
Expand All @@ -57,6 +64,8 @@ import androidx.compose.material.icons.outlined.Wallet
import androidx.compose.material.icons.rounded.AccountBalance
import androidx.compose.material.icons.rounded.AccountCircle
import androidx.compose.material.icons.rounded.Add
import androidx.compose.material.icons.rounded.Check
import androidx.compose.material.icons.rounded.CheckCircle
import androidx.compose.material.icons.rounded.Contacts
import androidx.compose.material.icons.rounded.Home
import androidx.compose.material.icons.rounded.Info
Expand Down Expand Up @@ -85,6 +94,9 @@ object MifosIcons {
val Visibility: ImageVector = Icons.Filled.Visibility
val Check: ImageVector = Icons.Default.Check
val KeyboardArrowDown: ImageVector = Icons.Default.KeyboardArrowDown
val KeyboardArrowUp: ImageVector = Icons.Default.KeyboardArrowUp
val DropDown: ImageVector = Icons.Default.ExpandMore
val DropUp: ImageVector = Icons.Default.ExpandLess
val Home = Icons.Outlined.Home
val HomeBoarder = Icons.Rounded.Home
val Payment = Icons.Rounded.SwapHoriz
Expand Down Expand Up @@ -129,4 +141,12 @@ object MifosIcons {
val Scan = Icons.Outlined.QrCodeScanner
val RadioButtonUnchecked = Icons.Default.RadioButtonUnchecked
val RadioButtonChecked = Icons.Filled.RadioButtonChecked

val ArrowForward = Icons.AutoMirrored.Filled.ArrowForward

val CurrencyRupee = Icons.Filled.CurrencyRupee
val CheckCircle = Icons.Rounded.CheckCircle
val CheckRounded = Icons.Rounded.Check

val Send = Icons.AutoMirrored.Filled.Send
}
Loading
Loading