Example #1
0
    def get(self, request, *args, **kwargs):
        u = request.GET.get('query', '')
        if len(u) < 2:
            return JsonResponse({'results': []})

        if "-" in u:
            code = (Q(event__slug__icontains=u.split("-")[0])
                    & Q(code__icontains=Order.normalize_code(u.split("-")[1])))
        else:
            code = Q(code__icontains=Order.normalize_code(u))
        qs = self.order_qs().order_by('pk').annotate(inr=Concat('invoices__prefix', 'invoices__invoice_no')).filter(
            code
            | Q(email__icontains=u)
            | Q(positions__attendee_name_cached__icontains=u)
            | Q(positions__attendee_email__icontains=u)
            | Q(invoice_address__name_cached__icontains=u)
            | Q(invoice_address__company__icontains=u)
            | Q(invoices__invoice_no=u)
            | Q(invoices__invoice_no=u.zfill(5))
            | Q(inr=u)
        ).select_related('event').annotate(pcnt=Count('invoices')).distinct()
        # Yep, we wouldn't need to count the invoices here. However, having this Count() statement in there
        # tricks Django into generating a GROUP BY clause that it otherwise wouldn't and that is required to
        # avoid duplicate results. Yay?

        return JsonResponse({
            'results': [
                {
                    'code': o.event.slug.upper() + '-' + o.code,
                    'status': o.get_status_display(),
                    'total': money_filter(o.total, o.event.currency)
                } for o in qs
            ]
        })
Example #2
0
def notify_incomplete_payment(o: Order):
    with language(o.locale):
        tz = pytz.timezone(o.event.settings.get('timezone', settings.TIME_ZONE))
        try:
            invoice_name = o.invoice_address.name
            invoice_company = o.invoice_address.company
        except InvoiceAddress.DoesNotExist:
            invoice_name = ""
            invoice_company = ""
        email_template = o.event.settings.mail_text_order_expire_warning
        email_context = {
            'event': o.event.name,
            'url': build_absolute_uri(o.event, 'presale:event.order', kwargs={
                'order': o.code,
                'secret': o.secret
            }),
            'expire_date': date_format(o.expires.astimezone(tz), 'SHORT_DATE_FORMAT'),
            'invoice_name': invoice_name,
            'invoice_company': invoice_company,
        }
        email_subject = ugettext('Your order received an incomplete payment: %(code)s') % {'code': o.code}

        try:
            o.send_mail(
                email_subject, email_template, email_context,
                'pretix.event.order.email.expire_warning_sent'
            )
        except SendMailException:
            logger.exception('Reminder email could not be sent')
Example #3
0
    def filter_qs(self, qs):
        fdata = self.cleaned_data

        if fdata.get('query'):
            u = fdata.get('query')

            if "-" in u:
                code = (Q(event__slug__icontains=u.rsplit("-", 1)[0])
                        & Q(code__icontains=Order.normalize_code(u.rsplit("-", 1)[1])))
            else:
                code = Q(code__icontains=Order.normalize_code(u))

            matching_invoices = Invoice.objects.filter(
                Q(invoice_no__iexact=u)
                | Q(invoice_no__iexact=u.zfill(5))
                | Q(full_invoice_no__iexact=u)
            ).values_list('order_id', flat=True)

            matching_positions = OrderPosition.objects.filter(
                Q(order=OuterRef('pk')) & Q(
                    Q(attendee_name_cached__icontains=u) | Q(attendee_email__icontains=u)
                    | Q(secret__istartswith=u)
                )
            ).values('id')

            qs = qs.annotate(has_pos=Exists(matching_positions)).filter(
                code
                | Q(email__icontains=u)
                | Q(invoice_address__name_cached__icontains=u)
                | Q(invoice_address__company__icontains=u)
                | Q(pk__in=matching_invoices)
                | Q(comment__icontains=u)
                | Q(has_pos=True)
            )

        if fdata.get('status'):
            s = fdata.get('status')
            if s == 'o':
                qs = qs.filter(status=Order.STATUS_PENDING, expires__lt=now().replace(hour=0, minute=0, second=0))
            elif s == 'np':
                qs = qs.filter(status__in=[Order.STATUS_PENDING, Order.STATUS_PAID])
            elif s == 'ne':
                qs = qs.filter(status__in=[Order.STATUS_PENDING, Order.STATUS_EXPIRED])
            elif s in ('p', 'n', 'e', 'c', 'r'):
                qs = qs.filter(status=s)

        if fdata.get('ordering'):
            qs = qs.order_by(self.get_order_by())

        if fdata.get('provider'):
            qs = qs.annotate(
                has_payment_with_provider=Exists(
                    OrderPayment.objects.filter(
                        Q(order=OuterRef('pk')) & Q(provider=fdata.get('provider'))
                    )
                )
            )
            qs = qs.filter(has_payment_with_provider=1)

        return qs
Example #4
0
    def filter_qs(self, qs):
        fdata = self.cleaned_data

        if fdata.get('query'):
            u = fdata.get('query')

            if "-" in u:
                code = (Q(event__slug__icontains=u.rsplit("-", 1)[0])
                        & Q(code__icontains=Order.normalize_code(u.rsplit("-", 1)[1])))
            else:
                code = Q(code__icontains=Order.normalize_code(u))

            matching_invoices = Invoice.objects.filter(
                Q(invoice_no__iexact=u)
                | Q(invoice_no__iexact=u.zfill(5))
                | Q(full_invoice_no__iexact=u)
            ).values_list('order_id', flat=True)

            matching_positions = OrderPosition.objects.filter(
                Q(order=OuterRef('pk')) & Q(
                    Q(attendee_name_cached__icontains=u) | Q(attendee_email__icontains=u)
                    | Q(secret__istartswith=u)
                )
            ).values('id')

            qs = qs.annotate(has_pos=Exists(matching_positions)).filter(
                code
                | Q(email__icontains=u)
                | Q(invoice_address__name_cached__icontains=u)
                | Q(invoice_address__company__icontains=u)
                | Q(pk__in=matching_invoices)
                | Q(comment__icontains=u)
                | Q(has_pos=True)
            )

        if fdata.get('status'):
            s = fdata.get('status')
            if s == 'o':
                qs = qs.filter(status=Order.STATUS_PENDING, expires__lt=now().replace(hour=0, minute=0, second=0))
            elif s == 'np':
                qs = qs.filter(status__in=[Order.STATUS_PENDING, Order.STATUS_PAID])
            elif s == 'ne':
                qs = qs.filter(status__in=[Order.STATUS_PENDING, Order.STATUS_EXPIRED])
            elif s in ('p', 'n', 'e', 'c', 'r'):
                qs = qs.filter(status=s)

        if fdata.get('ordering'):
            qs = qs.order_by(self.get_order_by())

        if fdata.get('provider'):
            qs = qs.annotate(
                has_payment_with_provider=Exists(
                    OrderPayment.objects.filter(
                        Q(order=OuterRef('pk')) & Q(provider=fdata.get('provider'))
                    )
                )
            )
            qs = qs.filter(has_payment_with_provider=1)

        return qs
Example #5
0
 def shred_payment_info(self, order: Order):
     if not order.payment_info:
         return
     d = json.loads(order.payment_info)
     new = {}
     if 'source' in d:
         new['source'] = {
             'id': d['source'].get('id'),
             'type': d['source'].get('type'),
             'brand': d['source'].get('brand'),
             'last4': d['source'].get('last4'),
             'bank_name': d['source'].get('bank_name'),
             'bank': d['source'].get('bank'),
             'bic': d['source'].get('bic'),
             'card': {
                 'brand': d['source'].get('card', {}).get('brand'),
                 'country': d['source'].get('card', {}).get('cuntry'),
                 'last4': d['source'].get('card', {}).get('last4'),
             }
         }
         new['amount'] = d['amount']
         new['currency'] = d['currency']
         new['status'] = d['status']
         new['id'] = d['id']
         new['_shredded'] = True
     order.payment_info = json.dumps(new)
     order.save(update_fields=['payment_info'])
Example #6
0
    def filter_qs(self, qs):
        fdata = self.cleaned_data

        if fdata.get('query'):
            u = fdata.get('query')
            if "-" in u:
                code = (
                    Q(event__slug__icontains=u.split("-")[0])
                    & Q(code__icontains=Order.normalize_code(u.split("-")[1])))
            else:
                code = Q(code__icontains=Order.normalize_code(u))
            qs = qs.filter(code
                           | Q(email__icontains=u)
                           | Q(positions__attendee_name__icontains=u)
                           | Q(positions__attendee_email__icontains=u)
                           | Q(invoice_address__name__icontains=u)
                           | Q(invoice_address__company__icontains=u))

        if fdata.get('status'):
            s = fdata.get('status')
            if s == 'o':
                qs = qs.filter(status=Order.STATUS_PENDING,
                               expires__lt=now().replace(hour=0,
                                                         minute=0,
                                                         second=0))
            elif s == 'ne':
                qs = qs.filter(
                    status__in=[Order.STATUS_PENDING, Order.STATUS_EXPIRED])
            else:
                qs = qs.filter(status=s)

        return qs
