Summary
When react-native-razorpay is used in the same app as another Razorpay Android SDK that depends on a newer com.razorpay:core (in our case com.razorpay:customui-core), Gradle force-upgrades the shared com.razorpay:core artifact. This silently swaps the analytics SDK that backs Standard Checkout onto a core version it was not built against, and produces a 100%-fatal NullPointerException in com.razorpay.Lumberjack.addOrderProperty when the user taps Cancel on the payment retry/failed dialog.
The crash is entirely inside the Razorpay SDK — app JS/native code is not on the stack.
Stack trace
java.lang.NullPointerException
at java.util.concurrent.ConcurrentHashMap.putVal (ConcurrentHashMap.java:1011)
at java.util.concurrent.ConcurrentHashMap.put (ConcurrentHashMap.java:1006)
at com.razorpay.Lumberjack.addOrderProperty (Lumberjack.java)
at com.razorpay.AnalyticsUtil.addProperty (AnalyticsUtil.java)
at com.razorpay.CheckoutPresenterImpl.destroyActivity (CheckoutPresenterImpl.java)
at com.razorpay.CheckoutPresenterImpl.handleRetry (CheckoutPresenterImpl.java)
at com.razorpay.CheckoutPresenterImpl$12.onNegativeButtonClick (CheckoutPresenterImpl.java)
at com.razorpay.CheckoutUtils$2.onClick (CheckoutUtils.java)
at android.app.Dialog ... (AlertController button handler)
Environment
react-native-razorpay: 2.3.1 (bundles native com.razorpay:standard-core:1.7.1 → com.razorpay:core:1.0.1)
- Also present in the app:
com.razorpay:customui-core:3.10.8 (→ com.razorpay:core:1.0.11)
- React Native New Architecture (Fabric), Android. Observed on Android 12–15, multiple OEMs.
- 100% fatal — no recovery.
Root cause (verified by decompiling the published AARs)
./gradlew :app:dependencies --configuration releaseRuntimeClasspath shows the shared core artifact being upgraded:
+--- project :react-native-razorpay
| \--- com.razorpay:standard-core:1.7.1
| \--- com.razorpay:core:1.0.1 -> 1.0.11 <-- forced upgrade
+--- com.razorpay:customui-core:3.10.8
\--- com.razorpay:core:1.0.11
Only one com.razorpay:core can be on the classpath, so it resolves to 1.0.11.
Inside com.razorpay:core, the Lumberjack analytics maps changed type between versions, while addOrderProperty stayed identical:
// addOrderProperty — IDENTICAL in every core version
static void addOrderProperty(String k, Object v) { orderProperties.put(k, v); }
// core <= 1.0.2: private static Map<String,Object> orderProperties = new HashMap(); // null value OK
// core >= 1.0.3: private static Map<String,Object> orderProperties = new ConcurrentHashMap(); // null value -> NPE
ConcurrentHashMap forbids null keys/values (putVal), whereas HashMap allowed them.
The null value originates inside Standard Checkout's own CheckoutPresenterImpl (this code is unchanged through the latest standard-core 1.7.12):
// User taps "Cancel" on the retry/failed dialog:
onNegativeButtonClick() -> handleRetry(null)
public void handleRetry(String str) { // str == null
if (CheckoutUtils.shouldRetryPayment(paymentAttempts)) {
if (str != null) { ...; loadForm(str); return; }
destroyActivity(0, ""); // safe only while retries remain
return;
}
destroyActivity(0, str); // retries exhausted -> str(null) flows through
}
public void destroyActivity(int i, String str) {
AnalyticsUtil.addProperty("destroy_result",
new AnalyticsProperty(str, AnalyticsProperty.Scope.ORDER)); // value == null
// -> Lumberjack.addOrderProperty("destroy_result", null)
// -> orderProperties.put("destroy_result", null)
// HashMap: fine ConcurrentHashMap: NPE
}
So a Standard Checkout SDK compiled against the null-tolerant HashMap core is executed against the null-hostile ConcurrentHashMap core, and its own destroyActivity(0, null) (passed from the Cancel handler when retries are exhausted) crashes the app.
This is not specific to old versions. We confirmed the latest standard-core:1.7.12 still calls handleRetry(null) -> destroyActivity(0, str), and the latest core:1.0.13 still uses an unguarded ConcurrentHashMap.put. So standard-core:1.7.12 + core:1.0.13 reproduces the same crash.
Steps to reproduce
- In an Android app, depend on
react-native-razorpay (Standard Checkout) and a Razorpay SDK that requires com.razorpay:core >= 1.0.3 (e.g. com.razorpay:customui-core:3.10.x). Confirm :app:dependencies resolves com.razorpay:core to >= 1.0.3.
- Open Standard Checkout for any order.
- Trigger a payment failure so Razorpay shows its "Payment failed / Retry?" dialog, and let retry attempts be exhausted (or otherwise reach the non-retry path).
- Tap Cancel on the dialog.
- App crashes with the NPE above.
Expected vs actual
- Expected: Cancelling the retry dialog dismisses checkout cleanly.
- Actual: Fatal
NullPointerException inside Lumberjack.addOrderProperty.
Suggested fix
Any one of:
- Null-guard the analytics put in
com.razorpay:core — Lumberjack.addOrderProperty / addPaymentProperty should skip or coalesce null keys/values before ConcurrentHashMap.put (keeping the concurrent map). This is the smallest, safest change and fixes the whole class of destroy_result-style nulls.
- Coalesce in
standard-core — destroyActivity(int, String) / handleRetry(null) should pass "" (or a non-null sentinel) instead of null.
- Document and publish a compatible version matrix so Standard Checkout and other
com.razorpay:* SDKs that share com.razorpay:core are guaranteed to align, since they cannot currently coexist safely on a single core.
Happy to provide additional decompiled diffs or a sample project if useful. Thanks!
Summary
When
react-native-razorpayis used in the same app as another Razorpay Android SDK that depends on a newercom.razorpay:core(in our casecom.razorpay:customui-core), Gradle force-upgrades the sharedcom.razorpay:coreartifact. This silently swaps the analytics SDK that backs Standard Checkout onto acoreversion it was not built against, and produces a 100%-fatalNullPointerExceptionincom.razorpay.Lumberjack.addOrderPropertywhen the user taps Cancel on the payment retry/failed dialog.The crash is entirely inside the Razorpay SDK — app JS/native code is not on the stack.
Stack trace
Environment
react-native-razorpay: 2.3.1 (bundles nativecom.razorpay:standard-core:1.7.1→com.razorpay:core:1.0.1)com.razorpay:customui-core:3.10.8(→com.razorpay:core:1.0.11)Root cause (verified by decompiling the published AARs)
./gradlew :app:dependencies --configuration releaseRuntimeClasspathshows the sharedcoreartifact being upgraded:Only one
com.razorpay:corecan be on the classpath, so it resolves to 1.0.11.Inside
com.razorpay:core, theLumberjackanalytics maps changed type between versions, whileaddOrderPropertystayed identical:ConcurrentHashMapforbids null keys/values (putVal), whereasHashMapallowed them.The null value originates inside Standard Checkout's own
CheckoutPresenterImpl(this code is unchanged through the lateststandard-core1.7.12):So a Standard Checkout SDK compiled against the null-tolerant
HashMapcore is executed against the null-hostileConcurrentHashMapcore, and its owndestroyActivity(0, null)(passed from the Cancel handler when retries are exhausted) crashes the app.This is not specific to old versions. We confirmed the latest
standard-core:1.7.12still callshandleRetry(null) -> destroyActivity(0, str), and the latestcore:1.0.13still uses an unguardedConcurrentHashMap.put. Sostandard-core:1.7.12 + core:1.0.13reproduces the same crash.Steps to reproduce
react-native-razorpay(Standard Checkout) and a Razorpay SDK that requirescom.razorpay:core >= 1.0.3(e.g.com.razorpay:customui-core:3.10.x). Confirm:app:dependenciesresolvescom.razorpay:coreto>= 1.0.3.Expected vs actual
NullPointerExceptioninsideLumberjack.addOrderProperty.Suggested fix
Any one of:
com.razorpay:core—Lumberjack.addOrderProperty/addPaymentPropertyshould skip or coalesce null keys/values beforeConcurrentHashMap.put(keeping the concurrent map). This is the smallest, safest change and fixes the whole class ofdestroy_result-style nulls.standard-core—destroyActivity(int, String)/handleRetry(null)should pass""(or a non-null sentinel) instead ofnull.com.razorpay:*SDKs that sharecom.razorpay:coreare guaranteed to align, since they cannot currently coexist safely on a singlecore.Happy to provide additional decompiled diffs or a sample project if useful. Thanks!