Skip to content

Migration guide for v15

remi-stripe edited this page Jul 31, 2026 · 5 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 Python SDK specific changelog for v15 has the corresponding changes in the SDKs as well as SDK specific breaking changes.

Minimum Python version raised to 3.9

Python versions 3.7 and 3.8 are no longer supported. The package now requires Python >= 3.9.

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

Note that Python 3.9 support will be dropped in March 2027. Please make plans to update to at least Python 3.10 before then.

StripeObject no longer inherits from dict

StripeObject — the base class for all Stripe API resources — no longer inherits from dict. Previously, every Stripe object was also a Python dictionary, which meant dict methods like .get(), .keys(), .values(), and .items() were available on every resource. This caused persistent confusion because subscription.items returned the built-in dict.items() method instead of the subscription's line items.

Stripe objects now store their data internally and expose it through attribute access, bracket notation, and to_dict().

What still works

# Attribute access — unchanged
customer.name

# Bracket notation — unchanged
customer["name"]

# Membership testing — unchanged
"name" in customer

# String representation — unchanged
print(customer)  # still prints formatted JSON

# Writing — unchanged
customer.name = "New Name"
customer["name"] = "New Name"

What no longer works

# dict methods are gone
customer.get("name")           # AttributeError
customer.keys()                # AttributeError
customer.values()              # AttributeError
customer.items()               # now returns the subscription's items (if applicable),
                               # not dict.items()
dict(customer)                 # empty / no longer dumps data
for key in customer:           # no longer iterates over keys
    ...

Migrating

Use to_dict() to get a plain dictionary. This method recursively converts all nested Stripe objects into native Python types:

# Before
data = dict(customer)
for key, value in customer.items():
    ...

# After
data = customer.to_dict()
for key, value in customer.to_dict().items():
    ...

Note: If you want to store the information as JSON, use str(customer) for example which will give you a JSON representation of the API resource as a string. If you want to use to_dict() first, make sure to use .to_dict(for_json=True) explicitly so that we properly serialize specific types such as Decimal to a str or expanded resource.

Replace .get() with attribute access or bracket notation:

# Before
name = customer.get("name", "default")

# After
name = customer.to_dict().get("name", "default")
# or
name = getattr(customer, "name", "default")

Removed deprecated methods

  • to_dict_recursive() — use to_dict() instead (it now recurses by default)
  • stripe_id property — use id instead

Decimal fields use Decimal instead of str

The v15 release of stripe-python introduces native Decimal support for all decimal_string fields. All fields with format: decimal in the Stripe API (such as unit_amount_decimal, quantity_decimal, and fx_rate) now use Python's decimal.Decimal instead of str in both request params and response objects. This applies to V1 and V2 resources.

The SDK handles conversion to/from the string wire format transparently.

Note: Amount-related fields such as unit_amount_decimal are still denominated in the smallest currency unit. The _decimal option lets you define sub-cent pricing for example. If you pass unit_amount_decimal: 9.99 for USD it means $0.0999 USD and not $9.99 USD. Always refer to our API Reference description for the given field if unsure.

No changes needed if you only pass values through

If you read a decimal field from one Stripe object and pass it to another without manipulating it, no changes are needed.

Reading decimal fields

Before:

invoice = stripe.Invoice.retrieve("in_xxx")
amount = invoice.lines.data[0].pricing.unit_amount_decimal  # "9.99" (str)
parsed = float(amount)

After:

invoice = stripe.Invoice.retrieve("in_xxx")
amount = invoice.lines.data[0].pricing.unit_amount_decimal  # Decimal("9.99")
str(amount)    # "9.99"
float(amount)  # 9.99

Setting decimal params

Before:

stripe.Price.create(
    unit_amount_decimal="9.99",
    currency="usd",
    recurring={"interval": "month"},
    product="prod_xxx",
)

After:

from decimal import Decimal

stripe.Price.create(
    unit_amount_decimal=Decimal("9.99"),
    currency="usd",
    recurring={"interval": "month"},
    product="prod_xxx",
)

If you compare decimal fields as strings

Code that compares decimal field values using string equality will break:

