From 371a70d7ff9912a6bfcd54bcbdac96ae8eae8d26 Mon Sep 17 00:00:00 2001 From: Changaco Date: Sun, 5 Nov 2017 23:45:15 +0100 Subject: [PATCH 1/8] add currency to payin page URLs --- www/%username/tip.spt | 5 ++++- www/%username/wallet/payin/%back_to.spt | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/www/%username/tip.spt b/www/%username/tip.spt index afc1214074..1da435b61e 100644 --- a/www/%username/tip.spt +++ b/www/%username/tip.spt @@ -71,7 +71,10 @@ if request.method == 'POST': back_to += '&' if '?' in back_to else '?' back_to += 'success=' + b64encode_s(out["msg"]) if out['amount'] and not out['is_funded'] and not out['is_pledge']: - response.redirect('/' + tipper.username + '/wallet/payin/' + b64encode_s(back_to)) + response.redirect( + '/' + tipper.username + '/wallet/payin/' + b64encode_s(back_to) + + '?currency=' + currency + ) response.redirect(back_to, trusted_url=False) else: out = tipper.get_tip_to(tippee) diff --git a/www/%username/wallet/payin/%back_to.spt b/www/%username/wallet/payin/%back_to.spt index 0eee156126..7f396eab99 100644 --- a/www/%username/wallet/payin/%back_to.spt +++ b/www/%username/wallet/payin/%back_to.spt @@ -79,7 +79,7 @@ title = _("Adding Money") {{ _("Easy and instantaneous") }}
{{ _("Fees: {0}% + {1}", *constants.FEE_PAYIN_CARD[currency].with_vat) }}

- + @@ -93,7 +93,7 @@ title = _("Adding Money") _("Cheaper¹ but cumbersome") }}
{{ _("Fee: {0}%", constants.FEE_PAYIN_BANK_WIRE.with_vat) }}

- + @@ -106,7 +106,7 @@ title = _("Adding Money") {{ _("Best for regular payments") }}
{{ _("Fee: {0}", constants.FEE_PAYIN_DIRECT_DEBIT[currency].with_vat) }}

- + % endif From 588a454839ead31c10d7ab0d7972d3374251f408 Mon Sep 17 00:00:00 2001 From: Changaco Date: Mon, 6 Nov 2017 14:34:06 +0100 Subject: [PATCH 2/8] put currency alongside amounts in payio forms --- www/%username/wallet/payin/bankwire/%back_to.spt | 8 ++++++-- www/%username/wallet/payin/card/%back_to.spt | 5 ++++- www/%username/wallet/payin/direct-debit/%exchange_id.spt | 8 ++++++-- www/%username/wallet/payout/%back_to.spt | 6 +++++- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/www/%username/wallet/payin/bankwire/%back_to.spt b/www/%username/wallet/payin/bankwire/%back_to.spt index 23c415e7b4..6605e9223f 100644 --- a/www/%username/wallet/payin/bankwire/%back_to.spt +++ b/www/%username/wallet/payin/bankwire/%back_to.spt @@ -49,10 +49,13 @@ if request.method == 'POST' and request.body.get('action') == 'email': exchange, payin = None, None -funded = float('inf') currency = request.qs.get('currency', currency) +if request.method == 'POST': + currency = request.body.get('currency', currency) if currency not in PAYIN_BANK_WIRE_MIN: - raise response.error(400, "`currency` value in querystring is invalid or non-supported") + raise response.error(400, "`currency` value '%s' is invalid or non-supported" % currency) + +funded = float('inf') balance = participant.get_balance_in(currency) donations = participant.get_giving_in(currency) weekly = donations - participant.get_receiving_in(currency) @@ -212,6 +215,7 @@ title = _("Adding Money")
+

