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:: shred_payment_info
|
||||||
|
|
||||||
|
.. automethod:: cancel_payment
|
||||||
|
|
||||||
.. autoattribute:: is_implicit
|
.. autoattribute:: is_implicit
|
||||||
|
|
||||||
.. autoattribute:: is_meta
|
.. autoattribute:: is_meta
|
||||||
|
|||||||
@@ -173,9 +173,26 @@ class OrderViewSet(viewsets.ModelViewSet):
|
|||||||
amount=ps
|
amount=ps
|
||||||
)
|
)
|
||||||
except OrderPayment.DoesNotExist:
|
except OrderPayment.DoesNotExist:
|
||||||
order.payments.filter(state__in=(OrderPayment.PAYMENT_STATE_PENDING,
|
for p in order.payments.filter(state__in=(OrderPayment.PAYMENT_STATE_PENDING,
|
||||||
OrderPayment.PAYMENT_STATE_CREATED)) \
|
OrderPayment.PAYMENT_STATE_CREATED)):
|
||||||
.update(state=OrderPayment.PAYMENT_STATE_CANCELED)
|
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(
|
p = order.payments.create(
|
||||||
state=OrderPayment.PAYMENT_STATE_CREATED,
|
state=OrderPayment.PAYMENT_STATE_CREATED,
|
||||||
provider='manual',
|
provider='manual',
|
||||||
@@ -896,13 +913,16 @@ class PaymentViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
if payment.state not in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED):
|
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)
|
return Response({'detail': 'Invalid state of payment'}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
with transaction.atomic():
|
try:
|
||||||
payment.state = OrderPayment.PAYMENT_STATE_CANCELED
|
with transaction.atomic():
|
||||||
payment.save()
|
payment.payment_provider.cancel_payment(payment)
|
||||||
payment.order.log_action('pretix.event.order.payment.canceled', {
|
payment.order.log_action('pretix.event.order.payment.canceled', {
|
||||||
'local_id': payment.local_id,
|
'local_id': payment.local_id,
|
||||||
'provider': payment.provider,
|
'provider': payment.provider,
|
||||||
}, user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth)
|
}, 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)
|
return self.retrieve(request, [], **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -654,6 +654,17 @@ class BasePaymentProvider:
|
|||||||
"""
|
"""
|
||||||
return False
|
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):
|
def execute_refund(self, refund: OrderRefund):
|
||||||
"""
|
"""
|
||||||
Will be called to execute an refund. Note that refunds have an amount property and can be partial.
|
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.status = Order.STATUS_PAID
|
||||||
self.order.save()
|
self.order.save()
|
||||||
elif self.open_payment:
|
elif self.open_payment:
|
||||||
self.open_payment.state = OrderPayment.PAYMENT_STATE_CANCELED
|
try:
|
||||||
self.open_payment.save()
|
with transaction.atomic():
|
||||||
self.order.log_action('pretix.event.order.payment.canceled', {
|
self.open_payment.payment_provider.cancel_payment(self.open_payment)
|
||||||
'local_id': self.open_payment.local_id,
|
self.order.log_action(
|
||||||
'provider': self.open_payment.provider,
|
'pretix.event.order.payment.canceled',
|
||||||
}, user=self.user, auth=self.auth)
|
{
|
||||||
|
'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:
|
elif self.order.status in (Order.STATUS_PENDING, Order.STATUS_EXPIRED) and self._totaldiff > 0:
|
||||||
if self.open_payment:
|
if self.open_payment:
|
||||||
self.open_payment.state = OrderPayment.PAYMENT_STATE_CANCELED
|
try:
|
||||||
self.open_payment.save()
|
with transaction.atomic():
|
||||||
self.order.log_action('pretix.event.order.payment.canceled', {
|
self.open_payment.payment_provider.cancel_payment(self.open_payment)
|
||||||
'local_id': self.open_payment.local_id,
|
self.order.log_action('pretix.event.order.payment.canceled', {
|
||||||
'provider': self.open_payment.provider,
|
'local_id': self.open_payment.local_id,
|
||||||
}, user=self.user, auth=self.auth)
|
'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):
|
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:
|
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,
|
if open_payment and open_payment.state in (OrderPayment.PAYMENT_STATE_PENDING,
|
||||||
OrderPayment.PAYMENT_STATE_CREATED):
|
OrderPayment.PAYMENT_STATE_CREATED):
|
||||||
open_payment.state = OrderPayment.PAYMENT_STATE_CANCELED
|
try:
|
||||||
open_payment.save(update_fields=['state'])
|
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.total = (order.positions.aggregate(sum=Sum('price'))['sum'] or 0) + (order.fees.aggregate(sum=Sum('value'))['sum'] or 0)
|
||||||
order.save(update_fields=['total'])
|
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.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.confirmed': _('Payment {local_id} has been confirmed.'),
|
||||||
'pretix.event.order.payment.canceled': _('Payment {local_id} has been canceled.'),
|
'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.started': _('Payment {local_id} has been started.'),
|
||||||
'pretix.event.order.payment.failed': _('Payment {local_id} has failed.'),
|
'pretix.event.order.payment.failed': _('Payment {local_id} has failed.'),
|
||||||
'pretix.event.order.quotaexceeded': _('The order could not be marked as paid: {message}'),
|
'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):
|
def post(self, *args, **kwargs):
|
||||||
if self.payment.state in (OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_PENDING):
|
if self.payment.state in (OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_PENDING):
|
||||||
with transaction.atomic():
|
try:
|
||||||
self.payment.state = OrderPayment.PAYMENT_STATE_CANCELED
|
with transaction.atomic():
|
||||||
self.payment.save()
|
self.payment.payment_provider.cancel_payment(self.payment)
|
||||||
self.order.log_action('pretix.event.order.payment.canceled', {
|
self.order.log_action('pretix.event.order.payment.canceled', {
|
||||||
'local_id': self.payment.local_id,
|
'local_id': self.payment.local_id,
|
||||||
'provider': self.payment.provider,
|
'provider': self.payment.provider,
|
||||||
}, user=self.request.user)
|
}, user=self.request.user if self.request.user.is_authenticated else None)
|
||||||
messages.success(self.request, _('This payment has been canceled.'))
|
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:
|
else:
|
||||||
messages.error(self.request, _('This payment can not be canceled at the moment.'))
|
messages.error(self.request, _('This payment can not be canceled at the moment.'))
|
||||||
return redirect(self.get_order_url())
|
return redirect(self.get_order_url())
|
||||||
@@ -859,9 +871,25 @@ class OrderTransition(OrderView):
|
|||||||
amount=ps
|
amount=ps
|
||||||
)
|
)
|
||||||
except OrderPayment.DoesNotExist:
|
except OrderPayment.DoesNotExist:
|
||||||
self.order.payments.filter(state__in=(OrderPayment.PAYMENT_STATE_PENDING,
|
for p in self.order.payments.filter(state__in=(OrderPayment.PAYMENT_STATE_PENDING,
|
||||||
OrderPayment.PAYMENT_STATE_CREATED)) \
|
OrderPayment.PAYMENT_STATE_CREATED)):
|
||||||
.update(state=OrderPayment.PAYMENT_STATE_CANCELED)
|
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(
|
p = self.order.payments.create(
|
||||||
state=OrderPayment.PAYMENT_STATE_CREATED,
|
state=OrderPayment.PAYMENT_STATE_CREATED,
|
||||||
provider='manual',
|
provider='manual',
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from django_scopes import scope, scopes_disabled
|
|||||||
from pretix.base.email import get_email_context
|
from pretix.base.email import get_email_context
|
||||||
from pretix.base.i18n import language
|
from pretix.base.i18n import language
|
||||||
from pretix.base.models import Event, Order, OrderPayment, Organizer, Quota
|
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.locking import LockTimeoutException
|
||||||
from pretix.base.services.mail import SendMailException
|
from pretix.base.services.mail import SendMailException
|
||||||
from pretix.base.services.orders import change_payment_provider
|
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')
|
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
|
@transaction.atomic
|
||||||
def _handle_transaction(trans: BankTransaction, code: str, event: Event=None, organizer: Organizer=None,
|
def _handle_transaction(trans: BankTransaction, code: str, event: Event=None, organizer: Organizer=None,
|
||||||
slug: str=None):
|
slug: str=None):
|
||||||
@@ -109,22 +134,13 @@ def _handle_transaction(trans: BankTransaction, code: str, event: Event=None, or
|
|||||||
p.confirm()
|
p.confirm()
|
||||||
except Quota.QuotaExceededException:
|
except Quota.QuotaExceededException:
|
||||||
trans.state = BankTransaction.STATE_VALID
|
trans.state = BankTransaction.STATE_VALID
|
||||||
trans.order.payments.filter(
|
cancel_old_payments(trans.order)
|
||||||
provider='banktransfer',
|
|
||||||
state__in=(OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_PENDING),
|
|
||||||
).update(state=OrderPayment.PAYMENT_STATE_CANCELED)
|
|
||||||
except SendMailException:
|
except SendMailException:
|
||||||
trans.state = BankTransaction.STATE_VALID
|
trans.state = BankTransaction.STATE_VALID
|
||||||
trans.order.payments.filter(
|
cancel_old_payments(trans.order)
|
||||||
provider='banktransfer',
|
|
||||||
state__in=(OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_PENDING),
|
|
||||||
).update(state=OrderPayment.PAYMENT_STATE_CANCELED)
|
|
||||||
else:
|
else:
|
||||||
trans.state = BankTransaction.STATE_VALID
|
trans.state = BankTransaction.STATE_VALID
|
||||||
trans.order.payments.filter(
|
cancel_old_payments(trans.order)
|
||||||
provider='banktransfer',
|
|
||||||
state__in=(OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_PENDING),
|
|
||||||
).update(state=OrderPayment.PAYMENT_STATE_CANCELED)
|
|
||||||
|
|
||||||
o = trans.order
|
o = trans.order
|
||||||
o.refresh_from_db()
|
o.refresh_from_db()
|
||||||
|
|||||||
Reference in New Issue
Block a user