def skip_unless_payment_is_required(self, request): # Check to see if payment is actually required for this order. shipping_address = self.get_shipping_address(request.basket) shipping_method = self.get_shipping_method(request.basket, shipping_address) if shipping_method: shipping_charge = shipping_method.calculate( request.basket, shipping_address.postcode) else: # It's unusual to get here as a shipping method should be set by # the time this skip-condition is called. In the absence of any # other evidence, we assume the shipping charge is zero. shipping_charge = prices.Price(currency=request.basket.currency, excl_tax=D('0.00'), tax=D('0.00')) total = self.get_order_totals(request.basket, shipping_charge) if total.excl_tax == D('0.00'): raise exceptions.PassedSkipCondition( url=reverse('checkout:preview'))
def test_returns_correct_totals_when_tax_is_known(self): basket = mock.Mock() basket.total_excl_tax = D('10.00') basket.total_incl_tax = D('12.00') basket.is_tax_known = True shipping_charge = prices.Price(currency=basket.currency, excl_tax=D('5.00'), tax=D('0.50')) applicator = SurchargeApplicator() surcharges = applicator.get_applicable_surcharges(basket) total = self.calculator.calculate(basket, shipping_charge, surcharges) self.assertIsInstance(total, prices.Price) self.assertEqual(D('10.00') + D('5.00') + D('20.0'), total.excl_tax) self.assertTrue(total.is_tax_known) self.assertEqual(D('12.00') + D('5.50') + D('22.00'), total.incl_tax) self.assertEqual(D('2.00') + D('0.50') + D('2.00'), total.tax)
def calculate(self, basket, postcode): # If the charge is free then tax must be free (musn't it?) and so we # immediately set the tax to zero package = Package(formato=CAIXA_PACOTE) tax = D(0) ret = {} try: lines = basket.lines.all() for line in lines: product = line.product weight = product.weight height = product.height width = product.width length = product.length if all([weight, height, width, length]): package.add_item( weight=weight, # Peso height=height, # Altura width=width, # Largura length=length # Comprimento ) client = Client(cep_origem='31330-130') servicos = client.calc_preco_prazo(package, postcode, PAC) ret.update(service=servicos[0]) count = 0 for servico in servicos: tax += D(servico.valor) * line.quantity print('TAX', tax) except Exception as e: tax = D(0) price = prices.Price(currency=basket.currency, excl_tax=D('0.00'), tax=tax) ret.update(price=price) return ret
def calculate(self, basket): return prices.Price(currency=basket.currency, excl_tax=D('0.00'), incl_tax=D('0.00'))
def calculate(self, basket): return prices.Price( currency=basket.currency, excl_tax=self.charge_excl_tax, incl_tax=self.charge_incl_tax)
def calculate(self, basket): # If the charge is free then tax must be free (musn't it?) and so we # immediately set the tax to zero return prices.Price( currency=basket.currency, excl_tax=D('0.00'), tax=D('0.00'))
def calculate(self, basket, **kwargs): return prices.Price(currency=basket.currency, excl_tax=self.excl_tax, incl_tax=self.incl_tax)
def calculate(self, basket, options=None): # TODO: move code to smth like ShippingCalculator class results = [] charge = D('0.0') self.messages = [] self.errors = [] # Note, when weighing the basket, we don't check whether the item # requires shipping or not. It is assumed that if something has a # weight, then it requires shipping. scale = Scale(attribute_code=self.weight_attribute, default_weight=self.default_weight) packer = Packer(self.containers, attribute_codes=self.size_attributes, weight_code=self.weight_attribute, default_weight=self.default_weight) weight = scale.weigh_basket(basket).quantize(weight_precision) # Should be a list of dicts { 'weight': weight, 'container' : container } packs = packer.pack_basket(basket) facade = self.facade if not self.destination: self.errors.append(_("ERROR! There is no shipping address for charge calculation!\n")) else: self.messages.append(_(u"""Approximated shipping price for {weight} kg from {origin} to {destination}\n""").format(weight=weight, origin=self.origin, destination=self.destination.city)) # Assuming cases like http protocol suggests: # e=200 - OK. Result contains charge value and extra info such as Branch code, etc # e=404 - Result is empty, no destination found via API, redirect # to address form or prompt to API city-codes selector # e=503 - API is offline. Skip this method. # e=300 - Too many choices found, Result contains list of charges-codes. # Prompt to found dest-codes selector # an URL for AJAXed city-to-city charge lookup details_url = reverse_lazy('shipping:charge-details', kwargs={'slug': self.code}) # an URL for AJAXed code by city lookup using Select2 widget lookup_url = reverse_lazy('shipping:city-lookup', kwargs={'slug': self.code}) # if options set make a short call to API for final calculation if options: errors = None try: results, errors = facade.get_charge(options['senderCityId'], options['receiverCityId'], packs) except CalculationError as e: self.errors.append("Post-calculation error: %s" % e.errors) self.messages.append(e.title) except: raise if not errors: (charge, msg, err, self.extra_form) = facade.parse_results(results, options=options) if msg: self.messages.append(msg) if err: self.errors.append(err) else: raise CalculationError("%s -> %s" % (options['senderCityId'], options['receiverCityId']), errors) else: try: results = facade.get_charges(weight, packs, self.origin, self.destination) except ApiOfflineError: self.errors.append(_(u"""%s API is offline. Can't calculate anything. Sorry!""") % self.name) self.messages.append(_(u"Please, choose another shipping method!")) except OriginCityNotFoundError as e: # Paranoid mode as ImproperlyConfigured should be raised by facade self.errors.append(_(u"""City of origin '%s' not found in the shipping company postcodes to calculate charge.""") % e.title) self.messages.append(_(u"""It seems like we couldn't find code for the city of origin (%s). Please, select it manually, choose another address or another shipping method. """) % e.title) except ImproperlyConfigured as e: # upraised error handling self.errors.append("ImproperlyConfigured error (%s)" % e.message) self.messages.append("Please, select another shipping method or call site administrator!") except CityNotFoundError as e: self.errors.append(_(u"""Can't find destination city '{title}' to calculate charge. Errors: {errors}""").format(title=e.title, errors=e.errors)) self.messages.append(_(u"""It seems like we can't find code for the city of destination (%s). Please, choose another address or another shipping method. """) % e.title) if CHANGE_DESTINATION: self.messages.append(_("Also, you can choose city of destination manually")) self.extra_form = facade.get_extra_form(origin=self.origin, lookup_url=lookup_url, details_url=details_url) except TooManyFoundError as e: self.errors.append(_(u"Found too many destinations for given city (%s)") % e.title) if CHANGE_DESTINATION: self.messages.append(_("Please refine your shipping address")) self.extra_form = facade.get_extra_form(origin=self.origin, choices=e.results, details_url=details_url) except CalculationError as e: self.errors.append(_(u"""Error occurred during charge calculation for given city (%s)""") % e.title) self.messages.append(_(u"API error was: %s") % e.errors) if CHANGE_DESTINATION: self.extra_form = facade.get_extra_form(origin=self.origin, details_url=details_url, lookup_url=lookup_url) except: raise else: (charge, msg, err, self.extra_form) = facade.parse_results(results, origin=self.origin, dest=self.destination, weight=weight, packs=packs) if msg: self.messages.append(msg) if err: self.errors.append(err) # Zero tax is assumed... return prices.Price( currency=basket.currency, excl_tax=charge, incl_tax=charge)
def calculate(self, basket): total = basket.num_items * self.charge_per_item return prices.Price( currency=basket.currency, excl_tax=total, incl_tax=total)
def calculate(self, order): return prices.Price(currency=order.currency, excl_tax=D('0.00'), tax=D('0.00'))
def calculate(self, order): return prices.Price( currency=order.currency, excl_tax=D('0.00'), # we insist a tax percentage on order total. tax=(order.total_excl_tax * self.rate).quantize(D('0.01')))
def calculate(self, basket): base_charge = self.method.calculate(basket) discount = self.offer.shipping_discount(base_charge.excl_tax) excl_tax = base_charge.excl_tax - discount return prices.Price(currency=base_charge.currency, excl_tax=excl_tax)
def calculate(self, basket): total = D('55.00') return prices.Price(currency=basket.currency, excl_tax=total, incl_tax=total)
def calculate(self): return prices.Price( currency=self.currency, excl_tax=self.excl_tax, tax=(self.excl_tax * self.rate).quantize(D('0.01')) )
def place_order(sender, **kwargs): """ collect basket, user, shipping_method and address, order_number, total and pass them to handle_order_placement, but first add payment events and sources """ request = kwargs.get('request', None) or HttpRequest() basket = sender user = basket.owner if basket.owner else AnonymousUser() guest_email = None strategy = selector.strategy(user=user) session_data = shipping_address = shipping_method = None log.debug("initialising: \n basket = %s \n usr = %s \n strategy = %s", basket, user, strategy) basket.strategy = strategy amount_allocated = kwargs['OutSum'] session_key = kwargs['session_key'] order_num = kwargs['order_num'] if session_key is not None: session = SessionStore(session_key=session_key) if len(session.items()): log.debug("Session %s successfully restored", session) request.session = session request.user = user session_data = CheckoutSessionData(request) if isinstance(user, AnonymousUser): guest_email = session_data.get_guest_email() order_placement = RobokassaOrderPlacement() order_placement.request = request if session_data is not None: order_placement.checkout_session = session_data shipping_address = order_placement.get_shipping_address(basket) shipping_method = order_placement.get_shipping_method( basket, shipping_address) total = order_placement.get_order_totals(basket, shipping_method) else: # session not found, lets try to place order anyway log.error(("Session was not restored, trying default order for " "basket #%s"), basket.id) basket.is_shipping_required = False total = prices.Price(currency=basket.currency, excl_tax=basket.total_excl_tax, incl_tax=basket.total_incl_tax) # now create payment source and events source_type, is_created = SourceType.objects.get_or_create( name=u'Робокасса', code='robokassa') source = Source(source_type=source_type, amount_allocated=amount_allocated, amount_debited=amount_allocated) order_placement.add_payment_source(source) order_placement.add_payment_event('allocated', amount_allocated) order_placement.add_payment_event('debited', amount_allocated) post_payment.send(sender=order_placement, user=user, source=source) # all done lets place an order order_placement.handle_order_placement(order_num, user, basket, shipping_address, shipping_method, total, guest_email=guest_email)
def submit( self, user, basket, shipping_address, shipping_method, # noqa (too complex (10)) shipping_charge, billing_address, order_total, payment_kwargs=None, order_kwargs=None): """ Submit a basket for order placement. The process runs as follows: * Generate an order number * Freeze the basket so it cannot be modified any more (important when redirecting the user to another site for payment as it prevents the basket being manipulated during the payment process). * Attempt to take payment for the order - If payment is successful, place the order - If a redirect is required (eg PayPal, 3DSecure), redirect - If payment is unsuccessful, show an appropriate error message :basket: The basket to submit. :payment_kwargs: Additional kwargs to pass to the handle_payment method :order_kwargs: Additional kwargs to pass to the place_order method """ if payment_kwargs is None: payment_kwargs = {} if order_kwargs is None: order_kwargs = {} # Taxes must be known at this point assert basket.is_tax_known, ( "Basket tax must be set before a user can place an order") assert shipping_charge.is_tax_known, ( "Shipping method tax must be set before a user can place an order") # Freeze the basket so it cannot be manipulated while the customer is # completing payment on a 3rd party site. Also, store a reference to # the basket in the session so that we know which basket to thaw if we # get an unsuccessful payment response when redirecting to a 3rd party # site. self.freeze_basket(basket) self.checkout_session.set_submitted_basket(basket) # We define a general error message for when an unanticipated payment # error occurs. error_msg = _("A problem occurred while processing payment for this " "order - no payment has been taken. Please " "contact customer services if this problem persists") signals.pre_payment.send_robust(sender=self, view=self) # Generate an order number for the top level that includes orders from all the shops top_level_order_number = self.generate_order_number(basket) self.checkout_session.set_order_number(top_level_order_number) logger.info("Order #%s: beginning submission process for basket #%d", top_level_order_number, basket.id) items_by_shop = {} try: self.handle_payment(top_level_order_number, order_total, **payment_kwargs) except RedirectRequired as e: # Redirect required (eg PayPal, 3DS) logger.info("Order #%s: redirecting to %s", top_level_order_number, e.url) return HttpResponseRedirect(e.url) except UnableToTakePayment as e: # Something went wrong with payment but in an anticipated way. Eg # their bankcard has expired, wrong card number - that kind of # thing. This type of exception is supposed to set a friendly error # message that makes sense to the customer. msg = six.text_type(e) logger.warning( "Order #%s: unable to take payment (%s) - restoring basket", top_level_order_number, msg) self.restore_frozen_basket() # We assume that the details submitted on the payment details view # were invalid (eg expired bankcard). return self.render_payment_details(self.request, error=msg, **payment_kwargs) except PaymentError as e: # A general payment error - Something went wrong which wasn't # anticipated. Eg, the payment gateway is down (it happens), your # credentials are wrong - that king of thing. # It makes sense to configure the checkout logger to # mail admins on an error as this issue warrants some further # investigation. msg = six.text_type(e) logger.error("Order #%s: payment error (%s)", top_level_order_number, msg, exc_info=True) self.restore_frozen_basket() messages.error(self.request, e.message) return self.render_payment_details(self.request, error=msg, **payment_kwargs) # return self.render_preview( # self.request, error=error_msg, **payment_kwargs) except Exception as e: # Unhandled exception - hopefully, you will only ever see this in # development... logger.error( "Order #%s: unhandled exception while taking payment (%s)", top_level_order_number, e, exc_info=True) self.restore_frozen_basket() messages.error(self.request, e.message) return self.render_payment_details(self.request, error=msg, **payment_kwargs) # return self.render_preview( # self.request, error=error_msg, **payment_kwargs) top_level_order = None try: top_level_order = self.handle_order_placement( top_level_order_number, user, basket, shipping_address, shipping_method, shipping_charge, order_total, billing_address, **order_kwargs) except UnableToPlaceOrder as e: # It's possible that something will go wrong while trying to # actually place an order. Not a good situation to be in as a # payment transaction may already have taken place, but needs # to be handled gracefully. msg = six.text_type(e) logger.error("Order #%s: unable to place order - %s", top_level_order_number, msg, exc_info=True) self.restore_frozen_basket() return self.render_preview(self.request, error=msg, **payment_kwargs) # Collect information to split into an order for each shop for line in basket.all_lines(): if (line.product.shop not in items_by_shop): items_by_shop[line.product.shop] = { "products": [], "order_total": 0 } items_by_shop[line.product.shop]["products"].append(line.product), items_by_shop[ line.product.shop]["order_total"] += line.line_price_excl_tax for shop in items_by_shop.keys(): # We generate the order number first as this will be used # in payment requests (ie before the order model has been # created). We also save it in the session for multi-stage # checkouts (eg where we redirect to a 3rd party site and place # the order on a different request). order_number = self.generate_order_number(basket, shop.id) order = None # This is needed to clear the payment events/sources as to not create the payment event/sources on the sub orders self._payment_events = [] self._payment_sources = [] try: order_kwargs['shop'] = shop order = self.handle_order_placement( order_number, user, basket, shipping_address, shipping_method, shipping_charge, prices.Price(currency=basket.currency, excl_tax=items_by_shop[shop]["order_total"], incl_tax=items_by_shop[shop]["order_total"]), billing_address, **order_kwargs) except UnableToPlaceOrder as e: # It's possible that something will go wrong while trying to # actually place an order. Not a good situation to be in as a # payment transaction may already have taken place, but needs # to be handled gracefully. msg = six.text_type(e) logger.error("Order #%s: unable to place order - %s", order_number, msg, exc_info=True) self.restore_frozen_basket() return self.render_preview(self.request, error=msg, **payment_kwargs) signals.post_payment.send_robust(sender=self, view=self) # If all is ok with payment, try and place order logger.info("Order #%s: payment successful, placing order", top_level_order_number) basket.submit() return self.handle_successful_order(top_level_order)