def checkout_with_ecommerce_service(user, course_key, course_mode, processor): # pylint: disable=invalid-name """ Create a new basket and trigger immediate checkout, using the E-Commerce API. """ course_id = unicode(course_key) try: api = ecommerce_api_client(user) # Make an API call to create the order and retrieve the results result = api.baskets.post({ 'products': [{'sku': course_mode.sku}], 'checkout': True, 'payment_processor_name': processor }) # Pass the payment parameters directly from the API response. return result.get('payment_data') except SlumberBaseException: params = {'username': user.username, 'mode': course_mode.slug, 'course_id': course_id} log.exception('Failed to create order for %(username)s %(mode)s mode of %(course_id)s', params) raise finally: audit_log( 'checkout_requested', course_id=course_id, mode=course_mode.slug, processor_name=processor, user_id=user.id )
def test_log_message(self, mock_log): """Verify that log messages are constructed correctly.""" audit_log('foo', qux='quux', bar='baz') # Verify that the logged message contains comma-separated # key-value pairs ordered alphabetically by key. message = 'foo: bar="baz", qux="quux"' self.assertTrue(mock_log.info.called_with(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 campaign_cookie = request.COOKIES.get(SAILTHRU_CAMPAIGN_COOKIE) if campaign_cookie: cookie = {SAILTHRU_CAMPAIGN_COOKIE: campaign_cookie} if api_session.cookies: requests.utils.add_dict_to_cookiejar(api_session.cookies, cookie) else: api_session.cookies = requests.utils.cookiejar_from_dict(cookie) 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) 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.debug(msg) self._enroll(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) # 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. return 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) return 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 )
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 campaign_cookie = request.COOKIES.get(SAILTHRU_CAMPAIGN_COOKIE) if campaign_cookie: cookie = {SAILTHRU_CAMPAIGN_COOKIE: campaign_cookie} if api_session.cookies: requests.utils.add_dict_to_cookiejar( api_session.cookies, cookie) else: api_session.cookies = requests.utils.cookiejar_from_dict( cookie) 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