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, }
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)
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}
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
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
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)
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)
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
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
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
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)
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)
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)
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
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, }
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}
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)
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, }
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
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)
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
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)