Beispiel #1
0
 def _enable_payment_providers(self):
     for path in settings.PAYMENT_PROCESSORS:
         processor = get_processor_class(path)
         Switch.objects.get_or_create(
             name=settings.PAYMENT_PROCESSOR_SWITCH_PREFIX + processor.NAME,
             defaults={'active': True}
         )
Beispiel #2
0
    def _assert_success_checkout_page(self):
        """ Verify that checkout page load successfully, and has necessary context. """

        # Create the credit seat
        self.course.create_or_update_seat('credit',
                                          True,
                                          self.price,
                                          self.partner,
                                          self.provider,
                                          credit_hours=self.credit_hours)

        self._enable_payment_providers()

        response = self.client.get(self.path)
        self.assertEqual(response.status_code, 200)
        self.assertDictContainsSubset({'course': self.course},
                                      response.context)

        # Verify that the payment processors are returned
        self.assertEqual(
            sorted(response.context['payment_processors'].keys()),
            sorted([
                get_processor_class(path).NAME.lower()
                for path in settings.PAYMENT_PROCESSORS
            ]))

        self.assertContains(
            response,
            'Congratulations! You are eligible to purchase academic course credit for this course.'
        )

        self.assertContains(response,
                            self.provider_data[0]['fulfillment_instructions'])
Beispiel #3
0
    def test_disabled_providers(self, disabled_processors, expected_context):
        """ Verify that payment processors can be disabled with their Waffle
        switches, and that an error is shown if none are available.
        """
        self._enable_payment_providers()

        for path in disabled_processors:
            processor_class = get_processor_class(path)
            switch, __ = Switch.objects.get_or_create(
                name=settings.PAYMENT_PROCESSOR_SWITCH_PREFIX +
                processor_class.NAME)
            switch.active = False
            switch.save()

        self.course.create_or_update_seat('credit',
                                          True,
                                          self.price,
                                          self.partner,
                                          self.provider,
                                          credit_hours=self.credit_hours)

        self._mock_eligibility_api(body=self.eligibilities)
        self._mock_providers_api(body=self.provider_data)

        response = self.client.get(self.path)
        self.assertEqual(response.status_code, 200)
        self.assertDictContainsSubset(expected_context, response.context)
Beispiel #4
0
 def get_queryset(self):
     """Fetch the list of payment processor classes based on Django settings."""
     processors = (get_processor_class(path)
                   for path in settings.PAYMENT_PROCESSORS)
     return [
         processor for processor in processors if processor.is_enabled()
     ]
Beispiel #5
0
    def _assert_success_checkout_page(self):
        """ Verify that checkout page load successfully, and has necessary context. """

        # Create the credit seat
        self.course.create_or_update_seat(
            'credit', True, self.price, self.partner, self.provider, credit_hours=self.credit_hours
        )

        self._enable_payment_providers()

        response = self.client.get(self.path)
        self.assertEqual(response.status_code, 200)
        self.assertDictContainsSubset({'course': self.course}, response.context)

        # Verify that the payment processors are returned
        self.assertEqual(
            sorted(response.context['payment_processors'].keys()),
            sorted([get_processor_class(path).NAME.lower() for path in settings.PAYMENT_PROCESSORS])
        )

        self.assertContains(
            response,
            'Congratulations! You are eligible to purchase academic course credit for this course.'
        )

        self.assertContains(
            response,
            self.provider_data[0]['fulfillment_instructions']
        )
Beispiel #6
0
    def get_context_data(self, **kwargs):
        context = super(Checkout, self).get_context_data(**kwargs)

        try:
            course = Course.objects.get(id=kwargs.get('course_id'))
        except Course.DoesNotExist:
            raise Http404

        # Make button text for each processor which will be shown to user.
        processors_dict = OrderedDict()
        for path in settings.PAYMENT_PROCESSORS:
            processor = get_processor_class(path).NAME.lower()
            if processor == 'cybersource':
                processors_dict[processor] = 'Checkout'
            elif processor == 'paypal':
                processors_dict[processor] = 'Checkout with PayPal'
            else:
                processors_dict[processor] = 'Checkout with {}'.format(processor)

        credit_seats = [
            seat for seat in course.seat_products if getattr(seat.attr, 'certificate_type', '') == self.CREDIT_MODE
        ]
        provider_ids = None
        if credit_seats:
            provider_ids = ",".join([seat.attr.credit_provider for seat in credit_seats if seat.attr.credit_provider])

        context.update({
            'request': self.request,
            'user': self.request.user,
            'course': course,
            'payment_processors': processors_dict,
            'credit_seats': credit_seats,
            'lms_url_root': settings.LMS_URL_ROOT,
            'provider_ids': provider_ids,
            'analytics_data': json.dumps({
                'course': {
                    'courseId': course.id
                },
                'tracking': {
                    'segmentApplicationId': settings.SEGMENT_KEY
                },
                'user': {
                    'username': self.request.user.get_username(),
                    'name': self.request.user.get_full_name(),
                    'email': self.request.user.email
                }
            })
        })

        return context
