def test_view_advances_to_payment_form(self, calculate_shipping_cost_mock):
        '''
        Test that the CheckoutFormView can advance to the PaymentForm
        '''
        calculate_shipping_cost_mock.return_value = Decimal(1.00)

        session = self.client.session
        cart = SessionCart(session)
        cart.add(self.variant)
        session.save()

        # Set up HTTP POST request.
        request_data = {
            'recipient_name': 'Foo Bar',
            'street_address': '123 Test St',
            'locality': 'Test',
            'region': 'OK',
            'postal_code': '12345',
            'country': 'US'
        }

        response = self.client.post(self.view_url,
                                    data=request_data,
                                    follow=True)
        rendered_html = response.content.decode()

        self.assertIn('how are you paying?', rendered_html)
    def test_view_can_load_and_retrieve_shipping_cost_from_invalid_backend_attribute(
            self, sum_mock, settings_mock, get_shipping_rate_mock):
        '''
        Test that the CheckoutFormView can recover from a badly-formed package
        import or missing module.
        '''
        sum_mock.return_value = Decimal(0.00)
        get_shipping_rate_mock.return_value = Decimal(0.00)

        settings_mock.FULFILLMENT_BACKENDS = [
            ('django.core.mail.backends.locmem.foo', 'Foo')
        ]

        session = self.client.session
        cart = SessionCart(session)
        cart.add(self.variant)
        session.save()

        # Set up HTTP POST request.
        request_data = {
            'recipient_name': 'Foo Bar',
            'street_address': '123 Test St',
            'locality': 'Test',
            'region': 'OK',
            'postal_code': '12345',
            'country': 'US'
        }

        response = self.client.post(self.view_url,
                                    data=request_data,
                                    follow=True)

        self.assertNotIn('shipping', self.client.session[views.UUID])
    def test_view_d0es_not_calculate_sales_tax_if_no_nexus_exists(
            self, calculate_shipping_cost_mock):
        '''
        Test that the CheckoutFormView calculates sales tax when the User
        does not reside within the nexus of the seller.
        '''
        calculate_shipping_cost_mock.return_value = Decimal(1.00)

        rate = TaxRate.objects.create(city='Test',
                                      state='OK',
                                      postal_code='12345',
                                      local_tax_rate=Decimal(0.01),
                                      state_tax_rate=Decimal(0.043))

        session = self.client.session
        cart = SessionCart(session)
        cart.add(self.variant)
        session.save()

        # Set up HTTP POST request.
        request_data = {
            'street_address': 'Buckingham Palace',
            'locality': 'London',
            'postal_code': 'SW1A 1AA',
            'country': 'GB'
        }
        response = self.client.post(self.view_url,
                                    data=request_data,
                                    follow=True)
        response = response.render()
        rendered_html = response.content.decode()

        self.assertNotIn('sales tax', rendered_html)
    def test_view_removes_step_data_for_key_in_get_request(
            self, calculate_shipping_cost_mock):
        '''
        Test that the CheckoutFormView can remove a given key from checkout
        Session Data when it is specified in an HTTP GET request.
        '''
        calculate_shipping_cost_mock.return_value = Decimal(1.00)

        session = self.client.session
        cart = SessionCart(session)
        cart.add(self.variant)
        session.save()

        request_data = {
            'recipient_name': 'Foo Bar',
            'street_address': '123 Test St',
            'locality': 'Test',
            'region': 'OK',
            'postal_code': '12345',
            'country': 'US'
        }
        response = self.client.post(self.view_url,
                                    data=request_data,
                                    follow=True)

        request_data = {'shipping': 'shipping'}
        response = self.client.get(self.view_url, data=request_data)

        self.assertNotIn('shipping', self.client.session[views.UUID])
    def test_view_detects_malformed_session_data(self,
                                                 calculate_shipping_cost_mock):
        '''
        Test that the View validates Session Data on an ongoing basis.
        '''
        calculate_shipping_cost_mock.return_value = Decimal(1.00)

        session = self.client.session
        cart = SessionCart(session)
        cart.add(self.variant)
        session.save()

        # Set up HTTP POST request.
        session_data = {
            'recipient_name': 'Foo Bar',
            'street_address': '123 Test St',
            'locality': 'Test',
            'region': 'OK',
            'postal_code': '1234567890',  # Simulate bad ZIP code.
            'country': 'US'
        }

        session = self.client.session
        session[views.UUID] = {'shipping': session_data}
        session.save()

        request_data = {'payment_method_nonce': 'fake-valid-nonce'}
        response = self.client.post(self.view_url,
                                    data=request_data,
                                    follow=True)
        rendered_html = response.content.decode()

        self.assertNotIn('shipping', self.client.session[views.UUID])
    def test_view_can_load_and_retrieve_shipping_cost_from_invalid_backend(
            self, sum_mock, settings_mock, get_shipping_rate_mock):
        '''
        Test that the CheckoutFormView can successfully load and retrieve a
        shipping cost from an API.
        '''
        sum_mock.return_value = Decimal(0.00)
        get_shipping_rate_mock.return_value = Decimal(0.00)

        settings_mock.FULFILLMENT_BACKENDS = [('foo', 'Foo')]

        session = self.client.session
        cart = SessionCart(session)
        cart.add(self.variant)
        session.save()

        # Set up HTTP POST request.
        request_data = {
            'recipient_name': 'Foo Bar',
            'street_address': '123 Test St',
            'locality': 'Test',
            'region': 'OK',
            'postal_code': '12345',
            'country': 'US'
        }

        response = self.client.post(self.view_url,
                                    data=request_data,
                                    follow=True)

        self.assertNotIn('shipping', self.client.session[views.UUID])
    def test_view_adds_valid_shipping_information_to_session_variable(
            self, calculate_shipping_cost_mock):
        '''
        Test that the CheckoutFormView adds valid shipping information from the
        AddressForm to the User's Session.
        '''
        calculate_shipping_cost_mock.return_value = Decimal(1.00)

        session = self.client.session
        cart = SessionCart(session)
        cart.add(self.variant)
        session.save()

        # Set up HTTP POST request.
        request_data = {
            'recipient_name': 'Foo Bar',
            'street_address': '123 Test St',
            'locality': 'Test',
            'region': 'OK',
            'postal_code': '12345',
            'country': 'US'
        }
        response = self.client.post(self.view_url,
                                    data=request_data,
                                    follow=True)

        self.assertEqual(self.client.session[views.UUID]['shipping'],
                         request_data)
    def test_view_advances_to_confirmation_page(self, sale_mock,
                                                calculate_shipping_cost_mock):
        '''
        Test that the CheckoutFormView can advance to the PaymentForm
        '''
        calculate_shipping_cost_mock.return_value = Decimal(1.00)

        session = self.client.session
        cart = SessionCart(session)
        cart.add(self.variant)
        session.save()

        sale_mock.return_value.is_success = True
        sale_mock.return_value.transaction.id = '12345'
        sale_mock.return_value.transaction.amount = Decimal(1.00)
        sale_mock.return_value.transaction.created_at = timezone.now()
        sale_mock.return_value.transaction.credit_card = {
            'cardholder_name': 'Foo Bar',
            'customer_location': 'US',
            'card_type': 'Foo',
            'last_4': '1234',
            'expiration_month': '12',
            'expiration_year': '2034',
        }

        # Set up HTTP POST request.
        request_data = {
            'recipient_name': 'Foo Bar',
            'street_address': '123 Test St',
            'locality': 'Test',
            'region': 'OK',
            'postal_code': '12345',
            'country': 'US'
        }
        response1 = self.client.post(self.view_url,
                                     data=request_data,
                                     follow=True)

        request_data = {'payment_method_nonce': 'fake-valid-nonce'}
        response2 = self.client.post(self.view_url,
                                     data=request_data,
                                     follow=True)
        rendered_html = response2.content.decode()

        order = Order.objects.last()
        expected = ('<h2>Order <strong>%s</strong> has been received!</h2>' %
                    order.token)

        self.assertIn(expected, rendered_html)
    def test_view_returns_200_status_if_variants_are_in_cart(self):
        '''
        Test that the CheckoutFormView returns a 200 OK status if there are
        Variants the SessionCart.
        '''
        session = self.client.session
        cart = SessionCart(session)
        cart.add(self.variant)
        session.save()

        response = self.client.get(self.view_url)
        response = response.render()
        rendered_html = response.content.decode()

        self.assertEqual(response.status_code, 200)
