Пример #1
0
    def setUp(self):
        super(CheckoutPageTest, self).setUp()
        self.switch = toggle_switch('ENABLE_CREDIT_APP', True)

        user = self.create_user(is_superuser=False)
        self.create_access_token(user)
        self.client.login(username=user.username, password=self.password)
        self.course_name = 'credit course'
        self.provider = 'ASU'
        self.price = 100
        self.thumbnail_url = 'http://www.edx.org/course.jpg'
        self.credit_hours = 2
        self.eligibility_url = get_lms_url('/api/credit/v1/eligibility/')
        self.provider_url = get_lms_url('/api/credit/v1/providers/')

        # Create the course
        self.course = Course.objects.create(id='edx/Demo_Course/DemoX',
                                            name=self.course_name,
                                            thumbnail_url=self.thumbnail_url)

        self.provider_data = [{
            'enable_integration': False,
            'description': 'Arizona State University',
            'url': 'https://credit.example.com/',
            'status_url': 'https://credit.example.com/status',
            'thumbnail_url': 'http://edX/DemoX/asset/images_course_image.jpg',
            'fulfillment_instructions': 'Sample fulfilment requirement.',
            'display_name': 'Arizona State University',
            'id': 'ASU'
        }]

        self.eligibilities = [{
            'deadline': '2016-10-28T09:56:44Z',
            'course_key': 'edx/cs01/2015'
        }]
Пример #2
0
    def get_context_data(self, **kwargs):
        context = super(CouponOfferView, self).get_context_data(**kwargs)

        code = self.request.GET.get('code', None)
        if code is not None:
            voucher, product = get_voucher(code=code)
            valid_voucher, msg = voucher_is_valid(voucher, product, self.request)
            if valid_voucher:
                api = EdxRestApiClient(
                    get_lms_url('api/courses/v1/'),
                )
                try:
                    course = api.courses(product.course_id).get()
                except SlumberHttpBaseException as e:
                    logger.exception('Could not get course information. [%s]', e)
                    return {
                        'error': _('Could not get course information. [{error}]'.format(error=e))
                    }

                course['image_url'] = get_lms_url(course['media']['course_image']['uri'])
                stock_records = voucher.offers.first().benefit.range.catalog.stock_records.first()
                context.update({
                    'course': course,
                    'code': code,
                    'price': stock_records.price_excl_tax,
                    'verified': (product.attr.certificate_type is 'verified')
                })
                return context
            return {
                'error': msg
            }
        return {
            'error': _('This coupon code is invalid.')
        }
Пример #3
0
 def get_context_data(self, **kwargs):
     context = super(CouponOfferView, self).get_context_data(**kwargs)
     footer = get_lms_footer()
     code = self.request.GET.get('code', None)
     if code is not None:
         voucher, product = get_voucher(code=code)
         valid_voucher, msg = voucher_is_valid(voucher, product,
                                               self.request)
         if valid_voucher:
             api = EdxRestApiClient(get_lms_url('api/courses/v1/'), )
             try:
                 course = api.courses(product.course_id).get()
             except SlumberHttpBaseException as e:
                 logger.exception('Could not get course information. [%s]',
                                  e)
                 return {
                     'error':
                     _('Could not get course information. [{error}]'.format(
                         error=e)),
                     'footer':
                     footer
                 }
             course['image_url'] = get_lms_url(
                 course['media']['course_image']['uri'])
             stock_records = voucher.offers.first(
             ).benefit.range.catalog.stock_records.first()
             benefit_type = voucher.offers.first().benefit.type
             benefit_value = voucher.offers.first().benefit.value
             price = stock_records.price_excl_tax
             if benefit_type == 'Percentage':
                 new_price = price - (price * (benefit_value / 100))
             else:
                 new_price = price - benefit_value
                 if new_price < 0:
                     new_price = 0.00
             context.update({
                 'benefit_type':
                 benefit_type,
                 'benefit_value':
                 benefit_value,
                 'course':
                 course,
                 'code':
                 code,
                 'price':
                 price,
                 'new_price':
                 "%.2f" % new_price,
                 'verified': (product.attr.certificate_type == 'verified'),
                 'footer':
                 footer
             })
             return context
         return {'error': msg, 'footer': footer}
     return {'error': _('This coupon code is invalid.'), 'footer': footer}
Пример #4
0
 def get_context_data(self, **kwargs):
     context = super(CouponOfferView, self).get_context_data(**kwargs)
     footer = get_lms_footer()
     code = self.request.GET.get('code', None)
     if code is not None:
         voucher, product = get_voucher_from_code(code=code)
         valid_voucher, msg = voucher_is_valid(voucher, product, self.request)
         if valid_voucher:
             api = EdxRestApiClient(
                 get_lms_url('api/courses/v1/'),
             )
             try:
                 course = api.courses(product.course_id).get()
             except SlumberHttpBaseException as e:
                 logger.exception('Could not get course information. [%s]', e)
                 return {
                     'error': _('Could not get course information. [{error}]'.format(error=e)),
                     'footer': footer
                 }
             course['image_url'] = get_lms_url(course['media']['course_image']['uri'])
             benefit = voucher.offers.first().benefit
             stock_record = benefit.range.catalog.stock_records.first()
             price = stock_record.price_excl_tax
             context.update(get_voucher_discount_info(benefit, price))
             if benefit.type == 'Percentage':
                 new_price = price - (price * (benefit.value / 100))
             else:
                 new_price = price - benefit.value
                 if new_price < 0:
                     new_price = Decimal(0)
             context.update({
                 'benefit': benefit,
                 'course': course,
                 'code': code,
                 'is_discount_value_percentage': benefit.type == 'Percentage',
                 'is_enrollment_code': benefit.type == Benefit.PERCENTAGE and benefit.value == 100.00,
                 'discount_value': "%.2f" % (price - new_price),
                 'price': price,
                 'new_price': "%.2f" % new_price,
                 'verified': (product.attr.certificate_type == 'verified'),
                 'verification_deadline': product.course.verification_deadline,
                 'footer': footer
             })
             return context
         return {
             'error': msg,
             'footer': footer
         }
     return {
         'error': _('This coupon code is invalid.'),
         'footer': footer
     }