Beispiel #7
0
    def post(self, request):
        """ Handle the response we've been given from the processor. """
        payment_processor = get_processor_class(settings.PAYMENT_PROCESSORS[0])
        # check the data we get
        params = request.POST.dict()
        result = payment_processor().handle_processor_response(params)
        if result[PC.SUCCESS]:
            # get the order
            order = Order.objects.get(number=result[PC.ORDER_NUMBER])
            # register the money in Oscar
            self._register_payment(order, payment_processor.NAME)
            # fulfill the order
            self._fulfill_order(order)

        # It doesn't matter how we respond to the payment processor if the
        # payment failed.
        return HttpResponse()
Beispiel #8
0
    def test_get_checkout_page_with_credit_seats(self):
        """ Verify page loads and has the necessary context. """
        response = self.client.get(self.path)
        self.assertEqual(response.status_code, 200)
        expected = {
            'course': self.course,
            'credit_seats': [self.seat],
        }
        self.assertDictContainsSubset(expected, response.context)

        # Verify the payment processors are returned
        self.assertEqual(sorted(response.context['payment_processors'].keys()),
                         sorted([get_processor_class(path).NAME.lower() for path in settings.PAYMENT_PROCESSORS]))

        self.assertContains(
            response,
            'You are purchasing {} credit hours for'.format(self.credit_hours)
        )
Beispiel #9
0
    def get_payment_processors(self):
        """
        Returns payment processor classes enabled for the corresponding Site

        Returns:
            list[BasePaymentProcessor]: Returns payment processor classes enabled for the corresponding Site
        """
        all_processors = [get_processor_class(path) for path in settings.PAYMENT_PROCESSORS]
        all_processor_names = {processor.NAME for processor in all_processors}

        missing_processor_configurations = self.payment_processors_set - all_processor_names
        if missing_processor_configurations:
            processor_config_repr = ", ".join(missing_processor_configurations)
            log.warning(
                'Unknown payment processors [%s] are configured for site %s', processor_config_repr, self.site.id
            )

        return [
            processor for processor in all_processors
            if processor.NAME in self.payment_processors_set and processor.is_enabled()
        ]
Beispiel #10
0
    def get_payment_processors(self):
        """
        Returns payment processor classes enabled for the corresponding Site

        Returns:
            list[BasePaymentProcessor]: Returns payment processor classes enabled for the corresponding Site
        """
        all_processors = [get_processor_class(path) for path in settings.PAYMENT_PROCESSORS]
        all_processor_names = {processor.NAME for processor in all_processors}

        missing_processor_configurations = self.payment_processors_set - all_processor_names
        if missing_processor_configurations:
            processor_config_repr = ", ".join(missing_processor_configurations)
            log.warning(
                'Unknown payment processors [%s] are configured for site %s', processor_config_repr, self.site.id
            )

        return [
            processor for processor in all_processors
            if processor.NAME in self.payment_processors_set and processor.is_enabled()
        ]
Beispiel #11
0
    def test_disabled_providers(self, disabled_processors, expected_context):
        """ Verify that payment processors can be disabled with their Waffle
        switches, and that an error is shown if none are available.
        """
        for path in disabled_processors:
            processor_class = get_processor_class(path)
            switch, __ = Switch.objects.get_or_create(
                name=settings.PAYMENT_PROCESSOR_SWITCH_PREFIX + processor_class.NAME
            )
            switch.active = False
            switch.save()

        self.course.create_or_update_seat(
            'credit', True, self.price, self.partner, self.provider, credit_hours=self.credit_hours
        )

        self._mock_eligibility_api(body=self.eligibilities)
        self._mock_providers_api(body=self.provider_data)

        response = self.client.get(self.path)
        self.assertEqual(response.status_code, 200)
        self.assertDictContainsSubset(expected_context, response.context)