Example #10
0
    def form_valid(self, form):
        logger.info('%s is valid' % type(form).__name__)

        cart = SessionCart(self.request.session)

        variants = Variant.objects.filter(product=self.product)
        for variant in variants:
            relevant_attributes = {
                key: variant.attributes[key]
                for key in variant.attributes if key in form.cleaned_data
            }
            if relevant_attributes == form.cleaned_data:
                logger.info('User selected "%s"' % variant)
                cart.add(variant)
                break

        return super(ProductView, self).form_valid(form)
Example #11
0
    def get_context_data(self, **kwargs):
        context = super(ProductView, self).get_context_data(**kwargs)

        cart = SessionCart(self.request.session)

        context.update({'product': self.product, 'cart': cart})

        return context
    def test_view_gracefully_handles_invalid_step_data_deletion_requests(
            self, calculate_shipping_cost_mock):
        '''
        Test that the CheckoutFormView gracefully handles step deletion when the
        step is not present within checkout Session Data.
        '''
        calculate_shipping_cost_mock.return_value = Decimal(1.00)

        session = self.client.session
        cart = SessionCart(session)
        cart.add(self.variant)
        session.save()

        # Set up HTTP GET request.
        request_data = {'shipping': 'shipping'}
        response = self.client.get(self.view_url, data=request_data)

        self.assertNotIn('shipping', self.client.session[views.UUID])
    def test_view_initially_redirects_to_self(self):
        '''
        Test that the CheckoutFormView initially redirects to itself.
        '''
        session = self.client.session
        cart = SessionCart(session)
        cart.add(self.variant)
        session.save()

        # Set up HTTP POST request.
        request_data = {
            'recipient_name': 'Foo Bar',
            'street_address': '123 Test St',
            'locality': 'Test',
            'region': 'OK',
            'postal_code': '12345',
            'country': 'US'
        }

        response = self.client.post(self.view_url, data=request_data)

        self.assertEqual(response.url, self.view_url)
