Skip to content

Commit

Permalink
Added Result passing mech to tell app about the payment result. (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
AmniX authored Oct 3, 2024
1 parent 6538bad commit 2db587a
Show file tree
Hide file tree
Showing 25 changed files with 441 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.komoju.android.sdk

/**
* This annotation means that this option is experimental and subject to change in Future.
* Please test your behaviour overall once you update the komoju SDK Version
*/
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
@Retention(AnnotationRetention.BINARY)
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.ANNOTATION_CLASS,
AnnotationTarget.PROPERTY,
AnnotationTarget.FIELD,
AnnotationTarget.LOCAL_VARIABLE,
AnnotationTarget.VALUE_PARAMETER,
AnnotationTarget.CONSTRUCTOR,
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.TYPEALIAS,
)
annotation class ExperimentalKomojuPaymentApi
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.transitions.SlideTransition
import com.komoju.android.sdk.navigation.PaymentResultScreenModel
import com.komoju.android.sdk.navigation.paymentResultScreenModel
import com.komoju.android.sdk.ui.screens.RouterEffect
import com.komoju.android.sdk.ui.screens.payment.KomojuPaymentScreen
import com.komoju.android.sdk.ui.theme.KomojuMobileSdkTheme
Expand Down Expand Up @@ -59,6 +61,8 @@ internal class KomojuPaymentActivity : ComponentActivity() {
},
)

private var commonScreenModel: PaymentResultScreenModel? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Expand Down Expand Up @@ -92,6 +96,7 @@ internal class KomojuPaymentActivity : ComponentActivity() {
Navigator(
KomojuPaymentScreen(viewModel.configuration),
) { navigator ->
commonScreenModel = navigator.paymentResultScreenModel()
SlideTransition(navigator)
RouterEffect(viewModel.router.collectAsStateWithLifecycle(), viewModel::onRouteConsumed)
NewIntentEffect(LocalContext.current, viewModel::onNewDeeplink)
Expand Down Expand Up @@ -120,11 +125,16 @@ internal class KomojuPaymentActivity : ComponentActivity() {
}

override fun finish() {
setResult(
RESULT_OK,
Intent().apply {
putExtra(KomojuStartPaymentForResultContract.RESULT_KEY, commonScreenModel?.result)
},
)
lifecycleScope.launch {
viewModel.toggleVisibility(false)
delay(ANIMATION_DURATION.toLong()) // Let the animation finish
super.finish()
}
// TODO: Set Result
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.komoju.android.sdk

import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.komoju.android.sdk.ui.screens.KomojuPaymentRoute
Expand Down Expand Up @@ -40,7 +39,6 @@ internal class KomojuPaymentViewModel(internal val configuration: KomojuSDK.Conf
},
),
)
Log.d("Aman", "handleIntentAction $deeplinkEntity")
}
}

Expand Down
33 changes: 30 additions & 3 deletions android/src/main/java/com/komoju/android/sdk/KomojuSDK.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.komoju.android.sdk

import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Parcelable
import androidx.activity.result.contract.ActivityResultContract
import com.komoju.android.sdk.types.Currency
Expand Down Expand Up @@ -30,6 +31,7 @@ object KomojuSDK {
internal val sessionId: String?, // Unique session ID for payment transaction.
internal val redirectURL: String = "", // URL to redirect after payment completion.
internal val configurableTheme: ConfigurableTheme, // Custom theme for UI elements.
internal val inlinedProcessing: Boolean, // Flag to enable inlined processing.
) : Parcelable {

/**
Expand All @@ -41,6 +43,7 @@ object KomojuSDK {
private var currency: Currency = Currency.JPY // Default currency is Japanese Yen.
private var isDebugMode: Boolean = false // Debug mode is off by default.
private var configurableTheme: ConfigurableTheme = ConfigurableTheme.default // Custom theme for UI elements.
private var inlinedProcessing: Boolean = false // Inlined processing is off by default.

/** Sets the language for the payment. */
fun setLanguage(language: Language) = apply {
Expand All @@ -57,10 +60,27 @@ object KomojuSDK {
this.isDebugMode = isDebugMode
}

/** Sets the custom theme for the payment UI. */
fun setConfigurableTheme(configurableTheme: ConfigurableTheme) = apply {
this.configurableTheme = configurableTheme
}

/**
* WARNING: Experimental API [Try this only if you are sure] Disabled by Default.
*
* This API enables or disables inlined processing.
* If this is enabled then The SDK will try to do processing with minimum amount of screens.
*
* For e.g.
* * If PayPay Payment id captured, it will close the SDK ASAP it verifies the payment.
* * When you will try to pay with Credit Card and Second step verification is not required, SDK will never show the WebView and will handle the callback itself.
*
*/
@ExperimentalKomojuPaymentApi
fun setInlinedProcessing(inlinedProcessing: Boolean) = apply {
this.inlinedProcessing = inlinedProcessing
}

/**
* Builds the [Configuration] instance with the provided settings.
*/
Expand All @@ -71,6 +91,7 @@ object KomojuSDK {
sessionId = sessionId,
isDebugMode = isDebugMode,
configurableTheme = configurableTheme,
inlinedProcessing = inlinedProcessing,
)
}
}
Expand All @@ -79,7 +100,8 @@ object KomojuSDK {
* Data class to hold the result of a payment transaction.
* @param isSuccessFul Whether the payment was successful or not.
*/
data class PaymentResult(val isSuccessFul: Boolean)
@Parcelize
data class PaymentResult(val isSuccessFul: Boolean) : Parcelable

/**
* Property that provides the contract to start the payment process
Expand Down Expand Up @@ -107,6 +129,7 @@ internal class KomojuStartPaymentForResultContract : ActivityResultContract<Komo

companion object {
const val CONFIGURATION_KEY: String = "KomojuSDK.Configuration" // Key for passing configuration data via Intent.
const val RESULT_KEY: String = "KomojuSDK.PaymentResult" // Key for passing result data via Intent.
}

/**
Expand Down Expand Up @@ -145,6 +168,10 @@ internal class KomojuStartPaymentForResultContract : ActivityResultContract<Komo
* @param intent The returned [Intent] containing the result data.
* @return [KomojuSDK.PaymentResult] indicating whether the payment was successful or not.
*/
override fun parseResult(resultCode: Int, intent: Intent?): KomojuSDK.PaymentResult =
KomojuSDK.PaymentResult(isSuccessFul = false) // Default result set to false; actual implementation may vary.
override fun parseResult(resultCode: Int, intent: Intent?): KomojuSDK.PaymentResult = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> intent?.getParcelableExtra(RESULT_KEY, KomojuSDK.PaymentResult::class.java)
else ->
@Suppress("DEPRECATION")
intent?.getParcelableExtra(RESULT_KEY)
} ?: KomojuSDK.PaymentResult(isSuccessFul = false)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.komoju.android.sdk.navigation

