Allow users to increase cancellation fees (#1622)

* Allow users to increase cancellation fees

* Fix typo
This commit is contained in:
Raphael Michel
2020-03-25 10:11:29 +01:00
committed by GitHub
parent 0a49b93b26
commit a5910016fd
14 changed files with 2675 additions and 32 deletions

View File

@@ -611,6 +611,7 @@ class EventSettingsSerializer(serializers.Serializer):
'cancel_allow_user_paid_keep',
'cancel_allow_user_paid_keep_fees',
'cancel_allow_user_paid_keep_percentage',
'cancel_allow_user_paid_adjust_fees',
]
def __init__(self, *args, **kwargs):

View File

@@ -907,6 +907,16 @@ DEFAULTS = {
label=_("Keep a percentual cancellation fee"),
)
},
'cancel_allow_user_paid_adjust_fees': {
'default': 'False',
'type': bool,
'form_class': forms.BooleanField,
'serializer_class': serializers.BooleanField,
'form_kwargs': dict(
label=_("Allow customers to voluntarily choose a lower refund"),
help_text=_("With this option enabled, your customers can choose to get a smaller refund to support you.")
)
},
'cancel_allow_user_paid_until': {
'default': None,
'type': RelativeDateWrapper,

View File

@@ -565,6 +565,7 @@ class CancelSettingsForm(SettingsForm):
'cancel_allow_user_paid_keep',
'cancel_allow_user_paid_keep_fees',
'cancel_allow_user_paid_keep_percentage',
'cancel_allow_user_paid_adjust_fees',
]

View File

@@ -19,6 +19,7 @@
{% bootstrap_field form.cancel_allow_user_paid_keep_percentage layout="control" %}
{% bootstrap_field form.cancel_allow_user_paid_keep_fees layout="control" %}
{% bootstrap_field form.cancel_allow_user_paid_until layout="control" %}
{% bootstrap_field form.cancel_allow_user_paid_adjust_fees layout="control" %}
{% if not gets_notification %}
<div class="alert alert-warning">
{% blocktrans trimmed %}

View File

@@ -44,7 +44,7 @@
<meta name="msapplication-config" content="{% url "presale:browserconfig.xml" %}">
<meta name="theme-color" content="{{ settings.primary_color|default:"#3b1c4a" }}">
</head>
<body class="nojs" data-locale="{{ request.LANGUAGE_CODE }}" data-now="{% now "U.u" %}" data-datetimeformat="{{ js_datetime_format }}" data-timeformat="{{ js_time_format }}" data-dateformat="{{ js_date_format }}" data-datetimelocale="{{ js_locale }}">
<body class="nojs" data-locale="{{ request.LANGUAGE_CODE }}" data-now="{% now "U.u" %}" data-datetimeformat="{{ js_datetime_format }}" data-timeformat="{{ js_time_format }}" data-dateformat="{{ js_date_format }}" data-datetimelocale="{{ js_locale }}" data-currency="{{ request.event.currency }}">
{{ html_page_header|safe }}
{% block above %}
{% endblock %}

View File

