def create(self, request, *args, **kwargs): if 'mark_refunded' in request.data: mark_refunded = request.data.pop('mark_refunded', False) else: mark_refunded = request.data.pop('mark_canceled', False) serializer = OrderRefundCreateSerializer( data=request.data, context=self.get_serializer_context()) serializer.is_valid(raise_exception=True) with transaction.atomic(): self.perform_create(serializer) r = serializer.instance serializer = OrderRefundSerializer(r, context=serializer.context) r.order.log_action( 'pretix.event.order.refund.created', { 'local_id': r.local_id, 'provider': r.provider, }, user=request.user if request.user.is_authenticated else None, auth=request.auth) if mark_refunded: mark_order_refunded( r.order, user=request.user if request.user.is_authenticated else None, auth=(request.auth if request.auth else None), ) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def refund(self, request, **kwargs): payment = self.get_object() amount = serializers.DecimalField(max_digits=10, decimal_places=2).to_internal_value( request.data.get('amount', str(payment.amount)) ) mark_refunded = request.data.get('mark_refunded', False) if payment.state != OrderPayment.PAYMENT_STATE_CONFIRMED: return Response({'detail': 'Invalid state of payment.'}, status=status.HTTP_400_BAD_REQUEST) full_refund_possible = payment.payment_provider.payment_refund_supported(payment) partial_refund_possible = payment.payment_provider.payment_partial_refund_supported(payment) available_amount = payment.amount - payment.refunded_amount if amount <= 0: return Response({'amount': ['Invalid refund amount.']}, status=status.HTTP_400_BAD_REQUEST) if amount > available_amount: return Response( {'amount': ['Invalid refund amount, only {} are available to refund.'.format(available_amount)]}, status=status.HTTP_400_BAD_REQUEST) if amount != payment.amount and not partial_refund_possible: return Response({'amount': ['Partial refund not available for this payment method.']}, status=status.HTTP_400_BAD_REQUEST) if amount == payment.amount and not full_refund_possible: return Response({'amount': ['Full refund not available for this payment method.']}, status=status.HTTP_400_BAD_REQUEST) r = payment.order.refunds.create( payment=payment, source=OrderRefund.REFUND_SOURCE_ADMIN, state=OrderRefund.REFUND_STATE_CREATED, amount=amount, provider=payment.provider ) try: r.payment_provider.execute_refund(r) except PaymentException as e: r.state = OrderRefund.REFUND_STATE_FAILED r.save() return Response({'detail': 'External error: {}'.format(str(e))}, status=status.HTTP_400_BAD_REQUEST) else: payment.order.log_action('pretix.event.order.refund.created', { 'local_id': r.local_id, 'provider': r.provider, }, user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth) if payment.order.pending_sum > 0: if mark_refunded: mark_order_refunded(payment.order, user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth) else: payment.order.status = Order.STATUS_PENDING payment.order.set_expires( now(), payment.order.event.subevents.filter( id__in=payment.order.positions.values_list('subevent_id', flat=True)) ) payment.order.save(update_fields=['status', 'expires']) return Response(OrderRefundSerializer(r).data, status=status.HTTP_200_OK)
def order_control_refund_perform(self, request: HttpRequest, order: Order) -> Union[bool, str]: """ Will be called if the event administrator confirms the refund. This should transfer the money back (if possible). You can return the URL the user should be redirected to if you need special behaviour or None to continue with default behaviour. On failure, you should use Django's message framework to display an error message to the user. The default implementation sets the Order's state to refunded and shows a success message. :param request: The HTTP request :param order: The order object """ from pretix.base.services.orders import mark_order_refunded mark_order_refunded(order, user=request.user) messages.success( request, _('The order has been marked as refunded. Please transfer the money ' 'back to the buyer manually.'))
def refund(request, **kwargs): with transaction.atomic(): action = get_object_or_404(RequiredAction, event=request.event, pk=kwargs.get('id'), action_type='pretix.plugins.stripe.refund', done=False) data = json.loads(action.data) action.done = True action.user = request.user action.save() order = get_object_or_404(Order, event=request.event, code=data['order']) if order.status != Order.STATUS_PAID: messages.error( request, _('The order cannot be marked as refunded as it is not marked as paid!' )) else: mark_order_refunded(order, user=request.user) messages.success( request, _('The order has been marked as refunded and the issue has been marked as resolved!' )) return redirect( reverse('control:event.order', kwargs={ 'organizer': request.event.organizer.slug, 'event': request.event.slug, 'code': data['order'] }))
def order_control_refund_perform(self, request, order) -> "bool|str": self._init_api() if order.payment_info: payment_info = json.loads(order.payment_info) else: payment_info = None if not payment_info: mark_order_refunded(order, user=request.user) messages.warning(request, _('We were unable to transfer the money back automatically. ' 'Please get in touch with the customer and transfer it back manually.')) return try: ch = stripe.Charge.retrieve(payment_info['id']) ch.refunds.create() ch.refresh() except stripe.error.InvalidRequestError or stripe.error.AuthenticationError or stripe.error.APIConnectionError \ as e: err = e.json_body['error'] messages.error(request, _('We had trouble communicating with Stripe. Please try again and contact ' 'support if the problem persists.')) logger.error('Stripe error: %s' % str(err)) except stripe.error.StripeError: mark_order_refunded(order) messages.warning(request, _('We were unable to transfer the money back automatically. ' 'Please get in touch with the customer and transfer it back manually.')) else: order = mark_order_refunded(order) order.payment_info = str(ch) order.save()
def order_control_refund_perform(self, request, order) -> "bool|str": self.init_api() if order.payment_info: payment_info = json.loads(order.payment_info) else: payment_info = None if not payment_info: mark_order_refunded(order, user=request.user) messages.warning(request, _('We were unable to transfer the money back automatically. ' 'Please get in touch with the customer and transfer it back manually.')) return for res in payment_info['transactions'][0]['related_resources']: for k, v in res.items(): if k == 'sale': sale = paypalrestsdk.Sale.find(v['id']) break refund = sale.refund({}) if not refund.success(): mark_order_refunded(order, user=request.user) messages.warning(request, _('We were unable to transfer the money back automatically. ' 'Please get in touch with the customer and transfer it back manually.')) else: sale = paypalrestsdk.Payment.find(payment_info['id']) order = mark_order_refunded(order, user=request.user) order.payment_info = json.dumps(sale.to_dict()) order.save()
def order_control_refund_perform(self, request, order) -> "bool|str": self.init_api() if order.payment_info: payment_info = json.loads(order.payment_info) else: payment_info = None if not payment_info: mark_order_refunded(order) messages.warning(request, _('We were unable to transfer the money back automatically. ' 'Please get in touch with the customer and transfer it back manually.')) return for res in payment_info['transactions'][0]['related_resources']: for k, v in res.items(): if k == 'sale': sale = paypalrestsdk.Sale.find(v['id']) break refund = sale.refund({}) if not refund.success(): mark_order_refunded(order, user=request.user) messages.warning(request, _('We were unable to transfer the money back automatically. ' 'Please get in touch with the customer and transfer it back manually.')) else: sale = paypalrestsdk.Payment.find(payment_info['id']) order = mark_order_refunded(order) order.payment_info = json.dumps(sale.to_dict()) order.save()
def process(self, request, **kwargs): refund = self.get_object() if refund.state != OrderRefund.REFUND_STATE_EXTERNAL: return Response({'detail': 'Invalid state of refund'}, status=status.HTTP_400_BAD_REQUEST) refund.done(user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth) if 'mark_refunded' in request.data: mark_refunded = request.data.get('mark_refunded', False) else: mark_refunded = request.data.get('mark_canceled', False) if mark_refunded: mark_order_refunded(refund.order, user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth) elif not (refund.order.status == Order.STATUS_PAID and refund.order.pending_sum <= 0): refund.order.status = Order.STATUS_PENDING refund.order.set_expires( now(), refund.order.event.subevents.filter( id__in=refund.order.positions.values_list('subevent_id', flat=True))) refund.order.save(update_fields=['status', 'expires']) return self.retrieve(request, [], **kwargs)
def create(self, request, *args, **kwargs): if 'mark_refunded' in request.data: mark_refunded = request.data.pop('mark_refunded', False) else: mark_refunded = request.data.pop('mark_canceled', False) serializer = OrderRefundCreateSerializer(data=request.data, context=self.get_serializer_context()) serializer.is_valid(raise_exception=True) with transaction.atomic(): self.perform_create(serializer) r = serializer.instance serializer = OrderRefundSerializer(r, context=serializer.context) r.order.log_action( 'pretix.event.order.refund.created', { 'local_id': r.local_id, 'provider': r.provider, }, user=request.user if request.user.is_authenticated else None, auth=request.auth ) if mark_refunded: mark_order_refunded( r.order, user=request.user if request.user.is_authenticated else None, auth=(request.auth if request.auth else None), ) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def order_control_refund_perform(self, request, order): info = json.loads(order.payment_info) merchant = self.settings.get('merchant_id') transact = info['transact'] amount = info['amount'] currency = info['currency'] orderid = info['orderid'] payload = { 'merchant': merchant, 'transact': transact, 'amount': amount, 'currency': currency, 'orderid': orderid, 'textreply': 'true', # 'fullreply': 'true' } if self.settings.get('test_mode'): payload['test'] = 1 if self.settings.get('use_md5key'): # https://tech.dibspayment.com/D2/API/MD5 key1 = self.settings.get('md5_key1') key2 = self.settings.get('md5_key2') parameters = 'merchant=' + merchant + '&orderid=' + orderid + '&transact=' + transact + '&amount=' + amount md5key = DIBS.md5(key2 + DIBS.md5(key1 + parameters)) payload['md5key'] = md5key (username, password) = self.get_api_authorization(merchant) if username is None or password is None: messages.error(request, _('Missing DIBS api username and password for merchant {merchant}.' ' Order cannot be refunded in DIBS.').format(merchant=merchant)) return None # https://tech.dibspayment.com/D2/API/Payment_functions/refundcgi url = 'https://{}:{}@payment.architrade.com/cgi-adm/refund.cgi'.format(username, password) r = requests.post(url, data=payload) data = parse_qs(r.text) status = data['status'][0] if 'status' in data else None result = int(data['result'][0]) if 'result' in data else -1 message = data['message'][0] if 'message' in data else None if result == DIBS.REFUND_ACCEPTED: from pretix.base.services.orders import mark_order_refunded mark_order_refunded(order, user=request.user) messages.success(request, _('The order has been marked as refunded and the money have been refunded in DIBS.')) else: messages.error(request, _('Error refunding in DIBS ({status}; {result}; {message})'.format(status=status, result=result, message=message))) logger.error(['order_control_refund_perform', r.text, r.status_code, info]) return None
def mark_refunded(self, request, **kwargs): order = self.get_object() if order.status != Order.STATUS_PAID: return Response({'detail': 'The order is not paid.'}, status=status.HTTP_400_BAD_REQUEST) mark_order_refunded( order, user=request.user if request.user.is_authenticated else None, api_token=(request.auth if isinstance(request.auth, TeamAPIToken) else None), ) return self.retrieve(request, [], **kwargs)
def mark_refunded(self, request, **kwargs): order = self.get_object() if order.status != Order.STATUS_PAID: return Response( {'detail': 'The order is not paid.'}, status=status.HTTP_400_BAD_REQUEST ) mark_order_refunded( order, user=request.user if request.user.is_authenticated else None, auth=(request.auth if isinstance(request.auth, (TeamAPIToken, OAuthAccessToken, Device)) else None), ) return self.retrieve(request, [], **kwargs)
def order_control_refund_perform(self, request, order) -> "bool|str": self._init_api() if order.payment_info: payment_info = json.loads(order.payment_info) else: payment_info = None if not payment_info or 'id' not in payment_info: mark_order_refunded(order, user=request.user) messages.warning( request, _('We were unable to transfer the money back automatically. ' 'Please get in touch with the customer and transfer it back manually.' )) return try: transaction = braintree.Transaction.find(payment_info['id']) if transaction.status in ("authorized", "submitted_for_settlement"): result = braintree.Transaction.void(payment_info['id']) elif transaction.status in ("settled", "settling"): result = braintree.Transaction.refund(payment_info['id']) else: mark_order_refunded(order, user=request.user) logger.warning( 'Braintree refund of invalid state requested: %s' % transaction.status) messages.warning( request, _('We were unable to transfer the money back automatically. ' 'Please get in touch with the customer and transfer it back manually.' )) return if result.is_success: transaction = braintree.Transaction.find(payment_info['id']) order = mark_order_refunded(order, user=request.user) order.payment_info = json.dumps(self._serialize(transaction)) order.save() else: order = mark_order_refunded(order, user=request.user) logger.warning('Braintree refund/void failed: %s' % result.message) messages.warning( request, _('We were unable to transfer the money back automatically. ' 'Please get in touch with the customer and transfer it back manually.' )) except BraintreeError as e: logger.exception('Braintree error: %s' % str(e)) mark_order_refunded(order, user=request.user) messages.warning( request, _('We were unable to transfer the money back automatically. ' 'Please get in touch with the customer and transfer it back manually.' ))
def order_control_refund_perform(self, request, order) -> "bool|str": self._init_api() if order.payment_info: payment_info = json.loads(order.payment_info) else: payment_info = None if not payment_info: mark_order_refunded(order, user=request.user) messages.warning( request, _('We were unable to transfer the money back automatically. ' 'Please get in touch with the customer and transfer it back manually.' )) return try: ch = stripe.Charge.retrieve(payment_info['id']) ch.refunds.create() ch.refresh() except (stripe.error.InvalidRequestError, stripe.error.AuthenticationError, stripe.error.APIConnectionError) \ as e: if e.json_body: err = e.json_body['error'] logger.exception('Stripe error: %s' % str(err)) else: err = {'message': str(e)} logger.exception('Stripe error: %s' % str(e)) messages.error( request, _('We had trouble communicating with Stripe. Please try again and contact ' 'support if the problem persists.')) logger.error('Stripe error: %s' % str(err)) except stripe.error.StripeError: mark_order_refunded(order, user=request.user) messages.warning( request, _('We were unable to transfer the money back automatically. ' 'Please get in touch with the customer and transfer it back manually.' )) else: order = mark_order_refunded(order, user=request.user) order.payment_info = str(ch) order.save()
def webhook(request, *args, **kwargs): event_json = json.loads(request.body.decode('utf-8')) # We do not check for the event type as we are not interested in the event it self, # we just use it as a trigger to look the charge up to be absolutely sure. # Another reason for this is that stripe events are not authenticated, so they could # come from anywhere. if event_json['data']['object']['object'] == "charge": charge_id = event_json['data']['object']['id'] elif event_json['data']['object']['object'] == "dispute": charge_id = event_json['data']['object']['charge'] else: return HttpResponse("Not interested in this data type", status=200) prov = Stripe(request.event) prov._init_api() try: charge = stripe.Charge.retrieve(charge_id) except stripe.error.StripeError: logger.exception('Stripe error on webhook. Event data: %s' % str(event_json)) return HttpResponse('Charge not found', status=500) metadata = charge['metadata'] if 'event' not in metadata: return HttpResponse('Event not given in charge metadata', status=200) if int(metadata['event']) != request.event.pk: return HttpResponse('Not interested in this event', status=200) try: order = request.event.orders.get(id=metadata['order']) except Order.DoesNotExist: return HttpResponse('Order not found', status=200) order.log_action('pretix.plugins.stripe.event', data=event_json) is_refund = charge['refunds']['total_count'] or charge['dispute'] if order.status == Order.STATUS_PAID and is_refund: mark_order_refunded(order, user=None) elif order.status == Order.STATUS_PENDING and charge['status'] == 'succeeded' and not is_refund: mark_order_paid(order, user=None) return HttpResponse(status=200)
def process(self, request, **kwargs): refund = self.get_object() if refund.state != OrderRefund.REFUND_STATE_EXTERNAL: return Response({'detail': 'Invalid state of refund'}, status=status.HTTP_400_BAD_REQUEST) refund.done(user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth) if request.data.get('mark_refunded', False): mark_order_refunded(refund.order, user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth) else: refund.order.status = Order.STATUS_PENDING refund.order.set_expires( now(), refund.order.event.subevents.filter( id__in=refund.order.positions.values_list('subevent_id', flat=True)) ) refund.order.save(update_fields=['status', 'expires']) return self.retrieve(request, [], **kwargs)
def webhook(request): event_json = json.loads(request.body.decode('utf-8')) event_type = event_json['type'] if event_type != 'charge.refunded': # Not interested return HttpResponse('Event is not a refund', status=200) charge = event_json['data']['object'] if charge['object'] != 'charge': return HttpResponse('Object is not a charge', status=200) metadata = charge['metadata'] if 'event' not in metadata: return HttpResponse('Event not given', status=200) try: event = Event.objects.get(id=metadata['event']) except Event.DoesNotExist: return HttpResponse('Event not found', status=200) try: order = Order.objects.get(id=metadata['order']) except Order.DoesNotExist: return HttpResponse('Order not found', status=200) prov = Stripe(event) prov._init_api() order.log_action('pretix.plugins.stripe.event', data=event_json) try: charge = stripe.Charge.retrieve(charge['id']) except stripe.error.StripeError as err: logger.error('Stripe error on webhook: %s Event data: %s' % (str(err), str(event_json))) return HttpResponse('StripeError', status=500) if charge['refunds'][ 'total_count'] > 0 and order.status == Order.STATUS_PAID: mark_order_refunded(order) return HttpResponse(status=200)
def webhook(request, *args, **kwargs): event_json = json.loads(request.body.decode('utf-8')) # We do not check for the event type as we are not interested in the event it self, # we just use it as a trigger to look the charge up to be absolutely sure. # Another reason for this is that stripe events are not authenticated, so they could # come from anywhere. if event_json['data']['object']['object'] == "charge": charge_id = event_json['data']['object']['id'] elif event_json['data']['object']['object'] == "dispute": charge_id = event_json['data']['object']['charge'] else: return HttpResponse("Not interested in this data type", status=200) prov = Stripe(request.event) prov._init_api() try: charge = stripe.Charge.retrieve(charge_id) except stripe.error.StripeError: logger.exception('Stripe error on webhook. Event data: %s' % str(event_json)) return HttpResponse('Charge not found', status=500) metadata = charge['metadata'] if 'event' not in metadata: return HttpResponse('Event not given in charge metadata', status=200) if int(metadata['event']) != request.event.pk: return HttpResponse('Not interested in this event', status=200) try: order = request.event.orders.get(id=metadata['order']) except Order.DoesNotExist: return HttpResponse('Order not found', status=200) order.log_action('pretix.plugins.stripe.event', data=event_json) if order.status == Order.STATUS_PAID and (charge['refunds']['total_count'] or charge['dispute']): mark_order_refunded(order, user=None) return HttpResponse(status=200)
def order_control_refund_perform(self, request: HttpRequest, order: Order) -> "bool|str": """ Will be called if the event administrator confirms the refund. This should transfer the money back (if possible). You can return the URL the user should be redirected to if you need special behaviour or None to continue with default behaviour. On failure, you should use Django's message framework to display an error message to the user. The default implementation sets the Order's state to refunded and shows a success message. :param request: The HTTP request :param order: The order object """ from pretix.base.services.orders import mark_order_refunded mark_order_refunded(order, user=request.user) messages.success(request, _('The order has been marked as refunded.'))
def webhook(request): event_json = json.loads(request.body.decode('utf-8')) event_type = event_json['type'] if event_type != 'charge.refunded': # Not interested return HttpResponse('Event is not a refund', status=200) charge = event_json['data']['object'] if charge['object'] != 'charge': return HttpResponse('Object is not a charge', status=200) metadata = charge['metadata'] if 'event' not in metadata: return HttpResponse('Event not given', status=200) try: event = Event.objects.get(id=metadata['event']) except Event.DoesNotExist: return HttpResponse('Event not found', status=200) try: order = Order.objects.get(id=metadata['order']) except Order.DoesNotExist: return HttpResponse('Order not found', status=200) prov = Stripe(event) prov._init_api() order.log_action('pretix.plugins.stripe.event', data=event_json) try: charge = stripe.Charge.retrieve(charge['id']) except stripe.error.StripeError as err: logger.error('Stripe error on webhook: %s Event data: %s' % (str(err), str(event_json))) return HttpResponse('StripeError', status=500) if charge['refunds']['total_count'] > 0 and order.status == Order.STATUS_PAID: mark_order_refunded(order) return HttpResponse(status=200)
def order_control_refund_perform(self, request, order) -> "bool|str": if order.payment_info: payment_info = json.loads(order.payment_info) else: payment_info = None if not payment_info or not self.refund_available: mark_order_refunded(order, user=request.user) messages.warning( request, _('We were unable to transfer the money back automatically. ' 'Please get in touch with the customer and transfer it back manually.' )) return f = self._refund_form(request) if not f.is_valid(): messages.error(request, _('Your input was invalid, please try again.')) return elif f.cleaned_data.get('auto_refund') == 'manual': order = mark_order_refunded(order, user=request.user) order.payment_manual = True order.save() return try: self._refund(payment_info['orderNumber'], order.total, self.event.currency, order.locale[:2]) except PaymentException as e: messages.error(request, str(e)) except requests.exceptions.RequestException as e: logger.exception('Wirecard error: %s' % str(e)) messages.error( request, _('We had trouble communicating with Wirecard. Please try again and contact ' 'support if the problem persists.')) else: mark_order_refunded(order, user=request.user)
def order_control_refund_perform(self, request: HttpRequest, order: Order) -> Union[bool, str]: from pretix.base.services.orders import mark_order_refunded mark_order_refunded(order, user=request.user) messages.success(request, _('The order has been marked as refunded.'))