Beispiel #12
0
 def get_payment_processors(self):
     """ Retrieve the list of active payment processors. """
     # TODO Retrieve this information from SiteConfiguration
     processors = (get_processor_class(path) for path in settings.PAYMENT_PROCESSORS)
     return [processor for processor in processors if processor.is_enabled()]
Beispiel #13
0
 def get_queryset(self):
     """Fetch the list of payment processor classes based on Django settings."""
     processors = (get_processor_class(path) for path in settings.PAYMENT_PROCESSORS)
     return [processor for processor in processors if processor.is_enabled()]
 def _enable_payment_providers(self):
     for path in settings.PAYMENT_PROCESSORS:
         processor = get_processor_class(path)
         Switch.objects.get_or_create(
             name=settings.PAYMENT_PROCESSOR_SWITCH_PREFIX + processor.NAME,
             defaults={'active': True})
Beispiel #15
0
 def _all_payment_processors(self):
     """ Returns all processor classes declared in settings. """
     all_processors = [get_processor_class(path) for path in settings.PAYMENT_PROCESSORS]
     return all_processors
Beispiel #16
0
    def get_context_data(self, **kwargs):
        context = super(Checkout, self).get_context_data(**kwargs)

        course = get_object_or_404(Course, id=kwargs.get('course_id'))

        deadline = self._check_credit_eligibility(self.request.user, kwargs.get('course_id'))
        if not deadline:
            return {
                'error': _(u'An error has occurred. We could not confirm that you are eligible for course credit. '
                           u'Try the transaction again.')
            }

        partner = get_partner_for_site(self.request)
        # Audit seats do not have a `certificate_type` attribute, so
        # we use getattr to avoid an exception.
        credit_seats = [
            seat for seat in course.seat_products
            if getattr(seat.attr, 'certificate_type', None) == self.CREDIT_MODE and
            seat.stockrecords.filter(partner=partner).exists()
        ]

        if not credit_seats:
            return {
                'error': _(u'No credit seat is available for this course.')
            }

        providers = self._get_providers_detail(credit_seats)
        if not providers:
            return {
                'error': _(u'An error has occurred. We could not confirm that the institution you selected offers this '
                           u'course credit. Try the transaction again.')
            }

        # Make button text for each processor which will be shown to user.
        processors_dict = OrderedDict()
        for path in settings.PAYMENT_PROCESSORS:
            processor_class = get_processor_class(path)
            if not processor_class.is_enabled():
                continue
            processor = processor_class.NAME.lower()
            if processor == 'cybersource':
                processors_dict[processor] = 'Checkout'
            elif processor == 'paypal':
                processors_dict[processor] = 'Checkout with PayPal'
            else:
                processors_dict[processor] = 'Checkout with {}'.format(processor)
        if len(processors_dict) == 0:
            context.update({
                'error': _(
                    u'All payment options are currently unavailable. Try the transaction again in a few minutes.'
                )
            })

        context.update({
            'course': course,
            'payment_processors': processors_dict,
            'deadline': deadline,
            'providers': providers,
            'analytics_data': json.dumps({
                'course': {
                    'courseId': course.id
                },
                'tracking': {
                    'segmentApplicationId': settings.SEGMENT_KEY
                },
                'user': {
                    'username': self.request.user.get_username(),
                    'name': self.request.user.get_full_name(),
                    'email': self.request.user.email
                }
            })
        })

        return context
Beispiel #17
0
 def get_queryset(self):
     """Fetch the list of payment processor classes based on Django settings."""
     return [get_processor_class(path) for path in settings.PAYMENT_PROCESSORS]
