Skip to content

Standard Checkout NPE in Lumberjack.addOrderProperty (ConcurrentHashMap) when com.razorpay:core is force-upgraded to >=1.0.3 by another Razorpay SDK #523

Description

@yagyesh-twofourlabs

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.1com.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

  1. 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.
  2. Open Standard Checkout for any order.
  3. 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).
  4. Tap Cancel on the dialog.
  5. 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:

  1. Null-guard the analytics put in com.razorpay:coreLumberjack.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.
  2. Coalesce in standard-coredestroyActivity(int, String) / handleRetry(null) should pass "" (or a non-null sentinel) instead of null.
  3. 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!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions