Skip to content

Commit 83add87

Browse files
Fix update on Embedded Android (#2260)
* Add a switch to test updating * Make updating nicer looking * Fix update on Android * Fix build on Android old arch * Add update test
1 parent f5da3b7 commit 83add87

File tree

13 files changed

+347
-37
lines changed

13 files changed

+347
-37
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# CHANGELOG
22

3+
## x.x.x - x-x-x
4+
5+
**Fixes**
6+
- Fixed `update()` not working on Android for EmbeddedPaymentElement. The intent configuration is now properly passed to the native side when calling `update()`.
7+
38
## 0.57.1 - 2025-12-11
49
**Fixes**
510
- Fixed Android crash `NoSuchKeyException: customFlow` when initializing PaymentSheet without explicitly setting the `customFlow` parameter.

android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementView.kt

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ class EmbeddedPaymentElementView(
5454
val intentConfiguration: PaymentSheet.IntentConfiguration,
5555
) : Event
5656

57+
data class Update(
58+
val intentConfiguration: PaymentSheet.IntentConfiguration,
59+
) : Event
60+
5761
data object Confirm : Event
5862

5963
data object ClearPaymentOption : Event
@@ -322,6 +326,47 @@ class EmbeddedPaymentElementView(
322326
}
323327
}
324328

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+
325370
is Event.Confirm -> {
326371
embedded.confirm()
327372
}
@@ -412,6 +457,10 @@ class EmbeddedPaymentElementView(
412457
events.trySend(Event.Configure(config, intentConfig))
413458
}
414459

460+
fun update(intentConfig: PaymentSheet.IntentConfiguration) {
461+
events.trySend(Event.Update(intentConfig))
462+
}
463+
415464
fun confirm() {
416465
events.trySend(Event.Confirm)
417466
}

android/src/main/java/com/reactnativestripesdk/EmbeddedPaymentElementViewManager.kt

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ package com.reactnativestripesdk
22

33
import android.annotation.SuppressLint
44
import android.content.Context
5+
import com.facebook.react.bridge.Arguments
56
import com.facebook.react.bridge.Dynamic
67
import com.facebook.react.bridge.ReadableMap
8+
import com.facebook.react.bridge.WritableArray
9+
import com.facebook.react.bridge.WritableMap
710
import com.facebook.react.module.annotations.ReactModule
811
import com.facebook.react.uimanager.ThemedReactContext
912
import com.facebook.react.uimanager.ViewGroupManager
@@ -23,6 +26,8 @@ import com.stripe.android.ExperimentalAllowsRemovalOfLastSavedPaymentMethodApi
2326
import com.stripe.android.paymentelement.EmbeddedPaymentElement
2427
import com.stripe.android.paymentelement.ExperimentalCustomPaymentMethodsApi
2528
import com.stripe.android.paymentsheet.PaymentSheet
29+
import org.json.JSONArray
30+
import org.json.JSONObject
2631

2732
@ReactModule(name = EmbeddedPaymentElementViewManager.NAME)
2833
class EmbeddedPaymentElementViewManager :
@@ -178,6 +183,62 @@ class EmbeddedPaymentElementViewManager :
178183
override fun clearPaymentOption(view: EmbeddedPaymentElementView) {
179184
view.clearPaymentOption()
180185
}
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+
}
181242
}
182243

183244
internal fun mapToRowSelectionBehaviorType(map: ReadableMap?): RowSelectionBehaviorType {

android/src/main/java/com/reactnativestripesdk/EventEmitterCompat.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ class EventEmitterCompat(
8484
invoke("embeddedPaymentElementLoadingFailed", value)
8585
}
8686

87+
fun emitEmbeddedPaymentElementUpdateComplete(value: ReadableMap?) {
88+
invoke("embeddedPaymentElementUpdateComplete", value)
89+
}
90+
8791
fun emitOnCustomPaymentMethodConfirmHandlerCallback(value: ReadableMap?) {
8892
invoke("onCustomPaymentMethodConfirmHandlerCallback", value)
8993
}

android/src/oldarch/java/com/facebook/react/viewmanagers/EmbeddedPaymentElementViewManagerDelegate.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ public void receiveCommand(T view, String commandName, @Nullable ReadableArray a
4444
case "clearPaymentOption":
4545
mViewManager.clearPaymentOption(view);
4646
break;
47+
case "update":
48+
mViewManager.update(view, args != null ? args.getString(0) : null);
49+
break;
4750
}
4851
}
4952
}

android/src/oldarch/java/com/facebook/react/viewmanagers/EmbeddedPaymentElementViewManagerInterface.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
package com.facebook.react.viewmanagers;
1111

1212
import android.view.View;
13+
import androidx.annotation.Nullable;
1314
import com.facebook.react.bridge.Dynamic;
1415

