Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow higher payment amounts #1919

Merged
merged 1 commit into from
Nov 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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