Skip to content
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
5 changes: 3 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ ext {
publishGroupId = 'com.processout'
publishVersion = file('version.resolved').getText().trim()

androidxCoreVersion = '1.15.0'
kotlinxCoroutinesVersion = '1.10.2'

androidxCoreVersion = '1.16.0'
androidxAppCompatVersion = '1.7.0'
androidxConstraintLayoutVersion = '2.2.1'
androidxActivityVersion = '1.10.1'
Expand All @@ -54,7 +56,6 @@ ext {

gmsWalletVersion = '19.4.0'
mlkitTextRecognitionVersion = '19.0.1'
kotlinxCoroutinesPlayServicesVersion = '1.9.0'

retrofitVersion = '2.11.0'
moshiVersion = '1.15.2'
Expand Down
4 changes: 3 additions & 1 deletion sdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ def setBuildConfig(buildType) {
}

dependencies {
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinxCoroutinesVersion"
api "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$kotlinxCoroutinesVersion"

api "androidx.appcompat:appcompat:$androidxAppCompatVersion"
api "androidx.core:core-ktx:$androidxCoreVersion"
api "androidx.activity:activity-ktx:$androidxActivityVersion"
Expand All @@ -110,7 +113,6 @@ dependencies {

api "com.google.android.material:material:$materialVersion"
api "com.google.android.gms:play-services-wallet:$gmsWalletVersion"
api "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$kotlinxCoroutinesPlayServicesVersion"

implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
implementation "com.squareup.retrofit2:converter-moshi:$retrofitVersion"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.processout.sdk.api.model.request

import com.processout.sdk.api.dispatcher.POEventDispatcher
import com.processout.sdk.api.model.response.PODynamicCheckoutPaymentMethod
import com.processout.sdk.api.model.response.PONativeAlternativePaymentMethodParameter
import com.processout.sdk.core.annotation.ProcessOutInternalApi
import java.util.UUID

/** @suppress */
@ProcessOutInternalApi
data class PODynamicCheckoutAlternativePaymentDefaultValuesRequest(
override val uuid: UUID,
val paymentMethod: PODynamicCheckoutPaymentMethod.AlternativePayment,
val parameters: List<PONativeAlternativePaymentMethodParameter>
) : POEventDispatcher.Request
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.processout.sdk.api.model.response

import com.processout.sdk.api.dispatcher.POEventDispatcher
import com.processout.sdk.api.model.request.PODynamicCheckoutAlternativePaymentDefaultValuesRequest
import com.processout.sdk.api.model.request.PONativeAlternativePaymentMethodDefaultValuesRequest
import com.processout.sdk.core.annotation.ProcessOutInternalApi
import java.util.UUID

/**
Expand All @@ -24,3 +26,9 @@ data class PONativeAlternativePaymentMethodDefaultValuesResponse internal constr
fun PONativeAlternativePaymentMethodDefaultValuesRequest.toResponse(
defaultValues: Map<String, String>
) = PONativeAlternativePaymentMethodDefaultValuesResponse(uuid, defaultValues)

/** @suppress */
@ProcessOutInternalApi
fun PODynamicCheckoutAlternativePaymentDefaultValuesRequest.toResponse(
defaultValues: Map<String, String>
) = PONativeAlternativePaymentMethodDefaultValuesResponse(uuid, defaultValues)
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ interface POCardTokenizationDelegate {
* Allows to choose default preferred card scheme based on issuer information.
* Primary card scheme is used by default.
*/
suspend fun preferredScheme(
fun preferredScheme(
issuerInformation: POCardIssuerInformation
): String? = issuerInformation.scheme

/**
* Allows to decide whether the flow should continue or complete after the failure.
* Returns _true_ by default.
*/
suspend fun shouldContinue(
fun shouldContinue(
failure: ProcessOutResult.Failure
): Boolean = true
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface POCardUpdateDelegate {
* Allows to decide whether the card update should continue or complete after the failure.
* Returns _true_ by default.
*/
suspend fun shouldContinue(
fun shouldContinue(
failure: ProcessOutResult.Failure
): Boolean = true
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,20 @@ import com.processout.sdk.ui.checkout.DynamicCheckoutInteractorState.PaymentMeth
import com.processout.sdk.ui.checkout.DynamicCheckoutInteractorState.PaymentMethod.AlternativePayment
import com.processout.sdk.ui.checkout.DynamicCheckoutInteractorState.PaymentMethod.Card
import com.processout.sdk.ui.checkout.DynamicCheckoutInteractorState.PaymentMethod.GooglePay
import com.processout.sdk.ui.checkout.PODynamicCheckoutConfiguration.AlternativePaymentConfiguration
import com.processout.sdk.ui.checkout.PODynamicCheckoutConfiguration.GooglePayConfiguration
import com.processout.sdk.ui.checkout.PODynamicCheckoutConfiguration.GooglePayConfiguration.BillingAddressConfiguration.Format.FULL
import com.processout.sdk.ui.checkout.PODynamicCheckoutConfiguration.GooglePayConfiguration.BillingAddressConfiguration.Format.MIN
import com.processout.sdk.ui.checkout.PODynamicCheckoutConfiguration.GooglePayConfiguration.CheckoutOption.COMPLETE_IMMEDIATE_PURCHASE
import com.processout.sdk.ui.checkout.PODynamicCheckoutConfiguration.GooglePayConfiguration.CheckoutOption.DEFAULT
import com.processout.sdk.ui.checkout.PODynamicCheckoutConfiguration.GooglePayConfiguration.TotalPriceStatus.ESTIMATED
import com.processout.sdk.ui.checkout.PODynamicCheckoutConfiguration.GooglePayConfiguration.TotalPriceStatus.FINAL
import com.processout.sdk.ui.napm.NativeAlternativePaymentCompletion
import com.processout.sdk.ui.napm.NativeAlternativePaymentEvent
import com.processout.sdk.ui.napm.NativeAlternativePaymentSideEffect
import com.processout.sdk.ui.napm.NativeAlternativePaymentViewModel
import com.processout.sdk.ui.checkout.dispatcher.DynamicCheckoutAlternativePaymentConfigurationRequest
import com.processout.sdk.ui.checkout.dispatcher.DynamicCheckoutAlternativePaymentConfigurationResponse
import com.processout.sdk.ui.checkout.dispatcher.DynamicCheckoutSavedPaymentMethodsRequest
import com.processout.sdk.ui.checkout.dispatcher.DynamicCheckoutSavedPaymentMethodsResponse
import com.processout.sdk.ui.napm.*
import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.*
import com.processout.sdk.ui.savedpaymentmethods.POSavedPaymentMethodsConfiguration
import com.processout.sdk.ui.shared.extension.orElse
import kotlinx.coroutines.*
Expand Down Expand Up @@ -122,11 +125,12 @@ internal class DynamicCheckoutInteractor(

private suspend fun start() {
handleCompletions()
dispatchEvents()
dispatchSideEffects()
collectEvents()
collectInvoice()
collectInvoiceAuthorizationRequest()
collectTokenizedCard()
collectNativeAlternativePaymentConfiguration()
collectSavedPaymentMethodsConfiguration()
fetchConfiguration()
}
Expand Down Expand Up @@ -438,20 +442,21 @@ internal class DynamicCheckoutInteractor(
POLogger.info("Selected payment method: %s", paymentMethod.original)
dispatch(DidSelectPaymentMethod(paymentMethod = paymentMethod.original))
resetPaymentMethods()
if (state.processingPaymentMethod != null) {
invalidateInvoice(
reason = PODynamicCheckoutInvoiceInvalidationReason.PaymentMethodChanged
)
} else if (state.invoice != null) {
start(paymentMethod)
}
_state.update {
it.copy(
selectedPaymentMethod = paymentMethod,
pendingSubmitPaymentMethod = null,
errorMessage = null
)
}
if (state.processingPaymentMethod != null) {
invalidateInvoice(
reason = PODynamicCheckoutInvoiceInvalidationReason.PaymentMethodChanged,
selectedPaymentMethod = paymentMethod
)
} else if (state.invoice != null) {
start(paymentMethod)
}
}
}

Expand All @@ -469,12 +474,14 @@ internal class DynamicCheckoutInteractor(
configuration = cardTokenization.configuration
.apply(paymentMethod.configuration)
)
is NativeAlternativePayment -> nativeAlternativePayment.start(
configuration = nativeAlternativePayment.configuration.copy(
invoiceId = configuration.invoiceRequest.invoiceId,
gatewayConfigurationId = paymentMethod.gatewayConfigurationId
is NativeAlternativePayment -> interactorScope.launch {
eventDispatcher.send(
DynamicCheckoutAlternativePaymentConfigurationRequest(
paymentMethod = paymentMethod.original,
configuration = configuration.alternativePayment
)
)
)
}
else -> {}
}
}
Expand All @@ -498,6 +505,64 @@ internal class DynamicCheckoutInteractor(
never -> CollectionMode.Never
}

private fun collectNativeAlternativePaymentConfiguration() {
eventDispatcher.subscribeForResponse<DynamicCheckoutAlternativePaymentConfigurationResponse>(
coroutineScope = interactorScope
) { response ->
_state.value.selectedPaymentMethod?.let { paymentMethod ->
if (paymentMethod.original != response.paymentMethod) {
return@let
}
if (paymentMethod is NativeAlternativePayment) {
if (response.configuration.returnUrl != configuration.alternativePayment.returnUrl) {
error("Changing alternative payment 'returnUrl' is not supported via delegate.")
}
nativeAlternativePayment.start(
configuration = nativeAlternativePayment.configuration
.apply(
invoiceId = configuration.invoiceRequest.invoiceId,
gatewayConfigurationId = paymentMethod.gatewayConfigurationId,
configuration = response.configuration
)
)
}
}
}
}

private fun PONativeAlternativePaymentConfiguration.apply(
invoiceId: String,
gatewayConfigurationId: String,
configuration: AlternativePaymentConfiguration
) = copy(
invoiceId = invoiceId,
gatewayConfigurationId = gatewayConfigurationId,
paymentConfirmation = paymentConfirmation.apply(configuration.paymentConfirmation),
barcode = configuration.barcode,
inlineSingleSelectValuesLimit = configuration.inlineSingleSelectValuesLimit
)

private fun PaymentConfirmationConfiguration.apply(
configuration: AlternativePaymentConfiguration.PaymentConfirmationConfiguration
) = copy(
timeoutSeconds = configuration.timeoutSeconds,
showProgressIndicatorAfterSeconds = configuration.showProgressIndicatorAfterSeconds,
confirmButton = configuration.confirmButton?.let {
Button(
text = it.text,
icon = it.icon
)
},
cancelButton = configuration.cancelButton?.let {
CancelButton(
text = it.text,
icon = it.icon,
disabledForSeconds = it.disabledForSeconds,
confirmation = it.confirmation
)
}
)

private fun onFieldValueChanged(event: FieldValueChanged) {
when (val paymentMethod = paymentMethod(event.paymentMethodId)) {
is Card -> cardTokenization.onEvent(
Expand Down Expand Up @@ -785,7 +850,10 @@ internal class DynamicCheckoutInteractor(
}
}

private fun invalidateInvoice(reason: PODynamicCheckoutInvoiceInvalidationReason) {
private fun invalidateInvoice(
reason: PODynamicCheckoutInvoiceInvalidationReason,
selectedPaymentMethod: PaymentMethod? = null
) {
POLogger.info("Invalidating invoice. Reason: %s", reason, attributes = logAttributes)
var errorMessage: String? = null
if (reason is PODynamicCheckoutInvoiceInvalidationReason.Failure) {
Expand All @@ -809,7 +877,7 @@ internal class DynamicCheckoutInteractor(
_state.update {
it.copy(
invoice = null,
selectedPaymentMethod = null,
selectedPaymentMethod = selectedPaymentMethod,
processingPaymentMethod = null,
errorMessage = errorMessage
)
Expand Down Expand Up @@ -993,14 +1061,37 @@ internal class DynamicCheckoutInteractor(
}
}

private fun collectEvents() {
private fun dispatch(event: PODynamicCheckoutEvent) {
interactorScope.launch {
eventDispatcher.send(event)
POLogger.debug("Event has been sent: %s", event)
}
}

private fun dispatchEvents() {
eventDispatcher.subscribeForRequest<POCardTokenizationShouldContinueRequest>(
coroutineScope = interactorScope
) { request ->
interactorScope.launch {
eventDispatcher.send(request.toResponse(shouldContinue = false))
}
}
eventDispatcher.subscribeForRequest<PONativeAlternativePaymentMethodDefaultValuesRequest>(
coroutineScope = interactorScope
) { request ->
activePaymentMethod()?.let { paymentMethod ->
if (paymentMethod is NativeAlternativePayment) {
interactorScope.launch {
val defaultValuesRequest = PODynamicCheckoutAlternativePaymentDefaultValuesRequest(
uuid = request.uuid,
paymentMethod = paymentMethod.original,
parameters = request.parameters
)
eventDispatcher.send(defaultValuesRequest)
}
}
}
}
eventDispatcher.subscribe<PONativeAlternativePaymentMethodEvent>(
coroutineScope = interactorScope
) { event ->
Expand All @@ -1010,13 +1101,6 @@ internal class DynamicCheckoutInteractor(
}
}

private fun dispatch(event: PODynamicCheckoutEvent) {
interactorScope.launch {
eventDispatcher.send(event)
POLogger.debug("Event has been sent: %s", event)
}
}

private fun dispatchSideEffects() {
interactorScope.launch(Dispatchers.Main.immediate) {
cardTokenization.sideEffects.collect { sideEffect ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,21 @@ internal data class DynamicCheckoutInteractorState(

data class Card(
override val id: String,
override val original: PODynamicCheckoutPaymentMethod,
override val original: PODynamicCheckoutPaymentMethod.Card,
val configuration: CardConfiguration,
val display: Display
) : PaymentMethod

data class GooglePay(
override val id: String,
override val original: PODynamicCheckoutPaymentMethod,
override val original: PODynamicCheckoutPaymentMethod.GooglePay,
val allowedPaymentMethods: String,
val paymentDataRequest: JSONObject
) : PaymentMethod

data class AlternativePayment(
override val id: String,
override val original: PODynamicCheckoutPaymentMethod,
override val original: PODynamicCheckoutPaymentMethod.AlternativePayment,
val gatewayConfigurationId: String,
val redirectUrl: String,
val savePaymentMethodField: Field?,
Expand All @@ -49,7 +49,7 @@ internal data class DynamicCheckoutInteractorState(

data class NativeAlternativePayment(
override val id: String,
override val original: PODynamicCheckoutPaymentMethod,
override val original: PODynamicCheckoutPaymentMethod.AlternativePayment,
val gatewayConfigurationId: String,
val display: Display
) : PaymentMethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ interface PODynamicCheckoutDelegate {
* Allows to choose default preferred card scheme based on issuer information.
* Primary card scheme is used by default.
*/
suspend fun preferredScheme(
fun preferredScheme(
issuerInformation: POCardIssuerInformation
): String? = issuerInformation.scheme

Expand All @@ -75,14 +75,24 @@ interface PODynamicCheckoutDelegate {
* It's not mandatory to provide default values for all parameters.
*/
suspend fun defaultValues(
gatewayConfigurationId: String,
paymentMethod: PODynamicCheckoutPaymentMethod.AlternativePayment,
parameters: List<PONativeAlternativePaymentMethodParameter>
): Map<String, String> = emptyMap()

/**
* Allows to override default alternative payment configuration.
* Invoked when payment method is about to start.
* __Note:__ Changing _returnUrl_ is not supported and will throw _IllegalStateException_.
*/
fun alternativePayment(
paymentMethod: PODynamicCheckoutPaymentMethod.AlternativePayment,
configuration: PODynamicCheckoutConfiguration.AlternativePaymentConfiguration
): PODynamicCheckoutConfiguration.AlternativePaymentConfiguration = configuration

/**
* Allows to customize saved payment methods configuration.
*/
suspend fun savedPaymentMethods(
fun savedPaymentMethods(
configuration: POSavedPaymentMethodsConfiguration
): POSavedPaymentMethodsConfiguration = configuration
}
Loading