@stripe/stripe-react-native 0.61.0 → 0.63.0

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 (111) hide show
  1. package/android/gradle.properties +2 -2
  2. package/android/src/main/java/com/reactnativestripesdk/EventEmitterCompat.kt +4 -0
  3. package/android/src/main/java/com/reactnativestripesdk/FakeOnrampSdkModule.kt +6 -0
  4. package/android/src/main/java/com/reactnativestripesdk/PaymentSheetManager.kt +200 -198
  5. package/android/src/main/java/com/reactnativestripesdk/StripeSdkModule.kt +3 -1
  6. package/android/src/main/java/com/reactnativestripesdk/pushprovisioning/AddToWalletButtonView.kt +17 -16
  7. package/android/src/oldarch/java/com/reactnativestripesdk/NativeOnrampSdkModuleSpec.java +8 -11
  8. package/android/src/onramp/java/com/reactnativestripesdk/OnrampMappers.kt +4 -0
  9. package/android/src/onramp/java/com/reactnativestripesdk/OnrampSdkModule.kt +16 -1
  10. package/android/src/test/java/com/reactnativestripesdk/mappers/OnrampMappersTest.kt +3 -0
  11. package/ios/Mappers.swift +15 -2
  12. package/ios/StripeOnrampSdk.h +3 -4
  13. package/lib/commonjs/components/AddToWalletButton.js +1 -1
  14. package/lib/commonjs/components/AddToWalletButton.js.map +1 -1
  15. package/lib/commonjs/components/AddressSheet.js +1 -1
  16. package/lib/commonjs/components/AddressSheet.js.map +1 -1
  17. package/lib/commonjs/components/AuBECSDebitForm.js +1 -1
  18. package/lib/commonjs/components/AuBECSDebitForm.js.map +1 -1
  19. package/lib/commonjs/components/CardField.js +1 -1
  20. package/lib/commonjs/components/CardField.js.map +1 -1
  21. package/lib/commonjs/components/CardForm.js +1 -1
  22. package/lib/commonjs/components/CardForm.js.map +1 -1
  23. package/lib/commonjs/components/PlatformPayButton.js +1 -1
  24. package/lib/commonjs/components/PlatformPayButton.js.map +1 -1
  25. package/lib/commonjs/components/StripeContainer.js +1 -1
  26. package/lib/commonjs/components/StripeContainer.js.map +1 -1
  27. package/lib/commonjs/connect/Components.js +1 -1
  28. package/lib/commonjs/connect/Components.js.map +1 -1
  29. package/lib/commonjs/connect/ConnectComponentsProvider.js +1 -1
  30. package/lib/commonjs/connect/ConnectComponentsProvider.js.map +1 -1
  31. package/lib/commonjs/connect/EmbeddedComponent.js +1 -1
  32. package/lib/commonjs/connect/EmbeddedComponent.js.map +1 -1
  33. package/lib/commonjs/connect/ModalCloseButton.js +1 -1
  34. package/lib/commonjs/connect/ModalCloseButton.js.map +1 -1
  35. package/lib/commonjs/connect/NavigationBar.js +1 -1
  36. package/lib/commonjs/connect/NavigationBar.js.map +1 -1
  37. package/lib/commonjs/events.js +1 -1
  38. package/lib/commonjs/events.js.map +1 -1
  39. package/lib/commonjs/helpers.js +1 -1
  40. package/lib/commonjs/specs/NativeAddToWalletButton.js +1 -1
  41. package/lib/commonjs/specs/NativeAddressSheet.js +1 -1
  42. package/lib/commonjs/specs/NativeApplePayButton.js +1 -1
  43. package/lib/commonjs/specs/NativeAuBECSDebitForm.js +1 -1
  44. package/lib/commonjs/specs/NativeCardField.js +1 -1
  45. package/lib/commonjs/specs/NativeCardField.js.map +1 -1
  46. package/lib/commonjs/specs/NativeCardForm.js +1 -1
  47. package/lib/commonjs/specs/NativeCardForm.js.map +1 -1
  48. package/lib/commonjs/specs/NativeConnectAccountOnboardingView.js +1 -1
  49. package/lib/commonjs/specs/NativeEmbeddedPaymentElement.js +1 -1
  50. package/lib/commonjs/specs/NativeEmbeddedPaymentElement.js.map +1 -1
  51. package/lib/commonjs/specs/NativeGooglePayButton.js +1 -1
  52. package/lib/commonjs/specs/NativeNavigationBar.js +1 -1
  53. package/lib/commonjs/specs/NativePaymentMethodMessagingElement.js +1 -1
  54. package/lib/commonjs/specs/NativeStripeContainer.js +1 -1
  55. package/lib/commonjs/types/EmbeddedPaymentElement.js +1 -1
  56. package/lib/commonjs/types/EmbeddedPaymentElement.js.map +1 -1
  57. package/lib/module/components/AddToWalletButton.js +1 -1
  58. package/lib/module/components/AddToWalletButton.js.map +1 -1
  59. package/lib/module/components/AddressSheet.js +1 -1
  60. package/lib/module/components/AddressSheet.js.map +1 -1
  61. package/lib/module/components/AuBECSDebitForm.js +1 -1
  62. package/lib/module/components/AuBECSDebitForm.js.map +1 -1
  63. package/lib/module/components/CardField.js +1 -1
  64. package/lib/module/components/CardField.js.map +1 -1
  65. package/lib/module/components/CardForm.js +1 -1
  66. package/lib/module/components/CardForm.js.map +1 -1
  67. package/lib/module/components/PlatformPayButton.js +1 -1
  68. package/lib/module/components/PlatformPayButton.js.map +1 -1
  69. package/lib/module/components/StripeContainer.js +1 -1
  70. package/lib/module/components/StripeContainer.js.map +1 -1
  71. package/lib/module/connect/Components.js +1 -1
  72. package/lib/module/connect/Components.js.map +1 -1
  73. package/lib/module/connect/ConnectComponentsProvider.js +1 -1
  74. package/lib/module/connect/ConnectComponentsProvider.js.map +1 -1
  75. package/lib/module/connect/EmbeddedComponent.js +1 -1
  76. package/lib/module/connect/EmbeddedComponent.js.map +1 -1
  77. package/lib/module/connect/ModalCloseButton.js +1 -1
  78. package/lib/module/connect/ModalCloseButton.js.map +1 -1
  79. package/lib/module/connect/NavigationBar.js +1 -1
  80. package/lib/module/connect/NavigationBar.js.map +1 -1
  81. package/lib/module/events.js +1 -1
  82. package/lib/module/events.js.map +1 -1
  83. package/lib/module/helpers.js +1 -1
  84. package/lib/module/specs/NativeAddToWalletButton.js +1 -1
  85. package/lib/module/specs/NativeAddressSheet.js +1 -1
  86. package/lib/module/specs/NativeApplePayButton.js +1 -1
  87. package/lib/module/specs/NativeAuBECSDebitForm.js +1 -1
  88. package/lib/module/specs/NativeCardField.js +1 -1
  89. package/lib/module/specs/NativeCardField.js.map +1 -1
  90. package/lib/module/specs/NativeCardForm.js +1 -1
  91. package/lib/module/specs/NativeCardForm.js.map +1 -1
  92. package/lib/module/specs/NativeConnectAccountOnboardingView.js +1 -1
  93. package/lib/module/specs/NativeEmbeddedPaymentElement.js +1 -1
  94. package/lib/module/specs/NativeEmbeddedPaymentElement.js.map +1 -1
  95. package/lib/module/specs/NativeGooglePayButton.js +1 -1
  96. package/lib/module/specs/NativeNavigationBar.js +1 -1
  97. package/lib/module/specs/NativePaymentMethodMessagingElement.js +1 -1
  98. package/lib/module/specs/NativeStripeContainer.js +1 -1
  99. package/lib/module/types/EmbeddedPaymentElement.js +1 -1
  100. package/lib/module/types/EmbeddedPaymentElement.js.map +1 -1
  101. package/lib/typescript/src/connect/connectTypes.d.ts +76 -0
  102. package/lib/typescript/src/connect/connectTypes.d.ts.map +1 -1
  103. package/lib/typescript/src/events.d.ts +5 -3
  104. package/lib/typescript/src/events.d.ts.map +1 -1
  105. package/lib/typescript/src/specs/NativeOnrampSdkModule.d.ts +2 -2
  106. package/lib/typescript/src/specs/NativeOnrampSdkModule.d.ts.map +1 -1
  107. package/package.json +1 -1
  108. package/src/connect/connectTypes.ts +88 -0
  109. package/src/events.ts +10 -13
  110. package/src/specs/NativeOnrampSdkModule.ts +2 -2
  111. package/stripe-react-native.podspec +1 -1
