Example #1
0
    def handle(self, sqs_message: SqsMessage) -> None:
        message_type = sqs_message.message_type
        message_data = sqs_message.message_data

        if message_type != 'customer_info':
            raise ValueError('SQS Message type "' + message_type + '" is unknown for ' + self.__class__.__name__)

        customer_data = message_data.get('customer')
        if not customer_data:
            raise ValueError('SQS Message does not have customer field')

        email = str(customer_data.get('email', '')).strip()
        if not email:
            raise ValueError('SQS Message does not have email field')

        information_model = self.__information_service.get(email)
        information = information_model.get_information()
        information.first_name = customer_data.get('first_name')
        information.last_name = customer_data.get('last_name')
        information.gender = customer_data.get('gender')
        information_model.insert_item(information)

        # set tier
        tier_id = customer_data['mpc_tier_id']
        tier = self.__tiers_storage.get_by_id(Id(str(tier_id)))
        if not tier:
            raise ValueError('Unable to change Tier #{} for Customer #{} - tier does not exist!'.format(
                tier_id,
                information.customer_id
            ))

        customer = self.__customers_storage.get_by_id(Id(information.customer_id))
        customer.tier = tier
        self.__customers_storage.save(customer)
    def init(self, customer_id: str, cart_id: str) -> None:
        customer_id = Id(customer_id)
        customer = self.__customer_storage.get_by_id(customer_id)
        if not customer:
            raise ApplicationLogicException('Customer does not exist!')

        cart_id = Id(cart_id)
        cart = self.__cart_storage.get_by_id(cart_id)
        if not cart:
            raise ApplicationLogicException('Cart does not exist!')
        elif cart.is_empty:
            raise ApplicationLogicException('Cart is empty!')
        elif cart.has_products_added_over_limit:
            raise ApplicationLogicException(
                'Cart has Products added over limit!')

        vat_percent = Percentage(self.__purchase_settings.vat)
        delivery_cost = Cost(self.__purchase_settings.fee)
        checkout_items = [
            Checkout.Item(cart_item.product, cart_item.qty)
            for cart_item in cart.items
        ]
        checkout = Checkout(customer, tuple(checkout_items), delivery_cost,
                            vat_percent)

        self.__checkout_storage.save(checkout)
    def __response_list(user_id: str) -> dict:
        customer_storage = CustomerStorageImplementation()
        customer_id = Id(user_id)
        customer = customer_storage.get_by_id(customer_id)

        delivery_addresses = [{
            'hash': delivery_address.address_hash,
            'address_type': delivery_address.address_type,
            'recipient_name': delivery_address.recipient_name,
            'phone_number': delivery_address.phone_number,
            'street_address': delivery_address.street_address,
            'suburb': delivery_address.suburb,
            'city': delivery_address.city,
            'province': delivery_address.province,
            'business_name': delivery_address.business_name,
            'complex_building': delivery_address.complex_building,
            'postal_code': delivery_address.postal_code,
            'special_instructions': delivery_address.special_instructions,
            'address_nickname': delivery_address.address_nickname,
            'is_billing': delivery_address.is_billing,
            'is_shipping': delivery_address.is_shipping,
        } for delivery_address in customer.delivery_addresses]

        return {
            'delivery_addresses': delivery_addresses,
        }
Example #4
0
    def handle(self, sqs_message: SqsMessage) -> None:
        incoming_tiers = tuple([
            CustomerTier(
                Id(str(row.get('id'))),
                Name(str(row.get('name'))),
                Percentage(int(row.get('credit_back_percent'))),
                int(row.get('spent_amount_min')),
                int(row.get('spent_amount_max')),
            ) for row in (sqs_message.message_data.get('tiers'))
        ])

        stored_tiers = self.__tiers_storage.get_all()

        # delete
        incoming_tiers_ids = [
            incoming_tier.id.value for incoming_tier in incoming_tiers
        ]
        for stored_tier in stored_tiers:
            if stored_tier.id.value not in incoming_tiers_ids:
                stored_tier.mark_as_deleted()
                self.__tiers_storage.save(stored_tier)

        # add / update
        for incoming_tier in incoming_tiers:
            self.__tiers_storage.save(incoming_tier)
Example #5
0
    def tier(self) -> dict:
        # customer tier - is a part of purchase module, not account information

        def __tier_to_dict(customer_tier: CustomerTier) -> dict:
            return {
                'name': customer_tier.name.value,
                'discount_rate': customer_tier.credit_back_percent.value,
                'is_neutral': customer_tier.is_neutral
            }

        # cache
        if self.__purchase_customer_tier_lazy_loading_cache:
            return __tier_to_dict(
                self.__purchase_customer_tier_lazy_loading_cache)

        # guests are in neutral tier
        if self.is_anonymous:
            self.__purchase_customer_tier_lazy_loading_cache = CustomerTierStorageImplementation(
            ).get_neutral()
            return __tier_to_dict(
                self.__purchase_customer_tier_lazy_loading_cache)

        # get assigned customer tier
        customer = CustomerStorageImplementation().get_by_id(
            Id(self.customer_id))
        self.__purchase_customer_tier_lazy_loading_cache = customer.tier
        return __tier_to_dict(self.__purchase_customer_tier_lazy_loading_cache)
    def mobicred_shopper_result():
        """
        https://support.peachpayments.com/hc/en-us/articles/360026704471-Mobicred-integration-guide
        mobicred_api_resource_path = str(blueprint.current_request.get_query_parameter('resourcePath') or '').strip()
        """

        # Attention! All payment logic is done in webhooks!

        # Theoretically we can just get the last order by customer,
        # because currently it's hard for user to do some tricks here,
        # but event in this case user just gets info about his another order.

        user = blueprint.current_request.current_user
        if user.is_anyonimous:
            raise UnauthorizedError('Authentication is required!')

        order_storage = OrderStorageImplementation()

        last_order: Optional[Order] = None
        orders = order_storage.get_all_for_customer(Id(user.id))
        for order in orders:
            if not last_order or order.created_at > last_order.created_at:
                last_order = order

        if not last_order:
            raise UnprocessableEntityError('No orders - something wrong!')

        return {'order_number': last_order.number.value}
Example #7
0
    def __get_customer(self, user_id: str) -> CustomerInterface:
        customer_id = Id(user_id)
        customer = self.__customer_storage.get_by_id(customer_id)
        if not customer:
            raise ApplicationLogicException('Customer does not exist!')

        return customer
Example #8
0
    def credit_cards_shopper_result():
        # Attention! All payment logic is done in webhooks!

        # Theoretically we can just get the last order by customer,
        # because currently it's hard for user to do some tricks here,
        # but event in this case user just gets info about his another order.

        order_storage = OrderStorageImplementation()

        try:
            user = __get_user()

            last_order: Optional[Order] = None
            orders = order_storage.get_all_for_customer(Id(user.id))
            for order in orders:
                if not last_order or order.created_at > last_order.created_at:
                    last_order = order

            if not last_order:
                raise UnprocessableEntityError('No orders - something wrong!')

            return {'order_number': last_order.number.value}

        except BaseException as e:
            return http_response_exception_or_throw(e)
    def get_waiting_for_payment_by_checkout_or_checkout_new(
            self, user_id: str) -> Order:
        def __log_flow(text: str) -> None:
            self.__logger.log_simple(
                'Create/Get Last order for User #{} : {}'.format(
                    user_id, text))

        # @todo : refactoring...

        # For some reasons order payments can fail (closing tabs during payment process, errors, ...).
        # This forces us to start process again, but in this case new order will be created.
        # Theoretically this is still correct, but we get many "failed" orders, which are cancelled by timeout.
        # Better way is using just now created orders, which can be detected by current checkout data.
        # @TODO : SHOULD BE 2 SEPARATED ACTIONS: CREATE ORDER AND DO PAYMENT. Current flow is not good.

        customer_id = Id(user_id)
        checkout = self.__checkout_storage.load(customer_id)
        if not checkout:
            raise ApplicationLogicException('Checkout does not exist!')

        # find or create new order
        order = None
        existed_orders = self.__order_storage.get_all_for_customer(
            checkout.customer_id)
        for existed_order in existed_orders:
            # this is not good method, but no ideas for now
            # EFT-payment orders are still in waiting_for_payment status after checkout complete -
            # this is not usual for other payment types. But each payment process fill payment_method property,
            # so we can separate "existed not paid uncompleted" orders from "existed not paid completed" orders.
            is_waiting_for_payment = existed_order.status.value == Order.Status.AWAITING_PAYMENT
            is_not_set_payment = existed_order.payment_method is None
            if is_waiting_for_payment and is_not_set_payment:
                if self.__is_order_matched_checkout(existed_order, checkout):
                    order = existed_order
                    __log_flow(
                        'Found Existed Order #{} matched with Checkout'.format(
                            existed_order.number.value))
                    break

        if not order:
            # create order
            order = self.__purchase_service.purchase(checkout)
            __log_flow('Created New Order #{}'.format(order.number.value))

            # send to portal
            # Theoretically we can redo it again, if some error occurs during process.
            try:
                __log_flow('New Order #{} SQS Sending...'.format(
                    order.number.value))
                event = OrderChangeSqsSenderEvent(order)
                self.__sqs_sender.send(event)
                __log_flow('New Order #{} SQS Sent!'.format(
                    order.number.value))
            except BaseException as e:
                self.__logger.log_exception(e)
                __log_flow(
                    'New Order #{} SQS NOT Sent because of Error: {}!'.format(
                        order.number.value, str(e)))

        return order
