🌊

Capacitor Stripeプラグインで依存をUp to Next Major versionにしない理由

2024/12/04に公開

2021年頃から、Capacitor Stripeプラグインのメンテナンスをしている榊原です。CapacitorはWebアプリをiOS/Androidアプリに変換するためのライブラリで、詳しくは以下の記事をご覧ください。

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

で、Capacitor Stripeプラグインというのはとても簡単にいうと、Capacitorを通して、自分のつくったWebアプリからStripeのネイティブ機能を使うことができるものです。もっと具体的にいうと、CapacitorからStripe SDKを操作できるようになっています。 現段階では、以下のプラグインをメンテナンスしています。

  • @capacitor-community/stripe : Stripeを使ったインターネット決済
  • @capacitor-community/stripe-identity : Stripe Identityを使った本人確認
  • @capacitor-community/stripe-terminal : Stripe Terminalを使った対面決済

@capacitor-community/stripe-terminal だけは昨年末から段階を踏んでリリースしていってるので、現段階でようやくrelease candidate phaseですが、他はstableとして多くのWeb開発者に利用してもらっています。

なぜ、マイナーバージョンまでを固定してるのか

Stripe SDKの「ラッパー」というと、どこまでのBug Fixや、最新機能を使えるのかと心配になるかと思います。当然ながら、ラッパーとして、対応していないSDKバージョンは指定できないので、大まかにStripe SDKはSemVerのバージョニングをしていると公開しているので、それに準じるようにしていました。

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

メジャー。最新バージョンとの後方互換性がない、対応が必要な変更がバージョンに含まれる場合は、「メジャー」バージョンコンポーネントが増分されます (必須パラメーターの追加や、タイプ、プロパティ、メソッド、パラメーターの変更)。たとえば、SDK の例外クラスの名前の変更がこれに含まれます。

マイナー、パッチは、後方互換性を保っている場合のみナンバリングされるとあります。ですので、SemVerに沿って考えるなら、常にメジャーバージョン内の最新バージョンを使うことが理想的ではあります。

ですが、現実的には、Stripeは度々、マイナーバージョンでの(ラッパーにとっては)破壊的変更を行うために、プラグインでは以下のようにマイナーバージョンまでを指定しています。

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

重々、メジャーバージョンを指定すべきであることはわかっているのですが、近日でいえば以下のような変更がありました。

Stripe Terminal SDK

最近、Stripe Terminal SDKは4.0.0がでました。そして、続いて、4.1.0がリリースされました。当然ながらこれはマイナーバージョンでの変更なのですが、なぜかJavaの SimulatorConfiguration の必須の引数がひとつ増えていました。 offlineEnabled: kotlin.Boolean = COMPILED_CODE が追加されており、対応しないとビルドは失敗します。

当然ながら、メジャーバージョンだけを指定していた場合、ユーザはマイナーバージョンのコントロールができないので、「ビルドエラー」となります。ユーザが直す方法は、プラグイン(ライブラリ)をForkするか、もしくはプルリクエストをだすしかないですよね。ネイティブユーザにとっては、引数をひとつ足すだけの「ほぼ作業なんてあってないような」変更ですが、プラグインユーザにとってはそれは決して小さな作業ではありません。

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

Payment SDKはここらへんは結構慎重にアップデートしてくれている感覚なのですが、dependencyに関してはそうではありません。まず、20.52.xから20.53.0にアップデートした時に、 dependency をアップデートして、ビルドエラーが発生しました。一応Issueをあげてはみたのですが、「依存関係更新したから、そっちも依存関係更新してね」とのことでした。いや、ネイティブユーザにとっては簡単な作業なのですが、プラグインユーザはこれアップデートするのはとても大変です。

基本的にCapacitorのユーザはWebアプリ制作者であってネイティブ開発者ではない上に、Capacitorプラグインのコードは私(他人)が書いたコードなので更に追うのは難しいですよね。

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/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/9232

それでも、アップデートかけないといけないなと思ったら今日は更に別のエラーが出ました。こちらは既存ライブラリが(どういうdependencyの指定をしてたかまで追っていませんが)エラーがでるようになったそうです。みんな、SemVerは平和でいいですよ・・・。

で、表題の「なぜ、マイナーバージョンまでを固定してるのか」なのですが、おわかりのように、こういったプラグインにとっての破壊的変更にできるだけユーザが巻き込まれないようにするためです。繰り返しになりますが、ネイティブユーザにとっては対応の労力は低く、またアップデートすることは既存の不具合の修正や新機能が利用できるすばらしい機会だと思っています。しかしながら、破壊的変更と秤にかけて、現時点ではマイナーバージョンまでを固定してリリースしています。

それでも最新版を使いたい

ただ、ここまではプラグイン作者としての「安定したバージョンを提供したい」ですので、それでも最新版を使いたいという需要もあるかと思います。破壊的なことない限りは最新の方がいいですものね。iOSに関しては、SDKのバージョンを変数で容易に提供する方法が見つかっていないので固定となってしまいますが、Androidに関しては 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'
  }

これを追記しなければプラグインデフォルトのバージョン指定となりますが、(自己責任になりますが)ここでバージョン指定することで最新版、もしくは不具合が発生している場合は最新版ではない以前のバージョンを直接指定することもできます。

3年ほど運用した感想なのですが、基本的にマイナーバージョンでの破壊的変更はiOSではそれほど見かけず、ほとんどがAndroidによるものですので、不具合が起きて(もちろんIssueで報告をもらえると嬉しいですが)緊急で対応しないといけない場合はこちらを設定してもらえればと思います。

まとめ

個人的には「最新版を提供することが正義」とは思いながら、こういった経緯でマイナーバージョンまでを固定してプラグインを提供しています。100%の利便性とはいえませんが、代わりに安定して決済機能や本人確認、対面決済を実装することができますので、興味ある方はぜひ一度お試しください。

それではまた。

Discussion