1516
public interface EmbeddedPaymentElementViewManagerInterface<T extends View> {
1617
void setConfiguration(T view, Dynamic value);
1718
void setIntentConfiguration(T view, Dynamic value);
1819
void confirm(T view);
1920
void clearPaymentOption(T view);
21+
void update(T view, @Nullable String intentConfigurationJson);
2022
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Test embedded payment element with discount toggle
2+
appId: ${APP_ID}
3+
---
4+
- launchApp:
5+
clearState: true
6+
- tapOn: "Accept a payment"
7+
- tapOn: "Prebuilt UI (EmbeddedPaymentElement)"
8+
- extendedWaitUntil:
9+
visible: "Card"
10+
timeout: 150000
11+
# Toggle the discount switch
12+
- tapOn:
13+
id: "discount_toggle_switch"
14+
# Wait for update to complete (updating overlay disappears)
15+
- extendedWaitUntil:
16+
notVisible: "Updating..."
17+
timeout: 30000
18+
- tapOn:
19+
text: "Card"
20+
- tapOn:
21+
text: "Card number"
22+
- inputText: "4242424242424242"
23+
- inputText: "0145"
24+
- inputText: "123"
25+
- scrollUntilVisible:
26+
element:
27+
id: primary_button # Android
28+
optional: true
29+
- scrollUntilVisible:
30+
element:
31+
text: "Continue" # iOS
32+
optional: true
33+
34+
- tapOn:
35+
id: primary_button # Android
36+
optional: true
37+
- tapOn:
38+
text: "Continue" # iOS
39+
optional: true
40+
41+
- scrollUntilVisible:
42+
element:
43+
text: "Complete payment"
44+
timeout: 30000
45+
- tapOn: "Complete payment"
46+
- assertVisible:
47+
text: "Success"
48+
- tapOn: "OK"

e2e-tests/embedded.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,21 @@ appId: ${APP_ID}
2020
optional: true
2121
- scrollUntilVisible:
2222
element:
23-
text: "Pay $60.99" # iOS
23+
text: "Continue" # iOS
2424
optional: true
2525

2626
- tapOn:
2727
id: primary_button # Android
2828
optional: true
2929
- tapOn:
30-
text: "Pay $60.99" # iOS
30+
text: "Continue" # iOS
3131
optional: true
3232

33+
- scrollUntilVisible:
34+
element:
35+
text: "Complete payment"
36+
timeout: 30000
37+
- tapOn: "Complete payment"
3338
- assertVisible:
3439
text: "Success"
3540
- tapOn: "OK"

example/ios/Podfile.lock

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1826,7 +1826,7 @@ PODS:
18261826
- StripePayments (= 25.0.1)
18271827
- StripePaymentsUI (= 25.0.1)
18281828
- StripeUICore (= 25.0.1)
1829-
- stripe-react-native (0.57.0):
1829+
- stripe-react-native (0.57.1):
18301830
- hermes-engine
18311831
- RCTRequired
18321832
- RCTTypeSafety
@@ -1847,10 +1847,10 @@ PODS:
18471847
- ReactCommon/turbomodule/bridging
18481848
- ReactCommon/turbomodule/core
18491849
- ReactNativeDependencies
1850-
- stripe-react-native/Core (= 0.57.0)
1851-
- stripe-react-native/NewArch (= 0.57.0)
1850+
- stripe-react-native/Core (= 0.57.1)
1851+
- stripe-react-native/NewArch (= 0.57.1)
18521852
- Yoga
1853-
- stripe-react-native/Core (0.57.0):
1853+
- stripe-react-native/Core (0.57.1):
18541854
- hermes-engine
18551855
- RCTRequired
18561856
- RCTTypeSafety
@@ -1878,7 +1878,7 @@ PODS:
18781878
- StripePaymentSheet (~> 25.0.1)
18791879
- StripePaymentsUI (~> 25.0.1)
18801880
- Yoga
1881-
- stripe-react-native/NewArch (0.57.0):
1881+
- stripe-react-native/NewArch (0.57.1):
18821882
- hermes-engine
18831883
- RCTRequired
18841884
- RCTTypeSafety
@@ -1900,7 +1900,7 @@ PODS:
19001900
- ReactCommon/turbomodule/core
19011901
- ReactNativeDependencies
19021902
- Yoga
1903-
- stripe-react-native/Onramp (0.57.0):
1903+
- stripe-react-native/Onramp (0.57.1):
19041904
- hermes-engine
19051905
- RCTRequired
19061906
- RCTTypeSafety
@@ -1924,7 +1924,7 @@ PODS:
19241924
- stripe-react-native/Core
19251925
- StripeCryptoOnramp (~> 25.0.1)
19261926
- Yoga
1927-
- stripe-react-native/Tests (0.57.0):
1927+
- stripe-react-native/Tests (0.57.1):
19281928
- hermes-engine
19291929
- RCTRequired
19301930
- RCTTypeSafety
@@ -2306,7 +2306,7 @@ SPEC CHECKSUMS:
23062306
RNCPicker: c8a3584b74133464ee926224463fcc54dfdaebca
23072307
RNScreens: 61c18865ab074f4d995ac8d7cf5060522a649d05
23082308
Stripe: 4728e3e0dd8df134e4a420ab504e929a93a815f0
2309-
stripe-react-native: 9045e39ccb93b1c7a4e442d22dcbb6754ed1950c
2309+
stripe-react-native: 366d481c8572dcc8a93f6401d8de31de6a5cce04
23102310
StripeApplePay: 43997281ace138a1c75a8f2d7be11925ea28644c
23112311
StripeCameraCore: e61d13cf270c450e699b12857dc75edc55a8455c
23122312
StripeCore: 457c30e2fd3a7c4b274a5ad53d1ff03661eef2a0
@@ -2317,8 +2317,8 @@ SPEC CHECKSUMS:
23172317
StripePaymentSheet: 3f93ce6ea84afde770d3c7e18a9b8f99aed63896
23182318
StripePaymentsUI: 626726a01255a6458c35436f7f6431dacee82684
23192319
StripeUICore: 30f8352fd7a5cf1541b7777a57b3ad1133bf6763
2320-
Yoga: 5934998fbeaef7845dbf698f698518695ab4cd1a
2320+
Yoga: 3fd22c2cae22d7a2b0f0b538f55f7c2a17dc94d5
23212321

23222322
PODFILE CHECKSUM: 605f3cad878b7c0eee93ebb4a78a8141d34cd328
23232323

2324-
COCOAPODS: 1.15.2
2324+
COCOAPODS: 1.16.2

0 commit comments

Comments
 (0)