diff --git a/CHANGELOG.md b/CHANGELOG.md index f3891930189..5c0a53b9d12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## XX.XX.XX - 2023-XX-XX +### PaymentSheet +* [ADDED][7337](https://github.com/stripe/stripe-android/pull/7337) PaymentSheet now supports Swish for PaymentIntents in private beta. + +### Payments +* [ADDED][7337](https://github.com/stripe/stripe-android/pull/7337) Added support for Swish for PaymentIntents in private beta. + ### Financial Connections * [FIXED][7331](https://github.com/stripe/stripe-android/pull/7331) When cancelling out of a auth session, going back to the consent screen required two back taps. diff --git a/example/AndroidManifest.xml b/example/AndroidManifest.xml index 3a1937cccab..e5ae8f1b57c 100644 --- a/example/AndroidManifest.xml +++ b/example/AndroidManifest.xml @@ -79,6 +79,7 @@ + diff --git a/example/res/values/strings.xml b/example/res/values/strings.xml index e2aa681d861..fca41e7aad9 100644 --- a/example/res/values/strings.xml +++ b/example/res/values/strings.xml @@ -34,6 +34,7 @@ "Manual US Bank Account Example" Cash App Pay Revolut Pay + Swish Card Brands Card number Possible Card Brands diff --git a/example/src/main/java/com/stripe/example/activity/LauncherActivity.kt b/example/src/main/java/com/stripe/example/activity/LauncherActivity.kt index 81f430f9822..756ead19d62 100644 --- a/example/src/main/java/com/stripe/example/activity/LauncherActivity.kt +++ b/example/src/main/java/com/stripe/example/activity/LauncherActivity.kt @@ -177,6 +177,10 @@ class LauncherActivity : AppCompatActivity() { activity.getString(R.string.revolut_pay_example), RevolutPayActivity::class.java ), + Item( + activity.getString(R.string.swish_example), + SwishExampleActivity::class.java + ), // This is for internal use so as not to confuse the user. Item( "StripeImage Example", diff --git a/example/src/main/java/com/stripe/example/activity/SwishExampleActivity.kt b/example/src/main/java/com/stripe/example/activity/SwishExampleActivity.kt new file mode 100644 index 00000000000..4ab402f140e --- /dev/null +++ b/example/src/main/java/com/stripe/example/activity/SwishExampleActivity.kt @@ -0,0 +1,91 @@ +package com.stripe.example.activity + +import android.os.Bundle +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.Button +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.Divider +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.google.accompanist.themeadapter.material.MdcTheme +import com.stripe.android.model.PaymentMethodCreateParams + +class SwishExampleActivity : StripeIntentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + val isProcessing by viewModel.inProgress.observeAsState(initial = false) + val status by viewModel.status.observeAsState(initial = "") + + SwishScreen( + isProcessing = isProcessing, + status = status, + onButtonPressed = { payWithSwish() }, + ) + } + } + + private fun payWithSwish() { + val params = PaymentMethodCreateParams.createSwish() + + createAndConfirmPaymentIntent( + country = "FR", + currency = "SEK", + paymentMethodCreateParams = params, + supportedPaymentMethods = "swish", + ) + } +} + +@Composable +private fun SwishScreen( + isProcessing: Boolean, + status: String, + onButtonPressed: () -> Unit, +) { + MdcTheme { + Column(modifier = Modifier.fillMaxSize()) { + Row(verticalAlignment = Alignment.CenterVertically) { + Button( + onClick = onButtonPressed, + enabled = !isProcessing, + modifier = Modifier.padding(16.dp), + ) { + Text("Pay with Swish") + } + + if (isProcessing) { + CircularProgressIndicator(modifier = Modifier.size(24.dp)) + } + } + + if (status.isNotBlank()) { + Divider( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + + Text( + text = status, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + ) + } + } + } +} diff --git a/payments-core/api/payments-core.api b/payments-core/api/payments-core.api index ac53190e0a4..fd90ba7e10c 100644 --- a/payments-core/api/payments-core.api +++ b/payments-core/api/payments-core.api @@ -3799,6 +3799,7 @@ public final class com/stripe/android/model/PaymentMethod$Type : java/lang/Enum, public static final field RevolutPay Lcom/stripe/android/model/PaymentMethod$Type; public static final field SepaDebit Lcom/stripe/android/model/PaymentMethod$Type; public static final field Sofort Lcom/stripe/android/model/PaymentMethod$Type; + public static final field Swish Lcom/stripe/android/model/PaymentMethod$Type; public static final field USBankAccount Lcom/stripe/android/model/PaymentMethod$Type; public static final field Upi Lcom/stripe/android/model/PaymentMethod$Type; public static final field WeChatPay Lcom/stripe/android/model/PaymentMethod$Type; @@ -3968,12 +3969,13 @@ public final class com/stripe/android/model/PaymentMethodCreateParams : android/ public synthetic fun (Lcom/stripe/android/model/PaymentMethodCreateParams$Netbanking;Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun (Lcom/stripe/android/model/PaymentMethodCreateParams$SepaDebit;Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun (Lcom/stripe/android/model/PaymentMethodCreateParams$Sofort;Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Lcom/stripe/android/model/PaymentMethodCreateParams$Swish;Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun (Lcom/stripe/android/model/PaymentMethodCreateParams$USBankAccount;Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun (Lcom/stripe/android/model/PaymentMethodCreateParams$Upi;Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component15 ()Lcom/stripe/android/model/PaymentMethod$BillingDetails; + public final fun component16 ()Lcom/stripe/android/model/PaymentMethod$BillingDetails; public final fun component3 ()Lcom/stripe/android/model/PaymentMethodCreateParams$Card; - public final fun copy (Ljava/lang/String;ZLcom/stripe/android/model/PaymentMethodCreateParams$Card;Lcom/stripe/android/model/PaymentMethodCreateParams$Ideal;Lcom/stripe/android/model/PaymentMethodCreateParams$Fpx;Lcom/stripe/android/model/PaymentMethodCreateParams$SepaDebit;Lcom/stripe/android/model/PaymentMethodCreateParams$AuBecsDebit;Lcom/stripe/android/model/PaymentMethodCreateParams$BacsDebit;Lcom/stripe/android/model/PaymentMethodCreateParams$Sofort;Lcom/stripe/android/model/PaymentMethodCreateParams$Upi;Lcom/stripe/android/model/PaymentMethodCreateParams$Netbanking;Lcom/stripe/android/model/PaymentMethodCreateParams$USBankAccount;Lcom/stripe/android/model/PaymentMethodCreateParams$Link;Lcom/stripe/android/model/PaymentMethodCreateParams$CashAppPay;Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Ljava/util/Set;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; - public static synthetic fun copy$default (Lcom/stripe/android/model/PaymentMethodCreateParams;Ljava/lang/String;ZLcom/stripe/android/model/PaymentMethodCreateParams$Card;Lcom/stripe/android/model/PaymentMethodCreateParams$Ideal;Lcom/stripe/android/model/PaymentMethodCreateParams$Fpx;Lcom/stripe/android/model/PaymentMethodCreateParams$SepaDebit;Lcom/stripe/android/model/PaymentMethodCreateParams$AuBecsDebit;Lcom/stripe/android/model/PaymentMethodCreateParams$BacsDebit;Lcom/stripe/android/model/PaymentMethodCreateParams$Sofort;Lcom/stripe/android/model/PaymentMethodCreateParams$Upi;Lcom/stripe/android/model/PaymentMethodCreateParams$Netbanking;Lcom/stripe/android/model/PaymentMethodCreateParams$USBankAccount;Lcom/stripe/android/model/PaymentMethodCreateParams$Link;Lcom/stripe/android/model/PaymentMethodCreateParams$CashAppPay;Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Ljava/util/Set;Ljava/util/Map;ILjava/lang/Object;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public final fun copy (Ljava/lang/String;ZLcom/stripe/android/model/PaymentMethodCreateParams$Card;Lcom/stripe/android/model/PaymentMethodCreateParams$Ideal;Lcom/stripe/android/model/PaymentMethodCreateParams$Fpx;Lcom/stripe/android/model/PaymentMethodCreateParams$SepaDebit;Lcom/stripe/android/model/PaymentMethodCreateParams$AuBecsDebit;Lcom/stripe/android/model/PaymentMethodCreateParams$BacsDebit;Lcom/stripe/android/model/PaymentMethodCreateParams$Sofort;Lcom/stripe/android/model/PaymentMethodCreateParams$Upi;Lcom/stripe/android/model/PaymentMethodCreateParams$Netbanking;Lcom/stripe/android/model/PaymentMethodCreateParams$USBankAccount;Lcom/stripe/android/model/PaymentMethodCreateParams$Link;Lcom/stripe/android/model/PaymentMethodCreateParams$CashAppPay;Lcom/stripe/android/model/PaymentMethodCreateParams$Swish;Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Ljava/util/Set;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public static synthetic fun copy$default (Lcom/stripe/android/model/PaymentMethodCreateParams;Ljava/lang/String;ZLcom/stripe/android/model/PaymentMethodCreateParams$Card;Lcom/stripe/android/model/PaymentMethodCreateParams$Ideal;Lcom/stripe/android/model/PaymentMethodCreateParams$Fpx;Lcom/stripe/android/model/PaymentMethodCreateParams$SepaDebit;Lcom/stripe/android/model/PaymentMethodCreateParams$AuBecsDebit;Lcom/stripe/android/model/PaymentMethodCreateParams$BacsDebit;Lcom/stripe/android/model/PaymentMethodCreateParams$Sofort;Lcom/stripe/android/model/PaymentMethodCreateParams$Upi;Lcom/stripe/android/model/PaymentMethodCreateParams$Netbanking;Lcom/stripe/android/model/PaymentMethodCreateParams$USBankAccount;Lcom/stripe/android/model/PaymentMethodCreateParams$Link;Lcom/stripe/android/model/PaymentMethodCreateParams$CashAppPay;Lcom/stripe/android/model/PaymentMethodCreateParams$Swish;Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Ljava/util/Set;Ljava/util/Map;ILjava/lang/Object;)Lcom/stripe/android/model/PaymentMethodCreateParams; public static final fun create (Lcom/stripe/android/model/PaymentMethodCreateParams$AuBecsDebit;Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams; public static final fun create (Lcom/stripe/android/model/PaymentMethodCreateParams$AuBecsDebit;Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; public static final fun create (Lcom/stripe/android/model/PaymentMethodCreateParams$BacsDebit;Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams; @@ -4038,6 +4040,9 @@ public final class com/stripe/android/model/PaymentMethodCreateParams : android/ public static final fun createRevolutPay ()Lcom/stripe/android/model/PaymentMethodCreateParams; public static final fun createRevolutPay (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams; public static final fun createRevolutPay (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public static final fun createSwish ()Lcom/stripe/android/model/PaymentMethodCreateParams; + public static final fun createSwish (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public static final fun createSwish (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; public static final fun createUSBankAccount ()Lcom/stripe/android/model/PaymentMethodCreateParams; public static final fun createUSBankAccount (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams; public static final fun createUSBankAccount (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; @@ -4254,6 +4259,10 @@ public final class com/stripe/android/model/PaymentMethodCreateParams$Companion public final fun createRevolutPay (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams; public final fun createRevolutPay (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; public static synthetic fun createRevolutPay$default (Lcom/stripe/android/model/PaymentMethodCreateParams$Companion;Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;ILjava/lang/Object;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public final fun createSwish ()Lcom/stripe/android/model/PaymentMethodCreateParams; + public final fun createSwish (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public final fun createSwish (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public static synthetic fun createSwish$default (Lcom/stripe/android/model/PaymentMethodCreateParams$Companion;Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;ILjava/lang/Object;)Lcom/stripe/android/model/PaymentMethodCreateParams; public final fun createUSBankAccount ()Lcom/stripe/android/model/PaymentMethodCreateParams; public final fun createUSBankAccount (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams; public final fun createUSBankAccount (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; @@ -4414,6 +4423,23 @@ public final class com/stripe/android/model/PaymentMethodCreateParams$Sofort$Cre public synthetic fun newArray (I)[Ljava/lang/Object; } +public final class com/stripe/android/model/PaymentMethodCreateParams$Swish : android/os/Parcelable, com/stripe/android/model/StripeParamsModel { + public static final field $stable I + public static final field CREATOR Landroid/os/Parcelable$Creator; + public fun ()V + public fun describeContents ()I + public fun toParamMap ()Ljava/util/Map; + public fun writeToParcel (Landroid/os/Parcel;I)V +} + +public final class com/stripe/android/model/PaymentMethodCreateParams$Swish$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/model/PaymentMethodCreateParams$Swish; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lcom/stripe/android/model/PaymentMethodCreateParams$Swish; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + public final class com/stripe/android/model/PaymentMethodCreateParams$USBankAccount : android/os/Parcelable, com/stripe/android/model/StripeParamsModel { public static final field $stable I public static final field CREATOR Landroid/os/Parcelable$Creator; @@ -6210,6 +6236,29 @@ public final class com/stripe/android/model/StripeIntent$NextActionData$SdkData$ public synthetic fun newArray (I)[Ljava/lang/Object; } +public final class com/stripe/android/model/StripeIntent$NextActionData$SwishRedirect : com/stripe/android/model/StripeIntent$NextActionData { + public static final field $stable I + public static final field CREATOR Landroid/os/Parcelable$Creator; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/stripe/android/model/StripeIntent$NextActionData$SwishRedirect; + public static synthetic fun copy$default (Lcom/stripe/android/model/StripeIntent$NextActionData$SwishRedirect;Ljava/lang/String;ILjava/lang/Object;)Lcom/stripe/android/model/StripeIntent$NextActionData$SwishRedirect; + public fun describeContents ()I + public fun equals (Ljava/lang/Object;)Z + public final fun getMobileAuthUrl ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; + public fun writeToParcel (Landroid/os/Parcel;I)V +} + +public final class com/stripe/android/model/StripeIntent$NextActionData$SwishRedirect$Creator : android/os/Parcelable$Creator { + public fun ()V + public final fun createFromParcel (Landroid/os/Parcel;)Lcom/stripe/android/model/StripeIntent$NextActionData$SwishRedirect; + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public final fun newArray (I)[Lcom/stripe/android/model/StripeIntent$NextActionData$SwishRedirect; + public synthetic fun newArray (I)[Ljava/lang/Object; +} + public final class com/stripe/android/model/StripeIntent$NextActionData$UpiAwaitNotification : com/stripe/android/model/StripeIntent$NextActionData { public static final field $stable I public static final field CREATOR Landroid/os/Parcelable$Creator; @@ -6271,6 +6320,7 @@ public final class com/stripe/android/model/StripeIntent$NextActionType : java/l public static final field DisplayKonbiniDetails Lcom/stripe/android/model/StripeIntent$NextActionType; public static final field DisplayOxxoDetails Lcom/stripe/android/model/StripeIntent$NextActionType; public static final field RedirectToUrl Lcom/stripe/android/model/StripeIntent$NextActionType; + public static final field SwishRedirect Lcom/stripe/android/model/StripeIntent$NextActionType; public static final field UpiAwaitNotification Lcom/stripe/android/model/StripeIntent$NextActionType; public static final field UseStripeSdk Lcom/stripe/android/model/StripeIntent$NextActionType; public static final field VerifyWithMicrodeposits Lcom/stripe/android/model/StripeIntent$NextActionType; diff --git a/payments-core/detekt-baseline.xml b/payments-core/detekt-baseline.xml index 43a950f62f3..15046f32954 100644 --- a/payments-core/detekt-baseline.xml +++ b/payments-core/detekt-baseline.xml @@ -7,6 +7,7 @@ ComplexCondition:ExpiryDateEditText.kt$ExpiryDateEditText.<no name provided>$expirationDate.month.length == 2 && latestInsertionSize > 0 && !inErrorState || rawNumericInput.length > 2 ConstructorParameterNaming:Source.kt$Source$private val _klarna: Klarna? = null ConstructorParameterNaming:Source.kt$Source$private val _weChat: WeChat? = null + CyclomaticComplexMethod:NextActionDataParser.kt$NextActionDataParser$override fun parse( json: JSONObject ): StripeIntent.NextActionData? CyclomaticComplexMethod:PaymentMethodJsonParser.kt$PaymentMethodJsonParser$override fun parse(json: JSONObject): PaymentMethod CyclomaticComplexMethod:Source.kt$Source.Companion$@SourceType @JvmStatic fun asSourceType(sourceType: String?): String CyclomaticComplexMethod:SourceJsonParser.kt$SourceJsonParser.Companion$@Source.SourceType private fun asSourceType(sourceType: String?): String diff --git a/payments-core/src/main/java/com/stripe/android/StripeIntentResult.kt b/payments-core/src/main/java/com/stripe/android/StripeIntentResult.kt index 95261a09200..a906c3073bb 100644 --- a/payments-core/src/main/java/com/stripe/android/StripeIntentResult.kt +++ b/payments-core/src/main/java/com/stripe/android/StripeIntentResult.kt @@ -91,6 +91,7 @@ abstract class StripeIntentResult internal constructor( StripeIntent.NextActionType.AlipayRedirect, StripeIntent.NextActionType.WeChatPayRedirect, StripeIntent.NextActionType.CashAppRedirect, + StripeIntent.NextActionType.SwishRedirect, null -> { false } diff --git a/payments-core/src/main/java/com/stripe/android/model/PaymentIntent.kt b/payments-core/src/main/java/com/stripe/android/model/PaymentIntent.kt index 7ac2c506a1d..ed629148d70 100644 --- a/payments-core/src/main/java/com/stripe/android/model/PaymentIntent.kt +++ b/payments-core/src/main/java/com/stripe/android/model/PaymentIntent.kt @@ -189,6 +189,9 @@ constructor( is StripeIntent.NextActionData.BlikAuthorize -> { StripeIntent.NextActionType.BlikAuthorize } + is StripeIntent.NextActionData.SwishRedirect -> { + StripeIntent.NextActionType.SwishRedirect + } is StripeIntent.NextActionData.AlipayRedirect, is StripeIntent.NextActionData.WeChatPayRedirect, null -> { diff --git a/payments-core/src/main/java/com/stripe/android/model/PaymentMethod.kt b/payments-core/src/main/java/com/stripe/android/model/PaymentMethod.kt index 2eda92216a4..23ab1543eb3 100644 --- a/payments-core/src/main/java/com/stripe/android/model/PaymentMethod.kt +++ b/payments-core/src/main/java/com/stripe/android/model/PaymentMethod.kt @@ -397,6 +397,13 @@ constructor( isVoucher = true, requiresMandate = false, hasDelayedSettlement = true, + ), + Swish( + code = "swish", + isReusable = false, + isVoucher = false, + requiresMandate = false, + hasDelayedSettlement = false, ); @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // For paymentsheet diff --git a/payments-core/src/main/java/com/stripe/android/model/PaymentMethodCreateParams.kt b/payments-core/src/main/java/com/stripe/android/model/PaymentMethodCreateParams.kt index b7d11394db5..26e7e174de8 100644 --- a/payments-core/src/main/java/com/stripe/android/model/PaymentMethodCreateParams.kt +++ b/payments-core/src/main/java/com/stripe/android/model/PaymentMethodCreateParams.kt @@ -34,6 +34,7 @@ data class PaymentMethodCreateParams internal constructor( private val usBankAccount: USBankAccount? = null, private val link: Link? = null, private val cashAppPay: CashAppPay? = null, + private val swish: Swish? = null, val billingDetails: PaymentMethod.BillingDetails? = null, private val metadata: Map? = null, private val productUsage: Set = emptySet(), @@ -65,6 +66,7 @@ data class PaymentMethodCreateParams internal constructor( usBankAccount: USBankAccount? = null, link: Link? = null, cashAppPay: CashAppPay? = null, + swish: Swish? = null, billingDetails: PaymentMethod.BillingDetails? = null, metadata: Map? = null, productUsage: Set = emptySet(), @@ -84,6 +86,7 @@ data class PaymentMethodCreateParams internal constructor( usBankAccount, link, cashAppPay, + swish, billingDetails, metadata, productUsage, @@ -224,6 +227,17 @@ data class PaymentMethodCreateParams internal constructor( metadata = metadata, ) + private constructor( + swish: Swish, + billingDetails: PaymentMethod.BillingDetails?, + metadata: Map?, + ) : this( + type = PaymentMethod.Type.Swish, + swish = swish, + billingDetails = billingDetails, + metadata = metadata, + ) + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) fun requiresMandate(): Boolean { return requiresMandate @@ -493,6 +507,14 @@ data class PaymentMethodCreateParams internal constructor( override fun toParamMap(): Map = emptyMap() } + /** + * Encapsulates parameters used to create [PaymentMethodCreateParams] when using Swish. + */ + @Parcelize + class Swish : StripeParamsModel, Parcelable { + override fun toParamMap(): Map = emptyMap() + } + @Parcelize @Suppress("DataClassPrivateConstructor") data class USBankAccount private constructor( @@ -958,6 +980,19 @@ data class PaymentMethodCreateParams internal constructor( return PaymentMethodCreateParams(CashAppPay(), billingDetails, metadata) } + /** + * Helper method to create [PaymentMethodCreateParams] with [Swish] as the payment + * method type. + */ + @JvmStatic + @JvmOverloads + fun createSwish( + billingDetails: PaymentMethod.BillingDetails? = null, + metadata: Map? = null + ): PaymentMethodCreateParams { + return PaymentMethodCreateParams(Swish(), billingDetails, metadata) + } + @JvmStatic @JvmOverloads fun createRevolutPay( diff --git a/payments-core/src/main/java/com/stripe/android/model/SetupIntent.kt b/payments-core/src/main/java/com/stripe/android/model/SetupIntent.kt index e56039d803a..dd9131737d6 100644 --- a/payments-core/src/main/java/com/stripe/android/model/SetupIntent.kt +++ b/payments-core/src/main/java/com/stripe/android/model/SetupIntent.kt @@ -141,6 +141,7 @@ data class SetupIntent internal constructor( is StripeIntent.NextActionData.BlikAuthorize, is StripeIntent.NextActionData.WeChatPayRedirect, is StripeIntent.NextActionData.UpiAwaitNotification, + is StripeIntent.NextActionData.SwishRedirect, null -> { null } diff --git a/payments-core/src/main/java/com/stripe/android/model/StripeIntent.kt b/payments-core/src/main/java/com/stripe/android/model/StripeIntent.kt index c74ea449fb8..0b3898b3c0f 100644 --- a/payments-core/src/main/java/com/stripe/android/model/StripeIntent.kt +++ b/payments-core/src/main/java/com/stripe/android/model/StripeIntent.kt @@ -97,7 +97,8 @@ sealed interface StripeIntent : StripeModel { UpiAwaitNotification("upi_await_notification"), CashAppRedirect("cashapp_handle_redirect_or_display_qr_code"), DisplayBoletoDetails("boleto_display_details"), - DisplayKonbiniDetails("konbini_display_details"); + DisplayKonbiniDetails("konbini_display_details"), + SwishRedirect("swish_handle_redirect_or_display_qr_code"); @Keep override fun toString(): String { @@ -333,5 +334,13 @@ sealed interface StripeIntent : StripeModel { data class CashAppRedirect( val mobileAuthUrl: String, ) : NextActionData() + + /** + * Contains the authentication URL for redirecting your customer to Swish. + */ + @Parcelize + data class SwishRedirect( + val mobileAuthUrl: String, + ) : NextActionData() } } diff --git a/payments-core/src/main/java/com/stripe/android/model/parsers/NextActionDataParser.kt b/payments-core/src/main/java/com/stripe/android/model/parsers/NextActionDataParser.kt index e591773e621..f0762966c71 100644 --- a/payments-core/src/main/java/com/stripe/android/model/parsers/NextActionDataParser.kt +++ b/payments-core/src/main/java/com/stripe/android/model/parsers/NextActionDataParser.kt @@ -28,6 +28,7 @@ internal class NextActionDataParser : ModelJsonParser VerifyWithMicrodepositsParser() StripeIntent.NextActionType.UpiAwaitNotification -> UpiAwaitNotificationParser() StripeIntent.NextActionType.CashAppRedirect -> CashAppRedirectParser() + StripeIntent.NextActionType.SwishRedirect -> SwishRedirectParser() null -> return null } return parser.parse(json.optJSONObject(nextActionType.code) ?: JSONObject()) @@ -265,6 +266,16 @@ internal class NextActionDataParser : ModelJsonParser { + + override fun parse(json: JSONObject): StripeIntent.NextActionData.SwishRedirect { + return StripeIntent.NextActionData.SwishRedirect( + mobileAuthUrl = json.optString("mobile_auth_url"), + ) + } + } + private companion object { private const val FIELD_NEXT_ACTION_TYPE = "type" } diff --git a/payments-core/src/main/java/com/stripe/android/payments/PaymentFlowResultProcessor.kt b/payments-core/src/main/java/com/stripe/android/payments/PaymentFlowResultProcessor.kt index bd9114865fc..eb8008edc0a 100644 --- a/payments-core/src/main/java/com/stripe/android/payments/PaymentFlowResultProcessor.kt +++ b/payments-core/src/main/java/com/stripe/android/payments/PaymentFlowResultProcessor.kt @@ -155,8 +155,13 @@ internal sealed class PaymentFlowResultProcessor + is StripeIntent.NextActionData.SwishRedirect -> { + authUrl = nextActionData.mobileAuthUrl + returnUrl = defaultReturnUrl.value + shouldCancelIntentOnUserNavigation = false + } + else -> { throw IllegalArgumentException("WebAuthenticator can't process nextActionData: $nextActionData") + } } beginWebAuth( diff --git a/payments-core/src/main/java/com/stripe/android/payments/core/injection/AuthenticationModule.kt b/payments-core/src/main/java/com/stripe/android/payments/core/injection/AuthenticationModule.kt index ea06d7dc49e..8fa7870b1d5 100644 --- a/payments-core/src/main/java/com/stripe/android/payments/core/injection/AuthenticationModule.kt +++ b/payments-core/src/main/java/com/stripe/android/payments/core/injection/AuthenticationModule.kt @@ -82,6 +82,14 @@ internal abstract class AuthenticationModule { webIntentAuthenticator: WebIntentAuthenticator ): PaymentAuthenticator + @IntentAuthenticatorMap + @Binds + @IntoMap + @IntentAuthenticatorKey(NextActionData.SwishRedirect::class) + abstract fun bindsSwishRedirectAuthenticator( + webIntentAuthenticator: WebIntentAuthenticator + ): PaymentAuthenticator + companion object { @Provides @Singleton diff --git a/payments-core/src/test/java/com/stripe/android/ApiKeyFixtures.kt b/payments-core/src/test/java/com/stripe/android/ApiKeyFixtures.kt index 95a8ae13342..c67f0fbe300 100644 --- a/payments-core/src/test/java/com/stripe/android/ApiKeyFixtures.kt +++ b/payments-core/src/test/java/com/stripe/android/ApiKeyFixtures.kt @@ -30,4 +30,6 @@ internal object ApiKeyFixtures { const val CASH_APP_PAY_PUBLISHABLE_KEY = "pk_test_ErsyMEOTudSjQR8hh0VrQr5X008sBXGOu6" const val REVOLUT_PAY_PUBLISHABLE_KEY = "pk_test_51KmkHbGoesj9fw9QAZJlz1qY4dns8nFmLKc7rXiWKAIj8QU7NPFPwSY1h8mqRaFRKQ9njs9pVJoo2jhN6ZKSDA4h00mjcbGF7b" + const val SWISH_PUBLISHABLE_KEY = + "pk_test_51JtgfQKG6vc7r7YCU0qQNOkDaaHrEgeHgGKrJMNfuWwaKgXMLzPUA1f8ZlCNPonIROLOnzpUnJK1C1xFH3M3Mz8X00Q6O4GfUt" } diff --git a/payments-core/src/test/java/com/stripe/android/PaymentMethodEndToEndTest.kt b/payments-core/src/test/java/com/stripe/android/PaymentMethodEndToEndTest.kt index 39c2ba456e0..1bd0a49ec7e 100644 --- a/payments-core/src/test/java/com/stripe/android/PaymentMethodEndToEndTest.kt +++ b/payments-core/src/test/java/com/stripe/android/PaymentMethodEndToEndTest.kt @@ -456,4 +456,13 @@ internal class PaymentMethodEndToEndTest { val paymentMethod = stripe.createPaymentMethodSynchronous(params) assertThat(paymentMethod.type).isEqualTo(PaymentMethod.Type.RevolutPay) } + + @Test + fun createPaymentMethod_withSwish_shouldCreateObject() { + val params = PaymentMethodCreateParamsFixtures.SWISH + val stripe = Stripe(context, ApiKeyFixtures.SWISH_PUBLISHABLE_KEY) + + val paymentMethod = stripe.createPaymentMethodSynchronous(params) + assertThat(paymentMethod.type).isEqualTo(PaymentMethod.Type.Swish) + } } diff --git a/payments-core/src/test/java/com/stripe/android/model/PaymentMethodCreateParamsFixtures.kt b/payments-core/src/test/java/com/stripe/android/model/PaymentMethodCreateParamsFixtures.kt index 280ea38302f..fd8159c71ec 100644 --- a/payments-core/src/test/java/com/stripe/android/model/PaymentMethodCreateParamsFixtures.kt +++ b/payments-core/src/test/java/com/stripe/android/model/PaymentMethodCreateParamsFixtures.kt @@ -121,6 +121,10 @@ internal object PaymentMethodCreateParamsFixtures { billingDetails = BILLING_DETAILS, ) + internal val SWISH = PaymentMethodCreateParams.createSwish( + billingDetails = BILLING_DETAILS, + ) + @JvmStatic fun createWith(metadata: Map): PaymentMethodCreateParams { return PaymentMethodCreateParams.create( diff --git a/payments-ui-core/res/drawable/stripe_ic_paymentsheet_pm_swish.xml b/payments-ui-core/res/drawable/stripe_ic_paymentsheet_pm_swish.xml new file mode 100644 index 00000000000..d0915928339 --- /dev/null +++ b/payments-ui-core/res/drawable/stripe_ic_paymentsheet_pm_swish.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/payments-ui-core/res/values/donottranslate.xml b/payments-ui-core/res/values/donottranslate.xml index 1a25a2a43d4..dbb3f1b7b67 100644 --- a/payments-ui-core/res/values/donottranslate.xml +++ b/payments-ui-core/res/values/donottranslate.xml @@ -17,7 +17,7 @@ GrabPay FPX Bank Boleto - + Swish Affirm Revolut Pay Amazon Pay diff --git a/payments-ui-core/src/main/assets/lpms.json b/payments-ui-core/src/main/assets/lpms.json index d0476a29f34..c077fe53adc 100644 --- a/payments-ui-core/src/main/assets/lpms.json +++ b/payments-ui-core/src/main/assets/lpms.json @@ -1086,5 +1086,10 @@ "type": "konbini_confirmation_number" } ] + }, + { + "type": "swish", + "async": false, + "fields": [] } ] diff --git a/payments-ui-core/src/main/java/com/stripe/android/paymentsheet/forms/PaymentMethodRequirements.kt b/payments-ui-core/src/main/java/com/stripe/android/paymentsheet/forms/PaymentMethodRequirements.kt index 15c94da1540..cb8e5e6193c 100644 --- a/payments-ui-core/src/main/java/com/stripe/android/paymentsheet/forms/PaymentMethodRequirements.kt +++ b/payments-ui-core/src/main/java/com/stripe/android/paymentsheet/forms/PaymentMethodRequirements.kt @@ -295,3 +295,9 @@ internal val KonbiniRequirement = PaymentMethodRequirements( siRequirements = null, confirmPMFromCustomer = null, ) + +internal val SwishRequirement = PaymentMethodRequirements( + piRequirements = emptySet(), + siRequirements = null, + confirmPMFromCustomer = false, +) diff --git a/payments-ui-core/src/main/java/com/stripe/android/ui/core/forms/resources/LpmRepository.kt b/payments-ui-core/src/main/java/com/stripe/android/ui/core/forms/resources/LpmRepository.kt index a72537174bc..b2ab5ec6aba 100644 --- a/payments-ui-core/src/main/java/com/stripe/android/ui/core/forms/resources/LpmRepository.kt +++ b/payments-ui-core/src/main/java/com/stripe/android/ui/core/forms/resources/LpmRepository.kt @@ -40,6 +40,7 @@ import com.stripe.android.paymentsheet.forms.PaypalRequirement import com.stripe.android.paymentsheet.forms.RevolutPayRequirement import com.stripe.android.paymentsheet.forms.SepaDebitRequirement import com.stripe.android.paymentsheet.forms.SofortRequirement +import com.stripe.android.paymentsheet.forms.SwishRequirement import com.stripe.android.paymentsheet.forms.USBankAccountRequirement import com.stripe.android.paymentsheet.forms.UpiRequirement import com.stripe.android.paymentsheet.forms.ZipRequirement @@ -600,6 +601,17 @@ class LpmRepository constructor( requirement = KonbiniRequirement, formSpec = LayoutSpec(sharedDataSpec.fields), ) + PaymentMethod.Type.Swish.code -> SupportedPaymentMethod( + code = "swish", + requiresMandate = false, + displayNameResource = R.string.stripe_paymentsheet_payment_method_swish, + iconResource = R.drawable.stripe_ic_paymentsheet_pm_swish, + lightThemeIconUrl = sharedDataSpec.selectorIcon?.lightThemePng, + darkThemeIconUrl = sharedDataSpec.selectorIcon?.darkThemePng, + tintIconOnSelection = false, + requirement = SwishRequirement, + formSpec = LayoutSpec(sharedDataSpec.fields), + ) else -> null } diff --git a/paymentsheet-example/src/androidTest/java/com/stripe/android/lpm/TestSwish.kt b/paymentsheet-example/src/androidTest/java/com/stripe/android/lpm/TestSwish.kt new file mode 100644 index 00000000000..09c4af401b0 --- /dev/null +++ b/paymentsheet-example/src/androidTest/java/com/stripe/android/lpm/TestSwish.kt @@ -0,0 +1,35 @@ +package com.stripe.android.lpm + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.stripe.android.BaseLpmTest +import com.stripe.android.test.core.Currency +import com.stripe.android.test.core.DelayedPMs +import com.stripe.android.test.core.GooglePayState +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +internal class TestSwish : BaseLpmTest() { + + private val swishUser = newUser.copy( + paymentMethod = lpmRepository.fromCode("swish")!!, + currency = Currency.SEK, + merchantCountryCode = "FR", + delayed = DelayedPMs.On, + googlePayState = GooglePayState.Off, + ) + + @Test + fun testSwish() { + testDriver.confirmNewOrGuestComplete( + testParameters = swishUser, + ) + } + + @Test + fun testSwishInCustomFlow() { + testDriver.confirmCustom( + testParameters = swishUser, + ) + } +} diff --git a/paymentsheet-example/src/androidTest/java/com/stripe/android/test/core/TestParameters.kt b/paymentsheet-example/src/androidTest/java/com/stripe/android/test/core/TestParameters.kt index 3ad8d20856d..639875daafa 100644 --- a/paymentsheet-example/src/androidTest/java/com/stripe/android/test/core/TestParameters.kt +++ b/paymentsheet-example/src/androidTest/java/com/stripe/android/test/core/TestParameters.kt @@ -147,6 +147,7 @@ enum class Currency { BRL, MXN, JPY, + SEK, } /** diff --git a/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/activity/PaymentSheetPlaygroundActivity.kt b/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/activity/PaymentSheetPlaygroundActivity.kt index b6a5d999714..7ec2617cd35 100644 --- a/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/activity/PaymentSheetPlaygroundActivity.kt +++ b/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/activity/PaymentSheetPlaygroundActivity.kt @@ -936,7 +936,8 @@ class PaymentSheetPlaygroundActivity : AppCompatActivity() { "MYR", "MXN", "BRL", - "JPY" + "JPY", + "SEK", ) } } diff --git a/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/model/PlaygroundCheckoutModel.kt b/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/model/PlaygroundCheckoutModel.kt index ca1dcb3cc0c..028864e4e7a 100644 --- a/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/model/PlaygroundCheckoutModel.kt +++ b/paymentsheet-example/src/main/java/com/stripe/android/paymentsheet/example/playground/model/PlaygroundCheckoutModel.kt @@ -52,6 +52,7 @@ data class CheckoutCurrency(val value: String) { val AUD = CheckoutCurrency("aud") val GBP = CheckoutCurrency("gbp") val PLN = CheckoutCurrency("pln") + val SEK = CheckoutCurrency("sek") } } diff --git a/paymentsheet/src/test/resources/swish-support.csv b/paymentsheet/src/test/resources/swish-support.csv new file mode 100644 index 00000000000..b72d3555109 --- /dev/null +++ b/paymentsheet/src/test/resources/swish-support.csv @@ -0,0 +1,49 @@ +lpm, hasCustomer, allowsDelayedPayment, intentSetupFutureUsage, intentHasShipping, intentLpms, supportCustomerSavedCard, formExists, formType, supportsAdding +swish, true, true, off_session, false, card/swish, false, false, not available, false +swish, true, true, off_session, false, card/eps/swish, false, false, not available, false +swish, true, false, off_session, false, card/swish, false, false, not available, false +swish, true, false, off_session, false, card/eps/swish, false, false, not available, false +swish, true, true, on_session, false, card/swish, false, false, not available, false +swish, true, true, on_session, false, card/eps/swish, false, false, not available, false +swish, true, false, on_session, false, card/swish, false, false, not available, false +swish, true, false, on_session, false, card/eps/swish, false, false, not available, false +swish, true, true, null, false, card/swish, false, true, oneTime, true +swish, true, true, null, false, card/eps/swish, false, true, oneTime, true +swish, true, false, null, false, card/swish, false, true, oneTime, true +swish, true, false, null, false, card/eps/swish, false, true, oneTime, true +swish, false, true, off_session, false, card/swish, false, false, not available, false +swish, false, true, off_session, false, card/eps/swish, false, false, not available, false +swish, false, false, off_session, false, card/swish, false, false, not available, false +swish, false, false, off_session, false, card/eps/swish, false, false, not available, false +swish, false, true, on_session, false, card/swish, false, false, not available, false +swish, false, true, on_session, false, card/eps/swish, false, false, not available, false +swish, false, false, on_session, false, card/swish, false, false, not available, false +swish, false, false, on_session, false, card/eps/swish, false, false, not available, false +swish, false, true, null, false, card/swish, false, true, oneTime, true +swish, false, true, null, false, card/eps/swish, false, true, oneTime, true +swish, false, false, null, false, card/swish, false, true, oneTime, true +swish, false, false, null, false, card/eps/swish, false, true, oneTime, true +swish, true, true, off_session, true, card/swish, false, false, not available, false +swish, true, true, off_session, true, card/eps/swish, false, false, not available, false +swish, true, false, off_session, true, card/swish, false, false, not available, false +swish, true, false, off_session, true, card/eps/swish, false, false, not available, false +swish, true, true, on_session, true, card/swish, false, false, not available, false +swish, true, true, on_session, true, card/eps/swish, false, false, not available, false +swish, true, false, on_session, true, card/swish, false, false, not available, false +swish, true, false, on_session, true, card/eps/swish, false, false, not available, false +swish, true, true, null, true, card/swish, false, true, oneTime, true +swish, true, true, null, true, card/eps/swish, false, true, oneTime, true +swish, true, false, null, true, card/swish, false, true, oneTime, true +swish, true, false, null, true, card/eps/swish, false, true, oneTime, true +swish, false, true, off_session, true, card/swish, false, false, not available, false +swish, false, true, off_session, true, card/eps/swish, false, false, not available, false +swish, false, false, off_session, true, card/swish, false, false, not available, false +swish, false, false, off_session, true, card/eps/swish, false, false, not available, false +swish, false, true, on_session, true, card/swish, false, false, not available, false +swish, false, true, on_session, true, card/eps/swish, false, false, not available, false +swish, false, false, on_session, true, card/swish, false, false, not available, false +swish, false, false, on_session, true, card/eps/swish, false, false, not available, false +swish, false, true, null, true, card/swish, false, true, oneTime, true +swish, false, true, null, true, card/eps/swish, false, true, oneTime, true +swish, false, false, null, true, card/swish, false, true, oneTime, true +swish, false, false, null, true, card/eps/swish, false, true, oneTime, true