@@ -1,6 +1,6 @@
1
- StripeSdk_kotlinVersion=2.3.10
1
+ StripeSdk_kotlinVersion=2.2.21
2
2
  StripeSdk_compileSdkVersion=30
3
3
  StripeSdk_targetSdkVersion=28
4
4
  StripeSdk_minSdkVersion=21
5
5
  # Keep StripeSdk_stripeVersion in sync with https://github.com/stripe/stripe-identity-react-native/blob/main/android/gradle.properties
6
- StripeSdk_stripeVersion=23.1.+
6
+ StripeSdk_stripeVersion=23.3.+
@@ -99,4 +99,8 @@ class EventEmitterCompat(
99
99
  fun emitPaymentMethodMessagingElementConfigureResult(value: ReadableMap?) {
100
100
  invoke("paymentMethodMessagingElementConfigureResult", value)
101
101
  }
102
+
103
+ fun emitOnCheckoutClientSecretRequested(value: ReadableMap?) {
104
+ invoke("onCheckoutClientSecretRequested", value)
105
+ }
102
106
  }
@@ -137,6 +137,12 @@ class FakeOnrampSdkModule(
137
137
  promise.resolveNotImplemented()
138
138
  }
139
139
 
140
+ @ReactMethod
141
+ override fun addListener(eventType: String?) {}
142
+
143
+ @ReactMethod
144
+ override fun removeListeners(count: Double) {}
145
+
140
146
  private fun Promise.resolveNotImplemented() {
141
147
  this.resolve(
142
148
  createFailedError(
@@ -86,209 +86,15 @@ class PaymentSheetManager(
86
86
  internal var paymentSheetIntentCreationCallback = CompletableDeferred<ReadableMap>()
87
87
  internal var paymentSheetConfirmationTokenCreationCallback = CompletableDeferred<ReadableMap>()
88
88
  private var keepJsAwake: KeepJsAwakeTask? = null
89
+ private var lastConfigureWasCustomFlow: Boolean? = null
89
90
 
90
91
  @SuppressLint("RestrictedApi")
91
92
  override fun onCreate() {
92
- val activity = getCurrentActivityOrResolveWithError(initPromise) ?: return
93
- val merchantDisplayName = arguments.getString("merchantDisplayName").orEmpty()
94
- if (merchantDisplayName.isEmpty()) {
95
- initPromise.resolve(
96
- createError(ErrorType.Failed.toString(), "merchantDisplayName cannot be empty or null."),
97
- )
98
- return
99
- }
100
- paymentIntentClientSecret = arguments.getString("paymentIntentClientSecret").orEmpty()
101
- setupIntentClientSecret = arguments.getString("setupIntentClientSecret").orEmpty()
102
- intentConfiguration =
103
- try {
104
- buildIntentConfiguration(arguments.getMap("intentConfiguration"))
105
- } catch (error: PaymentSheetException) {
106
- initPromise.resolve(createError(ErrorType.Failed.toString(), error))
107
- return
108
- }
109
-
110
- // Determine which callback type to use based on what's provided
111
- val intentConfigMap = arguments.getMap("intentConfiguration")
112
- val useConfirmationTokenCallback = intentConfigMap?.hasKey("confirmationTokenConfirmHandler") == true
113
-
114
- val paymentOptionCallback =
115
- PaymentOptionResultCallback { paymentOptionResult ->
116
- paymentOptionResult.paymentOption?.let { paymentOption ->
117
- // Convert drawable to bitmap asynchronously to avoid shared state issues
118
- CoroutineScope(Dispatchers.Default).launch {
119
- val imageString =
120
- try {
121
- convertDrawableToBase64(paymentOption.icon())
122
- } catch (e: Exception) {
123
- val result =
124
- createError(
125
- PaymentSheetErrorType.Failed.toString(),
126
- "Failed to process payment option image: ${e.message}",
127
- )
128
- resolvePresentPromise(result)
129
- return@launch
130
- }
131
-
132
- val option: WritableMap = Arguments.createMap()
133
- option.putString("label", paymentOption.label)
134
- option.putString("image", imageString)
135
- val additionalFields: Map<String, Any> = mapOf("didCancel" to paymentOptionResult.didCancel)
136
- val result = createResult("paymentOption", option, additionalFields)
137
- resolvePresentPromise(result)
138
- }
139
- } ?: run {
140
- val result =
141
- if (paymentSheetTimedOut) {
142
- paymentSheetTimedOut = false
143
- createError(PaymentSheetErrorType.Timeout.toString(), "The payment has timed out")
144
- } else {
145
- createError(
146
- PaymentSheetErrorType.Canceled.toString(),
147
- "The payment option selection flow has been canceled",
148
- )
149
- }
150
- resolvePresentPromise(result)
151
- }
152
- }
153
-
154
- val paymentResultCallback =
155
- PaymentSheetResultCallback { paymentResult ->
156
- if (paymentSheetTimedOut) {
157
- paymentSheetTimedOut = false
158
- resolvePaymentResult(
159
- createError(PaymentSheetErrorType.Timeout.toString(), "The payment has timed out"),
160
- )
161
- } else {
162
- when (paymentResult) {
163
- is PaymentSheetResult.Canceled -> {
164
- resolvePaymentResult(
165
- createError(
166
- PaymentSheetErrorType.Canceled.toString(),
167
- "The payment flow has been canceled",
168
- ),
169
- )
170
- }
171
-
172
- is PaymentSheetResult.Failed -> {
173
- resolvePaymentResult(
174
- createError(PaymentSheetErrorType.Failed.toString(), paymentResult.error),
175
- )
176
- }
177
-
178
- is PaymentSheetResult.Completed -> {
179
- resolvePaymentResult(Arguments.createMap())
180
- }
181
- }
182
- }
183
- }
184
-
185
- val createIntentCallback =
186
- CreateIntentCallback { paymentMethod, shouldSavePaymentMethod ->
187
- val stripeSdkModule: StripeSdkModule? = context.getNativeModule(StripeSdkModule::class.java)
188
- val params =
189
- Arguments.createMap().apply {
190
- putMap("paymentMethod", mapFromPaymentMethod(paymentMethod))
191
- putBoolean("shouldSavePaymentMethod", shouldSavePaymentMethod)
192
- }
193
-
194
- stripeSdkModule?.eventEmitter?.emitOnConfirmHandlerCallback(params)
195
-
196
- val resultFromJavascript = paymentSheetIntentCreationCallback.await()
197
- // reset the completable
198
- paymentSheetIntentCreationCallback = CompletableDeferred<ReadableMap>()
199
-
200
- return@CreateIntentCallback resultFromJavascript.getString("clientSecret")?.let {
201
- CreateIntentResult.Success(clientSecret = it)
202
- }
203
- ?: run {
204
- val errorMap = resultFromJavascript.getMap("error")
205
- CreateIntentResult.Failure(
206
- cause = Exception(errorMap?.getString("message")),
207
- displayMessage = errorMap?.getString("localizedMessage"),
208
- )
209
- }
210
- }
211
-
212
- val createConfirmationTokenCallback =
213
- CreateIntentWithConfirmationTokenCallback { confirmationToken ->
214
- val stripeSdkModule: StripeSdkModule? = context.getNativeModule(StripeSdkModule::class.java)
215
- val params =
216
- Arguments.createMap().apply {
217
- putMap("confirmationToken", mapFromConfirmationToken(confirmationToken))
218
- }
219
-
220
- stripeSdkModule?.eventEmitter?.emitOnConfirmationTokenHandlerCallback(params)
221
-
222
- val resultFromJavascript = paymentSheetConfirmationTokenCreationCallback.await()
223
- // reset the completable
224
- paymentSheetConfirmationTokenCreationCallback = CompletableDeferred<ReadableMap>()
225
-
226
- return@CreateIntentWithConfirmationTokenCallback resultFromJavascript.getString("clientSecret")?.let {
227
- CreateIntentResult.Success(clientSecret = it)
228
- }
229
- ?: run {
230
- val errorMap = resultFromJavascript.getMap("error")
231
- CreateIntentResult.Failure(
232
- cause = Exception(errorMap?.getString("message")),
233
- displayMessage = errorMap?.getString("localizedMessage"),
234
- )
235
- }
236
- }
237
-
238
- if (arguments.getBooleanOr("customFlow", false)) {
239
- if (flowController == null) {
240
- flowController =
241
- if (intentConfiguration != null) {
242
- val builder =
243
- PaymentSheet.FlowController
244
- .Builder(
245
- resultCallback = paymentResultCallback,
246
- paymentOptionResultCallback = paymentOptionCallback,
247
- )
248
- if (useConfirmationTokenCallback) {
249
- builder.createIntentCallback(createConfirmationTokenCallback)
250
- } else {
251
- builder.createIntentCallback(createIntentCallback)
252
- }
253
- builder
254
- .confirmCustomPaymentMethodCallback(this)
255
- .build(activity)
256
- } else {
257
- PaymentSheet.FlowController
258
- .Builder(
259
- resultCallback = paymentResultCallback,
260
- paymentOptionResultCallback = paymentOptionCallback,
261
- ).confirmCustomPaymentMethodCallback(this)
262
- .build(activity)
263
- }
264
- }
265
- } else {
266
- if (paymentSheet == null) {
267
- paymentSheet =
268
- if (intentConfiguration != null) {
269
- val builder = PaymentSheet.Builder(paymentResultCallback)
270
- if (useConfirmationTokenCallback) {
271
- builder.createIntentCallback(createConfirmationTokenCallback)
272
- } else {
273
- builder.createIntentCallback(createIntentCallback)
274
- }
275
- @SuppressLint("RestrictedApi")
276
- builder
277
- .confirmCustomPaymentMethodCallback(this)
278
- .build(activity, signal)
279
- } else {
280
- @SuppressLint("RestrictedApi")
281
- PaymentSheet
282
- .Builder(paymentResultCallback)
283
- .confirmCustomPaymentMethodCallback(this)
284
- .build(activity, signal)
285
- }
286
- }
287
- }
288
93
  configure(arguments, initPromise)
289
94
  }
290
95
 
291
96
  override fun onDestroy() {
97
+ super.onDestroy()
292
98
  flowController = null
293
99
  paymentSheet = null
294
100
  }
@@ -380,15 +186,211 @@ class PaymentSheetManager(
380
186
 
381
187
  paymentSheetConfiguration = configurationBuilder.build()
382
188
  if (args.getBooleanOr("customFlow", false)) {
189
+ lastConfigureWasCustomFlow = true
190
+ if (flowController == null) {
191
+ initFlowController(args, promise)
192
+ }
383
193
  configureFlowController(promise)
384
194
  } else {
195
+ lastConfigureWasCustomFlow = false
196
+ if (paymentSheet == null) {
197
+ initPaymentSheet(args, promise)
198
+ }
385
199
  promise.resolve(Arguments.createMap())
386
200
  }
387
201
  }
388
202
 
203
+ private fun initPaymentSheet(
204
+ args: ReadableMap,
205
+ promise: Promise,
206
+ ) {
207
+ val activity = getCurrentActivityOrResolveWithError(promise) ?: return
208
+ val intentConfigMap = args.getMap("intentConfiguration")
209
+ val useConfirmationTokenCallback = intentConfigMap?.hasKey("confirmationTokenConfirmHandler") == true
210
+ paymentSheet =
211
+ if (intentConfiguration != null) {
212
+ val builder = PaymentSheet.Builder(buildPaymentSheetResultCallback())
213
+ if (useConfirmationTokenCallback) {
214
+ builder.createIntentCallback(buildCreateConfirmationTokenCallback())
215
+ } else {
216
+ builder.createIntentCallback(buildIntentCreationCallback())
217
+ }
218
+ @SuppressLint("RestrictedApi")
219
+ builder
220
+ .confirmCustomPaymentMethodCallback(this)
221
+ .build(activity, signal)
222
+ } else {
223
+ @SuppressLint("RestrictedApi")
224
+ PaymentSheet
225
+ .Builder(buildPaymentSheetResultCallback())
226
+ .confirmCustomPaymentMethodCallback(this)
227
+ .build(activity, signal)
228
+ }
229
+ }
230
+
231
+ private fun initFlowController(
232
+ args: ReadableMap,
233
+ promise: Promise,
234
+ ) {
235
+ val activity = getCurrentActivityOrResolveWithError(promise) ?: return
236
+ val intentConfigMap = args.getMap("intentConfiguration")
237
+ val useConfirmationTokenCallback =
238
+ intentConfigMap?.hasKey("confirmationTokenConfirmHandler") == true
239
+ flowController =
240
+ if (intentConfiguration != null) {
241
+ val builder =
242
+ PaymentSheet.FlowController
243
+ .Builder(
244
+ resultCallback = buildPaymentSheetResultCallback(),
245
+ paymentOptionResultCallback = buildPaymentOptionCallback(),
246
+ )
247
+ if (useConfirmationTokenCallback) {
248
+ builder.createIntentCallback(buildCreateConfirmationTokenCallback())
249
+ } else {
250
+ builder.createIntentCallback(buildIntentCreationCallback())
251
+ }
252
+ builder.confirmCustomPaymentMethodCallback(this)
253
+ builder.build(activity)
254
+ } else {
255
+ PaymentSheet.FlowController
256
+ .Builder(
257
+ resultCallback = buildPaymentSheetResultCallback(),
258
+ paymentOptionResultCallback = buildPaymentOptionCallback(),
259
+ ).confirmCustomPaymentMethodCallback(this)
260
+ .build(activity)
261
+ }
262
+ }
263
+
264
+ private fun buildCreateConfirmationTokenCallback(): CreateIntentWithConfirmationTokenCallback {
265
+ return CreateIntentWithConfirmationTokenCallback { confirmationToken ->
266
+ val stripeSdkModule: StripeSdkModule? = context.getNativeModule(StripeSdkModule::class.java)
267
+ val params =
268
+ Arguments.createMap().apply {
269
+ putMap("confirmationToken", mapFromConfirmationToken(confirmationToken))
270
+ }
271
+
272
+ stripeSdkModule?.eventEmitter?.emitOnConfirmationTokenHandlerCallback(params)
273
+
274
+ val resultFromJavascript = paymentSheetConfirmationTokenCreationCallback.await()
275
+ // reset the completable
276
+ paymentSheetConfirmationTokenCreationCallback = CompletableDeferred<ReadableMap>()
277
+
278
+ return@CreateIntentWithConfirmationTokenCallback resultFromJavascript.getString("clientSecret")?.let {
279
+ CreateIntentResult.Success(clientSecret = it)
280
+ }
281
+ ?: run {
282
+ val errorMap = resultFromJavascript.getMap("error")
283
+ CreateIntentResult.Failure(
284
+ cause = Exception(errorMap?.getString("message")),
285
+ displayMessage = errorMap?.getString("localizedMessage"),
286
+ )
287
+ }
288
+ }
289
+ }
290
+
291
+ private fun buildIntentCreationCallback(): CreateIntentCallback {
292
+ return CreateIntentCallback { paymentMethod, shouldSavePaymentMethod ->
293
+ val stripeSdkModule: StripeSdkModule? = context.getNativeModule(StripeSdkModule::class.java)
294
+ val params =
295
+ Arguments.createMap().apply {
296
+ putMap("paymentMethod", mapFromPaymentMethod(paymentMethod))
297
+ putBoolean("shouldSavePaymentMethod", shouldSavePaymentMethod)
298
+ }
299
+
300
+ stripeSdkModule?.eventEmitter?.emitOnConfirmHandlerCallback(params)
301
+
302
+ val resultFromJavascript = paymentSheetIntentCreationCallback.await()
303
+ // reset the completable
304
+ paymentSheetIntentCreationCallback = CompletableDeferred<ReadableMap>()
305
+
306
+ return@CreateIntentCallback resultFromJavascript.getString("clientSecret")?.let {
307
+ CreateIntentResult.Success(clientSecret = it)
308
+ }
309
+ ?: run {
310
+ val errorMap = resultFromJavascript.getMap("error")
311
+ CreateIntentResult.Failure(
312
+ cause = Exception(errorMap?.getString("message")),
313
+ displayMessage = errorMap?.getString("localizedMessage"),
314
+ )
315
+ }
316
+ }
317
+ }
318
+
319
+ private fun buildPaymentSheetResultCallback(): PaymentSheetResultCallback =
320
+ PaymentSheetResultCallback { paymentResult ->
321
+ if (paymentSheetTimedOut) {
322
+ paymentSheetTimedOut = false
323
+ resolvePaymentResult(
324
+ createError(PaymentSheetErrorType.Timeout.toString(), "The payment has timed out"),
325
+ )
326
+ } else {
327
+ when (paymentResult) {
328
+ is PaymentSheetResult.Canceled -> {
329
+ resolvePaymentResult(
330
+ createError(
331
+ PaymentSheetErrorType.Canceled.toString(),
332
+ "The payment flow has been canceled",
333
+ ),
334
+ )
335
+ }
336
+
337
+ is PaymentSheetResult.Failed -> {
338
+ resolvePaymentResult(
339
+ createError(PaymentSheetErrorType.Failed.toString(), paymentResult.error),
340
+ )
341
+ }
342
+
343
+ is PaymentSheetResult.Completed -> {
344
+ resolvePaymentResult(Arguments.createMap())
345
+ }
346
+ }
347
+ }
348
+ }
349
+
350
+ private fun buildPaymentOptionCallback(): PaymentOptionResultCallback {
351
+ return PaymentOptionResultCallback { paymentOptionResult ->
352
+ paymentOptionResult.paymentOption?.let { paymentOption ->
353
+ // Convert drawable to bitmap asynchronously to avoid shared state issues
354
+ CoroutineScope(Dispatchers.Default).launch {
355
+ val imageString =
356
+ try {
357
+ convertDrawableToBase64(paymentOption.icon())
358
+ } catch (e: Exception) {
359
+ val result =
360
+ createError(
361
+ PaymentSheetErrorType.Failed.toString(),
362
+ "Failed to process payment option image: ${e.message}",
363
+ )
364
+ resolvePresentPromise(result)
365
+ return@launch
366
+ }
367
+
368
+ val option: WritableMap = Arguments.createMap()
369
+ option.putString("label", paymentOption.label)
370
+ option.putString("image", imageString)
371
+ val additionalFields: Map<String, Any> = mapOf("didCancel" to paymentOptionResult.didCancel)
372
+ val result = createResult("paymentOption", option, additionalFields)
373
+ resolvePresentPromise(result)
374
+ }
375
+ } ?: run {
376
+ val result =
377
+ if (paymentSheetTimedOut) {
378
+ paymentSheetTimedOut = false
379
+ createError(PaymentSheetErrorType.Timeout.toString(), "The payment has timed out")
380
+ } else {
381
+ createError(
382
+ PaymentSheetErrorType.Canceled.toString(),
383
+ "The payment option selection flow has been canceled",
384
+ )
385
+ }
386
+ resolvePresentPromise(result)
387
+ }
388
+ }
389
+ }
390
+
389
391
  override fun onPresent() {
390
392
  keepJsAwake = KeepJsAwakeTask(context).apply { start() }
391
- if (paymentSheet != null) {
393
+ if (lastConfigureWasCustomFlow == false) {
392
394
  if (!paymentIntentClientSecret.isNullOrEmpty()) {
393
395
  paymentSheet?.presentWithPaymentIntent(
394
396
  paymentIntentClientSecret!!,
@@ -402,7 +404,7 @@ class PaymentSheetManager(
402
404
  configuration = paymentSheetConfiguration,
403
405
  )
404
406
  }
405
- } else if (flowController != null) {
407
+ } else if (lastConfigureWasCustomFlow == true && flowController != null) {
406
408
  flowController?.presentPaymentOptions()
407
409
  } else {
408
410
  promise?.resolve(createMissingInitError())
@@ -258,7 +258,9 @@ class StripeSdkModule(
258
258
  promise: Promise,
259
259
  ) {
260
260
  if (paymentSheetManager != null) {
261
- paymentSheetManager?.configure(params, promise)
261
+ UiThreadUtil.runOnUiThread {
262
+ paymentSheetManager?.configure(params, promise)
263
+ }
262
264
  } else {
263
265
  paymentSheetManager =
264
266
  PaymentSheetManager(reactApplicationContext, params, promise).also {
@@ -2,6 +2,7 @@ package com.reactnativestripesdk.pushprovisioning
2
2
 
3
3
  import android.annotation.SuppressLint
4
4
  import android.content.res.ColorStateList
5
+ import android.graphics.Bitmap
5
6
  import android.graphics.drawable.Drawable
6
7
  import android.graphics.drawable.RippleDrawable
7
8
  import android.view.MotionEvent
@@ -12,10 +13,9 @@ import androidx.core.graphics.toColorInt
12
13
  import androidx.core.net.toUri
13
14
  import com.facebook.common.executors.UiThreadImmediateExecutorService
14
15
  import com.facebook.common.references.CloseableReference
15
- import com.facebook.datasource.BaseDataSubscriber
16
16
  import com.facebook.datasource.DataSource
17
17
  import com.facebook.drawee.backends.pipeline.Fresco
18
- import com.facebook.imagepipeline.image.CloseableBitmap
18
+ import com.facebook.imagepipeline.datasource.BaseBitmapReferenceDataSubscriber
19
19
  import com.facebook.imagepipeline.image.CloseableImage
20
20
  import com.facebook.imagepipeline.request.ImageRequestBuilder
21
21
  import com.facebook.react.bridge.ReadableMap
@@ -36,6 +36,7 @@ class AddToWalletButtonView(
36
36
 
37
37
  private var loadedSource: String? = null
38
38
  private var currentDataSource: DataSource<CloseableReference<CloseableImage>>? = null
39
+ private var currentBitmapReference: CloseableReference<Bitmap>? = null
39
40
 
40
41
  init {
41
42
  scaleType = ScaleType.CENTER_CROP
@@ -118,20 +119,14 @@ class AddToWalletButtonView(
118
119
  currentDataSource = dataSource
119
120
 
120
121
  dataSource.subscribe(
121
- object : BaseDataSubscriber<CloseableReference<CloseableImage>>() {
122
- override fun onNewResultImpl(dataSource: DataSource<CloseableReference<CloseableImage>>) {
123
- if (!dataSource.isFinished) return
124
- val imageRef = dataSource.result ?: return
125
-
126
- try {
127
- val image = imageRef.get()
128
- if (image is CloseableBitmap) {
129
- val drawable = image.underlyingBitmap.toDrawable(resources)
130
- setImageWithRipple(drawable)
131
- }
132
- } finally {
133
- CloseableReference.closeSafely(imageRef)
134
- }
122
+ object : BaseBitmapReferenceDataSubscriber() {
123
+ override fun onNewResultImpl(bitmapReference: CloseableReference<Bitmap>?) {
124
+ val image = bitmapReference?.get() ?: return
125
+
126
+ currentBitmapReference = bitmapReference.cloneOrNull()
127
+
128
+ val drawable = image.toDrawable(resources)
129
+ setImageWithRipple(drawable)
135
130
  }
136
131
 
137
132
  override fun onFailureImpl(dataSource: DataSource<CloseableReference<CloseableImage>>) {
@@ -165,6 +160,12 @@ class AddToWalletButtonView(
165
160
 
166
161
  private fun cancelCurrentRequest() {
167
162
  currentDataSource?.close()
163
+
164
+ currentBitmapReference?.let {
165
+ CloseableReference.closeSafely(it)
166
+ }
167
+
168
+ currentBitmapReference = null
168
169
  currentDataSource = null
169
170
  }
170
171
 
@@ -18,7 +18,6 @@ import com.facebook.react.bridge.ReactApplicationContext;
18
18
  import com.facebook.react.bridge.ReactContextBaseJavaModule;
19
19
  import com.facebook.react.bridge.ReactMethod;
20
20
  import com.facebook.react.bridge.ReadableMap;
21
- import com.facebook.react.modules.core.DeviceEventManagerModule;
22
21
  import com.facebook.react.turbomodule.core.interfaces.TurboModule;
23
22
  import javax.annotation.Nonnull;
24
23
 
@@ -34,16 +33,6 @@ public abstract class NativeOnrampSdkModuleSpec extends ReactContextBaseJavaModu
34
33
  return NAME;
35
34
  }
36
35
 
37
- private void invoke(String eventName, Object params) {
38
- getReactApplicationContext()
39
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
40
- .emit(eventName, params);
41
- }
42
-
43
- protected final void emitOnCheckoutClientSecretRequested(ReadableMap value) {
44
- invoke("onCheckoutClientSecretRequested", value);
45
- }
46
-
47
36
  @ReactMethod
48
37
  @DoNotStrip
49
38
  public abstract void initialise(ReadableMap params, Promise promise);
@@ -111,4 +100,12 @@ public abstract class NativeOnrampSdkModuleSpec extends ReactContextBaseJavaModu
111
100
  @ReactMethod
112
101
  @DoNotStrip
113
102
  public abstract void logout(Promise promise);
103
+
104
+ @ReactMethod
105
+ @DoNotStrip
106
+ public abstract void addListener(String eventType);
107
+
108
+ @ReactMethod
109
+ @DoNotStrip
110
+ public abstract void removeListeners(double count);
114
111
  }
@@ -1,8 +1,11 @@
1
+ @file:OptIn(ExperimentalCryptoOnramp::class)
2
+
1
3
  package com.reactnativestripesdk
2
4
 
3
5
  import android.annotation.SuppressLint
4
6
  import androidx.compose.ui.graphics.Color
5
7
  import com.facebook.react.bridge.ReadableMap
8
+ import com.stripe.android.crypto.onramp.ExperimentalCryptoOnramp
6
9
  import com.stripe.android.crypto.onramp.model.OnrampConfiguration
7
10
  import com.stripe.android.crypto.onramp.model.PaymentMethodDisplayData
8
11
  import com.stripe.android.googlepaylauncher.GooglePayEnvironment
@@ -148,6 +151,7 @@ internal fun mapAppearance(appearanceMap: ReadableMap): LinkAppearance {
148
151
  .primaryButton(primaryButton)
149
152
  }
150
153
 
154
+ @OptIn(ExperimentalCryptoOnramp::class)
151
155
  @SuppressLint("RestrictedApi")
152
156
  internal fun mapPaymentDetailsType(type: PaymentMethodDisplayData.Type): String =
153
157
  when (type) {
@@ -1,3 +1,5 @@
1
+ @file:OptIn(ExperimentalCryptoOnramp::class)
2
+
1
3
  package com.reactnativestripesdk
2
4
 
3
5
  import android.annotation.SuppressLint
@@ -23,6 +25,7 @@ import com.reactnativestripesdk.utils.createOnrampNotConfiguredError
23
25
  import com.reactnativestripesdk.utils.createResult
24
26
  import com.reactnativestripesdk.utils.getValOr
25
27
  import com.reactnativestripesdk.utils.mapToPaymentSheetAddress
28
+ import com.stripe.android.crypto.onramp.ExperimentalCryptoOnramp
26
29
  import com.stripe.android.crypto.onramp.OnrampCoordinator
27
30
  import com.stripe.android.crypto.onramp.model.CryptoNetwork
28
31
  import com.stripe.android.crypto.onramp.model.KycInfo
@@ -57,10 +60,12 @@ import kotlinx.coroutines.withContext
57
60
  import kotlinx.coroutines.withTimeout
58
61
 
59
62
  @SuppressLint("RestrictedApi")
63
+ @OptIn(ExperimentalCryptoOnramp::class)
60
64
  @ReactModule(name = NativeOnrampSdkModuleSpec.NAME)
61
65
  class OnrampSdkModule(
62
66
  reactContext: ReactApplicationContext,
63
67
  ) : NativeOnrampSdkModuleSpec(reactContext) {
68
+ private val eventEmitterCompat = EventEmitterCompat(reactContext)
64
69
  private lateinit var publishableKey: String
65
70
  private var stripeAccountId: String? = null
66
71
 
@@ -142,7 +147,7 @@ class OnrampSdkModule(
142
147
  val params = Arguments.createMap()
143
148
  params.putString("onrampSessionId", sessionId)
144
149
 
145
- emitOnCheckoutClientSecretRequested(params)
150
+ eventEmitterCompat.emitOnCheckoutClientSecretRequested(params)
146
151
 
147
152
  checkoutClientSecretDeferred!!.await()
148
153
  }
@@ -632,6 +637,16 @@ class OnrampSdkModule(
632
637
  }
633
638
  }
634
639
 
640
+ @ReactMethod
641
+ override fun addListener(eventType: String?) {
642
+ // noop, iOS only
643
+ }
644
+
645
+ @ReactMethod
646
+ override fun removeListeners(count: Double) {
647
+ // noop, iOS only
648
+ }
649
+
635
650
  private fun handleOnrampIdentityVerificationResult(
636
651
  result: OnrampVerifyIdentityResult,
637
652
  promise: Promise,
@@ -1,3 +1,5 @@
1
+ @file:OptIn(ExperimentalCryptoOnramp::class)
2
+
1
3
  package com.reactnativestripesdk.mappers
2
4
 
3
5
  import android.annotation.SuppressLint
@@ -5,6 +7,7 @@ import androidx.compose.ui.graphics.toArgb
5
7
  import com.reactnativestripesdk.mapAppearance
6
8
  import com.reactnativestripesdk.mapConfig
7
9
  import com.reactnativestripesdk.utils.readableMapOf
10
+ import com.stripe.android.crypto.onramp.ExperimentalCryptoOnramp
8
11
  import com.stripe.android.link.LinkAppearance.Style
9
12
  import org.junit.Assert.assertEquals
10
13
  import org.junit.Assert.assertNotNull