Example #7
0
def notify_incomplete_payment(o: Order):
    with language(o.locale):
        tz = pytz.timezone(o.event.settings.get('timezone', settings.TIME_ZONE))
        try:
            invoice_name = o.invoice_address.name
            invoice_company = o.invoice_address.company
        except InvoiceAddress.DoesNotExist:
            invoice_name = ""
            invoice_company = ""
        email_template = o.event.settings.mail_text_order_expire_warning
        email_context = {
            'event': o.event.name,
            'url': build_absolute_uri(o.event, 'presale:event.order.open', kwargs={
                'order': o.code,
                'secret': o.secret,
                'hash': o.email_confirm_hash()
            }),
            'expire_date': date_format(o.expires.astimezone(tz), 'SHORT_DATE_FORMAT'),
            'invoice_name': invoice_name,
            'invoice_company': invoice_company,
        }
        email_subject = ugettext('Your order received an incomplete payment: %(code)s') % {'code': o.code}

        try:
            o.send_mail(
                email_subject, email_template, email_context,
                'pretix.event.order.email.expire_warning_sent'
            )
        except SendMailException:
            logger.exception('Reminder email could not be sent')
Example #8
0
    def get_queryset(self):
        qs = Order.objects.all()
        if not self.request.user.is_superuser:
            qs = qs.filter(
                Q(event__organizer_id__in=self.request.user.teams.filter(
                    all_events=True, can_view_orders=True).values_list('organizer', flat=True))
                | Q(event_id__in=self.request.user.teams.filter(
                    can_view_orders=True).values_list('limit_events__id', flat=True))
            )

        if self.request.GET.get("query", "") != "":
            u = self.request.GET.get("query", "")
            if "-" in u:
                code = (Q(event__slug__icontains=u.split("-")[0])
                        & Q(code__icontains=Order.normalize_code(u.split("-")[1])))
            else:
                code = Q(code__icontains=Order.normalize_code(u))
            qs = qs.filter(
                code
                | Q(email__icontains=u)
                | Q(positions__attendee_name__icontains=u)
                | Q(positions__attendee_email__icontains=u)
                | Q(invoice_address__name__icontains=u)
                | Q(invoice_address__company__icontains=u)
            )
            print(qs.query)

        if self.request.GET.get("ordering", "") != "":
            p = self.request.GET.get("ordering", "")
            p_admissable = ('event', '-event', '-code', 'code', '-email', 'email', '-total', 'total', '-datetime',
                            'datetime', '-status', 'status')
            if p in p_admissable:
                qs = qs.order_by(p)

        return qs.distinct().prefetch_related('event', 'event__organizer')
Example #9
0
    def get(self, request, *args, **kwargs):
        u = request.GET.get('query', '')
        if len(u) < 2:
            return JsonResponse({'results': []})

        if "-" in u:
            code = (Q(event__slug__icontains=u.split("-")[0])
                    & Q(code__icontains=Order.normalize_code(u.split("-")[1])))
        else:
            code = Q(code__icontains=Order.normalize_code(u))
        qs = self.order_qs().order_by('pk').annotate(
            inr=Concat('invoices__prefix', 'invoices__invoice_no')).filter(
                code
                | Q(email__icontains=u)
                | Q(all_positions__attendee_name_cached__icontains=u)
                | Q(all_positions__attendee_email__icontains=u)
                | Q(invoice_address__name_cached__icontains=u)
                | Q(invoice_address__company__icontains=u)
                | Q(invoices__invoice_no=u)
                | Q(invoices__invoice_no=u.zfill(5))
                | Q(inr=u)).select_related('event').annotate(
                    pcnt=Count('invoices')).distinct()
        # Yep, we wouldn't need to count the invoices here. However, having this Count() statement in there
        # tricks Django into generating a GROUP BY clause that it otherwise wouldn't and that is required to
        # avoid duplicate results. Yay?

        return JsonResponse({
            'results': [{
                'code': o.event.slug.upper() + '-' + o.code,
                'status': o.get_status_display(),
                'total': money_filter(o.total, o.event.currency)
            } for o in qs]
        })
Example #10
0
def mark_order_paid(order: Order, provider: str=None, info: str=None, date: datetime=None, manual: bool=None,
                    force: bool=False, send_mail: bool=True, user: User=None) -> Order:
    """
    Marks an order as paid. This sets the payment provider, info and date and returns
    the order object.

    :param provider: The payment provider that marked this as paid
    :type provider: str
    :param info: The information to store in order.payment_info
    :type info: str
    :param date: The date the payment was received (if you pass ``None``, the current
                 time will be used).
    :type date: datetime
    :param force: Whether this payment should be marked as paid even if no remaining
                  quota is available (default: ``False``).
    :type force: boolean
    :param send_mail: Whether an email should be sent to the user about this event (default: ``True``).
    :type send_mail: boolean
    :param user: The user that performed the change
    :raises Quota.QuotaExceededException: if the quota is exceeded and ``force`` is ``False``
    """
    with order.event.lock():
        can_be_paid = order._can_be_paid()
        if not force and can_be_paid is not True:
            raise Quota.QuotaExceededException(can_be_paid)
        order.payment_provider = provider or order.payment_provider
        order.payment_info = info or order.payment_info
        order.payment_date = date or now()
        if manual is not None:
            order.payment_manual = manual
        order.status = Order.STATUS_PAID
        order.save()

    order.log_action('pretix.event.order.paid', {
        'provider': provider,
        'info': info,
        'date': date,
        'manual': manual,
        'force': force
    }, user=user)
    order_paid.send(order.event, order=order)

    if send_mail:
        with language(order.locale):
            mail(
                order.email, _('Payment received for your order: %(code)s') % {'code': order.code},
                order.event.settings.mail_text_order_paid,
                {
                    'event': order.event.name,
                    'url': build_absolute_uri(order.event, 'presale:event.order', kwargs={
                        'order': order.code,
                        'secret': order.secret
                    }),
                    'downloads': order.event.settings.get('ticket_download', as_type=bool)
                },
                order.event, locale=order.locale
            )
    return order
Example #11
0
def mark_order_paid(order: Order, provider: str=None, info: str=None, date: datetime=None, manual: bool=None,
                    force: bool=False, send_mail: bool=True, user: User=None) -> Order:
    """
    Marks an order as paid. This sets the payment provider, info and date and returns
    the order object.

    :param provider: The payment provider that marked this as paid
    :type provider: str
    :param info: The information to store in order.payment_info
    :type info: str
    :param date: The date the payment was received (if you pass ``None``, the current
                 time will be used).
    :type date: datetime
    :param force: Whether this payment should be marked as paid even if no remaining
                  quota is available (default: ``False``).
    :type force: boolean
    :param send_mail: Whether an email should be sent to the user about this event (default: ``True``).
    :type send_mail: boolean
    :param user: The user that performed the change
    :raises Quota.QuotaExceededException: if the quota is exceeded and ``force`` is ``False``
    """
    with order.event.lock() as now_dt:
        can_be_paid = order._can_be_paid()
        if not force and can_be_paid is not True:
            raise Quota.QuotaExceededException(can_be_paid)
        order.payment_provider = provider or order.payment_provider
        order.payment_info = info or order.payment_info
        order.payment_date = date or now_dt
        if manual is not None:
            order.payment_manual = manual
        order.status = Order.STATUS_PAID
        order.save()

    order.log_action('pretix.event.order.paid', {
        'provider': provider,
        'info': info,
        'date': date,
        'manual': manual,
        'force': force
    }, user=user)
    order_paid.send(order.event, order=order)

    if send_mail:
        with language(order.locale):
            mail(
                order.email, _('Payment received for your order: %(code)s') % {'code': order.code},
                order.event.settings.mail_text_order_paid,
                {
                    'event': order.event.name,
                    'url': build_absolute_uri(order.event, 'presale:event.order', kwargs={
                        'order': order.code,
                        'secret': order.secret
                    }),
                    'downloads': order.event.settings.get('ticket_download', as_type=bool)
                },
                order.event, locale=order.locale
            )
    return order
Example #12
0
 def shred_payment_info(self, order: Order):
     if not order.payment_info:
         return
     d = json.loads(order.payment_info)
     d['reference'] = 'â–ˆ'
     d['payer'] = 'â–ˆ'
     d['_shredded'] = True
     order.payment_info = json.dumps(d)
     order.save(update_fields=['payment_info'])
Example #13
0
def cancel_order(order: Order, user: User=None):
    """
    Mark this order as canceled
    :param order: The order to change
    :param user: The user that performed the change
    """
    order.status = Order.STATUS_CANCELLED
    order.save()
    order.log_action('pretix.event.order.cancelled', user=user)
    return order
Example #14
0
def mark_order_refunded(order: Order, user: User=None):
    """
    Mark this order as refunded. This sets the payment status and returns the order object.
    :param order: The order to change
    :param user: The user that performed the change
    """
    order.status = Order.STATUS_REFUNDED
    order.save()
    order.log_action('pretix.event.order.refunded', user=user)
    return order
Example #15
0
def cancel_order(order: Order, user: User = None):
    """
    Mark this order as canceled
    :param order: The order to change
    :param user: The user that performed the change
    """
    order.status = Order.STATUS_CANCELLED
    order.save()
    order.log_action('pretix.event.order.cancelled', user=user)
    return order
Example #16
0
def mark_order_refunded(order: Order, user: User = None):
    """
    Mark this order as refunded. This sets the payment status and returns the order object.
    :param order: The order to change
    :param user: The user that performed the change
    """
    order.status = Order.STATUS_REFUNDED
    order.save()
    order.log_action('pretix.event.order.refunded', user=user)
    return order