@@ -2,6 +2,7 @@
{% load i18n %}
{% load money %}
{% load eventurl %}
{% load l10n %}
{% block title %}{% trans "Cancel order" %}{% endblock %}
{% block content %}
<h2>
@@ -9,38 +10,79 @@
Cancel order: {{ code }}
{% endblocktrans %}
</h2>
<p>
{% blocktrans trimmed %}
Do you really want to cancel this order? You cannot revert this action.
{% endblocktrans %}
{% trans "This will invalidate all tickets in this order." %}
</p>
{% if refund_amount %}
{% if can_auto_refund %}
<p>
<strong>
{% blocktrans trimmed with amount=refund_amount|money:request.event.currency %}
The refund amount of {{ amount }} will automatically be sent back to your original payment method. Depending on the payment method,
please allow for up to two weeks before this appears on your statement.
{% endblocktrans %}
</strong>
</p>
{% else %}
<div class="alert alert-warning">
{% blocktrans trimmed with amount=refund_amount|money:request.event.currency %}
With to the payment method you used, the refund amount of {{ amount }} <strong>can not be sent back to you automatically</strong>. Instead, the
event organizer will need to initiate the transfer manually. Please be patient as this might take a bit longer.
{% endblocktrans %}
</div>
{% endif %}
{% endif %}
<form method="post" action="{% eventurl request.event "presale:event.order.cancel.do" secret=order.secret order=order.code %}" data-asynctask>
<p>
{% blocktrans trimmed %}
Do you really want to cancel this order? You cannot revert this action.
{% endblocktrans %}
{% trans "This will invalidate all tickets in this order." %}
</p>
{% if request.event.settings.cancel_allow_user_paid_adjust_fees %}
<p>
{% if cancel_fee %}
{% blocktrans trimmed with fee=order.user_cancel_fee|money:request.event.currency %}
If you want, you can request a refund for the full amount minus a cancellation fee of
{{ fee }}.
{% endblocktrans %}
{% else %}
{% blocktrans trimmed with fee=order.user_cancel_fee|money:request.event.currency %}
If you want, you can request a full refund.
{% endblocktrans %}
{% endif %}
{% trans "However, if you want us to help keep the lights on here, please consider using the slider below to request a smaller refund. Thank you!" %}
</p>
<div class="cancel-fee-slider">
<div id="cancel-fee-keep"></div>
<input
id="cancel-fee-slider"
type="text"
name="cancel_fee"
class="col-md-6 col-sm-12"
value="{{ cancel_fee|stringformat:".2f" }}"
data-slider-min="{{ cancel_fee|stringformat:".2f" }}"
data-slider-value="{{ cancel_fee|stringformat:".2f" }}"
data-slider-step="0.01"
data-slider-max="{{ order.payment_refund_sum|stringformat:".2f" }}"
data-slider-tooltip="hide"/>
<div id="cancel-fee-refund">AS</div>
</div>
<div class="text-center" id="cancel-fee-custom-link">
<a id="cancel-fee-custom"><small>Enter custom amount</small></a>
</div>
{% else %}
<p>
<strong>{% trans "Refund amount:" %}</strong> {{ refund_amount|money:request.event.currency }}
</p>
{% endif %}
{% if refund_amount %}
{% if can_auto_refund %}
<p>
<strong>
{% blocktrans trimmed %}
The refund amount will automatically be sent back to your original payment method. Depending
on the payment method, please allow for up to two weeks before this appears on your
statement.
{% endblocktrans %}
</strong>
</p>
{% else %}
<p>
{% blocktrans trimmed %}
With the payment method you used, the refund amount <strong>can not be sent back to you
automatically</strong>. Instead, the event organizer will need to initiate the transfer
manually. Please be patient as this might take a bit longer.
{% endblocktrans %}
</p>
{% endif %}
{% endif %}
{% csrf_token %}
<div class="row checkout-button-row">
<div class="col-md-4">
<a class="btn btn-block btn-default btn-lg"
href="{% eventurl request.event "presale:event.order" secret=order.secret order=order.code %}">
href="{% eventurl request.event "presale:event.order" secret=order.secret order=order.code %}">
{% trans "No, take me back" %}
</a>
</div>

View File

@@ -6,7 +6,9 @@
<script type="text/javascript" src="{% static "js/jquery.formset.js" %}"></script>
<script type="text/javascript" src="{% static "bootstrap/js/bootstrap.js" %}"></script>
<script type="text/javascript" src="{% static "datetimepicker/bootstrap-datetimepicker.js" %}"></script>
<script type="text/javascript" src="{% static "slider/bootstrap-slider.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/jquery.qrcode.min.js" %}"></script>
<script type="text/javascript" src="{% static "pretixpresale/js/widget/floatformat.js" %}"></script>
<script type="text/javascript" src="{% static "pretixpresale/js/ui/questions.js" %}"></script>
<script type="text/javascript" src="{% static "pretixpresale/js/ui/main.js" %}"></script>
<script type="text/javascript" src="{% static "pretixbase/js/asynctask.js" %}"></script>

