Exemplo n.º 1
0
    def _add_enterprise_data_to_enrollment_api_post(self, data, order):
        """ Augment enrollment api POST data with enterprise specific data.

        Checks the order to see if there was a discount applied and if that discount
        was associated with an EnterpriseCustomer. If so, enterprise specific data
        is added to the POST data and an EnterpriseCustomerUser model is created if
        one does not already exist.

        Arguments:
            data (dict): The POST data for the enrollment API.
            order (Order): The order.
        """
        # Collect the EnterpriseCustomer UUID from the coupon, if any.
        enterprise_customer_uuid = None
        for discount in order.discounts.all():
            try:
                enterprise_customer_uuid = discount.voucher.benefit.range.enterprise_customer
            except AttributeError:
                # The voucher did not have an enterprise customer associated with it.
                pass

            if enterprise_customer_uuid is not None:
                data['linked_enterprise_customer'] = str(enterprise_customer_uuid)
                break

        # If an EnterpriseCustomer UUID is associated with the coupon, create an EnterpriseCustomerUser
        # on the Enterprise service if one doesn't already exist.
        if enterprise_customer_uuid is not None:
            get_or_create_enterprise_customer_user(
                order.site,
                enterprise_customer_uuid,
                order.user.username
            )
Exemplo n.º 2
0
 def _create_enterprise_customer_user(self, order):
     """
     Create the enterprise customer user if an EnterpriseCustomer UUID is associated in the order's discount voucher.
     """
     enterprise_customer_uuid = None
     for discount in order.discounts.all():
         if discount.voucher:
             enterprise_customer_uuid = get_enterprise_customer_uuid_from_voucher(
                 discount.voucher)
         if enterprise_customer_uuid is not None:
             get_or_create_enterprise_customer_user(
                 order.site, enterprise_customer_uuid, order.user.username)
             break
Exemplo n.º 3
0
    def _add_enterprise_data_to_enrollment_api_post(self, data, order):
        """ Augment enrollment api POST data with enterprise specific data.

        Checks the order to see if there was a discount applied and if that discount
        was associated with an EnterpriseCustomer. If so, enterprise specific data
        is added to the POST data and an EnterpriseCustomerUser model is created if
        one does not already exist.

        Arguments:
            data (dict): The POST data for the enrollment API.
            order (Order): The order.
        """
        # Collect the EnterpriseCustomer UUID from the coupon, if any.
        enterprise_customer_uuid = None
        for discount in order.discounts.all():
            if discount.voucher:
                logger.info("Getting enterprise_customer_uuid from discount voucher for order [%s]", order.number)
                enterprise_customer_uuid = get_enterprise_customer_uuid_from_voucher(discount.voucher)
                logger.info(
                    "enterprise_customer_uuid on discount voucher for order [%s] is [%s]",
                    order.number, enterprise_customer_uuid
                )

            if enterprise_customer_uuid is not None:
                logger.info(
                    "Adding linked_enterprise_customer to data with enterprise_customer_uuid [%s] for order [%s]",
                    enterprise_customer_uuid, order.number
                )
                data['linked_enterprise_customer'] = str(enterprise_customer_uuid)
                break
        # If an EnterpriseCustomer UUID is associated with the coupon, create an EnterpriseCustomerUser
        # on the Enterprise service if one doesn't already exist.
        if enterprise_customer_uuid is not None:
            logger.info(
                "Getting or creating enterprise_customer_user "
                "for site [%s], enterprise customer [%s], and username [%s], for order [%s]",
                order.site, enterprise_customer_uuid, order.user.username, order.number
            )
            get_or_create_enterprise_customer_user(
                order.site,
                enterprise_customer_uuid,
                order.user.username
            )
            logger.info(
                "Finished get_or_create enterpruise customer user for order [%s]",
                order.number
            )
Exemplo n.º 4
0
    def test_post_enterprise_customer_user(self, mock_helpers,
                                           expected_return):
        """
        Verify that "get_enterprise_customer" returns an appropriate response from the
        "enterprise-customer" Enterprise service API endpoint.
        """
        for mock in mock_helpers:
            getattr(self, mock)()

        response = get_or_create_enterprise_customer_user(
            self.site, TEST_ENTERPRISE_CUSTOMER_UUID, self.learner.username)

        self.assertDictContainsSubset(expected_return, response)