Example #17
0
    def create(self, validated_data):
        fees_data = validated_data.pop('fees') if 'fees' in validated_data else []
        positions_data = validated_data.pop('positions') if 'positions' in validated_data else []
        if 'invoice_address' in validated_data:
            ia = InvoiceAddress(**validated_data.pop('invoice_address'))
        else:
            ia = None

        with self.context['event'].lock():
            quotadiff = Counter()
            for pos_data in positions_data:
                new_quotas = (pos_data.get('variation').quotas.filter(subevent=pos_data.get('subevent'))
                              if pos_data.get('variation')
                              else pos_data.get('item').quotas.filter(subevent=pos_data.get('subevent')))
                quotadiff.update(new_quotas)

                for quota, diff in quotadiff.items():
                    avail = quota.availability()
                    if avail[0] != Quota.AVAILABILITY_OK or (avail[1] is not None and avail[1] < diff):
                        raise ValidationError(
                            'There is not enough quota available on quota "{}" to perform the operation.'.format(
                                quota.name
                            )
                        )

            order = Order(event=self.context['event'], **validated_data)
            order.set_expires(subevents=[p['subevent'] for p in positions_data])
            order.total = sum([p['price'] for p in positions_data]) + sum([f['value'] for f in fees_data], Decimal('0.00'))
            if order.total == Decimal('0.00') and validated_data.get('status') != Order.STATUS_PAID:
                order.payment_provider = 'free'
                order.status = Order.STATUS_PAID
            elif order.payment_provider == "free" and order.total != Decimal('0.00'):
                raise ValidationError('You cannot use the "free" payment provider for non-free orders.')
            order.save()
            if ia:
                ia.order = order
                ia.save()
            pos_map = {}
            for pos_data in positions_data:
                answers_data = pos_data.pop('answers')
                addon_to = pos_data.pop('addon_to')
                pos = OrderPosition(**pos_data)
                pos.order = order
                pos._calculate_tax()
                if addon_to:
                    pos.addon_to = pos_map[addon_to]
                pos.save()
                pos_map[pos.positionid] = pos
                for answ_data in answers_data:
                    options = answ_data.pop('options')
                    answ = pos.answers.create(**answ_data)
                    answ.options.add(*options)
        for fee_data in fees_data:
            f = OrderFee(**fee_data)
            f.order = order
            f._calculate_tax()
            f.save()

        return order
Example #18
0
    def shred_payment_info(self, order: Order):
        """
        When personal data is removed from an event, this method is called to scrub payment-related data
        from an order. By default, it removes all info from the ``payment_info`` attribute. You can override
        this behavior if you want to retain attributes that are not personal data on their own, i.e. a
        reference to a transaction in an external system. You can also override this to scrub more data, e.g.
        data from external sources that is saved in LogEntry objects or other places.

        :param order: An order
        """
        order.payment_info = None
        order.save(update_fields=['payment_info'])
Example #19
0
def _find_order_for_code(base_qs, code):
    try_codes = [
        code,
        Order.normalize_code(code, is_fallback=True),
        code[:settings.ENTROPY['order_code']],
        Order.normalize_code(code[:settings.ENTROPY['order_code']], is_fallback=True)
    ]
    for c in try_codes:
        try:
            return base_qs.get(code=c)
        except Order.DoesNotExist:
            pass
Example #20
0
 def shred_payment_info(self, order: Order):
     if not order.payment_info:
         return
     d = json.loads(order.payment_info)
     new = {'_shreded': True}
     for k in ('paymentState', 'amount', 'authenticated', 'paymentType',
               'pretix_orderCode', 'currency', 'orderNumber',
               'financialInstitution', 'message', 'mandateId', 'dueDate'):
         if k in d:
             new[k] = d[k]
     order.payment_info = json.dumps(new)
     order.save(update_fields=['payment_info'])
Example #21
0
def event_index(request, organizer, event):
    subevent = None
    if request.GET.get("subevent", "") != "" and request.event.has_subevents:
        i = request.GET.get("subevent", "")
        try:
            subevent = request.event.subevents.get(pk=i)
        except SubEvent.DoesNotExist:
            pass

    widgets = []
    for r, result in event_dashboard_widgets.send(sender=request.event, subevent=subevent):
        widgets.extend(result)

    can_change_orders = request.user.has_event_permission(request.organizer, request.event, 'can_change_orders',
                                                          request=request)
    qs = request.event.logentry_set.all().select_related('user', 'content_type', 'api_token', 'oauth_application',
                                                         'device').order_by('-datetime')
    qs = qs.exclude(action_type__in=OVERVIEW_BLACKLIST)
    if not request.user.has_event_permission(request.organizer, request.event, 'can_view_orders', request=request):
        qs = qs.exclude(content_type=ContentType.objects.get_for_model(Order))
    if not request.user.has_event_permission(request.organizer, request.event, 'can_view_vouchers', request=request):
        qs = qs.exclude(content_type=ContentType.objects.get_for_model(Voucher))

    a_qs = request.event.requiredaction_set.filter(done=False)

    ctx = {
        'widgets': rearrange(widgets),
        'logs': qs[:5],
        'actions': a_qs[:5] if can_change_orders else [],
        'comment_form': CommentForm(initial={'comment': request.event.comment})
    }

    ctx['has_overpaid_orders'] = Order.annotate_overpayments(request.event.orders).filter(
        Q(~Q(status=Order.STATUS_CANCELED) & Q(pending_sum_t__lt=0))
        | Q(Q(status=Order.STATUS_CANCELED) & Q(pending_sum_rc__lt=0))
    ).exists()
    ctx['has_pending_orders_with_full_payment'] = Order.annotate_overpayments(request.event.orders).filter(
        Q(status__in=(Order.STATUS_EXPIRED, Order.STATUS_PENDING)) & Q(pending_sum_t__lte=0) & Q(require_approval=False)
    ).exists()
    ctx['has_pending_refunds'] = OrderRefund.objects.filter(
        order__event=request.event,
        state__in=(OrderRefund.REFUND_STATE_CREATED, OrderRefund.REFUND_STATE_EXTERNAL)
    ).exists()
    ctx['has_pending_approvals'] = request.event.orders.filter(
        status=Order.STATUS_PENDING,
        require_approval=True
    ).exists()

    for a in ctx['actions']:
        a.display = a.display(request)

    return render(request, 'pretixcontrol/event/index.html', ctx)
Example #22
0
def event_index(request, organizer, event):
    subevent = None
    if request.GET.get("subevent", "") != "" and request.event.has_subevents:
        i = request.GET.get("subevent", "")
        try:
            subevent = request.event.subevents.get(pk=i)
        except SubEvent.DoesNotExist:
            pass

    widgets = []
    for r, result in event_dashboard_widgets.send(sender=request.event, subevent=subevent):
        widgets.extend(result)

    can_change_orders = request.user.has_event_permission(request.organizer, request.event, 'can_change_orders',
                                                          request=request)
    qs = request.event.logentry_set.all().select_related('user', 'content_type', 'api_token', 'oauth_application',
                                                         'device').order_by('-datetime')
    qs = qs.exclude(action_type__in=OVERVIEW_BLACKLIST)
    if not request.user.has_event_permission(request.organizer, request.event, 'can_view_orders', request=request):
        qs = qs.exclude(content_type=ContentType.objects.get_for_model(Order))
    if not request.user.has_event_permission(request.organizer, request.event, 'can_view_vouchers', request=request):
        qs = qs.exclude(content_type=ContentType.objects.get_for_model(Voucher))

    a_qs = request.event.requiredaction_set.filter(done=False)

    ctx = {
        'widgets': rearrange(widgets),
        'logs': qs[:5],
        'actions': a_qs[:5] if can_change_orders else [],
        'comment_form': CommentForm(initial={'comment': request.event.comment})
    }

    ctx['has_overpaid_orders'] = Order.annotate_overpayments(request.event.orders).filter(
        Q(~Q(status__in=(Order.STATUS_REFUNDED, Order.STATUS_CANCELED)) & Q(pending_sum_t__lt=0))
        | Q(Q(status__in=(Order.STATUS_REFUNDED, Order.STATUS_CANCELED)) & Q(pending_sum_rc__lt=0))
    ).exists()
    ctx['has_pending_orders_with_full_payment'] = Order.annotate_overpayments(request.event.orders).filter(
        Q(status__in=(Order.STATUS_EXPIRED, Order.STATUS_PENDING)) & Q(pending_sum_t__lte=0) & Q(require_approval=False)
    ).exists()
    ctx['has_pending_refunds'] = OrderRefund.objects.filter(
        order__event=request.event,
        state__in=(OrderRefund.REFUND_STATE_CREATED, OrderRefund.REFUND_STATE_EXTERNAL)
    ).exists()
    ctx['has_pending_approvals'] = request.event.orders.filter(
        status=Order.STATUS_PENDING,
        require_approval=True
    ).exists()

    for a in ctx['actions']:
        a.display = a.display(request)

    return render(request, 'pretixcontrol/event/index.html', ctx)
Example #23
0
def notify_incomplete_payment(o: Order):
    with language(o.locale, o.event.settings.region):
        email_template = o.event.settings.mail_text_order_expire_warning
        email_context = get_email_context(event=o.event, order=o)
        email_subject = gettext('Your order received an incomplete payment: %(code)s') % {'code': o.code}

        try:
            o.send_mail(
                email_subject, email_template, email_context,
                'pretix.event.order.email.expire_warning_sent'
            )
        except SendMailException:
            logger.exception('Reminder email could not be sent')