Пример #5
0
    def _publish_creditcourse(self, course_id, access_token):
        """Creates or updates a CreditCourse object on the LMS."""
        url = get_lms_url('api/credit/v1/courses/')

        data = {
            'course_key': course_id,
            'enabled': True
        }

        headers = {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer ' + access_token
        }

        kwargs = {
            'url': url,
            'data': json.dumps(data),
            'headers': headers,
            'timeout': self.timeout
        }
        response = requests.post(**kwargs)
        if response.status_code == 400:
            # The CreditCourse already exists. Try updating it.
            kwargs['url'] += course_id.strip('/') + '/'
            response = requests.put(**kwargs)

        return response
Пример #6
0
def get_provider_data(provider_id):
    """Get the provider information for provider id provider.

    Args:
        provider_id(str): Identifier for the provider

    Returns: dict
    """
    provider_info_url = get_lms_url('api/credit/v1/providers/{}'.format(provider_id))
    timeout = settings.PROVIDER_DATA_PROCESSING_TIMEOUT
    headers = {
        'Content-Type': 'application/json',
        'X-Edx-Api-Key': settings.EDX_API_KEY
    }
    try:
        response = requests.get(provider_info_url, headers=headers, timeout=timeout)
        if response.status_code == 200:
            return response.json()
        else:
            logger.error(
                'Failed retrieve provider information for %s provider. Provider API returned status code %d. Error: %s',
                provider_id, response.status_code, response.text)
            return None
    except requests.exceptions.ConnectionError:
        logger.exception('Connection error occurred during getting data for %s provider', provider_id)
        return None
    except requests.Timeout:
        logger.exception('Failed to retrieve data for %s provider, connection timeout', provider_id)
        return None
Пример #7
0
    def test_proper_code(self):
        """ Verify that proper information is returned when a valid code is provided. """
        course = CourseFactory()
        seat = course.create_or_update_seat('verified', True, 50, self.partner)
        sr = StockRecord.objects.get(product=seat)
        catalog = Catalog.objects.create(name='Test catalog',
                                         partner=self.partner)
        catalog.stock_records.add(sr)
        range_ = RangeFactory(catalog=catalog)
        self.prepare_voucher(range_=range_)

        course_info = {
            "media": {
                "course_image": {
                    "uri":
                    "/asset-v1:edX+DemoX+Demo_Course+type@asset+block@images_course_image.jpg"
                }
            },
            "name": "edX Demonstration Course",
        }
        course_info_json = json.dumps(course_info)
        course_url = get_lms_url('api/courses/v1/courses/{}/'.format(
            course.id))
        httpretty.register_uri(httpretty.GET,
                               course_url,
                               body=course_info_json,
                               content_type='application/json')

        url = self.offer_url + '?code={}'.format('COUPONTEST')
        response = self.client.get(url)
        self.assertEqual(response.context['course']['name'],
                         _('edX Demonstration Course'))
        self.assertEqual(response.context['code'], _('COUPONTEST'))
Пример #8
0
def send_course_purchase_email(sender, order=None, **kwargs):  # pylint: disable=unused-argument
    """Send course purchase notification email when a course is purchased."""
    if waffle.switch_is_active('ENABLE_NOTIFICATIONS'):
        # We do not currently support email sending for orders with more than one item.
        if len(order.lines.all()) == ORDER_LINE_COUNT:
            product = order.lines.first().product
            provider_id = getattr(product.attr, 'credit_provider', None)
            if not provider_id:
                logger.error(
                    'Failed to send credit receipt notification. Credit seat product [%s] has not provider.', product.id
                )
                return
            elif product.get_product_class().name == 'Seat':
                provider_data = get_provider_data(provider_id)
                if provider_data:
                    send_notification(
                        order.user,
                        'CREDIT_RECEIPT',
                        {
                            'course_title': product.title,
                            'receipt_page_url': get_lms_url(
                                '/commerce/checkout/receipt/?basket_id={}'.format(order.basket.id)
                            ),
                            'credit_hours': product.attr.credit_hours,
                            'credit_provider': provider_data['display_name'],
                        }
                    )

        else:
            logger.info('Currently support receipt emails for order with one item.')
Пример #9
0
def send_course_purchase_email(sender, order=None, **kwargs):  # pylint: disable=unused-argument
    """Send course purchase notification email when a course is purchased."""
    if waffle.switch_is_active('ENABLE_NOTIFICATIONS'):
        # We do not currently support email sending for orders with more than one item.
        if len(order.lines.all()) == ORDER_LINE_COUNT:
            product = order.lines.first().product
            provider_id = getattr(product.attr, 'credit_provider', None)
            if not provider_id:
                logger.error(
                    'Failed to send credit receipt notification. Credit seat product [%s] has not provider.',
                    product.id)
                return
            elif product.get_product_class().name == 'Seat':
                provider_data = get_provider_data(provider_id)
                if provider_data:
                    send_notification(
                        order.user, 'CREDIT_RECEIPT', {
                            'course_title':
                            product.title,
                            'receipt_page_url':
                            get_lms_url(
                                '/commerce/checkout/receipt/?orderNum={}'.
                                format(order.number)),
                            'credit_hours':
                            product.attr.credit_hours,
                            'credit_provider':
                            provider_data['display_name'],
                        })

        else:
            logger.info(
                'Currently support receipt emails for order with one item.')
Пример #10
0
    def credit_api_client(self):
        """ Returns an instance of the Credit API client. """

        return EdxRestApiClient(
            get_lms_url('api/credit/v1/'),
            oauth_access_token=self.request.user.access_token
        )
Пример #11
0
def get_lms_footer():
    """
    Retrieve LMS footer via branding API.

    Returns:
        str: HTML representation of the footer.
    """
    try:
        response = requests.get(
            get_lms_url('api/branding/v1/footer'),
            data={'language': 'en'}
        )
        if response.status_code == 200:
            return response.text
        else:
            logger.error(
                'Failed retrieve provider information for %s provider. Provider API returned status code %d. Error: %s',
                settings.LMS_URL_ROOT, response.status_code, response.text)
            return None
    except requests.exceptions.ConnectionError:
        logger.exception('Connection error occurred during getting data for %s provider', settings.LMS_URL_ROOT)
        return None
    except requests.Timeout:
        logger.exception('Failed to retrieve data for %s provider, connection timeout', settings.LMS_URL_ROOT)
        return None