Beispiel #18
0
    def get_context_data(self, **kwargs):
        context = super(Checkout, self).get_context_data(**kwargs)

        course = get_object_or_404(Course, id=kwargs.get('course_id'))
        context['course'] = course

        deadline = self._check_credit_eligibility(self.request.user, kwargs.get('course_id'))
        if not deadline:
            context.update({
                'error': _('An error has occurred. We could not confirm that you are eligible for course credit. '
                           'Try the transaction again.')
            })
            return context

        partner = get_partner_for_site(self.request)
        strategy = self.request.strategy
        # Audit seats do not have a `certificate_type` attribute, so
        # we use getattr to avoid an exception.
        credit_seats = []

        for seat in course.seat_products:
            if getattr(seat.attr, 'certificate_type', None) != self.CREDIT_MODE:
                continue

            purchase_info = strategy.fetch_for_product(seat)
            if purchase_info.availability.is_available_to_buy and seat.stockrecords.filter(partner=partner).exists():
                credit_seats.append(seat)

        if not credit_seats:
            msg = _(
                'Credit is not currently available for "{course_name}". If you are currently enrolled in the '
                'course, please try again after all grading is complete. If you need additional assistance, '
                'please contact the {site_name} Support Team.'
            ).format(
                course_name=course.name,
                site_name=self.request.site.name
            )

            context.update({'error': msg})
            return context

        providers = self._get_providers_detail(credit_seats)
        if not providers:
            context.update({
                'error': _('An error has occurred. We could not confirm that the institution you selected offers this '
                           'course credit. Try the transaction again.')
            })
            return context

        # Make button text for each processor which will be shown to user.
        processors_dict = OrderedDict()
        for path in settings.PAYMENT_PROCESSORS:
            processor_class = get_processor_class(path)
            if not processor_class.is_enabled():
                continue
            processor = processor_class.NAME.lower()
            if processor == 'cybersource':
                processors_dict[processor] = 'Checkout'
            elif processor == 'paypal':
                processors_dict[processor] = 'Checkout with PayPal'
            else:
                processors_dict[processor] = 'Checkout with {}'.format(processor)
        if len(processors_dict) == 0:
            context.update({
                'error': _(
                    'All payment options are currently unavailable. Try the transaction again in a few minutes.'
                )
            })

        context.update({
            'course': course,
            'payment_processors': processors_dict,
            'deadline': deadline,
            'providers': providers,
            'analytics_data': prepare_analytics_data(
                self.request.user,
                self.request.site.siteconfiguration.segment_key,
                course.id
            ),
        })

        return context
Beispiel #19
0
    def get_context_data(self, **kwargs):
        context = super(Checkout, self).get_context_data(**kwargs)

        course = get_object_or_404(Course, id=kwargs.get('course_id'))
        context['course'] = course

        deadline = self._check_credit_eligibility(self.request.user,
                                                  kwargs.get('course_id'))
        if not deadline:
            context.update({
                'error':
                _('An error has occurred. We could not confirm that you are eligible for course credit. '
                  'Try the transaction again.')
            })
            return context

        partner = get_partner_for_site(self.request)
        strategy = self.request.strategy
        # Audit seats do not have a `certificate_type` attribute, so
        # we use getattr to avoid an exception.
        credit_seats = []

        for seat in course.seat_products:
            if getattr(seat.attr, 'certificate_type',
                       None) != self.CREDIT_MODE:
                continue

            purchase_info = strategy.fetch_for_product(seat)
            if purchase_info.availability.is_available_to_buy and seat.stockrecords.filter(
                    partner=partner).exists():
                credit_seats.append(seat)

        if not credit_seats:
            msg = _(
                'Credit is not currently available for "{course_name}". If you are currently enrolled in the '
                'course, please try again after all grading is complete. If you need additional assistance, '
                'please contact the {site_name} Support Team.').format(
                    course_name=course.name, site_name=self.request.site.name)

            context.update({'error': msg})
            return context

        providers = self._get_providers_detail(credit_seats)
        if not providers:
            context.update({
                'error':
                _('An error has occurred. We could not confirm that the institution you selected offers this '
                  'course credit. Try the transaction again.')
            })
            return context

        # Make button text for each processor which will be shown to user.
        processors_dict = OrderedDict()
        for path in settings.PAYMENT_PROCESSORS:
            processor_class = get_processor_class(path)
            if not processor_class.is_enabled():
                continue
            processor = processor_class.NAME.lower()
            if processor == 'cybersource':
                processors_dict[processor] = 'Checkout'
            elif processor == 'paypal':
                processors_dict[processor] = 'Checkout with PayPal'
            else:
                processors_dict[processor] = 'Checkout with {}'.format(
                    processor)
        if len(processors_dict) == 0:
            context.update({
                'error':
                _('All payment options are currently unavailable. Try the transaction again in a few minutes.'
                  )
            })

        context.update({
            'course':
            course,
            'payment_processors':
            processors_dict,
            'deadline':
            deadline,
            'providers':
            providers,
            'analytics_data':
            prepare_analytics_data(
                self.request.user,
                self.request.site.siteconfiguration.segment_key, course.id),
        })

        return context
Beispiel #20
0
 def test_get_processor_class(self):
     """ Verify that the method retrieves the correct class. """
     actual = helpers.get_processor_class(
         'ecommerce.extensions.payment.tests.processors.DummyProcessor')
     self.assertIs(actual, DummyProcessor)