Example #24
0
def _send_mail(order: Order, subject: LazyI18nString, message: LazyI18nString,
               subevent: SubEvent, refund_amount: Decimal, user: User,
               positions: list):
    with language(order.locale, order.event.settings.region):
        try:
            ia = order.invoice_address
        except InvoiceAddress.DoesNotExist:
            ia = InvoiceAddress(order=order)

        email_context = get_email_context(event_or_subevent=subevent
                                          or order.event,
                                          refund_amount=refund_amount,
                                          order=order,
                                          position_or_address=ia,
                                          event=order.event)
        real_subject = str(subject).format_map(TolerantDict(email_context))
        try:
            order.send_mail(
                real_subject,
                message,
                email_context,
                'pretix.event.order.email.event_canceled',
                user,
            )
        except SendMailException:
            logger.exception('Order canceled email could not be sent')

        for p in positions:
            if subevent and p.subevent_id != subevent.id:
                continue

            if p.addon_to_id is None and p.attendee_email and p.attendee_email != order.email:
                real_subject = str(subject).format_map(
                    TolerantDict(email_context))
                email_context = get_email_context(event_or_subevent=p.subevent
                                                  or order.event,
                                                  event=order.event,
                                                  refund_amount=refund_amount,
                                                  position_or_address=p,
                                                  order=order,
                                                  position=p)
                try:
                    order.send_mail(real_subject,
                                    message,
                                    email_context,
                                    'pretix.event.order.email.event_canceled',
                                    position=p,
                                    user=user)
                except SendMailException:
                    logger.exception(
                        'Order canceled email could not be sent to attendee')
Example #25
0
def mark_order_paid(order: Order,
                    provider: str = None,
                    info: str = None,
                    date: datetime = None,
                    manual: bool = None,
                    force: bool = False):
    """
    Marks an order as paid. This clones the order object, sets the payment provider,
    info and date and returns the cloned order object.

    :param provider: The payment provider that marked this as paid
    :type provider: str
    :param info: The information to store in order.payment_info
    :type info: str
    :param date: The date the payment was received (if you pass ``None``, the current
                 time will be used).
    :type date: datetime
    :param force: Whether this payment should be marked as paid even if no remaining
                  quota is available (default: ``False``).
    :type force: boolean
    :raises Quota.QuotaExceededException: if the quota is exceeded and ``force`` is ``False``
    """
    with order.event.lock():
        can_be_paid = order._can_be_paid()
        if not force and can_be_paid is not True:
            raise Quota.QuotaExceededException(can_be_paid)
        order = order.clone()
        order.payment_provider = provider or order.payment_provider
        order.payment_info = info or order.payment_info
        order.payment_date = date or now()
        if manual is not None:
            order.payment_manual = manual
        order.status = Order.STATUS_PAID
        order.save()
        order_paid.send(order.event, order=order)

    mail(order.email,
         _('Payment received for your order: %(code)s') % {'code': order.code},
         'pretixpresale/email/order_paid.txt', {
             'order':
             order,
             'event':
             order.event,
             'url':
             build_absolute_uri(order.event,
                                'presale:event.order',
                                kwargs={
                                    'order': order.code,
                                    'secret': order.secret
                                }),
             'downloads':
             order.event.settings.get('ticket_download', as_type=bool)
         },
         order.event,
         locale=order.locale)
    return order
Example #26
0
    def order_control_refund_render(self, order: Order) -> str:
        """
        Will be called if the event administrator clicks an order's 'refund' button.
        This can be used to display information *before* the order is being refunded.

        It should return HTML code which should be displayed to the user. It should
        contain information about to which extend the money will be refunded
        automatically.

        :param order: The order object
        """
        order.log_action('pretix.base.order.refunded')
        return '<div class="alert alert-warning">%s</div>' % _('The money can not be automatically refunded, '
                                                               'please transfer the money back manually.')
Example #27
0
    def order_control_refund_render(self, order: Order) -> str:
        """
        Will be called if the event administrator clicks an order's 'refund' button.
        This can be used to display information *before* the order is being refunded.

        It should return HTML code which should be displayed to the user. It should
        contain information about to which extend the money will be refunded
        automatically.

        :param order: The order object
        """
        order.log_action('pretix.base.order.refunded')
        return '<div class="alert alert-warning">%s</div>' % _('The money can not be automatically refunded, '
                                                               'please transfer the money back manually.')
Example #28
0
    def filter_qs(self, qs):
        fdata = self.cleaned_data

        if fdata.get('query'):
            u = fdata.get('query')

            if "-" in u:
                code = (Q(event__slug__icontains=u.split("-")[0])
                        & Q(code__icontains=Order.normalize_code(u.split("-")[1])))
            else:
                code = Q(code__icontains=Order.normalize_code(u))

            matching_invoice = Invoice.objects.filter(
                order=OuterRef('pk'),
            ).annotate(
                inr=Concat('prefix', 'invoice_no')
            ).filter(
                Q(invoice_no__iexact=u)
                | Q(invoice_no__iexact=u.zfill(5))
                | Q(inr=u)
            )

            qs = qs.annotate(has_inv=Exists(matching_invoice))
            qs = qs.filter(
                code
                | Q(email__icontains=u)
                | Q(positions__attendee_name__icontains=u)
                | Q(positions__attendee_email__icontains=u)
                | Q(invoice_address__name__icontains=u)
                | Q(invoice_address__company__icontains=u)
                | Q(has_inv=True)
            )

        if fdata.get('status'):
            s = fdata.get('status')
            if s == 'o':
                qs = qs.filter(status=Order.STATUS_PENDING, expires__lt=now().replace(hour=0, minute=0, second=0))
            elif s == 'ne':
                qs = qs.filter(status__in=[Order.STATUS_PENDING, Order.STATUS_EXPIRED])
            else:
                qs = qs.filter(status=s)

        if fdata.get('ordering'):
            qs = qs.order_by(dict(self.fields['ordering'].choices)[fdata.get('ordering')])

        if fdata.get('provider'):
            qs = qs.filter(payment_provider=fdata.get('provider'))

        return qs
Example #29
0
    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx['customer'] = self.request.customer
        ctx['memberships'] = self.request.customer.memberships.with_usages().select_related(
            'membership_type', 'granted_in', 'granted_in__order', 'granted_in__order__event'
        )
        ctx['invoice_addresses'] = InvoiceAddress.profiles.filter(customer=self.request.customer)
        ctx['is_paginated'] = True

        for m in ctx['memberships']:
            if m.membership_type.max_usages:
                m.percent = int(m.usages / m.membership_type.max_usages * 100)
            else:
                m.percent = 0

        s = OrderPosition.objects.filter(
            order=OuterRef('pk')
        ).order_by().values('order').annotate(k=Count('id')).values('k')
        annotated = {
            o['pk']: o
            for o in
            Order.annotate_overpayments(Order.objects, sums=True).filter(
                pk__in=[o.pk for o in ctx['orders']]
            ).annotate(
                pcnt=Subquery(s, output_field=IntegerField()),
            ).values(
                'pk', 'pcnt',
            )
        }

        for o in ctx['orders']:
            if o.pk not in annotated:
                continue
            o.count_positions = annotated.get(o.pk)['pcnt']
        return ctx
Example #30
0
def mark_order_paid(order: Order, provider: str=None, info: str=None, date: datetime=None, manual: bool=None,
                    force: bool=False):
    """
    Marks an order as paid. This clones the order object, sets the payment provider,
    info and date and returns the cloned order object.

    :param provider: The payment provider that marked this as paid
    :type provider: str
    :param info: The information to store in order.payment_info
    :type info: str
    :param date: The date the payment was received (if you pass ``None``, the current
                 time will be used).
    :type date: datetime
    :param force: Whether this payment should be marked as paid even if no remaining
                  quota is available (default: ``False``).
    :type force: boolean
    :raises Quota.QuotaExceededException: if the quota is exceeded and ``force`` is ``False``
    """
    with order.event.lock():
        can_be_paid = order._can_be_paid()
        if not force and can_be_paid is not True:
            raise Quota.QuotaExceededException(can_be_paid)
        order = order.clone()
        order.payment_provider = provider or order.payment_provider
        order.payment_info = info or order.payment_info
        order.payment_date = date or now()
        if manual is not None:
            order.payment_manual = manual
        order.status = Order.STATUS_PAID
        order.save()
        order_paid.send(order.event, order=order)

    mail(
        order.email, _('Payment received for your order: %(code)s') % {'code': order.code},
        'pretixpresale/email/order_paid.txt',
        {
            'order': order,
            'event': order.event,
            'url': build_absolute_uri('presale:event.order', kwargs={
                'event': order.event.slug,
                'organizer': order.event.organizer.slug,
                'order': order.code,
                'secret': order.secret
            }),
            'downloads': order.event.settings.get('ticket_download', as_type=bool)
        },
        order.event, locale=order.locale
    )
    return order
Example #31
0
    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 an 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 Orders state to refunded and shows a success
        message.

        :param request: The HTTP request
        :param order: The order object
        """
        order.mark_refunded()
        messages.success(request, _('The order has been marked as refunded.'))
Example #32
0
    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 an 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 Orders state to refunded and shows a success
        message.

        :param request: The HTTP request
        :param order: The order object
        """
        order.mark_refunded()
        messages.success(request, _('The order has been marked as refunded.'))
