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 ] })
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')
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
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'])
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
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')
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')
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] })
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
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
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'])
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
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
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
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
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
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'])
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
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'])
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)
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)
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')
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')
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
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.')
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
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
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
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.'))
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'])
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] })
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] })
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()
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
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
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
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()
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()
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)
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
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
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()
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
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 ] })