Beispiel #21
0
    def create(self, request, *args, **kwargs):
        """Add one product to a basket, then prepare an order.

        Protected by JWT authentication. Consuming services (e.g., the LMS)
        must authenticate themselves by passing a JWT in the Authorization
        HTTP header, prepended with the string 'JWT'. The JWT payload should
        contain user details. At a minimum, these details must include a
        username; providing an email is recommended.

        Expects a SKU to be provided in the POST data, which is then used
        to populate the user's basket with the corresponding product, freeze
        that basket, and prepare an order using that basket. If the order
        total is zero (i.e., the ordered product was free), an attempt to
        fulfill the order is made.

        Arguments:
            request (HttpRequest)

        Returns:
            HTTP_200_OK if the order was created successfully, with order data in JSON format
            HTTP_400_BAD_REQUEST if the client has provided invalid data or has attempted
                to add an unavailable product to their basket, with reason for the failure
                in JSON format
            HTTP_401_UNAUTHORIZED if an unauthenticated request is denied permission to access
                the endpoint
            HTTP_429_TOO_MANY_REQUESTS if the client has made requests at a rate exceeding that
                allowed by the OrdersThrottle

        Example:
            Create an order for the user with username 'Saul' as follows. (Successful fulfillment
            requires that a user with username 'Saul' exists on the LMS, and that EDX_API_KEY be
            configured on both Oscar and the LMS.)

            >>> url = 'http://*****:*****@bettercallsaul.com'}, 'insecure-secret-key')
            >>> headers = {
                'content-type': 'application/json',
                'Authorization': 'JWT ' + token
            }
            >>> response = requests.post(url, data=json.dumps(data), headers=headers)
            >>> response.status_code
            200
            >>> response.content
            '{
                "currency": "USD",
                "date_placed": "2015-02-27T18:42:34.017218Z",
                "lines": [
                    {
                        "description": "Seat in DemoX Course with Honor Certificate",
                        "status": "Complete",
                        "title": "Seat in DemoX Course with Honor Certificate",
                        "unit_price_excl_tax": 0.0
                    }
                ],
                "number": "OSCR-100021",
                "status": "Complete",
                "total_excl_tax": 0.0
            }'
        """
        sku = request.data.get('sku')
        if sku:
            try:
                product = data.get_product(sku)
            except exceptions.ProductNotFoundError as error:
                return self._report_bad_request(error.message, exceptions.PRODUCT_NOT_FOUND_USER_MESSAGE)
        else:
            return self._report_bad_request(
                exceptions.SKU_NOT_FOUND_DEVELOPER_MESSAGE,
                exceptions.SKU_NOT_FOUND_USER_MESSAGE
            )

        basket = data.get_basket(request.user)
        availability = basket.strategy.fetch_for_product(product).availability

        # If an exception is raised before order creation but after basket creation,
        # an empty basket for the user will be left in the system. However, if this
        # user attempts to order again, the `get_basket` utility will merge all old
        # baskets with a new one, returning a fresh basket.
        if not availability.is_available_to_buy:
            return self._report_bad_request(
                exceptions.PRODUCT_UNAVAILABLE_DEVELOPER_MESSAGE.format(
                    sku=sku,
                    availability=availability.message
                ),
                exceptions.PRODUCT_UNAVAILABLE_USER_MESSAGE
            )

        payment_processor = get_processor_class(settings.PAYMENT_PROCESSORS[0])

        order = self._prepare_order(basket, product, sku, payment_processor)

        if order.total_excl_tax == self.FREE:
            logger.info(
                u"Attempting to immediately fulfill order [%s] totaling [%.2f %s]",
                order.number,
                order.total_excl_tax,
                order.currency,
            )

            order = self.fulfill_order(order)

        order_data = self._assemble_order_data(order, payment_processor)

        return Response(order_data, status=status.HTTP_200_OK)
Beispiel #22
0
 def test_get_processor_class(self):
     """ Verify that the method retrieves the correct class. """
     actual = helpers.get_processor_class('ecommerce.extensions.payment.tests.processors.DummyProcessor')
     self.assertIs(actual, DummyProcessor)
Beispiel #23
0
 def _all_payment_processors(self):
     """ Returns all processor classes declared in settings. """
     all_processors = [
         get_processor_class(path) for path in settings.PAYMENT_PROCESSORS
     ]
     return all_processors
Beispiel #24
0
 def get_queryset(self):
     """Fetch the list of payment processor classes based on Django settings."""
     return [get_processor_class(path) for path in settings.PAYMENT_PROCESSORS]