Example #33
0
 def shred_payment_info(self, order: Order):
     d = json.loads(order.payment_info)
     new = {
         'id':
         d.get('id'),
         'payer': {
             'payer_info': {
                 'email': 'â–ˆ'
             }
         },
         'update_time':
         d.get('update_time'),
         'transactions': [{
             'amount': t.get('amount')
         } for t in d.get('transactions', [])],
         '_shredded':
         True
     }
     order.payment_info = json.dumps(new)
     order.save(update_fields=['payment_info'])
Example #34
0
    def get(self, request, *args, **kwargs):
        from django.utils.formats import localize

        query = request.GET.get('query', '')
        if len(query) < 2:
            return JsonResponse({'results': []})

        qs = self.order_qs().filter(
            Q(code__icontains=query)
            | Q(code__icontains=Order.normalize_code(query))).select_related(
                'event')
        return JsonResponse({
            'results': [{
                'code': o.event.slug.upper() + '-' + o.code,
                'status': o.get_status_display(),
                'total': localize(o.total) + ' ' + o.event.currency
            } for o in qs]
        })
Example #35
0
    def get(self, request, *args, **kwargs):
        query = request.GET.get('query', '')
        if len(query) < 2:
            return JsonResponse({'results': []})

        qs = self.request.event.orders.filter(
            Q(code__icontains=query)
            | Q(code__icontains=Order.normalize_code(query)))
        return JsonResponse({
            'results': [{
                'code':
                o.code,
                'status':
                o.get_status_display(),
                'total':
                lformat("%.2f", o.total) + ' ' + self.request.event.currency
            } for o in qs]
        })
Example #36
0
def _handle_transaction(event: Event, trans: BankTransaction, code: str):
    try:
        trans.order = event.orders.get(code=code)
    except Order.DoesNotExist:
        normalized_code = Order.normalize_code(code)
        try:
            trans.order = event.orders.get(code=normalized_code)
        except Order.DoesNotExist:
            trans.state = BankTransaction.STATE_NOMATCH
            trans.save()
            return

    if trans.order.status == Order.STATUS_PAID:
        trans.state = BankTransaction.STATE_DUPLICATE
    elif trans.order.status == Order.STATUS_REFUNDED:
        trans.state = BankTransaction.STATE_ERROR
        trans.message = ugettext_noop('The order has already been refunded.')
    elif trans.order.status == Order.STATUS_CANCELED:
        trans.state = BankTransaction.STATE_ERROR
        trans.message = ugettext_noop('The order has already been canceled.')
    elif trans.amount != trans.order.total:
        trans.state = BankTransaction.STATE_INVALID
        trans.message = ugettext_noop('The transaction amount is incorrect.')
    else:
        try:
            mark_order_paid(trans.order,
                            provider='banktransfer',
                            info=json.dumps({
                                'reference': trans.reference,
                                'date': trans.date,
                                'payer': trans.payer,
                                'trans_id': trans.pk
                            }))
        except Quota.QuotaExceededException as e:
            trans.state = BankTransaction.STATE_ERROR
            trans.message = str(e)
        except SendMailException:
            trans.state = BankTransaction.STATE_ERROR
            trans.message = ugettext_noop('Problem sending email.')
        else:
            trans.state = BankTransaction.STATE_VALID
    trans.save()
Example #37
0
    def get_context_data(self, **kwargs):
        ctx = super().get_context_data()
        ctx['filter_form'] = self.filter_form
        ctx['meta_fields'] = [
            self.filter_form[k] for k in self.filter_form.fields
            if k.startswith('meta_')
        ]

        # Only compute this annotations for this page (query optimization)
        s = OrderPosition.objects.filter(
            order=OuterRef('pk')).order_by().values('order').annotate(
                k=Count('id')).values('k')
        annotated = {
            o['pk']: o
            for o in Order.annotate_overpayments(Order.objects).using(
                settings.DATABASE_REPLICA).filter(
                    pk__in=[o.pk for o in ctx['orders']]).annotate(
                        pcnt=Subquery(s, output_field=IntegerField()),
                        has_cancellation_request=Exists(
                            CancellationRequest.objects.filter(
                                order=OuterRef('pk')))).
            values('pk', 'pcnt', 'is_overpaid', 'is_underpaid',
                   'is_pending_with_full_payment', 'has_external_refund',
                   'has_pending_refund', 'has_cancellation_request')
        }

        for o in ctx['orders']:
            if o.pk not in annotated:
                continue
            o.pcnt = annotated.get(o.pk)['pcnt']
            o.is_overpaid = annotated.get(o.pk)['is_overpaid']
            o.is_underpaid = annotated.get(o.pk)['is_underpaid']
            o.is_pending_with_full_payment = annotated.get(
                o.pk)['is_pending_with_full_payment']
            o.has_external_refund = annotated.get(o.pk)['has_external_refund']
            o.has_pending_refund = annotated.get(o.pk)['has_pending_refund']
            o.has_cancellation_request = annotated.get(
                o.pk)['has_cancellation_request']

        return ctx
Example #38
0
def _handle_transaction(event: Event, trans: BankTransaction, code: str):
    try:
        trans.order = event.orders.get(code=code)
    except Order.DoesNotExist:
        normalized_code = Order.normalize_code(code)
        try:
            trans.order = event.orders.get(code=normalized_code)
        except Order.DoesNotExist:
            trans.state = BankTransaction.STATE_NOMATCH
            trans.save()
            return

    if trans.order.status == Order.STATUS_PAID:
        trans.state = BankTransaction.STATE_DUPLICATE
    elif trans.order.status == Order.STATUS_REFUNDED:
        trans.state = BankTransaction.STATE_ERROR
        trans.message = ugettext_noop('The order has already been refunded.')
    elif trans.order.status == Order.STATUS_CANCELED:
        trans.state = BankTransaction.STATE_ERROR
        trans.message = ugettext_noop('The order has already been canceled.')
    elif trans.amount != trans.order.total:
        trans.state = BankTransaction.STATE_INVALID
        trans.message = ugettext_noop('The transaction amount is incorrect.')
    else:
        try:
            mark_order_paid(trans.order, provider='banktransfer', info=json.dumps({
                'reference': trans.reference,
                'date': trans.date,
                'payer': trans.payer,
                'trans_id': trans.pk
            }))
        except Quota.QuotaExceededException as e:
            trans.state = BankTransaction.STATE_ERROR
            trans.message = str(e)
        except SendMailException:
            trans.state = BankTransaction.STATE_ERROR
            trans.message = ugettext_noop('Problem sending email.')
        else:
            trans.state = BankTransaction.STATE_VALID
    trans.save()
Example #39
0
    def filter_qs(self, qs):
        fdata = self.cleaned_data

        if fdata.get('query'):
            u = fdata.get('query')

            if "-" in u:
                code = (Q(event__slug__icontains=u.rsplit("-", 1)[0])
                        & Q(code__icontains=Order.normalize_code(u.rsplit("-", 1)[1])))
            else:
                code = Q(code__icontains=Order.normalize_code(u))

            matching_invoices = Invoice.objects.filter(
                Q(invoice_no__iexact=u)
                | Q(invoice_no__iexact=u.zfill(5))
                | Q(full_invoice_no__iexact=u)
            ).values_list('order_id', flat=True)
            matching_positions = OrderPosition.objects.filter(
                Q(
                    Q(attendee_name_cached__icontains=u) | Q(attendee_email__icontains=u)
                    | Q(secret__istartswith=u)
                    | Q(pseudonymization_id__istartswith=u)
                )
            ).values_list('order_id', flat=True)
            matching_invoice_addresses = InvoiceAddress.objects.filter(
                Q(
                    Q(name_cached__icontains=u) | Q(company__icontains=u)
                )
            ).values_list('order_id', flat=True)
            matching_orders = Order.objects.filter(
                code
                | Q(email__icontains=u)
                | Q(comment__icontains=u)
            ).values_list('id', flat=True)

            mainq = (
                Q(pk__in=matching_orders)
                | Q(pk__in=matching_invoices)
                | Q(pk__in=matching_positions)
                | Q(pk__in=matching_invoice_addresses)
                | Q(pk__in=matching_invoices)
            )
            for recv, q in order_search_filter_q.send(sender=getattr(self, 'event', None), query=u):
                mainq = mainq | q
            qs = qs.filter(
                mainq
            )

        if fdata.get('status'):
            s = fdata.get('status')
            if s == 'o':
                qs = qs.filter(status=Order.STATUS_PENDING, expires__lt=now().replace(hour=0, minute=0, second=0))
            elif s == 'np':
                qs = qs.filter(status__in=[Order.STATUS_PENDING, Order.STATUS_PAID])
            elif s == 'ne':
                qs = qs.filter(status__in=[Order.STATUS_PENDING, Order.STATUS_EXPIRED])
            elif s in ('p', 'n', 'e', 'c', 'r'):
                qs = qs.filter(status=s)
            elif s == 'overpaid':
                qs = Order.annotate_overpayments(qs, refunds=False, results=False, sums=True)
                qs = qs.filter(
                    Q(~Q(status=Order.STATUS_CANCELED) & Q(pending_sum_t__lt=0))
                    | Q(Q(status=Order.STATUS_CANCELED) & Q(pending_sum_rc__lt=0))
                )
            elif s == 'rc':
                qs = qs.filter(
                    cancellation_requests__isnull=False
                )
            elif s == 'pendingpaid':
                qs = Order.annotate_overpayments(qs, refunds=False, results=False, sums=True)
                qs = qs.filter(
                    Q(status__in=(Order.STATUS_EXPIRED, Order.STATUS_PENDING)) & Q(pending_sum_t__lte=0)
                    & Q(require_approval=False)
                )
            elif s == 'underpaid':
                qs = Order.annotate_overpayments(qs, refunds=False, results=False, sums=True)
                qs = qs.filter(
                    status=Order.STATUS_PAID,
                    pending_sum_t__gt=0
                )
            elif s == 'pa':
                qs = qs.filter(
                    status=Order.STATUS_PENDING,
                    require_approval=True
                )
            elif s == 'testmode':
                qs = qs.filter(
                    testmode=True
                )
            elif s == 'cp':
                s = OrderPosition.objects.filter(
                    order=OuterRef('pk')
                )
                qs = qs.annotate(
                    has_pc=Exists(s)
                ).filter(
                    Q(status=Order.STATUS_PAID, has_pc=False) | Q(status=Order.STATUS_CANCELED)
                )

        if fdata.get('ordering'):
            qs = qs.order_by(self.get_order_by())

        if fdata.get('provider'):
            qs = qs.annotate(
                has_payment_with_provider=Exists(
                    OrderPayment.objects.filter(
                        Q(order=OuterRef('pk')) & Q(provider=fdata.get('provider'))
                    )
                )
            )
            qs = qs.filter(has_payment_with_provider=1)

        return qs
