Skip to content

Commit

Permalink
Merge pull request #2489 from liberapay/stripe-sdd-pm-5
Browse files Browse the repository at this point in the history
  • Loading branch information
Changaco authored Oct 29, 2024
2 parents a692ba6 + 0eedd0c commit 13dbdf4
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 114 deletions.
77 changes: 40 additions & 37 deletions liberapay/models/participant.py
Original file line number Diff line number Diff line change
Expand Up @@ -3366,54 +3366,57 @@ def group_tips_into_payments(self, tips):
groups['self_donation'].append(tip)
else:
n_fundable += 1
if tippee_p.payment_providers & 1 == 1:
members = set(members)
members.discard(self.id)
in_sepa = self.db.one("""
SELECT true
FROM current_takes t
JOIN payment_accounts a ON a.participant = t.member
WHERE t.team = %(tippee)s
AND t.member IN %(members)s
AND a.provider = 'stripe'
AND a.is_current
AND a.charges_enabled
AND a.country IN %(SEPA)s
LIMIT 1
""", dict(members=members, tippee=tip.tippee, SEPA=SEPA))
if in_sepa:
group = stripe_europe.setdefault(tip.amount.currency, [])
if len(group) == 0:
groups['fundable'].append(group)
group.append(tip)
else:
groups['fundable'].append([tip])
else:
groups['fundable'].append([tip])
else:
n_fundable += 1
if tippee_p.payment_providers & 1 == 1:
in_sepa = self.db.one("""
SELECT true
FROM payment_accounts a
WHERE a.participant = %(tippee)s
AND a.provider = 'stripe'
AND a.is_current
AND a.charges_enabled
AND a.country IN %(SEPA)s
LIMIT 1
""", dict(tippee=tip.tippee, SEPA=SEPA))
in_sepa = tip.tippee_p.has_stripe_sepa_for(self)
if in_sepa:
group = stripe_europe.setdefault(tip.amount.currency, [])
if len(group) == 0:
groups['fundable'].append(group)
group.append(tip)
else:
groups['fundable'].append([tip])
else:
n_fundable += 1
in_sepa = tip.tippee_p.has_stripe_sepa_for(self)
if in_sepa:
group = stripe_europe.setdefault(tip.amount.currency, [])
if len(group) == 0:
groups['fundable'].append(group)
group.append(tip)
else:
groups['fundable'].append([tip])
return groups, n_fundable

def has_stripe_sepa_for(self, tipper):
if tipper == self or self.payment_providers & 1 == 0:
return False
if self.kind == 'group':
return self.db.one("""
SELECT true
FROM current_takes t
JOIN participants p ON p.id = t.member
JOIN payment_accounts a ON a.participant = t.member
WHERE t.team = %(tippee)s
AND t.member <> %(tipper)s
AND t.amount <> 0
AND p.is_suspended IS NOT TRUE
AND a.provider = 'stripe'
AND a.is_current
AND a.charges_enabled
AND a.country IN %(SEPA)s
LIMIT 1
""", dict(tipper=tipper.id, tippee=self.id, SEPA=SEPA))
else:
return self.db.one("""
SELECT true
FROM payment_accounts a
WHERE a.participant = %(tippee)s
AND a.provider = 'stripe'
AND a.is_current
AND a.charges_enabled
AND a.country IN %(SEPA)s
LIMIT 1
""", dict(tippee=self.id, SEPA=SEPA))