Example #10
0
    def remove(self, checkout_id: str) -> None:
        checkout_id = Id(checkout_id)
        checkout = self.__checkout_storage.load(checkout_id)
        if not checkout:
            raise ApplicationLogicException('Checkout does not exist!')

        self.__checkout_storage.remove(checkout_id)
    def checkout_next_tier_indication():
        # @todo : this is a crutch
        # This should not be calculated on mpc side. This should be an api request to somewhere,
        # but this is impossible for now, so we have what we have.

        from chalicelib.settings import settings
        # from chalicelib.libs.core.elastic import Elastic
        from chalicelib.libs.models.mpc.base import DynamoModel
        from chalicelib.libs.purchase.customer.storage import CustomerStorageImplementation
        from chalicelib.libs.purchase.customer.storage import CustomerTierStorageImplementation

        try:
            user_id = __get_user().id

            customers_storage = CustomerStorageImplementation()
            tiers_storage = CustomerTierStorageImplementation()
            customer = customers_storage.get_by_id(Id(user_id))
            if customer.tier.is_neutral:
                return {
                    'currently_spent': 0,
                    'next_tier': None
                }

            # Spent amount for customer can not exist,
            # if customer spent nothing or sqs is delayed, for example.

            # We can use a tier's minimal amount to return a value close to real.
            # elastic = Elastic(
            #     settings.AWS_ELASTICSEARCH_CUSTOMER_TIERS_CUSTOMER_INFO_SPENT_AMOUNT,
            #     settings.AWS_ELASTICSEARCH_CUSTOMER_TIERS_CUSTOMER_INFO_SPENT_AMOUNT
            # )
            # row = elastic.get_data(customer.email.value)
            dynamo_db = DynamoModel(settings.AWS_DYNAMODB_CMS_TABLE_NAME)
            dynamo_db.PARTITION_KEY = 'PURCHASE_CUSTOMER_SPENT_AMOUNT'
            row = dynamo_db.find_item(customer.email.value)

            customer_spent_amount = float(row['spent_amount'] or 0) if row else customer.tier.spent_amount_min

            current_tiers = list(tiers_storage.get_all())
            current_tiers.sort(key=lambda tier: tier.spent_amount_min)
            for i in range(0, len(current_tiers) - 1):
                if current_tiers[i].id == customer.tier.id:
                    next_tier = current_tiers[i+1]
                    break
            else:
                next_tier = current_tiers[-1]

            return {
                'currently_spent': customer_spent_amount,
                'next_tier': {
                    'name': next_tier.name.value,
                    'amount_min': next_tier.spent_amount_min,
                }
            }
        except BaseException as e:
            return http_response_exception_or_throw(e)
Example #12
0
    def orders_list():
        orders_storage = OrderStorageImplementation()

        try:
            user_id = __get_user_id()
            customer_id = Id(user_id)
            orders = orders_storage.get_all_for_customer(customer_id)
            return __orders_response(orders)
        except BaseException as e:
            return http_response_exception_or_throw(e)
Example #13
0
 def __create_entity(self, row: dict) -> CustomerTier:
     entity = self.__reflector.construct(CustomerTier, {
         self.__class__.__ENTITY_PROPERTY_ID: Id(str(row['id'])),
         self.__class__.__ENTITY_PROPERTY_NAME: Name(row['name']),
         self.__class__.__ENTITY_PROPERTY_CREDIT_BACK_PERCENT: Percentage(int(row['credit_back_percent'])),
         self.__class__.__ENTITY_PROPERTY_SPENT_AMOUNT_MIN: int(row['spent_amount_min']),
         self.__class__.__ENTITY_PROPERTY_SPENT_AMOUNT_MAX: int(row['spent_amount_max']),
         self.__class__.__ENTITY_PROPERTY_IS_DELETED: row['is_deleted'],
     })
     return entity
Example #14
0
    def returns_list():
        returns_storage = ReturnRequestStorageImplementation()
        orders_storage = OrderStorageImplementation()

        customer_id = Id(__get_user().id)
        returns = returns_storage.get_all_for_customer(customer_id)

        orders_map = {}
        _order_numbers = [
            return_request_item.order_number for return_request in returns
            for return_request_item in return_request.items
        ]
        _orders = orders_storage.get_all_by_numbers(tuple(_order_numbers))
        for _order_number in _order_numbers:
            for _order in _orders:
                if _order.number.value == _order_number.value:
                    orders_map[_order_number.value] = _order
                    break
            else:
                raise ValueError(
                    '{} - Unable to find Order #{} for Customer\'s #{} returns'
                    .format(returns_list.__qualname__, _order_number.value,
                            __get_user().id))

        response = []
        for return_request in returns:
            items = []
            for return_item in return_request.items:
                order = orders_map[return_item.order_number.value]
                items.append({
                    'order_number':
                    return_item.order_number.value,
                    'ordered_at':
                    order.created_at.strftime('%Y-%m-%d %H:%M:%S'),
                    'cost':
                    return_item.cost.value,
                })

            response.append({
                'request_number':
                return_request.number.value,
                'requested_at':
                return_request.requested_at.strftime('%Y-%m-%d %H:%M:%S'),
                'items':
                items,
                'status': {
                    'value': return_request.total_status.value,
                    'label': return_request.total_status.label,
                }
            })

        return response
Example #15
0
    def __restore(self, data) -> Checkout:
        customer_id = Id(data.get('sk'))
        customer = self.__customer_storage.get_by_id(customer_id)

        checkout_items = []
        for item in data.get('checkout_items', tuple()):
            simple_sku = SimpleSku(str(item.get('simple_sku')))
            qty = Qty(int(item.get('qty')))

            product = self.__product_storage.load(simple_sku)
            checkout_item = Checkout.Item(product, qty)
            checkout_items.append(checkout_item)

        delivery_address = DeliveryAddress(
            data.get('delivery_address').get('recipient_name'),
            data.get('delivery_address').get('phone_number'),
            data.get('delivery_address').get('street_address'),
            data.get('delivery_address').get('suburb'),
            data.get('delivery_address').get('city'),
            data.get('delivery_address').get('province'),
            data.get('delivery_address').get('complex_building'),
            data.get('delivery_address').get('postal_code'),
            data.get('delivery_address').get('business_name'),
            data.get('delivery_address').get('special_instructions')
        ) if data.get('delivery_address', None) else None

        delivery_cost = Cost(float(data.get('delivery_cost')))
        vat = Percentage(float(data.get('vat_percent')))

        # can not exist for old data
        available_credits_amount = Cost(
            float(data.get('credits_available_amount', '0') or '0'))
        is_credits_in_use = bool(int(
            data.get('is_credits_in_use', '0') or '0'))

        # @todo : reflection
        checkout = object.__new__(Checkout)
        checkout._Checkout__customer = customer
        checkout._Checkout__checkout_items = checkout_items
        checkout._Checkout__delivery_address = delivery_address
        checkout._Checkout__delivery_cost = delivery_cost
        checkout._Checkout__vat_percent = vat
        checkout._Checkout__available_credits_amount = available_credits_amount
        checkout._Checkout__is_credits_in_use = is_credits_in_use

        return checkout
