Skip to content

Migration guide for v19

Prathmesh Ranaut edited this page Mar 26, 2026 · 2 revisions

This version uses API version 2026-03-25.dahlia. If the format of this API version looks new to you, see our new API release process.

Please review our API changelog for 2026-03-25.dahlia to understand all the breaking changes to the Stripe API, the reasons behind them and potential alternatives.

The Ruby SDK specific changelog for v19 has the corresponding changes in the SDKs as well as SDK specific breaking changes.

Minimum Ruby version raised to 2.7

Ruby 2.6 is no longer supported. The gem now requires Ruby >= 2.7.0.

Action required: Upgrade to Ruby 2.7 or later before updating to the latest SDK version. For more information, see our language version support policy.

Note that this is the last major release that supports Ruby 2.7. Please make plans to update to at least 2.8 (and ideally Ruby 3.3 or higher) before September 2026.

Webhook parsing methods now reject the wrong event type

Previously, passing a V2 event notification payload to Stripe::Webhook.construct_event (or a V1 event payload to StripeClient#parse_event_notification) would silently succeed and return a malformed object. Both methods now raise ArgumentError with a message pointing you to the correct method.

Before (silently broken):

# V2 payload accidentally passed to V1 method — returned garbage, no error
event = Stripe::Webhook.construct_event(v2_payload, sig_header, secret)

After (loud error with guidance):

# Now raises ArgumentError
event = Stripe::Webhook.construct_event(v2_payload, sig_header, secret)

The fix — use the right method for each event version:

# V1 events (object: "event")
event = Stripe::Webhook.construct_event(payload, sig_header, secret)

# V2 event notifications (object: "v2.core.event")
event = client.parse_event_notification(payload, sig_header, secret)

decimal_string fields changed from String to BigDecimal

All API fields marked as decimal_string now use BigDecimal instead of String in both request parameters and response objects. The SDK handles bidirectional coercion:

  • Responses: String values from the API (e.g., "9.99") are automatically converted to BigDecimal.
  • Requests: BigDecimal, Integer, Float, and String values are all accepted and converted to plain decimal notation strings on the wire.

Affected fields (across V1 and V2 resources):

Resource Fields
Checkout::Session fx_rate (on currency_conversion)
Climate::Order metric_tons
Climate::Product metric_tons_available
CreditNoteLineItem unit_amount_decimal
InvoiceItem quantity_decimal, unit_amount_decimal
InvoiceLineItem quantity_decimal, unit_amount_decimal
Issuing::Authorization / Issuing::Transaction quantity_decimal, unit_cost_decimal, gross_amount_decimal, local_amount_decimal, national_amount_decimal
Plan amount_decimal, flat_amount_decimal, unit_amount_decimal
Price unit_amount_decimal, flat_amount_decimal (including currency_options and tiers)
V2::Core::Account / V2::Core::AccountPerson percent_ownership
Request params on Invoice, Product, Quote, Subscription, SubscriptionItem, SubscriptionSchedule, PaymentLink unit_amount_decimal, flat_amount_decimal, quantity_decimal

Before:

# Reading — was String
session = Stripe::Checkout::Session.retrieve("cs_test_xxx")
rate = session.currency_conversion.fx_rate
puts rate.class  # => String
puts rate         # => "1.23"

# Writing — passed String
Stripe::Climate::Order.create(
  product: "climsku_xxx",
  metric_tons: "5.5"
)

After:

# Reading — now BigDecimal
session = Stripe::Checkout::Session.retrieve("cs_test_xxx")
rate = session.currency_conversion.fx_rate
puts rate.class  # => BigDecimal
puts rate         # => 0.123e1 (BigDecimal display)
puts rate.to_s("F")  # => "1.23" (plain decimal string)

# Writing — BigDecimal is preferred, but String/Integer/Float still work
Stripe::Climate::Order.create(
  product: "climsku_xxx",
  metric_tons: BigDecimal("5.5")
)

# These also work (coerced automatically):
metric_tons: "5.5"     # String → still accepted
metric_tons: 5.5       # Float → coerced (but may lose precision)
metric_tons: 5         # Integer → coerced

Migration pattern:

# String comparison — update to use BigDecimal
# Before:
if line_item.unit_amount_decimal == "0.50"
# After:
if line_item.unit_amount_decimal == BigDecimal("0.50")

# String formatting — use to_s("F") for display
# Before:
puts "Rate: #{rate}"           # "1.23"
# After:
puts "Rate: #{rate.to_s("F")}" # "1.23"

# Arithmetic — works naturally with BigDecimal
total = line_item.unit_amount_decimal * line_item.quantity_decimal
# Returns BigDecimal, no precision loss

New runtime dependency: bigdecimal

The bigdecimal gem is now an explicit runtime dependency in stripe.gemspec.

Action required: If you previously vendored or pinned bigdecimal, ensure there are no conflicts.

Util.objects_to_ids signature change and V2 null serialization

The Stripe::Util.objects_to_ids method now accepts an optional serialize_empty: keyword argument. When true, nil values in hashes are preserved instead of being stripped.

Before:

Stripe::Util.objects_to_ids(params)

After:

# Existing behavior preserved (strip nils):
Stripe::Util.objects_to_ids(params)

# Preserve nils for V2 JSON bodies:
Stripe::Util.objects_to_ids(params, serialize_empty: true)

V2 Amount type consolidation

V2 resources previously generated a separate, per-field Amount class for every monetary amount property (e.g., OutboundPayment.Amount, AnnualRevenue.Amount). These duplicates have been replaced with a single shared Amount type. The fields (value and currency) are identical — only the type name and import path changed.

Amount fields now use the shared Stripe::V2::Amount class. Sorbet type signatures reflect the change.

# Before — nested class per resource
# amount was typed as Stripe::V2::Core::Account::Identity::BusinessDetails::AnnualRevenue::Amount

# After — shared Amount type
account = client.v2.core.accounts.retrieve("acct_123")
amount = account.identity.business_details.annual_revenue.amount
# amount is now a Stripe::V2::Amount
puts amount.value    # Integer
puts amount.currency # String, e.g. "usd"

If you reference the type directly in Sorbet signatures:

# Before
sig { params(amt: Stripe::V2::Core::Account::Identity::BusinessDetails::AnnualRevenue::Amount).void }

# After
sig { params(amt: Stripe::V2::Amount).void }

Clone this wiki locally