@stripe/stripe-react-native 0.57.1 → 0.57.3
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.
- package/README.md +0 -8
- package/android/build.gradle +1 -0
- package/android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementView.kt +49 -0
- package/android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementViewManager.kt +61 -0
- package/android/src/main/java/com/reactnativestripesdk/EventEmitterCompat.kt +4 -0
- package/android/src/main/java/com/reactnativestripesdk/PaymentSheetManager.kt +131 -30
- package/android/src/main/java/com/reactnativestripesdk/customersheet/CustomerSheetManager.kt +38 -13
- package/android/src/oldarch/java/com/facebook/react/viewmanagers/EmbeddedPaymentElementViewManagerDelegate.java +3 -0
- package/android/src/oldarch/java/com/facebook/react/viewmanagers/EmbeddedPaymentElementViewManagerInterface.java +2 -0
- package/android/src/test/java/com/reactnativestripesdk/DrawableConversionPropertyTest.kt +224 -0
- package/android/src/test/java/com/reactnativestripesdk/DrawableConversionTest.kt +146 -0
- package/android/src/test/java/com/reactnativestripesdk/DrawableLoadingTest.kt +150 -0
- package/android/src/test/java/com/reactnativestripesdk/PaymentOptionImageConsistencyTest.kt +186 -0
- package/lib/commonjs/components/AddToWalletButton.js +1 -1
- package/lib/commonjs/components/AddToWalletButton.js.map +1 -1
- package/lib/commonjs/components/AddressSheet.js +1 -1
- package/lib/commonjs/components/AddressSheet.js.map +1 -1
- package/lib/commonjs/components/AuBECSDebitForm.js +1 -1
- package/lib/commonjs/components/AuBECSDebitForm.js.map +1 -1
- package/lib/commonjs/components/CardField.js +1 -1
- package/lib/commonjs/components/CardField.js.map +1 -1
- package/lib/commonjs/components/CardForm.js +1 -1
- package/lib/commonjs/components/CardForm.js.map +1 -1
- package/lib/commonjs/components/PlatformPayButton.js +1 -1
- package/lib/commonjs/components/PlatformPayButton.js.map +1 -1
- package/lib/commonjs/components/StripeContainer.js +1 -1
- package/lib/commonjs/components/StripeContainer.js.map +1 -1
- package/lib/commonjs/connect/Components.js +1 -1
- package/lib/commonjs/connect/Components.js.map +1 -1
- package/lib/commonjs/connect/ConnectComponentsProvider.js +1 -1
- package/lib/commonjs/connect/ConnectComponentsProvider.js.map +1 -1
- package/lib/commonjs/connect/EmbeddedComponent.js +1 -1
- package/lib/commonjs/connect/EmbeddedComponent.js.map +1 -1
- package/lib/commonjs/connect/ModalCloseButton.js +1 -1
- package/lib/commonjs/connect/ModalCloseButton.js.map +1 -1
- package/lib/commonjs/connect/NavigationBar.js +1 -1
- package/lib/commonjs/connect/NavigationBar.js.map +1 -1
- package/lib/commonjs/events.js.map +1 -1
- package/lib/commonjs/helpers.js +1 -1
- package/lib/commonjs/hooks/useOnramp.js +1 -1
- package/lib/commonjs/hooks/useOnramp.js.map +1 -1
- package/lib/commonjs/specs/NativeAddToWalletButton.js +1 -1
- package/lib/commonjs/specs/NativeAddressSheet.js +1 -1
- package/lib/commonjs/specs/NativeApplePayButton.js +1 -1
- package/lib/commonjs/specs/NativeAuBECSDebitForm.js +1 -1
- package/lib/commonjs/specs/NativeCardField.js +1 -1
- package/lib/commonjs/specs/NativeCardField.js.map +1 -1
- package/lib/commonjs/specs/NativeCardForm.js +1 -1
- package/lib/commonjs/specs/NativeCardForm.js.map +1 -1
- package/lib/commonjs/specs/NativeConnectAccountOnboardingView.js +1 -1
- package/lib/commonjs/specs/NativeEmbeddedPaymentElement.js +1 -1
- package/lib/commonjs/specs/NativeEmbeddedPaymentElement.js.map +1 -1
- package/lib/commonjs/specs/NativeGooglePayButton.js +1 -1
- package/lib/commonjs/specs/NativeNavigationBar.js +1 -1
- package/lib/commonjs/specs/NativeStripeContainer.js +1 -1
- package/lib/commonjs/types/EmbeddedPaymentElement.js +1 -1
- package/lib/commonjs/types/EmbeddedPaymentElement.js.map +1 -1
- package/lib/module/components/AddToWalletButton.js +1 -1
- package/lib/module/components/AddToWalletButton.js.map +1 -1
- package/lib/module/components/AddressSheet.js +1 -1
- package/lib/module/components/AddressSheet.js.map +1 -1
- package/lib/module/components/AuBECSDebitForm.js +1 -1
- package/lib/module/components/AuBECSDebitForm.js.map +1 -1
- package/lib/module/components/CardField.js +1 -1
- package/lib/module/components/CardField.js.map +1 -1
- package/lib/module/components/CardForm.js +1 -1
- package/lib/module/components/CardForm.js.map +1 -1
- package/lib/module/components/PlatformPayButton.js +1 -1
- package/lib/module/components/PlatformPayButton.js.map +1 -1
- package/lib/module/components/StripeContainer.js +1 -1
- package/lib/module/components/StripeContainer.js.map +1 -1
- package/lib/module/connect/Components.js +1 -1
- package/lib/module/connect/Components.js.map +1 -1
- package/lib/module/connect/ConnectComponentsProvider.js +1 -1
- package/lib/module/connect/ConnectComponentsProvider.js.map +1 -1
- package/lib/module/connect/EmbeddedComponent.js +1 -1
- package/lib/module/connect/EmbeddedComponent.js.map +1 -1
- package/lib/module/connect/ModalCloseButton.js +1 -1
- package/lib/module/connect/ModalCloseButton.js.map +1 -1
- package/lib/module/connect/NavigationBar.js +1 -1
- package/lib/module/connect/NavigationBar.js.map +1 -1
- package/lib/module/events.js.map +1 -1
- package/lib/module/helpers.js +1 -1
- package/lib/module/hooks/useOnramp.js +1 -1
- package/lib/module/hooks/useOnramp.js.map +1 -1
- package/lib/module/specs/NativeAddToWalletButton.js +1 -1
- package/lib/module/specs/NativeAddressSheet.js +1 -1
- package/lib/module/specs/NativeApplePayButton.js +1 -1
- package/lib/module/specs/NativeAuBECSDebitForm.js +1 -1
- package/lib/module/specs/NativeCardField.js +1 -1
- package/lib/module/specs/NativeCardField.js.map +1 -1
- package/lib/module/specs/NativeCardForm.js +1 -1
- package/lib/module/specs/NativeCardForm.js.map +1 -1
- package/lib/module/specs/NativeConnectAccountOnboardingView.js +1 -1
- package/lib/module/specs/NativeEmbeddedPaymentElement.js +1 -1
- package/lib/module/specs/NativeEmbeddedPaymentElement.js.map +1 -1
- package/lib/module/specs/NativeGooglePayButton.js +1 -1
- package/lib/module/specs/NativeNavigationBar.js +1 -1
- package/lib/module/specs/NativeStripeContainer.js +1 -1
- package/lib/module/types/EmbeddedPaymentElement.js +1 -1
- package/lib/module/types/EmbeddedPaymentElement.js.map +1 -1
- package/lib/typescript/src/connect/EmbeddedComponent.d.ts.map +1 -1
- package/lib/typescript/src/events.d.ts +1 -0
- package/lib/typescript/src/events.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useOnramp.d.ts +2 -1
- package/lib/typescript/src/hooks/useOnramp.d.ts.map +1 -1
- package/lib/typescript/src/specs/NativeEmbeddedPaymentElement.d.ts +1 -0
- package/lib/typescript/src/specs/NativeEmbeddedPaymentElement.d.ts.map +1 -1
- package/lib/typescript/src/types/EmbeddedPaymentElement.d.ts.map +1 -1
- package/package.json +13 -7
- package/src/connect/EmbeddedComponent.tsx +10 -10
- package/src/events.ts +1 -0
- package/src/hooks/useOnramp.tsx +5 -1
- package/src/specs/NativeEmbeddedPaymentElement.ts +5 -1
- package/src/types/EmbeddedPaymentElement.tsx +24 -3
package/README.md
CHANGED
|
@@ -215,14 +215,6 @@ function App() {
|
|
|
215
215
|
|
|
216
216
|
You can find more details about the `StripeProvider` component in the [API reference](https://stripe.dev/stripe-react-native/api-reference/index.html#StripeProvider).
|
|
217
217
|
|
|
218
|
-
##### Additional steps for webhook forwarding
|
|
219
|
-
|
|
220
|
-
Certain payment methods require a [webhook listener](https://stripe.com/docs/payments/payment-intents/verifying-status#webhooks) to notify you of changes in the status. When developing locally, you can use the [Stripe CLI](https://stripe.com/docs/stripe-cli) to forward webhook events to your local dev server.
|
|
221
|
-
|
|
222
|
-
- [Install the `stripe-cli`](https://stripe.com/docs/stripe-cli#install)
|
|
223
|
-
- Run `stripe listen --forward-to localhost:4242/webhook`
|
|
224
|
-
- The CLI will print a webhook secret (such as, `whsec_***`) to the console. Set STRIPE_WEBHOOK_SECRET to this value in your `example/.env` file.
|
|
225
|
-
|
|
226
218
|
## Testing
|
|
227
219
|
|
|
228
220
|
This library includes a built in mock file for Jest.
|
package/android/build.gradle
CHANGED
|
@@ -183,6 +183,7 @@ dependencies {
|
|
|
183
183
|
testImplementation "org.mockito:mockito-core:3.+"
|
|
184
184
|
testImplementation "org.robolectric:robolectric:4.10"
|
|
185
185
|
testImplementation "androidx.test:core:1.4.0"
|
|
186
|
+
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0"
|
|
186
187
|
|
|
187
188
|
implementation "androidx.compose.ui:ui:1.7.8"
|
|
188
189
|
implementation "androidx.compose.foundation:foundation-layout:1.7.8"
|
|
@@ -54,6 +54,10 @@ class EmbeddedPaymentElementView(
|
|
|
54
54
|
val intentConfiguration: PaymentSheet.IntentConfiguration,
|
|
55
55
|
) : Event
|
|
56
56
|
|
|
57
|
+
data class Update(
|
|
58
|
+
val intentConfiguration: PaymentSheet.IntentConfiguration,
|
|
59
|
+
) : Event
|
|
60
|
+
|
|
57
61
|
data object Confirm : Event
|
|
58
62
|
|
|
59
63
|
data object ClearPaymentOption : Event
|
|
@@ -322,6 +326,47 @@ class EmbeddedPaymentElementView(
|
|
|
322
326
|
}
|
|
323
327
|
}
|
|
324
328
|
|
|
329
|
+
is Event.Update -> {
|
|
330
|
+
val elemConfig = latestElementConfig
|
|
331
|
+
if (elemConfig == null) {
|
|
332
|
+
val payload =
|
|
333
|
+
Arguments.createMap().apply {
|
|
334
|
+
putString("message", "Cannot update: no element configuration exists")
|
|
335
|
+
}
|
|
336
|
+
requireStripeSdkModule().eventEmitter.emitEmbeddedPaymentElementLoadingFailed(payload)
|
|
337
|
+
requireStripeSdkModule().eventEmitter.emitEmbeddedPaymentElementUpdateComplete(null)
|
|
338
|
+
return@collect
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
val result =
|
|
342
|
+
embedded.configure(
|
|
343
|
+
intentConfiguration = ev.intentConfiguration,
|
|
344
|
+
configuration = elemConfig,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
when (result) {
|
|
348
|
+
is EmbeddedPaymentElement.ConfigureResult.Succeeded -> {
|
|
349
|
+
val payload =
|
|
350
|
+
Arguments.createMap().apply {
|
|
351
|
+
putString("status", "succeeded")
|
|
352
|
+
}
|
|
353
|
+
requireStripeSdkModule().eventEmitter.emitEmbeddedPaymentElementUpdateComplete(payload)
|
|
354
|
+
}
|
|
355
|
+
is EmbeddedPaymentElement.ConfigureResult.Failed -> {
|
|
356
|
+
val err = result.error
|
|
357
|
+
val msg = err.localizedMessage ?: err.toString()
|
|
358
|
+
val failPayload =
|
|
359
|
+
Arguments.createMap().apply {
|
|
360
|
+
putString("message", msg)
|
|
361
|
+
}
|
|
362
|
+
requireStripeSdkModule().eventEmitter.emitEmbeddedPaymentElementLoadingFailed(failPayload)
|
|
363
|
+
requireStripeSdkModule().eventEmitter.emitEmbeddedPaymentElementUpdateComplete(null)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
latestIntentConfig = ev.intentConfiguration
|
|
368
|
+
}
|
|
369
|
+
|
|
325
370
|
is Event.Confirm -> {
|
|
326
371
|
embedded.confirm()
|
|
327
372
|
}
|
|
@@ -412,6 +457,10 @@ class EmbeddedPaymentElementView(
|
|
|
412
457
|
events.trySend(Event.Configure(config, intentConfig))
|
|
413
458
|
}
|
|
414
459
|
|
|
460
|
+
fun update(intentConfig: PaymentSheet.IntentConfiguration) {
|
|
461
|
+
events.trySend(Event.Update(intentConfig))
|
|
462
|
+
}
|
|
463
|
+
|
|
415
464
|
fun confirm() {
|
|
416
465
|
events.trySend(Event.Confirm)
|
|
417
466
|
}
|
|
@@ -2,8 +2,11 @@ package com.reactnativestripesdk
|
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.content.Context
|
|
5
|
+
import com.facebook.react.bridge.Arguments
|
|
5
6
|
import com.facebook.react.bridge.Dynamic
|
|
6
7
|
import com.facebook.react.bridge.ReadableMap
|
|
8
|
+
import com.facebook.react.bridge.WritableArray
|
|
9
|
+
import com.facebook.react.bridge.WritableMap
|
|
7
10
|
import com.facebook.react.module.annotations.ReactModule
|
|
8
11
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
9
12
|
import com.facebook.react.uimanager.ViewGroupManager
|
|
@@ -23,6 +26,8 @@ import com.stripe.android.ExperimentalAllowsRemovalOfLastSavedPaymentMethodApi
|
|
|
23
26
|
import com.stripe.android.paymentelement.EmbeddedPaymentElement
|
|
24
27
|
import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi
|
|
25
28
|
import com.stripe.android.paymentsheet.PaymentSheet
|
|
29
|
+
import org.json.JSONArray
|
|
30
|
+
import org.json.JSONObject
|
|
26
31
|
|
|
27
32
|
@ReactModule(name = EmbeddedPaymentElementViewManager.NAME)
|
|
28
33
|
class EmbeddedPaymentElementViewManager :
|
|
@@ -178,6 +183,62 @@ class EmbeddedPaymentElementViewManager :
|
|
|
178
183
|
override fun clearPaymentOption(view: EmbeddedPaymentElementView) {
|
|
179
184
|
view.clearPaymentOption()
|
|
180
185
|
}
|
|
186
|
+
|
|
187
|
+
override fun update(
|
|
188
|
+
view: EmbeddedPaymentElementView,
|
|
189
|
+
intentConfigurationJson: String?,
|
|
190
|
+
) {
|
|
191
|
+
intentConfigurationJson?.let { json ->
|
|
192
|
+
try {
|
|
193
|
+
val jsonObject = JSONObject(json)
|
|
194
|
+
val cfg = jsonToWritableMap(jsonObject)
|
|
195
|
+
val intentConfig = buildIntentConfiguration(cfg)
|
|
196
|
+
if (intentConfig != null) {
|
|
197
|
+
view.update(intentConfig)
|
|
198
|
+
}
|
|
199
|
+
} catch (e: Exception) {
|
|
200
|
+
android.util.Log.e("EmbeddedPaymentElement", "Failed to parse intent config JSON", e)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private fun jsonToWritableMap(json: JSONObject): WritableMap {
|
|
206
|
+
val map = Arguments.createMap()
|
|
207
|
+
val keys = json.keys()
|
|
208
|
+
while (keys.hasNext()) {
|
|
209
|
+
val key = keys.next()
|
|
210
|
+
when (val value = json.get(key)) {
|
|
211
|
+
is Boolean -> map.putBoolean(key, value)
|
|
212
|
+
is Int -> map.putInt(key, value)
|
|
213
|
+
is Double -> map.putDouble(key, value)
|
|
214
|
+
is Long -> map.putDouble(key, value.toDouble())
|
|
215
|
+
is String -> map.putString(key, value)
|
|
216
|
+
is JSONObject -> map.putMap(key, jsonToWritableMap(value))
|
|
217
|
+
is JSONArray -> map.putArray(key, jsonToWritableArray(value))
|
|
218
|
+
JSONObject.NULL -> map.putNull(key)
|
|
219
|
+
else -> map.putString(key, value.toString())
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return map
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private fun jsonToWritableArray(json: JSONArray): WritableArray {
|
|
226
|
+
val array = Arguments.createArray()
|
|
227
|
+
for (i in 0 until json.length()) {
|
|
228
|
+
when (val value = json.get(i)) {
|
|
229
|
+
is Boolean -> array.pushBoolean(value)
|
|
230
|
+
is Int -> array.pushInt(value)
|
|
231
|
+
is Double -> array.pushDouble(value)
|
|
232
|
+
is Long -> array.pushDouble(value.toDouble())
|
|
233
|
+
is String -> array.pushString(value)
|
|
234
|
+
is JSONObject -> array.pushMap(jsonToWritableMap(value))
|
|
235
|
+
is JSONArray -> array.pushArray(jsonToWritableArray(value))
|
|
236
|
+
JSONObject.NULL -> array.pushNull()
|
|
237
|
+
else -> array.pushString(value.toString())
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return array
|
|
241
|
+
}
|
|
181
242
|
}
|
|
182
243
|
|
|
183
244
|
internal fun mapToRowSelectionBehaviorType(map: ReadableMap?): RowSelectionBehaviorType {
|
|
@@ -84,6 +84,10 @@ class EventEmitterCompat(
|
|
|
84
84
|
invoke("embeddedPaymentElementLoadingFailed", value)
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
fun emitEmbeddedPaymentElementUpdateComplete(value: ReadableMap?) {
|
|
88
|
+
invoke("embeddedPaymentElementUpdateComplete", value)
|
|
89
|
+
}
|
|
90
|
+
|
|
87
91
|
fun emitOnCustomPaymentMethodConfirmHandlerCallback(value: ReadableMap?) {
|
|
88
92
|
invoke("onCustomPaymentMethodConfirmHandlerCallback", value)
|
|
89
93
|
}
|
|
@@ -58,8 +58,11 @@ import kotlinx.coroutines.CoroutineScope
|
|
|
58
58
|
import kotlinx.coroutines.Dispatchers
|
|
59
59
|
import kotlinx.coroutines.delay
|
|
60
60
|
import kotlinx.coroutines.launch
|
|
61
|
+
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
62
|
+
import kotlinx.coroutines.withTimeoutOrNull
|
|
61
63
|
import java.io.ByteArrayOutputStream
|
|
62
64
|
import kotlin.Exception
|
|
65
|
+
import kotlin.coroutines.resume
|
|
63
66
|
|
|
64
67
|
@OptIn(
|
|
65
68
|
ReactNativeSdkInternal::class,
|
|
@@ -140,28 +143,42 @@ class PaymentSheetManager(
|
|
|
140
143
|
|
|
141
144
|
val paymentOptionCallback =
|
|
142
145
|
PaymentOptionResultCallback { paymentOptionResult ->
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
val imageString =
|
|
146
|
+
paymentOptionResult.paymentOption?.let { paymentOption ->
|
|
147
|
+
// Convert drawable to bitmap asynchronously to avoid shared state issues
|
|
148
|
+
CoroutineScope(Dispatchers.Default).launch {
|
|
149
|
+
val imageString =
|
|
150
|
+
try {
|
|
151
|
+
convertDrawableToBase64(paymentOption.icon())
|
|
152
|
+
} catch (e: Exception) {
|
|
153
|
+
val result =
|
|
154
|
+
createError(
|
|
155
|
+
PaymentSheetErrorType.Failed.toString(),
|
|
156
|
+
"Failed to process payment option image: ${e.message}",
|
|
157
|
+
)
|
|
158
|
+
resolvePresentPromise(result)
|
|
159
|
+
return@launch
|
|
160
|
+
}
|
|
161
|
+
|
|
147
162
|
val option: WritableMap = Arguments.createMap()
|
|
148
|
-
option.putString("label",
|
|
163
|
+
option.putString("label", paymentOption.label)
|
|
149
164
|
option.putString("image", imageString)
|
|
150
165
|
val additionalFields: Map<String, Any> = mapOf("didCancel" to paymentOptionResult.didCancel)
|
|
151
|
-
createResult("paymentOption", option, additionalFields)
|
|
166
|
+
val result = createResult("paymentOption", option, additionalFields)
|
|
167
|
+
resolvePresentPromise(result)
|
|
152
168
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
169
|
+
} ?: run {
|
|
170
|
+
val result =
|
|
171
|
+
if (paymentSheetTimedOut) {
|
|
172
|
+
paymentSheetTimedOut = false
|
|
173
|
+
createError(PaymentSheetErrorType.Timeout.toString(), "The payment has timed out")
|
|
174
|
+
} else {
|
|
175
|
+
createError(
|
|
176
|
+
PaymentSheetErrorType.Canceled.toString(),
|
|
177
|
+
"The payment option selection flow has been canceled",
|
|
178
|
+
)
|
|
163
179
|
}
|
|
164
|
-
|
|
180
|
+
resolvePresentPromise(result)
|
|
181
|
+
}
|
|
165
182
|
}
|
|
166
183
|
|
|
167
184
|
val paymentResultCallback =
|
|
@@ -413,16 +430,31 @@ class PaymentSheetManager(
|
|
|
413
430
|
private fun configureFlowController() {
|
|
414
431
|
val onFlowControllerConfigure =
|
|
415
432
|
PaymentSheet.FlowController.ConfigCallback { _, _ ->
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
val imageString =
|
|
433
|
+
flowController?.getPaymentOption()?.let { paymentOption ->
|
|
434
|
+
// Launch async job to convert drawable, but resolve promise synchronously
|
|
435
|
+
CoroutineScope(Dispatchers.Default).launch {
|
|
436
|
+
val imageString =
|
|
437
|
+
try {
|
|
438
|
+
convertDrawableToBase64(paymentOption.icon())
|
|
439
|
+
} catch (e: Exception) {
|
|
440
|
+
val result =
|
|
441
|
+
createError(
|
|
442
|
+
PaymentSheetErrorType.Failed.toString(),
|
|
443
|
+
"Failed to process payment option image: ${e.message}",
|
|
444
|
+
)
|
|
445
|
+
initPromise.resolve(result)
|
|
446
|
+
return@launch
|
|
447
|
+
}
|
|
448
|
+
|
|
420
449
|
val option: WritableMap = Arguments.createMap()
|
|
421
|
-
option.putString("label",
|
|
450
|
+
option.putString("label", paymentOption.label)
|
|
422
451
|
option.putString("image", imageString)
|
|
423
|
-
createResult("paymentOption", option)
|
|
424
|
-
|
|
425
|
-
|
|
452
|
+
val result = createResult("paymentOption", option)
|
|
453
|
+
initPromise.resolve(result)
|
|
454
|
+
}
|
|
455
|
+
} ?: run {
|
|
456
|
+
initPromise.resolve(Arguments.createMap())
|
|
457
|
+
}
|
|
426
458
|
}
|
|
427
459
|
|
|
428
460
|
if (!paymentIntentClientSecret.isNullOrEmpty()) {
|
|
@@ -550,17 +582,86 @@ class PaymentSheetManager(
|
|
|
550
582
|
}
|
|
551
583
|
}
|
|
552
584
|
|
|
585
|
+
suspend fun waitForDrawableToLoad(
|
|
586
|
+
drawable: Drawable,
|
|
587
|
+
timeoutMs: Long = 3000,
|
|
588
|
+
): Drawable {
|
|
589
|
+
// If already loaded, return immediately
|
|
590
|
+
if (drawable.intrinsicWidth > 1 && drawable.intrinsicHeight > 1) {
|
|
591
|
+
return drawable
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Use callback to be notified when drawable finishes loading
|
|
595
|
+
return withTimeoutOrNull(timeoutMs) {
|
|
596
|
+
suspendCancellableCoroutine { continuation ->
|
|
597
|
+
val callback =
|
|
598
|
+
object : Drawable.Callback {
|
|
599
|
+
override fun invalidateDrawable(who: Drawable) {
|
|
600
|
+
// Drawable has changed/loaded - check if it's ready now
|
|
601
|
+
if (who.intrinsicWidth > 1 && who.intrinsicHeight > 1) {
|
|
602
|
+
who.callback = null // Remove callback
|
|
603
|
+
if (continuation.isActive) {
|
|
604
|
+
continuation.resume(who)
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
override fun scheduleDrawable(
|
|
610
|
+
who: Drawable,
|
|
611
|
+
what: Runnable,
|
|
612
|
+
`when`: Long,
|
|
613
|
+
) {}
|
|
614
|
+
|
|
615
|
+
override fun unscheduleDrawable(
|
|
616
|
+
who: Drawable,
|
|
617
|
+
what: Runnable,
|
|
618
|
+
) {}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
drawable.callback = callback
|
|
622
|
+
|
|
623
|
+
// Trigger an invalidation to check if it loads immediately
|
|
624
|
+
drawable.invalidateSelf()
|
|
625
|
+
|
|
626
|
+
continuation.invokeOnCancellation { drawable.callback = null }
|
|
627
|
+
}
|
|
628
|
+
} ?: drawable // Return drawable even if timeout (best effort)
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
suspend fun convertDrawableToBase64(drawable: Drawable): String? {
|
|
632
|
+
val loadedDrawable = waitForDrawableToLoad(drawable)
|
|
633
|
+
val bitmap = getBitmapFromDrawable(loadedDrawable)
|
|
634
|
+
return getBase64FromBitmap(bitmap)
|
|
635
|
+
}
|
|
636
|
+
|
|
553
637
|
fun getBitmapFromDrawable(drawable: Drawable): Bitmap? {
|
|
554
638
|
val drawableCompat = DrawableCompat.wrap(drawable).mutate()
|
|
555
|
-
|
|
639
|
+
|
|
640
|
+
// Determine the size to use - prefer intrinsic size, fall back to bounds
|
|
641
|
+
val width =
|
|
642
|
+
if (drawableCompat.intrinsicWidth > 0) {
|
|
643
|
+
drawableCompat.intrinsicWidth
|
|
644
|
+
} else {
|
|
645
|
+
drawableCompat.bounds.width()
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
val height =
|
|
649
|
+
if (drawableCompat.intrinsicHeight > 0) {
|
|
650
|
+
drawableCompat.intrinsicHeight
|
|
651
|
+
} else {
|
|
652
|
+
drawableCompat.bounds.height()
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (width <= 0 || height <= 0) {
|
|
556
656
|
return null
|
|
557
657
|
}
|
|
558
|
-
|
|
559
|
-
|
|
658
|
+
|
|
659
|
+
val bitmap = createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
|
560
660
|
bitmap.eraseColor(Color.TRANSPARENT)
|
|
561
661
|
val canvas = Canvas(bitmap)
|
|
562
|
-
|
|
563
|
-
|
|
662
|
+
drawableCompat.setBounds(0, 0, canvas.width, canvas.height)
|
|
663
|
+
drawableCompat.draw(canvas)
|
|
664
|
+
|
|
564
665
|
return bitmap
|
|
565
666
|
}
|
|
566
667
|
|
package/android/src/main/java/com/reactnativestripesdk/customersheet/CustomerSheetManager.kt
CHANGED
|
@@ -17,8 +17,7 @@ import com.reactnativestripesdk.ReactNativeCustomerSessionProvider
|
|
|
17
17
|
import com.reactnativestripesdk.buildBillingDetails
|
|
18
18
|
import com.reactnativestripesdk.buildBillingDetailsCollectionConfiguration
|
|
19
19
|
import com.reactnativestripesdk.buildPaymentSheetAppearance
|
|
20
|
-
import com.reactnativestripesdk.
|
|
21
|
-
import com.reactnativestripesdk.getBitmapFromDrawable
|
|
20
|
+
import com.reactnativestripesdk.convertDrawableToBase64
|
|
22
21
|
import com.reactnativestripesdk.mapToCardBrandAcceptance
|
|
23
22
|
import com.reactnativestripesdk.utils.CreateTokenErrorType
|
|
24
23
|
import com.reactnativestripesdk.utils.ErrorType
|
|
@@ -163,25 +162,49 @@ class CustomerSheetManager(
|
|
|
163
162
|
}
|
|
164
163
|
|
|
165
164
|
private fun handleResult(result: CustomerSheetResult) {
|
|
166
|
-
var promiseResult = Arguments.createMap()
|
|
167
165
|
when (result) {
|
|
168
166
|
is CustomerSheetResult.Failed -> {
|
|
169
167
|
resolvePresentPromise(createError(ErrorType.Failed.toString(), result.exception))
|
|
170
168
|
}
|
|
171
169
|
|
|
172
170
|
is CustomerSheetResult.Selected -> {
|
|
173
|
-
|
|
171
|
+
// Convert drawable asynchronously to avoid shared state issues
|
|
172
|
+
CoroutineScope(Dispatchers.Default).launch {
|
|
173
|
+
try {
|
|
174
|
+
val promiseResult = createPaymentOptionResult(result.selection)
|
|
175
|
+
resolvePresentPromise(promiseResult)
|
|
176
|
+
} catch (e: Exception) {
|
|
177
|
+
resolvePresentPromise(
|
|
178
|
+
createError(
|
|
179
|
+
ErrorType.Failed.toString(),
|
|
180
|
+
"Failed to process payment option image: ${e.message}",
|
|
181
|
+
),
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
174
185
|
}
|
|
175
186
|
|
|
176
187
|
is CustomerSheetResult.Canceled -> {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
188
|
+
// Convert drawable asynchronously to avoid shared state issues
|
|
189
|
+
CoroutineScope(Dispatchers.Default).launch {
|
|
190
|
+
try {
|
|
191
|
+
val promiseResult = createPaymentOptionResult(result.selection)
|
|
192
|
+
promiseResult.putMap(
|
|
193
|
+
"error",
|
|
194
|
+
Arguments.createMap().also { it.putString("code", ErrorType.Canceled.toString()) },
|
|
195
|
+
)
|
|
196
|
+
resolvePresentPromise(promiseResult)
|
|
197
|
+
} catch (e: Exception) {
|
|
198
|
+
resolvePresentPromise(
|
|
199
|
+
createError(
|
|
200
|
+
ErrorType.Failed.toString(),
|
|
201
|
+
"Failed to process payment option image: ${e.message}",
|
|
202
|
+
),
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
182
206
|
}
|
|
183
207
|
}
|
|
184
|
-
resolvePresentPromise(promiseResult)
|
|
185
208
|
}
|
|
186
209
|
|
|
187
210
|
override fun onPresent() {
|
|
@@ -355,7 +378,7 @@ class CustomerSheetManager(
|
|
|
355
378
|
)
|
|
356
379
|
}
|
|
357
380
|
|
|
358
|
-
internal fun createPaymentOptionResult(selection: PaymentOptionSelection?): WritableMap {
|
|
381
|
+
internal suspend fun createPaymentOptionResult(selection: PaymentOptionSelection?): WritableMap {
|
|
359
382
|
var paymentOptionResult = Arguments.createMap()
|
|
360
383
|
|
|
361
384
|
when (selection) {
|
|
@@ -392,16 +415,18 @@ class CustomerSheetManager(
|
|
|
392
415
|
}.build()
|
|
393
416
|
}
|
|
394
417
|
|
|
395
|
-
private fun buildResult(
|
|
418
|
+
private suspend fun buildResult(
|
|
396
419
|
label: String,
|
|
397
420
|
drawable: Drawable,
|
|
398
421
|
paymentMethod: PaymentMethod?,
|
|
399
422
|
): WritableMap {
|
|
423
|
+
val imageString = convertDrawableToBase64(drawable)
|
|
424
|
+
|
|
400
425
|
val result = Arguments.createMap()
|
|
401
426
|
val paymentOption =
|
|
402
427
|
Arguments.createMap().also {
|
|
403
428
|
it.putString("label", label)
|
|
404
|
-
it.putString("image",
|
|
429
|
+
it.putString("image", imageString)
|
|
405
430
|
}
|
|
406
431
|
result.putMap("paymentOption", paymentOption)
|
|
407
432
|
if (paymentMethod != null) {
|
|
@@ -44,6 +44,9 @@ public class EmbeddedPaymentElementViewManagerDelegate<T extends View, U extends
|
|
|
44
44
|
case "clearPaymentOption":
|
|
45
45
|
mViewManager.clearPaymentOption(view);
|
|
46
46
|
break;
|
|
47
|
+
case "update":
|
|
48
|
+
mViewManager.update(view, args != null ? args.getString(0) : null);
|
|
49
|
+
break;
|
|
47
50
|
}
|
|
48
51
|
}
|
|
49
52
|
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
package com.facebook.react.viewmanagers;
|
|
11
11
|
|
|
12
12
|
import android.view.View;
|
|
13
|
+
import androidx.annotation.Nullable;
|
|
13
14
|
import com.facebook.react.bridge.Dynamic;
|
|
14
15
|
|
|
15
16
|
public interface EmbeddedPaymentElementViewManagerInterface<T extends View> {
|
|
@@ -17,4 +18,5 @@ public interface EmbeddedPaymentElementViewManagerInterface<T extends View> {
|
|
|
17
18
|
void setIntentConfiguration(T view, Dynamic value);
|
|
18
19
|
void confirm(T view);
|
|
19
20
|
void clearPaymentOption(T view);
|
|
21
|
+
void update(T view, @Nullable String intentConfigurationJson);
|
|
20
22
|
}
|