Skip to content

Commit

Permalink
allow higher payment amounts (#1919)
Browse files Browse the repository at this point in the history
  • Loading branch information
Changaco committed Nov 16, 2020
1 parent 503b382 commit ef8bc6d
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 46 deletions.
36 changes: 28 additions & 8 deletions liberapay/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,10 +309,20 @@ def __missing__(self, currency):
'EUR': Money('40.00', 'EUR'),
'USD': Money('48.00', 'USD'),
}),
'max_acceptable': MoneyAutoConvertDict({
'EUR': Money('5000.00', 'EUR'),
'USD': Money('5000.00', 'USD'),
}),
'max_acceptable': {
'new_donor': MoneyAutoConvertDict({
'EUR': Money('5200.00', 'EUR'),
'USD': Money('5200.00', 'USD'),
}),
'active_donor': MoneyAutoConvertDict({
'EUR': Money('12000.00', 'EUR'),
'USD': Money('12000.00', 'USD'),
}),
'trusted_donor': MoneyAutoConvertDict({
'EUR': Money('52000.00', 'EUR'),
'USD': Money('52000.00', 'USD'),
}),
},
},
'stripe': {
'min_acceptable': MoneyAutoConvertDict({ # fee > 10%
Expand All @@ -327,10 +337,20 @@ def __missing__(self, currency):
'EUR': Money('40.00', 'EUR'),
'USD': Money('48.00', 'USD'),
}),
'max_acceptable': MoneyAutoConvertDict({
'EUR': Money('5000.00', 'EUR'),
'USD': Money('5000.00', 'USD'),
}),
'max_acceptable': {
'new_donor': MoneyAutoConvertDict({
'EUR': Money('5200.00', 'EUR'),
'USD': Money('5200.00', 'USD'),
}),
'active_donor': MoneyAutoConvertDict({
'EUR': Money('12000.00', 'EUR'),
'USD': Money('12000.00', 'USD'),
}),
'trusted_donor': MoneyAutoConvertDict({
'EUR': Money('52000.00', 'EUR'),
'USD': Money('52000.00', 'USD'),
}),
},
},
}

Expand Down
2 changes: 1 addition & 1 deletion liberapay/models/participant.py
Original file line number Diff line number Diff line change
Expand Up @@ -2685,7 +2685,7 @@ def find_partial_match(new_sp, current_schedule_map):
else:
tip.renewal_amount = None
if not tip.renewal_amount or tip.renewal_amount < (tip.amount * 2):
pp = PayinProspect([tip], 'stripe')
pp = PayinProspect(self, [tip], 'stripe')
tip.renewal_amount = pp.moderate_proposed_amount
else:
tip.renewal_amount = None
Expand Down
19 changes: 17 additions & 2 deletions liberapay/payin/prospect.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class PayinProspect:
'suggested_amounts',
)

