def test_match_discount_quantity(self): """Method to test the quantity calculation of discount code""" with self.app.test_request_context(): ticket = TicketFactory() discount_code = DiscountCodeTicketFactory(tickets_number=5) discount_code.tickets.append(ticket) order_without_discount = OrderFactory(status='completed') db.session.commit() # Attendees associated with the order without discount code should not be counted AttendeeFactoryBase.create_batch( 10, order_id=order_without_discount.id, ticket_id=ticket.id) self.assertTrue( TicketingManager.match_discount_quantity(discount_code, ticket_holders=[1])) order_with_discount = OrderFactory( status='completed', discount_code_id=discount_code.id) db.session.commit() # Attendees associated with the order with discount code should be counted AttendeeFactoryBase.create_batch(5, order_id=order_with_discount.id, ticket_id=ticket.id) self.assertFalse( TicketingManager.match_discount_quantity(discount_code, ticket_holders=[1])) self.assertEqual(5, discount_code.confirmed_attendees_count)
def create_object(self, data, view_kwargs): """ create_object method for the Charges layer charge the user using paypal or stripe :param data: :param view_kwargs: :return: """ if view_kwargs.get('order_identifier').isdigit(): # when id is passed order = Order.query.filter_by(id=view_kwargs['order_identifier']).first() else: # when identifier is passed order = Order.query.filter_by(identifier=view_kwargs['order_identifier']).first() if not order: raise ObjectNotFound({'parameter': 'order_identifier'}, "Order with identifier: {} not found".format(view_kwargs['order_identifier'])) elif order.status == 'cancelled' or order.status == 'expired' or order.status == 'completed': raise ConflictException({'parameter': 'id'}, "You cannot charge payments on a cancelled, expired or completed order") elif (not order.amount) or order.amount == 0: raise ConflictException({'parameter': 'id'}, "You cannot charge payments on a free order") data['id'] = order.id # charge through stripe if order.payment_mode == 'stripe': if not data.get('stripe'): raise UnprocessableEntity({'source': ''}, "stripe token is missing") if not order.event.can_pay_by_stripe: raise ConflictException({'': ''}, "This event doesn't accept payments by Stripe") success, response = TicketingManager.charge_stripe_order_payment(order, data['stripe']) data['status'] = success data['message'] = response # charge through paypal elif order.payment_mode == 'paypal': if (not data.get('paypal_payer_id')) or (not data.get('paypal_payment_id')): raise UnprocessableEntity({'source': ''}, "paypal_payer_id or paypal_payment_id or both missing") if not order.event.can_pay_by_paypal: raise ConflictException({'': ''}, "This event doesn't accept payments by Paypal") success, response = TicketingManager.charge_paypal_order_payment(order, data['paypal_payer_id'], data['paypal_payment_id']) data['status'] = success data['message'] = response return data
def create_object(self, data, view_kwargs): order = Order.query.filter_by(id=view_kwargs['id']).first() if order.payment_mode == 'stripe': if data.get('stripe') is None: raise UnprocessableEntity({'source': ''}, "stripe token is missing") success, response = TicketingManager.charge_stripe_order_payment(order, data['stripe']) if not success: raise UnprocessableEntity({'source': 'stripe_token_id'}, response) elif order.payment_mode == 'paypal': success, response = TicketingManager.charge_paypal_order_payment(order) if not success: raise UnprocessableEntity({'source': ''}, response) return order
def after_create_object(self, order, data, view_kwargs): """ after create object method for OrderListPost Class :param order: Object created from mashmallow_jsonapi :param data: :param view_kwargs: :return: """ order_tickets = {} for holder in order.ticket_holders: save_to_db(holder) if not order_tickets.get(holder.ticket_id): order_tickets[holder.ticket_id] = 1 else: order_tickets[holder.ticket_id] += 1 order.user = current_user # create pdf tickets. create_pdf_tickets_for_holder(order) for ticket in order_tickets: od = OrderTicket(order_id=order.id, ticket_id=ticket, quantity=order_tickets[ticket]) save_to_db(od) order.quantity = order.tickets_count save_to_db(order) if not has_access('is_coorganizer', event_id=data['event']): TicketingManager.calculate_update_amount(order) # send e-mail and notifications if the order status is completed if order.status == 'completed': send_email_to_attendees(order, current_user.id) send_notif_to_attendees(order, current_user.id) order_url = make_frontend_url(path='/orders/{identifier}'.format( identifier=order.identifier)) for organizer in order.event.organizers: send_notif_ticket_purchase_organizer(organizer, order.invoice_number, order_url, order.event.name, order.identifier) data['user_id'] = current_user.id
def before_create_object(self, data, view_kwargs): """ before create object method for OrderListPost Class :param data: :param view_kwargs: :return: """ for ticket_holder in data['ticket_holders']: # Ensuring that the attendee exists and doesn't have an associated order. try: ticket_holder_object = self.session.query( TicketHolder).filter_by(id=int(ticket_holder), deleted_at=None).one() if ticket_holder_object.order_id: raise ConflictException( {'pointer': '/data/relationships/attendees'}, "Order already exists for attendee with id {}".format( str(ticket_holder))) except NoResultFound: raise ConflictException( {'pointer': '/data/relationships/attendees'}, "Attendee with id {} does not exists".format( str(ticket_holder))) if data.get('cancel_note'): del data['cancel_note'] if data.get('payment_mode') != 'free' and not data.get('amount'): raise ConflictException({'pointer': '/data/attributes/amount'}, "Amount cannot be null for a paid order") if not data.get('amount'): data['amount'] = 0 # Apply discount only if the user is not event admin if data.get('discount') and not has_access('is_coorganizer', event_id=data['event']): discount_code = safe_query_without_soft_deleted_entries( self, DiscountCode, 'id', data['discount'], 'discount_code_id') if not discount_code.is_active: raise UnprocessableEntity({'source': 'discount_code_id'}, "Inactive Discount Code") else: now = datetime.utcnow() valid_from = datetime.strptime(discount_code.valid_from, '%Y-%m-%d %H:%M:%S') valid_till = datetime.strptime(discount_code.valid_till, '%Y-%m-%d %H:%M:%S') if not (valid_from <= now <= valid_till): raise UnprocessableEntity({'source': 'discount_code_id'}, "Inactive Discount Code") if not TicketingManager.match_discount_quantity( discount_code, data['ticket_holders']): raise UnprocessableEntity({'source': 'discount_code_id'}, 'Discount Usage Exceeded') if discount_code.event.id != data[ 'event'] and discount_code.user_for == TICKET: raise UnprocessableEntity({'source': 'discount_code_id'}, "Invalid Discount Code")
def after_create_object(self, order, data, view_kwargs): """ after create object method for OrderListPost Class :param order: :param data: :param view_kwargs: :return: """ order_tickets = {} for holder in order.ticket_holders: if holder.id != current_user.id: pdf = create_save_pdf( render_template('/pdf/ticket_attendee.html', order=order, holder=holder)) else: pdf = create_save_pdf( render_template('/pdf/ticket_purchaser.html', order=order)) holder.pdf_url = pdf save_to_db(holder) if order_tickets.get(holder.ticket_id) is None: order_tickets[holder.ticket_id] = 1 else: order_tickets[holder.ticket_id] += 1 for ticket in order_tickets: od = OrderTicket(order_id=order.id, ticket_id=ticket, quantity=order_tickets[ticket]) save_to_db(od) order.quantity = order.get_tickets_count() save_to_db(order) if not has_access('is_coorganizer', event_id=data['event']): TicketingManager.calculate_update_amount(order) send_email_to_attendees(order, current_user.id) send_notif_to_attendees(order, current_user.id) order_url = make_frontend_url(path='/orders/{identifier}'.format( identifier=order.identifier)) for organizer in order.event.organizers: send_notif_ticket_purchase_organizer(organizer, order.invoice_number, order_url, order.event.name) data['user_id'] = current_user.id
def calculate_amount(): data = request.get_json() tickets = data['tickets'] discount_code = None if 'discount-code' in data: discount_code_id = data['discount-code'] discount_code = safe_query(db, DiscountCode, 'id', discount_code_id, 'id') if not TicketingManager.match_discount_quantity(discount_code, tickets, None): return UnprocessableEntityError({'source': 'discount-code'}, 'Discount Usage Exceeded').respond() return jsonify(calculate_order_amount(tickets, discount_code))
def after_create_object(self, order, data, view_kwargs): """ after create object method for OrderListPost Class :param order: Object created from mashmallow_jsonapi :param data: :param view_kwargs: :return: """ order_tickets = {} for holder in order.ticket_holders: save_to_db(holder) if not order_tickets.get(holder.ticket_id): order_tickets[holder.ticket_id] = 1 else: order_tickets[holder.ticket_id] += 1 order.user = current_user # create pdf tickets. create_pdf_tickets_for_holder(order) for ticket in order_tickets: od = OrderTicket(order_id=order.id, ticket_id=ticket, quantity=order_tickets[ticket]) save_to_db(od) order.quantity = order.tickets_count save_to_db(order) if not has_access('is_coorganizer', event_id=data['event']): TicketingManager.calculate_update_amount(order) # send e-mail and notifications if the order status is completed if order.status == 'completed': send_email_to_attendees(order, current_user.id) send_notif_to_attendees(order, current_user.id) order_url = make_frontend_url(path='/orders/{identifier}'.format(identifier=order.identifier)) for organizer in order.event.organizers: send_notif_ticket_purchase_organizer(organizer, order.invoice_number, order_url, order.event.name, order.identifier) data['user_id'] = current_user.id
def before_create_object(self, data, view_kwargs): """ before create object method for OrderListPost Class :param data: :param view_kwargs: :return: """ for ticket_holder in data['ticket_holders']: # Ensuring that the attendee exists and doesn't have an associated order. try: ticket_holder_object = self.session.query(TicketHolder).filter_by(id=int(ticket_holder), deleted_at=None).one() if ticket_holder_object.order_id: raise ConflictException({'pointer': '/data/relationships/attendees'}, "Order already exists for attendee with id {}".format(str(ticket_holder))) except NoResultFound: raise ConflictException({'pointer': '/data/relationships/attendees'}, "Attendee with id {} does not exists".format(str(ticket_holder))) if data.get('cancel_note'): del data['cancel_note'] if data.get('payment_mode') != 'free' and not data.get('amount'): raise ConflictException({'pointer': '/data/attributes/amount'}, "Amount cannot be null for a paid order") # Apply discount only if the user is not event admin if data.get('discount') and not has_access('is_coorganizer', event_id=data['event']): discount_code = safe_query_without_soft_deleted_entries(self, DiscountCode, 'id', data['discount'], 'discount_code_id') if not discount_code.is_active: raise UnprocessableEntity({'source': 'discount_code_id'}, "Inactive Discount Code") else: now = datetime.utcnow() valid_from = datetime.strptime(discount_code.valid_from, '%Y-%m-%d %H:%M:%S') valid_till = datetime.strptime(discount_code.valid_till, '%Y-%m-%d %H:%M:%S') if not (valid_from <= now <= valid_till): raise UnprocessableEntity({'source': 'discount_code_id'}, "Inactive Discount Code") if not TicketingManager.match_discount_quantity(discount_code, data['ticket_holders']): raise UnprocessableEntity({'source': 'discount_code_id'}, 'Discount Usage Exceeded') if discount_code.event.id != data['event'] and discount_code.user_for == TICKET: raise UnprocessableEntity({'source': 'discount_code_id'}, "Invalid Discount Code")
def before_create_object(self, data, view_kwargs): """ before create object method for OrderListPost Class :param data: :param view_kwargs: :return: """ if data.get('cancel_note'): del data['cancel_note'] # Apply discount only if the user is not event admin if data.get('discount') and not has_access('is_coorganizer', event_id=data['event']): discount_code = safe_query(self, DiscountCode, 'id', data['discount'], 'discount_code_id') if not discount_code.is_active: raise UnprocessableEntity({'source': 'discount_code_id'}, "Inactive Discount Code") else: now = datetime.utcnow() valid_from = datetime.strptime(discount_code.valid_from, '%Y-%m-%d %H:%M:%S') valid_till = datetime.strptime(discount_code.valid_till, '%Y-%m-%d %H:%M:%S') if not (valid_from <= now <= valid_till): raise UnprocessableEntity({'source': 'discount_code_id'}, "Inactive Discount Code") if not TicketingManager.match_discount_quantity( discount_code, data['ticket_holders']): raise UnprocessableEntity({'source': 'discount_code_id'}, 'Discount Usage Exceeded') if discount_code.event.id != data[ 'event'] and discount_code.user_for == TICKET: raise UnprocessableEntity({'source': 'discount_code_id'}, "Invalid Discount Code")
def create_object(self, data, view_kwargs): """ create_object method for the Charges layer charge the user using paypal or stripe :param data: :param view_kwargs: :return: """ if view_kwargs.get('order_identifier').isdigit(): # when id is passed order = Order.query.filter_by( id=view_kwargs['order_identifier']).first() else: # when identifier is passed order = Order.query.filter_by( identifier=view_kwargs['order_identifier']).first() if not order: raise ObjectNotFound( {'parameter': 'order_identifier'}, "Order with identifier: {} not found".format( view_kwargs['order_identifier']), ) if (order.status == 'cancelled' or order.status == 'expired' or order.status == 'completed'): raise ConflictError( {'parameter': 'id'}, "You cannot charge payments on a cancelled, expired or completed order", ) if (not order.amount) or order.amount == 0: raise ConflictError({'parameter': 'id'}, "You cannot charge payments on a free order") data['id'] = order.id # charge through stripe if order.payment_mode == 'stripe': if not order.event.can_pay_by_stripe: raise ConflictError( {'': ''}, "This event doesn't accept payments by Stripe") success, response = TicketingManager.charge_stripe_order_payment( order) data['status'] = success data['message'] = response # charge through paypal elif order.payment_mode == 'paypal': if (not data.get('paypal_payer_id')) or ( not data.get('paypal_payment_id')): raise UnprocessableEntityError( {'source': ''}, "paypal_payer_id or paypal_payment_id or both missing") if not order.event.can_pay_by_paypal: raise ConflictError( {'': ''}, "This event doesn't accept payments by Paypal") success, response = TicketingManager.charge_paypal_order_payment( order, data['paypal_payer_id'], data['paypal_payment_id']) data['status'] = success data['message'] = response return data
def calculate_order_amount(tickets, discount_code): from app.api.helpers.ticketing import TicketingManager if discount_code: if not TicketingManager.match_discount_quantity(discount_code, tickets, None): return UnprocessableEntityError({'source': 'discount-code'}, 'Discount Usage Exceeded').respond() event = tax = tax_included = fees = None total_amount = total_tax = total_discount = 0.0 ticket_list = [] for ticket_info in tickets: tax_amount = tax_percent = 0.0 tax_data = {} discount_amount = discount_percent = 0.0 discount_data = {} sub_total = ticket_fee = 0.0 ticket_identifier = ticket_info['id'] quantity = ticket_info['quantity'] ticket = safe_query_without_soft_deleted_entries(db, Ticket, 'id', ticket_identifier, 'id') if not event: event = ticket.event fees = TicketFees.query.filter_by(currency=event.payment_currency).first() elif ticket.event.id != event.id: raise UnprocessableEntity({'source': 'data/tickets'}, "Invalid Ticket") if not tax and event.tax: tax = event.tax tax_included = tax.is_tax_included_in_price if ticket.type == 'donation': price = ticket_info.get('price') if not price or price > ticket.max_price or price < ticket.min_price: raise UnprocessableEntity({'source': 'data/tickets'}, "Price for donation ticket invalid") else: price = ticket.price if discount_code: for code in ticket.discount_codes: if discount_code.id == code.id: if code.type == 'amount': discount_amount = code.value discount_percent = (discount_amount / price) * 100 else: discount_amount = (price * code.value)/100 discount_percent = code.value discount_data = { 'code': discount_code.code, 'percent': round(discount_percent, 2), 'amount': round(discount_amount, 2) } if tax: if not tax_included: tax_amount = ((price - discount_amount) * tax.rate)/100 tax_percent = tax.rate else: tax_amount = ((price - discount_amount) * tax.rate)/(100 + tax.rate) tax_percent = tax.rate tax_data = { 'percent': round(tax_percent, 2), 'amount': round(tax_amount, 2), } total_tax = total_tax + tax_amount * quantity total_discount = total_discount + discount_amount*quantity if fees and not ticket.is_fee_absorbed: ticket_fee = fees.service_fee * (price * quantity) / 100 if ticket_fee > fees.maximum_fee: ticket_fee = fees.maximum_fee if tax_included: sub_total = ticket_fee + (price - discount_amount)*quantity else: sub_total = ticket_fee + (price + tax_amount - discount_amount)*quantity total_amount = total_amount + sub_total ticket_list.append({ 'id': ticket.id, 'name': ticket.name, 'price': price, 'quantity': quantity, 'discount': discount_data, 'tax': tax_data, 'ticket_fee': round(ticket_fee, 2), 'sub_total': round(sub_total, 2) }) return dict(tax_included=tax_included, total_amount=round(total_amount, 2), total_tax=round(total_tax, 2), total_discount=round(total_discount, 2), tickets=ticket_list)