{{ _("Please select a precomputed amount or input a custom one.") }}

    % for weeks in weeks_list diff --git a/www/%username/wallet/payin/card/%back_to.spt b/www/%username/wallet/payin/card/%back_to.spt index 6afa813730..faa7fb8f87 100644 --- a/www/%username/wallet/payin/card/%back_to.spt +++ b/www/%username/wallet/payin/card/%back_to.spt @@ -32,8 +32,10 @@ if request.method == 'GET' and 'transactionId' in request.qs: response.redirect(request.path.raw+'?exchange_id=%s' % payin.Tag) currency = request.qs.get('currency', currency) +if request.method == 'POST': + currency = request.body.get('currency', currency) if currency not in PAYIN_CARD_MIN: - raise response.error(400, "`currency` value in querystring is invalid or non-supported") + raise response.error(400, "`currency` value '%s' is invalid or non-supported" % currency) exchange = None routes = ExchangeRoute.from_network(participant, 'mango-cc', currency=currency) @@ -173,6 +175,7 @@ title = _("Adding Money")
    +

    {{ _("Please select a precomputed amount or input a custom one.") }}

      % for weeks in weeks_list diff --git a/www/%username/wallet/payin/direct-debit/%exchange_id.spt b/www/%username/wallet/payin/direct-debit/%exchange_id.spt index e21159b6da..c04ecc4c1a 100644 --- a/www/%username/wallet/payin/direct-debit/%exchange_id.spt +++ b/www/%username/wallet/payin/direct-debit/%exchange_id.spt @@ -54,10 +54,13 @@ else: if route is not None: route.__dict__['participant'] = participant -funded = float('inf') currency = request.qs.get('currency', currency) +if request.method == 'POST': + currency = request.body.get('currency', currency) if currency not in PAYIN_DIRECT_DEBIT_MIN: - raise response.error(400, "`currency` value in querystring is invalid or non-supported") + raise response.error(400, "`currency` value '%s' is invalid or non-supported" % currency) + +funded = float('inf') balance = participant.get_balance_in(currency) donations = participant.get_giving_in(currency) weekly = donations - participant.get_receiving_in(currency) @@ -222,6 +225,7 @@ title = _("Adding Money")
      +

      {{ _("Please select a precomputed amount or input a custom one.") }}

        % for weeks in weeks_list diff --git a/www/%username/wallet/payout/%back_to.spt b/www/%username/wallet/payout/%back_to.spt index c56de405f8..813ccdcbeb 100644 --- a/www/%username/wallet/payout/%back_to.spt +++ b/www/%username/wallet/payout/%back_to.spt @@ -24,9 +24,12 @@ exchange = None bank_account = None back_to = b64decode_s(request.path['back_to'], default=None) + currency = request.qs.get('currency', currency) +if request.method == 'POST': + currency = request.body.get('currency', currency) if currency not in constants.CURRENCIES: - raise response.error(400, "`currency` value in querystring is invalid or non-supported") + raise response.error(400, "`currency` value '%s' is invalid or non-supported" % currency) if request.method == 'POST': body = request.body @@ -148,6 +151,7 @@ title = _("Withdrawing Money")
        +
        From 5a201c924f41477f6b0c791011fa54b485e29e45 Mon Sep 17 00:00:00 2001 From: Changaco Date: Thu, 9 Nov 2017 14:49:05 +0100 Subject: [PATCH 3/8] fix `Participant.update_giving()` --- liberapay/models/participant.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/liberapay/models/participant.py b/liberapay/models/participant.py index d1930df6fa..6f310529de 100644 --- a/liberapay/models/participant.py +++ b/liberapay/models/participant.py @@ -1065,8 +1065,8 @@ def get_current_wallet(self, currency=None, create=False): from liberapay.billing.transactions import create_wallet return create_wallet(self.db, self, currency) - def get_current_wallets(self): - return self.db.all(""" + def get_current_wallets(self, cursor=None): + return (cursor or self.db).all(""" SELECT * FROM wallets WHERE owner = %s @@ -1552,7 +1552,7 @@ def update_giving(self, cursor=None): ORDER BY p2.join_time IS NULL, t.ctime ASC """, (self.id,)) updated = [] - for wallet in self.get_current_wallets(): + for wallet in self.get_current_wallets(cursor): fake_balance = wallet.balance currency = fake_balance.currency fake_balance += self.get_receiving_in(currency, cursor) From 9723fef31e2bd38b48a8c43717ab60e953281872 Mon Sep 17 00:00:00 2001 From: Changaco Date: Thu, 9 Nov 2017 17:21:49 +0100 Subject: [PATCH 4/8] fix currency mixup in `propagate_exchange()` --- liberapay/billing/transactions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/liberapay/billing/transactions.py b/liberapay/billing/transactions.py index 616c4c620b..4e538cd23c 100644 --- a/liberapay/billing/transactions.py +++ b/liberapay/billing/transactions.py @@ -382,8 +382,9 @@ def propagate_exchange(cursor, participant, exchange, error, amount): AND b.ts < now() - INTERVAL %s AND b.disputed IS NOT TRUE AND b.locked_for IS NULL + AND b.amount::currency = %s ORDER BY b.owner = e.participant DESC, b.ts - """, (participant.id, QUARANTINE)) + """, (participant.id, QUARANTINE, amount.currency)) withdrawable = sum(b.amount for b in bundles) x = -amount if x > withdrawable: From fa25781b92573067994cf2a9095c9f1822aa7edb Mon Sep 17 00:00:00 2001 From: Changaco Date: Thu, 9 Nov 2017 17:23:00 +0100 Subject: [PATCH 5/8] fix currency mixup in DB check --- liberapay/models/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/liberapay/models/__init__.py b/liberapay/models/__init__.py index daeb2e7370..53c1576c13 100644 --- a/liberapay/models/__init__.py +++ b/liberapay/models/__init__.py @@ -183,6 +183,7 @@ def _check_bundles_grouped_by_withdrawal_against_exchanges(cursor): LEFT JOIN ( SELECT b.withdrawal, sum(b.amount) as withdrawn FROM cash_bundles b + WHERE b.withdrawal IS NOT NULL GROUP BY b.withdrawal ) AS b ON b.withdrawal = e.id ) From 79e6d25307d3ab9939330876f64413247fd4f99e Mon Sep 17 00:00:00 2001 From: Changaco Date: Tue, 7 Nov 2017 10:50:41 +0100 Subject: [PATCH 6/8] handle multiple currencies in payout page --- liberapay/models/participant.py | 9 ++++ liberapay/utils/currencies.py | 4 ++ www/%username/wallet/payout/%back_to.spt | 64 ++++++++++++++++++------ 3 files changed, 63 insertions(+), 14 deletions(-) diff --git a/liberapay/models/participant.py b/liberapay/models/participant.py index 6f310529de..6efa65ed87 100644 --- a/liberapay/models/participant.py +++ b/liberapay/models/participant.py @@ -59,6 +59,7 @@ NS, deserialize, erase_cookie, serialize, set_cookie, emails, i18n, markdown, ) +from liberapay.utils.currencies import MoneyBasket from liberapay.website import website @@ -1082,6 +1083,14 @@ def get_balance_in(self, currency): AND is_current """, (self.id, currency)) or ZERO[currency] + def get_balances(self): + return self.db.one(""" + SELECT basket_sum(balance) + FROM wallets + WHERE owner = %s + AND is_current + """, (self.id,)) or MoneyBasket() + # Events # ====== diff --git a/liberapay/utils/currencies.py b/liberapay/utils/currencies.py index 1d5c062155..66da2c1a34 100644 --- a/liberapay/utils/currencies.py +++ b/liberapay/utils/currencies.py @@ -90,6 +90,10 @@ def __bool__(self): __nonzero__ = __bool__ + @property + def currencies_present(self): + return [m.currency for m in self if m.amount] + @classmethod def sum(cls, amounts): r = cls(Money('0.00', 'EUR'), usd=Money('0.00', 'USD')) diff --git a/www/%username/wallet/payout/%back_to.spt b/www/%username/wallet/payout/%back_to.spt index 813ccdcbeb..f66da42708 100644 --- a/www/%username/wallet/payout/%back_to.spt +++ b/www/%username/wallet/payout/%back_to.spt @@ -25,22 +25,20 @@ bank_account = None back_to = b64decode_s(request.path['back_to'], default=None) -currency = request.qs.get('currency', currency) +currency = request.qs.get('currency') if request.method == 'POST': - currency = request.body.get('currency', currency) -if currency not in constants.CURRENCIES: + currency = request.body.get('currency', currency or state['currency']) +if currency is not None and currency not in constants.CURRENCIES: raise response.error(400, "`currency` value '%s' is invalid or non-supported" % currency) if request.method == 'POST': - body = request.body - amount = Money(parse_decimal(request.body['amount']), currency) if amount <= 0: raise response.error(400, _("The amount cannot be zero.")) routes = ExchangeRoute.from_network(participant, 'mango-ba') if not routes: raise response.error(400, "no route") - ignore_high_fee = body.get('confirmed') == 'true' + ignore_high_fee = request.body.get('confirmed') == 'true' try: exchange = payout(website.db, routes[0], amount, ignore_high_fee) except TransactionFeeTooHigh as e: @@ -57,13 +55,25 @@ if request.method == 'POST': elif 'exchange_id' in request.qs: exchange = website.db.one("SELECT * FROM exchanges WHERE id = %s AND participant = %s", (request.qs['exchange_id'], participant.id)) + currency = exchange.amount.currency + +if not currency: + balances = participant.get_balances() + currencies = balances.currencies_present + if len(currencies) == 1: + currency = currencies[0] + elif len(currencies) == 0: + currency = state['currency'] + else: + show_form = False -balance = participant.get_balance_in(currency) -success = getattr(exchange, 'status', None) in ('created', 'succeeded') -show_form = balance > 0 and not success -if show_form or not success: - withdrawable = participant.get_withdrawable_amount(currency) - show_form = withdrawable > 0 +if currency: + balance = participant.get_balance_in(currency) + success = getattr(exchange, 'status', None) in ('created', 'succeeded') + show_form = balance > 0 and not success + if show_form or not success: + withdrawable = participant.get_withdrawable_amount(currency) + show_form = withdrawable > 0 if show_form: mp_account = participant.get_mangopay_account() @@ -103,7 +113,9 @@ title = _("Withdrawing Money")
        + % if show_form + % endif % if exchange
        {{ @@ -113,6 +125,8 @@ title = _("Withdrawing Money") _("The attempt to send {0} to your bank account has failed. Error message: {1}", -exchange.amount, exchange.note) }}
        % endif + + % if currency

        {{ _("You have {0} in your Liberapay wallet.", balance) }} % if not success @@ -132,8 +146,15 @@ title = _("Withdrawing Money")

        {{ _("Go back") }}

        % endif - - % if show_form + % else +

        {{ _("You have {0} in your Liberapay wallet.", balances) }}

        +

        {{ _("Which currency do you want to withdraw?") }}

        +

        + % for currency in currencies + {{ locale.currencies.get(currency, currency) }} ({{ locale.currency_symbols.get(currency, currency) }}) + % endfor +

        {{ _( "Withdrawing euros to a SEPA bank account is free, transfers to other " "countries cost {0} each. Withdrawing US dollars costs {1} per transfer " @@ -141,6 +162,21 @@ title = _("Withdrawing Money") constants.FEE_PAYOUT['EUR']['foreign'].with_vat, constants.FEE_PAYOUT['USD']['*'].with_vat, ) }}

        + % endif + + % if show_form + % if currency == 'EUR' +

        {{ _( + "Withdrawing euros to a SEPA bank account is free, transfers to other " + "countries cost {0} each.", + constants.FEE_PAYOUT['EUR']['foreign'].with_vat, + ) }}

        + % elif currency == 'USD' +

        {{ _( + "Withdrawing US dollars costs {0} per transfer, whatever the destination country is.", + constants.FEE_PAYOUT['USD']['*'].with_vat, + ) }}

        + % endif

        {{ _("Amount") }}

        From 334ffcf896e06fb88d60a870735488e461f7f33d Mon Sep 17 00:00:00 2001 From: Changaco Date: Thu, 9 Nov 2017 17:24:36 +0100 Subject: [PATCH 7/8] tweak wallet history tables --- liberapay/utils/i18n.py | 2 +- www/%username/wallet/index.html.spt | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/liberapay/utils/i18n.py b/liberapay/utils/i18n.py index 5f8b45545c..c37dec2d7f 100644 --- a/liberapay/utils/i18n.py +++ b/liberapay/utils/i18n.py @@ -76,7 +76,7 @@ def format_decimal(self, *a): return format_decimal(*a, locale=self) def format_money_basket(self, basket): - return ', '.join( + return ' + '.join( format_currency(money.amount, money.currency, locale=self) for money in basket if money ) or '0' diff --git a/www/%username/wallet/index.html.spt b/www/%username/wallet/index.html.spt index 862180d078..7cd6e09f5b 100644 --- a/www/%username/wallet/index.html.spt +++ b/www/%username/wallet/index.html.spt @@ -186,11 +186,15 @@ else: locale.format_money_basket(event['balances']) }} - % for delta in event['wallet_deltas'] - % if delta.amount or delta.currency == participant.main_currency + % set wallet_deltas = event['wallet_deltas'] + % if wallet_deltas + % set n = len(wallet_deltas.currencies_present) + % for delta in wallet_deltas + % if delta.amount or n == 0 and delta.currency == participant.main_currency {{ format_money_delta(delta) }}
        % endif % endfor + % endif From c151285aa1a5341562ccfeea75965f5bca14b05f Mon Sep 17 00:00:00 2001 From: Changaco Date: Thu, 9 Nov 2017 17:36:44 +0100 Subject: [PATCH 8/8] handle multiple currencies in Close Account page --- www/%username/settings/close.spt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/www/%username/settings/close.spt b/www/%username/settings/close.spt index 306ef4796e..05f7d8181a 100644 --- a/www/%username/settings/close.spt +++ b/www/%username/settings/close.spt @@ -25,6 +25,8 @@ pending_payouts = website.db.one(""" AND status = 'created' """, (participant.id,)) +balances = participant.get_balances() + title = _("Close Account") subhead = participant.username @@ -60,22 +62,17 @@ subhead = participant.username

        {{ _("Wallet") }}

        {{ _("You have {0} in your wallet. What should we do with it?", - participant.balance) }}

        - + balances) }}

        -

        {{ _( "If neither option works for you, please contact support@liberapay.com." ) }}