Skip to content

Commit

Permalink
Support events in Financial Connections
Browse files Browse the repository at this point in the history
  • Loading branch information
tillh-stripe committed Feb 7, 2025
1 parent 447804f commit 53df471
Show file tree
Hide file tree
Showing 11 changed files with 253 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.reactnativestripesdk.utils.*
import com.reactnativestripesdk.utils.createError
import com.reactnativestripesdk.utils.createResult
import com.reactnativestripesdk.utils.mapFromPaymentIntentResult
import com.reactnativestripesdk.utils.mapFromSetupIntentResult
import com.stripe.android.financialconnections.FinancialConnections
import com.stripe.android.model.PaymentIntent
import com.stripe.android.model.SetupIntent
import com.stripe.android.model.StripeIntent
Expand All @@ -32,6 +34,18 @@ class CollectBankAccountLauncherFragment(
) : Fragment() {
private lateinit var collectBankAccountLauncher: CollectBankAccountLauncher

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val stripeSdkModule: StripeSdkModule? = context.getNativeModule(StripeSdkModule::class.java)
if (stripeSdkModule != null && stripeSdkModule.eventListenerCount > 0) {
FinancialConnections.setEventListener { event ->
val params = mapFromFinancialConnectionsEvent(event)
stripeSdkModule.sendEvent(context, "onFinancialConnectionsEvent", params)
}
}
}

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
collectBankAccountLauncher = createBankAccountLauncher()
Expand Down Expand Up @@ -63,6 +77,13 @@ class CollectBankAccountLauncherFragment(
}
}

override fun onDestroy() {
super.onDestroy()

// Remove any event listener that might be set
FinancialConnections.clearEventListener()
}