Example #16
0
    def set_delivery_address(self, checkout_id: str,
                             customer_address_hash: str) -> None:
        checkout_id = customer_id = Id(checkout_id)

        # @todo : use Id or create Hash object-value
        if not isinstance(customer_address_hash, str):
            raise ArgumentTypeException(self.set_delivery_address,
                                        'customer_address_hash',
                                        customer_address_hash)
        elif not str(customer_address_hash).strip():
            raise ArgumentCannotBeEmptyException(self.set_delivery_address,
                                                 'customer_address_hash')

        checkout = self.__checkout_storage.load(checkout_id)
        if not checkout:
            raise ApplicationLogicException('Checkout does not exist!')

        customer = self.__customer_storage.get_by_id(customer_id)
        if not customer:
            raise ApplicationLogicException('Customer does not exist!')

        for delivery_address in customer.delivery_addresses:
            if delivery_address.address_hash == customer_address_hash:
                customer_delivery_address = delivery_address
                break
        else:
            raise ApplicationLogicException(
                'Customer Delivery Address does not exist!')

        delivery_address = DeliveryAddress(
            customer_delivery_address.recipient_name,
            customer_delivery_address.phone_number,
            customer_delivery_address.street_address,
            customer_delivery_address.suburb, customer_delivery_address.city,
            customer_delivery_address.province,
            customer_delivery_address.complex_building,
            customer_delivery_address.postal_code,
            customer_delivery_address.business_name,
            customer_delivery_address.special_instructions)

        checkout.delivery_address = delivery_address
        self.__checkout_storage.save(checkout)
    def checkout_credit_spend():
        checkout_storage = CheckoutStorageImplementation()

        try:
            user = __get_user()

            checkout = checkout_storage.load(Id(user.id))
            if not checkout:
                raise ApplicationLogicException('Checkout does not exist!')

            if blueprint.current_request.method == 'POST':
                checkout.use_credits()
            elif blueprint.current_request.method == 'DELETE':
                checkout.unuse_credits()

            checkout_storage.save(checkout)

            return __response_checkout(user.id)
        except BaseException as e:
            return http_response_exception_or_throw(e)
Example #18
0
    def orders_view(order_number):
        orders_storage = OrderStorageImplementation()

        try:
            order_number = str(order_number).strip()
            if not order_number:
                raise HttpIncorrectInputDataException('order_number is required!')

            order_number = Order.Number(order_number)
            order = orders_storage.load(order_number)
            if not order:
                raise HttpNotFoundException('Order does not exist!')

            user_id = __get_user_id()
            customer_id = Id(user_id)
            if order.customer_id != customer_id:
                raise HttpAccessDenyException()

            return __orders_response([order])[0]
        except BaseException as e:
            return http_response_exception_or_throw(e)
Example #19
0
    def get_by_id(self, customer_id: Id) -> Optional[CustomerInterface]:
        if not isinstance(customer_id, Id):
            raise ArgumentTypeException(self.get_by_id, 'customer_id', customer_id)

        information = InformationModel(customer_id.value).get_information()
        if not information.email:
            raise ValueError('User information is incorrect - {}'.format(information.to_dict()))

        # @todo : link to tier should be with other data. Will be moved to Nexus soon.
        # row = self.__tier_elastic.get_data(information.email)
        row = self.__tiers_dynamo.find_item(information.email)

        customer_tier = self.__tiers_storage.get_by_id(Id(str(row['tier_id']))) \
            if row else self.__tiers_storage.get_neutral()
        if not customer_tier:
            # something wrong with tiers set
            raise ValueError('Customer {} is assigned to unknown Customer Tier #{}'.format(
                customer_id.value,
                row['tier_id']
            ))

        return CustomerImplementation(customer_id, information, customer_tier)
Example #20
0
    def __restore(self, data: dict) -> Cart:
        cart_items = []
        for item_data in data.get('cart_items', tuple()):
            simple_sku = SimpleSku(str(item_data.get('simple_sku')))
            qty = Qty(int(item_data.get('qty')))
            product = self.__product_storage.load(simple_sku)
            cart_items.append(
                self.__reflector.construct(
                    Cart.Item, {
                        self.__class__.__ENTITY_PROPERTY_ITEMS_PRODUCT:
                        product,
                        self.__class__.__ENTITY_PROPERTY_ITEMS_QTY: qty,
                    }))

        cart: Cart = self.__reflector.construct(
            Cart, {
                self.__class__.__ENTITY_PROPERTY_ID: Id(data.get('sk')),
                self.__class__.__ENTITY_PROPERTY_ITEMS: cart_items,
                self.__class__.__ENTITY_PROPERTY_VAT_PERCENT:
                self.__vat_percent
            })

        return cart
