def test_if_already_enroll(self): """ Test that enrollment api is skipped of user is already enroll is course. """ def create_audit(course_key): """Helper function to create a fake enrollment""" return Enrollment({"course_details": {"course_id": course_key}}) def get_student_enrollments(): """List of existing enrollments""" return Enrollments([ {"course_details": {"course_id": self.line1.course_key}}, {"course_details": {"course_id": self.line2.course_key}} ]) create_audit_mock = MagicMock(side_effect=create_audit) get_enrollments_mock = MagicMock( side_effect=get_student_enrollments, is_enrolled_in=True ) enrollments_mock = MagicMock( create_audit_student_enrollment=create_audit_mock, get_student_enrollments=get_enrollments_mock ) edx_api_mock = MagicMock(enrollments=enrollments_mock) with patch('ecommerce.api.EdxApi', return_value=edx_api_mock): enroll_user_on_success(self.order) assert get_enrollments_mock.called is True assert not create_audit_mock.called
def test_enroll(self): """ Test that an enrollment is made for each course key attached to the order and that the CachedEnrollments are produced. """ def create_audit(course_key): """Helper function to create a fake enrollment""" return Enrollment({"course_details": {"course_id": course_key}}) def get_student_enrollments(): """List of existing enrollments""" return Enrollments([]) create_audit_mock = MagicMock(side_effect=create_audit) get_enrollments_mock = MagicMock(side_effect=get_student_enrollments) enrollments_mock = MagicMock( create_audit_student_enrollment=create_audit_mock, get_student_enrollments=get_enrollments_mock ) edx_api_mock = MagicMock(enrollments=enrollments_mock) with patch('ecommerce.api.EdxApi', return_value=edx_api_mock): enroll_user_on_success(self.order) assert get_enrollments_mock.called is True assert len(create_audit_mock.call_args_list) == self.order.line_set.count() for i, line in enumerate(self.order.line_set.all()): assert create_audit_mock.call_args_list[i][0] == (line.course_key, ) assert CachedEnrollment.objects.count() == self.order.line_set.count() for line in self.order.line_set.all(): enrollment = CachedEnrollment.objects.get( user=self.order.user, course_run__edx_course_key=line.course_key, ) assert enrollment.data == create_audit(line.course_key).json
def test_failed(self): """ Test that an exception is raised containing a list of exceptions of the failed enrollments """ def create_audit(course_key): """Fail for first course key""" if course_key == self.line1.course_key: raise Exception("fatal error {}".format(course_key)) return Enrollment({"course_details": {"course_id": course_key}}) def get_student_enrollments(): """List of existing enrollments""" return Enrollments([]) create_audit_mock = MagicMock(side_effect=create_audit) get_enrollments_mock = MagicMock(side_effect=get_student_enrollments) enrollments_mock = MagicMock( create_audit_student_enrollment=create_audit_mock, get_student_enrollments=get_enrollments_mock) edx_api_mock = MagicMock(enrollments=enrollments_mock) with patch('ecommerce.api.EdxApi', return_value=edx_api_mock): with self.assertRaises(EcommerceEdxApiException) as ex: enroll_user_on_success(self.order) assert len(ex.exception.args[0]) == 1 assert ex.exception.args[0][0].args[0] == 'fatal error {}'.format( self.line1.course_key) assert len( create_audit_mock.call_args_list) == self.order.line_set.count() for i, line in enumerate(self.order.line_set.all()): assert create_audit_mock.call_args_list[i][0] == ( line.course_key, ) assert CachedEnrollment.objects.count() == 1 enrollment = CachedEnrollment.objects.get( user=self.order.user, course_run__edx_course_key=self.line2.course_key, ) assert enrollment.data == create_audit(self.line2.course_key).json
def test_failed(self): """ Test that an exception is raised containing a list of exceptions of the failed enrollments """ def create_audit(course_key): """Fail for first course key""" if course_key == self.line1.course_key: raise Exception("fatal error {}".format(course_key)) return Enrollment({"course_details": {"course_id": course_key}}) def get_student_enrollments(): """List of existing enrollments""" return Enrollments([]) create_audit_mock = MagicMock(side_effect=create_audit) get_enrollments_mock = MagicMock(side_effect=get_student_enrollments) enrollments_mock = MagicMock( create_audit_student_enrollment=create_audit_mock, get_student_enrollments=get_enrollments_mock ) edx_api_mock = MagicMock(enrollments=enrollments_mock) with patch('ecommerce.api.EdxApi', return_value=edx_api_mock): with self.assertRaises(EcommerceEdxApiException) as ex: enroll_user_on_success(self.order) assert len(ex.exception.args[0]) == 1 assert ex.exception.args[0][0].args[0] == 'fatal error {}'.format(self.line1.course_key) assert len(create_audit_mock.call_args_list) == self.order.line_set.count() for i, line in enumerate(self.order.line_set.all()): assert create_audit_mock.call_args_list[i][0] == (line.course_key, ) assert CachedEnrollment.objects.count() == 1 enrollment = CachedEnrollment.objects.get( user=self.order.user, course_run__edx_course_key=self.line2.course_key, ) assert enrollment.data == create_audit(self.line2.course_key).json
def post(self, request, *args, **kwargs): """ If the course run is part of a financial aid program, create a new unfulfilled Order and return information used to submit to CyberSource. If the program does not have financial aid, this will return a URL to let the user pay for the course on edX. """ user_ip, _ = get_client_ip(request) try: course_id = request.data['course_id'] except KeyError: raise ValidationError("Missing course_id") course_run = get_object_or_404( CourseRun, course__program__live=True, edx_course_key=course_id, ) if course_run.course.program.financial_aid_availability: order = create_unfulfilled_order(course_id, request.user) dashboard_url = request.build_absolute_uri('/dashboard/') if order.total_price_paid == 0: # If price is $0, don't bother going to CyberSource, just mark as fulfilled order.status = Order.FULFILLED order.save_and_log(request.user) try: enroll_user_on_success(order) except: # pylint: disable=bare-except log.exception( "Error occurred when enrolling user in one or more courses for order %s. " "See other errors above for more info.", order) try: MailgunClient().send_individual_email( "Error occurred when enrolling user during $0 checkout", "Error occurred when enrolling user during $0 checkout for {order}. " "Exception: {exception}".format( order=order, exception=traceback.format_exc()), settings.ECOMMERCE_EMAIL, ) except: # pylint: disable=bare-except log.exception( "Error occurred when sending the email to notify support " "of user enrollment error during order %s $0 checkout", order, ) # This redirects the user to our order success page payload = {} url = make_dashboard_receipt_url(dashboard_url, course_id, 'receipt') method = 'GET' else: # This generates a signed payload which is submitted as an HTML form to CyberSource payload = generate_cybersource_sa_payload( order, dashboard_url, user_ip) url = settings.CYBERSOURCE_SECURE_ACCEPTANCE_URL method = 'POST' else: # This redirects the user to edX to purchase the course there payload = {} url = urljoin(settings.EDXORG_BASE_URL, '/course_modes/choose/{}/'.format(course_id)) method = 'GET' return Response({ 'payload': payload, 'url': url, 'method': method, })
def post(self, request, *args, **kwargs): """ Confirmation from CyberSource which fulfills an existing Order. """ # First, save this information in a receipt receipt = Receipt.objects.create(data=request.data) # Link the order with the receipt if we can parse it reference_number = request.data['req_reference_number'] order = get_new_order_by_reference_number(reference_number) receipt.order = order receipt.save() decision = request.data['decision'] if order.status == Order.FAILED and decision == CYBERSOURCE_DECISION_CANCEL: # This is a duplicate message, ignore since it's already handled return Response(status=HTTP_200_OK) elif order.status != Order.CREATED: raise EcommerceException( "Order {} is expected to have status 'created'".format( order.id)) if decision != CYBERSOURCE_DECISION_ACCEPT: order.status = Order.FAILED log.warning( "Order fulfillment failed: received a decision that wasn't ACCEPT for order %s", order, ) if decision != CYBERSOURCE_DECISION_CANCEL: try: MailgunClient().send_individual_email( "Order fulfillment failed, decision={decision}".format( decision=decision), "Order fulfillment failed for order {order}".format( order=order, ), settings.ECOMMERCE_EMAIL) except: # pylint: disable=bare-except log.exception( "Error occurred when sending the email to notify " "about order fulfillment failure for order %s", order, ) else: order.status = Order.FULFILLED order.save_and_log(None) if order.status == Order.FULFILLED: try: enroll_user_on_success(order) except: # pylint: disable=bare-except log.exception( "Error occurred when enrolling user in one or more courses for order %s. " "See other errors above for more info.", order) try: MailgunClient().send_individual_email( "Error occurred when enrolling user during order fulfillment", "Error occurred when enrolling user during order fulfillment for {order}. " "Exception: {exception}".format( order=order, exception=traceback.format_exc()), settings.ECOMMERCE_EMAIL, ) except: # pylint: disable=bare-except log.exception( "Error occurred when sending the email to notify support " "of user enrollment error during order %s fulfillment", order, ) # The response does not matter to CyberSource return Response(status=HTTP_200_OK)
def post(self, request, *args, **kwargs): """ If the course run is part of a financial aid program, create a new unfulfilled Order and return information used to submit to CyberSource. If the program does not have financial aid, this will return a URL to let the user pay for the course on edX. """ try: course_id = request.data['course_id'] except KeyError: raise ValidationError("Missing course_id") course_run = get_object_or_404( CourseRun, course__program__live=True, edx_course_key=course_id, ) if course_run.course.program.financial_aid_availability: order = create_unfulfilled_order(course_id, request.user) dashboard_url = request.build_absolute_uri('/dashboard/') if order.total_price_paid == 0: # If price is $0, don't bother going to CyberSource, just mark as fulfilled order.status = Order.FULFILLED order.save_and_log(request.user) try: enroll_user_on_success(order) except: # pylint: disable=bare-except log.exception( "Error occurred when enrolling user in one or more courses for order %s. " "See other errors above for more info.", order ) try: MailgunClient().send_individual_email( "Error occurred when enrolling user during $0 checkout", "Error occurred when enrolling user during $0 checkout for {order}. " "Exception: {exception}".format( order=order, exception=traceback.format_exc() ), settings.ECOMMERCE_EMAIL, ) except: # pylint: disable=bare-except log.exception( "Error occurred when sending the email to notify support " "of user enrollment error during order %s $0 checkout", order, ) # This redirects the user to our order success page payload = {} url = make_dashboard_receipt_url(dashboard_url, course_id, 'receipt') method = 'GET' else: # This generates a signed payload which is submitted as an HTML form to CyberSource payload = generate_cybersource_sa_payload(order, dashboard_url) url = settings.CYBERSOURCE_SECURE_ACCEPTANCE_URL method = 'POST' else: # This redirects the user to edX to purchase the course there payload = {} url = urljoin(settings.EDXORG_BASE_URL, '/course_modes/choose/{}/'.format(course_id)) method = 'GET' return Response({ 'payload': payload, 'url': url, 'method': method, })
def post(self, request, *args, **kwargs): """ Confirmation from CyberSource which fulfills an existing Order. """ # First, save this information in a receipt receipt = Receipt.objects.create(data=request.data) # Link the order with the receipt if we can parse it reference_number = request.data['req_reference_number'] order = get_new_order_by_reference_number(reference_number) receipt.order = order receipt.save() decision = request.data['decision'] if order.status == Order.FAILED and decision == CYBERSOURCE_DECISION_CANCEL: # This is a duplicate message, ignore since it's already handled return Response(status=HTTP_200_OK) elif order.status != Order.CREATED: raise EcommerceException("Order {} is expected to have status 'created'".format(order.id)) if decision != CYBERSOURCE_DECISION_ACCEPT: order.status = Order.FAILED log.warning( "Order fulfillment failed: received a decision that wasn't ACCEPT for order %s", order, ) if decision != CYBERSOURCE_DECISION_CANCEL: try: MailgunClient().send_individual_email( "Order fulfillment failed, decision={decision}".format( decision=decision ), "Order fulfillment failed for order {order}".format( order=order, ), settings.ECOMMERCE_EMAIL ) except: # pylint: disable=bare-except log.exception( "Error occurred when sending the email to notify " "about order fulfillment failure for order %s", order, ) else: order.status = Order.FULFILLED order.save_and_log(None) if order.status == Order.FULFILLED: try: enroll_user_on_success(order) except: # pylint: disable=bare-except log.exception( "Error occurred when enrolling user in one or more courses for order %s. " "See other errors above for more info.", order ) try: MailgunClient().send_individual_email( "Error occurred when enrolling user during order fulfillment", "Error occurred when enrolling user during order fulfillment for {order}. " "Exception: {exception}".format( order=order, exception=traceback.format_exc() ), settings.ECOMMERCE_EMAIL, ) except: # pylint: disable=bare-except log.exception( "Error occurred when sending the email to notify support " "of user enrollment error during order %s fulfillment", order, ) # The response does not matter to CyberSource return Response(status=HTTP_200_OK)