Example #40
0
    def create(self, validated_data):
        fees_data = validated_data.pop(
            'fees') if 'fees' in validated_data else []
        positions_data = validated_data.pop(
            'positions') if 'positions' in validated_data else []
        payment_provider = validated_data.pop('payment_provider')
        payment_info = validated_data.pop('payment_info', '{}')

        if 'invoice_address' in validated_data:
            iadata = validated_data.pop('invoice_address')
            name = iadata.pop('name', '')
            if name and not iadata.get('name_parts'):
                iadata['name_parts'] = {'_legacy': name}
            ia = InvoiceAddress(**iadata)
        else:
            ia = None

        with self.context['event'].lock() as now_dt:
            quotadiff = Counter()

            consume_carts = validated_data.pop('consume_carts', [])
            delete_cps = []
            quota_avail_cache = {}
            if consume_carts:
                for cp in CartPosition.objects.filter(
                        event=self.context['event'],
                        cart_id__in=consume_carts):
                    quotas = (cp.variation.quotas.filter(subevent=cp.subevent)
                              if cp.variation else cp.item.quotas.filter(
                                  subevent=cp.subevent))
                    for quota in quotas:
                        if quota not in quota_avail_cache:
                            quota_avail_cache[quota] = list(
                                quota.availability())
                        if quota_avail_cache[quota][1] is not None:
                            quota_avail_cache[quota][1] += 1
                    if cp.expires > now_dt:
                        quotadiff.subtract(quotas)
                    delete_cps.append(cp)

            errs = [{} for p in positions_data]

            for i, pos_data in enumerate(positions_data):
                new_quotas = (pos_data.get('variation').quotas.filter(
                    subevent=pos_data.get('subevent'))
                              if pos_data.get('variation') else
                              pos_data.get('item').quotas.filter(
                                  subevent=pos_data.get('subevent')))
                if len(new_quotas) == 0:
                    errs[i]['item'] = [
                        ugettext_lazy(
                            'The product "{}" is not assigned to a quota.').
                        format(str(pos_data.get('item')))
                    ]
                else:
                    for quota in new_quotas:
                        if quota not in quota_avail_cache:
                            quota_avail_cache[quota] = list(
                                quota.availability())

                        if quota_avail_cache[quota][1] is not None:
                            quota_avail_cache[quota][1] -= 1
                            if quota_avail_cache[quota][1] < 0:
                                errs[i]['item'] = [
                                    ugettext_lazy(
                                        'There is not enough quota available on quota "{}" to perform the operation.'
                                    ).format(quota.name)
                                ]

                quotadiff.update(new_quotas)

            if any(errs):
                raise ValidationError({'positions': errs})

            if validated_data.get('locale', None) is None:
                validated_data['locale'] = self.context[
                    'event'].settings.locale
            order = Order(event=self.context['event'], **validated_data)
            order.set_expires(
                subevents=[p.get('subevent') for p in positions_data])
            order.total = sum([p['price'] for p in positions_data]) + sum(
                [f['value'] for f in fees_data], Decimal('0.00'))
            order.meta_info = "{}"
            order.save()

            if order.total == Decimal('0.00') and validated_data.get(
                    'status') != Order.STATUS_PAID:
                order.status = Order.STATUS_PAID
                order.save()
                order.payments.create(
                    amount=order.total,
                    provider='free',
                    state=OrderPayment.PAYMENT_STATE_CONFIRMED,
                    payment_date=now())
            elif payment_provider == "free" and order.total != Decimal('0.00'):
                raise ValidationError(
                    'You cannot use the "free" payment provider for non-free orders.'
                )
            elif validated_data.get('status') == Order.STATUS_PAID:
                order.payments.create(
                    amount=order.total,
                    provider=payment_provider,
                    info=payment_info,
                    payment_date=now(),
                    state=OrderPayment.PAYMENT_STATE_CONFIRMED)
            elif payment_provider:
                order.payments.create(amount=order.total,
                                      provider=payment_provider,
                                      info=payment_info,
                                      state=OrderPayment.PAYMENT_STATE_CREATED)

            if ia:
                ia.order = order
                ia.save()
            pos_map = {}
            for pos_data in positions_data:
                answers_data = pos_data.pop('answers', [])
                addon_to = pos_data.pop('addon_to', None)
                attendee_name = pos_data.pop('attendee_name', '')
                if attendee_name and not pos_data.get('attendee_name_parts'):
                    pos_data['attendee_name_parts'] = {
                        '_legacy': attendee_name
                    }
                pos = OrderPosition(**pos_data)
                pos.order = order
                pos._calculate_tax()
                if addon_to:
                    pos.addon_to = pos_map[addon_to]
                pos.save()
                pos_map[pos.positionid] = pos
                for answ_data in answers_data:
                    options = answ_data.pop('options', [])
                    answ = pos.answers.create(**answ_data)
                    answ.options.add(*options)

            for cp in delete_cps:
                cp.delete()
        for fee_data in fees_data:
            f = OrderFee(**fee_data)
            f.order = order
            f._calculate_tax()
            f.save()

        return order
Example #41
0
def _handle_transaction(trans: BankTransaction, code: str, event: Event=None, organizer: Organizer=None,
                        slug: str=None):
    if event:
        try:
            trans.order = event.orders.get(code=code)
        except Order.DoesNotExist:
            normalized_code = Order.normalize_code(code)
            try:
                trans.order = event.orders.get(code=normalized_code)
            except Order.DoesNotExist:
                trans.state = BankTransaction.STATE_NOMATCH
                trans.save()
                return
    else:
        qs = Order.objects.filter(event__organizer=organizer)
        if slug:
            qs = qs.filter(event__slug__iexact=slug)
        try:
            trans.order = qs.get(code=code)
        except Order.DoesNotExist:
            normalized_code = Order.normalize_code(code)
            try:
                trans.order = qs.get(code=normalized_code)
            except Order.DoesNotExist:
                trans.state = BankTransaction.STATE_NOMATCH
                trans.save()
                return

    if trans.order.status == Order.STATUS_PAID and trans.order.pending_sum <= Decimal('0.00'):
        trans.state = BankTransaction.STATE_DUPLICATE
    elif trans.order.status == Order.STATUS_REFUNDED:
        trans.state = BankTransaction.STATE_ERROR
        trans.message = ugettext_noop('The order has already been refunded.')
    elif trans.order.status == Order.STATUS_CANCELED:
        trans.state = BankTransaction.STATE_ERROR
        trans.message = ugettext_noop('The order has already been canceled.')
    else:
        p = trans.order.payments.get_or_create(
            amount=trans.amount,
            provider='banktransfer',
            state__in=(OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_PENDING),
            defaults={
                'state': OrderPayment.PAYMENT_STATE_CREATED,
            }
        )[0]
        p.info_data = {
            'reference': trans.reference,
            'date': trans.date,
            'payer': trans.payer,
            'trans_id': trans.pk
        }
        try:
            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)
        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)
        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)
    trans.save()
