From 3781fc02fafb9144e7901ccabc4998cc70918a95 Mon Sep 17 00:00:00 2001 From: Changaco Date: Mon, 15 Oct 2018 08:37:09 +0200 Subject: [PATCH] improve handling of currency exponents --- liberapay/billing/transactions.py | 36 ++++++++++------- liberapay/constants.py | 3 -- liberapay/models/_mixin_team.py | 8 ++-- liberapay/models/participant.py | 32 +++++++-------- liberapay/payin/stripe.py | 27 +++++++++++-- liberapay/testing/__init__.py | 10 +++-- liberapay/testing/mangopay.py | 4 +- liberapay/utils/currencies.py | 40 ++++++++++++------- tests/py/test_currencies.py | 30 +++++++++++--- www/%username/charts.json.spt | 3 +- www/%username/giving/index.html.spt | 1 - www/%username/giving/pay/stripe/%payin_id.spt | 4 +- www/about/stats.spt | 4 +- 13 files changed, 130 insertions(+), 72 deletions(-) diff --git a/liberapay/billing/transactions.py b/liberapay/billing/transactions.py index d198852736..543b8cbda1 100644 --- a/liberapay/billing/transactions.py +++ b/liberapay/billing/transactions.py @@ -30,6 +30,12 @@ QUARANTINE = '%s days' % QUARANTINE.days +def Money_to_cents(m): + r = Money(currency=m.currency) + r.amount = int(m.amount * 100) + return r + + def repr_error(o): r = o.ResultCode if r == '000000': @@ -98,9 +104,9 @@ def payout(db, route, amount, ignore_high_fee=False): e_id = record_exchange(db, route, -credit_amount, fee, vat, participant, 'pre').id payout = BankWirePayOut() payout.AuthorId = participant.mangopay_user_id - payout.DebitedFunds = amount.int() + payout.DebitedFunds = Money_to_cents(amount) payout.DebitedWalletId = participant.get_current_wallet(amount.currency).remote_id - payout.Fees = fee.int() + payout.Fees = Money_to_cents(fee) payout.BankAccountId = route.address payout.BankWireRef = str(e_id) payout.Tag = str(e_id) @@ -139,11 +145,11 @@ def charge(db, route, amount, return_url, billing_address=None): if billing_address: payin.Billing = {'Address': billing_address} payin.CreditedWalletId = wallet.remote_id - payin.DebitedFunds = charge_amount.int() + payin.DebitedFunds = Money_to_cents(charge_amount) payin.CardId = route.address payin.SecureMode = 'FORCE' payin.SecureModeReturnURL = return_url - payin.Fees = fee.int() + payin.Fees = Money_to_cents(fee) payin.Tag = str(e_id) try: test_hook() @@ -207,9 +213,9 @@ def execute_direct_debit(db, exchange, route): payin = DirectDebitDirectPayIn() payin.AuthorId = participant.mangopay_user_id payin.CreditedWalletId = exchange.wallet_id - payin.DebitedFunds = debit_amount.int() + payin.DebitedFunds = Money_to_cents(debit_amount) payin.MandateId = route.mandate - payin.Fees = fee.int() + payin.Fees = Money_to_cents(fee) payin.Tag = str(e_id) try: test_hook() @@ -241,8 +247,8 @@ def payin_bank_wire(db, participant, debit_amount): payin = BankWirePayIn() payin.AuthorId = participant.mangopay_user_id payin.CreditedWalletId = wallet.remote_id - payin.DeclaredDebitedFunds = debit_amount.int() - payin.DeclaredFees = fee.int() + payin.DeclaredDebitedFunds = Money_to_cents(debit_amount) + payin.DeclaredFees = Money_to_cents(fee) payin.Tag = str(e_id) try: test_hook() @@ -505,7 +511,7 @@ def transfer(db, tipper, tippee, amount, context, **kw): tr.AuthorId = tipper_wallet.remote_owner_id tr.CreditedUserId = tippee_wallet.remote_owner_id tr.CreditedWalletId = wallet_to - tr.DebitedFunds = amount.int() + tr.DebitedFunds = Money_to_cents(amount) tr.DebitedWalletId = wallet_from tr.Fees = Money(0, amount.currency) tr.Tag = str(t_id) @@ -611,7 +617,7 @@ def initiate_transfer(db, t_id): tr.AuthorId = tipper_wallet.remote_owner_id tr.CreditedUserId = tippee_wallet.remote_owner_id tr.CreditedWalletId = tippee_wallet.remote_id - tr.DebitedFunds = amount.int() + tr.DebitedFunds = Money_to_cents(amount) tr.DebitedWalletId = tipper_wallet.remote_id tr.Fees = Money(0, amount.currency) tr.Tag = str(t_id) @@ -774,8 +780,8 @@ def refund_payin(db, exchange, amount, participant): m_refund = PayInRefund(payin_id=exchange.remote_id) m_refund.AuthorId = wallet.remote_owner_id m_refund.Tag = str(e_refund.id) - m_refund.DebitedFunds = amount.int() - m_refund.Fees = -fee.int() + m_refund.DebitedFunds = Money_to_cents(amount) + m_refund.Fees = -Money_to_cents(fee) try: m_refund.save() except Exception as e: @@ -926,8 +932,8 @@ def refund_disputed_payin(db, exchange, create_debts=False, refund_fee=False, dr m_refund = PayInRefund(payin_id=exchange.remote_id) m_refund.AuthorId = wallet.remote_owner_id m_refund.Tag = str(e_refund.id) - m_refund.DebitedFunds = amount.int() - m_refund.Fees = -fee.int() + m_refund.DebitedFunds = Money_to_cents(amount) + m_refund.Fees = -Money_to_cents(fee) try: m_refund.save() except Exception as e: @@ -1020,7 +1026,7 @@ def recover_lost_funds(db, exchange, lost_amount, repudiation_id): tr.AuthorId = original_owner.mangopay_user_id tr.CreditedUserId = chargebacks_account.mangopay_user_id tr.CreditedWalletId = to_wallet - tr.DebitedFunds = exchange.amount.int() + tr.DebitedFunds = Money_to_cents(exchange.amount) tr.DebitedWalletId = from_wallet tr.Fees = Money(0, currency) tr.RepudiationId = repudiation_id diff --git a/liberapay/constants.py b/liberapay/constants.py index 3d4a588bbb..aca11451a9 100644 --- a/liberapay/constants.py +++ b/liberapay/constants.py @@ -69,7 +69,6 @@ def with_vat(self): D_CENT = Decimal('0.01') D_INF = Decimal('inf') D_MAX = Decimal('999999999999.99') -D_UNIT = Decimal('1.00') D_ZERO = Decimal('0.00') DONATION_LIMITS_WEEKLY_EUR_USD = (Decimal('0.01'), Decimal('100.00')) @@ -386,6 +385,4 @@ def make_standard_tip(label, weekly, currency): USERNAME_MAX_SIZE = 32 USERNAME_SUFFIX_BLACKLIST = set('.txt .html .htm .json .xml'.split()) -ZERO = {c: Money(D_ZERO, c) for c in ('EUR', 'USD', None)} - del _ diff --git a/liberapay/models/_mixin_team.py b/liberapay/models/_mixin_team.py index c8004f9d9a..8d3e1963c3 100644 --- a/liberapay/models/_mixin_team.py +++ b/liberapay/models/_mixin_team.py @@ -7,7 +7,7 @@ from collections import OrderedDict from statistics import median -from liberapay.constants import ZERO, TAKE_THROTTLING_THRESHOLD +from liberapay.constants import TAKE_THROTTLING_THRESHOLD from liberapay.utils import NS, group_by from liberapay.utils.currencies import Money, MoneyBasket @@ -39,7 +39,7 @@ def add_member(self, member, cursor=None): raise MemberLimitReached if member.status != 'active': raise InactiveParticipantAdded - self.set_take_for(member, ZERO[member.main_currency], self, cursor=cursor) + self.set_take_for(member, Money.ZEROS[member.main_currency], self, cursor=cursor) def remove_all_members(self, cursor=None): (cursor or self.db).run(""" @@ -95,7 +95,7 @@ def compute_max_this_week(self, member_id, last_week, currency): leftover, or last week's median take, or one currency unit (e.g. €1.00). """ nonzero_last_week = [a.convert(currency).amount for a in last_week.values() if a] - member_last_week = last_week.get(member_id, ZERO[currency]).convert(currency) + member_last_week = last_week.get(member_id, Money.ZEROS[currency]).convert(currency) return max( member_last_week * 2, member_last_week + last_week.initial_leftover.fuzzy_sum(currency), @@ -289,7 +289,7 @@ def get_members(self): compute_max = self.throttle_takes and nmembers > 1 and last_week.sum members = OrderedDict() members.leftover = self.leftover - zero = ZERO[self.main_currency] + zero = Money.ZEROS[self.main_currency] for take in takes: member = {} m_id = member['id'] = take['member_id'] diff --git a/liberapay/models/participant.py b/liberapay/models/participant.py index 7ba2ac6e64..6a94273445 100644 --- a/liberapay/models/participant.py +++ b/liberapay/models/participant.py @@ -2,6 +2,7 @@ from base64 import b64decode, b64encode from datetime import timedelta +from decimal import Decimal from email.utils import formataddr from hashlib import pbkdf2_hmac, md5, sha1 from os import urandom @@ -16,7 +17,6 @@ from cached_property import cached_property from html2text import html2text import mangopay -from mangopay.utils import Money from markupsafe import escape as htmlescape from pando import json from pando.utils import utcnow @@ -25,12 +25,12 @@ import requests from liberapay.constants import ( - ASCII_ALLOWED_IN_USERNAME, AVATAR_QUERY, CURRENCIES, D_UNIT, D_ZERO, + ASCII_ALLOWED_IN_USERNAME, AVATAR_QUERY, CURRENCIES, DONATION_LIMITS, EMAIL_VERIFICATION_TIMEOUT, EVENTS, HTML_A, PASSWORD_MAX_SIZE, PASSWORD_MIN_SIZE, PAYMENT_SLUGS, PERIOD_CONVERSION_RATES, PRIVILEGES, PROFILE_VISIBILITY_ATTRS, PUBLIC_NAME_MAX_SIZE, SESSION, SESSION_REFRESH, SESSION_TIMEOUT, - USERNAME_MAX_SIZE, USERNAME_SUFFIX_BLACKLIST, ZERO, + USERNAME_MAX_SIZE, USERNAME_SUFFIX_BLACKLIST, ) from liberapay.exceptions import ( BadAmount, @@ -73,7 +73,7 @@ NS, deserialize, erase_cookie, serialize, set_cookie, urlquote, emails, i18n, markdown, ) -from liberapay.utils.currencies import MoneyBasket +from liberapay.utils.currencies import Money, MoneyBasket from liberapay.utils.emails import check_email_blacklist, normalize_email_address from liberapay.website import website @@ -295,7 +295,7 @@ def get_chargebacks_account(cls, currency): ON CONFLICT (remote_id) DO UPDATE SET remote_owner_id = 'CREDIT' -- dummy update RETURNING * - """, ('CREDIT_' + currency, ZERO[currency], p.id)) + """, ('CREDIT_' + currency, Money.ZEROS[currency], p.id)) return p, wallet def refetch(self): @@ -584,7 +584,7 @@ def distribute_balances_to_donees(self, final_gift=True): ] if len(takes) == 1 and len(tip.takes) == 1 and tip.takes[0]['amount'] == 0: # Team of one with a zero take - tip.takes[0]['amount'].amount = D_UNIT + tip.takes[0]['amount'].amount = Decimal('1') tip.total_takes = MoneyBasket(*[t['amount'] for t in tip.takes]) tips = [t for t in tips if getattr(t, 'total_takes', -1) != 0] transfers = [] @@ -598,7 +598,7 @@ def distribute_balances_to_donees(self, final_gift=True): key=lambda t: (t.amount, t.ctime), reverse=True ) total = Money.sum((t.amount for t in tips_in_this_currency), currency) - distributed = ZERO[currency] + distributed = Money.ZEROS[currency] initial_balance = wallet.balance transfers_in_this_currency = [] @@ -1268,7 +1268,7 @@ def get_withdrawable_amount(self, currency): AND disputed IS NOT TRUE AND locked_for IS NULL AND (amount).currency = %s - """, (self.id, QUARANTINE, currency)) or ZERO[currency] + """, (self.id, QUARANTINE, currency)) or Money.ZEROS[currency] def can_withdraw(self, amount): return self.get_withdrawable_amount(amount.currency) >= amount @@ -1302,7 +1302,7 @@ def get_balance_in(self, currency): WHERE owner = %s AND balance::currency = %s AND is_current - """, (self.id, currency)) or ZERO[currency] + """, (self.id, currency)) or Money.ZEROS[currency] def get_balances(self): return self.db.one(""" @@ -1840,7 +1840,7 @@ def get_giving_in(self, currency): AND t.amount::currency = %s AND p.status = 'active' AND (p.goal IS NULL OR p.goal >= 0) - """, (self.id, currency)) or ZERO[currency] + """, (self.id, currency)) or Money.ZEROS[currency] def get_receiving_in(self, currency, cursor=None): r = (cursor or self.db).one(""" @@ -1849,14 +1849,14 @@ def get_receiving_in(self, currency, cursor=None): WHERE t.tippee = %s AND t.amount::currency = %s AND t.is_funded - """, (self.id, currency)) or ZERO[currency] + """, (self.id, currency)) or Money.ZEROS[currency] if currency not in CURRENCIES: raise ValueError(currency) r += Money((cursor or self.db).one(""" SELECT sum((t.actual_amount).{0}) FROM current_takes t WHERE t.member = %s - """.format(currency), (self.id,)) or D_ZERO, currency) + """.format(currency), (self.id,)) or Money.ZEROS[currency].amount, currency) return r def get_exact_receiving(self): @@ -1890,7 +1890,7 @@ def update_giving(self, cursor=None): currencies = set(t.amount.currency for t in tips) balances = {w.balance.currency: w.balance for w in self.get_current_wallets(cursor)} for currency in currencies: - fake_balance = balances.get(currency, ZERO[currency]) + fake_balance = balances.get(currency, Money.ZEROS[currency]) fake_balance += self.get_receiving_in(currency, cursor) for tip in (t for t in tips if t.amount.currency == currency): if tip.amount <= (tip.paid_in_advance or 0): @@ -1939,7 +1939,7 @@ def update_receiving(self, cursor=None): with self.db.get_cursor(cursor) as c: if self.kind == 'group': c.run("LOCK TABLE takes IN EXCLUSIVE MODE") - zero = ZERO[self.main_currency] + zero = Money.ZEROS[self.main_currency] r = c.one(""" WITH our_tips AS ( SELECT amount @@ -2042,7 +2042,7 @@ def _zero_tip_dict(tippee, currency=None): tippee = Participant.from_id(tippee) if not currency or currency not in tippee.accepted_currencies: currency = tippee.main_currency - zero = ZERO[currency] + zero = Money.ZEROS[currency] return dict(amount=zero, is_funded=False, tippee=tippee.id, period='weekly', periodic_amount=zero) @@ -2107,7 +2107,7 @@ def get_tip_distribution(self): tip_amounts = [] npatrons = 0 currency = self.main_currency - contributed = ZERO[currency] + contributed = Money.ZEROS[currency] for rec in recs: tip_amounts.append([ rec.amount, diff --git a/liberapay/payin/stripe.py b/liberapay/payin/stripe.py index 08f1120970..7454cfa6d3 100644 --- a/liberapay/payin/stripe.py +++ b/liberapay/payin/stripe.py @@ -1,5 +1,7 @@ from __future__ import absolute_import, division, print_function, unicode_literals +from decimal import Decimal + import stripe import stripe.error @@ -8,6 +10,25 @@ from .common import update_payin, update_payin_transfer +# https://stripe.com/docs/currencies#presentment-currencies +ZERO_DECIMAL_CURRENCIES = """ + BIF CLP DJF GNF JPY KMF KRW MGA PYG RWF UGX VND VUV XAF XOF XPF +""".split() + + +def int_to_Money(amount, currency): + currency = currency.upper() + if currency in ZERO_DECIMAL_CURRENCIES: + return Money(Decimal(amount), currency) + return Money(Decimal(amount) / 100, currency) + + +def Money_to_int(m): + if m.currency in ZERO_DECIMAL_CURRENCIES: + return int(m.amount) + return int(m.amount * 100) + + def repr_stripe_error(e): """Given a `StripeError` exception, return an error message suitable for display. """ @@ -45,7 +66,7 @@ def destination_charge(db, payin, payer, statement_descriptor): destination = {'account': destination} try: charge = stripe.Charge.create( - amount=amount.int().amount, + amount=Money_to_int(amount), currency=amount.currency.lower(), customer=route.remote_user_id, destination=destination, @@ -63,8 +84,8 @@ def destination_charge(db, payin, payer, statement_descriptor): return update_payin(db, payin.id, '', 'failed', str(e)) bt = charge.balance_transaction - amount_settled = Money(bt.amount, bt.currency.upper()) / 100 - fee = Money(bt.fee, bt.currency.upper()) / 100 + amount_settled = int_to_Money(bt.amount, bt.currency) + fee = int_to_Money(bt.fee, bt.currency) net_amount = amount_settled - fee if destination: diff --git a/liberapay/testing/__init__.py b/liberapay/testing/__init__.py index ba6449ce02..35176aa0b8 100644 --- a/liberapay/testing/__init__.py +++ b/liberapay/testing/__init__.py @@ -8,7 +8,6 @@ from os.path import dirname, join, realpath from aspen import resources -from mangopay.utils import Money from pando.utils import utcnow from pando.testing.client import Client from psycopg2 import IntegrityError, InternalError @@ -17,7 +16,7 @@ from liberapay.billing.transactions import ( record_exchange, record_exchange_result, prepare_transfer, _record_transfer_result ) -from liberapay.constants import SESSION, ZERO +from liberapay.constants import SESSION from liberapay.elsewhere._base import UserInfo from liberapay.main import website from liberapay.models.account_elsewhere import AccountElsewhere @@ -30,6 +29,7 @@ ) from liberapay.security.csrf import CSRF_TOKEN from liberapay.testing.vcr import use_cassette +from liberapay.utils.currencies import Money TOP = realpath(join(dirname(dirname(__file__)), '..')) @@ -41,6 +41,10 @@ def EUR(amount): return Money(amount, 'EUR') +def JPY(amount): + return Money(amount, 'JPY') + + def USD(amount): return Money(amount, 'USD') @@ -217,7 +221,7 @@ def make_participant(self, username, **kw): if is_person and participant.mangopay_user_id: wallet_id = kw2.get('mangopay_wallet_id', -participant.id) - zero = ZERO[participant.main_currency] + zero = Money.ZEROS[participant.main_currency] self.db.run(""" INSERT INTO wallets (remote_id, balance, owner, remote_owner_id) diff --git a/liberapay/testing/mangopay.py b/liberapay/testing/mangopay.py index 14c823fbfa..e0f4e70a7f 100644 --- a/liberapay/testing/mangopay.py +++ b/liberapay/testing/mangopay.py @@ -8,10 +8,10 @@ import mock import requests -from liberapay.constants import ZERO from liberapay.models.exchange_route import ExchangeRoute from liberapay.testing import Harness from liberapay.testing.vcr import use_cassette +from liberapay.utils.currencies import Money class MangopayHarness(Harness): @@ -46,7 +46,7 @@ def fake_transfer(tr): def fake_wallet(w): - w.Balance = ZERO[w.Currency] + w.Balance = Money.ZEROS[w.Currency] w.Id = -next(FakeTransfersHarness.wallet_id_serial) diff --git a/liberapay/utils/currencies.py b/liberapay/utils/currencies.py index 3b2090e1a0..8e97ec4af3 100644 --- a/liberapay/utils/currencies.py +++ b/liberapay/utils/currencies.py @@ -10,11 +10,11 @@ import requests import xmltodict -from liberapay.constants import CURRENCIES, D_CENT, D_ZERO, ZERO +from liberapay.constants import CURRENCIES, D_CENT, D_ZERO from liberapay.website import website -def _convert(self, c): +def _convert(self, c, rounding=ROUND_HALF_UP): if self.currency == c: return self if 'EUR' in (self.currency, c): @@ -25,17 +25,17 @@ def _convert(self, c): website.currency_exchange_rates[('EUR', c)] ) amount = self.amount * rate - return Money(amount.quantize(D_CENT), c) + return Money(amount, c, rounding=rounding) def _sum(cls, amounts, currency): - a = ZERO[currency].amount + a = Money.ZEROS[currency].amount for m in amounts: if m.currency != currency: raise CurrencyMismatch(m.currency, currency, 'sum') a += m.amount return cls(a, currency) -def _Money_init(self, amount=D_ZERO, currency=None): +def _Money_init(self, amount=Decimal('0'), currency=None, rounding=None): if not isinstance(amount, Decimal): amount = Decimal(str(amount)) # Why `str(amount)`? Because: @@ -43,6 +43,9 @@ def _Money_init(self, amount=D_ZERO, currency=None): # Decimal('0.2300000000000000099920072216264088638126850128173828125') # >>> Decimal(str(0.23)) # Decimal('0.23') + if rounding is not None: + minimum = Money.MINIMUMS[currency].amount + amount = amount.quantize(minimum, rounding=rounding) self.amount = amount self.currency = currency @@ -56,16 +59,22 @@ def _Money_eq(self, other): return False def _Money_round(self, rounding=ROUND_HALF_UP): - minimum = Money.minimums[self.currency] - return Money(self.amount.quantize(minimum, rounding=rounding), self.currency) + return Money(self.amount, self.currency, rounding=rounding) class _Minimums(defaultdict): def __missing__(self, currency): exponent = website.db.one("SELECT get_currency_exponent(%s)", (currency,)) - minimum = D_CENT if exponent == 2 else Decimal(10) ** (-exponent) + minimum = Money((D_CENT if exponent == 2 else Decimal(10) ** (-exponent)), currency) self[currency] = minimum return minimum +class _Zeros(defaultdict): + def __missing__(self, currency): + minimum = Money.MINIMUMS[currency].amount + zero = Money((D_ZERO if minimum is D_CENT else minimum - minimum), currency) + self[currency] = zero + return zero + Money.__init__ = _Money_init Money.__nonzero__ = Money.__bool__ @@ -75,21 +84,22 @@ def __missing__(self, currency): Money.__str__ = lambda m: '%(amount)s %(currency)s' % m.__dict__ Money.__unicode__ = Money.__str__ Money.convert = _convert -Money.int = lambda m: Money(int(m.amount * 100), m.currency) -Money.minimum = lambda m: Money.minimums[m.currency] -Money.minimums = _Minimums() +Money.minimum = lambda m: Money.MINIMUMS[m.currency] +Money.MINIMUMS = _Minimums() Money.round = _Money_round Money.round_down = lambda m: m.round(ROUND_DOWN) Money.round_up = lambda m: m.round(ROUND_UP) Money.sum = classmethod(_sum) -Money.zero = lambda m: Money(D_ZERO, m.currency) +Money.zero = lambda m: Money.ZEROS[m.currency] +Money.ZEROS = _Zeros() class MoneyBasket(object): def __init__(self, *args, **decimals): self.amounts = OrderedDict( - (currency, decimals.get(currency, D_ZERO)) for currency in CURRENCIES + (currency, decimals.get(currency, Money.ZEROS[currency].amount)) + for currency in CURRENCIES ) for arg in args: if isinstance(arg, Money): @@ -207,7 +217,7 @@ def currencies_present(self): return [k for k, v in self.amounts.items() if v > 0] def fuzzy_sum(self, currency, rounding=ROUND_UP): - a = ZERO[currency].amount + a = Money.ZEROS[currency].amount fuzzy = False for m in self: if m.currency == currency: @@ -215,7 +225,7 @@ def fuzzy_sum(self, currency, rounding=ROUND_UP): elif m.amount: a += m.amount * website.currency_exchange_rates[(m.currency, currency)] fuzzy = True - r = Money(a.quantize(D_CENT, rounding=rounding), currency) + r = Money(a, currency, rounding=rounding) r.fuzzy = fuzzy return r diff --git a/tests/py/test_currencies.py b/tests/py/test_currencies.py index 13888d1ee6..43a0503960 100644 --- a/tests/py/test_currencies.py +++ b/tests/py/test_currencies.py @@ -6,7 +6,8 @@ from liberapay.billing.transactions import swap_currencies, Transfer from liberapay.exceptions import NegativeBalance, TransferError -from liberapay.testing import EUR, USD, Harness, Foobar +from liberapay.payin.stripe import int_to_Money, Money_to_int +from liberapay.testing import EUR, JPY, USD, Harness, Foobar from liberapay.testing.mangopay import FakeTransfersHarness, MangopayHarness, fake_transfer from liberapay.utils.currencies import Money, MoneyBasket @@ -37,10 +38,10 @@ def test_convert_non_euro(self): assert expected == actual def test_minimums(self): - assert Money.minimums['EUR'] == D('0.01') - assert Money.minimums['USD'] == D('0.01') - assert Money.minimums['KRW'] == D('1') - assert Money.minimums['JPY'] == D('1') + assert Money.MINIMUMS['EUR'].amount == D('0.01') + assert Money.MINIMUMS['USD'].amount == D('0.01') + assert Money.MINIMUMS['KRW'].amount == D('1') + assert Money.MINIMUMS['JPY'].amount == D('1') def test_rounding(self): assert Money('0.001', 'EUR').round() == Money('0.00', 'EUR') @@ -150,3 +151,22 @@ def fail_on_second(tr): 'homer': MoneyBasket(EUR('5.00'), USD('1.00')), 'david': MoneyBasket(), } + + +class TestCurrenciesWithStripe(Harness): + + def test_Money_to_int(self): + expected = 101 + actual = Money_to_int(EUR('1.01')) + assert expected == actual + expected = 1 + actual = Money_to_int(JPY('1')) + assert expected == actual + + def test_int_to_Money(self): + expected = USD('1.02') + actual = int_to_Money(102, 'USD') + assert expected == actual + expected = JPY('1') + actual = int_to_Money(1, 'JPY') + assert expected == actual diff --git a/www/%username/charts.json.spt b/www/%username/charts.json.spt index 0753eb0b43..71e1f729b9 100644 --- a/www/%username/charts.json.spt +++ b/www/%username/charts.json.spt @@ -11,6 +11,7 @@ this to mean, "no chart." """ from liberapay.utils import get_participant +from liberapay.utils.currencies import Money [---] @@ -58,7 +59,7 @@ if not transfers: raise response.json([]) currency = participant.main_currency -zero = constants.ZERO[currency] +zero = Money.ZEROS[currency] paydays_i = iter(paydays) curpayday = next(paydays_i) curpayday['receipts'] = zero diff --git a/www/%username/giving/index.html.spt b/www/%username/giving/index.html.spt index 0fdc5273f5..ad51b1172c 100644 --- a/www/%username/giving/index.html.spt +++ b/www/%username/giving/index.html.spt @@ -3,7 +3,6 @@ from decimal import Decimal from pando.utils import utcnow -from liberapay.constants import ZERO from liberapay.models.participant import Participant from liberapay.utils import get_participant, group_by diff --git a/www/%username/giving/pay/stripe/%payin_id.spt b/www/%username/giving/pay/stripe/%payin_id.spt index 10eb2ee69a..0407702994 100644 --- a/www/%username/giving/pay/stripe/%payin_id.spt +++ b/www/%username/giving/pay/stripe/%payin_id.spt @@ -9,7 +9,7 @@ import stripe.error from liberapay.models.exchange_route import ExchangeRoute from liberapay.models.participant import Participant from liberapay.payin.common import prepare_payin, prepare_payin_transfer, resolve_destination -from liberapay.payin.stripe import destination_charge, repr_stripe_error +from liberapay.payin.stripe import destination_charge, Money_to_int, repr_stripe_error from liberapay.utils import get_participant, NS, partition ONE_YEAR = { @@ -87,7 +87,7 @@ if request.method == 'POST': raise response.error(400, "unknown token type") source = stripe.Source.create( - amount=amount.int().amount if one_off else None, + amount=Money_to_int(amount) if one_off else None, owner={ 'address': owner_address, 'email': payer.email or payer.get_any_email(), diff --git a/www/about/stats.spt b/www/about/stats.spt index 8fd6494835..644b0d837c 100644 --- a/www/about/stats.spt +++ b/www/about/stats.spt @@ -1,7 +1,7 @@ # coding: utf8 from __future__ import division, print_function, unicode_literals -from liberapay.utils.currencies import MoneyBasket +from liberapay.utils.currencies import Money, MoneyBasket db = website.db db_qc5 = website.db_qc5 @@ -9,7 +9,7 @@ db_qc5 = website.db_qc5 [--------------------------------------------------------] title = _("Stats") -zero = constants.ZERO[currency] +zero = Money.ZEROS[currency] escrow = db_qc5.one("SELECT basket_sum(balance) FROM wallets") or MoneyBasket() nusers = db_qc5.one(""" SELECT count(*)