Properly restrict refunds to full payment amount

This commit is contained in:
Raphael Michel
2018-09-03 15:41:05 +02:00
parent 7274905a92
commit 21530f315f
6 changed files with 27 additions and 12 deletions

View File

@@ -188,6 +188,17 @@ class Order(LoggedModel):
except TypeError:
return None
@property
def payment_refund_sum(self):
payment_sum = self.payments.filter(
state__in=(OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED)
).aggregate(s=Sum('amount'))['s'] or Decimal('0.00')
refund_sum = self.refunds.filter(
state__in=(OrderRefund.REFUND_STATE_DONE, OrderRefund.REFUND_STATE_TRANSIT,
OrderRefund.REFUND_STATE_CREATED)
).aggregate(s=Sum('amount'))['s'] or Decimal('0.00')
return payment_sum - refund_sum
@property
def pending_sum(self):
total = self.total

View File

@@ -375,9 +375,11 @@ class OrderRefundForm(forms.Form):
self.order = kwargs.pop('order')
super().__init__(*args, **kwargs)
change_decimal_field(self.fields['partial_amount'], self.order.event.currency)
if self.order.status in (Order.STATUS_REFUNDED, Order.STATUS_CANCELED):
del self.fields['action']
def clean_partial_amount(self):
max_amount = self.order.total - self.order.pending_sum
max_amount = self.order.payment_refund_sum
val = self.cleaned_data.get('partial_amount')
if val is not None and (val > max_amount or val <= 0):
raise ValidationError(_('The refund amount needs to be positive and less than {}.').format(max_amount))

View File

@@ -50,10 +50,7 @@
{% trans "Cancel order" %}
</a>
{% endif %}
{% if order.status == 'p' %}
<button name="status" value="n" class="btn btn-default">{% trans "Mark as not paid" %}</button>
{% endif %}
{% if overpaid|add:order.total != 0 %}
{% if order.payment_refund_sum > 0 %}
<a href="{% url "control:event.order.refunds.start" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}" class="btn btn-default">
{% trans "Create a refund" %}
</a>

View File

@@ -35,10 +35,12 @@
</div>
</fieldset>
<p>&nbsp;</p>
<fieldset class="form-inline form-order-change">
<legend>{% trans "What should happen to the order?" %}</legend>
{% bootstrap_field form.action layout='horizontal' horizontal_label_class='sr-only' horizontal_field_class='' %}
</fieldset>
{% if form.action %}
<fieldset class="form-inline form-order-change">
<legend>{% trans "What should happen to the order?" %}</legend>
{% bootstrap_field form.action layout='horizontal' horizontal_label_class='sr-only' horizontal_field_class='' %}
</fieldset>
{% endif %}
<p>&nbsp;</p>
{% csrf_token %}

View File

@@ -442,7 +442,7 @@ class OrderRefundView(OrderView):
data=self.request.POST if self.request.method == "POST" else None,
prefix='start',
initial={
'partial_amount': self.order.total - self.order.pending_sum,
'partial_amount': self.order.payment_refund_sum,
'action': (
'mark_pending' if self.order.status == Order.STATUS_PAID
else 'do_nothing'
@@ -465,7 +465,7 @@ class OrderRefundView(OrderView):
# Algorithm to choose which payments are to be refunded to create the least hassle
if self.start_form.cleaned_data.get('mode') == 'full':
to_refund = full_refund = self.order.total - self.order.pending_sum
to_refund = full_refund = self.order.payment_refund_sum
else:
to_refund = full_refund = self.start_form.cleaned_data.get('partial_amount')

View File

@@ -1402,7 +1402,10 @@ def test_refund_paid_order_automatically(client, env, monkeypatch):
def charge_retr(*args, **kwargs):
def refund_create(amount):
pass
r = MockedCharge()
r.id = 'foo'
r.status = 'succeeded'
return r
c = MockedCharge()
c.refunds.create = refund_create