Пример #12
0
def get_lms_footer():
    """
    Retrieve LMS footer via branding API.

    Returns:
        str: HTML representation of the footer.
    """
    try:
        response = requests.get(get_lms_url('api/branding/v1/footer'),
                                data={'language': 'en'})
        if response.status_code == 200:
            return response.text
        else:
            logger.error(
                'Failed retrieve provider information for %s provider. Provider API returned status code %d. Error: %s',
                settings.LMS_URL_ROOT, response.status_code, response.text)
            return None
    except requests.exceptions.ConnectionError:
        logger.exception(
            'Connection error occurred during getting data for %s provider',
            settings.LMS_URL_ROOT)
        return None
    except requests.Timeout:
        logger.exception(
            'Failed to retrieve data for %s provider, connection timeout',
            settings.LMS_URL_ROOT)
        return None
Пример #13
0
    def test_proper_code(self):
        """ Verify that proper information is returned when a valid code is provided. """
        course = CourseFactory()
        seat = course.create_or_update_seat('verified', True, 50, self.partner)
        sr = StockRecord.objects.get(product=seat)
        catalog = Catalog.objects.create(name='Test catalog', partner=self.partner)
        catalog.stock_records.add(sr)
        range_ = RangeFactory(catalog=catalog)
        self.prepare_voucher(range_=range_)

        course_info = {
            "media": {
                "course_image": {
                    "uri": "/asset-v1:edX+DemoX+Demo_Course+type@asset+block@images_course_image.jpg"
                }
            },
            "name": "edX Demonstration Course",
        }
        course_info_json = json.dumps(course_info)
        course_url = get_lms_url('api/courses/v1/courses/{}/'.format(course.id))
        httpretty.register_uri(httpretty.GET, course_url, body=course_info_json, content_type='application/json')

        url = self.offer_url + '?code={}'.format('COUPONTEST')
        response = self.client.get(url)
        self.assertEqual(response.context['course']['name'], _('edX Demonstration Course'))
        self.assertEqual(response.context['code'], _('COUPONTEST'))
Пример #14
0
    def test_post_checkout_callback(self):
        """
        When the post_checkout signal is emitted, the receiver should attempt
        to fulfill the newly-placed order and send receipt email.
        """
        httpretty.register_uri(
            httpretty.GET, get_lms_url('api/credit/v1/providers/ASU'),
            body='{"display_name": "Hogwarts"}',
            content_type="application/json"
        )
        toggle_switch('ENABLE_NOTIFICATIONS', True)
        user = UserFactory()
        course = Course.objects.create(id='edX/DemoX/Demo_Course', name='Demo Course')
        partner = self.create_partner('edx')
        seat = course.create_or_update_seat('credit', False, 50, partner, 'ASU', None, 2)

        basket = BasketFactory()
        basket.add_product(seat, 1)
        order = factories.create_order(number=1, basket=basket, user=user)
        send_course_purchase_email(None, order=order)
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].subject, 'Order Receipt')
        self.assertEqual(
            mail.outbox[0].body,
            '\nPayment confirmation for: {course_title}'
            '\n\nDear {full_name},'
            '\n\nThank you for purchasing {credit_hours} credit hours from {credit_provider} for {course_title}. '
            'A charge will appear on your credit or debit card statement with a company name of "{platform_name}".'
            '\n\nTo receive your course credit, you must also request credit at the {credit_provider} website. '
            'For a link to request credit from {credit_provider}, or to see the status of your credit request, '
            'go to your {platform_name} dashboard.'
            '\n\nTo explore other credit-eligible courses, visit the {platform_name} website. '
            'We add new courses frequently!'
            '\n\nTo view your payment information, visit the following website.'
            '\n{receipt_url}'
            '\n\nThank you. We hope you enjoyed your course!'
            '\nThe {platform_name} team'
            '\n\nYou received this message because you purchased credit hours for {course_title}, '
            'an {platform_name} course.\n'.format(
                course_title=order.lines.first().product.title,
                full_name=user.get_full_name(),
                credit_hours=2,
                credit_provider='Hogwarts',
                platform_name=settings.PLATFORM_NAME,
                receipt_url=get_lms_url('/commerce/checkout/receipt/?basket_id={}'.format(order.basket.id))
            )
        )
Пример #15
0
    def test_post_checkout_callback(self):
        """
        When the post_checkout signal is emitted, the receiver should attempt
        to fulfill the newly-placed order and send receipt email.
        """
        httpretty.register_uri(httpretty.GET,
                               get_lms_url('api/credit/v1/providers/ASU'),
                               body='{"display_name": "Hogwarts"}',
                               content_type="application/json")
        toggle_switch('ENABLE_NOTIFICATIONS', True)
        user = UserFactory()
        course = Course.objects.create(id='edX/DemoX/Demo_Course',
                                       name='Demo Course')
        seat = course.create_or_update_seat('credit', False, 50, self.partner,
                                            'ASU', None, 2)

        basket = BasketFactory()
        basket.add_product(seat, 1)
        order = factories.create_order(number=1, basket=basket, user=user)
        send_course_purchase_email(None, order=order)
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].subject, 'Order Receipt')
        self.assertEqual(
            mail.outbox[0].body, '\nPayment confirmation for: {course_title}'
            '\n\nDear {full_name},'
            '\n\nThank you for purchasing {credit_hours} credit hours from {credit_provider} for {course_title}. '
            'A charge will appear on your credit or debit card statement with a company name of "{platform_name}".'
            '\n\nTo receive your course credit, you must also request credit at the {credit_provider} website. '
            'For a link to request credit from {credit_provider}, or to see the status of your credit request, '
            'go to your {platform_name} dashboard.'
            '\n\nTo explore other credit-eligible courses, visit the {platform_name} website. '
            'We add new courses frequently!'
            '\n\nTo view your payment information, visit the following website.'
            '\n{receipt_url}'
            '\n\nThank you. We hope you enjoyed your course!'
            '\nThe {platform_name} team'
            '\n\nYou received this message because you purchased credit hours for {course_title}, '
            'an {platform_name} course.\n'.format(
                course_title=order.lines.first().product.title,
                full_name=user.get_full_name(),
                credit_hours=2,
                credit_provider='Hogwarts',
                platform_name=settings.PLATFORM_NAME,
                receipt_url=get_lms_url(
                    '/commerce/checkout/receipt/?orderNum={}'.format(
                        order.number))))