Example #42
0
def mail(email: str, subject: str, template: Union[str, LazyI18nString],
         context: Dict[str, Any]=None, event: Event=None, locale: str=None,
         order: Order=None, position: OrderPosition=None, headers: dict=None, sender: str=None,
         invoices: list=None, attach_tickets=False):
    """
    Sends out an email to a user. The mail will be sent synchronously or asynchronously depending on the installation.

    :param email: The email address of the recipient

    :param subject: The email subject. Should be localized to the recipients's locale or a lazy object that will be
        localized by being casted to a string.

    :param template: The filename of a template to be used. It will be rendered with the locale given in the locale
        argument and the context given in the next argument. Alternatively, you can pass a LazyI18nString and
        ``context`` will be used as the argument to a  Python ``.format_map()`` call on the template.

    :param context: The context for rendering the template (see ``template`` parameter)

    :param event: The event this email is related to (optional). If set, this will be used to determine the sender,
        a possible prefix for the subject and the SMTP server that should be used to send this email.

    :param order: The order this email is related to (optional). If set, this will be used to include a link to the
        order below the email.

    :param order: The order position this email is related to (optional). If set, this will be used to include a link
        to the order position instead of the order below the email.

    :param headers: A dict of custom mail headers to add to the mail

    :param locale: The locale to be used while evaluating the subject and the template

    :param sender: Set the sender email address. If not set and ``event`` is set, the event's default will be used,
        otherwise the system default.

    :param invoices: A list of invoices to attach to this email.

    :param attach_tickets: Whether to attach tickets to this email, if they are available to download.

    :raises MailOrderException: on obvious, immediate failures. Not raising an exception does not necessarily mean
        that the email has been sent, just that it has been queued by the email backend.
    """
    if email == INVALID_ADDRESS:
        return

    headers = headers or {}

    with language(locale):
        if isinstance(context, dict) and event:
            for k, v in event.meta_data.items():
                context['meta_' + k] = v

        if isinstance(context, dict) and order:
            try:
                context.update({
                    'invoice_name': order.invoice_address.name,
                    'invoice_company': order.invoice_address.company
                })
            except InvoiceAddress.DoesNotExist:
                context.update({
                    'invoice_name': '',
                    'invoice_company': ''
                })
        renderer = ClassicMailRenderer(None)
        content_plain = body_plain = render_mail(template, context)
        subject = str(subject).format_map(context)
        sender = sender or (event.settings.get('mail_from') if event else settings.MAIL_FROM)
        if event:
            sender_name = event.settings.mail_from_name or str(event.name)
            sender = formataddr((sender_name, sender))
        else:
            sender = formataddr((settings.PRETIX_INSTANCE_NAME, sender))

        subject = str(subject)
        signature = ""

        bcc = []
        if event:
            renderer = event.get_html_mail_renderer()
            if event.settings.mail_bcc:
                for bcc_mail in event.settings.mail_bcc.split(','):
                    bcc.append(bcc_mail.strip())

            if event.settings.mail_from == settings.DEFAULT_FROM_EMAIL and event.settings.contact_mail and not headers.get('Reply-To'):
                headers['Reply-To'] = event.settings.contact_mail

            prefix = event.settings.get('mail_prefix')
            if prefix and prefix.startswith('[') and prefix.endswith(']'):
                prefix = prefix[1:-1]
            if prefix:
                subject = "[%s] %s" % (prefix, subject)

            body_plain += "\r\n\r\n-- \r\n"

            signature = str(event.settings.get('mail_text_signature'))
            if signature:
                signature = signature.format(event=event.name)
                body_plain += signature
                body_plain += "\r\n\r\n-- \r\n"

            if order and order.testmode:
                subject = "[TESTMODE] " + subject

            if order and position:
                body_plain += _(
                    "You are receiving this email because someone placed an order for {event} for you."
                ).format(event=event.name)
                body_plain += "\r\n"
                body_plain += _(
                    "You can view your order details at the following URL:\n{orderurl}."
                ).replace("\n", "\r\n").format(
                    event=event.name, orderurl=build_absolute_uri(
                        order.event, 'presale:event.order.position', kwargs={
                            'order': order.code,
                            'secret': position.web_secret,
                            'position': position.positionid,
                        }
                    )
                )
            elif order:
                body_plain += _(
                    "You are receiving this email because you placed an order for {event}."
                ).format(event=event.name)
                body_plain += "\r\n"
                body_plain += _(
                    "You can view your order details at the following URL:\n{orderurl}."
                ).replace("\n", "\r\n").format(
                    event=event.name, orderurl=build_absolute_uri(
                        order.event, 'presale:event.order.open', kwargs={
                            'order': order.code,
                            'secret': order.secret,
                            'hash': order.email_confirm_hash()
                        }
                    )
                )
            body_plain += "\r\n"

        try:
            try:
                body_html = renderer.render(content_plain, signature, str(subject), order, position)
            except TypeError:
                # Backwards compatibility
                warnings.warn('E-mail renderer called without position argument because position argument is not '
                              'supported.',
                              DeprecationWarning)
                body_html = renderer.render(content_plain, signature, str(subject), order)
        except:
            logger.exception('Could not render HTML body')
            body_html = None

        send_task = mail_send_task.si(
            to=[email],
            bcc=bcc,
            subject=subject,
            body=body_plain,
            html=body_html,
            sender=sender,
            event=event.id if event else None,
            headers=headers,
            invoices=[i.pk for i in invoices] if invoices and not position else [],
            order=order.pk if order else None,
            position=position.pk if position else None,
            attach_tickets=attach_tickets
        )

        if invoices:
            task_chain = [invoice_pdf_task.si(i.pk).on_error(send_task) for i in invoices if not i.file]
        else:
            task_chain = []

        task_chain.append(send_task)
        chain(*task_chain).apply_async()
Example #43
0
 def get_order(self, code):
     try:
         return Order.objects.get(code=code, event=self.request.event)
     except Order.DoesNotExist:
         return Order.objects.get(code=Order.normalize_code(code), event=self.request.event)
Example #44
0
    def create(self, validated_data):
        fees_data = validated_data.pop('fees') if 'fees' in validated_data else []
        positions_data = validated_data.pop('positions') if 'positions' in validated_data else []
        payment_provider = validated_data.pop('payment_provider')
        payment_info = validated_data.pop('payment_info', '{}')
        payment_date = validated_data.pop('payment_date', now())
        force = validated_data.pop('force', False)

        if 'invoice_address' in validated_data:
            iadata = validated_data.pop('invoice_address')
            name = iadata.pop('name', '')
            if name and not iadata.get('name_parts'):
                iadata['name_parts'] = {
                    '_legacy': name
                }
            ia = InvoiceAddress(**iadata)
        else:
            ia = None

        with self.context['event'].lock() as now_dt:
            quotadiff = Counter()

            consume_carts = validated_data.pop('consume_carts', [])
            delete_cps = []
            quota_avail_cache = {}
            if consume_carts:
                for cp in CartPosition.objects.filter(event=self.context['event'], cart_id__in=consume_carts):
                    quotas = (cp.variation.quotas.filter(subevent=cp.subevent)
                              if cp.variation else cp.item.quotas.filter(subevent=cp.subevent))
                    for quota in quotas:
                        if quota not in quota_avail_cache:
                            quota_avail_cache[quota] = list(quota.availability())
                        if quota_avail_cache[quota][1] is not None:
                            quota_avail_cache[quota][1] += 1
                    if cp.expires > now_dt:
                        quotadiff.subtract(quotas)
                    delete_cps.append(cp)

            errs = [{} for p in positions_data]

            if not force:
                for i, pos_data in enumerate(positions_data):
                    new_quotas = (pos_data.get('variation').quotas.filter(subevent=pos_data.get('subevent'))
                                  if pos_data.get('variation')
                                  else pos_data.get('item').quotas.filter(subevent=pos_data.get('subevent')))
                    if len(new_quotas) == 0:
                        errs[i]['item'] = [ugettext_lazy('The product "{}" is not assigned to a quota.').format(
                            str(pos_data.get('item'))
                        )]
                    else:
                        for quota in new_quotas:
                            if quota not in quota_avail_cache:
                                quota_avail_cache[quota] = list(quota.availability())

                            if quota_avail_cache[quota][1] is not None:
                                quota_avail_cache[quota][1] -= 1
                                if quota_avail_cache[quota][1] < 0:
                                    errs[i]['item'] = [
                                        ugettext_lazy('There is not enough quota available on quota "{}" to perform the operation.').format(
                                            quota.name
                                        )
                                    ]

                    quotadiff.update(new_quotas)

            if any(errs):
                raise ValidationError({'positions': errs})

            if validated_data.get('locale', None) is None:
                validated_data['locale'] = self.context['event'].settings.locale
            order = Order(event=self.context['event'], **validated_data)
            order.set_expires(subevents=[p.get('subevent') for p in positions_data])
            order.total = sum([p['price'] for p in positions_data]) + sum([f['value'] for f in fees_data], Decimal('0.00'))
            order.meta_info = "{}"
            order.save()

            if order.total == Decimal('0.00') and validated_data.get('status') != Order.STATUS_PAID:
                order.status = Order.STATUS_PAID
                order.save()
                order.payments.create(
                    amount=order.total, provider='free', state=OrderPayment.PAYMENT_STATE_CONFIRMED,
                    payment_date=now()
                )
            elif payment_provider == "free" and order.total != Decimal('0.00'):
                raise ValidationError('You cannot use the "free" payment provider for non-free orders.')
            elif validated_data.get('status') == Order.STATUS_PAID:
                order.payments.create(
                    amount=order.total,
                    provider=payment_provider,
                    info=payment_info,
                    payment_date=payment_date,
                    state=OrderPayment.PAYMENT_STATE_CONFIRMED
                )
            elif payment_provider:
                order.payments.create(
                    amount=order.total,
                    provider=payment_provider,
                    info=payment_info,
                    state=OrderPayment.PAYMENT_STATE_CREATED
                )

            if ia:
                ia.order = order
                ia.save()
            pos_map = {}
            for pos_data in positions_data:
                answers_data = pos_data.pop('answers', [])
                addon_to = pos_data.pop('addon_to', None)
                attendee_name = pos_data.pop('attendee_name', '')
                if attendee_name and not pos_data.get('attendee_name_parts'):
                    pos_data['attendee_name_parts'] = {
                        '_legacy': attendee_name
                    }
                pos = OrderPosition(**pos_data)
                pos.order = order
                pos._calculate_tax()
                if addon_to:
                    pos.addon_to = pos_map[addon_to]
                pos.save()
                pos_map[pos.positionid] = pos
                for answ_data in answers_data:
                    options = answ_data.pop('options', [])
                    answ = pos.answers.create(**answ_data)
                    answ.options.add(*options)

            for cp in delete_cps:
                cp.delete()
        for fee_data in fees_data:
            f = OrderFee(**fee_data)
            f.order = order
            f._calculate_tax()
            f.save()

        return order
