Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Result passing mech to tell app about the payment result. #26

Merged
merged 1 commit into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
@@ -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
Loading