Exemple #1
0
    def form_valid(self, form):
        self.data = form.cleaned_data
        basket = self.data['basket']
        request = self.request

        sdn_check_failure = self.check_sdn(request, self.data)
        if sdn_check_failure is not None:
            return sdn_check_failure

        self.basket_id = basket.id
        self.order_number = basket.order_number

        basket_add_organization_attribute(basket, self.data)

        # Freeze the basket since the user is paying for it now.
        basket.freeze()

        try:
            payment_processor_response, transaction_id = self.payment_processor.initiate_payment(
                basket,
                request,
                self.data,
            )
            self.transaction_id = transaction_id
        except GatewayError:
            return self.redirect_to_payment_error()
        else:
            return self.complete_order(payment_processor_response)
Exemple #2
0
    def test_handle_post_order_for_bulk_purchase(self, __):
        """
        Ensure that the bulk purchase order is linked to the provided business
        client when the method `handle_post_order` is invoked.
        """
        toggle_switch(ENROLLMENT_CODE_SWITCH, True)

        course = CourseFactory(partner=self.partner)
        course.create_or_update_seat('verified',
                                     True,
                                     50,
                                     create_enrollment_code=True)
        enrollment_code = Product.objects.get(
            product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME)
        user = UserFactory()
        basket = BasketFactory(owner=user, site=self.site)
        basket.add_product(enrollment_code, quantity=1)
        order = create_order(number=1, basket=basket, user=user)
        request_data = {
            'organization': 'Dummy Business Client',
            PURCHASER_BEHALF_ATTRIBUTE: 'False',
        }
        # Manually add organization and purchaser attributes on the basket for testing
        basket_add_organization_attribute(basket, request_data)

        EdxOrderPlacementMixin().handle_post_order(order)

        # Now verify that a new business client has been created in current
        # order is now linked with that client through Invoice model.
        business_client = BusinessClient.objects.get(
            name=request_data['organization'])
        assert Invoice.objects.get(
            order=order).business_client == business_client
Exemple #3
0
    def test_handle_post_order_for_seat_purchase(self, __):
        """
        Ensure that the single seat purchase order is not linked any business
        client when the method `handle_post_order` is invoked.
        """
        toggle_switch(ENROLLMENT_CODE_SWITCH, False)

        course = CourseFactory(partner=self.partner)
        verified_product = course.create_or_update_seat('verified', True, 50)
        user = UserFactory()
        basket = BasketFactory(owner=user, site=self.site)
        basket.add_product(verified_product, quantity=1)
        order = create_order(number=1, basket=basket, user=user)
        request_data = {
            'organization': 'Dummy Business Client',
            PURCHASER_BEHALF_ATTRIBUTE: 'False',
        }
        # Manually add organization and purchaser attributes on the basket for testing
        basket_add_organization_attribute(basket, request_data)

        EdxOrderPlacementMixin().handle_post_order(order)

        # Now verify that the single seat order is not linked to business
        # client by checking that there is no record for BusinessClient.
        assert not BusinessClient.objects.all()
Exemple #4
0
    def create_order_with_billing_address(self):
        """ Creates an order object with a bit of extra information for HubSpot unit tests"""
        enrollment_code = Product.objects.get(
            product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME)
        user = UserFactory()
        basket = factories.BasketFactory(owner=user, site=self.site)
        basket.add_product(enrollment_code, self.QUANTITY)

        # add organization and purchaser attributes manually to the basket for testing purposes
        basket_data = {
            'organization': 'Dummy Business Client',
            PURCHASER_BEHALF_ATTRIBUTE: 'True'
        }
        basket_add_organization_attribute(basket, basket_data)

        # add some additional data the billing address to exercise some of the code paths in the unit we are testing
        billing_address = factories.BillingAddressFactory()
        billing_address.line2 = 'Suite 321'
        billing_address.line4 = "City"
        billing_address.state = "State"
        billing_address.country.name = "United States of America"

        # create new order adding in the additional billing address info
        return create_order(number=2,
                            basket=basket,
                            user=user,
                            billing_address=billing_address)