Пример #16
0
 def mock_footer_api_response(self):
     """ Helper function to register an API endpoint for the footer information. """
     footer_url = get_lms_url('api/branding/v1/footer')
     footer_content = {
         'footer': 'edX Footer'
     }
     content_json = json.dumps(footer_content)
     httpretty.register_uri(httpretty.GET, footer_url, body=content_json, content_type='application/json')
Пример #17
0
    def mock_credit_api_timeout(self):
        """ Mock a timeout when calling the Credit API providers endpoint. """

        def callback(request, uri, headers):  # pylint: disable=unused-argument
            raise Timeout

        url = get_lms_url("/api/credit/v1/providers/")
        httpretty.register_uri(httpretty.GET, url, body=callback, content_type="application/json")
Пример #18
0
    def get(self, request):
        """
        Looks up the passed code and adds the matching product to a basket,
        then applies the voucher and if the basket total is FREE places the order and
        enrolls the user in the course.
        """
        template_name = 'coupons/offer.html'
        code = request.GET.get('code', None)

        if not code:
            return render(request, template_name,
                          {'error': _('Code not provided')})

        voucher, product = get_voucher(code=code)
        valid_voucher, msg = voucher_is_valid(voucher, product, request)
        if not valid_voucher:
            return render(request, template_name, {'error': msg})

        basket = self._prepare_basket(request.site, request.user, product,
                                      voucher)
        if basket.total_excl_tax == AC.FREE:
            basket.freeze()
            order_metadata = data_api.get_order_metadata(basket)

            logger.info(
                u"Preparing to place order [%s] for the contents of basket [%d]",
                order_metadata[AC.KEYS.ORDER_NUMBER],
                basket.id,
            )

            # Place an order. If order placement succeeds, the order is committed
            # to the database so that it can be fulfilled asynchronously.
            order = self.handle_order_placement(
                order_number=order_metadata[AC.KEYS.ORDER_NUMBER],
                user=basket.owner,
                basket=basket,
                shipping_address=None,
                shipping_method=order_metadata[AC.KEYS.SHIPPING_METHOD],
                shipping_charge=order_metadata[AC.KEYS.SHIPPING_CHARGE],
                billing_address=None,
                order_total=order_metadata[AC.KEYS.ORDER_TOTAL],
            )
        else:
            return render(
                request, template_name, {
                    'error':
                    _('Basket total not $0, current value = ${basket_price}'.
                      format(basket_price=basket.total_excl_tax))
                })

        if order.status is ORDER.COMPLETE:
            return HttpResponseRedirect(get_lms_url(''))
        else:
            logger.error('Order was not completed [%s]', order.id)
            return render(request, template_name,
                          {'error': _('Error when trying to redeem code')})
Пример #19
0
 def test_get_provider_data_unavailable_request(self):
     """
     Check if None return on the bad request
     """
     httpretty.register_uri(
         httpretty.GET, get_lms_url('api/credit/v1/providers/ABC'),
         status=400
     )
     provider_data = get_provider_data('ABC')
     self.assertEqual(provider_data, None)
Пример #20
0
    def test_post_checkout_callback(self):
        """
        When the post_checkout signal is emitted, the receiver should attempt
        to fulfill the newly-placed order and send receipt email.
        """
        httpretty.register_uri(
            httpretty.GET, get_lms_url('api/credit/v1/providers/ASU'),
            body='{"display_name": "Hogwarts"}',
            content_type="application/json"
        )
        toggle_switch('ENABLE_NOTIFICATIONS', True)
        user = UserFactory()
        course = Course.objects.create(id='edX/DemoX/Demo_Course', name='Demo Course')
        partner = self.create_partner('edx')
        seat = course.create_or_update_seat('credit', False, 50, partner, 'ASU', None, 2)

        basket = BasketFactory()
        basket.add_product(seat, 1)
        order = factories.create_order(number=1, basket=basket, user=user)
        send_course_purchase_email(None, order=order)
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].subject, 'Order Receipt')
        self.assertEqual(
            mail.outbox[0].body,
            '\nReceipt Confirmation for: {course_name}'
            '\n\nHi {full_name},\n\n'
            'Thank you for purchasing {credit_hour} credit hours from {provider_name} for {course_name}.'
            ' The charge below will appear on your next credit or debit card statement with a '
            'company name of {platform_name}.\n\nYou can see the status the status of your credit request or '
            'complete the credit request process on your {platform_name} dashboard\nTo browse other '
            'credit-eligible courses visit the edX website. More courses are added all the time.\n\n'
            'Thank you and congratulation on your achievement. We hope you enjoy the course!\n\n'
            'To view receipt please visit the link below'
            '\n\n{receipt_url}\n\n'
            '{platform_name} team\n\nThe edX team\n'.format(
                course_name=order.lines.first().product.title,
                full_name=user.get_full_name(),
                credit_hour=2,
                provider_name='Hogwarts',
                platform_name=settings.PLATFORM_NAME,
                receipt_url=get_lms_url('/commerce/checkout/receipt/?basket_id={}'.format(order.basket.id))
            )
        )
Пример #21
0
    def _publish_creditcourse(self, course_id, access_token):
        """Creates or updates a CreditCourse object on the LMS."""

        api = EdxRestApiClient(get_lms_url('api/credit/v1/'),
                               oauth_access_token=access_token,
                               timeout=self.timeout)

        data = {'course_key': course_id, 'enabled': True}

        api.courses(course_id).put(data)
Пример #22
0
 def test_get_provider_data_unavailable_request(self):
     """
     Check if None return on the bad request
     """
     httpretty.register_uri(
         httpretty.GET, get_lms_url('api/credit/v1/providers/ABC'),
         status=400
     )
     provider_data = get_provider_data('ABC')
     self.assertEqual(provider_data, None)
