mirror of
https://github.com/pretix/pretix.git
synced 2026-08-05 09:57:49 +00:00
API: Allow to create payments directly
This commit is contained in:
@@ -1621,6 +1621,10 @@ Order payment endpoints
|
||||
|
||||
These endpoints have been added.
|
||||
|
||||
.. versionchanged:: 3.6
|
||||
|
||||
Payments can now be created through the API.
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/payments/
|
||||
|
||||
Returns a list of all payments for an order.
|
||||
@@ -1829,6 +1833,61 @@ Order payment endpoints
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||
:statuscode 404: The requested order or payment does not exist.
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/orders/(code)/payments/
|
||||
|
||||
Creates a new payment.
|
||||
|
||||
Be careful with the ``info`` parameter: You can pass a nested JSON object that will be set as the internal ``info``
|
||||
value of the payment object that will be created. How this value is handled is up to the payment provider and you
|
||||
should only use this if you know the specific payment provider in detail. Please keep in mind that the payment
|
||||
provider will not be called to do anything about this (i.e. if you pass a bank account to a debit provider, *no*
|
||||
charge will be created), this is just informative in case you *handled the payment already*.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/v1/organizers/bigevents/events/sampleconf/orders/ABC12/payments/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"state": "confirmed",
|
||||
"amount": "23.00",
|
||||
"payment_date": "2017-12-04T12:13:12Z",
|
||||
"info": {},
|
||||
"provider": "banktransfer"
|
||||
}
|
||||
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 201 Created
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"local_id": 1,
|
||||
"state": "confirmed",
|
||||
"amount": "23.00",
|
||||
"created": "2017-12-01T10:00:00Z",
|
||||
"payment_date": "2017-12-04T12:13:12Z",
|
||||
"payment_url": null,
|
||||
"details": {},
|
||||
"provider": "banktransfer"
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to access
|
||||
:param event: The ``slug`` field of the event to access
|
||||
:param order: The ``code`` field of the order to access
|
||||
:statuscode 201: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
|
||||
:statuscode 404: The requested order does not exist.
|
||||
|
||||
|
||||
Order refund endpoints
|
||||
----------------------
|
||||
|
||||
@@ -1034,6 +1034,20 @@ class InvoiceSerializer(I18nAwareModelSerializer):
|
||||
'internal_reference')
|
||||
|
||||
|
||||
class OrderPaymentCreateSerializer(I18nAwareModelSerializer):
|
||||
provider = serializers.CharField(required=True, allow_null=False, allow_blank=False)
|
||||
info = CompatibleJSONField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = OrderPayment
|
||||
fields = ('state', 'amount', 'payment_date', 'provider', 'info')
|
||||
|
||||
def create(self, validated_data):
|
||||
order = OrderPayment(order=self.context['order'], **validated_data)
|
||||
order.save()
|
||||
return order
|
||||
|
||||
|
||||
class OrderRefundCreateSerializer(I18nAwareModelSerializer):
|
||||
payment = serializers.IntegerField(required=False, allow_null=True)
|
||||
provider = serializers.CharField(required=True, allow_null=False, allow_blank=False)
|
||||
|
||||
@@ -23,9 +23,10 @@ from rest_framework.response import Response
|
||||
|
||||
from pretix.api.models import OAuthAccessToken
|
||||
from pretix.api.serializers.order import (
|
||||
InvoiceSerializer, OrderCreateSerializer, OrderPaymentSerializer,
|
||||
OrderPositionSerializer, OrderRefundCreateSerializer,
|
||||
OrderRefundSerializer, OrderSerializer, PriceCalcSerializer,
|
||||
InvoiceSerializer, OrderCreateSerializer, OrderPaymentCreateSerializer,
|
||||
OrderPaymentSerializer, OrderPositionSerializer,
|
||||
OrderRefundCreateSerializer, OrderRefundSerializer, OrderSerializer,
|
||||
PriceCalcSerializer,
|
||||
)
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import (
|
||||
@@ -825,17 +826,62 @@ class OrderPositionViewSet(mixins.DestroyModelMixin, viewsets.ReadOnlyModelViewS
|
||||
raise ValidationError(str(e))
|
||||
|
||||
|
||||
class PaymentViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
|
||||
serializer_class = OrderPaymentSerializer
|
||||
queryset = OrderPayment.objects.none()
|
||||
permission = 'can_view_orders'
|
||||
write_permission = 'can_change_orders'
|
||||
lookup_field = 'local_id'
|
||||
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
ctx['order'] = get_object_or_404(Order, code=self.kwargs['order'], event=self.request.event)
|
||||
return ctx
|
||||
|
||||
def get_queryset(self):
|
||||
order = get_object_or_404(Order, code=self.kwargs['order'], event=self.request.event)
|
||||
return order.payments.all()
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = OrderPaymentCreateSerializer(data=request.data, context=self.get_serializer_context())
|
||||
serializer.is_valid(raise_exception=True)
|
||||
with transaction.atomic():
|
||||
mark_confirmed = False
|
||||
if serializer.validated_data['state'] == OrderPayment.PAYMENT_STATE_CONFIRMED:
|
||||
serializer.validated_data['state'] = OrderPayment.PAYMENT_STATE_PENDING
|
||||
mark_confirmed = True
|
||||
self.perform_create(serializer)
|
||||
r = serializer.instance
|
||||
if mark_confirmed:
|
||||
try:
|
||||
r.confirm(
|
||||
user=self.request.user if self.request.user.is_authenticated else None,
|
||||
auth=self.request.auth,
|
||||
count_waitinglist=False,
|
||||
force=request.data.get('force', False)
|
||||
)
|
||||
except Quota.QuotaExceededException:
|
||||
pass
|
||||
except SendMailException:
|
||||
pass
|
||||
|
||||
serializer = OrderPaymentSerializer(r, context=serializer.context)
|
||||
|
||||
r.order.log_action(
|
||||
'pretix.event.order.payment.started', {
|
||||
'local_id': r.local_id,
|
||||
'provider': r.provider,
|
||||
},
|
||||
user=request.user if request.user.is_authenticated else None,
|
||||
auth=request.auth
|
||||
)
|
||||
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save()
|
||||
|
||||
@action(detail=True, methods=['POST'])
|
||||
def confirm(self, request, **kwargs):
|
||||
payment = self.get_object()
|
||||
|
||||
@@ -401,6 +401,48 @@ def test_payment_detail(token_client, organizer, event, order):
|
||||
assert TEST_PAYMENTS_RES[0] == resp.data
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_payment_create_confirmed(token_client, organizer, event, order):
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
), format='json', data={
|
||||
'provider': 'banktransfer',
|
||||
'state': 'confirmed',
|
||||
'amount': order.total,
|
||||
'info': {
|
||||
'foo': 'bar'
|
||||
}
|
||||
})
|
||||
with scopes_disabled():
|
||||
p = order.payments.last()
|
||||
assert resp.status_code == 201
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_CONFIRMED
|
||||
assert p.info_data == {'foo': 'bar'}
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_PAID
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_payment_create_pending(token_client, organizer, event, order):
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/'.format(
|
||||
organizer.slug, event.slug, order.code
|
||||
), format='json', data={
|
||||
'provider': 'banktransfer',
|
||||
'state': 'pending',
|
||||
'amount': order.total,
|
||||
'info': {
|
||||
'foo': 'bar'
|
||||
}
|
||||
})
|
||||
with scopes_disabled():
|
||||
p = order.payments.last()
|
||||
assert resp.status_code == 201
|
||||
assert p.state == OrderPayment.PAYMENT_STATE_PENDING
|
||||
assert p.info_data == {'foo': 'bar'}
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_PENDING
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_payment_confirm(token_client, organizer, event, order):
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/orders/{}/payments/2/confirm/'.format(
|
||||
|
||||
Reference in New Issue
Block a user