forked from CGM_Public/pretix_original
Payment provider API: Add method cancel_payment
This commit is contained in:
@@ -112,6 +112,8 @@ The provider class
|
||||
|
||||
.. automethod:: shred_payment_info
|
||||
|
||||
.. automethod:: cancel_payment
|
||||
|
||||
.. autoattribute:: is_implicit
|
||||
|
||||
.. autoattribute:: is_meta
|
||||
|
||||
@@ -173,9 +173,26 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
amount=ps
|
||||
)
|
||||
except OrderPayment.DoesNotExist:
|
||||
order.payments.filter(state__in=(OrderPayment.PAYMENT_STATE_PENDING,
|
||||
OrderPayment.PAYMENT_STATE_CREATED)) \
|
||||
.update(state=OrderPayment.PAYMENT_STATE_CANCELED)
|
||||
for p in order.payments.filter(state__in=(OrderPayment.PAYMENT_STATE_PENDING,
|
||||
OrderPayment.PAYMENT_STATE_CREATED)):
|
||||
try:
|
||||
with transaction.atomic():
|
||||
p.payment_provider.cancel_payment(p)
|
||||
order.log_action('pretix.event.order.payment.canceled', {
|
||||
'local_id': p.local_id,
|
||||
'provider': p.provider,
|
||||
}, user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth)
|
||||
except PaymentException as e:
|
||||
order.log_action(
|
||||
'pretix.event.order.payment.canceled.failed',
|
||||
{
|
||||
'local_id': p.local_id,
|
||||
'provider': p.provider,
|
||||
'error': str(e)
|
||||
},
|
||||
user=self.request.user if self.request.user.is_authenticated else None,
|
||||
auth=self.request.auth
|
||||
)
|
||||
p = order.payments.create(
|
||||
state=OrderPayment.PAYMENT_STATE_CREATED,
|
||||
provider='manual',
|
||||
@@ -896,13 +913,16 @@ class PaymentViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
if payment.state not in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED):
|
||||
return Response({'detail': 'Invalid state of payment'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
with transaction.atomic():
|
||||
payment.state = OrderPayment.PAYMENT_STATE_CANCELED
|
||||
payment.save()
|
||||
payment.order.log_action('pretix.event.order.payment.canceled', {
|
||||
'local_id': payment.local_id,
|
||||
'provider': payment.provider,
|
||||
}, user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth)
|
||||
try:
|
||||
with transaction.atomic():
|
||||
payment.payment_provider.cancel_payment(payment)
|
||||
payment.order.log_action('pretix.event.order.payment.canceled', {
|
||||
'local_id': payment.local_id,
|
||||
'provider': payment.provider,
|
||||
}, user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth)
|
||||
except PaymentException as e:
|
||||
return Response({'detail': 'External error: {}'.format(str(e))},
|
||||
status=status.HTTP_400_BAD_REQUEST)
|
||||
return self.retrieve(request, [], **kwargs)
|
||||
|
||||
|
||||
|
||||
@@ -654,6 +654,17 @@ class BasePaymentProvider:
|
||||
"""
|
||||
return False
|
||||
|
||||
def cancel_payment(self, payment: OrderPayment):
|
||||
"""
|
||||
Will be called to cancel a payment. The default implementation just sets the payment state to canceled,
|
||||
but in some cases you might want to notify an external provider.
|
||||
|
||||
On success, you should set ``payment.state = OrderPayment.PAYMENT_STATE_CANCELED`` (or call the super method).
|
||||
On failure, you should raise a PaymentException.
|
||||
"""
|
||||
payment.state = OrderPayment.PAYMENT_STATE_CANCELED
|
||||
payment.save()
|
||||
|
||||
def execute_refund(self, refund: OrderRefund):
|
||||
"""
|
||||
Will be called to execute an refund. Note that refunds have an amount property and can be partial.
|
||||
|
||||
@@ -1183,20 +1183,49 @@ class OrderChangeManager:
|
||||
self.order.status = Order.STATUS_PAID
|
||||
self.order.save()
|
||||
elif self.open_payment:
|
||||
self.open_payment.state = OrderPayment.PAYMENT_STATE_CANCELED
|
||||
self.open_payment.save()
|
||||
self.order.log_action('pretix.event.order.payment.canceled', {
|
||||
'local_id': self.open_payment.local_id,
|
||||
'provider': self.open_payment.provider,
|
||||
}, user=self.user, auth=self.auth)
|
||||
try:
|
||||
with transaction.atomic():
|
||||
self.open_payment.payment_provider.cancel_payment(self.open_payment)
|
||||
self.order.log_action(
|
||||
'pretix.event.order.payment.canceled',
|
||||
{
|
||||
'local_id': self.open_payment.local_id,
|
||||
'provider': self.open_payment.provider,
|
||||
},
|
||||
user=self.user,
|
||||
auth=self.auth
|
||||
)
|
||||
except PaymentException as e:
|
||||
self.order.log_action(
|
||||
'pretix.event.order.payment.canceled.failed',
|
||||
{
|
||||
'local_id': self.open_payment.local_id,
|
||||
'provider': self.open_payment.provider,
|
||||
'error': str(e)
|
||||
},
|
||||
user=self.user,
|
||||
auth=self.auth
|
||||
)
|
||||
elif self.order.status in (Order.STATUS_PENDING, Order.STATUS_EXPIRED) and self._totaldiff > 0:
|
||||
if self.open_payment:
|
||||
self.open_payment.state = OrderPayment.PAYMENT_STATE_CANCELED
|
||||
self.open_payment.save()
|
||||
self.order.log_action('pretix.event.order.payment.canceled', {
|
||||
'local_id': self.open_payment.local_id,
|
||||
'provider': self.open_payment.provider,
|
||||
}, user=self.user, auth=self.auth)
|
||||
try:
|
||||
with transaction.atomic():
|
||||
self.open_payment.payment_provider.cancel_payment(self.open_payment)
|
||||
self.order.log_action('pretix.event.order.payment.canceled', {
|
||||
'local_id': self.open_payment.local_id,
|
||||
'provider': self.open_payment.provider,
|
||||
}, user=self.user, auth=self.auth)
|
||||
except PaymentException as e:
|
||||
self.order.log_action(
|
||||
'pretix.event.order.payment.canceled.failed',
|
||||
{
|
||||
'local_id': self.open_payment.local_id,
|
||||
'provider': self.open_payment.provider,
|
||||
'error': str(e)
|
||||
},
|
||||
user=self.user,
|
||||
auth=self.auth,
|
||||
)
|
||||
|
||||
def _check_paid_to_free(self):
|
||||
if self.order.total == 0 and (self._totaldiff < 0 or (self.split_order and self.split_order.total > 0)) and not self.order.require_approval:
|
||||
@@ -1726,8 +1755,22 @@ def change_payment_provider(order: Order, payment_provider, amount=None, new_pay
|
||||
|
||||
if open_payment and open_payment.state in (OrderPayment.PAYMENT_STATE_PENDING,
|
||||
OrderPayment.PAYMENT_STATE_CREATED):
|
||||
open_payment.state = OrderPayment.PAYMENT_STATE_CANCELED
|
||||
open_payment.save(update_fields=['state'])
|
||||
try:
|
||||
with transaction.atomic():
|
||||
open_payment.payment_provider.cancel_payment(open_payment)
|
||||
order.log_action('pretix.event.order.payment.canceled', {
|
||||
'local_id': open_payment.local_id,
|
||||
'provider': open_payment.provider,
|
||||
})
|
||||
except PaymentException as e:
|
||||
order.log_action(
|
||||
'pretix.event.order.payment.canceled.failed',
|
||||
{
|
||||
'local_id': open_payment.local_id,
|
||||
'provider': open_payment.provider,
|
||||
'error': str(e)
|
||||
},
|
||||
)
|
||||
|
||||
order.total = (order.positions.aggregate(sum=Sum('price'))['sum'] or 0) + (order.fees.aggregate(sum=Sum('value'))['sum'] or 0)
|
||||
order.save(update_fields=['total'])
|
||||
|
||||
@@ -225,6 +225,7 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
||||
'pretix.event.order.email.resend': _('An email with a link to the order detail page has been resent to the user.'),
|
||||
'pretix.event.order.payment.confirmed': _('Payment {local_id} has been confirmed.'),
|
||||
'pretix.event.order.payment.canceled': _('Payment {local_id} has been canceled.'),
|
||||
'pretix.event.order.payment.canceled.failed': _('Cancelling payment {local_id} has failed.'),
|
||||
'pretix.event.order.payment.started': _('Payment {local_id} has been started.'),
|
||||
'pretix.event.order.payment.failed': _('Payment {local_id} has failed.'),
|
||||
'pretix.event.order.quotaexceeded': _('The order could not be marked as paid: {message}'),
|
||||
|
||||
@@ -480,14 +480,26 @@ class OrderPaymentCancel(OrderView):
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
if self.payment.state in (OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_PENDING):
|
||||
with transaction.atomic():
|
||||
self.payment.state = OrderPayment.PAYMENT_STATE_CANCELED
|
||||
self.payment.save()
|
||||
self.order.log_action('pretix.event.order.payment.canceled', {
|
||||
'local_id': self.payment.local_id,
|
||||
'provider': self.payment.provider,
|
||||
}, user=self.request.user)
|
||||
messages.success(self.request, _('This payment has been canceled.'))
|
||||
try:
|
||||
with transaction.atomic():
|
||||
self.payment.payment_provider.cancel_payment(self.payment)
|
||||
self.order.log_action('pretix.event.order.payment.canceled', {
|
||||
'local_id': self.payment.local_id,
|
||||
'provider': self.payment.provider,
|
||||
}, user=self.request.user if self.request.user.is_authenticated else None)
|
||||
except PaymentException as e:
|
||||
self.order.log_action(
|
||||
'pretix.event.order.payment.canceled.failed',
|
||||
{
|
||||
'local_id': self.payment.local_id,
|
||||
'provider': self.payment.provider,
|
||||
'error': str(e)
|
||||
},
|
||||
user=self.request.user if self.request.user.is_authenticated else None,
|
||||
)
|
||||
messages.error(self.request, str(e))
|
||||
else:
|
||||
messages.success(self.request, _('This payment has been canceled.'))
|
||||
else:
|
||||
messages.error(self.request, _('This payment can not be canceled at the moment.'))
|
||||
return redirect(self.get_order_url())
|
||||
@@ -859,9 +871,25 @@ class OrderTransition(OrderView):
|
||||
amount=ps
|
||||
)
|
||||
except OrderPayment.DoesNotExist:
|
||||
self.order.payments.filter(state__in=(OrderPayment.PAYMENT_STATE_PENDING,
|
||||
OrderPayment.PAYMENT_STATE_CREATED)) \
|
||||
.update(state=OrderPayment.PAYMENT_STATE_CANCELED)
|
||||
for p in self.order.payments.filter(state__in=(OrderPayment.PAYMENT_STATE_PENDING,
|
||||
OrderPayment.PAYMENT_STATE_CREATED)):
|
||||
try:
|
||||
with transaction.atomic():
|
||||
p.payment_provider.cancel_payment(p)
|
||||
self.order.log_action('pretix.event.order.payment.canceled', {
|
||||
'local_id': p.local_id,
|
||||
'provider': p.provider,
|
||||
}, user=self.request.user if self.request.user.is_authenticated else None)
|
||||
except PaymentException as e:
|
||||
self.order.log_action(
|
||||
'pretix.event.order.payment.canceled.failed',
|
||||
{
|
||||
'local_id': p.local_id,
|
||||
'provider': p.provider,
|
||||
'error': str(e)
|
||||
},
|
||||
user=self.request.user if self.request.user.is_authenticated else None,
|
||||
)
|
||||
p = self.order.payments.create(
|
||||
state=OrderPayment.PAYMENT_STATE_CREATED,
|
||||
provider='manual',
|
||||
|
||||
@@ -12,6 +12,7 @@ from django_scopes import scope, scopes_disabled
|
||||
from pretix.base.email import get_email_context
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import Event, Order, OrderPayment, Organizer, Quota
|
||||
from pretix.base.payment import PaymentException
|
||||
from pretix.base.services.locking import LockTimeoutException
|
||||
from pretix.base.services.mail import SendMailException
|
||||
from pretix.base.services.orders import change_payment_provider
|
||||
@@ -38,6 +39,30 @@ def notify_incomplete_payment(o: Order):
|
||||
logger.exception('Reminder email could not be sent')
|
||||
|
||||
|
||||
def cancel_old_payments(order):
|
||||
for p in order.payments.filter(
|
||||
state__in=(OrderPayment.PAYMENT_STATE_PENDING,
|
||||
OrderPayment.PAYMENT_STATE_CREATED),
|
||||
provider='banktransfer',
|
||||
):
|
||||
try:
|
||||
with transaction.atomic():
|
||||
p.payment_provider.cancel_payment(p)
|
||||
order.log_action('pretix.event.order.payment.canceled', {
|
||||
'local_id': p.local_id,
|
||||
'provider': p.provider,
|
||||
})
|
||||
except PaymentException as e:
|
||||
order.log_action(
|
||||
'pretix.event.order.payment.canceled.failed',
|
||||
{
|
||||
'local_id': p.local_id,
|
||||
'provider': p.provider,
|
||||
'error': str(e)
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def _handle_transaction(trans: BankTransaction, code: str, event: Event=None, organizer: Organizer=None,
|
||||
slug: str=None):
|
||||
@@ -109,22 +134,13 @@ def _handle_transaction(trans: BankTransaction, code: str, event: Event=None, or
|
||||
p.confirm()
|
||||
except Quota.QuotaExceededException:
|
||||
trans.state = BankTransaction.STATE_VALID
|
||||
trans.order.payments.filter(
|
||||
provider='banktransfer',
|
||||
state__in=(OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_PENDING),
|
||||
).update(state=OrderPayment.PAYMENT_STATE_CANCELED)
|
||||
cancel_old_payments(trans.order)
|
||||
except SendMailException:
|
||||
trans.state = BankTransaction.STATE_VALID
|
||||
trans.order.payments.filter(
|
||||
provider='banktransfer',
|
||||
state__in=(OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_PENDING),
|
||||
).update(state=OrderPayment.PAYMENT_STATE_CANCELED)
|
||||
cancel_old_payments(trans.order)
|
||||
else:
|
||||
trans.state = BankTransaction.STATE_VALID
|
||||
trans.order.payments.filter(
|
||||
provider='banktransfer',
|
||||
state__in=(OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_PENDING),
|
||||
).update(state=OrderPayment.PAYMENT_STATE_CANCELED)
|
||||
cancel_old_payments(trans.order)
|
||||
|
||||
o = trans.order
|
||||
o.refresh_from_db()
|
||||
|
||||
Reference in New Issue
Block a user