Exemple #5
0
    def test_successful_order_for_bulk_purchase(self):
        """
        Verify the view redirects to the Receipt page when the Order has been
        successfully placed for bulk purchase and also that the order is linked
        to the provided business client.
        """
        toggle_switch(ENROLLMENT_CODE_SWITCH, True)

        course = CourseFactory()
        course.create_or_update_seat('verified', True, 50, create_enrollment_code=True)
        enrollment_code = Product.objects.get(product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME)
        self.basket = create_basket(owner=self.user, site=self.site)
        self.basket.add_product(enrollment_code, quantity=1)

        # The basket should not have an associated order if no payment was made.
        self.assertFalse(Order.objects.filter(basket=self.basket).exists())

        request_data = self.generate_notification(
            self.basket,
            billing_address=self.billing_address,
        )
        request_data.update({'organization': 'Dummy Business Client'})
        request_data.update({PURCHASER_BEHALF_ATTRIBUTE: "False"})
        # Manually add organization and purchaser attributes on the basket for testing
        basket_add_organization_attribute(self.basket, request_data)

        response = self.client.post(self.path, request_data)
        self.assertTrue(Order.objects.filter(basket=self.basket).exists())
        self.assertEqual(response.status_code, 302)

        # Now verify that a new business client has been created and current
        # order is now linked with that client through Invoice model.
        order = Order.objects.filter(basket=self.basket).first()
        business_client = BusinessClient.objects.get(name=request_data['organization'])
        assert Invoice.objects.get(order=order).business_client == business_client
Exemple #6
0
    def _get_basket(self, payment_id):
        """
        Retrieve a basket using a payment ID.

        Arguments:
            payment_id: payment_id received from PayPal.

        Returns:
            It will return related basket or log exception and return None if
            duplicate payment_id received or any other exception occurred.

        """
        try:
            basket = PaymentProcessorResponse.objects.get(
                processor_name=self.payment_processor.NAME,
                transaction_id=payment_id
            ).basket
            basket.strategy = strategy.Default()
            Applicator().apply(basket, basket.owner, self.request)

            basket_add_organization_attribute(basket, self.request.GET)
            return basket
        except MultipleObjectsReturned:
            logger.warning(u"Duplicate payment ID [%s] received from PayPal.", payment_id)
            return None
        except Exception:  # pylint: disable=broad-except
            logger.exception(u"Unexpected error during basket retrieval while executing PayPal payment.")
            return None
Exemple #7
0
 def add_required_attributes_to_basket(self, order, purchased_by_org):
     """ Utility method that will setup Basket with attributes needed for unit tests """
     # add organization and purchaser attributes manually to the basket for testing purposes
     basket_data = {
         'organization': 'Dummy Business Client',
         PURCHASER_BEHALF_ATTRIBUTE: '{}'.format(purchased_by_org)
     }
     basket_add_organization_attribute(order.basket, basket_data)
Exemple #8
0
    def test_successful_payment_for_bulk_purchase(self):
        """
        Verify that when a Order has been successfully placed for bulk
        purchase then that order is linked to the provided business client.
        """
        toggle_switch(ENROLLMENT_CODE_SWITCH, True)

        course = CourseFactory()
        course.create_or_update_seat('verified',
                                     True,
                                     50,
                                     self.partner,
                                     create_enrollment_code=True)
        basket = create_basket(owner=self.user, site=self.site)
        enrollment_code = Product.objects.get(
            product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME)
        basket.add_product(enrollment_code, quantity=1)
        basket.strategy = Selector().strategy()

        data = self.generate_form_data(basket.id)
        data.update({'organization': 'Dummy Business Client'})

        # Manually add organization attribute on the basket for testing
        basket_add_organization_attribute(basket, data)

        card_type = 'American Express'
        label = '1986'
        charge = stripe.Charge.construct_from(
            {
                'id': '2404',
                'source': {
                    'brand': card_type,
                    'last4': label,
                },
            }, 'fake-key')

        billing_address = BillingAddressFactory()
        with mock.patch.object(Stripe,
                               'get_address_from_token') as address_mock:
            address_mock.return_value = billing_address

            with mock.patch.object(stripe.Charge, 'create') as charge_mock:
                charge_mock.return_value = charge
                response = self.client.post(self.path, data)

            address_mock.assert_called_once_with(data['stripe_token'])

        self.assert_successful_order_response(response, basket.order_number)
        self.assert_order_created(basket, billing_address, card_type, label)

        # Now verify that a new business client has been created and current
        # order is now linked with that client through Invoice model.
        order = Order.objects.filter(basket=basket).first()
        business_client = BusinessClient.objects.get(name=data['organization'])
        assert Invoice.objects.get(
            order=order).business_client == business_client