import androidx.compose.runtime.Composable
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.rememberNavigatorScreenModel
import cafe.adriel.voyager.navigator.Navigator
import com.komoju.android.sdk.KomojuSDK

internal class PaymentResultScreenModel : ScreenModel {
var result: KomojuSDK.PaymentResult? = null
private set

fun setResult(result: KomojuSDK.PaymentResult) {
this.result = result
}
}

@Composable
internal fun Navigator.paymentResultScreenModel() = rememberNavigatorScreenModel(PaymentResultScreenModel::class.simpleName) { PaymentResultScreenModel() }
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.compose.ui.platform.LocalContext
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import com.komoju.android.sdk.KomojuSDK
import com.komoju.android.sdk.navigation.paymentResultScreenModel
import com.komoju.android.sdk.ui.screens.awating.KonbiniAwaitingPaymentScreen
import com.komoju.android.sdk.ui.screens.failed.PaymentFailedScreen
import com.komoju.android.sdk.ui.screens.failed.Reason
Expand All @@ -29,6 +30,7 @@ internal sealed class Router {
data class ReplaceAll(val route: KomojuPaymentRoute) : Router()
data class Handle(val url: String) : Router()
data class Browser(val url: String) : Router()
data class SetPaymentResultAndPop(val result: KomojuSDK.PaymentResult) : Router()
}