Example #21
0
    def __restore(self, data: dict) -> Order:
        order_number = Order.Number(data.get('sk'))
        customer_id = Id(data.get('customer_id'))
        delivery_cost = Cost(float(data.get('delivery_cost')))
        vat_percent = Percentage(float(data.get('vat_percent')))
        credits_spent = Cost(float(data.get('credits_spent') or '0'))

        payment_method = self.__restore_payment_method(
            data.get('payment_method'),
            json.loads(data.get('payment_method_extra_data_json') or '{}')
            if data.get('payment_method') else None)

        delivery_address = DeliveryAddress(
            data.get('delivery_address_recipient_name'),
            data.get('delivery_address_phone_number'),
            data.get('delivery_address_street_address'),
            data.get('delivery_address_suburb'),
            data.get('delivery_address_city'),
            data.get('delivery_address_province'),
            data.get('delivery_address_complex_building'),
            data.get('delivery_address_postal_code'),
            data.get('delivery_address_business_name'),
            data.get('delivery_address_special_instructions'))

        status_changes = []
        for status_change_data in data.get('status_history'):
            status = Order.Status(status_change_data.get('status'))
            changed_at = datetime.datetime.strptime(
                status_change_data.get('datetime'), '%Y-%m-%dT%H:%M:%S.%f')
            status_change = self.__reflector.construct(
                Order.StatusChangesHistory.Change, {
                    '__status': status,
                    '__datetime': changed_at
                })
            status_changes.append(status_change)

        status_change_history = Order.StatusChangesHistory(
            tuple(status_changes))

        order_items = []
        for item_data in data.get('order_items'):
            event_code = EventCode(item_data.get('event_code'))
            simple_sku = SimpleSku(item_data.get('simple_sku'))
            product_original_price = Cost(
                item_data.get('product_original_price'))
            product_current_price = Cost(
                item_data.get('product_current_price'))
            fbucks_earnings = Cost(item_data.get('fbucks_earnings'))
            dtd = Dtd(
                Dtd.Occasion(
                    Name(item_data.get('dtd_occasion_name')),
                    Description(item_data.get('dtd_occasion_description')))
                if item_data.get('dtd_occasion_name') else None,
                datetime.date(
                    int(item_data.get('dtd_date_from').split('-')[0]),
                    int(item_data.get('dtd_date_from').split('-')[1]),
                    int(item_data.get('dtd_date_from').split('-')[2])),
                datetime.date(int(item_data.get('dtd_date_to').split('-')[0]),
                              int(item_data.get('dtd_date_to').split('-')[1]),
                              int(item_data.get('dtd_date_to').split('-')[2])),
                int(item_data.get('dtd_working_days_from')),
                int(item_data.get('dtd_working_days_to')))

            qty_ordered = Qty(int(item_data.get('qty_ordered')))
            qty_return_requested = Qty(
                int(item_data.get('qty_return_requested') or 0))
            qty_return_returned = Qty(
                int(item_data.get('qty_return_returned') or 0))
            qty_cancelled_before_payment = Qty(
                int(item_data.get('qty_cancelled_before_payment') or 0))
            qty_cancelled_after_payment_requested = Qty(
                int(
                    item_data.get('qty_cancelled_after_payment_requested')
                    or 0))
            qty_cancelled_after_payment_cancelled = Qty(
                int(
                    item_data.get('qty_cancelled_after_payment_cancelled')
                    or 0))
            qty_refunded = Qty(int(item_data.get('qty_refunded') or 0))
            qty_modified_at = datetime.datetime.strptime(
                item_data.get('qty_modified_at'), '%Y-%m-%dT%H:%M:%S.%f')

            order_item = self.__reflector.construct(
                Order.Item, {
                    '__event_code': event_code,
                    '__simple_sku': simple_sku,
                    '__product_original_price': product_original_price,
                    '__product_current_price': product_current_price,
                    '__dtd': dtd,
                    '__qty_ordered': qty_ordered,
                    '__qty_return_requested': qty_return_requested,
                    '__qty_return_returned': qty_return_returned,
                    '__qty_cancelled_before_payment':
                    qty_cancelled_before_payment,
                    '__qty_cancelled_after_payment_requested':
                    qty_cancelled_after_payment_requested,
                    '__qty_cancelled_after_payment_cancelled':
                    qty_cancelled_after_payment_cancelled,
                    '__qty_refunded': qty_refunded,
                    '__qty_modified_at': qty_modified_at,
                    '__fbucks_earnings': fbucks_earnings
                })
            order_items.append(order_item)

        order = self.__reflector.construct(
            Order, {
                '__order_number': order_number,
                '__customer_id': customer_id,
                '__items': order_items,
                '__delivery_address': delivery_address,
                '__delivery_cost': delivery_cost,
                '__vat_percent': vat_percent,
                '__payment_method': payment_method,
                '__status_history': status_change_history,
                '__credits_spent': credits_spent,
            })

        return order
    def regular_eft_checkout():
        checkout_storage = CheckoutStorageImplementation()
        order_storage = OrderStorageImplementation()
        order_app_service = OrderAppService()
        logger = Logger()
        mailer = MailerImplementation()

        # 1. Get or create order. Critical!
        # ------------------------------------------------------

        try:
            user = __get_user()

            # @todo : refactoring
            checkout = checkout_storage.load(Id(user.id))
            if not checkout:
                raise ApplicationLogicException('Checkout does not exist!')
            elif checkout.total_due.value == 0:
                raise ApplicationLogicException('Unable to checkout 0 amount with Regular Eft!')

            order = order_app_service.get_waiting_for_payment_by_checkout_or_checkout_new(user.id)

            def __log_order_flow(text: str) -> None:
                logger.log_simple('Regular EFT : Checkout : {} : {}'.format(order.number.value, text))

            __log_order_flow('Start')

            # Attention!
            # Currently we use f-bucks only! Other credits are not available for now!
            # @todo : other credit types
            # @todo : copy-paste code
            # @todo : when reservation of credits amount will be done, perhaps, use sqs to spend credits
            if order.credit_spent_amount.value > 0:
                __log_order_flow('Spending Credits...')
                """"""
                from chalicelib.libs.purchase.core import Checkout
                see = Checkout.__init__
                """"""
                # @TODO : refactoring : raw data usage
                import uuid
                import datetime
                from chalicelib.settings import settings
                from chalicelib.libs.core.elastic import Elastic
                fbucks_customer_amount_elastic = Elastic(
                    settings.AWS_ELASTICSEARCH_FBUCKS_CUSTOMER_AMOUNT,
                    settings.AWS_ELASTICSEARCH_FBUCKS_CUSTOMER_AMOUNT,
                )
                fbucks_customer_amount_changes_elastic = Elastic(
                    settings.AWS_ELASTICSEARCH_FBUCKS_CUSTOMER_AMOUNT_CHANGES,
                    settings.AWS_ELASTICSEARCH_FBUCKS_CUSTOMER_AMOUNT_CHANGES,
                )
                fbucks_customer_amount_elastic.update_data(order.customer_id.value, {
                    'script': 'ctx._source.amount -= ' + str(order.credit_spent_amount.value),
                })
                fbucks_customer_amount_changes_elastic.create(str(uuid.uuid4()) + str(order.customer_id.value), {
                    "customer_id": order.customer_id.value,
                    "amount": -order.credit_spent_amount.value,
                    "changed_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                    "order_number": order.number.value,
                })
                __log_order_flow('Spending Credits: Done!')

            __log_order_flow('Order Updating...')
            order.payment_method = RegularEftOrderPaymentMethod()
            order_storage.save(order)
            __log_order_flow('Order Updated!')
        except BaseException as e:
            logger.log_exception(e)
            return http_response_exception_or_throw(e)

        # 2. Send eft email. Not critical.
        # Theoretically can be redone or downloaded manually.
        # ------------------------------------------------------

        try:
            __log_order_flow('EFT Email Sending...')
            message = RegularEftBankDetailsMailMessage(order)
            mailer.send(message)
            __log_order_flow('EFT Email Sent!')
        except BaseException as e:
            logger.log_exception(e)
            __log_order_flow('EFT Email Not Sent because of Error: {}'.format(str(e)))

        # 3. Flush cart, checkout. Not critical.
        # ------------------------------------------------------

        # flush cart
        try:
            __log_order_flow('Cart Flushing...')
            from chalicelib.libs.purchase.cart.service import CartAppService
            cart_service = CartAppService()
            cart_service.clear_cart(user.session_id)
            __log_order_flow('Cart Flushed!')
        except BaseException as e:
            logger.log_exception(e)
            __log_order_flow('Cart Not Flushed because of Error: {}'.format(str(e)))

        # flush checkout
        try:
            __log_order_flow('Checkout Flushing...')
            from chalicelib.libs.purchase.checkout.service import CheckoutAppService
            checkout_service = CheckoutAppService()
            checkout_service.remove(user.id)
            __log_order_flow('Checkout Flushed!')
        except BaseException as e:
            logger.log_exception(e)
            __log_order_flow('Checkout Not Flushed because of Error: {}'.format(str(e)))

        return {
            'order_number': order.number.value
        }
    def __response_checkout(user_id) -> dict:
        products = MpcProduct()
        dtd_calculator = DtdCalculatorImplementation()
        checkout_storage = CheckoutStorageImplementation()

        customer_id = Id(user_id)
        checkout = checkout_storage.load(customer_id)
        if not checkout:
            raise ApplicationLogicException('Checkout is not initiated!')

        tier = blueprint.current_request.current_user.profile.tier

        checkout_items_data = []
        for checkout_item in checkout.checkout_items:
            product = products.getRawDataBySimpleSku(checkout_item.simple_sku.value)
            product_sizes = product.get('sizes', []) if product else tuple()
            size = tuple(filter(lambda s: s.get('simple_sku') == checkout_item.simple_sku.value, product_sizes))[0]
            dtd = dtd_calculator.calculate(checkout_item.simple_sku, checkout_item.qty)

            item_fbucks = None
            if not tier['is_neutral'] and not blueprint.current_request.current_user.is_anyonimous:
                item_fbucks = math.ceil(checkout_item.current_cost.value * tier['discount_rate'] / 100)

            checkout_items_data.append({
                'sku': product['sku'],
                'simple_sku': checkout_item.simple_sku.value,
                'name': product.get('title'),
                'brand_name': product.get('brand'),
                'size_name': size.get('size'),
                'image_url': product.get('image', {}).get('src', None),
                'qty_available': int(size.get('qty')),
                'qty_added': checkout_item.qty.value,
                'is_added_over_limit': checkout_item.is_added_over_limit,
                'product_original_price': checkout_item.product_original_price.value,
                'product_current_price': checkout_item.product_current_price.value,
                'original_cost': checkout_item.original_cost.value,
                'current_cost': checkout_item.current_cost.value,
                'dtd': {
                    'occasion': {
                        'name': dtd.occasion.name.value,
                        'description': dtd.occasion.description.value,
                    } if dtd.occasion else None,
                    'date_from': dtd.date_from.strftime('%Y-%m-%d'),
                    'date_to': dtd.date_to.strftime('%Y-%m-%d'),
                    'working_days_from': dtd.working_days_from,
                    'working_days_to': dtd.working_days_to,
                },
                'fbucks': item_fbucks,
            })

        return {
            'checkout_items': checkout_items_data,
            'original_subtotal': checkout.original_subtotal.value,
            'current_subtotal': checkout.current_subtotal.value,
            'current_subtotal_vat_amount': checkout.current_subtotal_vat_amount.value,
            'delivery_cost': checkout.delivery_cost.value,
            'credits_available': checkout.credits_amount_available.value,
            'credits_in_use': checkout.credits_amount_in_use.value,
            'total_due': checkout.total_due.value,
            'delivery_address': {
                'hash': checkout.delivery_address.address_hash,
                'recipient_name': checkout.delivery_address.recipient_name,
                'phone_number': checkout.delivery_address.phone_number,
                'street_address': checkout.delivery_address.street_address,
                'suburb': checkout.delivery_address.suburb,
                'city': checkout.delivery_address.city,
                'province': checkout.delivery_address.province,
                'complex_building': checkout.delivery_address.complex_building,
                'postal_code': checkout.delivery_address.postal_code,
                'business_name': checkout.delivery_address.business_name,
                'special_instructions': checkout.delivery_address.special_instructions,
            } if checkout.delivery_address else None,
        }
