diff --git a/CHANGELOG.md b/CHANGELOG.md index 32d2c71d1a3..0f95c775398 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## XX.XX.XX - 20XX-XX-XX +### Payments +* [ADDED][9753](https://github.com/stripe/stripe-android/pull/9753) Added support for Crypto to API bindings and payment sheet. + ## 21.2.1 - 2024-12-02 ### PaymentSheet - [FIXED][9695](https://github.com/stripe/stripe-android/pull/9695) US Bank Account now supports the `instant_or_skip` verification method. diff --git a/example/AndroidManifest.xml b/example/AndroidManifest.xml index 4d1579c6eb8..fbf37d1d421 100644 --- a/example/AndroidManifest.xml +++ b/example/AndroidManifest.xml @@ -83,6 +83,7 @@ + diff --git a/example/res/values/strings.xml b/example/res/values/strings.xml index dd4aeac73b2..ff8bb1adb0f 100644 --- a/example/res/values/strings.xml +++ b/example/res/values/strings.xml @@ -39,6 +39,7 @@ Sunbit Billie Satispay + Crypto Swish MobilePay Card Brands diff --git a/example/src/main/java/com/stripe/example/activity/CryptoActivity.kt b/example/src/main/java/com/stripe/example/activity/CryptoActivity.kt new file mode 100644 index 00000000000..4fce262d190 --- /dev/null +++ b/example/src/main/java/com/stripe/example/activity/CryptoActivity.kt @@ -0,0 +1,90 @@ +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.PaymentMethod +import com.stripe.android.model.PaymentMethodCreateParams + +class CryptoActivity : 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 = "") + + CryptoPayScreen( + isProcessing = isProcessing, + status = status, + onButtonPressed = { payWithCrypto() } + ) + } + } + + private fun payWithCrypto() { + val params = PaymentMethodCreateParams.createCrypto() + createAndConfirmPaymentIntent( + country = "US", + paymentMethodCreateParams = params, + supportedPaymentMethods = PaymentMethod.Type.Crypto.code, + currency = "USD", + ) + } +} + +@Composable +private fun CryptoPayScreen( + 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 Crypto") + } + + 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/example/src/main/java/com/stripe/example/activity/LauncherActivity.kt b/example/src/main/java/com/stripe/example/activity/LauncherActivity.kt index 4164aa08c6d..e594106925a 100644 --- a/example/src/main/java/com/stripe/example/activity/LauncherActivity.kt +++ b/example/src/main/java/com/stripe/example/activity/LauncherActivity.kt @@ -193,6 +193,10 @@ class LauncherActivity : AppCompatActivity() { activity.getString(R.string.satispay_example), SatispayActivity::class.java ), + Item( + activity.getString(R.string.crypto_example), + CryptoActivity::class.java + ), // This is for internal use so as not to confuse the user. Item( "StripeImage Example", diff --git a/payments-core/api/payments-core.api b/payments-core/api/payments-core.api index b8efb6f9a1d..dcd722f71d6 100644 --- a/payments-core/api/payments-core.api +++ b/payments-core/api/payments-core.api @@ -3526,6 +3526,7 @@ public final class com/stripe/android/model/PaymentMethod$Type : java/lang/Enum, public static final field CardPresent Lcom/stripe/android/model/PaymentMethod$Type; public static final field CashAppPay Lcom/stripe/android/model/PaymentMethod$Type; public static final field Companion Lcom/stripe/android/model/PaymentMethod$Type$Companion; + public static final field Crypto Lcom/stripe/android/model/PaymentMethod$Type; public static final field Eps Lcom/stripe/android/model/PaymentMethod$Type; public static final field Fpx Lcom/stripe/android/model/PaymentMethod$Type; public static final field Giropay Lcom/stripe/android/model/PaymentMethod$Type; @@ -3797,6 +3798,10 @@ public final class com/stripe/android/model/PaymentMethodCreateParams : android/ public static final fun createCashAppPay (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams; public static final fun createCashAppPay (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; public static final fun createCashAppPay (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Lcom/stripe/android/model/PaymentMethod$AllowRedisplay;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public static final fun createCrypto ()Lcom/stripe/android/model/PaymentMethodCreateParams; + public static final fun createCrypto (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public static final fun createCrypto (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public static final fun createCrypto (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Lcom/stripe/android/model/PaymentMethod$AllowRedisplay;)Lcom/stripe/android/model/PaymentMethodCreateParams; public static final fun createEps (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams; public static final fun createEps (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; public static final fun createEps (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Lcom/stripe/android/model/PaymentMethod$AllowRedisplay;)Lcom/stripe/android/model/PaymentMethodCreateParams; @@ -4091,6 +4096,11 @@ public final class com/stripe/android/model/PaymentMethodCreateParams$Companion public final fun createCashAppPay (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; public final fun createCashAppPay (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Lcom/stripe/android/model/PaymentMethod$AllowRedisplay;)Lcom/stripe/android/model/PaymentMethodCreateParams; public static synthetic fun createCashAppPay$default (Lcom/stripe/android/model/PaymentMethodCreateParams$Companion;Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Lcom/stripe/android/model/PaymentMethod$AllowRedisplay;ILjava/lang/Object;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public final fun createCrypto ()Lcom/stripe/android/model/PaymentMethodCreateParams; + public final fun createCrypto (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public final fun createCrypto (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public final fun createCrypto (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Lcom/stripe/android/model/PaymentMethod$AllowRedisplay;)Lcom/stripe/android/model/PaymentMethodCreateParams; + public static synthetic fun createCrypto$default (Lcom/stripe/android/model/PaymentMethodCreateParams$Companion;Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Lcom/stripe/android/model/PaymentMethod$AllowRedisplay;ILjava/lang/Object;)Lcom/stripe/android/model/PaymentMethodCreateParams; public final fun createEps (Lcom/stripe/android/model/PaymentMethod$BillingDetails;)Lcom/stripe/android/model/PaymentMethodCreateParams; public final fun createEps (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;)Lcom/stripe/android/model/PaymentMethodCreateParams; public final fun createEps (Lcom/stripe/android/model/PaymentMethod$BillingDetails;Ljava/util/Map;Lcom/stripe/android/model/PaymentMethod$AllowRedisplay;)Lcom/stripe/android/model/PaymentMethodCreateParams; 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 ee2697edd06..57c297267f1 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 @@ -383,6 +383,13 @@ constructor( requiresMandate = false, hasDelayedSettlement = false, ), + Crypto( + "crypto", + isReusable = false, + isVoucher = false, + requiresMandate = false, + hasDelayedSettlement = false, + ), AmazonPay( "amazon_pay", isReusable = false, 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 9a1e2124fcc..86a88c53dcd 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 @@ -1241,6 +1241,25 @@ constructor( ) } + /** + * Helper method to create [PaymentMethodCreateParams] with [PaymentMethod.Type.Crypto] as the payment + * method type + */ + @JvmStatic + @JvmOverloads + fun createCrypto( + billingDetails: PaymentMethod.BillingDetails? = null, + metadata: Map? = null, + allowRedisplay: PaymentMethod.AllowRedisplay? = null, + ): PaymentMethodCreateParams { + return PaymentMethodCreateParams( + type = PaymentMethod.Type.Crypto, + billingDetails = billingDetails, + metadata = metadata, + allowRedisplay = allowRedisplay, + ) + } + /** * Helper method to create [PaymentMethodCreateParams] with [Swish] as the payment * method type. 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 35e5018a94d..b4c3be35a62 100644 --- a/payments-core/src/test/java/com/stripe/android/ApiKeyFixtures.kt +++ b/payments-core/src/test/java/com/stripe/android/ApiKeyFixtures.kt @@ -45,4 +45,6 @@ internal object ApiKeyFixtures { "pk_test_51PSnNaAlz2yHYCNZgjajit4L8Hl1rDDPPCj9XhHNZWRSi4vwHhrHIbTgstLJptPSzwQVl1HlyqhwWRs1rBJHag8W00sM0SOXIL" const val SATISPAY_PUBLISHABLE_KEY = "pk_test_51PSnETIFbdis1OxTALF4Z8ugUQpVS06UQDVahMSmwrbEYphjNYitXtOSqMPVKfzl3jukg6gLLrtZNnPlDrRbDpMd00U0tId6iv" + const val CRYPTO_PUBLISHABLE_KEY = + "pk_test_51HvTI7Lu5o3P18Zp6t5AgBSkMvWoTtA0nyA7pVYDqpfLkRtWun7qZTYCOHCReprfLM464yaBeF72UFfB7cY9WG4a00ZnDtiC2C" } 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 8d4b039ed75..f3cec8ae299 100644 --- a/payments-core/src/test/java/com/stripe/android/PaymentMethodEndToEndTest.kt +++ b/payments-core/src/test/java/com/stripe/android/PaymentMethodEndToEndTest.kt @@ -510,6 +510,15 @@ internal class PaymentMethodEndToEndTest { assertThat(paymentMethod.type).isEqualTo(PaymentMethod.Type.Satispay) } + @Test + fun createPaymentMethod_withCrypto_shouldCreateObject() { + val params = PaymentMethodCreateParamsFixtures.CRYPTO + val stripe = Stripe(context, ApiKeyFixtures.CRYPTO_PUBLISHABLE_KEY) + + val paymentMethod = stripe.createPaymentMethodSynchronous(params) + assertThat(paymentMethod.type).isEqualTo(PaymentMethod.Type.Crypto) + } + @Test fun createPaymentMethod_withMultibanco_shouldCreateObject() { val params = PaymentMethodCreateParamsFixtures.MULTIBANCO 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 1abb00e7c3f..88079398b69 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 @@ -146,6 +146,10 @@ internal object PaymentMethodCreateParamsFixtures { billingDetails = BILLING_DETAILS ) + internal val CRYPTO = PaymentMethodCreateParams.createCrypto( + billingDetails = BILLING_DETAILS + ) + @JvmStatic fun createWith(metadata: Map): PaymentMethodCreateParams { return PaymentMethodCreateParams.create( diff --git a/payments-ui-core/res/drawable-night/stripe_ic_paymentsheet_pm_crypto.xml b/payments-ui-core/res/drawable-night/stripe_ic_paymentsheet_pm_crypto.xml new file mode 100644 index 00000000000..8aecee752e6 --- /dev/null +++ b/payments-ui-core/res/drawable-night/stripe_ic_paymentsheet_pm_crypto.xml @@ -0,0 +1,10 @@ + + + diff --git a/payments-ui-core/res/drawable/stripe_ic_paymentsheet_pm_crypto.xml b/payments-ui-core/res/drawable/stripe_ic_paymentsheet_pm_crypto.xml new file mode 100644 index 00000000000..8a4b32046fb --- /dev/null +++ b/payments-ui-core/res/drawable/stripe_ic_paymentsheet_pm_crypto.xml @@ -0,0 +1,10 @@ + + + diff --git a/payments-ui-core/res/values/donottranslate.xml b/payments-ui-core/res/values/donottranslate.xml index 9debbe5c71e..ad3f6347dab 100644 --- a/payments-ui-core/res/values/donottranslate.xml +++ b/payments-ui-core/res/values/donottranslate.xml @@ -25,6 +25,7 @@ Sunbit Billie Satispay + Crypto Alma MobilePay Zip diff --git a/payments-ui-core/src/main/resources/lpms.json b/payments-ui-core/src/main/resources/lpms.json index d2452f1252e..2d90fd069e0 100644 --- a/payments-ui-core/src/main/resources/lpms.json +++ b/payments-ui-core/src/main/resources/lpms.json @@ -917,6 +917,33 @@ } } }, + { + "type": "crypto", + "async": false, + "selector_icon": { + "light_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-crypto@3x-94c06c199e78e6d9ff9290210912bd5e.png", + "light_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-crypto-15fd4ffeafd1b13e40688c8a06d79ba4.svg", + "dark_theme_png": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-crypto_dark@3x-8f7b0e91b45cb56de550af37d41aac1d.png", + "dark_theme_svg": "https://js.stripe.com/v3/fingerprinted/img/payment-methods/icon-pm-crypto_dark-f19bb5c5400c6cde94dd53b7f1ce7217.svg" + }, + "fields": [ + ], + "next_action_spec": { + "confirm_response_status_specs": { + "requires_action": { + "type": "redirect_to_url" + } + }, + "post_confirm_handling_pi_status_specs": { + "succeeded": { + "type": "finished" + }, + "requires_action": { + "type": "canceled" + } + } + } + }, { "type": "mobilepay", "async": false, diff --git a/paymentsheet-example/src/androidTest/java/com/stripe/android/lpm/TestCrypto.kt b/paymentsheet-example/src/androidTest/java/com/stripe/android/lpm/TestCrypto.kt new file mode 100644 index 00000000000..f6411df0995 --- /dev/null +++ b/paymentsheet-example/src/androidTest/java/com/stripe/android/lpm/TestCrypto.kt @@ -0,0 +1,33 @@ +package com.stripe.android.lpm + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.stripe.android.BasePlaygroundTest +import com.stripe.android.model.PaymentMethod +import com.stripe.android.paymentsheet.example.playground.settings.Country +import com.stripe.android.paymentsheet.example.playground.settings.CountrySettingsDefinition +import com.stripe.android.paymentsheet.example.playground.settings.Currency +import com.stripe.android.paymentsheet.example.playground.settings.CurrencySettingsDefinition +import com.stripe.android.paymentsheet.example.playground.settings.SupportedPaymentMethodsSettingsDefinition +import com.stripe.android.test.core.TestParameters +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +internal class TestCrypto : BasePlaygroundTest() { + private val testParameters = TestParameters.create( + paymentMethodCode = "crypto", + ) { settings -> + settings[CountrySettingsDefinition] = Country.US + settings[CurrencySettingsDefinition] = Currency.USD + settings[SupportedPaymentMethodsSettingsDefinition] = listOf( + PaymentMethod.Type.Crypto + ).joinToString(",") + } + + @Test + fun testCrypto() { + testDriver.confirmCustom( + testParameters = testParameters, + ) + } +} diff --git a/paymentsheet/src/main/java/com/stripe/android/lpmfoundations/paymentmethod/PaymentMethodRegistry.kt b/paymentsheet/src/main/java/com/stripe/android/lpmfoundations/paymentmethod/PaymentMethodRegistry.kt index 60bf919105b..ed1e22f7a08 100644 --- a/paymentsheet/src/main/java/com/stripe/android/lpmfoundations/paymentmethod/PaymentMethodRegistry.kt +++ b/paymentsheet/src/main/java/com/stripe/android/lpmfoundations/paymentmethod/PaymentMethodRegistry.kt @@ -13,6 +13,7 @@ import com.stripe.android.lpmfoundations.paymentmethod.definitions.BlikDefinitio import com.stripe.android.lpmfoundations.paymentmethod.definitions.BoletoDefinition import com.stripe.android.lpmfoundations.paymentmethod.definitions.CardDefinition import com.stripe.android.lpmfoundations.paymentmethod.definitions.CashAppPayDefinition +import com.stripe.android.lpmfoundations.paymentmethod.definitions.CryptoDefinition import com.stripe.android.lpmfoundations.paymentmethod.definitions.EpsDefinition import com.stripe.android.lpmfoundations.paymentmethod.definitions.FpxDefinition import com.stripe.android.lpmfoundations.paymentmethod.definitions.GiroPayDefinition @@ -54,6 +55,7 @@ internal object PaymentMethodRegistry { BoletoDefinition, CardDefinition, CashAppPayDefinition, + CryptoDefinition, EpsDefinition, FpxDefinition, GiroPayDefinition, diff --git a/paymentsheet/src/main/java/com/stripe/android/lpmfoundations/paymentmethod/definitions/CryptoDefinition.kt b/paymentsheet/src/main/java/com/stripe/android/lpmfoundations/paymentmethod/definitions/CryptoDefinition.kt new file mode 100644 index 00000000000..fd5565ac24c --- /dev/null +++ b/paymentsheet/src/main/java/com/stripe/android/lpmfoundations/paymentmethod/definitions/CryptoDefinition.kt @@ -0,0 +1,35 @@ +package com.stripe.android.lpmfoundations.paymentmethod.definitions + +import com.stripe.android.lpmfoundations.luxe.SupportedPaymentMethod +import com.stripe.android.lpmfoundations.paymentmethod.AddPaymentMethodRequirement +import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodDefinition +import com.stripe.android.lpmfoundations.paymentmethod.PaymentMethodMetadata +import com.stripe.android.lpmfoundations.paymentmethod.UiDefinitionFactory +import com.stripe.android.model.PaymentMethod +import com.stripe.android.ui.core.R +import com.stripe.android.ui.core.elements.SharedDataSpec + +internal object CryptoDefinition : PaymentMethodDefinition { + override val type: PaymentMethod.Type = PaymentMethod.Type.Crypto + + override val supportedAsSavedPaymentMethod: Boolean = false + + override fun requirementsToBeUsedAsNewPaymentMethod( + hasIntentToSetup: Boolean + ): Set = setOf( + AddPaymentMethodRequirement.UnsupportedForSetup, + ) + + override fun requiresMandate(metadata: PaymentMethodMetadata): Boolean = false + + override fun uiDefinitionFactory(): UiDefinitionFactory = CryptoUiDefinitionFactory +} + +private object CryptoUiDefinitionFactory : UiDefinitionFactory.RequiresSharedDataSpec { + override fun createSupportedPaymentMethod(sharedDataSpec: SharedDataSpec) = SupportedPaymentMethod( + paymentMethodDefinition = CryptoDefinition, + sharedDataSpec = sharedDataSpec, + displayNameResource = R.string.stripe_paymentsheet_payment_method_crypto, + iconResource = R.drawable.stripe_ic_paymentsheet_pm_crypto, + ) +}