Exemple #9
0
    def _get_basket(self, payment_id):
        """
        Retrieve a basket using a payment ID.

        Arguments:
            payment_id: payment_id received from PayPal.

        Returns:
            It will return related basket or log exception and return None if
            duplicate payment_id received or any other exception occurred.

        """
        try:
            basket = PaymentProcessorResponse.objects.get(
                processor_name=self.payment_processor.NAME,
                transaction_id=payment_id).basket
            basket.strategy = strategy.Default()
            # TODO: Remove as a part of REVMI-124 as this is a hacky solution
            # The problem is that orders are being created after payment processing, and the discount is not
            # saved in the database, so it needs to be calculated again in order to save the correct info to the
            # order. REVMI-124 will create the order before payment processing, when we have the discount context.
            if waffle.flag_is_active(
                    self.request,
                    DYNAMIC_DISCOUNT_FLAG) and basket.lines.count() == 1:
                discount_lms_url = get_lms_url('/api/discounts/')
                lms_discount_client = EdxRestApiClient(
                    discount_lms_url,
                    jwt=self.request.site.siteconfiguration.access_token)
                ck = basket.lines.first().product.course_id
                user_id = self.request.user.lms_user_id
                try:
                    response = lms_discount_client.user(user_id).course(
                        ck).get()
                    self.request.GET = self.request.GET.copy()
                    self.request.GET['discount_jwt'] = response.get('jwt')
                except (SlumberHttpBaseException, Timeout) as error:
                    logger.warning(
                        'Failed to get discount jwt from LMS. [%s] returned [%s]',
                        discount_lms_url, error.response)
            # END TODO

            Applicator().apply(basket, basket.owner, self.request)

            basket_add_organization_attribute(basket, self.request.GET)
            return basket
        except MultipleObjectsReturned:
            logger.warning(u"Duplicate payment ID [%s] received from PayPal.",
                           payment_id)
            return None
        except Exception:  # pylint: disable=broad-except
            logger.exception(
                u"Unexpected error during basket retrieval while executing PayPal payment."
            )
            return None
Exemple #10
0
    def test_execution_for_bulk_purchase(self):
        """
        Verify redirection to LMS receipt page after attempted payment
        execution if the Otto receipt page is disabled for bulk purchase and
        also that the order is linked to the provided business client..
        """
        toggle_switch(ENROLLMENT_CODE_SWITCH, True)
        self.mock_oauth2_response()

        course = CourseFactory(partner=self.partner)
        course.create_or_update_seat('verified',
                                     True,
                                     50,
                                     create_enrollment_code=True)
        self.basket = create_basket(owner=UserFactory(), site=self.site)
        enrollment_code = Product.objects.get(
            product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME)
        factories.create_stockrecord(enrollment_code,
                                     num_in_stock=2,
                                     price_excl_tax='10.00')
        self.basket.add_product(enrollment_code, quantity=1)

        # Create a payment record the view can use to retrieve a basket
        self.mock_payment_creation_response(self.basket)
        self.processor.get_transaction_parameters(self.basket,
                                                  request=self.request)
        self.mock_payment_execution_response(self.basket)
        self.mock_payment_creation_response(self.basket, find=True)

        # Manually add organization attribute on the basket for testing
        self.RETURN_DATA.update({'organization': 'Dummy Business Client'})
        self.RETURN_DATA.update({PURCHASER_BEHALF_ATTRIBUTE: 'False'})
        basket_add_organization_attribute(self.basket, self.RETURN_DATA)

        response = self.client.get(reverse('paypal:execute'), self.RETURN_DATA)
        self.assertRedirects(
            response,
            get_receipt_page_url(
                order_number=self.basket.order_number,
                site_configuration=self.basket.site.siteconfiguration,
                disable_back_button=True,
            ),
            fetch_redirect_response=False)

        # Now verify that a new business client has been created and current
        # order is now linked with that client through Invoice model.
        order = Order.objects.filter(basket=self.basket).first()
        business_client = BusinessClient.objects.get(
            name=self.RETURN_DATA['organization'])
        assert Invoice.objects.get(
            order=order).business_client == business_client
Exemple #11
0
    def form_valid(self, form):
        data = form.cleaned_data
        basket = data['basket']
        request = self.request
        user = request.user

        sdn_check_failure = self.check_sdn(request, data)
        if sdn_check_failure is not None:
            return sdn_check_failure

        # Add extra parameters for Silent Order POST
        extra_parameters = {
            'payment_method':
            'card',
            'unsigned_field_names':
            ','.join(Cybersource.PCI_FIELDS),
            'bill_to_email':
            user.email,
            # Fall back to order number when there is no session key (JWT auth)
            'device_fingerprint_id':
            request.session.session_key or basket.order_number,
        }

        for source, destination in self.FIELD_MAPPINGS.items():
            extra_parameters[destination] = clean_field_value(data[source])

        parameters = Cybersource(self.request.site).get_transaction_parameters(
            basket,
            use_client_side_checkout=True,
            extra_parameters=extra_parameters)

        logger.info(
            'Parameters signed for CyberSource transaction [%s], associated with basket [%d].',
            # TODO: transaction_id is None in logs. This should be fixed.
            parameters.get('transaction_id'),
            basket.id)

        # This parameter is only used by the Web/Mobile flow. It is not needed for for Silent Order POST.
        parameters.pop('payment_page_url', None)

        # Ensure that the response can be properly rendered so that we
        # don't have to deal with thawing the basket in the event of an error.
        response = JsonResponse({'form_fields': parameters})

        basket_add_organization_attribute(basket, data)

        # Freeze the basket since the user is paying for it now.
        basket.freeze()

        return response