Example #24
0
    def returns_create_submit():
        """
        POST : {
            items: [
                {
                    order_number: str,
                    simple_sku: str,
                    qty: int,
                    reason: str,
                    file_ids: [str, ...],
                    additional_comment: str|null,
                },
                ...
            ],
            delivery_method: str,
            refund_method: {
                type: str,
                params: {
                    # credit_card_eft
                    account_holder_name: str,
                    account_number: str,
                    branch_code: str
                }
            }
        }
        """

        user_id = __get_user().id
        now = datetime.datetime.now()
        returns_storage = ReturnRequestStorageImplementation()
        orders_storage = OrderStorageImplementation()
        file_storage = FileStorageImplementation()
        sqs_sender = SqsSenderImplementation()
        logger = Logger()

        # 1. Check input
        # -------------------------------

        request_data = blueprint.current_request.json_body or {}
        input_items = request_data.get('items')
        if not input_items or not isinstance(input_items, (list, tuple, set)):
            raise BadRequestError(
                'Incorrect Input Data! Parameter "items" is required!')
        elif sum([
                not isinstance(item, dict)
                or not isinstance(item.get('order_number'), str)
                or not isinstance(item.get('simple_sku'), str)
                or not isinstance(item.get('qty'), int)
                or not isinstance(item.get('reason'), str)
                or not isinstance(item.get('file_ids'),
                                  (list, tuple, set)) or sum([
                                      not isinstance(file_id, str)
                                      for file_id in item['file_ids']
                                  ]) > 0
                or not (item.get('additional_comment') is None
                        or isinstance(item.get('additional_comment'), str))
                for item in input_items
        ]) > 0:
            raise BadRequestError(
                'Incorrect Input Data! Incorrect "items" structure!')

        delivery_method_input_descriptor = request_data.get('delivery_method')
        if not delivery_method_input_descriptor or not isinstance(
                delivery_method_input_descriptor, str):
            raise BadRequestError(
                'Incorrect Input Data! Parameter "delivery_method" is required!'
            )

        refund_method_input_data = request_data.get('refund_method')
        if not refund_method_input_data:
            raise BadRequestError(
                'Incorrect Input Data! Parameter "refund_method" is required!')
        elif (not isinstance(refund_method_input_data, dict)
              or not isinstance(refund_method_input_data.get('type'), str)
              or not isinstance(refund_method_input_data.get('params'), dict)):
            raise BadRequestError(
                'Incorrect Input Data! Parameter "refund_method" is incorrect!'
            )

        # collect control data
        initial_data = __get_initial_data()
        control_data = {
            'reasons': [reason['key'] for reason in initial_data['reasons']],
            'delivery_methods': [
                _delivery_method['key']
                for _delivery_method in initial_data['delivery_methods']
            ],
            'orders': {},
        }
        for order_data in initial_data['orders']:
            order_number = order_data['order_number']
            for order_data_item in order_data['items']:
                simple_sku = order_data_item['simple_sku']
                qty = order_data_item['qty_can_return']
                control_data['orders'][order_number] = control_data[
                    'orders'].get(order_number) or {}
                control_data['orders'][order_number][simple_sku] = qty

        # validate input data
        if (
                # items
                sum([
                    item['order_number'] not in control_data['orders'].keys()
                    or item['simple_sku']
                    not in control_data['orders'][item['order_number']].keys()
                    or item['qty'] not in range(
                        1, control_data['orders'][item['order_number']][
                            item['simple_sku']] + 1)
                    or item['reason'] not in control_data['reasons'] or sum([
                        not file_id.strip() or not file_storage.get(file_id)
                        for file_id in item['file_ids']
                    ]) > 0 or (item['additional_comment'] is not None
                               and len(item['additional_comment']) > 255)
                    for item in input_items
                ]) > 0

                # delivery method
                or delivery_method_input_descriptor
                not in control_data['delivery_methods']

                # refund method (method structure/data check)
                or refund_method_input_data['type'] not in [
                    EftRefundMethod('test', 'test', 'test').descriptor,
                    StoreCreditRefundMethod().descriptor,
                    MobicredRefundMethod().descriptor,
                    CreditCardRefundMethod().descriptor,
                ] or
            (refund_method_input_data['type'] == EftRefundMethod(
                'test', 'test', 'test').descriptor and
             (not isinstance(
                 refund_method_input_data.get(
                     'params', {}).get('account_holder_name'), str) or
              not refund_method_input_data['params'].get('account_holder_name')
              or not isinstance(
                  refund_method_input_data.get('params',
                                               {}).get('account_number'), str)
              or not refund_method_input_data['params'].get('account_number')
              or not isinstance(
                  refund_method_input_data.get('params',
                                               {}).get('branch_code'), str)
              or not refund_method_input_data['params'].get('branch_code')))
                or (refund_method_input_data['type'] in (
                    StoreCreditRefundMethod().descriptor,
                    MobicredRefundMethod().descriptor,
                    CreditCardRefundMethod().descriptor,
                ) and len(refund_method_input_data['params']) > 0)):
            raise BadRequestError('Incorrect Input Data! Incorrect values!')

        # check duplicates in order
        if len(
                set([
                    str(item['order_number']) + str(item['simple_sku'])
                    for item in input_items
                ])) != len(input_items):
            raise BadRequestError(
                'Incorrect Input Data! Input items has duplicates!')

        # check refund methods
        # "...credit-card should be allowed only when one of selected orders was paid by credit card,
        # but eft and credits should be available for all return-requests..."
        _allowed_refund_methods_keys = []
        for item in input_items:
            _order_refund_method_keys = [
                _order_refund_method['key']
                for _order in initial_data['orders']
                if _order['order_number'] == item['order_number']
                for _order_refund_method in _order['refund_methods']
            ]

            # intersection of all input orders
            if len(_allowed_refund_methods_keys) == 0:
                _allowed_refund_methods_keys = _order_refund_method_keys
            else:
                _allowed_refund_methods_keys = [
                    key for key in _allowed_refund_methods_keys
                    if key in _order_refund_method_keys
                ]
        if refund_method_input_data[
                'type'] not in _allowed_refund_methods_keys:
            raise BadRequestError(
                'Incorrect Input Data! Refund method {} is not allowed for selected orders!'
                .format(refund_method_input_data['type']))

        # 2. Create Return Request entity
        # -------------------------------

        return_request_items = []
        for item in input_items:
            order_number = item['order_number']
            simple_sku = item['simple_sku']
            qty = item['qty']

            cost = None
            for initial_order in initial_data['orders']:
                if initial_order['order_number'] == order_number:
                    for initial_item in initial_order['items']:
                        if initial_item['simple_sku'] == simple_sku:
                            cost = tuple(
                                filter(lambda x: x.get('qty') == qty,
                                       initial_item['costs']))[0].get('cost')
                            break

            reason = ReturnRequest.Item.Reason(item['reason'])

            attached_files = tuple([
                ReturnRequest.Item.AttachedFile(file_storage.get(file_id).url)
                for file_id in item['file_ids']
            ])

            additional_comment = item.get('additional_comment') if item.get(
                'additional_comment') else None
            additional_comment = ReturnRequest.Item.AdditionalComment(
                additional_comment) if additional_comment else None

            return_request_items.append(
                ReturnRequest.Item(OrderNumber(order_number),
                                   SimpleSku(simple_sku), Qty(qty), Cost(cost),
                                   reason, attached_files, additional_comment))

        delivery_method_instance = None
        for _delivery_method_instance in [
                HandDeliveryMethod(),
                CourierOrPostOffice(),
                RunwaysaleToCollect()
        ]:
            if _delivery_method_instance.descriptor == delivery_method_input_descriptor:
                delivery_method_instance = _delivery_method_instance
                break

        refund_method_instance = None
        for _refund_method_instance in [
                StoreCreditRefundMethod(),
                EftRefundMethod('test', 'test', 'test'),
                MobicredRefundMethod(),
                CreditCardRefundMethod()
        ]:
            if _refund_method_instance.descriptor == refund_method_input_data[
                    'type']:
                refund_method_instance = _refund_method_instance.__class__(
                    **refund_method_input_data['params'])
                break

        return_request = ReturnRequest(
            Id(user_id), ReturnRequest.Number(now.strftime('%y%j03%f')),
            tuple(return_request_items), delivery_method_instance,
            refund_method_instance)

        # 3. Modify orders qty
        # -------------------------------

        modified_orders = {}
        for return_item in return_request.items:
            order = modified_orders.get(
                return_item.order_number.value) or orders_storage.load(
                    return_item.order_number)
            order.request_return(return_item.simple_sku, return_item.qty)
            modified_orders[order.number.value] = order

        # 4. Save changes
        # -------------------------------

        def __log_flow(text: str) -> None:
            logger.log_simple('Return Request #{} - Creation : {}'.format(
                return_request.number.value, text))

        __log_flow('Start')

        __log_flow('Saving Return Request...')
        returns_storage.save(return_request)
        __log_flow('Saving Return Request - Done!')

        __log_flow('Saving Orders...')
        for order in tuple(modified_orders.values()):
            __log_flow('Saving Order #{}...'.format(order.number.value))
            orders_storage.save(order)
            __log_flow('Saving Order #{} - Done!'.format(order.number.value))
        __log_flow('Saving Orders - Done!')

        # 5. Send SQS
        # -------------------------------

        __log_flow('SQS Sending Return Request...')
        sqs_sender.send(ReturnRequestChangeSqsSenderEvent(return_request))
        __log_flow('SQS Sending Return Request - Done!')

        __log_flow('End')

        return {'request_number': return_request.number.value}
