Skip to content

Commit

Permalink
Hook up UPI polling flow (#5704)
Browse files Browse the repository at this point in the history
* Hook up polling flow

* Fix lint

* Increase polling screen height

* Fix build issue with @Preview annotations

* Update API

* Add correct failure icon
  • Loading branch information
tillh-stripe authored Oct 14, 2022
1 parent ea8d0ee commit 30b9be6
Show file tree
Hide file tree
Showing 13 changed files with 618 additions and 16 deletions.
68 changes: 64 additions & 4 deletions paymentsheet/api/paymentsheet.api
Original file line number Diff line number Diff line change
Expand Up @@ -1519,13 +1519,21 @@ public final class com/stripe/android/paymentsheet/paymentdatacollection/ach/di/
public static fun providesProductUsage (Lcom/stripe/android/paymentsheet/paymentdatacollection/ach/di/USBankAccountFormViewModelModule;)Ljava/util/Set;
}

public final class com/stripe/android/paymentsheet/paymentdatacollection/polling/ComposableSingletons$PollingFragmentKt {
public static final field INSTANCE Lcom/stripe/android/paymentsheet/paymentdatacollection/polling/ComposableSingletons$PollingFragmentKt;
public static field lambda-1 Lkotlin/jvm/functions/Function2;
public final class com/stripe/android/paymentsheet/paymentdatacollection/polling/ComposableSingletons$PollingScreenKt {
public static final field INSTANCE Lcom/stripe/android/paymentsheet/paymentdatacollection/polling/ComposableSingletons$PollingScreenKt;
public static field lambda-1 Lkotlin/jvm/functions/Function3;
public static field lambda-2 Lkotlin/jvm/functions/Function2;
public static field lambda-3 Lkotlin/jvm/functions/Function2;
public static field lambda-4 Lkotlin/jvm/functions/Function2;
public static field lambda-5 Lkotlin/jvm/functions/Function2;
public static field lambda-6 Lkotlin/jvm/functions/Function2;
public fun <init> ()V
public final fun getLambda-1$paymentsheet_release ()Lkotlin/jvm/functions/Function2;
public final fun getLambda-1$paymentsheet_release ()Lkotlin/jvm/functions/Function3;
public final fun getLambda-2$paymentsheet_release ()Lkotlin/jvm/functions/Function2;
public final fun getLambda-3$paymentsheet_release ()Lkotlin/jvm/functions/Function2;
public final fun getLambda-4$paymentsheet_release ()Lkotlin/jvm/functions/Function2;
public final fun getLambda-5$paymentsheet_release ()Lkotlin/jvm/functions/Function2;
public final fun getLambda-6$paymentsheet_release ()Lkotlin/jvm/functions/Function2;
}

public final class com/stripe/android/paymentsheet/paymentdatacollection/polling/DefaultTimeProvider_Factory : dagger/internal/Factory {
Expand All @@ -1552,6 +1560,58 @@ public final class com/stripe/android/paymentsheet/paymentdatacollection/polling
public static fun newInstance (Lcom/stripe/android/paymentsheet/paymentdatacollection/polling/PollingViewModel$Args;Lcom/stripe/android/polling/IntentStatusPoller;Lcom/stripe/android/paymentsheet/paymentdatacollection/polling/TimeProvider;Lkotlinx/coroutines/CoroutineDispatcher;Landroidx/lifecycle/SavedStateHandle;)Lcom/stripe/android/paymentsheet/paymentdatacollection/polling/PollingViewModel;
}

public final class com/stripe/android/paymentsheet/paymentdatacollection/polling/PollingViewModel_Factory_MembersInjector : dagger/MembersInjector {
public fun <init> (Ljavax/inject/Provider;)V
public static fun create (Ljavax/inject/Provider;)Ldagger/MembersInjector;
public fun injectMembers (Lcom/stripe/android/paymentsheet/paymentdatacollection/polling/PollingViewModel$Factory;)V
public synthetic fun injectMembers (Ljava/lang/Object;)V
public static fun injectSubcomponentBuilderProvider (Lcom/stripe/android/paymentsheet/paymentdatacollection/polling/PollingViewModel$Factory;Ljavax/inject/Provider;)V
}

public final class com/stripe/android/paymentsheet/paymentdatacollection/polling/di/DaggerPollingComponent {
public static fun builder ()Lcom/stripe/android/paymentsheet/paymentdatacollection/polling/di/PollingComponent$Builder;
}

public final class com/stripe/android/paymentsheet/paymentdatacollection/polling/di/PollingViewModelModule_Companion_ProvidePaymentConfigurationFactory : dagger/internal/Factory {
public fun <init> (Ljavax/inject/Provider;)V
public static fun create (Ljavax/inject/Provider;)Lcom/stripe/android/paymentsheet/paymentdatacollection/polling/di/PollingViewModelModule_Companion_ProvidePaymentConfigurationFactory;
public fun get ()Lcom/stripe/android/PaymentConfiguration;
public synthetic fun get ()Ljava/lang/Object;
public static fun providePaymentConfiguration (Landroid/content/Context;)Lcom/stripe/android/PaymentConfiguration;
}

public final class com/stripe/android/paymentsheet/paymentdatacollection/polling/di/PollingViewModelModule_Companion_ProvidePublishableKeyFactory : dagger/internal/Factory {
public fun <init> (Ljavax/inject/Provider;)V
public static fun create (Ljavax/inject/Provider;)Lcom/stripe/android/paymentsheet/paymentdatacollection/polling/di/PollingViewModelModule_Companion_ProvidePublishableKeyFactory;
public synthetic fun get ()Ljava/lang/Object;
public fun get ()Lkotlin/jvm/functions/Function0;
public static fun providePublishableKey (Landroid/content/Context;)Lkotlin/jvm/functions/Function0;
}

public final class com/stripe/android/paymentsheet/paymentdatacollection/polling/di/PollingViewModelModule_Companion_ProvidesAppContextFactory : dagger/internal/Factory {
public fun <init> (Ljavax/inject/Provider;)V
public static fun create (Ljavax/inject/Provider;)Lcom/stripe/android/paymentsheet/paymentdatacollection/polling/di/PollingViewModelModule_Companion_ProvidesAppContextFactory;
public fun get ()Landroid/content/Context;
public synthetic fun get ()Ljava/lang/Object;
public static fun providesAppContext (Landroid/app/Application;)Landroid/content/Context;
}

public final class com/stripe/android/paymentsheet/paymentdatacollection/polling/di/PollingViewModelModule_Companion_ProvidesEnableLoggingFactory : dagger/internal/Factory {
public fun <init> ()V
public static fun create ()Lcom/stripe/android/paymentsheet/paymentdatacollection/polling/di/PollingViewModelModule_Companion_ProvidesEnableLoggingFactory;
public fun get ()Ljava/lang/Boolean;
public synthetic fun get ()Ljava/lang/Object;
public static fun providesEnableLogging ()Z
}

public final class com/stripe/android/paymentsheet/paymentdatacollection/polling/di/PollingViewModelModule_Companion_ProvidesProductUsageFactory : dagger/internal/Factory {
public fun <init> ()V
public static fun create ()Lcom/stripe/android/paymentsheet/paymentdatacollection/polling/di/PollingViewModelModule_Companion_ProvidesProductUsageFactory;
public synthetic fun get ()Ljava/lang/Object;
public fun get ()Ljava/util/Set;
public static fun providesProductUsage ()Ljava/util/Set;
}

public final class com/stripe/android/paymentsheet/repositories/CustomerApiRepository_Factory : dagger/internal/Factory {
public fun <init> (Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;)V
public static fun create (Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;)Lcom/stripe/android/paymentsheet/repositories/CustomerApiRepository_Factory;
Expand Down
1 change: 1 addition & 0 deletions paymentsheet/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ dependencies {
implementation "androidx.compose.runtime:runtime-livedata:$androidxComposeVersion"
implementation "com.google.accompanist:accompanist-flowlayout:$accompanistVersion"
// Tooling support (Previews, etc.)
implementation "androidx.compose.ui:ui-tooling-preview:$androidxComposeVersion"
debugImplementation "androidx.compose.ui:ui-tooling:$androidxComposeVersion"

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutinesVersion"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="34dp"
android:height="34dp"
android:viewportWidth="34"
android:viewportHeight="34">
<path
android:pathData="M33,17C33,8.163 25.837,1 17,1C8.163,1 1,8.163 1,17C1,25.837 8.163,33 17,33C25.837,33 33,25.837 33,17Z"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#C84801" />
<path
android:pathData="M17,20.5L17,8.5"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#C84801"
android:strokeLineCap="round" />
<path
android:pathData="M16.006,26a0.994,1 0,1 0,1.989 0a0.994,1 0,1 0,-1.989 0z"
android:fillColor="#C84801" />
</vector>
12 changes: 12 additions & 0 deletions paymentsheet/res/values/totranslate.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">

<!-- UPI -->
<string name="upi_polling_header">Approve payment</string>
<string name="upi_polling_message">Open your UPI app to approve your payment within %s</string>
<string name="upi_polling_cancel">Cancel and pay another way</string>

<string name="upi_polling_payment_failed_title">Payment failed</string>
<string name="upi_polling_payment_failed_message">Please go back and try another payment method</string>

</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ internal class PollingActivity : AppCompatActivity() {
}

if (savedInstanceState == null) {
val fragment = PollingFragment.newInstance()
val fragment = PollingFragment.newInstance(args)
fragment.isCancelable = false
fragment.show(supportFragmentManager, fragment.tag)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import com.stripe.android.payments.core.authentication.PaymentAuthenticator
import com.stripe.android.view.AuthActivityStarterHost
import javax.inject.Singleton

private const val UPI_TIME_LIMIT_IN_SECONDS = 5 * 60
private const val UPI_INITIAL_DELAY_IN_SECONDS = 5
private const val UPI_MAX_ATTEMPTS = 12

@Singleton
internal class PollingAuthenticator : PaymentAuthenticator<StripeIntent> {

Expand All @@ -22,7 +26,10 @@ internal class PollingAuthenticator : PaymentAuthenticator<StripeIntent> {
) {
val args = PollingContract.Args(
clientSecret = requireNotNull(authenticatable.clientSecret),
statusBarColor = host.statusBarColor
statusBarColor = host.statusBarColor,
timeLimitInSeconds = UPI_TIME_LIMIT_IN_SECONDS,
initialDelayInSeconds = UPI_INITIAL_DELAY_IN_SECONDS,
maxAttempts = UPI_MAX_ATTEMPTS,
)
pollingLauncher?.launch(args)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ internal class PollingContract :
internal data class Args(
val clientSecret: String,
@ColorInt val statusBarColor: Int?,
val timeLimitInSeconds: Int,
val initialDelayInSeconds: Int,
val maxAttempts: Int,
) : Parcelable {

internal companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,44 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.ui.Modifier
import androidx.activity.addCallback
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.unit.dp
import androidx.core.os.bundleOf
import androidx.fragment.app.setFragmentResult
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.stripe.android.StripeIntentResult
import com.stripe.android.payments.PaymentFlowResult
import com.stripe.android.ui.core.PaymentsTheme
import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.seconds

private const val KEY_POLLING_ARGS = "KEY_POLLING_ARGS"

internal class PollingFragment : BottomSheetDialogFragment() {

private val args: PollingContract.Args by lazy {
requireNotNull(arguments?.getParcelable(KEY_POLLING_ARGS))
}

private val viewModel by viewModels<PollingViewModel> {
PollingViewModel.Factory(
applicationSupplier = { requireActivity().application },
argsSupplier = {
PollingViewModel.Args(
clientSecret = args.clientSecret,
timeLimit = args.timeLimitInSeconds.seconds,
initialDelay = args.initialDelayInSeconds.seconds,
maxAttempts = args.maxAttempts,
)
},
owner = this,
)
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
Expand All @@ -22,20 +50,71 @@ internal class PollingFragment : BottomSheetDialogFragment() {
return ComposeView(requireContext()).apply {
setContent {
PaymentsTheme {
Text(
text = "Coming soon 🚧",
modifier = Modifier.padding(32.dp)
)
PollingScreen(viewModel)
}
}
}
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

requireActivity().onBackPressedDispatcher.addCallback(
owner = viewLifecycleOwner,
enabled = false,
onBackPressed = {}
)

viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect(this@PollingFragment::handleUiState)
}
}
}

private fun handleUiState(uiState: PollingUiState) {
if (uiState.pollingState == PollingState.Success) {
finishWithSuccess()
} else if (uiState.pollingState == PollingState.Canceled) {
finishWithCancellation()
}
}

private fun finishWithSuccess() {
val successResult = PaymentFlowResult.Unvalidated(
clientSecret = args.clientSecret,
flowOutcome = StripeIntentResult.Outcome.SUCCEEDED,
)
finishWithResult(successResult)
}

private fun finishWithCancellation() {
val cancelResult = PaymentFlowResult.Unvalidated(
clientSecret = args.clientSecret,
flowOutcome = StripeIntentResult.Outcome.CANCELED,
canCancelSource = false,
)
finishWithResult(cancelResult)
}

private fun finishWithResult(paymentFlowResult: PaymentFlowResult.Unvalidated) {
setFragmentResult(
requestKey = KEY_FRAGMENT_RESULT,
result = paymentFlowResult.toBundle(),
)
dismiss()
}

companion object {

const val KEY_FRAGMENT_RESULT = "KEY_FRAGMENT_RESULT_PollingFragment"

fun newInstance(): PollingFragment {
return PollingFragment()
fun newInstance(args: PollingContract.Args): PollingFragment {
return PollingFragment().apply {
arguments = bundleOf(
KEY_POLLING_ARGS to args
)
}
}
}
}
Loading

0 comments on commit 30b9be6

Please sign in to comment.