Exemple #12
0
    def form_valid(self, form):
        form_data = form.cleaned_data
        basket = form_data['basket']
        token = form_data['stripe_token']
        order_number = basket.order_number

        basket_add_organization_attribute(basket, self.request.POST)

        try:
            billing_address = self.payment_processor.get_address_from_token(
                token)
        except Exception:  # pylint: disable=broad-except
            logger.exception(
                'An error occurred while parsing the billing address for basket [%d]. No billing address will be '
                'stored for the resulting order [%s].', basket.id,
                order_number)
            billing_address = None

        try:
            self.handle_payment(token, basket)
        except Exception:  # pylint: disable=broad-except
            logger.exception(
                'An error occurred while processing the Stripe payment for basket [%d].',
                basket.id)
            return JsonResponse({}, status=400)

        shipping_method = NoShippingRequired()
        shipping_charge = shipping_method.calculate(basket)
        order_total = OrderTotalCalculator().calculate(basket, shipping_charge)

        order = self.handle_order_placement(order_number=order_number,
                                            user=basket.owner,
                                            basket=basket,
                                            shipping_address=None,
                                            shipping_method=shipping_method,
                                            shipping_charge=shipping_charge,
                                            billing_address=billing_address,
                                            order_total=order_total,
                                            request=self.request)
        self.handle_post_order(order)

        receipt_url = get_receipt_page_url(
            site_configuration=self.request.site.siteconfiguration,
            order_number=order_number,
            disable_back_button=True,
        )
        return JsonResponse({'url': receipt_url}, status=201)
Exemple #13
0
    def _get_basket(self, payment_id):
        """
        Retrieve a basket using a payment ID.

        Arguments:
            payment_id: payment_id received from PayPal.

        Returns:
            It will return related basket or log exception and return None if
            duplicate payment_id received or any other exception occurred.

        """
        try:
            basket = PaymentProcessorResponse.objects.get(
                processor_name=self.payment_processor.NAME,
                transaction_id=payment_id).basket
            basket.strategy = strategy.Default()

            # TODO: Remove as a part of REVMI-124 as this is a hacky solution
            # The problem is that orders are being created after payment processing, and the discount is not
            # saved in the database, so it needs to be calculated again in order to save the correct info to the
            # order. REVMI-124 will create the order before payment processing, when we have the discount context.
            self._add_dynamic_discount_to_request(basket)
            # END TODO

            Applicator().apply(basket, basket.owner, self.request)

            basket_add_organization_attribute(basket, self.request.GET)
            return basket
        except MultipleObjectsReturned:
            logger.warning(u"Duplicate payment ID [%s] received from PayPal.",
                           payment_id)
            return None
        except Exception:  # pylint: disable=broad-except
            logger.exception(
                u"Unexpected error during basket retrieval while executing PayPal payment."
            )
            return None