Example #25
0
    def credit_cards_checkout():
        cards_storage = CreditCardsStorageImplementation()
        checkout_storage = CheckoutStorageImplementation()
        order_app_service = OrderAppService()
        cart_service = CartAppService()
        checkout_service = CheckoutAppService()
        logger = Logger()

        try:
            user = __get_user()

            card_response_id = (blueprint.current_request.json_body
                                or {}).get('card_id') or None
            if not card_response_id:
                raise BadRequestError('Incorrect Input Data!')

            card = None
            for _card in cards_storage.get_all_by_customer(user.id):
                if __get_card_response(_card)['id'] == card_response_id:
                    card = _card
                    break

            if not card:
                raise NotFoundError('Card does not exist!')
            elif not card.is_verified:
                raise ApplicationLogicException(
                    'Unable to checkout with Not Verified Card!')

            checkout = checkout_storage.load(Id(user.id))
            if not checkout:
                raise HttpNotFoundException('Checkout does not exist!')
            elif checkout.total_due.value == 0:
                raise ApplicationLogicException(
                    'Unable to checkout 0 amount with Credit Cards!')

            order = order_app_service.get_waiting_for_payment_by_checkout_or_checkout_new(
                user.id)

            def __log_flow(text: str) -> None:
                logger.log_simple('Credit Cards : Checkout : {} : {}'.format(
                    order.number.value, text))

            __log_flow('Start')

            # init
            try:
                __log_flow('Payment Request...')
                response = requests.post(
                    url=settings.PEACH_PAYMENT_BASE_URL +
                    'registrations/{}/payments'.format(card.token),
                    data={
                        'entityId':
                        settings.PEACH_PAYMENT_ENTITY_ID,
                        'amount':
                        '%.2f' % order.total_current_cost_ordered.value,
                        'paymentType':
                        'DB',
                        'currency':
                        'ZAR',
                        'shopperResultUrl':
                        requests.utils.requote_uri(settings.FRONTEND_BASE_URL +
                                                   '/order/confirmation/{}'.
                                                   format(order.number.value)),
                        'customParameters[order_number]':
                        order.number.value,
                    },
                    headers={
                        'Authorization':
                        'Bearer {}'.format(settings.PEACH_PAYMENT_ACCESS_TOKEN)
                    })
                if response.status_code != 200:
                    raise Exception(
                        'Peach Payment Request has been Failed: {} - {} - {}'.
                        format(response.status_code, response.reason,
                               response.text))

                response_data = response.json()
                if response_data['result']['code'] not in (
                        '000.200.000',  # transaction pending
                ):
                    raise Exception(
                        'Credit Card Initial request response is not good: {} - {}'
                        .format(response_data['result']['code'],
                                response_data['result']['description']))

                __log_flow('Payment Request is Done!')
            except BaseException as e:
                logger.log_exception(e)
                __log_flow(
                    'Payment Request is Not done because of Error: {}'.format(
                        str(e)))
                raise UnprocessableEntityError(
                    'Credit Card Payment is unavailable now!')

            # flush cart (silently)
            try:
                __log_flow('Cart Flushing...')
                cart_service.clear_cart(user.session_id)
                __log_flow('Cart Flushed!')
            except BaseException as e:
                logger.log_exception(e)
                __log_flow('Cart is NOT Flushed because of Error: {}'.format(
                    str(e)))

            # flush checkout (silently)
            try:
                __log_flow('Checkout Flushing...')
                checkout_service.remove(user.id)
                __log_flow('Checkout Flushed!')
            except BaseException as e:
                logger.log_exception(e)
                __log_flow(
                    'Checkout is NOT Flushed because of Error: {}'.format(
                        str(e)))

            result = {
                'order_number':
                order.number.value,
                'url':
                response_data['redirect']['url'],
                'method':
                'POST',
                'parameters': [{
                    'name': param['name'],
                    'value': param['value'],
                } for param in response_data['redirect']['parameters']]
            }

            __log_flow('End')

            return result
        except BaseException as e:
            logger.log_exception(e)
            return http_response_exception_or_throw(e)
Example #26
0
    def __get_initial_data():
        orders_storage = OrderStorageImplementation()
        products_storage = ProductStorageImplementation()

        orders = []
        products_map = {}

        # "...credit-card should be allowed only when one of selected orders was paid by credit card,
        # but eft and credits should be available for all return-requests..."
        payment_refund_methods_map = {
            # @todo : payment methods descriptors
            'regular_eft': [{
                'key': refund_method.descriptor,
                'label': refund_method.label,
            } for refund_method in [
                StoreCreditRefundMethod(),
                EftRefundMethod('test', 'test', 'test')
            ]],
            'customer_credit': [{
                'key': refund_method.descriptor,
                'label': refund_method.label,
            } for refund_method in [
                StoreCreditRefundMethod(),
                EftRefundMethod('test', 'test', 'test')
            ]],
            'mobicred': [{
                'key': refund_method.descriptor,
                'label': refund_method.label,
            } for refund_method in [
                StoreCreditRefundMethod(),
                EftRefundMethod('test', 'test', 'test'),
                MobicredRefundMethod(),
            ]],
            'credit_card': [{
                'key': refund_method.descriptor,
                'label': refund_method.label,
            } for refund_method in [
                StoreCreditRefundMethod(),
                EftRefundMethod('test', 'test', 'test'),
                CreditCardRefundMethod()
            ]]
        }

        for order in orders_storage.get_all_for_customer(Id(__get_user().id)):
            if not order.is_returnable:
                continue

            items = []
            for item in order.items:
                product = products_map.get(
                    item.simple_sku.value) or products_storage.load(
                        item.simple_sku)
                products_map[item.simple_sku.value] = product

                items.append({
                    'simple_sku':
                    item.simple_sku.value,
                    'product_name':
                    product.name.value,
                    'img_url':
                    product.image_urls[0] if product.image_urls else None,
                    'costs': [{
                        'qty': qty,
                        'cost': item.get_refund_cost(Qty(qty)).value
                    } for qty in range(1, item.qty_processable.value + 1)],
                    'qty_can_return':
                    item.qty_processable.value,
                })

            orders.append({
                'order_number':
                order.number.value,
                'ordered_at':
                order.created_at.strftime('%Y-%m-%d %H:%M:%S'),
                'can_be_returned_till':
                order.is_returnable_till.strftime('%Y-%m-%d %H:%M:%S'),
                'items':
                items,
                'refund_methods':
                payment_refund_methods_map[order.payment_method.descriptor]
            })

        return {
            'reasons': [{
                'key': reason.descriptor,
                'label': reason.label,
            } for reason in [
                ReturnRequest.Item.Reason(ReturnRequest.Item.Reason.TOO_BIG),
                ReturnRequest.Item.Reason(ReturnRequest.Item.Reason.TOO_SMALL),
                ReturnRequest.Item.Reason(
                    ReturnRequest.Item.Reason.SELECTED_WRONG_SIZE),
                ReturnRequest.Item.Reason(
                    ReturnRequest.Item.Reason.DONT_LIKE_IT),
                ReturnRequest.Item.Reason(
                    ReturnRequest.Item.Reason.NOT_HAPPY_WITH_QTY),
                ReturnRequest.Item.Reason(
                    ReturnRequest.Item.Reason.RECEIVED_WRONG_SIZE),
                ReturnRequest.Item.Reason(
                    ReturnRequest.Item.Reason.RECEIVED_DAMAGED),
            ]],
            'delivery_methods': [{
                'key': delivery_method.descriptor,
                'label': delivery_method.label,
            } for delivery_method in [
                HandDeliveryMethod(),
                CourierOrPostOffice(),
                RunwaysaleToCollect()
            ]],
            'orders':
            orders,
        }