Пример #23
0
    def mock_credit_api_timeout(self):
        """ Mock a timeout when calling the Credit API providers endpoint. """
        def callback(request, uri, headers):  # pylint: disable=unused-argument
            raise Timeout

        url = get_lms_url('/api/credit/v1/providers/')
        httpretty.register_uri(httpretty.GET,
                               url,
                               body=callback,
                               content_type='application/json')
Пример #24
0
    def mock_creditcourse_endpoint(self, course_id, status, body=None):
        self.assertTrue(httpretty.is_enabled(), 'httpretty must be enabled to mock Credit API calls.')

        url = get_lms_url('/api/credit/v1/courses/{}/'.format(course_id))
        httpretty.register_uri(
            httpretty.PUT,
            url,
            status=status,
            body=json.dumps(body),
            content_type=JSON
        )
Пример #25
0
    def get(self, request):
        """
        Looks up the passed code and adds the matching product to a basket,
        then applies the voucher and if the basket total is FREE places the order and
        enrolls the user in the course.
        """
        template_name = 'coupons/offer.html'
        code = request.GET.get('code', None)

        if not code:
            return render(request, template_name, {'error': _('Code not provided')})

        voucher, product = get_voucher(code=code)
        valid_voucher, msg = voucher_is_valid(voucher, product, request)
        if not valid_voucher:
            return render(request, template_name, {'error': msg})

        basket = self._prepare_basket(request.site, request.user, product, voucher)
        if basket.total_excl_tax == AC.FREE:
            basket.freeze()
            order_metadata = data_api.get_order_metadata(basket)

            logger.info(
                u"Preparing to place order [%s] for the contents of basket [%d]",
                order_metadata[AC.KEYS.ORDER_NUMBER],
                basket.id,
            )

            # Place an order. If order placement succeeds, the order is committed
            # to the database so that it can be fulfilled asynchronously.
            order = self.handle_order_placement(
                order_number=order_metadata[AC.KEYS.ORDER_NUMBER],
                user=basket.owner,
                basket=basket,
                shipping_address=None,
                shipping_method=order_metadata[AC.KEYS.SHIPPING_METHOD],
                shipping_charge=order_metadata[AC.KEYS.SHIPPING_CHARGE],
                billing_address=None,
                order_total=order_metadata[AC.KEYS.ORDER_TOTAL],
            )
        else:
            return render(
                request,
                template_name,
                {'error': _('Basket total not $0, current value = ${basket_price}'.format(
                    basket_price=basket.total_excl_tax
                ))}
            )

        if order.status is ORDER.COMPLETE:
            return HttpResponseRedirect(get_lms_url(''))
        else:
            logger.error('Order was not completed [%s]', order.id)
            return render(request, template_name, {'error': _('Error when trying to redeem code')})
Пример #26
0
 def test_get_provider_data(self):
     """
     Check if correct data returns on the full filled request.
     """
     httpretty.register_uri(
         httpretty.GET, get_lms_url('api/credit/v1/providers/ASU'),
         body='{"display_name": "Arizona State University"}',
         content_type="application/json"
     )
     provider_data = get_provider_data('ASU')
     self.assertDictEqual(provider_data, {"display_name": "Arizona State University"})
Пример #27
0
 def test_get_provider_data(self):
     """
     Check if correct data returns on the full filled request.
     """
     httpretty.register_uri(
         httpretty.GET, get_lms_url('api/credit/v1/providers/ASU'),
         body='{"display_name": "Arizona State University"}',
         content_type="application/json"
     )
     provider_data = get_provider_data('ASU')
     self.assertDictEqual(provider_data, {"display_name": "Arizona State University"})
Пример #28
0
    def setUp(self):
        super(CheckoutPageTest, self).setUp()
        self.switch = toggle_switch('ENABLE_CREDIT_APP', True)

        user = self.create_user(is_superuser=False)
        self.create_access_token(user)
        self.client.login(username=user.username, password=self.password)
        self.course_name = 'credit course'
        self.provider = 'ASU'
        self.price = 100
        self.thumbnail_url = 'http://www.edx.org/course.jpg'
        self.credit_hours = 2
        self.eligibility_url = get_lms_url('/api/credit/v1/eligibility/')
        self.provider_url = get_lms_url('/api/credit/v1/providers/')

        # Create the course
        self.course = Course.objects.create(
            id='edx/Demo_Course/DemoX',
            name=self.course_name,
            thumbnail_url=self.thumbnail_url
        )

        self.provider_data = [
            {
                'enable_integration': False,
                'description': 'Arizona State University',
                'url': 'https://credit.example.com/',
                'status_url': 'https://credit.example.com/status',
                'thumbnail_url': 'http://edX/DemoX/asset/images_course_image.jpg',
                'fulfillment_instructions': 'Sample fulfilment requirement.',
                'display_name': 'Arizona State University',
                'id': 'ASU'
            }
        ]

        self.eligibilities = [
            {
                'deadline': '2016-10-28T09:56:44Z',
                'course_key': 'edx/cs01/2015'
            }
        ]
Пример #29
0
class DataFunctionsTests(TestCase):
    """ Tests for api data functions. """
    footer_url = get_lms_url('api/branding/v1/footer')

    def setUp(self):
        super(DataFunctionsTests, self).setUp()

    @httpretty.activate
    def test_get_lms_footer_success(self):
        """ Verify footer information is retrieved. """
        content = {'footer': 'edX Footer'}
        content_json = json.dumps(content)
        httpretty.register_uri(httpretty.GET,
                               self.footer_url,
                               body=content_json,
                               content_type='application/json')
        response = json.loads(get_lms_footer())
        self.assertEqual(response['footer'], 'edX Footer')

    @httpretty.activate
    def test_get_lms_footer_failure(self):
        """ Verify None is returned on a non-200 status code. """
        httpretty.register_uri(httpretty.GET,
                               self.footer_url,
                               status=404,
                               content_type='application/json')
        response = get_lms_footer()
        self.assertIsNone(response)

    def test_get_lms_footer_conn_error(self):
        """ Verify proper logger message is displayed in case of a connection error. """
        with mock.patch('requests.get',
                        side_effect=requests.exceptions.ConnectionError()):
            with LogCapture(LOGGER_NAME) as l:
                response = get_lms_footer()
                l.check((
                    LOGGER_NAME, 'ERROR',
                    u'Connection error occurred during getting data for {lms_url} provider'
                    .format(lms_url=settings.LMS_URL_ROOT)))
                self.assertIsNone(response)

    def test_get_lms_footer_timeout(self):
        """ Verify proper logger message is displayed in case of a time out. """
        with mock.patch('requests.get', side_effect=requests.Timeout()):
            with LogCapture(LOGGER_NAME) as l:
                response = get_lms_footer()
                l.check((
                    LOGGER_NAME, 'ERROR',
                    u'Failed to retrieve data for {lms_url} provider, connection timeout'
                    .format(lms_url=settings.LMS_URL_ROOT)))
                self.assertIsNone(response)
