diff --git a/core/src/main/java/io/snabble/sdk/checkout/Checkout.kt b/core/src/main/java/io/snabble/sdk/checkout/Checkout.kt index fa617f0481..54401b7507 100644 --- a/core/src/main/java/io/snabble/sdk/checkout/Checkout.kt +++ b/core/src/main/java/io/snabble/sdk/checkout/Checkout.kt @@ -3,14 +3,18 @@ package io.snabble.sdk.checkout import androidx.annotation.RestrictTo import androidx.annotation.VisibleForTesting import androidx.lifecycle.LiveData -import io.snabble.sdk.* +import io.snabble.sdk.FulfillmentState +import io.snabble.sdk.MutableAccessibleLiveData +import io.snabble.sdk.PaymentMethod +import io.snabble.sdk.Product +import io.snabble.sdk.Project +import io.snabble.sdk.Snabble import io.snabble.sdk.Snabble.instance import io.snabble.sdk.payment.PaymentCredentials import io.snabble.sdk.shoppingcart.ShoppingCart import io.snabble.sdk.utils.Dispatch import io.snabble.sdk.utils.Logger import java.io.File -import java.util.* import java.util.concurrent.Future class Checkout @JvmOverloads constructor( @@ -499,6 +503,7 @@ class Checkout @JvmOverloads constructor( || state == CheckoutState.PAYMENT_PROCESSING || (state == CheckoutState.PAYMENT_APPROVED && !areAllFulfillmentsClosed()) || state == CheckoutState.PAYONE_SEPA_MANDATE_REQUIRED + || state == CheckoutState.AUTHENTICATING ) { scheduleNextPoll() } @@ -580,6 +585,10 @@ class Checkout @JvmOverloads constructor( notifyStateChanged(CheckoutState.PAYMENT_PROCESSING) } + CheckState.AUTHENTICATING -> { + notifyStateChanged(CheckoutState.AUTHENTICATING) + } + CheckState.SUCCESSFUL -> { val exitToken = checkoutProcess.exitToken return if (exitToken != null && (exitToken.format.isNullOrEmpty() || exitToken.value.isNullOrEmpty())) { diff --git a/core/src/main/java/io/snabble/sdk/checkout/CheckoutApi.kt b/core/src/main/java/io/snabble/sdk/checkout/CheckoutApi.kt index 00dec7fd82..d2462491d6 100644 --- a/core/src/main/java/io/snabble/sdk/checkout/CheckoutApi.kt +++ b/core/src/main/java/io/snabble/sdk/checkout/CheckoutApi.kt @@ -119,6 +119,7 @@ enum class CheckState { @SerializedName("unauthorized") UNAUTHORIZED, @SerializedName("pending") PENDING, @SerializedName("processing") PROCESSING, + @SerializedName("authenticating") AUTHENTICATING, @SerializedName("successful") SUCCESSFUL, @SerializedName("transferred") TRANSFERRED, @SerializedName("failed") FAILED @@ -387,6 +388,9 @@ data class CheckoutProcessResponse( val authorizePaymentLink: String? get() = links?.get("authorizePayment")?.href + val paymentRedirect: String? + get() = links?.get("paymentRedirect")?.href + val originCandidateLink: String? get() = paymentResult?.originCandidateLink } diff --git a/core/src/main/java/io/snabble/sdk/checkout/CheckoutState.kt b/core/src/main/java/io/snabble/sdk/checkout/CheckoutState.kt index 5cc0df7893..f2d15b11e6 100644 --- a/core/src/main/java/io/snabble/sdk/checkout/CheckoutState.kt +++ b/core/src/main/java/io/snabble/sdk/checkout/CheckoutState.kt @@ -26,6 +26,12 @@ enum class CheckoutState { */ VERIFYING_PAYMENT_METHOD, + /** + * The payment need further authentication. + * Use the paymentRedirect link provided for further authentication. + */ + AUTHENTICATING, + /** * Age needs to be verified */ diff --git a/ui/src/main/java/io/snabble/sdk/ui/checkout/AuthenticationFragment.kt b/ui/src/main/java/io/snabble/sdk/ui/checkout/AuthenticationFragment.kt new file mode 100644 index 0000000000..b917845a64 --- /dev/null +++ b/ui/src/main/java/io/snabble/sdk/ui/checkout/AuthenticationFragment.kt @@ -0,0 +1,79 @@ +package io.snabble.sdk.ui.checkout + +import android.os.Bundle +import android.view.View +import android.webkit.CookieManager +import android.webkit.WebResourceRequest +import android.webkit.WebSettings +import android.webkit.WebView +import android.webkit.WebViewClient +import io.snabble.sdk.Snabble +import io.snabble.sdk.ui.BaseFragment +import io.snabble.sdk.ui.R +import io.snabble.sdk.utils.Logger + +class AuthenticationFragment : BaseFragment(R.layout.snabble_fragment_authentication) { + override fun onActualViewCreated(view: View, savedInstanceState: Bundle?) { + super.onActualViewCreated(view, savedInstanceState) + + val currentCheckout = Snabble.checkedInProject.value?.checkout + + // Setup your views here after inflation + val webview = view.findViewById(R.id.authentication_webview) + webview.setupCookies() + + // Configure WebView settings + webview.settings.apply { + javaScriptEnabled = true + domStorageEnabled = true + loadWithOverviewMode = true + useWideViewPort = true + setSupportZoom(true) + builtInZoomControls = true + displayZoomControls = false + + // Additional cookie-related settings + databaseEnabled = true + cacheMode = WebSettings.LOAD_DEFAULT + } + + webview.webViewClient = object : WebViewClient() { + override fun onReceivedError(view: WebView?, errorCode: Int, description: String?, failingUrl: String?) { + Logger.d("onReceivedError $failingUrl") + } + + override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { + when (request.url.toString()) { + SUCCESS, + ERROR, + BACK -> { + // No need to pop it since we're polling for the checkout state in the activity + // and navigate away as soon the get an update state + return true + } + } + return super.shouldOverrideUrlLoading(view, request) + } + } + + val redirectUrl = currentCheckout?.checkoutProcess?.paymentRedirect + redirectUrl?.let { + webview.loadUrl(it) + } + } + + // Extension function for cleaner code + fun WebView.setupCookies() { + val cookieManager = CookieManager.getInstance() + cookieManager.setAcceptCookie(true) + cookieManager.setAcceptThirdPartyCookies(this, true) + cookieManager.flush() + } + + private companion object { + + const val SUCCESS = "snabble://payone/success" + const val ERROR = "snabble://payone/error" + const val BACK = "snabble://payone/back" + } +} diff --git a/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutActivity.kt b/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutActivity.kt index cdf873365b..ddb447e64d 100644 --- a/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutActivity.kt +++ b/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutActivity.kt @@ -214,6 +214,8 @@ class CheckoutActivity : FragmentActivity() { } } + CheckoutState.AUTHENTICATING -> R.id.snabble_nav_authentication + CheckoutState.DEPOSIT_RETURN_REDEMPTION_FAILED, CheckoutState.PAYMENT_ABORTED -> { finish() diff --git a/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutHelper.kt b/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutHelper.kt index 7e812d2661..83a07dc920 100644 --- a/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutHelper.kt +++ b/ui/src/main/java/io/snabble/sdk/ui/checkout/CheckoutHelper.kt @@ -22,6 +22,7 @@ val CheckoutState.isCheckoutState: Boolean CheckoutState.WAIT_FOR_GATEKEEPER, CheckoutState.WAIT_FOR_APPROVAL, CheckoutState.PAYMENT_PROCESSING, + CheckoutState.AUTHENTICATING, CheckoutState.PAYMENT_APPROVED, CheckoutState.DENIED_TOO_YOUNG, CheckoutState.DENIED_BY_PAYMENT_PROVIDER, diff --git a/ui/src/main/res/layout/snabble_fragment_authentication.xml b/ui/src/main/res/layout/snabble_fragment_authentication.xml new file mode 100644 index 0000000000..aca169a277 --- /dev/null +++ b/ui/src/main/res/layout/snabble_fragment_authentication.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/ui/src/main/res/navigation/snabble_nav_checkout.xml b/ui/src/main/res/navigation/snabble_nav_checkout.xml index d922abee64..d547782538 100644 --- a/ui/src/main/res/navigation/snabble_nav_checkout.xml +++ b/ui/src/main/res/navigation/snabble_nav_checkout.xml @@ -37,6 +37,11 @@ android:name="io.snabble.sdk.ui.checkout.PaymentStatusFragment" android:label="@string/Snabble.Checkout.title" /> + +