Skip to content

Commit

Permalink
[go] update stripe-react-native to 0.27.2 (expo#22917)
Browse files Browse the repository at this point in the history
# Why

update vendoring modules for sdk 49

# How

- `et uvm -m @stripe/stripe-react-native -c 'v0.27.2'`
- backport native stripe version to old sdks' podspec and build.gradle
- transform manually
  - [android] remove `GooglePayButtonManager.kt`, `GooglePayButtonView.kt`
  - [android] remove `GooglePayButtonManager()` and `AddToWalletButtonManager()` in `StripeSdkPackage.kt`
  - [android] revert `import host.exp.expoview.R` change in `CardFormView.kt`
  - [ios] remove `stripe-react-native/ios/Tests/**/*` test files

# Test Plan

1. clone and setup [stripe example app](https://github.com/stripe/stripe-react-native#run-the-example-app)
2. in `example/`
    - `yarn add file:/path/to/expo/packages/expo`
    - yarn add for `[email protected]`, `[email protected]` and other unsynced packages
    - and other necessary changes. i'd shared my patch file here: https://gist.github.com/Kudo/1d976feb403c325828b61b301bea3276
3. install unversioned expo go to simulator/emulator
4. modify the stripe cloned repo's maestro e2e test cases
    - cd `e2e-tests`
    - modify the ***.yml** file you want to test and replace `- launchApp` to

    ```yml
    - stopApp
    - openLink: exp://127.0.0.1:19000
    ```
    - replace the 127.0.0.1:19000 to the local dev-server address
5. run maestro test, e.g. `maestro --device {device_id} test -e APP_ID=host.exp.Exponent cardfield-basic.yml`
6. i only went through these test cases
    - cardfield-basic.yml
    - paymentsheet-basic.yml
    - paymentsheet-customFlow.yml
  • Loading branch information
Kudo authored Jun 19, 2023
1 parent 7980d26 commit 2ad44b9
Show file tree
Hide file tree
Showing 38 changed files with 755 additions and 345 deletions.
4 changes: 2 additions & 2 deletions android/expoview/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,8 @@ dependencies {
api 'com.github.troZee:ViewPager2:v1.0.6'

// stripe-react-native
implementation('com.stripe:stripe-android:20.19.+')
implementation('com.stripe:financial-connections:20.19.+')
implementation('com.stripe:stripe-android:20.25.+')
implementation('com.stripe:financial-connections:20.25.+')
compileOnly 'com.stripe:stripe-android-issuing-push-provisioning:1.1.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0'
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
import versioned.host.exp.exponent.modules.api.components.reactnativestripesdk.utils.getIntOrNull
import versioned.host.exp.exponent.modules.api.components.reactnativestripesdk.utils.getValOr
import com.stripe.android.databinding.BecsDebitWidgetBinding
import com.stripe.android.databinding.StripeBecsDebitWidgetBinding
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.view.BecsDebitWidget
import com.stripe.android.view.StripeEditText
Expand All @@ -35,7 +35,7 @@ class AuBECSDebitFormView(private val context: ThemedReactContext) : FrameLayout
if (!this::becsDebitWidget.isInitialized || value == null) {
return
}
val binding = BecsDebitWidgetBinding.bind(becsDebitWidget)
val binding = StripeBecsDebitWidgetBinding.bind(becsDebitWidget)
val textColor = getValOr(value, "textColor", null)
val textErrorColor = getValOr(value, "textErrorColor", null)
val placeholderColor = getValOr(value, "placeholderColor", null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import versioned.host.exp.exponent.modules.api.components.reactnativestripesdk.u
import versioned.host.exp.exponent.modules.api.components.reactnativestripesdk.utils.mapCardBrand
import com.stripe.android.core.model.CountryCode
import com.stripe.android.core.model.CountryUtils
import com.stripe.android.databinding.CardInputWidgetBinding
import com.stripe.android.databinding.StripeCardInputWidgetBinding
import com.stripe.android.model.Address
import com.stripe.android.model.PaymentMethodCreateParams
import com.stripe.android.view.CardInputListener
Expand All @@ -32,7 +32,7 @@ import com.stripe.android.view.StripeEditText

class CardFieldView(context: ThemedReactContext) : FrameLayout(context) {
private var mCardWidget: CardInputWidget = CardInputWidget(context)
private val cardInputWidgetBinding = CardInputWidgetBinding.bind(mCardWidget)
private val cardInputWidgetBinding = StripeCardInputWidgetBinding.bind(mCardWidget)
val cardDetails: MutableMap<String, Any?> = mutableMapOf("brand" to "", "last4" to "", "expiryMonth" to null, "expiryYear" to null, "postalCode" to "", "validNumber" to "Unknown", "validCVC" to "Unknown", "validExpiryDate" to "Unknown")
var cardParams: PaymentMethodCreateParams.Card? = null
var cardAddress: Address? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import com.google.android.material.shape.ShapeAppearanceModel
import versioned.host.exp.exponent.modules.api.components.reactnativestripesdk.utils.*
import versioned.host.exp.exponent.modules.api.components.reactnativestripesdk.utils.mapCardBrand
import com.stripe.android.core.model.CountryCode
import com.stripe.android.databinding.CardMultilineWidgetBinding
import com.stripe.android.databinding.StripeCardMultilineWidgetBinding
import com.stripe.android.databinding.StripeCardFormViewBinding
import com.stripe.android.model.Address
import com.stripe.android.model.PaymentMethodCreateParams
Expand All @@ -36,7 +36,7 @@ class CardFormView(context: ThemedReactContext) : FrameLayout(context) {
var cardParams: PaymentMethodCreateParams.Card? = null
var cardAddress: Address? = null
private val cardFormViewBinding = StripeCardFormViewBinding.bind(cardForm)
private val multilineWidgetBinding = CardMultilineWidgetBinding.bind(cardFormViewBinding.cardMultilineWidget)
private val multilineWidgetBinding = StripeCardMultilineWidgetBinding.bind(cardFormViewBinding.cardMultilineWidget)

init {
cardFormViewBinding.cardMultilineWidgetContainer.isFocusable = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class PaymentMethodCreateParamsFactory(
PaymentMethod.Type.USBankAccount -> createUSBankAccountParams(paymentMethodData)
PaymentMethod.Type.PayPal -> createPayPalParams()
PaymentMethod.Type.Affirm -> createAffirmParams()
PaymentMethod.Type.CashAppPay -> createCashAppParams()
else -> {
throw Exception("This paymentMethodType is not supported yet")
}
Expand Down Expand Up @@ -202,13 +203,17 @@ class PaymentMethodCreateParamsFactory(
return PaymentMethodCreateParams.createAffirm(billingDetailsParams)
}

@Throws(PaymentMethodCreateParamsException::class)
private fun createCashAppParams(): PaymentMethodCreateParams {
return PaymentMethodCreateParams.createCashAppPay(billingDetailsParams)
}

@Throws(PaymentMethodCreateParamsException::class)
fun createParams(clientSecret: String, paymentMethodType: PaymentMethod.Type?, isPaymentIntent: Boolean): ConfirmStripeIntentParams {
try {
return when (paymentMethodType) {
PaymentMethod.Type.Card -> createCardStripeIntentParams(clientSecret, isPaymentIntent)
PaymentMethod.Type.USBankAccount -> createUSBankAccountStripeIntentParams(clientSecret, isPaymentIntent)
PaymentMethod.Type.PayPal -> createPayPalStripeIntentParams(clientSecret, isPaymentIntent)
PaymentMethod.Type.Affirm -> createAffirmStripeIntentParams(clientSecret, isPaymentIntent)
PaymentMethod.Type.Ideal,
PaymentMethod.Type.Alipay,
Expand All @@ -223,7 +228,9 @@ class PaymentMethodCreateParamsFactory(
PaymentMethod.Type.Fpx,
PaymentMethod.Type.AfterpayClearpay,
PaymentMethod.Type.AuBecsDebit,
PaymentMethod.Type.Klarna -> {
PaymentMethod.Type.Klarna,
PaymentMethod.Type.PayPal,
PaymentMethod.Type.CashAppPay -> {
val params = createPaymentMethodParams(paymentMethodType)

return if (isPaymentIntent) {
Expand All @@ -232,11 +239,13 @@ class PaymentMethodCreateParamsFactory(
paymentMethodCreateParams = params,
clientSecret = clientSecret,
setupFutureUsage = mapToPaymentIntentFutureUsage(getValOr(options, "setupFutureUsage")),
mandateData = buildMandateDataParams()
)
} else {
ConfirmSetupIntentParams.create(
paymentMethodCreateParams = params,
clientSecret = clientSecret,
mandateData = buildMandateDataParams()
)
}
}
Expand Down Expand Up @@ -339,20 +348,6 @@ class PaymentMethodCreateParamsFactory(
}
}

@Throws(PaymentMethodCreateParamsException::class)
private fun createPayPalStripeIntentParams(clientSecret: String, isPaymentIntent: Boolean): ConfirmStripeIntentParams {
if (!isPaymentIntent) {
throw PaymentMethodCreateParamsException("PayPal is not yet supported through SetupIntents.")
}

val params = createPayPalParams()

return ConfirmPaymentIntentParams.createWithPaymentMethodCreateParams(
paymentMethodCreateParams = params,
clientSecret = clientSecret,
)
}

@Throws(PaymentMethodCreateParamsException::class)
private fun createAffirmStripeIntentParams(clientSecret: String, isPaymentIntent: Boolean): ConfirmStripeIntentParams {
if (!isPaymentIntent) {
Expand All @@ -366,6 +361,7 @@ class PaymentMethodCreateParamsFactory(
paymentMethodCreateParams = params,
clientSecret = clientSecret,
setupFutureUsage = mapToPaymentIntentFutureUsage(getValOr(options, "setupFutureUsage")),
mandateData = buildMandateDataParams()
)
}

Expand Down Expand Up @@ -401,6 +397,20 @@ class PaymentMethodCreateParamsFactory(
null
)
}

private fun buildMandateDataParams(): MandateDataParams? {
getMapOrNull(paymentMethodData, "mandateData")?.let { mandateData ->
getMapOrNull(mandateData, "customerAcceptance")?.let { customerAcceptance ->
getMapOrNull(customerAcceptance, "online")?.let { onlineParams ->
return MandateDataParams(MandateDataParams.Type.Online(
ipAddress = getValOr(onlineParams, "ipAddress", "") ?: "",
userAgent = getValOr(onlineParams, "userAgent", "") ?: "",
))
}
}
}
return null
}
}

class PaymentMethodCreateParamsException(message: String) : Exception(message)
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package versioned.host.exp.exponent.modules.api.components.reactnativestripesdk

import android.app.Activity
import android.app.Application
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Base64
import android.view.LayoutInflater
import android.view.View
Expand All @@ -21,10 +25,7 @@ import versioned.host.exp.exponent.modules.api.components.reactnativestripesdk.a
import versioned.host.exp.exponent.modules.api.components.reactnativestripesdk.utils.*
import versioned.host.exp.exponent.modules.api.components.reactnativestripesdk.utils.createError
import versioned.host.exp.exponent.modules.api.components.reactnativestripesdk.utils.createResult
import com.stripe.android.paymentsheet.PaymentOptionCallback
import com.stripe.android.paymentsheet.PaymentSheet
import com.stripe.android.paymentsheet.PaymentSheetResult
import com.stripe.android.paymentsheet.PaymentSheetResultCallback
import com.stripe.android.paymentsheet.*
import java.io.ByteArrayOutputStream

class PaymentSheetFragment(
Expand All @@ -38,6 +39,7 @@ class PaymentSheetFragment(
private lateinit var paymentSheetConfiguration: PaymentSheet.Configuration
private var confirmPromise: Promise? = null
private var presentPromise: Promise? = null
private var paymentSheetTimedOut = false

override fun onCreateView(
inflater: LayoutInflater,
Expand All @@ -62,6 +64,7 @@ class PaymentSheetFragment(
val googlePayConfig = buildGooglePayConfig(arguments?.getBundle("googlePay"))
val allowsDelayedPaymentMethods = arguments?.getBoolean("allowsDelayedPaymentMethods")
val billingDetailsBundle = arguments?.getBundle("defaultBillingDetails")
val billingConfigParams = arguments?.getBundle("billingDetailsCollectionConfiguration")
paymentIntentClientSecret = arguments?.getString("paymentIntentClientSecret").orEmpty()
setupIntentClientSecret = arguments?.getString("setupIntentClientSecret").orEmpty()
val appearance = try {
Expand All @@ -84,27 +87,48 @@ class PaymentSheetFragment(
option.putString("image", imageString)
createResult("paymentOption", option)
} ?: run {
createError(PaymentSheetErrorType.Canceled.toString(), "The payment option selection flow has been canceled")
if (paymentSheetTimedOut) {
paymentSheetTimedOut = false
createError(PaymentSheetErrorType.Timeout.toString(), "The payment has timed out")
} else {
createError(PaymentSheetErrorType.Canceled.toString(), "The payment option selection flow has been canceled")
}
}
presentPromise?.resolve(result)
}

val paymentResultCallback = PaymentSheetResultCallback { paymentResult ->
when (paymentResult) {
is PaymentSheetResult.Canceled -> {
resolvePaymentResult(createError(PaymentSheetErrorType.Canceled.toString(), "The payment flow has been canceled"))
}
is PaymentSheetResult.Failed -> {
resolvePaymentResult(createError(PaymentSheetErrorType.Failed.toString(), paymentResult.error))
}
is PaymentSheetResult.Completed -> {
resolvePaymentResult(WritableNativeMap())
// Remove the fragment now, we can be sure it won't be needed again if an intent is successful
removeFragment(context)
if (paymentSheetTimedOut) {
paymentSheetTimedOut = false
resolvePaymentResult(createError(PaymentSheetErrorType.Timeout.toString(), "The payment has timed out"))
} else {
when (paymentResult) {
is PaymentSheetResult.Canceled -> {
resolvePaymentResult(createError(PaymentSheetErrorType.Canceled.toString(), "The payment flow has been canceled"))
}
is PaymentSheetResult.Failed -> {
resolvePaymentResult(createError(PaymentSheetErrorType.Failed.toString(), paymentResult.error))
}
is PaymentSheetResult.Completed -> {
resolvePaymentResult(WritableNativeMap())
// Remove the fragment now, we can be sure it won't be needed again if an intent is successful
removeFragment(context)
paymentSheet = null
flowController = null
}
}
}
}

val billingDetailsConfig = PaymentSheet.BillingDetailsCollectionConfiguration(
name = mapToCollectionMode(billingConfigParams?.getString("name")),
phone = mapToCollectionMode(billingConfigParams?.getString("phone")),
email = mapToCollectionMode(billingConfigParams?.getString("email")),
address = mapToAddressCollectionMode(billingConfigParams?.getString("address")),
attachDefaultsToPaymentMethod = billingConfigParams?.getBoolean("attachDefaultsToPaymentMethod")
?: false
)

var defaultBillingDetails: PaymentSheet.BillingDetails? = null
if (billingDetailsBundle != null) {
val addressBundle = billingDetailsBundle.getBundle("address")
Expand Down Expand Up @@ -133,7 +157,8 @@ class PaymentSheetFragment(
googlePay = googlePayConfig,
appearance = appearance,
shippingDetails = shippingDetails,
primaryButtonLabel = primaryButtonLabel
primaryButtonLabel = primaryButtonLabel,
billingDetailsCollectionConfiguration = billingDetailsConfig
)

if (arguments?.getBoolean("customFlow") == true) {
Expand All @@ -155,7 +180,45 @@ class PaymentSheetFragment(
}
} else if(flowController != null) {
flowController?.presentPaymentOptions()
} else {
promise.resolve(createMissingInitError())
}
}

fun presentWithTimeout(timeout: Long, promise: Promise) {
var paymentSheetActivity: Activity? = null

val activityLifecycleCallbacks = object : Application.ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
paymentSheetActivity = activity
}

override fun onActivityStarted(activity: Activity) {}

override fun onActivityResumed(activity: Activity) {}

override fun onActivityPaused(activity: Activity) {}

override fun onActivityStopped(activity: Activity) {}

override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}

override fun onActivityDestroyed(activity: Activity) {
paymentSheetActivity = null
context.currentActivity?.application?.unregisterActivityLifecycleCallbacks(this)
}
}

Handler(Looper.getMainLooper()).postDelayed({
paymentSheetActivity?.let {
it.finish()
paymentSheetTimedOut = true
}
}, timeout)

context.currentActivity?.application?.registerActivityLifecycleCallbacks(activityLifecycleCallbacks)

this.present(promise)
}

fun confirmPayment(promise: Promise) {
Expand Down Expand Up @@ -205,6 +268,10 @@ class PaymentSheetFragment(
companion object {
internal const val TAG = "payment_sheet_launch_fragment"

internal fun createMissingInitError(): WritableMap {
return createError(PaymentSheetErrorType.Failed.toString(), "No payment sheet has been initialized yet. You must call `initPaymentSheet` before `presentPaymentSheet`.")
}

internal fun buildGooglePayConfig(params: Bundle?): PaymentSheet.GooglePayConfiguration? {
if (params == null) {
return null
Expand Down Expand Up @@ -244,3 +311,21 @@ fun getBase64FromBitmap(bitmap: Bitmap?): String? {
val imageBytes: ByteArray = stream.toByteArray()
return Base64.encodeToString(imageBytes, Base64.DEFAULT)
}

fun mapToCollectionMode(str: String?): PaymentSheet.BillingDetailsCollectionConfiguration.CollectionMode {
return when (str) {
"automatic" -> PaymentSheet.BillingDetailsCollectionConfiguration.CollectionMode.Automatic
"never" -> PaymentSheet.BillingDetailsCollectionConfiguration.CollectionMode.Never
"always" -> PaymentSheet.BillingDetailsCollectionConfiguration.CollectionMode.Always
else -> PaymentSheet.BillingDetailsCollectionConfiguration.CollectionMode.Automatic
}
}

fun mapToAddressCollectionMode(str: String?): PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode {
return when (str) {
"automatic" -> PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode.Automatic
"never" -> PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode.Never
"full" -> PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode.Full
else -> PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode.Automatic
}
}
Loading

0 comments on commit 2ad44b9

Please sign in to comment.