Skip to content

Commit

Permalink
fix: add check to avoid crashes when currentActivity is null (#846)
Browse files Browse the repository at this point in the history
  • Loading branch information
charliecruzan-stripe authored Apr 18, 2022
1 parent 9611773 commit e27d3f0
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 66 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# CHANGELOG

- [#846](https://github.com/stripe/stripe-react-native/pull/846) Fix: Avoid crashes when `currentActivity` is null
- [#879](https://github.com/stripe/stripe-react-native/pull/879) Feat: Add support for ACHv2 payments on Android (already existed on iOS).
- [#879](https://github.com/stripe/stripe-react-native/pull/879) Chore: Upgraded `stripe-android` from v19.3.+ to v20.0.+
- [#837](https://github.com/stripe/stripe-react-native/pull/837) BREAKING CHANGE: Mostly fixes and changes to types, but some method's now accept slightly different parameters:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import android.widget.FrameLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.stripe.android.model.PaymentIntent
import com.stripe.android.model.SetupIntent
import com.stripe.android.model.StripeIntent
Expand All @@ -16,7 +17,7 @@ import com.stripe.android.payments.bankaccount.CollectBankAccountLauncher
import com.stripe.android.payments.bankaccount.navigation.CollectBankAccountResult

class CollectBankAccountLauncherFragment(
private val activity: AppCompatActivity,
private val context: ReactApplicationContext,
private val publishableKey: String,
private val clientSecret: String,
private val isPaymentIntent: Boolean,
Expand Down Expand Up @@ -77,7 +78,7 @@ class CollectBankAccountLauncherFragment(
promise.resolve(createError(ErrorType.Failed.toString(), result.error))
}
}
activity.supportFragmentManager.beginTransaction().remove(this).commit()
(context.currentActivity as? AppCompatActivity)?.supportFragmentManager?.beginTransaction()?.remove(this)?.commit()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ var ON_PAYMENT_OPTION_ACTION = "com.reactnativestripesdk.PAYMENT_OPTION_ACTION"
var ON_CONFIGURE_FLOW_CONTROLLER = "com.reactnativestripesdk.CONFIGURE_FLOW_CONTROLLER_ACTION"
var ON_INIT_PAYMENT_SHEET = "com.reactnativestripesdk.INIT_PAYMENT_SHEET"

var ON_GOOGLE_PAY_FRAGMENT_CREATED = "com.reactnativestripesdk.ON_GOOGLE_PAY_FRAGMENT_CREATED"
var ON_INIT_GOOGLE_PAY = "com.reactnativestripesdk.ON_INIT_GOOGLE_PAY"
var ON_GOOGLE_PAY_RESULT = "com.reactnativestripesdk.ON_GOOGLE_PAY_RESULT"
var ON_GOOGLE_PAYMENT_METHOD_RESULT = "com.reactnativestripesdk.ON_GOOGLE_PAYMENT_METHOD_RESULT"
10 changes: 10 additions & 0 deletions android/src/main/java/com/reactnativestripesdk/Errors.kt
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ internal fun createError(code: String, message: String?): WritableMap {
return mapError(code, message, message, null, null, null)
}

internal fun createMissingActivityError(): WritableMap {
return mapError(
"Failed",
"Activity doesn't exist yet. You can safely retry this method.",
null,
null,
null,
null)
}

internal fun createError(code: String, error: PaymentIntent.Error?): WritableMap {
return mapError(code, error?.message, error?.message, error?.declineCode, error?.type?.code, error?.code)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,6 @@ class GooglePayFragment : Fragment() {
readyCallback = ::onGooglePayLauncherReady,
resultCallback = ::onGooglePayResult
)

val intent = Intent(ON_GOOGLE_PAY_FRAGMENT_CREATED)
localBroadcastManager.sendBroadcast(intent)
}

fun presentForPaymentIntent(clientSecret: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import android.widget.FrameLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.stripe.android.googlepaylauncher.GooglePayEnvironment
import com.stripe.android.googlepaylauncher.GooglePayPaymentMethodLauncher

class GooglePayPaymentMethodLauncherFragment(
private val activity: AppCompatActivity,
private val context: ReactApplicationContext,
private val isTestEnv: Boolean,
private val paymentMethodRequired: Boolean,
private val promise: Promise
Expand All @@ -36,7 +37,7 @@ class GooglePayPaymentMethodLauncherFragment(
),
readyCallback = {
promise.resolve(it)
activity.supportFragmentManager.beginTransaction().remove(this).commit()
(context.currentActivity as? AppCompatActivity)?.supportFragmentManager?.beginTransaction()?.remove(this)?.commit()
},
resultCallback = {}
)
Expand Down
130 changes: 72 additions & 58 deletions android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

@ReactModule(name = StripeSdkModule.NAME)
class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
class StripeSdkModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
var cardFieldView: CardFieldView? = null
var cardFormView: CardFormView? = null

Expand Down Expand Up @@ -89,16 +89,17 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ

private val googlePayReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
if (intent.action == ON_GOOGLE_PAY_FRAGMENT_CREATED) {
googlePayFragment = (currentActivity as AppCompatActivity).supportFragmentManager.findFragmentByTag("google_pay_launch_fragment") as GooglePayFragment
}
if (intent.action == ON_INIT_GOOGLE_PAY) {
val isReady = intent.extras?.getBoolean("isReady") ?: false

if (isReady) {
initGooglePayPromise?.resolve(WritableNativeMap())
} else {
initGooglePayPromise?.resolve(createError(GooglePayErrorType.Failed.toString(), "Google Pay is not available on this device"))
initGooglePayPromise?.resolve(
createError(
GooglePayErrorType.Failed.toString(),
"Google Pay is not available on this device. You can use isGooglePaySupported to preemptively check for Google Pay support."
)
)
}
}
if (intent.action == ON_GOOGLE_PAYMENT_METHOD_RESULT) {
Expand Down Expand Up @@ -215,43 +216,39 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
PaymentConfiguration.init(reactApplicationContext, publishableKey, stripeAccountId)

paymentLauncherFragment = PaymentLauncherFragment(stripe, publishableKey, stripeAccountId)
(currentActivity as AppCompatActivity).supportFragmentManager.beginTransaction()
.add(paymentLauncherFragment, "payment_launcher_fragment")
.commit()

val localBroadcastManager = LocalBroadcastManager.getInstance(reactApplicationContext)
localBroadcastManager.registerReceiver(mPaymentSheetReceiver, IntentFilter(ON_PAYMENT_RESULT_ACTION))
localBroadcastManager.registerReceiver(mPaymentSheetReceiver, IntentFilter(ON_PAYMENT_OPTION_ACTION))
localBroadcastManager.registerReceiver(mPaymentSheetReceiver, IntentFilter(ON_CONFIGURE_FLOW_CONTROLLER))
localBroadcastManager.registerReceiver(mPaymentSheetReceiver, IntentFilter(ON_INIT_PAYMENT_SHEET))

localBroadcastManager.registerReceiver(googlePayReceiver, IntentFilter(ON_GOOGLE_PAY_FRAGMENT_CREATED))
localBroadcastManager.registerReceiver(googlePayReceiver, IntentFilter(ON_INIT_GOOGLE_PAY))
localBroadcastManager.registerReceiver(googlePayReceiver, IntentFilter(ON_GOOGLE_PAY_RESULT))
localBroadcastManager.registerReceiver(googlePayReceiver, IntentFilter(ON_GOOGLE_PAYMENT_METHOD_RESULT))

promise.resolve(null)
getCurrentActivityOrResolveWithError(promise)?.let {
it.supportFragmentManager.beginTransaction()
.add(paymentLauncherFragment, "payment_launcher_fragment")
.commit()

val localBroadcastManager = LocalBroadcastManager.getInstance(reactApplicationContext)
localBroadcastManager.registerReceiver(mPaymentSheetReceiver, IntentFilter(ON_PAYMENT_RESULT_ACTION))
localBroadcastManager.registerReceiver(mPaymentSheetReceiver, IntentFilter(ON_PAYMENT_OPTION_ACTION))
localBroadcastManager.registerReceiver(mPaymentSheetReceiver, IntentFilter(ON_CONFIGURE_FLOW_CONTROLLER))
localBroadcastManager.registerReceiver(mPaymentSheetReceiver, IntentFilter(ON_INIT_PAYMENT_SHEET))

localBroadcastManager.registerReceiver(googlePayReceiver, IntentFilter(ON_INIT_GOOGLE_PAY))
localBroadcastManager.registerReceiver(googlePayReceiver, IntentFilter(ON_GOOGLE_PAY_RESULT))
localBroadcastManager.registerReceiver(googlePayReceiver, IntentFilter(ON_GOOGLE_PAYMENT_METHOD_RESULT))

promise.resolve(null)
}
}

@ReactMethod
@SuppressWarnings("unused")
fun initPaymentSheet(params: ReadableMap, promise: Promise) {
val activity = currentActivity as AppCompatActivity?
getCurrentActivityOrResolveWithError(promise)?.let { activity ->
this.initPaymentSheetPromise = promise

if (activity == null) {
promise.resolve(createError("Failed", "Activity doesn't exist"))
return
}

this.initPaymentSheetPromise = promise

paymentSheetFragment = PaymentSheetFragment().also {
val bundle = toBundleObject(params)
it.arguments = bundle
paymentSheetFragment = PaymentSheetFragment().also {
val bundle = toBundleObject(params)
it.arguments = bundle
}
activity.supportFragmentManager.beginTransaction()
.add(paymentSheetFragment!!, "payment_sheet_launch_fragment")
.commit()
}
activity.supportFragmentManager.beginTransaction()
.add(paymentSheetFragment!!, "payment_sheet_launch_fragment")
.commit()
}

@ReactMethod
Expand All @@ -269,12 +266,13 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
}

private fun payWithFpx() {
AddPaymentMethodActivityStarter(currentActivity as AppCompatActivity)
.startForResult(
AddPaymentMethodActivityStarter.Args.Builder()
.setPaymentMethodType(PaymentMethod.Type.Fpx)
.build()
)
getCurrentActivityOrResolveWithError(confirmPromise)?.let {
AddPaymentMethodActivityStarter(it)
.startForResult(AddPaymentMethodActivityStarter.Args.Builder()
.setPaymentMethodType(PaymentMethod.Type.Fpx)
.build()
)
}
}

private fun onFpxPaymentMethodResult(result: AddPaymentMethodActivityStarter.Result) {
Expand Down Expand Up @@ -482,7 +480,7 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
// promise.resolve(createError("Failed", "You must provide appId"))
// return
// }
// payWithWeChatPay(paymentIntentClientSecret, appId)
// payWithWeChatPay(paymentIntentClientSecret, appId, promise)
//
// return
// }
Expand Down Expand Up @@ -560,31 +558,34 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
@SuppressWarnings("unused")
fun isGooglePaySupported(params: ReadableMap?, promise: Promise) {
val fragment = GooglePayPaymentMethodLauncherFragment(
currentActivity as AppCompatActivity,
reactContext,
getBooleanOrFalse(params, "testEnv"),
getBooleanOrFalse(params, "existingPaymentMethodRequired"),
promise
)

(currentActivity as AppCompatActivity).supportFragmentManager.beginTransaction()
.add(fragment, "google_pay_support_fragment")
.commit()
getCurrentActivityOrResolveWithError(promise)?.let {
it.supportFragmentManager.beginTransaction()
.add(fragment, "google_pay_support_fragment")
.commit()
}
}

@ReactMethod
@SuppressWarnings("unused")
fun initGooglePay(params: ReadableMap, promise: Promise) {
val activity = currentActivity as AppCompatActivity
val fragment = GooglePayFragment().also {
googlePayFragment = GooglePayFragment().also {
val bundle = toBundleObject(params)
it.arguments = bundle
}

initGooglePayPromise = promise
getCurrentActivityOrResolveWithError(promise)?.let {
initGooglePayPromise = promise

activity.supportFragmentManager.beginTransaction()
.add(fragment, "google_pay_launch_fragment")
.commit()
it.supportFragmentManager.beginTransaction()
.add(googlePayFragment!!, "google_pay_launch_fragment")
.commit()
}
}

@ReactMethod
Expand Down Expand Up @@ -644,17 +645,18 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
)

val fragment = CollectBankAccountLauncherFragment(
currentActivity as AppCompatActivity,
reactContext,
publishableKey,
clientSecret,
isPaymentIntent,
collectParams,
promise
)

(currentActivity as AppCompatActivity).supportFragmentManager.beginTransaction()
.add(fragment, "collect_bank_account_launcher_fragment")
.commit()
getCurrentActivityOrResolveWithError(promise)?.let {
it.supportFragmentManager.beginTransaction()
.add(fragment, "collect_bank_account_launcher_fragment")
.commit()
}
}

@ReactMethod
Expand Down Expand Up @@ -725,6 +727,18 @@ class StripeSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJ
}
}

/**
* Safely get and cast the current activity as an AppCompatActivity. If that fails, the promise
* provided will be resolved with an error message instructing the user to retry the method.
*/
private fun getCurrentActivityOrResolveWithError(promise: Promise?): AppCompatActivity? {
(currentActivity as? AppCompatActivity)?.let {
return it
}
promise?.resolve(createMissingActivityError())
return null
}

companion object {
const val NAME = "StripeSdk"
}
Expand Down

0 comments on commit e27d3f0

Please sign in to comment.