View File

@@ -723,8 +723,10 @@ class OrderCancel(EventViewMixin, OrderDetailMixin, TemplateView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['order'] = self.order
refund_amount = self.order.payment_refund_sum - self.order.user_cancel_fee
fee = self.order.user_cancel_fee
refund_amount = self.order.payment_refund_sum - fee
proposals = self.order.propose_auto_refunds(refund_amount)
ctx['cancel_fee'] = fee
ctx['refund_amount'] = refund_amount
ctx['can_auto_refund'] = sum(proposals.values()) == refund_amount
return ctx
@@ -750,6 +752,13 @@ class OrderCancelDo(EventViewMixin, OrderDetailMixin, AsyncAction, View):
fee = None
if self.order.status == Order.STATUS_PAID and self.order.total != Decimal('0.00'):
fee = self.order.user_cancel_fee
if 'cancel_fee' in request.POST and self.request.event.settings.cancel_allow_user_paid_adjust_fees:
custom_fee = Decimal(request.POST.get('cancel_fee'))
if fee <= custom_fee <= self.order.payment_refund_sum:
fee = custom_fee
else:
messages.error(request, _('You chose an invalid cancellation fee.'))
return redirect(self.get_order_url())
return self.do(self.order.pk, cancellation_fee=fee, try_auto_refund=True)
def get_context_data(self, **kwargs):

View File

@@ -14,6 +14,14 @@ function ngettext(singular, plural, count) {
return plural;
}
function interpolate(fmt, object, named) {
if (named) {
return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])});
} else {
return fmt.replace(/%s/g, function(match){return String(obj.shift())});
}
}
var form_handlers = function (el) {
el.find(".datetimepicker").each(function () {
$(this).datetimepicker({
@@ -300,6 +308,44 @@ $(function () {
form_handlers($("body"));
var cancel_fee_slider_update = function () {
if (typeof django === "undefined") {
window.setTimeout(cancel_fee_slider_update, 100);
return;
}
$("#cancel-fee-keep").text(interpolate(
gettext("We keep %(currency)s %(amount)s"),
{
'currency': $("body").attr("data-currency"),
'amount': floatformat(cancel_fee_slider.getValue(), 2)
},
true
));
$("#cancel-fee-refund").text(interpolate(
gettext("You get %(currency)s %(amount)s back"),
{
'currency': $("body").attr("data-currency"),
'amount': floatformat((cancel_fee_slider.getAttribute("max") - cancel_fee_slider.getValue()), 2)
},
true
));
}
var cancel_fee_slider = $('#cancel-fee-slider').slider({
}).on('slide', function () {
cancel_fee_slider_update();
}).data('slider');
if (cancel_fee_slider) {
cancel_fee_slider_update();
$("#cancel-fee-custom").click(function () {
try {
var newinp = parseFloat(prompt(gettext("Please enter the amount we can keep."), cancel_fee_slider.getValue().toString()).replace(',', '.'));
cancel_fee_slider.setValue(newinp);
cancel_fee_slider_update();
} catch {
}
});
}
// Lightbox
lightbox.init();
});

View File

@@ -196,3 +196,34 @@ section.front-page {
h2.subevent-head {
margin-top: 10px;
}
.cancel-fee-slider {
display: flex;
flex-direction: row;
align-items: center;
.slider-selection {
background: $brand-success;
}
.slider-handle {
@include slider_background-image($brand-success, darken($brand-success, 5%), mix($brand-success, darken($brand-success, 5%)));
}
input {
flex: 0;
}
& > div {
flex: 1;
font-weight: bold;
}
& > div:first-child {
text-align: right;
padding-right: 20px;
}
& > div:last-child {
text-align: left;
padding-left: 20px;
}
}
#cancel-fee-custom-link {
margin-bottom: 20px;
}

View File

@@ -3,6 +3,7 @@
@import "../../pretixbase/scss/_theme.scss";
@import "../../lightbox/css/lightbox.scss";
@import "../../datetimepicker/_bootstrap-datetimepicker.scss";
@import "../../slider/_bootstrap-slider.scss";
@import "../../fontawesome/scss/font-awesome.scss";
@import "_theme.scss";

View File

@@ -0,0 +1,387 @@
/*! =========================================================
* bootstrap-slider.js
*
* Maintainers:
* Kyle Kemp
* - Twitter: @seiyria
* - Github: seiyria
* Rohit Kalkur
* - Twitter: @Rovolutionary
* - Github: rovolution
*
* =========================================================
*
* bootstrap-slider is released under the MIT License
* Copyright (c) 2019 Kyle Kemp, Rohit Kalkur, and contributors
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* ========================================================= */
$slider-line-height: 20px !default;
$slider-border-radius: 4px !default;
$slider-horizontal-width: 210px !default;
$slider-vertical-height: 210px !default;
// Primary colors
$slider-primary: null !default;
@if variable-exists(brand-primary) {
$slider-primary: $brand-primary !default;
} @else {
$slider-primary: #0480BE !default;
}
$slider-primary-top: $slider-primary !default;
$slider-primary-bottom: darken($slider-primary, 5%) !default;
$slider-secondary-top: saturate(lighten($slider-primary, 28%), 20%) !default;
$slider-secondary-bottom: saturate(lighten($slider-primary, 23%), 2%) !default;
// grays for slider channel and disabled states
$slider-gray-1: #BEBEBE !default;
$slider-gray-2: #DFDFDF !default;
$slider-gray-3: #E5E5E5 !default;
$slider-gray-4: #E9E9E9 !default;
$slider-gray-5: #F5F5F5 !default;
$slider-gray-6: #F9F9F9 !default;
// unicode color for demo page
$slider-unicode-color: #726204 !default;
@mixin slider_background-image($colorstart:#F5F5F5, $colorend:#F9F9F9, $backcolor: #F7F7F7) {
background-color: $backcolor;
background-image: -moz-linear-gradient(top, $colorstart, $colorend);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from($colorstart), to($colorend));
background-image: -webkit-linear-gradient(top, $colorstart, $colorend);
background-image: -o-linear-gradient(top, $colorstart, $colorend);
background-image: linear-gradient(to bottom, $colorstart, $colorend);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{$colorstart}', endColorstr='#{$colorend}', GradientType=0);
}
@mixin slider_box-sizing($value) {
-webkit-box-sizing: $value;
-moz-box-sizing: $value;
box-sizing: $value;
}
@mixin slider_box-shadow($value...) {
-webkit-box-shadow: $value;
-moz-box-shadow: $value;
box-shadow: $value;
}
@mixin slider_border-radius($value) {
-webkit-border-radius: $value;
-moz-border-radius: $value;
border-radius: $value;
}
.slider {
display: inline-block;
vertical-align: middle;
position: relative;
&.slider-horizontal {
width: $slider-horizontal-width;
height: $slider-line-height;
.slider-track {
height: $slider-line-height/2;
width: 100%;
margin-top: -$slider-line-height/4;
top: 50%;
left: 0;
}
.slider-selection, .slider-track-low, .slider-track-high {
height: 100%;
top: 0;
bottom: 0;
}
.slider-tick,
.slider-handle {
margin-left: -$slider-line-height/2;
&.triangle {
position: relative;
top: 50%;
transform: translateY(-50%);
border-width: 0 $slider-line-height/2 $slider-line-height/2 $slider-line-height/2;
width: 0;
height: 0;
border-bottom-color: $slider-primary-bottom;
margin-top: 0;
}
}
.slider-tick-container {
white-space: nowrap;
position: absolute;
top: 0;
left: 0;
width: 100%;
}
.slider-tick-label-container {
white-space: nowrap;
margin-top: $slider-line-height;
.slider-tick-label {
display: inline-block;
padding-top: $slider-line-height * 1.2;
text-align: center;
}
}
&.slider-rtl {
.slider-track {
left: initial;
right: 0;
}
.slider-tick,
.slider-handle {
margin-left: initial;
margin-right: -$slider-line-height/2;
}
.slider-tick-container {
left: initial;
right: 0;
}
}
}
&.slider-vertical {
height: $slider-vertical-height;
width: $slider-line-height;
.slider-track {
width: $slider-line-height/2;
height: 100%;
left: 25%;
top: 0;
}
.slider-selection {
width: 100%;
left: 0;
top: 0;
bottom: 0;
}
.slider-track-low, .slider-track-high {
width: 100%;
left: 0;
right: 0;
}
.slider-tick,
.slider-handle {
margin-top: -$slider-line-height/2;
&.triangle {
border-width: $slider-line-height/2 0 $slider-line-height/2 $slider-line-height/2;
width: 1px;
height: 1px;
border-left-color: $slider-primary-bottom;
margin-left: 0;
}
}
.slider-tick-label-container {
white-space: nowrap;
.slider-tick-label {
padding-left: $slider-line-height * .2;
}
}
&.slider-rtl {
.slider-track {
left: initial;
right: 25%;
}
.slider-selection {
left: initial;
right: 0;
}
.slider-tick,
.slider-handle {
&.triangle {
border-width: $slider-line-height/2 $slider-line-height/2 $slider-line-height/2 0;
}
}
.slider-tick-label-container {
.slider-tick-label {
padding-left: initial;
padding-right: $slider-line-height * .2;
}
}
}
}
&.slider-disabled {
.slider-handle {
@include slider_background-image($slider-gray-2, $slider-gray-1, mix($slider-gray-2, $slider-gray-1));
}
.slider-track {
@include slider_background-image($slider-gray-3, $slider-gray-4, mix($slider-gray-3, $slider-gray-4));
cursor: not-allowed;
}
}
input {
display: none;
}
.tooltip-inner {
white-space: nowrap;
max-width: none;
}
.tooltip {
pointer-events: none;
&.top {
margin-top: -36px;
}
}
.tooltip-inner {
white-space: nowrap;
max-width: none;
}
.hide {
display: none;
}
}
.slider-track {
@include slider_background-image($slider-gray-5, $slider-gray-6, mix($slider-gray-5, $slider-gray-6));
@include slider_box-shadow(inset 0 1px 2px rgba(0, 0, 0, 0.1));
@include slider_border-radius($slider-border-radius);
position: absolute;
cursor: pointer;
}
.slider-selection {
@include slider_background-image($slider-gray-6, $slider-gray-5, mix($slider-gray-6, $slider-gray-5));
@include slider_box-shadow(inset 0 -1px 0 rgba(0, 0, 0, 0.15));
@include slider_box-sizing(border-box);
@include slider_border-radius($slider-border-radius);
position: absolute;
}
.slider-selection.tick-slider-selection {
@include slider_background-image($slider-secondary-top, $slider-secondary-bottom, mix($slider-secondary-top, $slider-secondary-bottom));
}
.slider-track-low, .slider-track-high {
@include slider_box-sizing(border-box);
@include slider_border-radius($slider-border-radius);
position: absolute;
background: transparent;
}
.slider-handle {
@include slider_background-image($slider-primary-top, $slider-primary-bottom, mix($slider-primary-top, $slider-primary-bottom));
@include slider_box-shadow(inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05));
position: absolute;
top: 0;
width: $slider-line-height;
height: $slider-line-height;
background-color: $slider-primary;
border: 0px solid transparent;
&:hover {
cursor: pointer;
}
&.round {
@include slider_border-radius($slider-line-height);
}
&.triangle {
background: transparent none;
}
&.custom {
background: transparent none;
&::before {
line-height: $slider-line-height;
font-size: 20px;
content: '\2605'; //unicode star character
color: $slider-unicode-color;
}
}
}
.slider-tick {
@include slider_background-image($slider-gray-5, $slider-gray-6, mix($slider-gray-5, $slider-gray-6));
@include slider_box-shadow(inset 0 -1px 0 rgba(0, 0, 0, 0.15));
@include slider_box-sizing(border-box);
position: absolute;
cursor: pointer;
width: $slider-line-height;
height: $slider-line-height;
filter: none;
opacity: 0.8;
border: 0px solid transparent;
&.round {
border-radius: 50%;
}
&.triangle {
background: transparent none;
}
&.custom {
background: transparent none;
&::before {
line-height: $slider-line-height;
font-size: 20px;
content: '\2605'; //unicode star character
color: $slider-unicode-color;
}
}
&.in-selection {
@include slider_background-image($slider-secondary-top, $slider-secondary-bottom, mix($slider-secondary-top, $slider-secondary-bottom));
opacity: 1;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -410,7 +410,7 @@ class OrdersTest(BaseOrdersTest):
'/%s/%s/order/%s/%s/cancel' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret)
)
assert response.status_code == 200
assert 'alert-warning' not in response.rendered_content
assert 'manually' not in response.rendered_content
response = self.client.post(
'/%s/%s/order/%s/%s/cancel/do' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), {
}, follow=True)
@@ -424,6 +424,58 @@ class OrdersTest(BaseOrdersTest):
with scopes_disabled():
assert self.order.refunds.count() == 1
def test_orders_cancel_paid_custom_fee_autorefund(self):
self.order.status = Order.STATUS_PAID
self.order.save()
with scopes_disabled():
self.order.payments.create(provider='testdummy_partialrefund', amount=self.order.total, state=OrderPayment.PAYMENT_STATE_CONFIRMED)
self.event.settings.cancel_allow_user_paid = True
self.event.settings.cancel_allow_user_paid_keep = Decimal('3.00')
self.event.settings.cancel_allow_user_paid_adjust_fees = True
response = self.client.get(
'/%s/%s/order/%s/%s/cancel' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret)
)
assert response.status_code == 200
response = self.client.post(
'/%s/%s/order/%s/%s/cancel/do' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), {
'cancel_fee': '6.00'
}, follow=True)
self.assertRedirects(response,
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code,
self.order.secret),
target_status_code=200)
self.order.refresh_from_db()
assert self.order.status == Order.STATUS_PAID
assert self.order.total == Decimal('6.00')
with scopes_disabled():
assert self.order.refunds.count() == 1
def test_orders_cancel_paid_custom_fee_limit(self):
self.order.status = Order.STATUS_PAID
self.order.save()
with scopes_disabled():
self.order.payments.create(provider='testdummy_partialrefund', amount=self.order.total, state=OrderPayment.PAYMENT_STATE_CONFIRMED)
self.event.settings.cancel_allow_user_paid = True
self.event.settings.cancel_allow_user_paid_keep = Decimal('3.00')
self.event.settings.cancel_allow_user_paid_adjust_fees = True
response = self.client.get(
'/%s/%s/order/%s/%s/cancel' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret)
)
assert response.status_code == 200
response = self.client.post(
'/%s/%s/order/%s/%s/cancel/do' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), {
'cancel_fee': '2.00'
}, follow=True)
self.assertRedirects(response,
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code,
self.order.secret),
target_status_code=200)
self.order.refresh_from_db()
assert self.order.status == Order.STATUS_PAID
assert self.order.total == Decimal('23.00')
with scopes_disabled():
assert self.order.refunds.count() == 0
def test_orders_cancel_paid_fee_no_autorefund(self):
self.order.status = Order.STATUS_PAID
self.order.save()
@@ -436,13 +488,12 @@ class OrdersTest(BaseOrdersTest):
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret)
)
assert response.status_code == 200
print(response.rendered_content)
assert 'cancellation fee of <strong>€3.00</strong>' in response.rendered_content
response = self.client.get(
'/%s/%s/order/%s/%s/cancel' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret)
)
assert response.status_code == 200
assert 'alert-warning' in response.rendered_content
assert 'manually' in response.rendered_content
assert '20.00' in response.rendered_content
response = self.client.post(
'/%s/%s/order/%s/%s/cancel/do' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), {