internal sealed interface KomojuPaymentRoute {
Expand Down Expand Up @@ -63,6 +65,7 @@ internal fun RouterEffect(routerState: State<Router?>, onHandled: () -> Unit) {
val context = LocalContext.current
val router = routerState.value
val backPressDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
val resultScreenModel = navigator.paymentResultScreenModel()
LaunchedEffect(router) {
when (router) {
is Router.Pop -> if (navigator.pop().not()) backPressDispatcher?.onBackPressed()
Expand All @@ -78,6 +81,10 @@ internal fun RouterEffect(routerState: State<Router?>, onHandled: () -> Unit) {

is Router.Browser -> context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(router.url)))
null -> Unit
is Router.SetPaymentResultAndPop -> {
resultScreenModel.setResult(router.result)
if (navigator.pop().not()) backPressDispatcher?.onBackPressed()
}
}
onHandled()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,6 @@ internal class PaymentFailedScreen(private val route: KomojuPaymentRoute.Payment
}
}

enum class Reason {
USER_CANCEL,
CREDIT_CARD_ERROR,
OTHER,
}

@Composable
private fun Screen.PaymentFailedScreenContent(route: KomojuPaymentRoute.PaymentFailed) {
val i18nTexts = LocalI18nTexts.current
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.komoju.android.sdk.ui.screens.failed

/**
* Enum class representing the reasons for a payment failure.
*
* This enum provides various constants that describe common reasons why a payment may fail.
* These reasons can be used to handle different error cases in the application and provide
* appropriate feedback to the user.
*/
enum class Reason {

/**
* Payment was canceled by the user.
*
* This reason indicates that the user deliberately canceled the payment process.
* It typically happens when the user decides not to complete the transaction.
*/
USER_CANCEL,

/**
* Payment failed due to a credit card error.
*
* This reason indicates that there was an issue with the user's credit card, such as
* an invalid card number, insufficient funds, or the card being declined by the issuer.
*/
CREDIT_CARD_ERROR,

/**
* Payment failed due to another unspecified reason.
*
* This reason is used when the cause of the failure does not fit into any of the other
* predefined categories.
*/
OTHER,
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.komoju.android.sdk.ui.screens.success

import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
Expand All @@ -14,18 +13,27 @@ import androidx.compose.material.icons.rounded.Clear
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import com.komoju.android.sdk.KomojuSDK
import com.komoju.android.sdk.R
import com.komoju.android.sdk.navigation.paymentResultScreenModel
import com.komoju.android.sdk.ui.composables.PrimaryButton
import com.komoju.android.sdk.ui.screens.RouterEffect
import com.komoju.android.sdk.ui.theme.KomojuMobileSdkTheme
import com.komoju.android.sdk.ui.theme.LocalI18nTexts
import com.komoju.android.sdk.utils.PreviewScreen

internal class PaymentSuccessScreen : Screen {
@Composable
Expand All @@ -35,9 +43,15 @@ internal class PaymentSuccessScreen : Screen {
}

@Composable
private fun PaymentSuccessScreenContent() {
val onBackPressDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
private fun Screen.PaymentSuccessScreenContent() {
val screenModel = rememberScreenModel { PaymentSuccessScreenModel() }
RouterEffect(screenModel.router.collectAsStateWithLifecycle(), screenModel::onRouteConsumed)
val i18nTexts = LocalI18nTexts.current
val navigator = LocalNavigator.currentOrThrow
val resultScreenModel = navigator.paymentResultScreenModel()
LaunchedEffect(Unit) {
resultScreenModel.setResult(KomojuSDK.PaymentResult(isSuccessFul = true))
}
Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) {
Icon(
Expand All @@ -46,7 +60,7 @@ private fun PaymentSuccessScreenContent() {
modifier = Modifier
.padding(16.dp)
.clickable {
onBackPressDispatcher?.onBackPressed()
screenModel.onCloseButtonClicked()
},
)
}
Expand All @@ -62,7 +76,7 @@ private fun PaymentSuccessScreenContent() {
.padding(16.dp),
text = i18nTexts["BACK_TO_STORE"],
) {
onBackPressDispatcher?.onBackPressed()
screenModel.onBackToStoreButtonClicked()
}
}
}
Expand All @@ -71,6 +85,6 @@ private fun PaymentSuccessScreenContent() {
@Preview
private fun PaymentSuccessScreenContentPreview() {
KomojuMobileSdkTheme {
PaymentSuccessScreenContent()
PreviewScreen.PaymentSuccessScreenContent()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.komoju.android.sdk.ui.screens.success

import com.komoju.android.sdk.navigation.RouterStateScreenModel

internal class PaymentSuccessScreenModel : RouterStateScreenModel<Unit>(Unit) {
fun onCloseButtonClicked() {
mutableRouter.pop()
}

fun onBackToStoreButtonClicked() {
mutableRouter.pop()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.komoju.android.sdk.ui.screens.KomojuPaymentRoute
import com.komoju.android.sdk.ui.screens.Router
import com.komoju.android.sdk.ui.screens.failed.Reason
import com.komoju.mobile.sdk.entities.PaymentStatus
import com.komoju.mobile.sdk.entities.PaymentStatus.Companion.isSuccessful
import com.komoju.mobile.sdk.remote.apis.KomojuRemoteApi
import kotlinx.coroutines.launch

Expand All @@ -26,9 +27,17 @@ internal class VerifyPaymentScreenModel(private val config: KomojuSDK.Configurat

private suspend fun processBySession() {
komojuApi.sessions.verifyPaymentBySessionID(config.sessionId.orEmpty()).onSuccess { paymentDetails ->
mutableRouter.value = when (paymentDetails.status) {
PaymentStatus.COMPLETED, PaymentStatus.CAPTURED -> Router.ReplaceAll(KomojuPaymentRoute.PaymentSuccess)
else -> Router.ReplaceAll(KomojuPaymentRoute.PaymentFailed(Reason.OTHER))
mutableRouter.value = when {
config.inlinedProcessing -> Router.SetPaymentResultAndPop(
KomojuSDK.PaymentResult(
isSuccessFul = paymentDetails.status.isSuccessful(),
),
)

else -> when (paymentDetails.status.isSuccessful()) {
true -> Router.ReplaceAll(KomojuPaymentRoute.PaymentSuccess)
else -> Router.ReplaceAll(KomojuPaymentRoute.PaymentFailed(Reason.OTHER))
}
}
}.onFailure {
mutableRouter.value = Router.ReplaceAll(KomojuPaymentRoute.PaymentFailed(Reason.OTHER))
Expand Down
Loading

0 comments on commit 2db587a

Please sign in to comment.