@stripe/stripe-react-native 0.30.0 → 0.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/android/gradle.properties +1 -1
  3. package/android/src/main/java/com/reactnativestripesdk/PaymentSheetAppearance.kt +41 -10
  4. package/android/src/main/java/com/reactnativestripesdk/PaymentSheetFragment.kt +10 -3
  5. package/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt +129 -82
  6. package/android/src/main/java/com/reactnativestripesdk/customersheet/CustomerSheetFragment.kt +329 -0
  7. package/android/src/main/java/com/reactnativestripesdk/customersheet/ReactNativeCustomerAdapter.kt +138 -0
  8. package/android/src/main/java/com/reactnativestripesdk/utils/Mappers.kt +8 -2
  9. package/ios/CustomerSheet/CustomerSheetUtils.swift +181 -0
  10. package/ios/CustomerSheet/ReactNativeCustomerAdapter.swift +173 -0
  11. package/ios/Mappers.swift +1 -1
  12. package/ios/StripeSdk+CustomerSheet.swift +166 -0
  13. package/ios/StripeSdk+PaymentSheet.swift +2 -2
  14. package/ios/StripeSdk.m +53 -0
  15. package/ios/StripeSdk.swift +14 -1
  16. package/jest/mock.js +0 -37
  17. package/lib/commonjs/NativeStripeSdk.js.map +1 -1
  18. package/lib/commonjs/components/CustomerSheet.js +2 -0
  19. package/lib/commonjs/components/CustomerSheet.js.map +1 -0
  20. package/lib/commonjs/functions.js +1 -1
  21. package/lib/commonjs/functions.js.map +1 -1
  22. package/lib/commonjs/index.js +1 -1
  23. package/lib/commonjs/index.js.map +1 -1
  24. package/lib/commonjs/types/CustomerSheet.js +2 -0
  25. package/lib/commonjs/types/CustomerSheet.js.map +1 -0
  26. package/lib/commonjs/types/Errors.js +1 -1
  27. package/lib/commonjs/types/Errors.js.map +1 -1
  28. package/lib/commonjs/types/index.js +1 -1
  29. package/lib/commonjs/types/index.js.map +1 -1
  30. package/lib/module/NativeStripeSdk.js.map +1 -1
  31. package/lib/module/components/CustomerSheet.js +2 -0
  32. package/lib/module/components/CustomerSheet.js.map +1 -0
  33. package/lib/module/functions.js +1 -1
  34. package/lib/module/functions.js.map +1 -1
  35. package/lib/module/index.js +1 -1
  36. package/lib/module/index.js.map +1 -1
  37. package/lib/module/types/CustomerSheet.js +2 -0
  38. package/lib/module/types/CustomerSheet.js.map +1 -0
  39. package/lib/module/types/Errors.js +1 -1
  40. package/lib/module/types/Errors.js.map +1 -1
  41. package/lib/module/types/index.js +1 -1
  42. package/lib/module/types/index.js.map +1 -1
  43. package/lib/typescript/src/NativeStripeSdk.d.ts +14 -1
  44. package/lib/typescript/src/components/CustomerSheet.d.ts +59 -0
  45. package/lib/typescript/src/index.d.ts +2 -0
  46. package/lib/typescript/src/types/CustomerSheet.d.ts +94 -0
  47. package/lib/typescript/src/types/Errors.d.ts +4 -0
  48. package/lib/typescript/src/types/index.d.ts +1 -0
  49. package/package.json +1 -1
  50. package/src/NativeStripeSdk.tsx +31 -0
  51. package/src/components/CustomerSheet.tsx +327 -0
  52. package/src/functions.ts +5 -0
  53. package/src/index.tsx +3 -0
  54. package/src/types/CustomerSheet.ts +111 -0
  55. package/src/types/Errors.ts +5 -0
  56. package/src/types/index.ts +1 -0
  57. package/stripe-react-native.podspec +1 -1
  58. package/android/src/main/java/com/reactnativestripesdk/GooglePayFragment.kt +0 -211