Example #14
0
    def get_context_data(self, **kwargs):
        context = super(HomePageView, self).get_context_data(**kwargs)

        cart = SessionCart(self.request.session)

        context.update({
            'products':
            [product for product in Product.objects.all() if product.salable],
            'cart':
            cart
        })

        return context
Example #15
0
    def dispatch(self, request, *args, **kwargs):

        if not self.request.session.has_key(UUID):
            self.request.session[UUID] = {}

        self.cart = SessionCart(self.request.session)
        self.subtotal = self.cart.total

        self.shipping_address = self.request.session[UUID].get('shipping')

        self.shipping_cost = Decimal(0.00)
        self.sales_tax = Decimal(0.00)
        self.total = Decimal(0.00)

        if self.shipping_address:

            # Calculate the shipping cost.
            self.shipping_cost = calculate_shipping_cost(
                address=self.shipping_address, products=self.cart)

            # Reset 'shipping' step, if no shipping cost could be calculated.
            if not self.shipping_cost:
                self.shipping_address = None
                del self.request.session[UUID]['shipping']
                self.request.session.modified = True

            # Otherwise, calculate sales tax, if the user is based in the US.
            elif self.shipping_address['country'] == 'US':
                # Disregard any ZIP+4 information.
                zip_code = self.shipping_address['postal_code'].split('-')[0]

                tax_rates = {
                    tax_rate.postal_code: tax_rate.tax_rate
                    for tax_rate in TaxRate.objects.all()
                }
                if zip_code in tax_rates:
                    sales_tax_rate = Decimal(0.06)
                    sales_tax = self.subtotal * sales_tax_rate
                    self.sales_tax = Decimal(sales_tax).quantize(
                        Decimal('1.00'), rounding=ROUND_CEILING)

        self.total = (self.subtotal + self.shipping_cost + self.sales_tax)

        self.steps = (
            {
                'name': 'shipping',
                'form_class': AddressForm,
                'template': 'orders/checkout.html',
                'context': {
                    'description': 'where are we sending this?',
                }
            },
            {
                'name': 'payment',
                'form_class': PaymentForm,
                'template': 'orders/checkout.html',
                'form_kwargs': {
                    'amount': self.total
                },
                'context': {
                    'description': 'how are you paying?',
                    'client_token': self.client_token,
                    'shipping_cost': self.shipping_cost,
                    'sales_tax': self.sales_tax,
                    'total': self.total
                }
            },
        )

        self.current_step = self.get_current_step()

        return super(CheckoutFormView, self).dispatch(request, *args, **kwargs)
