From f50613492df1c1ac8ae0b761675cfddaa08cc777 Mon Sep 17 00:00:00 2001 From: Changaco Date: Sun, 15 Oct 2017 13:14:40 +0200 Subject: [PATCH] adapt payin constants to the currency --- liberapay/billing/fees.py | 3 +- liberapay/constants.py | 29 +++++++++++++++---- tests/py/test_payio.py | 17 ++++++----- .../wallet/payin/bankwire/%back_to.spt | 18 +++++++----- www/%username/wallet/payin/card/%back_to.spt | 21 ++++++++------ .../payin/direct-debit/%exchange_id.spt | 20 +++++++------ www/about/faq.spt | 2 +- 7 files changed, 68 insertions(+), 42 deletions(-) diff --git a/liberapay/billing/fees.py b/liberapay/billing/fees.py index e3faf1e61d..cbd3934936 100644 --- a/liberapay/billing/fees.py +++ b/liberapay/billing/fees.py @@ -13,13 +13,14 @@ ) -def upcharge(amount, fees, min_amount): +def upcharge(amount, fees, min_amounts): """Given an amount, return a higher amount and the difference. """ assert isinstance(amount, Money), type(amount) fees = fees if isinstance(fees, Fees) else fees[amount.currency] + min_amount = min_amounts[amount.currency] if amount < min_amount: amount = min_amount diff --git a/liberapay/constants.py b/liberapay/constants.py index 9d5caf297e..abe6b59831 100644 --- a/liberapay/constants.py +++ b/liberapay/constants.py @@ -196,12 +196,29 @@ def with_vat(self): PASSWORD_MIN_SIZE = 8 PASSWORD_MAX_SIZE = 150 -PAYIN_BANK_WIRE_MIN = Decimal('2.00') # fee ≈ 0.99% -PAYIN_BANK_WIRE_TARGET = Decimal('5.00') # fee ≈ 0.6% -PAYIN_CARD_MIN = Decimal("15.00") # fee ≈ 3.5% -PAYIN_CARD_TARGET = Decimal("92.00") # fee ≈ 2.33% -PAYIN_DIRECT_DEBIT_MIN = Decimal('25.00') # fee ≈ 3.6% -PAYIN_DIRECT_DEBIT_TARGET = Decimal('99.00') # fee ≈ 0.94% +PAYIN_BANK_WIRE_MIN = {k: Money('2.00', k) for k in ('EUR', 'USD')} # fee ≈ 0.99% +PAYIN_BANK_WIRE_TARGET = {k: Money('5.00', k) for k in ('EUR', 'USD')} # fee ≈ 0.6% +PAYIN_BANK_WIRE_MAX = {k: Money('2500.00', k) for k in ('EUR', 'USD')} +PAYIN_CARD_MIN = { + 'EUR': Money('15.00', 'EUR'), # fee ≈ 3.5% + 'USD': Money('20.00', 'USD'), # fee ≈ 4.58% +} +PAYIN_CARD_TARGET = { + 'EUR': Money('92.00', 'EUR'), # fee ≈ 2.33% + 'USD': Money('95.00', 'USD'), # fee ≈ 3.27% +} +PAYIN_CARD_MAX = {k: Money('2500.00', k) for k in ('EUR', 'USD')} +PAYIN_DIRECT_DEBIT_MIN_EUR_GBP = Decimal('25.00') # fee ≈ 3.6% +PAYIN_DIRECT_DEBIT_MIN = { + 'EUR': Money(PAYIN_DIRECT_DEBIT_MIN_EUR_GBP, 'EUR'), + 'GBP': Money(PAYIN_DIRECT_DEBIT_MIN_EUR_GBP, 'GBP'), +} +PAYIN_DIRECT_DEBIT_TARGET_EUR_GBP = Decimal('99.00') # fee ≈ 0.94% +PAYIN_DIRECT_DEBIT_TARGET = { + 'EUR': Money(PAYIN_DIRECT_DEBIT_TARGET_EUR_GBP, 'EUR'), + 'GBP': Money(PAYIN_DIRECT_DEBIT_TARGET_EUR_GBP, 'GBP'), +} +PAYIN_DIRECT_DEBIT_MAX = {k: Money('2500.00', k) for k in ('EUR', 'USD')} PERIOD_CONVERSION_RATES = { 'weekly': Decimal(1), diff --git a/tests/py/test_payio.py b/tests/py/test_payio.py index 92ca37b6de..9426ca2e2b 100644 --- a/tests/py/test_payio.py +++ b/tests/py/test_payio.py @@ -307,29 +307,29 @@ def test_upcharge_basically_works(self): def test_upcharge_full_in_rounded_case(self): actual = upcharge_card(EUR('5.00')) - expected = upcharge_card(EUR(PAYIN_CARD_MIN)) + expected = upcharge_card(PAYIN_CARD_MIN['EUR']) assert actual == expected def test_upcharge_at_min(self): - actual = upcharge_card(EUR(PAYIN_CARD_MIN)) + actual = upcharge_card(PAYIN_CARD_MIN['EUR']) expected = (EUR('15.54'), EUR('0.54'), EUR('0.08')) assert actual == expected assert actual[1] / actual[0] < D('0.035') # less than 3.5% fee def test_upcharge_at_target(self): - actual = upcharge_card(EUR(PAYIN_CARD_TARGET)) + actual = upcharge_card(PAYIN_CARD_TARGET['EUR']) expected = (EUR('94.19'), EUR('2.19'), EUR('0.32')) assert actual == expected assert actual[1] / actual[0] < D('0.024') # less than 2.4% fee def test_upcharge_at_one_cent(self): actual = upcharge_card(EUR('0.01')) - expected = upcharge_card(EUR(PAYIN_CARD_MIN)) + expected = upcharge_card(PAYIN_CARD_MIN['EUR']) assert actual == expected def test_upcharge_at_min_minus_one_cent(self): - actual = upcharge_card(EUR(PAYIN_CARD_MIN) - EUR('0.01')) - expected = upcharge_card(EUR(PAYIN_CARD_MIN)) + actual = upcharge_card(PAYIN_CARD_MIN['EUR'] - EUR('0.01')) + expected = upcharge_card(PAYIN_CARD_MIN['EUR']) assert actual == expected def test_skim_credit(self): @@ -442,16 +442,17 @@ def throw_transactions_back_in_time(self): """) def test_1_sync_with_mangopay_records_exchange_success(self): + amount = PAYIN_CARD_MIN['EUR'].amount with mock.patch('liberapay.billing.transactions.record_exchange_result') as rer: rer.side_effect = Foobar() with self.assertRaises(Foobar): - charge(self.db, self.janet_route, PAYIN_CARD_MIN, 'http://localhost/') + charge(self.db, self.janet_route, amount, 'http://localhost/') exchange = self.db.one("SELECT * FROM exchanges") assert exchange.status == 'pre' sync_with_mangopay(self.db) exchange = self.db.one("SELECT * FROM exchanges") assert exchange.status == 'succeeded' - assert Participant.from_username('janet').balance == PAYIN_CARD_MIN + assert Participant.from_username('janet').balance == amount def test_2_sync_with_mangopay_handles_payins_that_didnt_happen(self): pass # this is for pep8 diff --git a/www/%username/wallet/payin/bankwire/%back_to.spt b/www/%username/wallet/payin/bankwire/%back_to.spt index 4dd5d2a6af..cf275cec1f 100644 --- a/www/%username/wallet/payin/bankwire/%back_to.spt +++ b/www/%username/wallet/payin/bankwire/%back_to.spt @@ -12,9 +12,6 @@ from liberapay.exceptions import InvalidNumber from liberapay.utils import b64decode_s, get_participant from liberapay.utils.i18n import Money -AMOUNT_MIN = upcharge_bank_wire(Money(PAYIN_BANK_WIRE_MIN, 'EUR'))[0] -AMOUNT_MAX = upcharge_bank_wire(Money(KYC_PAYIN_YEARLY_THRESHOLD, 'EUR'))[0] - NOTIF_BIT_FAIL = EVENTS['payin_bankwire_failed'].bit NOTIF_BIT_SUCC = EVENTS['payin_bankwire_succeeded'].bit @@ -53,13 +50,16 @@ if request.method == 'POST' and request.body.get('action') == 'email': exchange, payin = None, None funded = float('inf') +currency = request.qs.get('currency', currency) +if currency not in PAYIN_BANK_WIRE_MIN: + raise response.error(400, "`currency` value in querystring is invalid or non-supported") donations = participant.get_giving_for_profile()[1] weekly = donations - participant.receiving if weekly > 0: funded = participant.balance // weekly - min_weeks = (constants.PAYIN_BANK_WIRE_MIN / weekly).to_integral_value(ROUND_UP) + min_weeks = (constants.PAYIN_BANK_WIRE_MIN[currency].amount / weekly).to_integral_value(ROUND_UP) max_weeks = min( - max(constants.PAYIN_BANK_WIRE_TARGET // weekly, 52), + max(constants.PAYIN_BANK_WIRE_TARGET[currency].amount // weekly, 52), constants.KYC_PAYIN_YEARLY_THRESHOLD // weekly ) weeks_list = sorted(set((min_weeks, 4, 13, 26, 39, max_weeks))) @@ -80,13 +80,15 @@ if request.method == 'POST': except: pass try: - amount = D(amount) + amount = Money(amount, currency) except (InvalidOperation, ValueError): raise InvalidNumber(amount) - if amount < AMOUNT_MIN.amount or amount > AMOUNT_MAX.amount: + amount_min = upcharge_bank_wire(constants.PAYIN_BANK_WIRE_MIN[currency])[0] + amount_max = upcharge_bank_wire(constants.PAYIN_BANK_WIRE_MAX[currency])[0] + if amount < amount_min or amount > amount_max: raise response.error(400, _( "'{0}' is not an acceptable amount (min={1}, max={2})", - amount, AMOUNT_MIN, AMOUNT_MAX + amount, amount_min, amount_max )) payin, exchange = payin_bank_wire(website.db, participant, amount) redir = request.path.raw+'?exchange_id=%s' % exchange.id diff --git a/www/%username/wallet/payin/card/%back_to.spt b/www/%username/wallet/payin/card/%back_to.spt index cf0e107778..5a0bae1c56 100644 --- a/www/%username/wallet/payin/card/%back_to.spt +++ b/www/%username/wallet/payin/card/%back_to.spt @@ -15,9 +15,6 @@ from liberapay.models.exchange_route import ExchangeRoute from liberapay.utils import b64decode_s, get_participant, is_card_expired from liberapay.utils.i18n import Money -AMOUNT_MIN = Money(PAYIN_CARD_MIN, 'EUR') -AMOUNT_MAX = skim_amount(Money(KYC_PAYIN_YEARLY_THRESHOLD, 'EUR'), FEE_PAYIN_CARD['EUR'])[0] - [---] participant = get_participant(state, restrict=True, block_suspended_user=True) @@ -50,14 +47,19 @@ if card: card = None funded = float('inf') +currency = request.qs.get('currency', currency) +if currency not in PAYIN_CARD_MIN: + raise response.error(400, "`currency` value in querystring is invalid or non-supported") donations = participant.get_giving_for_profile()[1] weekly = donations - participant.receiving +amount_min = PAYIN_CARD_MIN[currency] +amount_max = skim_amount(constants.PAYIN_CARD_MAX[currency], FEE_PAYIN_CARD[currency])[0] if weekly > 0: funded = participant.balance // weekly - min_weeks = (constants.PAYIN_CARD_MIN / weekly).to_integral_value(ROUND_UP) + min_weeks = (amount_min.amount / weekly).to_integral_value(ROUND_UP) max_weeks = min( - max(constants.PAYIN_CARD_TARGET // weekly, 52), - AMOUNT_MAX.amount // weekly + max(constants.PAYIN_CARD_TARGET[currency].amount // weekly, 52), + amount_max.amount // weekly ) weeks_list = sorted(set((min_weeks, 4, 13, 26, 39, max_weeks))) weeks_list = [w for w in weeks_list if w >= min_weeks and w <= max_weeks] @@ -82,10 +84,11 @@ if request.method == 'POST': amount = D(amount) except (InvalidOperation, ValueError): raise InvalidNumber(amount) - if amount < AMOUNT_MIN.amount or amount > AMOUNT_MAX.amount: + amount = Money(amount, currency) + if amount < amount_min.amount or amount > amount_max.amount: raise response.error(400, _( "'{0}' is not an acceptable amount (min={1}, max={2})", - amount, AMOUNT_MIN, AMOUNT_MAX + amount, amount_min, amount_max )) return_url = website.canonical_url + request.path.raw try: @@ -177,7 +180,7 @@ title = _("Adding Money") % set months = _months.quantize(D('1'))