Example #45
0
    def filter_qs(self, qs):
        fdata = self.cleaned_data
        qs = super().filter_qs(qs)

        if fdata.get('item'):
            qs = qs.filter(all_positions__item=fdata.get('item'), all_positions__canceled=False).distinct()

        if fdata.get('subevent'):
            qs = qs.filter(all_positions__subevent=fdata.get('subevent'), all_positions__canceled=False).distinct()

        if fdata.get('question') and fdata.get('answer') is not None:
            q = fdata.get('question')

            if q.type == Question.TYPE_FILE:
                answers = QuestionAnswer.objects.filter(
                    orderposition__order_id=OuterRef('pk'),
                    question_id=q.pk,
                    file__isnull=False
                )
                qs = qs.annotate(has_answer=Exists(answers)).filter(has_answer=True)
            elif q.type in (Question.TYPE_CHOICE, Question.TYPE_CHOICE_MULTIPLE):
                answers = QuestionAnswer.objects.filter(
                    question_id=q.pk,
                    orderposition__order_id=OuterRef('pk'),
                    options__pk=fdata.get('answer')
                )
                qs = qs.annotate(has_answer=Exists(answers)).filter(has_answer=True)
            else:
                answers = QuestionAnswer.objects.filter(
                    question_id=q.pk,
                    orderposition__order_id=OuterRef('pk'),
                    answer__iexact=fdata.get('answer')
                )
                qs = qs.annotate(has_answer=Exists(answers)).filter(has_answer=True)

        if fdata.get('status') == 'overpaid':
            qs = Order.annotate_overpayments(qs, refunds=False, results=False, sums=True)
            qs = qs.filter(
                Q(~Q(status=Order.STATUS_CANCELED) & Q(pending_sum_t__lt=0))
                | Q(Q(status=Order.STATUS_CANCELED) & Q(pending_sum_rc__lt=0))
            )
        elif fdata.get('status') == 'pendingpaid':
            qs = Order.annotate_overpayments(qs, refunds=False, results=False, sums=True)
            qs = qs.filter(
                Q(status__in=(Order.STATUS_EXPIRED, Order.STATUS_PENDING)) & Q(pending_sum_t__lte=0)
                & Q(require_approval=False)
            )
        elif fdata.get('status') == 'underpaid':
            qs = Order.annotate_overpayments(qs, refunds=False, results=False, sums=True)
            qs = qs.filter(
                status=Order.STATUS_PAID,
                pending_sum_t__gt=0
            )
        elif fdata.get('status') == 'pa':
            qs = qs.filter(
                status=Order.STATUS_PENDING,
                require_approval=True
            )
        elif fdata.get('status') == 'testmode':
            qs = qs.filter(
                testmode=True
            )
        elif fdata.get('status') == 'cp':
            s = OrderPosition.objects.filter(
                order=OuterRef('pk')
            )
            qs = qs.annotate(
                has_pc=Exists(s)
            ).filter(
                Q(status=Order.STATUS_PAID, has_pc=False) | Q(status=Order.STATUS_CANCELED)
            )

        return qs
Example #46
0
def _handle_transaction(trans: BankTransaction, code: str, event: Event=None, organizer: Organizer=None,
                        slug: str=None):
    if event:
        try:
            trans.order = event.orders.get(code=code)
        except Order.DoesNotExist:
            normalized_code = Order.normalize_code(code)
            try:
                trans.order = event.orders.get(code=normalized_code)
            except Order.DoesNotExist:
                trans.state = BankTransaction.STATE_NOMATCH
                trans.save()
                return
    else:
        qs = Order.objects.filter(event__organizer=organizer)
        if slug:
            qs = qs.filter(event__slug__iexact=slug)
        try:
            trans.order = qs.get(code=code)
        except Order.DoesNotExist:
            normalized_code = Order.normalize_code(code)
            try:
                trans.order = qs.get(code=normalized_code)
            except Order.DoesNotExist:
                trans.state = BankTransaction.STATE_NOMATCH
                trans.save()
                return

    if trans.order.status == Order.STATUS_PAID and trans.order.pending_sum <= Decimal('0.00'):
        trans.state = BankTransaction.STATE_DUPLICATE
    elif trans.order.status == Order.STATUS_CANCELED:
        trans.state = BankTransaction.STATE_ERROR
        trans.message = ugettext_noop('The order has already been canceled.')
    else:
        try:
            p, created = trans.order.payments.get_or_create(
                amount=trans.amount,
                provider='banktransfer',
                state__in=(OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_PENDING),
                defaults={
                    'state': OrderPayment.PAYMENT_STATE_CREATED,
                }
            )
        except OrderPayment.MultipleObjectsReturned:
            created = False
            p = trans.order.payments.filter(
                amount=trans.amount,
                provider='banktransfer',
                state__in=(OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_PENDING),
            ).last()

        p.info_data = {
            'reference': trans.reference,
            'date': trans.date,
            'payer': trans.payer,
            'trans_id': trans.pk
        }

        if created:
            # We're perform a payment method switchign on-demand here
            old_fee, new_fee, fee = change_payment_provider(trans.order, p.payment_provider, p.amount, new_payment=p)  # noqa
            if fee:
                p.fee = fee
                p.save(update_fields=['fee'])

        try:
            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)
        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)
        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)

            o = trans.order
            o.refresh_from_db()
            if o.pending_sum > Decimal('0.00') and o.status == Order.STATUS_PENDING:
                notify_incomplete_payment(o)

    trans.save()
Example #47
0
def mark_order_paid(order: Order, provider: str=None, info: str=None, date: datetime=None, manual: bool=None,
                    force: bool=False, send_mail: bool=True, user: User=None, mail_text='') -> Order:
    """
    Marks an order as paid. This sets the payment provider, info and date and returns
    the order object.

    :param provider: The payment provider that marked this as paid
    :type provider: str
    :param info: The information to store in order.payment_info
    :type info: str
    :param date: The date the payment was received (if you pass ``None``, the current
                 time will be used).
    :type date: datetime
    :param force: Whether this payment should be marked as paid even if no remaining
                  quota is available (default: ``False``).
    :type force: boolean
    :param send_mail: Whether an email should be sent to the user about this event (default: ``True``).
    :type send_mail: boolean
    :param user: The user that performed the change
    :param mail_text: Additional text to be included in the email
    :type mail_text: str
    :raises Quota.QuotaExceededException: if the quota is exceeded and ``force`` is ``False``
    """
    if order.status == Order.STATUS_PAID:
        return order

    with order.event.lock() as now_dt:
        can_be_paid = order._can_be_paid()
        if not force and can_be_paid is not True:
            raise Quota.QuotaExceededException(can_be_paid)
        order.payment_provider = provider or order.payment_provider
        order.payment_info = info or order.payment_info
        order.payment_date = date or now_dt
        if manual is not None:
            order.payment_manual = manual
        order.status = Order.STATUS_PAID
        order.save()

    order.log_action('pretix.event.order.paid', {
        'provider': provider,
        'info': info,
        'date': date or now_dt,
        'manual': manual,
        'force': force
    }, user=user)
    order_paid.send(order.event, order=order)

    if order.event.settings.get('invoice_generate') in ('True', 'paid') and invoice_qualified(order):
        if not order.invoices.exists():
            generate_invoice(order)

    if send_mail:
        with language(order.locale):
            try:
                invoice_name = order.invoice_address.name
                invoice_company = order.invoice_address.company
            except InvoiceAddress.DoesNotExist:
                invoice_name = ""
                invoice_company = ""
            email_template = order.event.settings.mail_text_order_paid
            email_context = {
                'event': order.event.name,
                'url': build_absolute_uri(order.event, 'presale:event.order', kwargs={
                    'order': order.code,
                    'secret': order.secret
                }),
                'downloads': order.event.settings.get('ticket_download', as_type=bool),
                'invoice_name': invoice_name,
                'invoice_company': invoice_company,
                'payment_info': mail_text
            }
            email_subject = _('Payment received for your order: %(code)s') % {'code': order.code}
            try:
                order.send_mail(
                    email_subject, email_template, email_context,
                    'pretix.event.order.email.order_paid', user
                )
            except SendMailException:
                logger.exception('Order paid email could not be sent')

    return order
Example #48
0
File: views.py Project: rixx/pretix
    def get(self, request, *args, **kwargs):
        query = request.GET.get('query', '')
        if len(query) < 2:
            return JsonResponse({'results': []})

        qs = self.request.event.orders.filter(Q(code__icontains=query) | Q(code__icontains=Order.normalize_code(query)))
        return JsonResponse({
            'results': [
                {
                    'code': o.code,
                    'status': o.get_status_display(),
                    'total': lformat("%.2f", o.total) + ' ' + self.request.event.currency
                } for o in qs
            ]
        })