Exemple #14
0
    def _get_basket(self, payment_id):
        """
        Retrieve a basket using a payment ID.

        Arguments:
            payment_id: payment_id received from Alipay.

        Returns:

        """
        try:
            basket = PaymentProcessorResponse.objects.get(
                processor_name=self.payment_processor.NAME,
                transaction_id=payment_id).basket
            basket.strategy = strategy.Default()
            Applicator().apply(basket, basket.owner, self.request)

            basket_add_organization_attribute(basket, self.request.GET)
            return basket
        except MultipleObjectsReturned:
            return None
        except Exception, e:
            logger.exception(e)
            return None
    def form_valid(self, form):
        data = form.cleaned_data
        basket = data['basket']
        request = self.request
        user = request.user

        hit_count = checkSDN(request,
                             data['first_name'] + ' ' + data['last_name'],
                             data['city'], data['country'])

        if hit_count > 0:
            logger.info(
                'SDNCheck function called for basket [%d]. It received %d hit(s).',
                request.basket.id,
                hit_count,
            )
            response_to_return = {
                'error': 'There was an error submitting the basket',
                'sdn_check_failure': {
                    'hit_count': hit_count
                }
            }

            return JsonResponse(response_to_return, status=403)

        logger.info(
            'SDNCheck function called for basket [%d]. It did not receive a hit.',
            request.basket.id,
        )

        # Add extra parameters for Silent Order POST
        extra_parameters = {
            'payment_method':
            'card',
            'unsigned_field_names':
            ','.join(Cybersource.PCI_FIELDS),
            'bill_to_email':
            user.email,
            # Fall back to order number when there is no session key (JWT auth)
            'device_fingerprint_id':
            request.session.session_key or basket.order_number,
        }

        for source, destination in six.iteritems(self.FIELD_MAPPINGS):
            extra_parameters[destination] = clean_field_value(data[source])

        parameters = Cybersource(self.request.site).get_transaction_parameters(
            basket,
            use_client_side_checkout=True,
            extra_parameters=extra_parameters)

        logger.info(
            'Parameters signed for CyberSource transaction [%s], associated with basket [%d].',
            # TODO: transaction_id is None in logs. This should be fixed.
            parameters.get('transaction_id'),
            basket.id)

        # This parameter is only used by the Web/Mobile flow. It is not needed for for Silent Order POST.
        parameters.pop('payment_page_url', None)

        # Ensure that the response can be properly rendered so that we
        # don't have to deal with thawing the basket in the event of an error.
        response = JsonResponse({'form_fields': parameters})

        basket_add_organization_attribute(basket, data)

        # Freeze the basket since the user is paying for it now.
        basket.freeze()

        return response
Exemple #16
0
    def form_valid(self, form):
        form_data = form.cleaned_data
        #basket = form_data['basket']
        committed_basket = Basket.objects.filter(owner=self.request.user,
                                                 status="Commited").last()
        committed_basket.strategy = self.request.strategy
        committed_basket.save()
        token = form_data['stripe_token']
        order_number = committed_basket.order_number
        if waffle.flag_is_active(
                self.request,
                DYNAMIC_DISCOUNT_FLAG) and committed_basket.lines.count() == 1:
            discount_lms_url = get_lms_url('/api/discounts/')
            lms_discount_client = EdxRestApiClient(
                discount_lms_url,
                jwt=self.request.site.siteconfiguration.access_token)
            ck = committed_basket.lines.first().product.course_id
            user_id = committed_basket.owner.lms_user_id
            response = lms_discount_client.course(ck).get()
            self.request.GET = self.request.GET.copy()
            self.request.GET['discount_jwt'] = response.get('jwt')
            self.request.POST = self.request.POST.copy()
            self.request.POST['discount_jwt'] = response.get('jwt')
        Applicator().apply(committed_basket, self.request.user, self.request)
        basket_add_organization_attribute(committed_basket, self.request.POST)
        committed_basket.freeze()
        try:
            billing_address = self.payment_processor.get_address_from_token(
                token)
        except Exception:  # pylint: disable=broad-except
            logger.exception(
                'An error occurred while parsing the billing address for basket [%d]. No billing address will be '
                'stored for the resulting order [%s].', committed_basket.id,
                order_number)
            billing_address = None

        try:
            self.handle_payment(token, committed_basket)
        except Exception:  # pylint: disable=broad-except
            logger.exception(
                'An error occurred while processing the Stripe payment for basket [%d].',
                committed_basket.id)
            return JsonResponse({}, status=400)

        # Remove paid items from last open baskets
        user_baskets = Basket.objects.filter(owner=self.request.user,
                                             status="Open")
        if user_baskets.exists():
            last_open_basket = user_baskets.last()
            del_lines = committed_basket.all_lines()
            open_lines = last_open_basket.all_lines()
        for line in del_lines:
            product = line.product
            filtered_lines = open_lines.filter(product_id=product.id)
            if filtered_lines.exists():
                filtered_lines.delete()
            last_open_basket.save()

        try:
            order = self.create_order(self.request,
                                      committed_basket,
                                      billing_address=billing_address)
            send_confirm_purchase_email(None,
                                        user=self.request.user,
                                        order=order)
        except Exception:  # pylint: disable=broad-except
            logger.exception(
                'An error occurred while processing the Stripe payment for basket [%d].',
                committed_basket.id)
            return JsonResponse({}, status=400)

        self.handle_post_order(order)

        receipt_url = get_receipt_page_url(
            site_configuration=self.request.site.siteconfiguration,
            order_number=order_number,
            disable_back_button=True,
        )
        return JsonResponse({'url': receipt_url}, status=201)