def get_tips_to(self, tippee_ids):
return self.db.all("""
SELECT t.*, p AS tippee_p
Expand Down
107 changes: 61 additions & 46 deletions liberapay/payin/cron.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,63 +201,78 @@ def send_upcoming_debit_notifications():
AND sp.notifs_count = 0
AND sp.payin IS NULL
AND sp.ctime < (current_timestamp - interval '6 hours')
GROUP BY sp.payer, (sp.amount).currency
GROUP BY sp.payer
HAVING min(sp.execution_date) <= (current_date + interval '14 days')
ORDER BY sp.payer, (sp.amount).currency
ORDER BY sp.payer
""")
for payer, payins in rows:
if not payer.can_attempt_payment:
continue
_check_scheduled_payins(db, payer, payins, automatic=True)
if not payins:
continue
payins.sort(key=itemgetter('execution_date'))
context = {
'payins': payins,
'total_amount': sum(sp['amount'] for sp in payins),
}
for sp in context['payins']:
payins.sort(key=lambda sp: (
sp['amount'].currency, sp['execution_date'], sp['id']
))
routes = website.db.all("""
SELECT r
FROM exchange_routes r
WHERE r.participant = %(payer_id)s
AND r.status = 'chargeable'
AND r.network::text LIKE 'stripe-%%'
""", dict(payer_id=payer.id))
for route in routes:
route.__dict__['participant'] = payer
grouped_payins = defaultdict(list)
for sp in payins:
for tr in sp['transfers']:
del tr['tip'], tr['beneficiary']
if len(payins) > 1:
last_execution_date = payins[-1]['execution_date']
max_execution_date = max(sp['execution_date'] for sp in payins)
assert last_execution_date == max_execution_date
context['ndays'] = (max_execution_date - utcnow().date()).days
currency = payins[0]['amount'].currency
while True:
route = db.one("""
SELECT r
FROM exchange_routes r
WHERE r.participant = %s
AND r.status = 'chargeable'
AND r.network::text LIKE 'stripe-%%'
ORDER BY r.is_default_for = %s DESC NULLS LAST
, r.is_default NULLS LAST
, r.network = 'stripe-sdd' DESC
, r.ctime DESC
LIMIT 1
""", (payer.id, currency))
if route is None:
break
route.sync_status()
if route.status == 'chargeable':
currency = sp['amount'].currency
routes.sort(key=lambda r: (
-(r.is_default_for == currency),
-(r.is_default is True),
-(r.network == 'stripe-sdd'),
-(r.ctime.timestamp()),
))
recipients_are_in_sepa = (
len(sp['transfers']) > 1 or db.Participant.from_id(
sp['transfers'][0]['tippee_id']
).has_stripe_sepa_for(payer)
)
suitable_route = None
for route in routes:
if route.network == 'stripe-sdd' and not recipients_are_in_sepa:
continue
suitable_route = route
break
if route:
event = 'upcoming_debit'
context['instrument_brand'] = route.get_brand()
context['instrument_partial_number'] = route.get_partial_number()
else:
event = 'missing_route'
payer.notify(event, email_unverified_address=True, **context)
counts[event] += 1
db.run("""
UPDATE scheduled_payins
SET notifs_count = notifs_count + 1
, last_notif_ts = now()
WHERE payer = %s
AND id IN %s
""", (payer.id, tuple(sp['id'] for sp in payins)))
grouped_payins[(currency, suitable_route)].append(sp)
del suitable_route
del payins
for (currency, route), payins in grouped_payins.items():
context = {
'payins': payins,
'total_amount': sum(sp['amount'] for sp in payins),
}
if len(payins) > 1:
last_execution_date = payins[-1]['execution_date']
max_execution_date = max(sp['execution_date'] for sp in payins)
assert last_execution_date == max_execution_date
context['ndays'] = (max_execution_date - utcnow().date()).days
if route:
event = 'upcoming_debit'
context['instrument_brand'] = route.get_brand()
context['instrument_partial_number'] = route.get_partial_number()
else:
event = 'missing_route'
payer.notify(event, email_unverified_address=True, **context)
counts[event] += 1
db.run("""
UPDATE scheduled_payins
SET notifs_count = notifs_count + 1
, last_notif_ts = now()
WHERE payer = %s
AND id IN %s
""", (payer.id, tuple(sp['id'] for sp in payins)))
for k, n in sorted(counts.items()):
logger.info("Sent %i %s notifications." % (n, k))

Expand Down
32 changes: 1 addition & 31 deletions www/%username/giving/pay/%payment_id.spt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ from functools import reduce
from math import ceil
from types import SimpleNamespace

from liberapay.constants import SEPA
from liberapay.i18n.base import BOLD
from liberapay.models.participant import Participant
from liberapay.utils import get_participant, group_by, partition
Expand All @@ -15,35 +14,6 @@ allow_stripe_card = website.app_conf.payin_methods.get('stripe-card', True)
allow_stripe_sdd = website.app_conf.payin_methods.get('stripe-sdd', True)
allow_paypal = website.app_conf.payin_methods.get('paypal', True)

def is_sepa(payment):
tip = payment.tips[0]
if tip.tippee_p.kind == 'group':
return website.db.one("""
SELECT true
FROM current_takes t
JOIN participants p ON p.id = t.member
JOIN payment_accounts a ON a.participant = t.member
WHERE t.team = %(tippee)s
AND t.amount <> 0
AND p.is_suspended IS NOT TRUE
AND a.provider = 'stripe'
AND a.is_current
AND a.charges_enabled
AND a.country IN %(SEPA)s
LIMIT 1
""", dict(tippee=tip.tippee, SEPA=SEPA), default=False)
else:
return website.db.one("""
SELECT true
FROM payment_accounts a
WHERE a.participant = %(tippee)s
AND a.provider = 'stripe'
AND a.is_current
AND a.charges_enabled
AND a.country IN %(SEPA)s
LIMIT 1
""", dict(tippee=tip.tippee, SEPA=SEPA), default=False)

[---]

payer = get_participant(state, restrict=True)
Expand Down Expand Up @@ -306,7 +276,7 @@ title = _("Funding your donations")
% endif
</div>
</div>
% set sepa = is_sepa(payment)
% set sepa = payment.tips[0].tippee_p.has_stripe_sepa_for(payer)
% set possible = allow_stripe_sdd and sepa and payment.currency == 'EUR'
% set warn = payer.guessed_country not in constants.SEPA
<div class="panel panel-default {{ '' if possible else 'muted' }}">
Expand Down

0 comments on commit 13dbdf4

Please sign in to comment.