@@ -0,0 +1,329 @@
1
+ package com.reactnativestripesdk
2
+
3
+ import android.app.Activity
4
+ import android.app.Application
5
+ import android.graphics.drawable.Drawable
6
+ import android.os.Bundle
7
+ import android.os.Handler
8
+ import android.os.Looper
9
+ import android.util.Log
10
+ import android.view.LayoutInflater
11
+ import android.view.View
12
+ import android.view.ViewGroup
13
+ import android.widget.FrameLayout
14
+ import androidx.fragment.app.Fragment
15
+ import com.facebook.react.bridge.*
16
+ import com.reactnativestripesdk.customersheet.ReactNativeCustomerAdapter
17
+ import com.reactnativestripesdk.utils.*
18
+ import com.stripe.android.customersheet.CustomerAdapter
19
+ import com.stripe.android.customersheet.CustomerEphemeralKey
20
+ import com.stripe.android.customersheet.CustomerSheet
21
+ import com.stripe.android.customersheet.CustomerSheetResult
22
+ import com.stripe.android.customersheet.ExperimentalCustomerSheetApi
23
+ import com.stripe.android.customersheet.PaymentOptionSelection
24
+ import com.stripe.android.model.PaymentMethod
25
+ import com.stripe.android.paymentsheet.*
26
+ import kotlinx.coroutines.CoroutineScope
27
+ import kotlinx.coroutines.Dispatchers
28
+ import kotlinx.coroutines.launch
29
+
30
+
31
+ @OptIn(ExperimentalCustomerSheetApi::class)
32
+ class CustomerSheetFragment : Fragment() {
33
+ private var customerSheet: CustomerSheet? = null
34
+ internal var customerAdapter: ReactNativeCustomerAdapter? = null
35
+ internal var context: ReactApplicationContext? = null
36
+ internal var initPromise: Promise? = null
37
+ private var presentPromise: Promise? = null
38
+
39
+ override fun onCreateView(
40
+ inflater: LayoutInflater,
41
+ container: ViewGroup?,
42
+ savedInstanceState: Bundle?
43
+ ): View {
44
+ return FrameLayout(requireActivity()).also {
45
+ it.visibility = View.GONE
46
+ }
47
+ }
48
+
49
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
50
+ super.onViewCreated(view, savedInstanceState)
51
+
52
+ val context = context ?: run {
53
+ Log.e("StripeReactNative", "No context found during CustomerSheet.initialize. Please file an issue: https://github.com/stripe/stripe-react-native/issues")
54
+ return
55
+ }
56
+ val initPromise = initPromise ?: run {
57
+ Log.e("StripeReactNative", "No promise found for CustomerSheet.initialize. Please file an issue: https://github.com/stripe/stripe-react-native/issues")
58
+ return
59
+ }
60
+
61
+ val headerTextForSelectionScreen = arguments?.getString("headerTextForSelectionScreen")
62
+ val merchantDisplayName = arguments?.getString("merchantDisplayName")
63
+ val googlePayEnabled = arguments?.getBoolean("googlePayEnabled") ?: false
64
+ val billingDetailsBundle = arguments?.getBundle("defaultBillingDetails")
65
+ val billingConfigParams = arguments?.getBundle("billingDetailsCollectionConfiguration")
66
+ val setupIntentClientSecret = arguments?.getString("setupIntentClientSecret")
67
+ val customerId = arguments?.getString("customerId")
68
+ val customerEphemeralKeySecret = arguments?.getString("customerEphemeralKeySecret")
69
+ val customerAdapterOverrideParams = arguments?.getBundle("customerAdapter")
70
+
71
+ if (customerId == null) {
72
+ initPromise.resolve(createError(ErrorType.Failed.toString(), "You must provide a value for `customerId`"))
73
+ return
74
+ }
75
+ if (customerEphemeralKeySecret == null) {
76
+ initPromise.resolve(createError(ErrorType.Failed.toString(), "You must provide a value for `customerEphemeralKeySecret`"))
77
+ return
78
+ }
79
+
80
+ val appearance = try {
81
+ buildPaymentSheetAppearance(arguments?.getBundle("appearance"), context)
82
+ } catch (error: PaymentSheetAppearanceException) {
83
+ initPromise.resolve(createError(ErrorType.Failed.toString(), error))
84
+ return
85
+ }
86
+
87
+ val configuration = CustomerSheet.Configuration.builder()
88
+ .appearance(appearance)
89
+ .googlePayEnabled(googlePayEnabled)
90
+ .merchantDisplayName(merchantDisplayName)
91
+ .headerTextForSelectionScreen(headerTextForSelectionScreen)
92
+
93
+ billingDetailsBundle?.let {
94
+ configuration.defaultBillingDetails(createDefaultBillingDetails(billingDetailsBundle))
95
+ }
96
+ billingConfigParams?.let {
97
+ configuration.billingDetailsCollectionConfiguration(createBillingDetailsCollectionConfiguration(billingConfigParams))
98
+ }
99
+
100
+ val customerAdapter = createCustomerAdapter(
101
+ context, customerId, customerEphemeralKeySecret, setupIntentClientSecret, customerAdapterOverrideParams
102
+ ).also {
103
+ this.customerAdapter = it
104
+ }
105
+
106
+ customerSheet = CustomerSheet.create(
107
+ fragment = this,
108
+ configuration = configuration.build(),
109
+ customerAdapter = customerAdapter,
110
+ callback = ::handleResult
111
+ )
112
+
113
+ initPromise.resolve(WritableNativeMap())
114
+ }
115
+
116
+ private fun handleResult(result: CustomerSheetResult) {
117
+ val presentPromise = presentPromise ?: run {
118
+ Log.e("StripeReactNative", "No promise found for CustomerSheet.present")
119
+ return
120
+ }
121
+
122
+ var promiseResult = Arguments.createMap()
123
+ when (result) {
124
+ is CustomerSheetResult.Failed -> {
125
+ presentPromise.resolve(createError(ErrorType.Failed.toString(), result.exception))
126
+ }
127
+ is CustomerSheetResult.Selected -> {
128
+ promiseResult = createPaymentOptionResult(result.selection)
129
+ }
130
+ is CustomerSheetResult.Canceled -> {
131
+ promiseResult = createPaymentOptionResult(result.selection)
132
+ promiseResult.putMap("error", Arguments.createMap().also { it.putString("code", ErrorType.Canceled.toString()) })
133
+ }
134
+ }
135
+ presentPromise.resolve(promiseResult)
136
+ }
137
+
138
+ fun present(timeout: Long?, promise: Promise) {
139
+ presentPromise = promise
140
+ if (timeout != null) {
141
+ presentWithTimeout(timeout, promise)
142
+ }
143
+ customerSheet?.present() ?: run {
144
+ promise.resolve(createMissingInitError())
145
+ }
146
+ }
147
+
148
+ private fun presentWithTimeout(timeout: Long, promise: Promise) {
149
+ var customerSheetActivity: Activity? = null
150
+ var activities: MutableList<Activity> = mutableListOf()
151
+ val activityLifecycleCallbacks = object : Application.ActivityLifecycleCallbacks {
152
+ override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
153
+ customerSheetActivity = activity
154
+ activities.add(activity)
155
+ }
156
+
157
+ override fun onActivityStarted(activity: Activity) {}
158
+
159
+ override fun onActivityResumed(activity: Activity) {}
160
+
161
+ override fun onActivityPaused(activity: Activity) {}
162
+
163
+ override fun onActivityStopped(activity: Activity) {}
164
+
165
+ override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
166
+
167
+ override fun onActivityDestroyed(activity: Activity) {
168
+ customerSheetActivity = null
169
+ activities = mutableListOf()
170
+ context?.currentActivity?.application?.unregisterActivityLifecycleCallbacks(this)
171
+ }
172
+ }
173
+
174
+ Handler(Looper.getMainLooper()).postDelayed({
175
+ //customerSheetActivity?.finish()
176
+ for (a in activities) {
177
+ a.finish()
178
+ }
179
+ }, timeout)
180
+
181
+
182
+ context?.currentActivity?.application?.registerActivityLifecycleCallbacks(activityLifecycleCallbacks)
183
+
184
+ customerSheet?.present() ?: run {
185
+ promise.resolve(createMissingInitError())
186
+ }
187
+ }
188
+
189
+ internal fun retrievePaymentOptionSelection(promise: Promise) {
190
+ CoroutineScope(Dispatchers.IO).launch {
191
+ runCatching {
192
+ val result = customerSheet?.retrievePaymentOptionSelection() ?: run {
193
+ promise.resolve(createMissingInitError())
194
+ return@launch
195
+ }
196
+ var promiseResult = Arguments.createMap()
197
+ when (result) {
198
+ is CustomerSheetResult.Failed -> {
199
+ promise.resolve(createError(ErrorType.Failed.toString(), result.exception))
200
+ }
201
+ is CustomerSheetResult.Selected -> {
202
+ promiseResult = createPaymentOptionResult(result.selection)
203
+ }
204
+ is CustomerSheetResult.Canceled -> {
205
+ promiseResult = createPaymentOptionResult(result.selection)
206
+ promiseResult.putMap("error", Arguments.createMap().also { it.putString("code", ErrorType.Canceled.toString()) })
207
+ }
208
+ }
209
+ promise.resolve(promiseResult)
210
+ }.onFailure {
211
+ promise.resolve(createError(CreateTokenErrorType.Failed.toString(), it.message))
212
+ }
213
+ }
214
+ }
215
+
216
+ companion object {
217
+ internal const val TAG = "customer_sheet_launch_fragment"
218
+
219
+ internal fun createMissingInitError(): WritableMap {
220
+ return createError(ErrorType.Failed.toString(), "No customer sheet has been initialized yet.")
221
+ }
222
+
223
+ internal fun createDefaultBillingDetails(bundle: Bundle): PaymentSheet.BillingDetails {
224
+ val addressBundle = bundle.getBundle("address")
225
+ val address = PaymentSheet.Address(
226
+ addressBundle?.getString("city"),
227
+ addressBundle?.getString("country"),
228
+ addressBundle?.getString("line1"),
229
+ addressBundle?.getString("line2"),
230
+ addressBundle?.getString("postalCode"),
231
+ addressBundle?.getString("state"))
232
+ return PaymentSheet.BillingDetails(
233
+ address,
234
+ bundle.getString("email"),
235
+ bundle.getString("name"),
236
+ bundle.getString("phone"))
237
+ }
238
+
239
+ internal fun createBillingDetailsCollectionConfiguration(bundle: Bundle): PaymentSheet.BillingDetailsCollectionConfiguration {
240
+ return PaymentSheet.BillingDetailsCollectionConfiguration(
241
+ name = mapToCollectionMode(bundle.getString("name")),
242
+ phone = mapToCollectionMode(bundle.getString("phone")),
243
+ email = mapToCollectionMode(bundle.getString("email")),
244
+ address = mapToAddressCollectionMode(bundle.getString("address")),
245
+ attachDefaultsToPaymentMethod = bundle.getBoolean("attachDefaultsToPaymentMethod")
246
+ )
247
+ }
248
+
249
+ internal fun createCustomerAdapter(
250
+ context: ReactApplicationContext,
251
+ customerId: String,
252
+ customerEphemeralKeySecret: String,
253
+ setupIntentClientSecret: String?,
254
+ customerAdapterOverrideParams: Bundle?,
255
+ ): ReactNativeCustomerAdapter {
256
+ val ephemeralKeyProvider = {
257
+ CustomerAdapter.Result.success(
258
+ CustomerEphemeralKey.create(
259
+ customerId = customerId,
260
+ ephemeralKey = customerEphemeralKeySecret,
261
+ )
262
+ )
263
+ }
264
+ val customerAdapter = if (setupIntentClientSecret != null) {
265
+ CustomerAdapter.create(
266
+ context,
267
+ customerEphemeralKeyProvider = ephemeralKeyProvider,
268
+ setupIntentClientSecretProvider = {
269
+ CustomerAdapter.Result.success(
270
+ setupIntentClientSecret,
271
+ )
272
+ }
273
+ )
274
+ } else {
275
+ CustomerAdapter.create(
276
+ context,
277
+ customerEphemeralKeyProvider = ephemeralKeyProvider,
278
+ setupIntentClientSecretProvider = null
279
+ )
280
+ }
281
+
282
+ return ReactNativeCustomerAdapter(
283
+ context = context,
284
+ adapter = customerAdapter,
285
+ overridesFetchPaymentMethods = customerAdapterOverrideParams?.getBoolean("fetchPaymentMethods") ?: false,
286
+ overridesAttachPaymentMethod = customerAdapterOverrideParams?.getBoolean("attachPaymentMethod") ?: false,
287
+ overridesDetachPaymentMethod = customerAdapterOverrideParams?.getBoolean("detachPaymentMethod") ?: false,
288
+ overridesSetSelectedPaymentOption = customerAdapterOverrideParams?.getBoolean("setSelectedPaymentOption") ?: false,
289
+ overridesFetchSelectedPaymentOption = customerAdapterOverrideParams?.getBoolean("fetchSelectedPaymentOption") ?: false,
290
+ overridesSetupIntentClientSecretForCustomerAttach = customerAdapterOverrideParams?.getBoolean("setupIntentClientSecretForCustomerAttach") ?: false
291
+ )
292
+ }
293
+
294
+ internal fun createPaymentOptionResult(selection: PaymentOptionSelection?): WritableMap {
295
+ var paymentOptionResult = Arguments.createMap()
296
+
297
+ when (selection) {
298
+ is PaymentOptionSelection.GooglePay -> {
299
+ paymentOptionResult = buildResult(
300
+ selection.paymentOption.label,
301
+ selection.paymentOption.icon(),
302
+ null)
303
+ }
304
+ is PaymentOptionSelection.PaymentMethod -> {
305
+ paymentOptionResult = buildResult(
306
+ selection.paymentOption.label,
307
+ selection.paymentOption.icon(),
308
+ selection.paymentMethod)
309
+ }
310
+ null -> {}
311
+ }
312
+
313
+ return paymentOptionResult
314
+ }
315
+
316
+ private fun buildResult(label: String, drawable: Drawable, paymentMethod: PaymentMethod?): WritableMap {
317
+ val result = Arguments.createMap()
318
+ val paymentOption = Arguments.createMap().also {
319
+ it.putString("label", label)
320
+ it.putString("image", getBase64FromBitmap(getBitmapFromDrawable(drawable)))
321
+ }
322
+ result.putMap("paymentOption", paymentOption)
323
+ if (paymentMethod != null) {
324
+ result.putMap("paymentMethod", mapFromPaymentMethod(paymentMethod))
325
+ }
326
+ return result
327
+ }
328
+ }
329
+ }
@@ -0,0 +1,138 @@
1
+ package com.reactnativestripesdk.customersheet
2
+
3
+ import android.util.Log
4
+ import com.facebook.react.bridge.Arguments
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.bridge.ReadableMap
7
+ import com.facebook.react.bridge.WritableMap
8
+ import com.reactnativestripesdk.StripeSdkModule
9
+ import com.stripe.android.customersheet.CustomerAdapter
10
+ import com.stripe.android.customersheet.ExperimentalCustomerSheetApi
11
+ import com.stripe.android.model.PaymentMethod
12
+ import kotlinx.coroutines.CompletableDeferred
13
+
14
+ @OptIn(ExperimentalCustomerSheetApi::class)
15
+ class ReactNativeCustomerAdapter (
16
+ private val context: ReactApplicationContext,
17
+ private val adapter: CustomerAdapter,
18
+ private val overridesFetchPaymentMethods: Boolean,
19
+ private val overridesAttachPaymentMethod: Boolean,
20
+ private val overridesDetachPaymentMethod: Boolean,
21
+ private val overridesSetSelectedPaymentOption: Boolean,
22
+ private val overridesFetchSelectedPaymentOption: Boolean,
23
+ private val overridesSetupIntentClientSecretForCustomerAttach: Boolean
24
+ ) : CustomerAdapter by adapter {
25
+ internal var fetchPaymentMethodsCallback: CompletableDeferred<List<PaymentMethod>>? = null
26
+ internal var attachPaymentMethodCallback: CompletableDeferred<PaymentMethod>? = null
27
+ internal var detachPaymentMethodCallback: CompletableDeferred<PaymentMethod>? = null
28
+ internal var setSelectedPaymentOptionCallback: CompletableDeferred<Unit>? = null
29
+ internal var fetchSelectedPaymentOptionCallback: CompletableDeferred<String?>? = null
30
+ internal var setupIntentClientSecretForCustomerAttachCallback: CompletableDeferred<String>? = null
31
+
32
+ override suspend fun retrievePaymentMethods(): CustomerAdapter.Result<List<PaymentMethod>> {
33
+ if (overridesFetchPaymentMethods) {
34
+ CompletableDeferred<List<PaymentMethod>>().also {
35
+ fetchPaymentMethodsCallback = it
36
+ emitEvent("onCustomerAdapterFetchPaymentMethodsCallback", Arguments.createMap())
37
+ val resultFromJavascript = it.await()
38
+ return CustomerAdapter.Result.success(resultFromJavascript)
39
+ }
40
+ }
41
+
42
+ return adapter.retrievePaymentMethods()
43
+ }
44
+
45
+ override suspend fun attachPaymentMethod(paymentMethodId: String): CustomerAdapter.Result<PaymentMethod> {
46
+ if (overridesAttachPaymentMethod) {
47
+ CompletableDeferred<PaymentMethod>().also {
48
+ attachPaymentMethodCallback = it
49
+ val params = Arguments.createMap().also {
50
+ it.putString("paymentMethodId", paymentMethodId)
51
+ }
52
+ emitEvent("onCustomerAdapterAttachPaymentMethodCallback", params)
53
+ val resultFromJavascript = it.await()
54
+ return CustomerAdapter.Result.success(resultFromJavascript)
55
+ }
56
+ }
57
+
58
+ return adapter.attachPaymentMethod(paymentMethodId)
59
+ }
60
+
61
+ override suspend fun detachPaymentMethod(paymentMethodId: String): CustomerAdapter.Result<PaymentMethod> {
62
+ if (overridesDetachPaymentMethod) {
63
+ CompletableDeferred<PaymentMethod>().also {
64
+ detachPaymentMethodCallback = it
65
+ val params = Arguments.createMap().also {
66
+ it.putString("paymentMethodId", paymentMethodId)
67
+ }
68
+ emitEvent("onCustomerAdapterDetachPaymentMethodCallback", params)
69
+ val resultFromJavascript = it.await()
70
+ return CustomerAdapter.Result.success(resultFromJavascript)
71
+ }
72
+ }
73
+
74
+ return adapter.detachPaymentMethod(paymentMethodId)
75
+ }
76
+
77
+ override suspend fun setSelectedPaymentOption(paymentOption: CustomerAdapter.PaymentOption?): CustomerAdapter.Result<Unit> {
78
+ if (overridesSetSelectedPaymentOption) {
79
+ CompletableDeferred<Unit>().also {
80
+ setSelectedPaymentOptionCallback = it
81
+ val params = Arguments.createMap().also {
82
+ it.putString("paymentOption", paymentOption?.id)
83
+ }
84
+ emitEvent("onCustomerAdapterSetSelectedPaymentOptionCallback", params)
85
+ val resultFromJavascript = it.await()
86
+ return CustomerAdapter.Result.success(resultFromJavascript)
87
+ }
88
+ }
89
+
90
+ return adapter.setSelectedPaymentOption(paymentOption)
91
+ }
92
+
93
+ override suspend fun retrieveSelectedPaymentOption(): CustomerAdapter.Result<CustomerAdapter.PaymentOption?> {
94
+ if (overridesFetchSelectedPaymentOption) {
95
+ CompletableDeferred<String?>().also {
96
+ fetchSelectedPaymentOptionCallback = it
97
+ emitEvent("onCustomerAdapterFetchSelectedPaymentOptionCallback", Arguments.createMap())
98
+ val resultFromJavascript = it.await()
99
+ return CustomerAdapter.Result.success(
100
+ if (resultFromJavascript != null) {
101
+ CustomerAdapter.PaymentOption.fromId(resultFromJavascript)
102
+ } else {
103
+ null
104
+ }
105
+ )
106
+ }
107
+ }
108
+
109
+ return adapter.retrieveSelectedPaymentOption()
110
+ }
111
+
112
+ override suspend fun setupIntentClientSecretForCustomerAttach(): CustomerAdapter.Result<String> {
113
+ if (overridesSetupIntentClientSecretForCustomerAttach) {
114
+ CompletableDeferred<String>().also {
115
+ setupIntentClientSecretForCustomerAttachCallback = it
116
+ emitEvent("onCustomerAdapterSetupIntentClientSecretForCustomerAttachCallback", Arguments.createMap())
117
+ val resultFromJavascript = it.await()
118
+ return CustomerAdapter.Result.success(resultFromJavascript)
119
+ }
120
+ }
121
+
122
+ return adapter.setupIntentClientSecretForCustomerAttach()
123
+ }
124
+
125
+ private fun emitEvent(eventName: String, params: WritableMap) {
126
+ val stripeSdkModule: StripeSdkModule? = context.getNativeModule(StripeSdkModule::class.java)
127
+ if (stripeSdkModule == null || stripeSdkModule.eventListenerCount == 0) {
128
+ Log.e(
129
+ "StripeReactNative",
130
+ "Tried to call $eventName, but no callback was found. Please file an issue: https://github.com/stripe/stripe-react-native/issues"
131
+ )
132
+ }
133
+
134
+ stripeSdkModule?.sendEvent(context, eventName, params)
135
+ }
136
+ }
137
+
138
+
@@ -850,9 +850,15 @@ fun toBundleObject(readableMap: ReadableMap?): Bundle {
850
850
  ReadableType.Null -> result.putString(key, null)
851
851
  ReadableType.Boolean -> result.putBoolean(key, readableMap.getBoolean(key))
852
852
  ReadableType.Number -> try {
853
- result.putInt(key, readableMap.getInt(key))
853
+ val numAsInt = readableMap.getInt(key)
854
+ val numAsDouble = readableMap.getDouble(key)
855
+ if (numAsDouble - numAsInt != 0.0) {
856
+ result.putDouble(key, numAsDouble)
857
+ } else {
858
+ result.putInt(key, numAsInt)
859
+ }
854
860
  } catch (e: Exception) {
855
- result.putDouble(key, readableMap.getDouble(key))
861
+ Log.e("toBundleException", "Failed to add number to bundle. Failed on: $key.")
856
862
  }
857
863
  ReadableType.String -> result.putString(key, readableMap.getString(key))
858
864
  ReadableType.Map -> result.putBundle(key, toBundleObject(readableMap.getMap(key)))
@@ -0,0 +1,181 @@
1
+ //
2
+ // CustomerSheetUtils.swift
3
+ // stripe-react-native
4
+ //
5
+ // Created by Charles Cruzan on 08/28/23.
6
+ //
7
+
8
+ import Foundation
9
+ @_spi(PrivateBetaCustomerSheet) import StripePaymentSheet
10
+
11
+ class CustomerSheetUtils {
12
+ internal class func buildCustomerSheetConfiguration(
13
+ appearance: PaymentSheet.Appearance,
14
+ style: PaymentSheet.UserInterfaceStyle,
15
+ removeSavedPaymentMethodMessage: String?,
16
+ returnURL: String?,
17
+ headerTextForSelectionScreen: String?,
18
+ applePayEnabled: Bool?,
19
+ merchantDisplayName: String?,
20
+ billingDetailsCollectionConfiguration: NSDictionary?,
21
+ defaultBillingDetails: NSDictionary?
22
+ ) -> CustomerSheet.Configuration {
23
+ var config = CustomerSheet.Configuration()
24
+ config.appearance = appearance
25
+ config.style = style
26
+ config.removeSavedPaymentMethodMessage = removeSavedPaymentMethodMessage
27
+ config.returnURL = returnURL
28
+ config.headerTextForSelectionScreen = headerTextForSelectionScreen
29
+ config.applePayEnabled = applePayEnabled ?? false
30
+ if let merchantDisplayName = merchantDisplayName {
31
+ config.merchantDisplayName = merchantDisplayName
32
+ }
33
+ if let billingConfigParams = billingDetailsCollectionConfiguration {
34
+ config.billingDetailsCollectionConfiguration.name = StripeSdk.mapToCollectionMode(str: billingConfigParams["name"] as? String)
35
+ config.billingDetailsCollectionConfiguration.phone = StripeSdk.mapToCollectionMode(str: billingConfigParams["phone"] as? String)
36
+ config.billingDetailsCollectionConfiguration.email = StripeSdk.mapToCollectionMode(str: billingConfigParams["email"] as? String)
37
+ config.billingDetailsCollectionConfiguration.address = StripeSdk.mapToAddressCollectionMode(str: billingConfigParams["address"] as? String)
38
+ config.billingDetailsCollectionConfiguration.attachDefaultsToPaymentMethod = billingConfigParams["attachDefaultsToPaymentMethod"] as? Bool == true
39
+ }
40
+ if let defaultBillingDetails = defaultBillingDetails {
41
+ config.defaultBillingDetails.name = defaultBillingDetails["name"] as? String
42
+ config.defaultBillingDetails.email = defaultBillingDetails["email"] as? String
43
+ config.defaultBillingDetails.phone = defaultBillingDetails["phone"] as? String
44
+ if let address = defaultBillingDetails["address"] as? [String: String] {
45
+ config.defaultBillingDetails.address = .init(city: address["city"],
46
+ country: address["country"],
47
+ line1: address["line1"],
48
+ line2: address["line2"],
49
+ postalCode: address["postalCode"],
50
+ state: address["state"])
51
+ }
52
+ }
53
+ return config
54
+ }
55
+
56
+ internal class func buildStripeCustomerAdapter(
57
+ customerId: String,
58
+ ephemeralKeySecret: String,
59
+ setupIntentClientSecret: String?,
60
+ customerAdapter: NSDictionary,
61
+ stripeSdk: StripeSdk
62
+ ) -> StripeCustomerAdapter {
63
+ if (customerAdapter.count > 0) {
64
+ return buildCustomerAdapterOverride(
65
+ customerAdapter: customerAdapter,
66
+ customerId: customerId,
67
+ ephemeralKeySecret: ephemeralKeySecret,
68
+ setupIntentClientSecret: setupIntentClientSecret,
69
+ stripeSdk: stripeSdk
70
+ )
71
+ }
72
+
73
+ if let setupIntentClientSecret = setupIntentClientSecret {
74
+ return StripeCustomerAdapter(
75
+ customerEphemeralKeyProvider: {
76
+ return CustomerEphemeralKey(customerId: customerId, ephemeralKeySecret: ephemeralKeySecret)
77
+ },
78
+ setupIntentClientSecretProvider: {
79
+ return setupIntentClientSecret
80
+ }
81
+ )
82
+ }
83
+
84
+ return StripeCustomerAdapter(
85
+ customerEphemeralKeyProvider: {
86
+ return CustomerEphemeralKey(customerId: customerId, ephemeralKeySecret: ephemeralKeySecret)
87
+ }
88
+ )
89
+ }
90
+
91
+ internal class func buildCustomerAdapterOverride(
92
+ customerAdapter: NSDictionary,
93
+ customerId: String,
94
+ ephemeralKeySecret: String,
95
+ setupIntentClientSecret: String?,
96
+ stripeSdk: StripeSdk
97
+ ) -> StripeCustomerAdapter {
98
+ return ReactNativeCustomerAdapter(
99
+ fetchPaymentMethods: customerAdapter["fetchPaymentMethods"] as? Bool ?? false,
100
+ attachPaymentMethod: customerAdapter["attachPaymentMethod"] as? Bool ?? false,
101
+ detachPaymentMethod: customerAdapter["detachPaymentMethod"] as? Bool ?? false,
102
+ setSelectedPaymentOption: customerAdapter["setSelectedPaymentOption"] as? Bool ?? false,
103
+ fetchSelectedPaymentOption: customerAdapter["fetchSelectedPaymentOption"] as? Bool ?? false,
104
+ setupIntentClientSecretForCustomerAttach: customerAdapter["setupIntentClientSecretForCustomerAttach"] as? Bool ?? false,
105
+ customerId: customerId,
106
+ ephemeralKeySecret: ephemeralKeySecret,
107
+ setupIntentClientSecret: setupIntentClientSecret,
108
+ stripeSdk: stripeSdk
109
+ )
110
+ }
111
+
112
+ internal class func getModalPresentationStyle(_ string: String?) -> UIModalPresentationStyle {
113
+ switch (string) {
114
+ case "fullscreen":
115
+ return .fullScreen
116
+ case "popover":
117
+ fallthrough
118
+ default:
119
+ return .popover
120
+ }
121
+ }
122
+
123
+ internal class func getModalTransitionStyle(_ string: String?) -> UIModalTransitionStyle {
124
+ switch (string) {
125
+ case "flip":
126
+ return .flipHorizontal
127
+ case "curl":
128
+ return .partialCurl
129
+ case "dissolve":
130
+ return .crossDissolve
131
+ case "slide":
132
+ fallthrough
133
+ default:
134
+ return .coverVertical
135
+ }
136
+ }
137
+
138
+
139
+ internal class func buildPaymentOptionResult(label: String, imageData: String?, paymentMethod: STPPaymentMethod?) -> NSMutableDictionary {
140
+ let result: NSMutableDictionary = [:]
141
+ let paymentOption: NSMutableDictionary = [:]
142
+ paymentOption.setValue(label, forKey: "label")
143
+ if (imageData != nil) {
144
+ paymentOption.setValue(imageData, forKey: "image")
145
+ }
146
+ result.setValue(paymentOption, forKey: "paymentOption")
147
+ if (paymentMethod != nil) {
148
+ result.setValue(Mappers.mapFromPaymentMethod(paymentMethod), forKey: "paymentMethod")
149
+ }
150
+ return result
151
+ }
152
+
153
+ internal class func interpretResult(result: CustomerSheet.CustomerSheetResult) -> NSDictionary {
154
+ var payload: NSMutableDictionary = [:]
155
+ switch result {
156
+ case .error(let error):
157
+ return Errors.createError(ErrorType.Failed, error as NSError)
158
+ case .selected(let paymentOption):
159
+ switch paymentOption {
160
+ case .applePay(let paymentOptionDisplayData):
161
+ payload = CustomerSheetUtils.buildPaymentOptionResult(label: paymentOptionDisplayData.label, imageData: paymentOptionDisplayData.image.pngData()?.base64EncodedString(), paymentMethod: nil)
162
+ case .paymentMethod(let paymentMethod, let paymentOptionDisplayData):
163
+ payload = CustomerSheetUtils.buildPaymentOptionResult(label: paymentOptionDisplayData.label, imageData: paymentOptionDisplayData.image.pngData()?.base64EncodedString(), paymentMethod: paymentMethod)
164
+ case .none:
165
+ break
166
+ }
167
+ case .canceled(let paymentOption):
168
+ switch paymentOption {
169
+ case .applePay(let paymentOptionDisplayData):
170
+ payload = CustomerSheetUtils.buildPaymentOptionResult(label: paymentOptionDisplayData.label, imageData: paymentOptionDisplayData.image.pngData()?.base64EncodedString(), paymentMethod: nil)
171
+ case .paymentMethod(let paymentMethod, let paymentOptionDisplayData):
172
+ payload = CustomerSheetUtils.buildPaymentOptionResult(label: paymentOptionDisplayData.label, imageData: paymentOptionDisplayData.image.pngData()?.base64EncodedString(), paymentMethod: paymentMethod)
173
+ case .none:
174
+ break
175
+ }
176
+ payload.setValue(["code": ErrorType.Canceled], forKey: "error")
177
+ }
178
+ return payload
179
+ }
180
+
181
+ }