Example #27
0
    def returns_view(return_number):
        customer_id = Id(__get_user().id)
        returns_storage = ReturnRequestStorageImplementation()
        orders_storage = OrderStorageImplementation()
        products_storage = ProductStorageImplementation()

        return_request = returns_storage.load(
            ReturnRequest.Number(return_number))
        if not return_request:
            raise NotFoundError(
                'Return Request #{} does not exist!'.format(return_number))
        elif return_request.customer_id != customer_id:
            raise ForbiddenError('It is not your Return Request!')

        response = {
            'request_number':
            return_request.number.value,
            'requested_at':
            return_request.requested_at.strftime('%Y-%m-%d %H:%M:%S'),
            'items': [],
            'delivery_method':
            return_request.delivery_method.label,
            'refund_method':
            return_request.refund_method.label,
            'status': {
                'value': return_request.total_status.value,
                'label': return_request.total_status.label,
            }
        }

        orders_map = {}
        products_map = {}
        for return_item in return_request.items:
            product = products_map.get(
                return_item.simple_sku.value) or products_storage.load(
                    return_item.simple_sku)
            products_map[return_item.simple_sku.value] = product

            order = orders_map.get(
                return_item.order_number.value) or orders_storage.load(
                    return_item.order_number)
            orders_map[return_item.order_number.value] = order

            response['items'].append({
                'order_number':
                return_item.order_number.value,
                'simple_sku':
                return_item.simple_sku.value,
                'product_name':
                product.name.value,
                'product_image_url':
                product.image_urls[0] if product.image_urls else None,
                'size_name':
                product.size_name.value,
                'ordered_at':
                order.created_at.strftime('%Y-%m-%d %H:%M:%S'),
                'cost':
                return_item.cost.value,
                'qty':
                return_item.qty.value,
                'status':
                return_item.status.label,
                'reason':
                return_item.reason.label,
                'attached_files': [{
                    'url': file.url
                } for file in return_item.attached_files],
                'additional_comment':
                return_item.additional_comment.value
                if return_item.additional_comment else None,
            })

        return response
Example #28
0
    def __response_cart(cart_id) -> dict:
        cart_storage = CartStorageImplementation()
        dtd_calculator = DtdCalculatorImplementation()

        def __return(cart_items: Tuple[Cart.Item], original_subtotal: float,
                     current_subtotal: float,
                     current_subtotal_vat_amount: float):
            tier = blueprint.current_request.current_user.profile.tier

            # fbucks available to spend
            available_fbucks_amount = None
            if not tier[
                    'is_neutral'] and not blueprint.current_request.current_user.is_anyonimous:
                """"""
                # @TODO : REFACTORING !!!
                from chalicelib.libs.purchase.customer.sqs import FbucksChargeSqsHandler
                see = FbucksChargeSqsHandler
                """"""
                from chalicelib.settings import settings
                from chalicelib.libs.core.elastic import Elastic
                __fbucks_customer_amount_elastic = Elastic(
                    settings.AWS_ELASTICSEARCH_FBUCKS_CUSTOMER_AMOUNT,
                    settings.AWS_ELASTICSEARCH_FBUCKS_CUSTOMER_AMOUNT,
                )
                fbucks_amount_row = __fbucks_customer_amount_elastic.get_data(
                    blueprint.current_request.current_user.id)
                available_fbucks_amount = fbucks_amount_row[
                    'amount'] or 0 if fbucks_amount_row else 0

            products = MpcProduct()
            items_data = []
            for cart_item in cart_items:
                product = products.getRawDataBySimpleSku(
                    cart_item.simple_sku.value)
                product_sizes = product.get('sizes', []) if product else ()
                size = tuple(
                    filter(
                        lambda s: s.get('simple_sku') == cart_item.simple_sku.
                        value, product_sizes))[0]
                dtd = dtd_calculator.calculate(cart_item.simple_sku,
                                               cart_item.qty)

                item_fbucks = None
                if not tier[
                        'is_neutral'] and not blueprint.current_request.current_user.is_anyonimous:
                    item_fbucks = math.ceil(cart_item.current_cost.value *
                                            tier['discount_rate'] / 100)

                items_data.append({
                    'sku':
                    product['sku'],
                    'simple_sku':
                    cart_item.simple_sku.value,
                    'name':
                    product.get('title'),
                    'brand_name':
                    product.get('brand'),
                    'size_name':
                    size.get('size'),
                    'image_url':
                    product.get('image', {}).get('src', None),
                    'qty_available':
                    int(size.get('qty')),
                    'qty_added':
                    cart_item.qty.value,
                    'is_added_over_limit':
                    cart_item.is_added_over_limit,
                    'product_original_price':
                    cart_item.product_original_price.value,
                    'product_current_price':
                    cart_item.product_current_price.value,
                    'original_cost':
                    cart_item.original_cost.value,
                    'current_cost':
                    cart_item.current_cost.value,
                    'dtd': {
                        'occasion': {
                            'name': dtd.occasion.name.value,
                            'description': dtd.occasion.description.value,
                        } if dtd.occasion else None,
                        'date_from': dtd.date_from.strftime('%Y-%m-%d'),
                        'date_to': dtd.date_to.strftime('%Y-%m-%d'),
                        'working_days_from': dtd.working_days_from,
                        'working_days_to': dtd.working_days_to,
                    },
                    'fbucks':
                    item_fbucks,
                })

            return {
                'items': items_data,
                'original_subtotal': original_subtotal,
                'current_subtotal': current_subtotal,
                'current_subtotal_vat_amount': current_subtotal_vat_amount,
                'available_fbucks_amount': available_fbucks_amount,
            }

        cart_id = Id(cart_id)
        cart = cart_storage.get_by_id(cart_id)
        return __return(
            cart.items if cart else tuple(),
            cart.original_subtotal.value if cart else 0.0,
            cart.current_subtotal.value if cart else 0.0,
            cart.current_subtotal_vat_amount.value if cart else 0.0)