Пример #30
0
    def get_context_data(self, **kwargs):
        context = super(BasketSummaryView, self).get_context_data(**kwargs)
        lines = context.get('line_list', [])
        api = EdxRestApiClient(get_lms_url('api/courses/v1/'))
        for line in lines:
            course_id = line.product.course_id

            # Get each course type so we can display to the user at checkout.
            try:
                line.certificate_type = get_certificate_type_display_value(line.product.attr.certificate_type)
            except ValueError:
                line.certificate_type = None

            cache_key = 'courses_api_detail_{}'.format(course_id)
            cache_hash = hashlib.md5(cache_key).hexdigest()
            try:
                course = cache.get(cache_hash)
                if not course:
                    course = api.courses(course_id).get()
                    course['image_url'] = get_lms_url(course['media']['course_image']['uri'])
                    cache.set(cache_hash, course, settings.COURSES_API_CACHE_TIMEOUT)
                line.course = course
            except (ConnectionError, SlumberBaseException, Timeout):
                logger.exception('Failed to retrieve data from Course API for course [%s].', course_id)

            if line.has_discount:
                line.discount_percentage = line.discount_value / line.unit_price_incl_tax * Decimal(100)
            else:
                line.discount_percentage = 0

        context.update({
            'payment_processors': self.get_payment_processors(),
            'homepage_url': get_lms_url(''),
            'footer': get_lms_footer(),
            'lines': lines,
            'faq_url': get_lms_url('') + '/verified-certificate',
        })
        return context
Пример #31
0
 def mock_course_api_response(self, course=None):
     """ Helper function to register an API endpoint for the course information. """
     course_info = {
         'media': {
             'course_image': {
                 'uri': '/asset-v1:test+test+test+type@asset+block@images_course_image.jpg'
             }
         },
         'name': course.name if course else 'Test course'
     }
     course_info_json = json.dumps(course_info)
     course_id = course.id if course else 'course-v1:test+test+test'
     course_url = get_lms_url('api/courses/v1/courses/{}/'.format(course_id))
     httpretty.register_uri(httpretty.GET, course_url, body=course_info_json, content_type='application/json')
Пример #32
0
    def get_context_data(self, **kwargs):
        context = super(CouponOfferView, self).get_context_data(**kwargs)

        code = self.request.GET.get('code', None)
        if code is not None:
            voucher, product = get_voucher(code=code)
            valid_voucher, msg = voucher_is_valid(voucher, product,
                                                  self.request)
            if valid_voucher:
                api = EdxRestApiClient(get_lms_url('api/courses/v1/'), )
                try:
                    course = api.courses(product.course_id).get()
                except SlumberHttpBaseException as e:
                    logger.exception('Could not get course information. [%s]',
                                     e)
                    return {
                        'error':
                        _('Could not get course information. [{error}]'.format(
                            error=e))
                    }

                course['image_url'] = get_lms_url(
                    course['media']['course_image']['uri'])
                stock_records = voucher.offers.first(
                ).benefit.range.catalog.stock_records.first()
                context.update({
                    'course':
                    course,
                    'code':
                    code,
                    'price':
                    stock_records.price_excl_tax,
                    'verified': (product.attr.certificate_type is 'verified')
                })
                return context
            return {'error': msg}
        return {'error': _('This coupon code is invalid.')}
Пример #33
0
    def _publish_creditcourse(self, course_id, access_token):
        """Creates or updates a CreditCourse object on the LMS."""

        api = EdxRestApiClient(
            get_lms_url('api/credit/v1/'),
            oauth_access_token=access_token,
            timeout=self.timeout
        )

        data = {
            'course_key': course_id,
            'enabled': True
        }

        api.courses(course_id).put(data)
Пример #34
0
    def test_course_information_error(self):
        """ Verify a response is returned when course information is not accessable. """
        course = CourseFactory()
        seat = course.create_or_update_seat('verified', True, 50, self.partner)
        _range = RangeFactory(products=[seat, ])
        prepare_voucher(code=COUPON_CODE, _range=_range)

        course_url = get_lms_url('api/courses/v1/courses/{}/'.format(course.id))
        httpretty.register_uri(httpretty.GET, course_url, status=404, content_type=CONTENT_TYPE)

        response = self.client.get(self.path_with_code)
        response_text = (
            'Could not get course information. '
            '[Client Error 404: http://127.0.0.1:8000/api/courses/v1/courses/{}/]'
        ).format(course.id)
        self.assertEqual(response.context['error'], _(response_text))
Пример #35
0
    def test_course_information_error(self):
        """ Verify a response is returned when course information is not accessable. """
        course = CourseFactory()
        seat = course.create_or_update_seat('verified', True, 50, self.partner)
        range_ = RangeFactory(products=[seat, ])
        self.prepare_voucher(range_=range_)

        course_url = get_lms_url('api/courses/v1/courses/{}/'.format(course.id))
        httpretty.register_uri(httpretty.GET, course_url, status=404, content_type='application/json')

        response = self.client.get(self.path_with_code)
        response_text = (
            'Could not get course information. '
            '[Client Error 404: http://127.0.0.1:8000/api/courses/v1/courses/{}/]'
        ).format(course.id)
        self.assertEqual(response.context['error'], _(response_text))
