-
Notifications
You must be signed in to change notification settings - Fork 535
Migration guide for v15
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.
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 — 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().
# 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"# 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
...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")-
to_dict_recursive()— useto_dict()instead (it now recurses by default) -
stripe_idproperty — useidinstead
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.
If you read a decimal field from one Stripe object and pass it to another without manipulating it, no changes are needed.
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.99Before:
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",
)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") # TrueRequest 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",
}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 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): ...