Exemplo n.º 5
0
    def fulfill_product(self, order, lines):
        """ Fulfills the purchase of a 'seat' by enrolling the associated student.

        Uses the order and the lines to determine which courses to enroll a student in, and with certain
        certificate types. May result in an error if the Enrollment API cannot be reached, or if there is
        additional business logic errors when trying to enroll the student.

        Args:
            order (Order): The Order associated with the lines to be fulfilled. The user associated with the order
                is presumed to be the student to enroll in a course.
            lines (List of Lines): Order Lines, associated with purchased products in an Order. These should only
                be "Seat" products.

        Returns:
            The original set of lines, with new statuses set based on the success or failure of fulfillment.

        """
        logger.info("Attempting to fulfill 'Seat' product types for order [%s]", order.number)

        api_key = getattr(settings, 'EDX_API_KEY', None)
        if not api_key:
            logger.error(
                'EDX_API_KEY must be set to use the EnrollmentFulfillmentModule'
            )
            for line in lines:
                line.set_status(LINE.FULFILLMENT_CONFIGURATION_ERROR)

            return order, lines

        for line in lines:
            try:
                mode = mode_for_seat(line.product)
                course_key = line.product.attr.course_key
            except AttributeError:
                logger.error("Supported Seat Product does not have required attributes, [certificate_type, course_key]")
                line.set_status(LINE.FULFILLMENT_CONFIGURATION_ERROR)
                continue
            try:
                provider = line.product.attr.credit_provider
            except AttributeError:
                logger.debug("Seat [%d] has no credit_provider attribute. Defaulted to None.", line.product.id)
                provider = None

            data = {
                'user': order.user.username,
                'is_active': True,
                'mode': mode,
                'course_details': {
                    'course_id': course_key
                },
                'enrollment_attributes': [
                    {
                        'namespace': 'order',
                        'name': 'order_number',
                        'value': order.number
                    }
                ]
            }
            if provider:
                data['enrollment_attributes'].append(
                    {
                        'namespace': 'credit',
                        'name': 'provider_id',
                        'value': provider
                    }
                )
            try:
                # Collect the EnterpriseCustomer UUID from the coupon, if any.
                enterprise_customer_uuid = None
                for discount in order.discounts.all():
                    if discount.voucher:
                        enterprise_customer_uuid = discount.voucher.benefit.range.enterprise_customer
                        if enterprise_customer_uuid is not None:
                            data['enterprise_course_consent'] = True
                            break

                # If an EnterpriseCustomer UUID is associated with the coupon, create an EnterpriseCustomerUser
                # on the Enterprise service if one doesn't already exist.
                if enterprise_customer_uuid is not None:
                    get_or_create_enterprise_customer_user(
                        order.site,
                        enterprise_customer_uuid,
                        order.user.username
                    )

                # Post to the Enrollment API. The LMS will take care of posting a new EnterpriseCourseEnrollment to
                # the Enterprise service if the user+course has a corresponding EnterpriseCustomerUser.
                response = self._post_to_enrollment_api(data, user=order.user)

                if response.status_code == status.HTTP_200_OK:
                    line.set_status(LINE.COMPLETE)

                    audit_log(
                        'line_fulfilled',
                        order_line_id=line.id,
                        order_number=order.number,
                        product_class=line.product.get_product_class().name,
                        course_id=course_key,
                        mode=mode,
                        user_id=order.user.id,
                        credit_provider=provider,
                    )
                else:
                    try:
                        data = response.json()
                        reason = data.get('message')
                    except Exception:  # pylint: disable=broad-except
                        reason = '(No detail provided.)'

                    logger.error(
                        "Fulfillment of line [%d] on order [%s] failed with status code [%d]: %s",
                        line.id, order.number, response.status_code, reason
                    )
                    line.set_status(LINE.FULFILLMENT_SERVER_ERROR)
            except ConnectionError:
                logger.error(
                    "Unable to fulfill line [%d] of order [%s] due to a network problem", line.id, order.number
                )
                line.set_status(LINE.FULFILLMENT_NETWORK_ERROR)
            except Timeout:
                logger.error(
                    "Unable to fulfill line [%d] of order [%s] due to a request time out", line.id, order.number
                )
                line.set_status(LINE.FULFILLMENT_TIMEOUT_ERROR)
        logger.info("Finished fulfilling 'Seat' product types for order [%s]", order.number)
        return order, lines