Пример #36
0
    def mock_credit_api_providers(self):
        """
        Mock GET requests to the Credit API's provider endpoint.

        /api/credit/v1/providers
        """
        self.assertTrue(httpretty.is_enabled())

        providers = [
            {"id": "shk", "display_name": "School of Hard Knocks"},
            {"id": "acme", "display_name": "Acme University"},
        ]
        providers.sort(key=lambda provider: provider["display_name"])
        provider_json = json.dumps(providers)
        url = get_lms_url("/api/credit/v1/providers/")
        httpretty.register_uri(httpretty.GET, url, body=provider_json, content_type="application/json")

        return providers, provider_json
Пример #37
0
 def prepare_course_information(self):
     """ Helper function to prepare an API endpoint that provides course information. """
     course = CourseFactory()
     seat = course.create_or_update_seat('verified', True, 50, self.partner)
     sr = StockRecord.objects.get(product=seat)
     catalog = Catalog.objects.create(name='Test catalog', partner=self.partner)
     catalog.stock_records.add(sr)
     range_ = RangeFactory(catalog=catalog)
     course_info = {
         "media": {
             "course_image": {
                 "uri": "/asset-v1:edX+DemoX+Demo_Course+type@asset+block@images_course_image.jpg"
             }
         },
         "name": "edX Demonstration Course",
     }
     course_info_json = json.dumps(course_info)
     course_url = get_lms_url('api/courses/v1/courses/{}/'.format(course.id))
     httpretty.register_uri(httpretty.GET, course_url, body=course_info_json, content_type='application/json')
     return range_
Пример #38
0
    def _mock_credit_api(self, creation_status, update_status, body=None):
        self.assertTrue(httpretty.is_enabled, 'httpretty must be enabled to mock Credit API calls.')

        url = get_lms_url('api/credit/v1/courses/')
        httpretty.register_uri(
            httpretty.POST,
            url,
            status=creation_status,
            body=json.dumps(body),
            content_type=JSON
        )

        if update_status is not None:
            url += self.course.id.strip('/') + '/'
            httpretty.register_uri(
                httpretty.PUT,
                url,
                status=update_status,
                body=json.dumps(body),
                content_type=JSON
            )
Пример #39
0
    def get_credit_providers(self):
        """
        Retrieve all credit providers from LMS.

        Results will be sorted alphabetically by display name.
        """
        key = 'credit_providers'
        credit_providers = cache.get(key, [])

        if not credit_providers:
            try:
                credit_api = EdxRestApiClient(
                    get_lms_url('/api/credit/v1/'),
                    oauth_access_token=self.request.user.access_token
                )
                credit_providers = credit_api.providers.get()
                credit_providers.sort(key=lambda provider: provider['display_name'])

                # Update the cache
                cache.set(key, credit_providers, settings.CREDIT_PROVIDER_CACHE_TIMEOUT)
            except (SlumberBaseException, Timeout):
                logger.exception('Failed to retrieve credit providers!')

        return credit_providers
Пример #40
0
    def mock_credit_api_providers(self):
        """
        Mock GET requests to the Credit API's provider endpoint.

        /api/credit/v1/providers
        """
        self.assertTrue(httpretty.is_enabled())

        providers = [{
            'id': 'shk',
            'display_name': 'School of Hard Knocks'
        }, {
            'id': 'acme',
            'display_name': 'Acme University'
        }]
        providers.sort(key=lambda provider: provider['display_name'])
        provider_json = json.dumps(providers)
        url = get_lms_url('/api/credit/v1/providers/')
        httpretty.register_uri(httpretty.GET,
                               url,
                               body=provider_json,
                               content_type='application/json')

        return providers, provider_json
Пример #41
0
def get_provider_data(provider_id):
    """Get the provider information for provider id provider.

    Args:
        provider_id(str): Identifier for the provider

    Returns: dict
    """
    provider_info_url = get_lms_url(
        'api/credit/v1/providers/{}'.format(provider_id))
    timeout = settings.PROVIDER_DATA_PROCESSING_TIMEOUT
    headers = {
        'Content-Type': 'application/json',
        'X-Edx-Api-Key': settings.EDX_API_KEY
    }
    try:
        response = requests.get(provider_info_url,
                                headers=headers,
                                timeout=timeout)
        if response.status_code == 200:
            return response.json()
        else:
            logger.error(
                'Failed retrieve provider information for %s provider. Provider API returned status code %d. Error: %s',
                provider_id, response.status_code, response.text)
            return None
    except requests.exceptions.ConnectionError:
        logger.exception(
            'Connection error occurred during getting data for %s provider',
            provider_id)
        return None
    except requests.Timeout:
        logger.exception(
            'Failed to retrieve data for %s provider, connection timeout',
            provider_id)
        return None
Пример #42
0
        'USER': '',
        'PASSWORD': '',
        'HOST': '',
        'PORT': '',
        'ATOMIC_REQUESTS': True,
    },
}
# END IN-MEMORY TEST DATABASE

# URL CONFIGURATION
ECOMMERCE_URL_ROOT = 'http://localhost:8002'

LMS_URL_ROOT = 'http://127.0.0.1:8000'

# The location of the LMS heartbeat page
LMS_HEARTBEAT_URL = get_lms_url('/heartbeat')

# The location of the LMS student dashboard
LMS_DASHBOARD_URL = get_lms_url('/dashboard')

COMMERCE_API_URL = get_lms_url('/api/commerce/v1/')
# END URL CONFIGURATION

# AUTHENTICATION
ENABLE_AUTO_AUTH = True

JWT_AUTH.update({
    'JWT_SECRET_KEY': 'insecure-secret-key',
    'JWT_ISSUERS': ('test-issuer', ),
})
Пример #43
0
    MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware', )

    DEBUG_TOOLBAR_PATCH_SETTINGS = False

# http://django-debug-toolbar.readthedocs.org/en/latest/installation.html
INTERNAL_IPS = ('127.0.0.1', )
# END TOOLBAR CONFIGURATION

# URL CONFIGURATION
ECOMMERCE_URL_ROOT = 'http://localhost:8002'

LMS_URL_ROOT = 'http://127.0.0.1:8000'

