Skip to content

Commit

Permalink
Merge pull request #796 from liberapay/currencies-5
Browse files Browse the repository at this point in the history
Currencies - part 5
  • Loading branch information
Changaco authored Nov 10, 2017
2 parents 457d868 + c151285 commit d6791cb
Show file tree
Hide file tree
Showing 13 changed files with 106 additions and 36 deletions.
3 changes: 2 additions & 1 deletion liberapay/billing/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions liberapay/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
15 changes: 12 additions & 3 deletions liberapay/models/participant.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -1065,8 +1066,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
Expand All @@ -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
# ======
Expand Down Expand Up @@ -1552,7 +1561,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)
Expand Down
4 changes: 4 additions & 0 deletions liberapay/utils/currencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
Expand Down
2 changes: 1 addition & 1 deletion liberapay/utils/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
11 changes: 4 additions & 7 deletions www/%username/settings/close.spt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ pending_payouts = website.db.one("""
AND status = 'created'
""", (participant.id,))

balances = participant.get_balances()

title = _("Close Account")
subhead = participant.username

Expand Down Expand Up @@ -60,22 +62,17 @@ subhead = participant.username
<h3>{{ _("Wallet") }}</h3>

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

balances) }}</p>
<ul>

<li><a href="{{ participant.path('wallet/payout/'+b64encode_s(request.line.uri)) }}"
>{{ _("Withdraw it") }}</a></li>

<li><label>
<input type="radio" name="disburse_to" value="downstream"
{{ 'disabled' if not participant.get_giving_for_profile()[1] else '' }} />
{{ 'disabled' if not participant.giving else '' }} />
{{ _("Give it to the {0}people I donate to{1}",
'<a href="%s">'|safe % participant.path('giving'), '</a>'|safe) }}
</label></li>

</ul>

<p>{{ _(
"If neither option works for you, please contact [email protected]."
) }}</p>
Expand Down
5 changes: 4 additions & 1 deletion www/%username/tip.spt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 6 additions & 2 deletions www/%username/wallet/index.html.spt
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,15 @@ else:
locale.format_money_basket(event['balances'])
}}</td>
<td class="wallet">
% 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) }}<br>
% endif
% endfor
% endif
</td>
<td class="fees"></td>
<td class="bank"></td>
Expand Down
6 changes: 3 additions & 3 deletions www/%username/wallet/payin/%back_to.spt
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ title = _("Adding Money")
{{ _("Easy and instantaneous") }}<br>
{{ _("Fees: {0}% + {1}", *constants.FEE_PAYIN_CARD[currency].with_vat) }}
</p>
<a class="overlay" href="{{ base_path }}/card/{{ b64encode_s(back_to) }}"></a>
<a class="overlay" href="{{ base_path }}/card/{{ b64encode_s(back_to) }}?currency={{ currency }}"></a>
</div></div>
</div>

Expand All @@ -93,7 +93,7 @@ title = _("Adding Money")
_("Cheaper¹ but cumbersome") }}<br>
{{ _("Fee: {0}%", constants.FEE_PAYIN_BANK_WIRE.with_vat) }}
</p>
<a class="overlay" href="{{ base_path }}/bankwire/{{ b64encode_s(back_to) }}"></a>
<a class="overlay" href="{{ base_path }}/bankwire/{{ b64encode_s(back_to) }}?currency={{ currency }}"></a>
</div></div>
</div>

Expand All @@ -106,7 +106,7 @@ title = _("Adding Money")
{{ _("Best for regular payments") }}<br>
{{ _("Fee: {0}", constants.FEE_PAYIN_DIRECT_DEBIT[currency].with_vat) }}
</p>
<a class="overlay" href="{{ base_path }}/direct-debit/"></a>
<a class="overlay" href="{{ base_path }}/direct-debit/?currency={{ currency }}"></a>
</div></div>
</div>
% endif
Expand Down
8 changes: 6 additions & 2 deletions www/%username/wallet/payin/bankwire/%back_to.spt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -212,6 +215,7 @@ title = _("Adding Money")

