Skip to content

Commit

Permalink
Added Support of Bank and PayEasy (#53)
Browse files Browse the repository at this point in the history
  • Loading branch information
AmniX authored Oct 21, 2024
1 parent f4ea74d commit f55d386
Show file tree
Hide file tree
Showing 23 changed files with 564 additions and 45 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ ktlint_standard = disabled

[*.{java,kt,kts,xml}]
indent_size = 4
max_line_length = 180
max_line_length = 140

[**/test/**]
ktlint_standard_class-naming = disabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ internal class PaymentResultScreenModel : ScreenModel {
}

@Composable
internal fun Navigator.paymentResultScreenModel() = rememberNavigatorScreenModel(PaymentResultScreenModel::class.simpleName) { PaymentResultScreenModel() }
internal fun Navigator.paymentResultScreenModel() = rememberNavigatorScreenModel(PaymentResultScreenModel::class.simpleName) {
PaymentResultScreenModel()
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

@Composable
internal fun InlinedPaymentPrimaryButton(text: String, state: InlinedPaymentPrimaryButtonState, modifier: Modifier = Modifier, onClick: () -> Unit) {
internal fun InlinedPaymentPrimaryButton(
text: String,
state: InlinedPaymentPrimaryButtonState,
modifier: Modifier = Modifier,
onClick: () -> Unit,
) {
val configurableTheme = LocalConfigurableTheme.current
Button(
modifier = modifier,
Expand All @@ -54,10 +59,27 @@ internal fun InlinedPaymentPrimaryButton(text: String, state: InlinedPaymentPrim
contentAlignment = Alignment.Center,
) {
when (state) {
InlinedPaymentPrimaryButtonState.LOADING -> CircularProgressIndicator(strokeWidth = 2.dp, color = LocalContentColor.current, modifier = Modifier.size(24.dp))
InlinedPaymentPrimaryButtonState.IDLE -> Text(modifier = Modifier.padding(8.dp), text = text, style = TextStyle(fontWeight = FontWeight.Bold), maxLines = 1)
InlinedPaymentPrimaryButtonState.SUCCESS -> Icon(Icons.Rounded.CheckCircle, contentDescription = null, modifier = Modifier.size(24.dp))
InlinedPaymentPrimaryButtonState.ERROR -> Icon(Icons.Rounded.Close, contentDescription = null, modifier = Modifier.size(24.dp))
InlinedPaymentPrimaryButtonState.LOADING -> CircularProgressIndicator(
strokeWidth = 2.dp,
color = LocalContentColor.current,
modifier = Modifier.size(24.dp),
)
InlinedPaymentPrimaryButtonState.IDLE -> Text(
modifier = Modifier.padding(8.dp),
text = text,
style = TextStyle(fontWeight = FontWeight.Bold),
maxLines = 1,
)
InlinedPaymentPrimaryButtonState.SUCCESS -> Icon(
Icons.Rounded.CheckCircle,
contentDescription = null,
modifier = Modifier.size(24.dp),
)
InlinedPaymentPrimaryButtonState.ERROR -> Icon(
Icons.Rounded.Close,
contentDescription = null,
modifier = Modifier.size(24.dp),
)
}
}
}
Expand All @@ -71,8 +93,9 @@ enum class InlinedPaymentPrimaryButtonState {
}

@Composable
fun rememberInlinedPaymentPrimaryButtonState(default: InlinedPaymentPrimaryButtonState = InlinedPaymentPrimaryButtonState.IDLE): MutableState<InlinedPaymentPrimaryButtonState> =
rememberSaveable { mutableStateOf(default) }
fun rememberInlinedPaymentPrimaryButtonState(
default: InlinedPaymentPrimaryButtonState = InlinedPaymentPrimaryButtonState.IDLE,
): MutableState<InlinedPaymentPrimaryButtonState> = rememberSaveable { mutableStateOf(default) }

@Composable
@Preview(showBackground = true, showSystemUi = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,13 @@ import com.komoju.android.sdk.R

@SuppressLint("SetJavaScriptEnabled")
@Composable
internal fun InlinedWebView(modifier: Modifier, url: String, onDone: (String) -> Unit, onChallengePresented: () -> Unit, onCloseButtonClicked: () -> Unit) {
internal fun InlinedWebView(
modifier: Modifier,
url: String,
onDone: (String) -> Unit,
onChallengePresented: () -> Unit,
onCloseButtonClicked: () -> Unit,
) {
val state = rememberWebViewState(url)
Column(modifier = modifier) {
Row(
Expand Down Expand Up @@ -81,7 +87,8 @@ internal fun InlinedWebView(modifier: Modifier, url: String, onDone: (String) ->
}
}

private class InlinedWebViewClient(private val onDeeplinkCaptured: (String) -> Unit, private val onChallengePresented: () -> Unit) : AccompanistWebViewClient() {
private class InlinedWebViewClient(private val onDeeplinkCaptured: (String) -> Unit, private val onChallengePresented: () -> Unit) :
AccompanistWebViewClient() {
@Deprecated("Deprecated in Java")
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean = view.checkAndOpen(url)
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean = view.checkAndOpen(request.url.toString())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ internal data class KonbiniAwaitingPaymentScreen(val route: KomojuPaymentRoute.K
val uiState by screenModel.state.collectAsStateWithLifecycle()
RouterEffect(screenModel.router.collectAsStateWithLifecycle(), screenModel::onRouteConsumed)
uiState.payment?.let {
PaymentStatus(it, onPrimaryButtonClicked = screenModel::onPrimaryButtonClicked, onSecondaryButtonClicked = screenModel::onSecondaryButtonClicked)
PaymentStatus(
payment = it,
onPrimaryButtonClicked = screenModel::onPrimaryButtonClicked,
onSecondaryButtonClicked = screenModel::onSecondaryButtonClicked,
)
}
if (uiState.isLoading) {
Box(
Expand Down Expand Up @@ -88,7 +92,12 @@ private fun PaymentStatus(payment: Payment, onPrimaryButtonClicked: () -> Unit,
Spacer(modifier = Modifier.padding(16.dp))
Text(payment.title, fontSize = 32.sp, style = TextStyle(fontWeight = FontWeight.Medium))
Spacer(modifier = Modifier.padding(8.dp))
Text(payment.description, fontSize = 16.sp, color = Gray700, style = TextStyle(fontWeight = FontWeight.Normal, textAlign = TextAlign.Center))
Text(
text = payment.description,
fontSize = 16.sp,
color = Gray700,
style = TextStyle(fontWeight = FontWeight.Normal, textAlign = TextAlign.Center),
)
Box(
modifier = Modifier
.weight(1f)
Expand Down Expand Up @@ -179,7 +188,9 @@ private val Payment.description
get() = when {
status == PaymentStatus.COMPLETED -> "Your payment has been processed successfully."
status == PaymentStatus.FAILED -> "Your payment has failed."
this is Payment.Konbini && status == PaymentStatus.AUTHORIZED -> "You need to go to your local ${this.konbiniStoreKey} and make the payment to proceed."
this is Payment.Konbini && status == PaymentStatus.AUTHORIZED ->
"You need to go to your local ${this.konbiniStoreKey}" +
" and make the payment to proceed."
else -> "Your payment is awaiting processing."
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ private suspend fun KomojuRemoteApi.payByToken(
}
}

private suspend fun KomojuRemoteApi.processBySession(sessionId: String, onSuccess: suspend (PaymentStatus) -> Unit, onError: (Reason) -> Unit) {
private suspend fun KomojuRemoteApi.processBySession(
sessionId: String,
onSuccess: suspend (PaymentStatus) -> Unit,
onError: (Reason) -> Unit,
) {
sessions.verifyPaymentBySessionID(sessionId).onSuccess { paymentDetails ->
onSuccess(paymentDetails.status)
}.onFailure {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.komoju.android.sdk.utils.CreditCardUtils.isValidCardHolderNameChar
import com.komoju.android.sdk.utils.CreditCardUtils.isValidCardNumber
import com.komoju.android.sdk.utils.CreditCardUtils.isValidExpiryDate
import com.komoju.android.sdk.utils.DeeplinkEntity
import com.komoju.android.sdk.utils.isKatakanaOnly
import com.komoju.android.sdk.utils.isValidEmail
import com.komoju.mobile.sdk.entities.Payment
import com.komoju.mobile.sdk.entities.PaymentMethod
Expand All @@ -33,7 +34,8 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch

internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configuration) : RouterStateScreenModel<KomojuPaymentUIState>(KomojuPaymentUIState()) {
internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configuration) :
RouterStateScreenModel<KomojuPaymentUIState>(KomojuPaymentUIState()) {
private val komojuApi: KomojuRemoteApi = KomojuRemoteApi(config.publishableKey, config.language.languageCode)
private val _offSitePaymentURL = MutableStateFlow<String?>(null)
val offSitePaymentURL = _offSitePaymentURL.asStateFlow()
Expand Down Expand Up @@ -141,7 +143,11 @@ internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configurat
mutableRouter.value = Router.ReplaceAll(
KomojuPaymentRoute.ProcessPayment(
configuration = config,
processType = KomojuPaymentRoute.ProcessPayment.ProcessType.PayByToken(tokens.id, request.amount, request.currency),
processType = KomojuPaymentRoute.ProcessPayment.ProcessType.PayByToken(
tokens.id,
request.amount,
request.currency,
),
),
)
}
Expand All @@ -151,9 +157,11 @@ internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configurat
if (config.inlinedProcessing) {
mutableState.update { it.copy(inlinedCreditCardProcessingURL = tokens.authURL) }
} else {
mutableRouter.value = Router.ReplaceAll(KomojuPaymentRoute.WebView(url = tokens.authURL, isJavaScriptEnabled = true))
mutableRouter.value =
Router.ReplaceAll(KomojuPaymentRoute.WebView(url = tokens.authURL, isJavaScriptEnabled = true))
}
}

ERRORED, UNKNOWN -> mutableRouter.value = Router.ReplaceAll(KomojuPaymentRoute.PaymentFailed(Reason.CREDIT_CARD_ERROR))
}
}.onFailure {
Expand Down Expand Up @@ -205,7 +213,11 @@ internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configurat
when (this) {
is Payment.Konbini -> mutableRouter.value = Router.Replace(KomojuPaymentRoute.KonbiniAwaitingPayment(config, payment = this))
is Payment.OffSitePayment -> _offSitePaymentURL.value = redirectURL
is Payment.Completed -> mutableRouter.value = Router.SetPaymentResultAndPop(KomojuSDK.PaymentResult(isSuccessFul = status == PaymentStatus.CAPTURED))
is Payment.Completed ->
mutableRouter.value =
Router.SetPaymentResultAndPop(KomojuSDK.PaymentResult(isSuccessFul = status == PaymentStatus.CAPTURED))
is Payment.BankTransfer -> mutableRouter.value = Router.ReplaceAll(KomojuPaymentRoute.WebView(url = instructionURL))
is Payment.PayEasy -> mutableRouter.value = Router.ReplaceAll(KomojuPaymentRoute.WebView(url = instructionURL))
else -> Unit
}
}
Expand All @@ -217,10 +229,54 @@ internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configurat
is PaymentMethod.NetCash -> state.value.netCashDisplayData.validate()
is PaymentMethod.BitCash -> state.value.bitCashDisplayData.validate()
is PaymentMethod.WebMoney -> state.value.webMoneyDisplayData.validate()
is PaymentMethod.BankTransfer,
is PaymentMethod.PayEasy,
-> state.value.commonDisplayData.validate()

is PaymentMethod.OffSitePayment -> true // No input required for Offsite payment
else -> false
}

private fun CommonDisplayData.validate(): Boolean {
val lastNameError = if (lastName.isBlank()) "The entered last name cannot be empty" else null
val firstNameError = if (firstName.isBlank()) "The entered first name cannot be empty" else null
val firstNamePhoneticError = when {
firstNamePhonetic.isBlank() -> "The entered first name phonetic cannot be empty"
firstNamePhonetic.isKatakanaOnly.not() -> "The entered first name phonetic must be a kana"
else -> null
}
val lastNamePhoneticError = when {
lastNamePhonetic.isBlank() -> "The entered last name phonetic cannot be empty"
lastNamePhonetic.isKatakanaOnly.not() -> "The entered last name phonetic must be a kana"
else -> null
}
val emailError = if (email.isValidEmail.not()) "The entered email is not valid" else null
val phoneNumberError = when {
phoneNumber.isBlank() -> "The entered phone number cannot be empty"
phoneNumber.length < 7 -> "The entered phone number is not valid"
phoneNumber.isDigitsOnly().not() -> "The entered phone number is not valid"
else -> null
}
mutableState.update {
it.copy(
commonDisplayData = it.commonDisplayData.copy(
lastNameError = lastNameError,
firstNameError = firstNameError,
firstNamePhoneticError = firstNamePhoneticError,
lastNamePhoneticError = lastNamePhoneticError,
emailError = emailError,
phoneNumberError = phoneNumberError,
),
)
}
return lastNameError == null &&
firstNameError == null &&
firstNamePhoneticError == null &&
lastNamePhoneticError == null &&
emailError == null &&
phoneNumberError == null
}

private fun WebMoneyDisplayData.validate(): Boolean {
val prepaidNumberError = when {
prepaidNumber.isBlank() -> "The entered prepaid number cannot be empty"
Expand Down Expand Up @@ -252,6 +308,7 @@ internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configurat
}
return idError == null
}

private fun NetCashDisplayData.validate(): Boolean {
val idError = when {
netCashId.isBlank() -> "The entered net cash id cannot be empty"
Expand Down Expand Up @@ -347,7 +404,7 @@ internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configurat
paymentMethod = this,
bitCashId = state.value.bitCashDisplayData.bitCashId,
)
is PaymentMethod.CreditCard -> error("Credit Card needs to generate tokens first!")

is PaymentMethod.Konbini -> PaymentRequest.Konbini(
paymentMethod = this,
konbiniBrand = state.value.konbiniDisplayData.selectedKonbiniBrand!!,
Expand All @@ -358,19 +415,40 @@ internal class KomojuPaymentScreenModel(private val config: KomojuSDK.Configurat
paymentMethod = this,
netCashId = state.value.netCashDisplayData.netCashId,
)
is PaymentMethod.Other -> error("payment method is not supported!")

is PaymentMethod.Paidy -> PaymentRequest.Paidy(
paymentMethod = this,
fullName = state.value.paidyDisplayData.fullName,
phoneNumber = state.value.paidyDisplayData.phoneNumber,
)
is PaymentMethod.BankTransfer -> TODO()
is PaymentMethod.PayEasy -> TODO()

is PaymentMethod.BankTransfer -> PaymentRequest.BankTransfer(
paymentMethod = this,
lastName = state.value.commonDisplayData.lastName,
firstName = state.value.commonDisplayData.firstName,
lastNamePhonetic = state.value.commonDisplayData.lastNamePhonetic,
firstNamePhonetic = state.value.commonDisplayData.firstNamePhonetic,
email = state.value.commonDisplayData.email,
phoneNumber = state.value.commonDisplayData.phoneNumber,
)

is PaymentMethod.PayEasy -> PaymentRequest.PayEasy(
paymentMethod = this,
lastName = state.value.commonDisplayData.lastName,
firstName = state.value.commonDisplayData.firstName,
lastNamePhonetic = state.value.commonDisplayData.lastNamePhonetic,
firstNamePhonetic = state.value.commonDisplayData.firstNamePhonetic,
email = state.value.commonDisplayData.email,
phoneNumber = state.value.commonDisplayData.phoneNumber,
)
is PaymentMethod.WebMoney -> PaymentRequest.WebMoney(
paymentMethod = this,
prepaidNumber = state.value.webMoneyDisplayData.prepaidNumber,
)

is PaymentMethod.OffSitePayment -> PaymentRequest.OffSitePaymentRequest(this)
is PaymentMethod.CreditCard -> error("Credit Card needs to generate tokens first!")
is PaymentMethod.Other -> error("payment method is not supported!")
}

fun onCloseClicked() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ internal data class CommonDisplayData(
val firstNamePhonetic: String = String.empty,
val email: String = String.empty,
val phoneNumber: String = String.empty,
val fullNameError: String? = null,
val lastNameError: String? = null,
val firstNameError: String? = null,
val lastNamePhoneticError: String? = null,
val firstNamePhoneticError: String? = null,
val emailError: String? = null,
val phoneNumberError: String? = null,
)

internal data class CreditCardDisplayData(
Expand Down
Loading

0 comments on commit f55d386

Please sign in to comment.