Exemplo n.º 6
0
    def is_satisfied(self, offer, basket):  # pylint: disable=unused-argument
        """
        Determines if a user is eligible for an enterprise customer offer
        based on their association with the enterprise customer.

        It also filter out the offer if the `enterprise_customer_catalog_uuid`
        value set on the offer condition does not match with the basket catalog
        value when explicitly provided by the enterprise learner.

        Note: Currently there is no mechanism to prioritize or apply multiple
        offers that may apply as opposed to disqualifying offers if the
        catalog doesn't explicitly match.

        Arguments:
            basket (Basket): Contains information about order line items, the current site,
                             and the user attempting to make the purchase.
        Returns:
            bool
        """
        if not basket.owner:
            # An anonymous user is never linked to any EnterpriseCustomer.
            return False

        enterprise_in_condition = str(self.enterprise_customer_uuid)
        enterprise_catalog = str(self.enterprise_customer_catalog_uuid) if self.enterprise_customer_catalog_uuid \
            else None
        enterprise_name_in_condition = str(self.enterprise_customer_name)
        username = basket.owner.username
        course_run_ids = []
        for line in basket.all_lines():
            course = line.product.course
            if not course:
                # Basket contains products not related to a course_run.
                # Only log for non-site offers to avoid noise.
                if offer.offer_type != ConditionalOffer.SITE:
                    logger.warning(
                        '[Code Redemption Failure] Unable to apply enterprise offer because '
                        'the Basket contains a product not related to a course_run. '
                        'User: %s, Offer: %s, Product: %s, Enterprise: %s, Catalog: %s',
                        username, offer.id, line.product.id,
                        enterprise_in_condition, enterprise_catalog)
                return False

            course_run_ids.append(course.id)

        courses_in_basket = ','.join(course_run_ids)
        user_enterprise = get_enterprise_id_for_user(basket.site, basket.owner)
        if user_enterprise and enterprise_in_condition != user_enterprise:
            # Learner is not linked to the EnterpriseCustomer associated with this condition.
            if offer.offer_type == ConditionalOffer.VOUCHER:
                logger.warning(
                    '[Code Redemption Failure] Unable to apply enterprise offer because Learner\'s '
                    'enterprise (%s) does not match this conditions\'s enterprise (%s). '
                    'User: %s, Offer: %s, Enterprise: %s, Catalog: %s, Courses: %s',
                    user_enterprise, enterprise_in_condition, username,
                    offer.id, enterprise_in_condition, enterprise_catalog,
                    courses_in_basket)

                logger.info(
                    '[Code Redemption Issue] Linking learner with the enterprise in Condition. '
                    'User [%s], Enterprise [%s]', username,
                    enterprise_in_condition)
                get_or_create_enterprise_customer_user(
                    basket.site, enterprise_in_condition, username, False)
                msg = _(
                    'This coupon has been made available through {new_enterprise}. '
                    'To redeem this coupon, you must first logout. When you log back in, '
                    'please select {new_enterprise} as your enterprise '
                    'and try again.').format(
                        new_enterprise=enterprise_name_in_condition)
                messages.warning(
                    crum.get_current_request(),
                    msg,
                )

            return False

        # Verify that the current conditional offer is related to the provided
        # enterprise catalog, this will also filter out offers which don't
        # have `enterprise_customer_catalog_uuid` value set on the condition.
        catalog = self._get_enterprise_catalog_uuid_from_basket(basket)
        if catalog:
            if offer.condition.enterprise_customer_catalog_uuid != catalog:
                logger.warning(
                    'Unable to apply enterprise offer %s because '
                    'Enterprise catalog id on the basket (%s) '
                    'does not match the catalog for this condition (%s).',
                    offer.id, catalog,
                    offer.condition.enterprise_customer_catalog_uuid)
                return False

        try:
            catalog_contains_course = catalog_contains_course_runs(
                basket.site,
                course_run_ids,
                enterprise_in_condition,
                enterprise_customer_catalog_uuid=enterprise_catalog)
        except (ReqConnectionError, KeyError, SlumberHttpBaseException,
                Timeout) as exc:
            logger.exception(
                '[Code Redemption Failure] Unable to apply enterprise offer because '
                'we failed to check if course_runs exist in the catalog. '
                'User: %s, Offer: %s, Message: %s, Enterprise: %s, Catalog: %s, Courses: %s',
                username, offer.id, exc, enterprise_in_condition,
                enterprise_catalog, courses_in_basket)
            return False

        if not catalog_contains_course:
            # Basket contains course runs that do not exist in the EnterpriseCustomerCatalogs
            # associated with the EnterpriseCustomer.
            logger.warning(
                '[Code Redemption Failure] Unable to apply enterprise offer because '
                'Enterprise catalog does not contain the course(s) in this basket. '
                'User: %s, Offer: %s, Enterprise: %s, Catalog: %s, Courses: %s',
                username, offer.id, enterprise_in_condition,
                enterprise_catalog, courses_in_basket)
            return False

        if not is_offer_max_discount_available(basket, offer):
            logger.warning(
                '[Enterprise Offer Failure] Unable to apply enterprise offer because bookings limit is consumed.'
                'User: %s, Offer: %s, Enterprise: %s, Catalog: %s, Courses: %s, BookingsLimit: %s, TotalDiscount: %s',
                username,
                offer.id,
                enterprise_in_condition,
                enterprise_catalog,
                courses_in_basket,
                offer.max_discount,
                offer.total_discount,
            )
            return False

        if not is_offer_max_user_discount_available(basket, offer):
            logger.warning(
                '[Enterprise Offer Failure] Unable to apply enterprise offer because user bookings limit is consumed.'
                'User: %s, Offer: %s, Enterprise: %s, Catalog: %s, Courses: %s, UserBookingsLimit: %s',
                username, offer.id, enterprise_in_condition,
                enterprise_catalog, courses_in_basket, offer.max_user_discount)
            return False

        return True