<fieldset id="amount" class="form-inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<input type="hidden" name="currency" value="{{ currency }}" />
<p>{{ _("Please select a precomputed amount or input a custom one.") }}</p>
<ul class="list-group radio-group">
% for weeks in weeks_list
Expand Down
5 changes: 4 additions & 1 deletion www/%username/wallet/payin/card/%back_to.spt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -173,6 +175,7 @@ title = _("Adding Money")
<fieldset id="amount" class="form-inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<input type="hidden" name="route_id" value="{{ route.id if route else '' }}" />
<input type="hidden" name="currency" value="{{ currency }}" />
<p>{{ _("Please select a precomputed amount or input a custom one.") }}</p>
<ul class="list-group radio-group">
% for weeks in weeks_list
Expand Down
8 changes: 6 additions & 2 deletions www/%username/wallet/payin/direct-debit/%exchange_id.spt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -222,6 +225,7 @@ title = _("Adding Money")
<fieldset id="amount" class="form-inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<input type="hidden" name="route_id" value="{{ route.id if route else '' }}" />
<input type="hidden" name="currency" value="{{ currency }}" />
<p>{{ _("Please select a precomputed amount or input a custom one.") }}</p>
<ul class="list-group radio-group">
% for weeks in weeks_list
Expand Down
66 changes: 53 additions & 13 deletions www/%username/wallet/payout/%back_to.spt
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,21 @@ exchange = None
bank_account = None

back_to = b64decode_s(request.path['back_to'], default=None)
currency = request.qs.get('currency', currency)
if currency not in constants.CURRENCIES:
raise response.error(400, "`currency` value in querystring is invalid or non-supported")

currency = request.qs.get('currency')
if request.method == 'POST':
body = request.body
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':
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:
Expand All @@ -54,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()
Expand Down Expand Up @@ -100,7 +113,9 @@ title = _("Withdrawing Money")
<form id="payout" action="javascript:" method="POST"
data-msg-loading="{{ _('Request in progress, please wait…') }}">

% if show_form
<noscript><div class="alert alert-danger">{{ _("JavaScript is required") }}</div></noscript>
% endif

% if exchange
<div class="alert alert-{{ 'success' if success else 'danger' }}">{{
Expand All @@ -110,6 +125,8 @@ title = _("Withdrawing Money")
_("The attempt to send {0} to your bank account has failed. Error message: {1}", -exchange.amount, exchange.note)
}}</div>
% endif

% if currency
<p>
{{ _("You have {0} in your Liberapay wallet.", balance) }}
% if not success
Expand All @@ -129,15 +146,37 @@ title = _("Withdrawing Money")
<p><a href="{{ response.sanitize_untrusted_url(back_to) }}"
class="btn btn-success">{{ _("Go back") }}</a></p>
% endif

% if show_form
% else
<p>{{ _("You have {0} in your Liberapay wallet.", balances) }}</p>
<p>{{ _("Which currency do you want to withdraw?") }}</p>
<p class="buttons">
% for currency in currencies
<a class="btn btn-default btn-lg" href="?currency={{ currency }}"
>{{ locale.currencies.get(currency, currency) }} ({{ locale.currency_symbols.get(currency, currency) }})</a>
% endfor
</p>
<p>{{ _(
"Withdrawing euros to a SEPA bank account is free, transfers to other "
"countries cost {0} each. Withdrawing US dollars costs {1} per transfer "
"regardless of the destination country.",
constants.FEE_PAYOUT['EUR']['foreign'].with_vat,
constants.FEE_PAYOUT['USD']['*'].with_vat,
) }}</p>
% endif

% if show_form
% if currency == 'EUR'
<p>{{ _(
"Withdrawing euros to a SEPA bank account is free, transfers to other "
"countries cost {0} each.",
constants.FEE_PAYOUT['EUR']['foreign'].with_vat,
) }}</p>
% elif currency == 'USD'
<p>{{ _(
"Withdrawing US dollars costs {0} per transfer, whatever the destination country is.",
constants.FEE_PAYOUT['USD']['*'].with_vat,
) }}</p>
% endif

<h3>{{ _("Amount") }}</h3>

Expand All @@ -148,6 +187,7 @@ title = _("Withdrawing Money")
<fieldset id="amount" class="form-inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<input type="hidden" name="route_id" value="{{ routes[0].id if routes else '' }}" />
<input type="hidden" name="currency" value="{{ currency }}" />
<div class="input-group">
<input name="amount" value="{{ format_decimal(recommended_withdrawal) }}"
class="form-control" size=6 required />
Expand Down

0 comments on commit d6791cb

Please sign in to comment.