# The location of the LMS heartbeat page
LMS_HEARTBEAT_URL = get_lms_url('/heartbeat')

# The location of the LMS student dashboard
LMS_DASHBOARD_URL = get_lms_url('/dashboard')

OAUTH2_PROVIDER_URL = get_lms_url('/oauth2')

COMMERCE_API_URL = get_lms_url('/api/commerce/v1/')
# END URL CONFIGURATION

# AUTHENTICATION
# Set these to the correct values for your OAuth2/OpenID Connect provider (e.g., devstack)
SOCIAL_AUTH_EDX_OIDC_KEY = 'replace-me'
SOCIAL_AUTH_EDX_OIDC_SECRET = 'replace-me'
SOCIAL_AUTH_EDX_OIDC_URL_ROOT = OAUTH2_PROVIDER_URL
SOCIAL_AUTH_EDX_OIDC_ID_TOKEN_DECRYPTION_KEY = SOCIAL_AUTH_EDX_OIDC_SECRET
Пример #44
0
 def mock_course_api_error(self, error):
     def callback(request, uri, headers):  # pylint: disable=unused-argument
         raise error
     course_url = get_lms_url('api/courses/v1/courses/{}/'.format(self.course))
     httpretty.register_uri(httpretty.GET, course_url, body=callback, content_type='application/json')
Пример #45
0
    MIDDLEWARE_CLASSES += ("debug_toolbar.middleware.DebugToolbarMiddleware",)

    DEBUG_TOOLBAR_PATCH_SETTINGS = False

# http://django-debug-toolbar.readthedocs.org/en/latest/installation.html
INTERNAL_IPS = ("127.0.0.1",)
# END TOOLBAR CONFIGURATION


# URL CONFIGURATION
ECOMMERCE_URL_ROOT = "http://localhost:8002"

LMS_URL_ROOT = "http://127.0.0.1:8000"

# The location of the LMS heartbeat page
LMS_HEARTBEAT_URL = get_lms_url("/heartbeat")

# The location of the LMS student dashboard
LMS_DASHBOARD_URL = get_lms_url("/dashboard")

OAUTH2_PROVIDER_URL = get_lms_url("/oauth2")

COMMERCE_API_URL = get_lms_url("/api/commerce/v1/")
# END URL CONFIGURATION


# AUTHENTICATION
# Set these to the correct values for your OAuth2/OpenID Connect provider (e.g., devstack)
SOCIAL_AUTH_EDX_OIDC_KEY = "replace-me"
SOCIAL_AUTH_EDX_OIDC_SECRET = "replace-me"
SOCIAL_AUTH_EDX_OIDC_URL_ROOT = OAUTH2_PROVIDER_URL
Пример #46
0
        'PASSWORD': '',
        'HOST': '',
        'PORT': '',
        'ATOMIC_REQUESTS': True,
    },
}
# END IN-MEMORY TEST DATABASE


# URL CONFIGURATION
ECOMMERCE_URL_ROOT = 'http://localhost:8002'

LMS_URL_ROOT = 'http://127.0.0.1:8000'

# The location of the LMS heartbeat page
LMS_HEARTBEAT_URL = get_lms_url('/heartbeat')

# The location of the LMS student dashboard
LMS_DASHBOARD_URL = get_lms_url('/dashboard')

COMMERCE_API_URL = get_lms_url('/api/commerce/v1/')
# END URL CONFIGURATION


# AUTHENTICATION
ENABLE_AUTO_AUTH = True

JWT_AUTH.update({
    'JWT_SECRET_KEY': 'insecure-secret-key',
    'JWT_ISSUERS': ('test-issuer',),
})
Пример #47
0
    )

    DEBUG_TOOLBAR_PATCH_SETTINGS = False

# http://django-debug-toolbar.readthedocs.org/en/latest/installation.html
INTERNAL_IPS = ('127.0.0.1',)
# END TOOLBAR CONFIGURATION


# URL CONFIGURATION
ECOMMERCE_URL_ROOT = 'http://localhost:8002'

LMS_URL_ROOT = 'http://127.0.0.1:8000'

# The location of the LMS heartbeat page
LMS_HEARTBEAT_URL = get_lms_url('/heartbeat')

# The location of the LMS student dashboard
LMS_DASHBOARD_URL = get_lms_url('/dashboard')

OAUTH2_PROVIDER_URL = get_lms_url('/oauth2')

COMMERCE_API_URL = get_lms_url('/api/commerce/v1/')
# END URL CONFIGURATION


# AUTHENTICATION
# Set these to the correct values for your OAuth2/OpenID Connect provider (e.g., devstack)
SOCIAL_AUTH_EDX_OIDC_KEY = 'replace-me'
SOCIAL_AUTH_EDX_OIDC_SECRET = 'replace-me'
SOCIAL_AUTH_EDX_OIDC_URL_ROOT = OAUTH2_PROVIDER_URL
Пример #48
0
DEBUG = True
ENABLE_AUTO_AUTH = True


# PAYMENT PROCESSING
PAYMENT_PROCESSOR_CONFIG = {
    'cybersource': {
        'soap_api_url': 'https://ics2wstest.ic3.com/commerce/1.x/transactionProcessor/CyberSourceTransaction_1.115.wsdl',
        'merchant_id': 'fake-merchant-id',
        'transaction_key': 'fake-transaction-key',
        'profile_id': 'fake-profile-id',
        'access_key': 'fake-access-key',
        'secret_key': 'fake-secret-key',
        'payment_page_url': 'https://testsecureacceptance.cybersource.com/pay',
        'receipt_page_url': get_lms_url('/commerce/checkout/receipt/'),
        'cancel_page_url': get_lms_url('/commerce/checkout/cancel/'),
    },
    'paypal': {
        'mode': 'sandbox',
        'client_id': 'fake-client-id',
        'client_secret': 'fake-client-secret',
        'receipt_url': get_lms_url('/commerce/checkout/receipt/'),
        'cancel_url': get_lms_url('/commerce/checkout/cancel/'),
        'error_url': get_lms_url('/commerce/checkout/error/'),
    },
}
# END PAYMENT PROCESSING

# Load private settings
if os.path.isfile(join(dirname(abspath(__file__)), 'private.py')):