← All articles

Why Capacitor Stripe Plugins Pin Minor SDK Versions Instead of Up to Next Major

Stripe SDK SemVer looks safe on paper, but minor releases often break plugin users. I explain why I pin to minor versions and how to override Android SDK versions in variables.gradle.

Published
Why Capacitor Stripe Plugins Pin Minor SDK Versions Instead of Up to Next Major cover image

Since around 2021, I have maintained the Capacitor Stripe plugins. Capacitor turns web apps into iOS and Android apps; for background, see the articles below.

https://zenn.dev/rdlabo/articles/4a241cacc7e364be8066https://zenn.dev/rdlabo/articles/4a241cacc7e364be8066
https://capacitorjs.jp/https://capacitorjs.jp/

In short, the Capacitor Stripe plugins let your web app use Stripe's native features through Capacitor—more concretely, they expose Stripe SDK APIs to Capacitor. I currently maintain these plugins:

  • @capacitor-community/stripe: Internet payments with Stripe
  • @capacitor-community/stripe-identity: Identity verification with Stripe Identity
  • @capacitor-community/stripe-terminal: In-person payments with Stripe Terminal

I have been rolling out @capacitor-community/stripe-terminal in stages since late last year; it is finally in release candidate phase. The others are stable and used by many web developers.

Why I pin through the minor version

Calling these plugins "wrappers" around the Stripe SDK raises a fair question: how far do bug fixes and latest features reach? As a wrapper, I cannot target SDK versions I do not support. Stripe says SDK versions broadly follow SemVer, so I aligned with that.

https://docs.stripe.com/sdks/versioning?locale=ja-JPhttps://docs.stripe.com/sdks/versioning?locale=ja-JP

Major. The major version component increments when a release includes breaking changes that require action and are not backward compatible with the latest version—for example, adding required parameters or changing types, properties, methods, or parameters. Renaming an SDK exception class is an example.

Minor and patch releases are numbered only when backward compatibility is preserved. So in SemVer terms, staying on the latest version within a major should be ideal.

In practice, though, Stripe often ships changes that are breaking for a wrapper in minor releases, so the plugins pin through the minor version like this:

 s.dependency 'StripePaymentSheet', '~> 23.32.0'
rootProject.ext.stripeAndroidVersion : '21.2.+'

I know I should specify only the major version, but recent examples include the following.

Stripe Terminal SDK

Stripe Terminal SDK 4.0.0 shipped recently, followed by 4.1.0. That is a minor bump, but Java's SimulatorConfiguration gained a required argument: offlineEnabled: kotlin.Boolean = COMPILED_CODE. Without handling it, the build fails.

If you only pinned the major version, users cannot control minor versions and hit a build error. Their options are to fork the plugin or open a pull request. For native developers, adding one argument is almost trivial. For plugin users, it is not a small task.

4.0.0 to 4.1.0

- public final data class SimulatorConfiguration public constructor(update: com.stripe.stripeterminal.external.models.SimulateReaderUpdate = COMPILED_CODE, simulatedCard: com.stripe.stripeterminal.external.models.SimulatedCard = COMPILED_CODE, simulatedTipAmount: kotlin.Long? = COMPILED_CODE) {

+ public final data class SimulatorConfiguration public constructor(update: com.stripe.stripeterminal.external.models.SimulateReaderUpdate = COMPILED_CODE, simulatedCard: com.stripe.stripeterminal.external.models.SimulatedCard = COMPILED_CODE, simulatedTipAmount: kotlin.Long? = COMPILED_CODE, offlineEnabled: kotlin.Boolean = COMPILED_CODE) {

Stripe SDK

The Payment SDK side feels cautious about updates, but dependencies are another story. When I moved from 20.52.x to 20.53.0 and updated dependencies, the build failed. I opened an issue and was told to update my dependencies too. Easy for native developers; very hard for plugin users.

Capacitor users are mostly web app authors, not native developers, and the plugin code is mine—not theirs—so tracking changes is even harder.

20.52.x to 20.53.0

java.lang.RuntimeException: Unable to get provider androidx.startup.InitializationProvider: androidx.startup.StartupException: androidx.startup.StartupException: java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/lifecycle/ReportFragment$ActivityInitializationListener;

https://github.com/stripe/stripe-android/issues/9534https://github.com/stripe/stripe-android/issues/9534

Currency

No static method performImeAction$default(Landroidx/compose/ui/semantics/SemanticsPropertyReceiver;Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V in class Landroidx/compose/ui/semantics/SemanticsPropertiesKt; or its super classes (declaration of 'androidx.compose.ui.semantics.SemanticsPropertiesKt' appears in /data/app/~~03iCRp6iktPxzncJ0hrLzw==/com.passenger.engineering.debug-UHIvL9R0D90sbbZRZ6P_4A==/base.apk)

https://github.com/stripe/stripe-android/issues/9232https://github.com/stripe/stripe-android/issues/9232

When I thought I finally had to update, another error appeared today. An existing library started failing (I have not traced exactly how dependencies were declared). SemVer is supposed to be peaceful...

So, to answer the title—why pin through the minor version—it is to keep plugin users from being swept up in these breaking changes as much as possible. Native developers face low effort and gain fixes and new features; weighed against breakage, I currently release with minor-version pins.

If you still want the latest SDK

Everything above reflects what I want as a plugin author: a stable version for users. Some of you will still want the latest SDK, and that is reasonable when nothing breaks. On iOS I have not found an easy variable-based way to expose SDK versions, so they stay fixed. On Android you can override versions directly in android/variables.gradle.

  ext {
...
    // If you use @capacitor-community/stripe:
+   stripeAndroidVersion = '20.39.+'

    // If you use @capacitor-community/stripe-identity:
+   identityVersion = '20.39.+'

    // If you use @capacitor-community/stripe-terminal:
+   stripeterminalCoreVersion = '3.5.0'
+   stripeterminalLocalmobileVersion = '3.5.0'
  }

Without these entries, the plugin defaults apply. At your own risk, you can pin the latest version—or an older one if the latest causes trouble.

After about three years of running the plugins, breaking minor bumps are rare on iOS and mostly Android. If something breaks (I appreciate issue reports), this override is the emergency escape hatch.

Summary

Personally I would like to ship the latest SDK, but given this history I pin through the minor version. It is not 100% convenient, but it lets you implement payments, identity verification, and in-person checkout more reliably. If you are interested, give the plugins a try.

See you next time.