def __init__(self, tips, provider):
def __init__(self, donor, tips, provider):
"""This method computes the suggested payment amounts.
Args:
Expand Down Expand Up @@ -60,8 +60,23 @@ def __init__(self, tips, provider):
self.one_weeks_worth
)
self.low_fee_amount = standard_amounts['low_fee'][self.currency]
standard_maximums = standard_amounts['max_acceptable']
donor_has_at_least_one_good_payment = donor.db.one("""
SELECT count(*)
FROM payins
WHERE payer = %s
AND status = 'succeeded'
AND refunded_amount IS NULL
AND ctime < (current_timestamp - interval '30 days')
""", (donor.id,)) > 0
self.max_acceptable_amount = min(
standard_amounts['max_acceptable'][self.currency],
(
standard_maximums['trusted_donor'][self.currency]
if donor.is_suspended is False else
standard_maximums['active_donor'][self.currency]
if donor_has_at_least_one_good_payment else
standard_maximums['new_donor'][self.currency]
),
self.twenty_years_worth
)
self.min_proposed_amount = min(
Expand Down
39 changes: 20 additions & 19 deletions tests/py/test_payins.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ def setUp(self):
def test_minimum_weekly_EUR_tip(self):
tip_amount = DONATION_LIMITS['EUR']['weekly'][0]
tip = self.alice.set_tip_to(self.bob, tip_amount)
pp = PayinProspect([tip], 'stripe')
pp = PayinProspect(self.alice, [tip], 'stripe')
assert pp.currency == 'EUR'
assert pp.period == 'weekly'
assert pp.one_periods_worth == tip_amount
Expand All @@ -198,7 +198,7 @@ def test_minimum_weekly_EUR_tip(self):
def test_minimum_monthly_EUR_tip(self):
tip_amount = DONATION_LIMITS['EUR']['monthly'][0]
tip = self.alice.set_tip_to(self.bob, tip_amount, period='monthly')
pp = PayinProspect([tip], 'stripe')
pp = PayinProspect(self.alice, [tip], 'stripe')
assert pp.currency == 'EUR'
assert pp.period == 'monthly'
assert pp.one_periods_worth == tip_amount
Expand All @@ -212,7 +212,7 @@ def test_minimum_monthly_EUR_tip(self):
def test_minimum_yearly_EUR_tip(self):
tip_amount = DONATION_LIMITS['EUR']['yearly'][0]
tip = self.alice.set_tip_to(self.bob, tip_amount, period='yearly')
pp = PayinProspect([tip], 'stripe')
pp = PayinProspect(self.alice, [tip], 'stripe')
assert pp.currency == 'EUR'
assert pp.period == 'yearly'
assert pp.one_periods_worth == tip_amount
Expand All @@ -226,7 +226,7 @@ def test_minimum_yearly_EUR_tip(self):
def test_small_weekly_USD_tip(self):
tip_amount = STANDARD_TIPS['USD'][1].weekly
tip = self.alice.set_tip_to(self.bob, tip_amount)
pp = PayinProspect([tip], 'stripe')
pp = PayinProspect(self.alice, [tip], 'stripe')
assert pp.currency == 'USD'
assert pp.period == 'weekly'
assert pp.one_periods_worth == tip_amount
Expand All @@ -239,7 +239,7 @@ def test_small_weekly_USD_tip(self):
def test_small_monthly_USD_tip(self):
tip_amount = USD('1.00')
tip = self.alice.set_tip_to(self.bob, tip_amount, period='monthly')
pp = PayinProspect([tip], 'stripe')
pp = PayinProspect(self.alice, [tip], 'stripe')
assert pp.currency == 'USD'
assert pp.period == 'monthly'
assert pp.one_periods_worth == tip_amount
Expand All @@ -252,7 +252,7 @@ def test_small_monthly_USD_tip(self):
def test_small_yearly_USD_tip(self):
tip_amount = USD('10.00')
tip = self.alice.set_tip_to(self.bob, tip_amount, period='yearly')
pp = PayinProspect([tip], 'stripe')
pp = PayinProspect(self.alice, [tip], 'stripe')
assert pp.currency == 'USD'
assert pp.period == 'yearly'
assert pp.one_periods_worth == tip_amount
Expand All @@ -267,7 +267,7 @@ def test_small_yearly_USD_tip(self):
def test_medium_weekly_JPY_tip(self):
tip_amount = STANDARD_TIPS['JPY'][2].weekly
tip = self.alice.set_tip_to(self.bob, tip_amount)
pp = PayinProspect([tip], 'stripe')
pp = PayinProspect(self.alice, [tip], 'stripe')
assert pp.currency == 'JPY'
assert pp.period == 'weekly'
assert pp.one_periods_worth == tip_amount
Expand All @@ -282,7 +282,7 @@ def test_medium_weekly_JPY_tip(self):
def test_medium_monthly_JPY_tip(self):
tip_amount = JPY('500')
tip = self.alice.set_tip_to(self.bob, tip_amount, period='monthly')
pp = PayinProspect([tip], 'stripe')
pp = PayinProspect(self.alice, [tip], 'stripe')
assert pp.currency == 'JPY'
assert pp.period == 'monthly'
assert pp.one_periods_worth == tip_amount
Expand All @@ -297,7 +297,7 @@ def test_medium_monthly_JPY_tip(self):
def test_medium_yearly_JPY_tip(self):
tip_amount = JPY('5000')
tip = self.alice.set_tip_to(self.bob, tip_amount, period='yearly')
pp = PayinProspect([tip], 'stripe')
pp = PayinProspect(self.alice, [tip], 'stripe')
assert pp.currency == 'JPY'
assert pp.period == 'yearly'
assert pp.one_periods_worth == tip_amount
Expand All @@ -310,7 +310,7 @@ def test_medium_yearly_JPY_tip(self):
def test_large_weekly_EUR_tip(self):
tip_amount = STANDARD_TIPS['EUR'][3].weekly
tip = self.alice.set_tip_to(self.bob, tip_amount)
pp = PayinProspect([tip], 'stripe')
pp = PayinProspect(self.alice, [tip], 'stripe')
assert pp.currency == 'EUR'
assert pp.period == 'weekly'
assert pp.one_periods_worth == tip_amount
Expand All @@ -324,7 +324,7 @@ def test_large_weekly_EUR_tip(self):
def test_large_monthly_EUR_tip(self):
tip_amount = EUR('25.00')
tip = self.alice.set_tip_to(self.bob, tip_amount, period='monthly')
pp = PayinProspect([tip], 'stripe')
pp = PayinProspect(self.alice, [tip], 'stripe')
assert pp.currency == 'EUR'
assert pp.period == 'monthly'
assert pp.one_periods_worth == tip_amount
Expand All @@ -338,7 +338,7 @@ def test_large_monthly_EUR_tip(self):
def test_large_yearly_EUR_tip(self):
tip_amount = EUR('500.00')
tip = self.alice.set_tip_to(self.bob, tip_amount, period='yearly')
pp = PayinProspect([tip], 'stripe')
pp = PayinProspect(self.alice, [tip], 'stripe')
assert pp.currency == 'EUR'
assert pp.period == 'yearly'
assert pp.one_periods_worth == tip_amount
Expand All @@ -350,20 +350,21 @@ def test_large_yearly_EUR_tip(self):
def test_maximum_yearly_EUR_tip(self):
tip_amount = EUR('5200.00')
tip = self.alice.set_tip_to(self.bob, tip_amount, period='yearly')
pp = PayinProspect([tip], 'stripe')
pp = PayinProspect(self.alice, [tip], 'stripe')
assert pp.currency == 'EUR'
assert pp.period == 'yearly'
assert pp.one_periods_worth == tip_amount
assert pp.one_weeks_worth == EUR('100.00')
assert pp.one_months_worth == EUR('433.33')
assert pp.one_years_worth == tip_amount
assert pp.suggested_amounts == [EUR('5000.00')]
assert pp.suggested_amounts == [EUR('5200.00')]
assert pp.max_acceptable_amount == EUR('5200.00')

def test_two_small_monthly_USD_tips(self):
tip_amount = USD('1.00')
tip1 = self.alice.set_tip_to(self.bob, tip_amount, period='monthly')
tip2 = self.alice.set_tip_to(self.carl, tip_amount, period='monthly')
pp = PayinProspect([tip1, tip2], 'stripe')
pp = PayinProspect(self.alice, [tip1, tip2], 'stripe')
assert pp.currency == 'USD'
assert pp.period == 'monthly'
assert pp.one_periods_worth == tip_amount * 2
Expand All @@ -379,7 +380,7 @@ def test_two_medium_yearly_KRW_tips(self):
tip_amount = KRW('50000')
tip1 = self.alice.set_tip_to(self.bob, tip_amount, period='yearly')
tip2 = self.alice.set_tip_to(self.carl, tip_amount, period='yearly')
pp = PayinProspect([tip1, tip2], 'stripe')
pp = PayinProspect(self.alice, [tip1, tip2], 'stripe')
assert pp.currency == 'KRW'
assert pp.period == 'yearly'
assert pp.one_periods_worth == tip_amount * 2
Expand All @@ -392,7 +393,7 @@ def test_two_medium_yearly_KRW_tips(self):
def test_two_very_different_EUR_tips(self):
tip1 = self.alice.set_tip_to(self.bob, EUR('0.24'), period='weekly')
tip2 = self.alice.set_tip_to(self.carl, EUR('240.00'), period='yearly')
pp = PayinProspect([tip1, tip2], 'stripe')
pp = PayinProspect(self.alice, [tip1, tip2], 'stripe')
assert pp.currency == 'EUR'
assert pp.period == 'weekly'
assert pp.one_periods_worth == EUR('4.86')
Expand All @@ -408,7 +409,7 @@ def test_three_very_different_EUR_tips(self):
tip1 = self.alice.set_tip_to(self.bob, EUR('0.01'), period='weekly')
tip2 = self.alice.set_tip_to(self.carl, EUR('1.00'), period='monthly')
tip3 = self.alice.set_tip_to(self.dana, EUR('5200.00'), period='yearly')
pp = PayinProspect([tip1, tip2, tip3], 'stripe')
pp = PayinProspect(self.alice, [tip1, tip2, tip3], 'stripe')
assert pp.currency == 'EUR'
assert pp.period == 'monthly'
assert pp.one_periods_worth == EUR('434.38')
Expand Down Expand Up @@ -616,7 +617,7 @@ def test_00_payin_stripe_card(self):
self.db.run("ALTER SEQUENCE payins_id_seq RESTART WITH %s", (self.offset,))
self.db.run("ALTER SEQUENCE payin_transfers_id_seq RESTART WITH %s", (self.offset,))
self.add_payment_account(self.creator_1, 'stripe')
tip = self.donor.set_tip_to(self.creator_1, EUR('0.02'))
tip = self.donor.set_tip_to(self.creator_1, EUR('0.05'))

# 1st request: test getting the payment page
r = self.client.GET(
Expand Down
11 changes: 4 additions & 7 deletions www/%username/giving/pay/paypal/%payin_id.spt
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,9 @@ if request.method == 'POST':
if len(tips) > 1:
raise response.error(400, "We don't support one-to-many payments through PayPal yet.")

tips_weekly_sum = Money.sum((tip.amount for tip in tips), payin_currency)
amount_min = max(
constants.PAYIN_AMOUNTS['paypal']['min_acceptable'][payin_currency],
tips_weekly_sum
)
amount_max = constants.PAYIN_AMOUNTS['paypal']['max_acceptable'][payin_currency]
prospect = PayinProspect(payer, tips, 'paypal')
amount_min = prospect.min_acceptable_amount
amount_max = prospect.max_acceptable_amount
if payin_amount < amount_min or payin_amount > amount_max:
raise response.error(400, _(
"'{0}' is not an acceptable amount (min={1}, max={2})",
Expand Down Expand Up @@ -118,7 +115,7 @@ if tippees:
]
if len(set(tip.amount.currency for tip in tips)) != 1:
raise response.invalid_input(tippees, 'beneficiary', 'querystring')
payment = PayinProspect(tips, 'paypal')
payment = PayinProspect(payer, tips, 'paypal')
del tips

elif not payin_id:
Expand Down
11 changes: 4 additions & 7 deletions www/%username/giving/pay/stripe/%payin_id.spt
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,9 @@ if request.method == 'POST':
if len(set(tip.amount.currency for tip in tips)) != 1:
raise response.invalid_input(body.get('tips'), 'tips', 'body')

tips_weekly_sum = Money.sum((tip.amount for tip in tips), payin_currency)
amount_min = max(
constants.PAYIN_AMOUNTS['stripe']['min_acceptable'][payin_currency],
tips_weekly_sum
)
amount_max = constants.PAYIN_AMOUNTS['stripe']['max_acceptable'][payin_currency]
prospect = PayinProspect(payer, tips, 'stripe')
amount_min = prospect.min_acceptable_amount
amount_max = prospect.max_acceptable_amount
if payin_amount < amount_min or payin_amount > amount_max:
raise response.error(400, _(
"'{0}' is not an acceptable amount (min={1}, max={2})",
Expand Down Expand Up @@ -170,7 +167,7 @@ if tippees:
]
if len(set(tip.amount.currency for tip in tips)) != 1:
raise response.invalid_input(tippees, 'beneficiary', 'querystring')
payment = PayinProspect(tips, 'stripe')
payment = PayinProspect(payer, tips, 'stripe')
del tips

elif not payin_id:
Expand Down
4 changes: 2 additions & 2 deletions www/%username/giving/schedule.spt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ if request.method == 'POST':
else:
new_amount = Money(new_amount, payin_currency, rounding=ROUND_HALF_UP)
tips = payer.get_tips_to([tr['tippee_id'] for tr in sp.transfers])
prospect = PayinProspect(tips, 'stripe')
prospect = PayinProspect(payer, tips, 'stripe')
if payin_currency != prospect.currency:
raise UnexpectedCurrency(new_amount, prospect.currency)
amount_min = prospect.min_acceptable_amount
Expand Down Expand Up @@ -77,7 +77,7 @@ if action:
raise response.invalid_input(sp_id, 'id', 'querystring')
if action == 'modify':
sp.tips = payer.get_tips_to([tr['tippee_id'] for tr in sp.transfers])
sp.prospect = PayinProspect(sp.tips, 'stripe')
sp.prospect = PayinProspect(payer, sp.tips, 'stripe')

[---] text/html
% extends "templates/layouts/settings.html"
Expand Down

0 comments on commit ef8bc6d

Please sign in to comment.