def post(self, request, *args, **kwargs): # pylint: disable=unused-argument """ Attempt to create the order and enroll the user. """ user = request.user valid, course_key, error = self._is_data_valid(request) if not valid: return DetailResponse(error, status=HTTP_406_NOT_ACCEPTABLE) # Don't do anything if an enrollment already exists course_id = unicode(course_key) enrollment = CourseEnrollment.get_enrollment(user, course_key) if enrollment and enrollment.is_active: msg = Messages.ENROLLMENT_EXISTS.format(course_id=course_id, username=user.username) return DetailResponse(msg, status=HTTP_409_CONFLICT) # If there is no honor course mode, this most likely a Prof-Ed course. Return an error so that the JS # redirects to track selection. honor_mode = CourseMode.mode_for_course(course_key, CourseMode.HONOR) if not honor_mode: msg = Messages.NO_HONOR_MODE.format(course_id=course_id) return DetailResponse(msg, status=HTTP_406_NOT_ACCEPTABLE) elif not honor_mode.sku: # If there are no course modes with SKUs, enroll the user without contacting the external API. msg = Messages.NO_SKU_ENROLLED.format( enrollment_mode=CourseMode.HONOR, course_id=course_id, username=user.username) log.debug(msg) self._enroll(course_key, user) return DetailResponse(msg) # Setup the API and report any errors if settings are not valid. try: api = EcommerceAPI() except InvalidConfigurationError: self._enroll(course_key, user) msg = Messages.NO_ECOM_API.format(username=user.username, course_id=unicode(course_key)) log.debug(msg) return DetailResponse(msg) # Make the API call try: order_number, order_status, _body = api.create_order( user, honor_mode.sku) if order_status == OrderStatus.COMPLETE: msg = Messages.ORDER_COMPLETED.format( order_number=order_number) log.debug(msg) return DetailResponse(msg) else: # TODO Before this functionality is fully rolled-out, this branch should be updated to NOT enroll the # user. Enrollments must be initiated by the E-Commerce API only. self._enroll(course_key, user) msg = u'Order %(order_number)s was received with %(status)s status. Expected %(complete_status)s. ' \ u'User %(username)s was enrolled in %(course_id)s by LMS.' msg_kwargs = { 'order_number': order_number, 'status': order_status, 'complete_status': OrderStatus.COMPLETE, 'username': user.username, 'course_id': course_id, } log.error(msg, msg_kwargs) msg = Messages.ORDER_INCOMPLETE_ENROLLED.format( order_number=order_number) return DetailResponse(msg, status=HTTP_202_ACCEPTED) except ApiError as err: # The API will handle logging of the error. return InternalRequestErrorResponse(err.message)
def post(self, request, *args, **kwargs): # pylint: disable=unused-argument """ Attempt to create the basket and enroll the user. """ user = request.user valid, course_key, error = self._is_data_valid(request) if not valid: return DetailResponse(error, status=HTTP_406_NOT_ACCEPTABLE) embargo_response = embargo_api.get_embargo_response(request, course_key, user) if embargo_response: return embargo_response # Don't do anything if an enrollment already exists course_id = unicode(course_key) enrollment = CourseEnrollment.get_enrollment(user, course_key) if enrollment and enrollment.is_active: msg = Messages.ENROLLMENT_EXISTS.format(course_id=course_id, username=user.username) return DetailResponse(msg, status=HTTP_409_CONFLICT) # If there is no honor course mode, this most likely a Prof-Ed course. Return an error so that the JS # redirects to track selection. honor_mode = CourseMode.mode_for_course(course_key, CourseMode.HONOR) if not honor_mode: msg = Messages.NO_HONOR_MODE.format(course_id=course_id) return DetailResponse(msg, status=HTTP_406_NOT_ACCEPTABLE) elif not honor_mode.sku: # If there are no course modes with SKUs, enroll the user without contacting the external API. msg = Messages.NO_SKU_ENROLLED.format(enrollment_mode=CourseMode.HONOR, course_id=course_id, username=user.username) log.info(msg) self._enroll(course_key, user) self._handle_marketing_opt_in(request, course_key, user) return DetailResponse(msg) # Setup the API try: api = ecommerce_api_client(user) except ValueError: self._enroll(course_key, user) msg = Messages.NO_ECOM_API.format(username=user.username, course_id=unicode(course_key)) log.debug(msg) return DetailResponse(msg) response = None # Make the API call try: response_data = api.baskets.post({ 'products': [{'sku': honor_mode.sku}], 'checkout': True, }) payment_data = response_data["payment_data"] if payment_data: # Pass data to the client to begin the payment flow. response = JsonResponse(payment_data) elif response_data['order']: # The order was completed immediately because there is no charge. msg = Messages.ORDER_COMPLETED.format(order_number=response_data['order']['number']) log.debug(msg) response = DetailResponse(msg) else: msg = u'Unexpected response from basket endpoint.' log.error( msg + u' Could not enroll user %(username)s in course %(course_id)s.', {'username': user.id, 'course_id': course_id}, ) raise InvalidResponseError(msg) except (exceptions.SlumberBaseException, exceptions.Timeout) as ex: log.exception(ex.message) return InternalRequestErrorResponse(ex.message) finally: audit_log( 'checkout_requested', course_id=course_id, mode=honor_mode.slug, processor_name=None, user_id=user.id ) self._handle_marketing_opt_in(request, course_key, user) return response
def post(self, request, *args, **kwargs): """ Attempt to create the basket and enroll the user. """ user = request.user valid, course_key, error = self._is_data_valid(request) if not valid: return DetailResponse(error, status=HTTP_406_NOT_ACCEPTABLE) embargo_response = embargo_api.get_embargo_response( request, course_key, user) if embargo_response: return embargo_response # Don't do anything if an enrollment already exists course_id = unicode(course_key) enrollment = CourseEnrollment.get_enrollment(user, course_key) if enrollment and enrollment.is_active: msg = Messages.ENROLLMENT_EXISTS.format(course_id=course_id, username=user.username) return DetailResponse(msg, status=HTTP_409_CONFLICT) # Check to see if enrollment for this course is closed. course = courses.get_course(course_key) if CourseEnrollment.is_enrollment_closed(user, course): msg = Messages.ENROLLMENT_CLOSED.format(course_id=course_id) log.info(u'Unable to enroll user %s in closed course %s.', user.id, course_id) return DetailResponse(msg, status=HTTP_406_NOT_ACCEPTABLE) # If there is no audit or honor course mode, this most likely # a Prof-Ed course. Return an error so that the JS redirects # to track selection. honor_mode = CourseMode.mode_for_course(course_key, CourseMode.HONOR) audit_mode = CourseMode.mode_for_course(course_key, CourseMode.AUDIT) # Accept either honor or audit as an enrollment mode to # maintain backwards compatibility with existing courses default_enrollment_mode = audit_mode or honor_mode if not default_enrollment_mode: msg = Messages.NO_DEFAULT_ENROLLMENT_MODE.format( course_id=course_id) return DetailResponse(msg, status=HTTP_406_NOT_ACCEPTABLE) elif default_enrollment_mode and not default_enrollment_mode.sku: # If there are no course modes with SKUs, enroll the user without contacting the external API. msg = Messages.NO_SKU_ENROLLED.format( enrollment_mode=default_enrollment_mode.slug, course_id=course_id, username=user.username) log.info(msg) self._enroll(course_key, user, default_enrollment_mode.slug) self._handle_marketing_opt_in(request, course_key, user) return DetailResponse(msg) # Setup the API try: api_session = requests.Session() api = ecommerce_api_client(user, session=api_session) except ValueError: self._enroll(course_key, user) msg = Messages.NO_ECOM_API.format(username=user.username, course_id=unicode(course_key)) log.debug(msg) return DetailResponse(msg) response = None # Make the API call try: # Pass along Sailthru campaign id self._add_request_cookie_to_api_session(api_session, request, SAILTHRU_CAMPAIGN_COOKIE) # Pass along UTM tracking info utm_cookie_name = RegistrationCookieConfiguration.current( ).utm_cookie_name self._add_request_cookie_to_api_session(api_session, request, utm_cookie_name) response_data = api.baskets.post({ 'products': [{ 'sku': default_enrollment_mode.sku }], 'checkout': True, }) payment_data = response_data["payment_data"] if payment_data: # Pass data to the client to begin the payment flow. response = JsonResponse(payment_data) elif response_data['order']: # The order was completed immediately because there is no charge. msg = Messages.ORDER_COMPLETED.format( order_number=response_data['order']['number']) log.debug(msg) response = DetailResponse(msg) else: msg = u'Unexpected response from basket endpoint.' log.error( msg + u' Could not enroll user %(username)s in course %(course_id)s.', { 'username': user.id, 'course_id': course_id }, ) raise InvalidResponseError(msg) except (exceptions.SlumberBaseException, exceptions.Timeout) as ex: log.exception(ex.message) return InternalRequestErrorResponse(ex.message) finally: audit_log('checkout_requested', course_id=course_id, mode=default_enrollment_mode.slug, processor_name=None, user_id=user.id) self._handle_marketing_opt_in(request, course_key, user) return response
def post(self, request, *args, **kwargs): """ Attempt to enroll the user, and if needed, create the basket. """ user = request.user valid, course_key, error = self._is_data_valid(request) if not valid: return DetailResponse(error, status=HTTP_406_NOT_ACCEPTABLE) embargo_response = embargo_api.get_embargo_response( request, course_key, user) if embargo_response: return embargo_response # Don't do anything if an enrollment already exists course_id = unicode(course_key) enrollment = CourseEnrollment.get_enrollment(user, course_key) if enrollment and enrollment.is_active: msg = Messages.ENROLLMENT_EXISTS.format(course_id=course_id, username=user.username) return DetailResponse(msg, status=HTTP_409_CONFLICT) # Check to see if enrollment for this course is closed. course = courses.get_course(course_key) if CourseEnrollment.is_enrollment_closed(user, course): msg = Messages.ENROLLMENT_CLOSED.format(course_id=course_id) log.info(u'Unable to enroll user %s in closed course %s.', user.id, course_id) return DetailResponse(msg, status=HTTP_406_NOT_ACCEPTABLE) # If there is no audit or honor course mode, this most likely # a Prof-Ed course. Return an error so that the JS redirects # to track selection. honor_mode = CourseMode.mode_for_course(course_key, CourseMode.HONOR) audit_mode = CourseMode.mode_for_course(course_key, CourseMode.AUDIT) # Accept either honor or audit as an enrollment mode to # maintain backwards compatibility with existing courses default_enrollment_mode = audit_mode or honor_mode if not default_enrollment_mode: msg = Messages.NO_DEFAULT_ENROLLMENT_MODE.format( course_id=course_id) return DetailResponse(msg, status=HTTP_406_NOT_ACCEPTABLE) elif not default_enrollment_mode.sku or STOP_BASKET_CREATION_FLAG.is_enabled( ): msg = Messages.ENROLL_DIRECTLY.format(username=user.username, course_id=course_id) if not default_enrollment_mode.sku: # If there are no course modes with SKUs, return a different message. msg = Messages.NO_SKU_ENROLLED.format( enrollment_mode=default_enrollment_mode.slug, course_id=course_id, username=user.username) log.info(msg) self._enroll(course_key, user, default_enrollment_mode.slug) self._handle_marketing_opt_in(request, course_key, user) return DetailResponse(msg) else: return self._create_basket_to_order(request, user, course_key, default_enrollment_mode)
def _create_basket_to_order(self, request, user, course_key, default_enrollment_mode): """ Connect to the ecommerce service to create the basket and the order to do the enrollment """ # Setup the API course_id = unicode(course_key) try: api_session = requests.Session() api = ecommerce_api_client(user, session=api_session) except ValueError: self._enroll(course_key, user) msg = Messages.NO_ECOM_API.format(username=user.username, course_id=course_id) log.debug(msg) return DetailResponse(msg) response = None # Make the API call try: # Pass along Sailthru campaign id self._add_request_cookie_to_api_session(api_session, request, SAILTHRU_CAMPAIGN_COOKIE) # Pass along UTM tracking info utm_cookie_name = RegistrationCookieConfiguration.current( ).utm_cookie_name self._add_request_cookie_to_api_session(api_session, request, utm_cookie_name) response_data = api.baskets.post({ 'products': [{ 'sku': default_enrollment_mode.sku }], 'checkout': True, }) payment_data = response_data["payment_data"] if payment_data: # Pass data to the client to begin the payment flow. response = JsonResponse(payment_data) elif response_data['order']: # The order was completed immediately because there is no charge. msg = Messages.ORDER_COMPLETED.format( order_number=response_data['order']['number']) log.debug(msg) response = DetailResponse(msg) else: msg = u'Unexpected response from basket endpoint.' log.error( msg + u' Could not enroll user %(username)s in course %(course_id)s.', { 'username': user.id, 'course_id': course_id }, ) raise InvalidResponseError(msg) except (exceptions.SlumberBaseException, exceptions.Timeout) as ex: log.exception(ex.message) return InternalRequestErrorResponse(ex.message) finally: audit_log('checkout_requested', course_id=course_id, mode=default_enrollment_mode.slug, processor_name=None, user_id=user.id) self._handle_marketing_opt_in(request, course_key, user) return response
def post(self, request, *args, **kwargs): # pylint: disable=unused-argument """ Attempt to create the basket and enroll the user. """ user = request.user valid, course_key, error = self._is_data_valid(request) if not valid: return DetailResponse(error, status=HTTP_406_NOT_ACCEPTABLE) # Don't do anything if an enrollment already exists course_id = unicode(course_key) enrollment = CourseEnrollment.get_enrollment(user, course_key) if enrollment and enrollment.is_active: msg = Messages.ENROLLMENT_EXISTS.format(course_id=course_id, username=user.username) return DetailResponse(msg, status=HTTP_409_CONFLICT) # If there is no honor course mode, this most likely a Prof-Ed course. Return an error so that the JS # redirects to track selection. honor_mode = CourseMode.mode_for_course(course_key, CourseMode.HONOR) if not honor_mode: msg = Messages.NO_HONOR_MODE.format(course_id=course_id) return DetailResponse(msg, status=HTTP_406_NOT_ACCEPTABLE) elif not honor_mode.sku: # If there are no course modes with SKUs, enroll the user without contacting the external API. msg = Messages.NO_SKU_ENROLLED.format(enrollment_mode=CourseMode.HONOR, course_id=course_id, username=user.username) log.debug(msg) self._enroll(course_key, user) return DetailResponse(msg) # Setup the API and report any errors if settings are not valid. try: api = EcommerceAPI() except InvalidConfigurationError: self._enroll(course_key, user) msg = Messages.NO_ECOM_API.format(username=user.username, course_id=unicode(course_key)) log.debug(msg) return DetailResponse(msg) # Make the API call try: response_data = api.create_basket( user, honor_mode.sku, payment_processor="cybersource", ) payment_data = response_data["payment_data"] if payment_data is not None: # it is time to start the payment flow. # NOTE this branch does not appear to be used at the moment. return JsonResponse(payment_data) elif response_data['order']: # the order was completed immediately because there was no charge. msg = Messages.ORDER_COMPLETED.format(order_number=response_data['order']['number']) log.debug(msg) return DetailResponse(msg) else: # Enroll in the honor mode directly as a failsafe. # This MUST be removed when this code handles paid modes. self._enroll(course_key, user) msg = u'Unexpected response from basket endpoint.' log.error( msg + u' Could not enroll user %(username)s in course %(course_id)s.', {'username': user.id, 'course_id': course_id}, ) raise InvalidResponseError(msg) except ApiError as err: # The API will handle logging of the error. return InternalRequestErrorResponse(err.message)