# Before — worked
invoice.lines.data[0].pricing.unit_amount_decimal == "9.99"  # True

# After — breaks (Decimal != str)
invoice.lines.data[0].pricing.unit_amount_decimal == "9.99"  # False

# Fix — compare as Decimal
from decimal import Decimal
invoice.lines.data[0].pricing.unit_amount_decimal == Decimal("9.99")  # True

Type annotations

Request param types are Decimal (not str). If you use type checking:

from decimal import Decimal
from stripe.params import PriceCreateParams

params: PriceCreateParams = {
    "unit_amount_decimal": Decimal("9.99"),  # Decimal, not str
    "currency": "usd",
    "recurring": {"interval": "month"},
    "product": "prod_xxx",
}

Affected fields

Every field below changes from str to Decimal on both response objects and request params.

Billing

Resource Field
stripe.Plan amount_decimal
stripe.Plan tiers[].flat_amount_decimal
stripe.Plan tiers[].unit_amount_decimal
stripe.Price unit_amount_decimal
stripe.Price tiers[].flat_amount_decimal
stripe.Price tiers[].unit_amount_decimal
stripe.Price currency_options[].unit_amount_decimal
stripe.Price currency_options[].tiers[].flat_amount_decimal
stripe.Price currency_options[].tiers[].unit_amount_decimal
stripe.InvoiceItem quantity_decimal
stripe.InvoiceItem pricing.unit_amount_decimal
stripe.InvoiceLineItem quantity_decimal
stripe.InvoiceLineItem pricing.unit_amount_decimal
stripe.CreditNoteLineItem unit_amount_decimal

Climate

Resource Field
stripe.climate.Order metric_tons
stripe.climate.Product metric_tons_available

Checkout

Resource Field
stripe.checkout.Session currency_conversion.fx_rate

Issuing

Resource Field
stripe.issuing.Authorization fleet.reported_breakdown.fuel.gross_amount_decimal
stripe.issuing.Authorization fleet.reported_breakdown.non_fuel.gross_amount_decimal
stripe.issuing.Authorization fleet.reported_breakdown.tax.local_amount_decimal
stripe.issuing.Authorization fleet.reported_breakdown.tax.national_amount_decimal
stripe.issuing.Authorization fuel.quantity_decimal
stripe.issuing.Authorization fuel.unit_cost_decimal
stripe.issuing.Transaction purchase_details.fleet.reported_breakdown.fuel.gross_amount_decimal
stripe.issuing.Transaction purchase_details.fleet.reported_breakdown.non_fuel.gross_amount_decimal
stripe.issuing.Transaction purchase_details.fleet.reported_breakdown.tax.local_amount_decimal
stripe.issuing.Transaction purchase_details.fleet.reported_breakdown.tax.national_amount_decimal
stripe.issuing.Transaction purchase_details.fuel.quantity_decimal
stripe.issuing.Transaction purchase_details.fuel.unit_cost_decimal

V2

Resource Field
stripe.v2.core.Account identity.individuals[].relationship.percent_ownership
stripe.v2.core.AccountPerson relationship.percent_ownership

Request-only decimal fields also change (e.g., price_data.unit_amount_decimal on Subscription, SubscriptionItem, SubscriptionSchedule, Quote, PaymentLink, Invoice line creation params).

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.

Resource amount fields now use the shared stripe.v2.Amount class instead of per-resource nested classes.

# Before
account = stripe.v2.core.AccountService.retrieve("acct_123")
amount = account.identity.business_details.annual_revenue.amount
# amount was typed as Account.Identity.BusinessDetails.AnnualRevenue.Amount

# After
from stripe.v2 import Amount

amount = account.identity.business_details.annual_revenue.amount
# amount is now typed as Amount
print(amount.value)     # int
print(amount.currency)  # str, e.g. "usd"

If you were referencing the nested Amount class directly (e.g. in type annotations), update the import:

# Before
from stripe.v2.core._account import Account
def process(amt: Account.Identity.BusinessDetails.AnnualRevenue.Amount): ...

# After
from stripe.v2 import Amount
def process(amt: Amount): ...

Clone this wiki locally