Example #29
0
    def __restore(self, data: dict) -> Order:
        order_number = Order.Number(data.get('order_number'))
        customer_id = Id(data.get('customer_id'))
        delivery_cost = Cost(float(data.get('delivery_cost')))
        vat_percent = Percentage(
            float(
                # I added "vat_percent" after first orders were stored,
                # but it's hard to make changes in elastic, so...
                # @todo : create migration tool.
                data.get('vat_percent') or self.__current_vat_value))
        credits_spent = Cost(float(data.get('credits_spent')
                                   or '0'))  # can be not existed in old data
        payment_method = self.__restore_payment_method(
            data.get('payment_method'),
            json.loads(data.get('payment_method_extra_data_json') or '{}')
            if data.get('payment_method') else None)

        delivery_address = DeliveryAddress(
            data.get('delivery_address_recipient_name'),
            data.get('delivery_address_phone_number'),
            data.get('delivery_address_street_address'),
            data.get('delivery_address_suburb'),
            data.get('delivery_address_city'),
            data.get('delivery_address_province'),
            data.get('delivery_address_complex_building'),
            data.get('delivery_address_postal_code'),
            data.get('delivery_address_business_name'),
            data.get('delivery_address_special_instructions'))

        status_changes = []
        for status_change_data in data.get('status_history'):
            status = Order.Status(status_change_data.get('status'))

            # elastic supports only 3 digits for milliseconds
            changed_at = datetime.datetime.strptime(
                status_change_data.get('datetime') + '000',
                '%Y-%m-%dT%H:%M:%S.%f')

            status_change = self.__reflector.construct(
                Order.StatusChangesHistory.Change, {
                    '__status': status,
                    '__datetime': changed_at
                })
            status_changes.append(status_change)

        status_change_history = Order.StatusChangesHistory(
            tuple(status_changes))

        order_items = []
        for item_data in data.get('order_items'):
            event_code = EventCode(item_data.get('event_code'))
            simple_sku = SimpleSku(item_data.get('simple_sku'))
            product_original_price = Cost(
                item_data.get('product_original_price'))
            product_current_price = Cost(
                item_data.get('product_current_price'))
            fbucks_earnings = Cost(item_data.get('fbucks_amount')
                                   or 0)  # old orders don't have this field
            dtd = Dtd(
                Dtd.Occasion(
                    Name(item_data.get('dtd_occasion_name')),
                    Description(item_data.get('dtd_occasion_description')))
                if item_data.get('dtd_occasion_name') else None,
                datetime.date(
                    int(item_data.get('dtd_date_from').split('-')[0]),
                    int(item_data.get('dtd_date_from').split('-')[1]),
                    int(item_data.get('dtd_date_from').split('-')[2])),
                datetime.date(int(item_data.get('dtd_date_to').split('-')[0]),
                              int(item_data.get('dtd_date_to').split('-')[1]),
                              int(item_data.get('dtd_date_to').split('-')[2])),
                int(item_data.get('dtd_working_days_from')),
                int(item_data.get('dtd_working_days_to')))
            qty_ordered = Qty(int(item_data.get('qty_ordered')))
            qty_return_requested = Qty(
                int(item_data.get('qty_return_requested') or 0))
            qty_return_returned = Qty(
                int(item_data.get('qty_return_returned') or 0))
            qty_cancelled_before_payment = Qty(
                int(item_data.get('qty_cancelled_before_payment') or 0))
            qty_cancelled_after_payment_requested = Qty(
                int(
                    item_data.get('qty_cancelled_after_payment_requested')
                    or 0))
            qty_cancelled_after_payment_cancelled = Qty(
                int(
                    item_data.get('qty_cancelled_after_payment_cancelled')
                    or 0))
            qty_refunded = Qty(int(item_data.get('qty_refunded') or 0))

            # elastic supports only 3 digits for milliseconds
            qty_modified_at = datetime.datetime.strptime(
                (
                    # "qty_modified_at" may not exist for old data (dev, test),
                    # but it's hard to make changes in elastic, so...
                    # @todo : create migration tool.
                    item_data.get('qty_modified_at')
                    or status_change_history.get_last().datetime.strftime(
                        '%Y-%m-%dT%H:%M:%S.%f')[:-3]) + '000',
                '%Y-%m-%dT%H:%M:%S.%f')

            order_item = self.__reflector.construct(
                Order.Item, {
                    '__event_code': event_code,
                    '__simple_sku': simple_sku,
                    '__product_original_price': product_original_price,
                    '__product_current_price': product_current_price,
                    '__dtd': dtd,
                    '__qty_ordered': qty_ordered,
                    '__qty_return_requested': qty_return_requested,
                    '__qty_return_returned': qty_return_returned,
                    '__qty_cancelled_before_payment':
                    qty_cancelled_before_payment,
                    '__qty_cancelled_after_payment_requested':
                    qty_cancelled_after_payment_requested,
                    '__qty_cancelled_after_payment_cancelled':
                    qty_cancelled_after_payment_cancelled,
                    '__qty_refunded': qty_refunded,
                    '__qty_modified_at': qty_modified_at,
                    '__fbucks_earnings': fbucks_earnings
                })
            order_items.append(order_item)

        order = self.__reflector.construct(
            Order, {
                '__order_number': order_number,
                '__customer_id': customer_id,
                '__items': order_items,
                '__delivery_address': delivery_address,
                '__delivery_cost': delivery_cost,
                '__vat_percent': vat_percent,
                '__payment_method': payment_method,
                '__status_history': status_change_history,
                '__credits_spent': credits_spent,
            })

        return order
Example #30
0
    def customer_credits_checkout():
        checkout_storage = CheckoutStorageImplementation()
        order_storage = OrderStorageImplementation()
        order_app_service = OrderAppService()
        cart_service = CartAppService()
        checkout_service = CheckoutAppService()
        sqs_sender = SqsSenderImplementation()
        logger = Logger()

        try:
            user = __get_user()

            # @todo : refactoring
            checkout = checkout_storage.load(Id(user.id))
            if not checkout:
                raise ApplicationLogicException('Checkout does not exist!')
            elif checkout.total_due.value != 0:
                raise ApplicationLogicException('Unable to checkout not 0 amount with Customer Credits!')

            order = order_app_service.get_waiting_for_payment_by_checkout_or_checkout_new(user.id)

            def __log_flow(text: str) -> None:
                logger.log_simple('Customer Credits Payment Log for Order #{} : {}'.format(
                    order.number.value,
                    text
                ))

            __log_flow('Start')

            try:
                __log_flow('Credits Spending...')
                # Attention!
                # Currently we use f-bucks only! Other credits are not available for now!
                # @todo : other credit types
                # @todo : copy-paste code
                # @todo : when reservation of credits amount will be done, perhaps, use sqs to spend credits
                """"""
                from chalicelib.libs.purchase.core import Checkout
                see = Checkout.__init__
                """"""
                # @TODO : refactoring : raw data usage
                import uuid
                import datetime
                from chalicelib.settings import settings
                from chalicelib.libs.core.elastic import Elastic
                fbucks_customer_amount_elastic = Elastic(
                    settings.AWS_ELASTICSEARCH_FBUCKS_CUSTOMER_AMOUNT,
                    settings.AWS_ELASTICSEARCH_FBUCKS_CUSTOMER_AMOUNT,
                )
                fbucks_customer_amount_changes_elastic = Elastic(
                    settings.AWS_ELASTICSEARCH_FBUCKS_CUSTOMER_AMOUNT_CHANGES,
                    settings.AWS_ELASTICSEARCH_FBUCKS_CUSTOMER_AMOUNT_CHANGES,
                )
                fbucks_customer_amount_elastic.update_data(order.customer_id.value, {
                    'script': 'ctx._source.amount -= ' + str(order.credit_spent_amount.value)
                })
                fbucks_customer_amount_changes_elastic.create(str(uuid.uuid4()) + str(order.customer_id.value), {
                    "customer_id": order.customer_id.value,
                    "amount": -order.credit_spent_amount.value,
                    "changed_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                    "order_number": order.number.value,
                })
                __log_flow('Credits Spent!')

                __log_flow('Order Updating...')
                order.payment_method = CustomerCreditsOrderPaymentMethod()
                order.status = Order.Status(Order.Status.PAYMENT_SENT)
                order.status = Order.Status(Order.Status.PAYMENT_RECEIVED)
                order_storage.save(order)
                __log_flow('Order Updated!')
            except BaseException as e:
                __log_flow('Not done because of Error : {}'.format(str(e)))
                raise e

            # send order update to sqs
            try:
                __log_flow('Order Change SQS Sending...')
                sqs_sender.send(OrderChangeSqsSenderEvent(order))
                __log_flow('Order Change SQS Sent!')
            except BaseException as e:
                __log_flow('Order Change SQS NOT Sent because of Error: {}!'.format(str(e)))
                logger.log_exception(e)

            # flush cart
            try:
                __log_flow('Cart Flushing...')
                cart_service.clear_cart(user.session_id)
                __log_flow('Cart Flushed!')
            except BaseException as e:
                __log_flow('Cart NOT Flushed because of Error: {}'.format(str(e)))
                logger.log_exception(e)

            # flush checkout
            try:
                __log_flow('Checkout Flushing...')
                checkout_service.remove(user.id)
                __log_flow('Checkout Flushed!')
            except BaseException as e:
                __log_flow('Checkout NOT Flushed because of Error: {}'.format(str(e)))
                logger.log_exception(e)

            result = {
                'order_number': order.number.value
            }

            __log_flow('End')

            return result
        except BaseException as e:
            logger.log_exception(e)
            return http_response_exception_or_throw(e)