private fun createBankAccountLauncher(): CollectBankAccountLauncher {
return CollectBankAccountLauncher.create(this) { result ->
when (result) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@ class FinancialConnectionsSheetFragment : Fragment() {
private lateinit var configuration: FinancialConnectionsSheet.Configuration
private lateinit var mode: Mode

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val stripeSdkModule: StripeSdkModule? = context.getNativeModule(StripeSdkModule::class.java)
if (stripeSdkModule != null && stripeSdkModule.eventListenerCount > 0) {
FinancialConnections.setEventListener { event ->
val params = mapFromFinancialConnectionsEvent(event)
stripeSdkModule.sendEvent(context, "onFinancialConnectionsEvent", params)
}
}
}

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
return FrameLayout(requireActivity()).also {
Expand Down Expand Up @@ -55,6 +67,13 @@ class FinancialConnectionsSheetFragment : Fragment() {
}
}

override fun onDestroy() {
super.onDestroy()

// Remove any event listener that might be set
FinancialConnections.clearEventListener()
}

private fun onFinancialConnectionsSheetForTokenResult(result: FinancialConnectionsSheetForTokenResult) {
when(result) {
is FinancialConnectionsSheetForTokenResult.Canceled -> {
Expand Down
46 changes: 46 additions & 0 deletions android/src/main/java/com/reactnativestripesdk/utils/Mappers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Log
import com.facebook.react.bridge.*
import com.stripe.android.financialconnections.analytics.FinancialConnectionsEvent
import com.stripe.android.PaymentAuthConfig
import com.stripe.android.model.*
import com.stripe.android.model.StripeIntent.NextActionType
Expand Down Expand Up @@ -957,3 +958,48 @@ internal fun mapToPreferredNetworks(networksAsInts: ArrayList<Int>?): List<CardB
intToCardBrand[it]
}
}

internal fun mapFromFinancialConnectionsEvent(event: FinancialConnectionsEvent): WritableMap {
return Arguments.createMap().apply {
putString("name", event.name.value)
putMap("metadata", event.metadata.toMap().toReadableMap())
}
}

private fun List<Any?>.toWritableArray(): WritableArray {
val writableArray = Arguments.createArray()

forEach { value ->
when (value) {
null -> writableArray.pushNull()
is Boolean -> writableArray.pushBoolean(value)
is Int -> writableArray.pushInt(value)
is Double -> writableArray.pushDouble(value)
is String -> writableArray.pushString(value)
is Map<*, *> -> writableArray.pushMap((value as Map<String, Any?>).toReadableMap())
is List<*> -> writableArray.pushArray((value as List<Any?>).toWritableArray())
else -> writableArray.pushString(value.toString())
}
}

return writableArray
}

private fun Map<String, Any?>.toReadableMap(): ReadableMap {
val writableMap = Arguments.createMap()

forEach { (key, value) ->
when (value) {
null -> writableMap.putNull(key)
is Boolean -> writableMap.putBoolean(key, value)
is Int -> writableMap.putInt(key, value)
is Double -> writableMap.putDouble(key, value)
is String -> writableMap.putString(key, value)
is Map<*, *> -> writableMap.putMap(key, (value as Map<String, Any?>).toReadableMap())
is List<*> -> writableMap.putArray(key, (value as List<Any?>).toWritableArray())
else -> writableMap.putString(key, value.toString())
}
}

return writableMap
}
6 changes: 6 additions & 0 deletions example/src/screens/ACHPaymentScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
verifyMicrodepositsForPayment,
VerifyMicrodepositsParams,
collectBankAccountForPayment,
FinancialConnectionsEvent,
} from '@stripe/stripe-react-native';
import Button from '../components/Button';
import PaymentScreen from '../components/PaymentScreen';
Expand Down Expand Up @@ -136,6 +137,10 @@ export default function ACHPaymentScreen() {

setSecret(clientSecret);

const onEvent = (event: FinancialConnectionsEvent) => {
console.log(`Received Financial Connections event: ${event}`);
};

const { paymentIntent, error } = await collectBankAccountForPayment(
clientSecret,
{
Expand All @@ -146,6 +151,7 @@ export default function ACHPaymentScreen() {
email,
},
},
onEvent: onEvent,
}
);

Expand Down
16 changes: 14 additions & 2 deletions ios/FinancialConnections.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,16 @@ class FinancialConnections {
internal static func present(
withClientSecret: String,
returnURL: String? = nil,
onEvent: ((FinancialConnectionsEvent) -> Void)? = nil,
resolve: @escaping RCTPromiseResolveBlock
) -> Void {
DispatchQueue.main.async {
FinancialConnectionsSheet(financialConnectionsSessionClientSecret: withClientSecret, returnURL: returnURL).present(
let financialConnectionsSheet = FinancialConnectionsSheet(
financialConnectionsSessionClientSecret: withClientSecret,
returnURL: returnURL
)
financialConnectionsSheet.onEvent = onEvent
financialConnectionsSheet.present(
from: findViewControllerPresenter(from: UIApplication.shared.delegate?.window??.rootViewController ?? UIViewController()),
completion: { result in
switch result {
Expand All @@ -35,10 +41,16 @@ class FinancialConnections {
internal static func presentForToken(
withClientSecret: String,
returnURL: String? = nil,
onEvent: ((FinancialConnectionsEvent) -> Void)? = nil,
resolve: @escaping RCTPromiseResolveBlock
) -> Void {
DispatchQueue.main.async {
FinancialConnectionsSheet(financialConnectionsSessionClientSecret: withClientSecret, returnURL: returnURL).presentForToken(
let financialConnectionsSheet = FinancialConnectionsSheet(
financialConnectionsSessionClientSecret: withClientSecret,
returnURL: returnURL
)
financialConnectionsSheet.onEvent = onEvent
financialConnectionsSheet.presentForToken(
from: findViewControllerPresenter(from: UIApplication.shared.delegate?.window??.rootViewController ?? UIViewController()),
completion: { result in
switch result {
Expand Down
23 changes: 23 additions & 0 deletions ios/Mappers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1050,4 +1050,27 @@ class Mappers {
return nil
}
}

class func financialConnectionsEventToMap(_ event: FinancialConnectionsEvent) -> [String: Any] {
var metadata: [String: Any] = [:]

if let manualEntry = event.metadata.manualEntry {
metadata["manualEntry"] = manualEntry
}

if let institutionName = event.metadata.institutionName {
metadata["institutionName"] = institutionName
}

if let errorCode = event.metadata.errorCode {
metadata["errorCode"] = errorCode.rawValue
}

let mappedEvent: [String: Any] = [
"name": event.name.rawValue,
"metadata": metadata
]

return mappedEvent
}
}
29 changes: 24 additions & 5 deletions ios/StripeSdk.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class StripeSdk: RCTEventEmitter, UIAdaptivePresentationControllerDelegate {
override func supportedEvents() -> [String]! {
return ["onOrderTrackingCallback", "onConfirmHandlerCallback", "onCustomerAdapterFetchPaymentMethodsCallback", "onCustomerAdapterAttachPaymentMethodCallback",
"onCustomerAdapterDetachPaymentMethodCallback", "onCustomerAdapterSetSelectedPaymentOptionCallback", "onCustomerAdapterFetchSelectedPaymentOptionCallback",
"onCustomerAdapterSetupIntentClientSecretForCustomerAttachCallback"]
"onCustomerAdapterSetupIntentClientSecretForCustomerAttachCallback", "onFinancialConnectionsEvent"]
}

@objc override static func requiresMainQueueSetup() -> Bool {
Expand Down Expand Up @@ -733,13 +733,19 @@ class StripeSdk: RCTEventEmitter, UIAdaptivePresentationControllerDelegate {
connectionsReturnURL = nil
}

let onEvent: (FinancialConnectionsEvent) -> Void = { [weak self] event in
let mappedEvent = Mappers.financialConnectionsEventToMap(event)
self?.sendEvent(withName: "onFinancialConnectionsEvent", body: mappedEvent)
}

if (isPaymentIntent) {
DispatchQueue.main.async {
STPBankAccountCollector().collectBankAccountForPayment(
clientSecret: clientSecret as String,
returnURL: connectionsReturnURL,
params: collectParams,
from: findViewControllerPresenter(from: UIApplication.shared.delegate?.window??.rootViewController ?? UIViewController())
from: findViewControllerPresenter(from: UIApplication.shared.delegate?.window??.rootViewController ?? UIViewController()),
onEvent: onEvent
) { intent, error in
if let error = error {
resolve(Errors.createError(ErrorType.Failed, error as NSError))
Expand All @@ -765,7 +771,8 @@ class StripeSdk: RCTEventEmitter, UIAdaptivePresentationControllerDelegate {
clientSecret: clientSecret as String,
returnURL: connectionsReturnURL,
params: collectParams,
from: findViewControllerPresenter(from: UIApplication.shared.delegate?.window??.rootViewController ?? UIViewController())
from: findViewControllerPresenter(from: UIApplication.shared.delegate?.window??.rootViewController ?? UIViewController()),
onEvent: onEvent
) { intent, error in
if let error = error {
resolve(Errors.createError(ErrorType.Failed, error as NSError))
Expand Down Expand Up @@ -1042,7 +1049,13 @@ class StripeSdk: RCTEventEmitter, UIAdaptivePresentationControllerDelegate {
} else {
returnURL = nil
}
FinancialConnections.presentForToken(withClientSecret: clientSecret, returnURL: returnURL, resolve: resolve)

let onEvent: (FinancialConnectionsEvent) -> Void = { [weak self] event in
let mappedEvent = Mappers.financialConnectionsEventToMap(event)
self?.sendEvent(withName: "onFinancialConnectionsEvent", body: mappedEvent)
}

FinancialConnections.presentForToken(withClientSecret: clientSecret, returnURL: returnURL, onEvent: onEvent, resolve: resolve)
}

@objc(collectFinancialConnectionsAccounts:resolver:rejecter:)
Expand All @@ -1061,7 +1074,13 @@ class StripeSdk: RCTEventEmitter, UIAdaptivePresentationControllerDelegate {
} else {
returnURL = nil
}
FinancialConnections.present(withClientSecret: clientSecret, returnURL: returnURL, resolve: resolve)

let onEvent: (FinancialConnectionsEvent) -> Void = { [weak self] event in
let mappedEvent = Mappers.financialConnectionsEventToMap(event)
self?.sendEvent(withName: "onFinancialConnectionsEvent", body: mappedEvent)
}

FinancialConnections.present(withClientSecret: clientSecret, returnURL: returnURL, onEvent: onEvent, resolve: resolve)
}

@objc(configureOrderTracking:orderIdentifier:webServiceUrl:authenticationToken:resolver:rejecter:)
Expand Down
2 changes: 1 addition & 1 deletion src/NativeStripeSdk.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ type NativeStripeSdkType = {
collectBankAccount(
isPaymentIntent: boolean,
clientSecret: string,
params: PaymentMethod.CollectBankAccountParams
params: Omit<PaymentMethod.CollectBankAccountParams, 'onEvent'>
): Promise<ConfirmSetupIntentResult | ConfirmPaymentResult>;
getConstants(): { API_VERSIONS: { CORE: string; ISSUING: string } };
canAddCardToWallet(
Expand Down
Loading

0 comments on commit 53df471

Please sign in to comment.