Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Standardise currency value storage as cents (StripeQuantumCurrencyAmountField) #955

Open
therefromhere opened this issue Aug 30, 2019 · 4 comments
Labels
currency storage Anything to do with storing and surfacing currency to/from the database
Milestone

Comments

@therefromhere
Copy link
Contributor

Stripe handles all currency values as integer cents, but we currently have a mixture of storage as cent and as decimals.

StripeDecimalCurrencyAmountField stores as decimal dollar (etc) values, ie $1.99 = Decimal("1.99"))

StripeQuantumCurrencyAmountField stores as integer cents values, ie $1.99 = int(199)

Currently we have something like 16 Decimal fields and 12 Quantum fields (see below). 3 of the Quantum fields are new since 2.0 (those in PaymentIntent), so we could potentially switch them to decimal before 2.1 release.

We should have a documented policy on this (eg a "don't use this for new fields" note in the docstring of one of those field classes?) , and ultimately aim to switch all values to either cents or decimal.

Obviously migrating existing fields needs to be done very carefully so users don't accidentally send value 100x to large or small - I think the approach is a good idea -

if not isinstance(amount, decimal.Decimal):
raise ValueError("You must supply a decimal value representing dollars.")

Thoughts @kavdev (cc @jleclanche )?

Related tickets: #247, #468

dump of current field usage, (editted from result ack ".* = StripeDecimalCurrencyAmountField|^class [^\(]*" -o on current master)

Decimal:

core.py

36:class BalanceTransaction
81:    amount = StripeDecimalCurrencyAmountField
82:    amount_refunded = StripeDecimalCurrencyAmountField

connect.py

464:class Transfer
478:    amount = StripeDecimalCurrencyAmountField
479:    amount_reversed = StripeDecimalCurrencyAmountField

payment_methods.py

369:class Source
374:    amount = StripeDecimalCurrencyAmountField

billing.py

27:class Coupon
29:    amount_off = StripeDecimalCurrencyAmountField

126:class Invoice
153:    amount_due = StripeDecimalCurrencyAmountField
162:    amount_paid = StripeDecimalCurrencyAmountField
166:    amount_remaining = StripeDecimalCurrencyAmountField
175:    application_fee_amount = StripeDecimalCurrencyAmountField
338:    subtotal = StripeDecimalCurrencyAmountField
342:    tax = StripeDecimalCurrencyAmountField
356:    total = StripeDecimalCurrencyAmountField

629:class InvoiceItem
641:    amount = StripeDecimalCurrencyAmountField
740:class Plan
767:    amount = StripeDecimalCurrencyAmountField

Quantum:

core.py
36:class BalanceTransaction
45:    amount = StripeQuantumCurrencyAmountField
56:    fee = StripeQuantumCurrencyAmountField
60:    net = StripeQuantumCurrencyAmountField

1188:class Dispute
1196:    amount = StripeQuantumCurrencyAmountField

1362:class PaymentIntent
1370:    amount = StripeQuantumCurrencyAmountField
1373:    amount_capturable = StripeQuantumCurrencyAmountField
1376:    amount_received = StripeQuantumCurrencyAmountField

1873:class Refund
1880:    amount = StripeQuantumCurrencyAmountField

connect.py

355:class ApplicationFee
365:    amount = StripeQuantumCurrencyAmountField
366:    amount_refunded = StripeQuantumCurrencyAmountField

392:class ApplicationFeeRefund
404:    amount = StripeQuantumCurrencyAmountField

536:class TransferReversal
543:    amount = StripeQuantumCurrencyAmountField
@therefromhere
Copy link
Contributor Author

Added #958 for a special case (probably would need a 3rd CurrencyAmountField class to support).

@kavdev
Copy link
Member

kavdev commented Sep 2, 2019

@therefromhere I think it's time we finally bit the bullet and moved fully to cents. We might want to have amount_*_decimal cached properties on our models to match what stripe is doing in #958 and to make the transition easier.

@therefromhere
Copy link
Contributor Author

therefromhere commented Sep 2, 2019

Yeah, I'm happy for us to move towards storing cents instead of decimal dollars.

So for now, shall we say that all new fields should be in cents (with comments on the fields to that effect), plus a note in the CHANGELOG about an upcoming migration of existing fields (probably for 3.0), and yeah we could look at adding *_cents and/or *_decimal properties on the models to help the migration.

We should also check the help text of existing CurrencyAmount fields, since some of them are definitely misleading.

I've not looked into this, but maybe subclassing Decimal and using that subclass for the base type of StripeDecimalCurrencyAmountField would also help with adding checks?

Note that I think we should hold off on #958 since it's likely to cause more confusion since it's decimal but holding cents.

  • Add note on field classes that StripeQuantumCurrencyAmountField should be used for new fields
  • Update changelog about 3.0 migration to cents
  • Investigate how we can minimize the pain of migration

I'll mention this in discord.

@therefromhere therefromhere modified the milestone: 3.0.0 Sep 5, 2019
@therefromhere therefromhere changed the title Standardise currency value storage (as cents or decimal) Standardise currency value storage as cents (StripeQuantumCurrencyAmountField) Sep 5, 2019
therefromhere added a commit to prismaticd/dj-stripe that referenced this issue Sep 7, 2019
therefromhere added a commit to prismaticd/dj-stripe that referenced this issue Sep 13, 2019
therefromhere added a commit that referenced this issue Sep 13, 2019
@kavdev kavdev added the currency storage Anything to do with storing and surfacing currency to/from the database label Sep 3, 2020
@sshishov
Copy link

sshishov commented Apr 6, 2022

We are using the price like 0.0089$ which is metered and has tiers. We are calculating the usage with some transaction done by the customer and for us storing cents will show None and cannot be calculated. How guys you are going to address this issue?

Currently for this small price we are having this exception when we are trying to print the price:

/home/user/venv/lib/python3.7/site-packages/djstripe/models/core.py in __str__(self)
   2242         subscriptions = Subscription.objects.filter(plan__id=self.id).count()
   2243         if self.recurring:
-> 2244             return f"{self.human_readable_price} for {self.product.name} ({subscriptions} subscriptions)"
   2245         return f"{self.human_readable_price} for {self.product.name}"
   2246

/home/user/venv/lib/python3.7/site-packages/djstripe/models/core.py in human_readable_price(self)
   2255             flat_amount_tier_1 = tier_1["flat_amount"]
   2256             formatted_unit_amount_tier_1 = get_friendly_currency_amount(
-> 2257                 tier_1["unit_amount"] / 100, self.currency
   2258             )
   2259             amount = f"Starts at {formatted_unit_amount_tier_1} per unit"

TypeError: unsupported operand type(s) for /: 'NoneType' and 'int'

And the dictionary of the object is showing:

 ...
 'billing_scheme': 'tiered',
 'lookup_key': '',
 'tiers': [{'flat_amount': 0,
   'flat_amount_decimal': '0',
   'unit_amount': None,
   'unit_amount_decimal': '7.5',
   'up_to': 5},
  {'flat_amount': 0,
   'flat_amount_decimal': '0',
   'unit_amount': None,
   'unit_amount_decimal': '3.8',
   'up_to': None}],
 'tiers_mode': 'volume',
 'transform_quantity': None}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
currency storage Anything to do with storing and surfacing currency to/from the database
Projects
None yet
3 participants