Example #16
0
class CheckoutFormView(FormView):

    form_class = AddressForm
    template_name = 'orders/checkout.html'
    success_url = '/'

    def __init__(self, *args, **kwargs):
        super(CheckoutFormView, self).__init__(*args, **kwargs)

        self.client_token = braintree.ClientToken.generate()
        self.deleted_data = None

    def get(self, request, *args, **kwargs):

        deleted_steps = [
            key for key in request.GET if key in self.request.session[UUID]
        ]
        for key in deleted_steps:
            self.delete_previous_step(key)

        template_response = None
        if deleted_steps:
            template_response = self.load_previous_step()

        return (super(CheckoutFormView, self).get(request, *args, **kwargs)
                if not template_response else template_response)

    def post(self, request, *args, **kwargs):

        template_response = None

        if not self.session_data_is_valid():
            template_response = self.load_previous_step()

        return (super(CheckoutFormView, self).post(request, *args, **kwargs)
                if not template_response else template_response)

    def dispatch(self, request, *args, **kwargs):

        if not self.request.session.has_key(UUID):
            self.request.session[UUID] = {}

        self.cart = SessionCart(self.request.session)
        self.subtotal = self.cart.total

        self.shipping_address = self.request.session[UUID].get('shipping')

        self.shipping_cost = Decimal(0.00)
        self.sales_tax = Decimal(0.00)
        self.total = Decimal(0.00)

        if self.shipping_address:

            # Calculate the shipping cost.
            self.shipping_cost = calculate_shipping_cost(
                address=self.shipping_address, products=self.cart)

            # Reset 'shipping' step, if no shipping cost could be calculated.
            if not self.shipping_cost:
                self.shipping_address = None
                del self.request.session[UUID]['shipping']
                self.request.session.modified = True

            # Otherwise, calculate sales tax, if the user is based in the US.
            elif self.shipping_address['country'] == 'US':
                # Disregard any ZIP+4 information.
                zip_code = self.shipping_address['postal_code'].split('-')[0]

                tax_rates = {
                    tax_rate.postal_code: tax_rate.tax_rate
                    for tax_rate in TaxRate.objects.all()
                }
                if zip_code in tax_rates:
                    sales_tax_rate = Decimal(0.06)
                    sales_tax = self.subtotal * sales_tax_rate
                    self.sales_tax = Decimal(sales_tax).quantize(
                        Decimal('1.00'), rounding=ROUND_CEILING)

        self.total = (self.subtotal + self.shipping_cost + self.sales_tax)

        self.steps = (
            {
                'name': 'shipping',
                'form_class': AddressForm,
                'template': 'orders/checkout.html',
                'context': {
                    'description': 'where are we sending this?',
                }
            },
            {
                'name': 'payment',
                'form_class': PaymentForm,
                'template': 'orders/checkout.html',
                'form_kwargs': {
                    'amount': self.total
                },
                'context': {
                    'description': 'how are you paying?',
                    'client_token': self.client_token,
                    'shipping_cost': self.shipping_cost,
                    'sales_tax': self.sales_tax,
                    'total': self.total
                }
            },
        )

        self.current_step = self.get_current_step()

        return super(CheckoutFormView, self).dispatch(request, *args, **kwargs)

    def get_current_step(self):
        '''
        Get the current step within the form wizard.
        '''
        completed_steps = self.request.session.get(UUID, {})

        remaining_steps = [
            step for step in self.steps if step['name'] not in completed_steps
        ]
        current_step = remaining_steps[0] if remaining_steps else None

        logger.debug('Completed steps: %s' %
                     ', '.join(step for step in completed_steps))
        logger.debug('Remaining steps: %s' %
                     ', '.join(step['name'] for step in remaining_steps))
        logger.debug('Current step: %s' %
                     (current_step['name'] if current_step else ''))

        return current_step

    def get_template_names(self):
        template_name = self.current_step['template']

        return [template_name]

    def get_form_class(self):
        logger.debug('Getting form class...')
        return self.current_step['form_class']

    def get_form_kwargs(self):
        kwargs = super(CheckoutFormView, self).get_form_kwargs()

        if 'form_kwargs' in self.current_step:
            kwargs.update(self.current_step['form_kwargs'])

        return kwargs

    def get_form(self, form_class=None):
        '''
        Get the Form object that will be supplied to the FormView's context.
        '''
        # Instantiate Form.
        form = super(CheckoutFormView, self).get_form(form_class=form_class)

        if isinstance(form, AddressForm):
            # Determine the IP address associated to the HTTP Request.
            ip_address = get_real_ip(self.request)

            # Populate the form's `country` field with the user's apparent
            # location.
            if ip_address and not form.is_bound:
                geo_ip2 = GeoIP2()
                location = geo_ip2.country(ip_address)
                form.fields['country'].initial = location['country_code']

        logger.debug(
            'Got %s %s form' %
            ('bound' if form.is_bound else 'unbound', form.__class__.__name__))

        return form

    def form_valid(self, form):

        self.request.session[UUID].update({
            self.current_step['name']:
            form.cleaned_data,
        })
        self.request.session.modified = True

        # Create an order if there are no more steps to complete.
        if not self.get_current_step():
            shipping_address = Address.objects.create(**self.shipping_address)
            order = Order.objects.create(shipping_address=shipping_address,
                                         subtotal=self.subtotal,
                                         sales_tax=self.sales_tax,
                                         shipping_cost=self.shipping_cost,
                                         total=self.total)

            self.order_token = order.token

            for variant in self.cart:
                purchase = Purchase.objects.create(order=order,
                                                   variant=variant,
                                                   price=variant.price)

            Transaction.objects.create(
                order=order,
                transaction_id=form.cleaned_data.get('transaction_id'),
                amount=form.cleaned_data.get('amount'),
                cardholder_name=form.cleaned_data.get('cardholder_name'),
                country=form.cleaned_data.get('country'),
                payment_card_type=form.cleaned_data.get('payment_card_type'),
                payment_card_last_4=form.cleaned_data.get(
                    'payment_card_last_4'),
                payment_card_expiration_date=form.cleaned_data.get(
                    'payment_card_expiration_date'),
                created_at=form.cleaned_data.get('created_at'),
                origin_ip_address=get_real_ip(self.request),
                authorized=form.cleaned_data.get('authorized'))

        return super(CheckoutFormView, self).form_valid(form)

    def session_data_is_valid(self):

        session_data = self.request.session.get(UUID, None)

        is_valid = True
        if session_data:
            completed_steps = [
                step for step in self.steps if step['name'] in session_data
            ]

            for completed_step in completed_steps:
                step_name = completed_step['name']
                form_class = completed_step['form_class']

                form = form_class(data=session_data[step_name])
                if not form.is_valid():
                    is_valid = False
                    break

        return is_valid

    def delete_previous_step(self, key=None):
        '''
        '''
        session_data = self.request.session[UUID]

        if not key:
            previous_index = self.steps.index(self.current_step) - 1
            key = self.steps[previous_index]['name']

        if key in session_data:
            self.deleted_data = session_data.get(key, None)
            del session_data[key]
            self.request.session.modified = True

        self.current_step = self.get_current_step()

    def load_previous_step(self):
        '''
        '''
        self.delete_previous_step()

        form_class = self.get_form_class()
        form = form_class(data=self.deleted_data)

        if not form.is_valid():
            form.add_error(None, 'Something went wrong here...')

        template_names = self.get_template_names()
        context_data = self.get_context_data(form=form)

        template_response = TemplateResponse(self.request, template_names,
                                             context_data)

        return template_response

    def get_success_url(self):
        logger.info('Getting Success URL...')

        if self.get_current_step():
            url = reverse('checkout:main')
        else:
            # Empty the cart.
            self.cart.empty()

            # Delete all CheckoutFormView Session Data.
            del self.request.session[UUID]

            # Add the Order Token to CheckoutFormView Session Data.
            self.request.session[UUID] = {'order_token': self.order_token}

            url = reverse('checkout:confirmation')

        logger.info('Redirecting to %s' % url)

        return url

    def get_context_data(self, **kwargs):
        context = super(CheckoutFormView, self).get_context_data(**kwargs)

        current_position = next(i for (i, step) in enumerate(self.steps)
                                if step['name'] == self.current_step['name'])

        context.update({
            'cart': self.cart,
            'subtotal': self.subtotal,
            'current_position': current_position,
            'steps': enumerate(self.steps),
        })

        step_context = self.current_step['context']
        if step_context:
            context.update(step_context)

        return context