diff --git a/lms/djangoapps/support/tests/test_refund.py b/lms/djangoapps/support/tests/test_refund.py deleted file mode 100644 index df763cb3ea..0000000000 --- a/lms/djangoapps/support/tests/test_refund.py +++ /dev/null @@ -1,130 +0,0 @@ -""" -Tests for refunds on the support dashboard - -DEPRECATION WARNING: -This test suite is deliberately separate from the other view tests -so we can easily deprecate it once the transition from shoppingcart -to the E-Commerce service is complete. - -""" - - -import datetime - -import pytz -from django.test.client import Client -from mock import patch - -from course_modes.models import CourseMode -from shoppingcart.models import CertificateItem, Order -from student.models import CourseEnrollment -from student.roles import SupportStaffRole -from student.tests.factories import UserFactory -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase -from xmodule.modulestore.tests.factories import CourseFactory - - -class RefundTests(ModuleStoreTestCase): - """ - Tests for the manual refund page - """ - - def setUp(self): - super(RefundTests, self).setUp() - - self.course = CourseFactory.create( - org='testorg', number='run1', display_name='refundable course' - ) - self.course_id = self.course.location.course_key - self.client = Client() - self.admin = UserFactory.create( - username='test_admin', - email='test_admin+support@edx.org', - password='foo' - ) - SupportStaffRole().add_users(self.admin) - self.client.login(username=self.admin.username, password='foo') - - self.student = UserFactory.create( - username='student', - email='student+refund@edx.org' - ) - self.course_mode = CourseMode.objects.get_or_create( - course_id=self.course_id, - mode_slug='verified', - min_price=1 - )[0] - - self.order = None - self.form_pars = {'course_id': str(self.course_id), 'user': self.student.email} - - def tearDown(self): - self.course_mode.delete() - Order.objects.filter(user=self.student).delete() - super(RefundTests, self).tearDown() - - def _enroll(self, purchase=True): - CourseEnrollment.enroll(self.student, self.course_id, self.course_mode.mode_slug) - if purchase: - self.order = Order.get_cart_for_user(self.student) - CertificateItem.add_to_order(self.order, self.course_id, 1, self.course_mode.mode_slug) - self.order.purchase() - self.course_mode.expiration_datetime = datetime.datetime(1983, 4, 6, tzinfo=pytz.UTC) - self.course_mode.save() - - def test_support_access(self): - response = self.client.get('/support/') - self.assertTrue(response.status_code, 200) - self.assertContains(response, 'Manual Refund') - response = self.client.get('/support/refund/') - self.assertTrue(response.status_code, 200) - - # users without the permission can't access support - SupportStaffRole().remove_users(self.admin) - response = self.client.get('/support/') - self.assertTrue(response.status_code, 302) - - response = self.client.get('/support/refund/') - self.assertTrue(response.status_code, 302) - - def test_bad_courseid(self): - response = self.client.post('/support/refund/', {'course_id': 'foo', 'user': self.student.email}) - self.assertContains(response, 'Course id invalid') - - def test_bad_user(self): - response = self.client.post('/support/refund/', {'course_id': str(self.course_id), 'user': 'unknown@foo.com'}) - self.assertContains(response, 'User not found') - - @patch('student.models.CourseEnrollment.refund_cutoff_date') - def test_not_refundable(self, cutoff_date): - self._enroll() - self.course_mode.expiration_datetime = datetime.datetime(2033, 4, 6, tzinfo=pytz.UTC) - self.course_mode.save() - cutoff_date.return_value = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=1) - response = self.client.post('/support/refund/', self.form_pars) - self.assertContains(response, 'not past the refund window') - - def test_no_order(self): - self._enroll(purchase=False) - response = self.client.post('/support/refund/', self.form_pars) - self.assertContains(response, u'No order found for %s' % self.student.username) - - def test_valid_order(self): - self._enroll() - response = self.client.post('/support/refund/', self.form_pars) - self.assertContains(response, "About to refund this order") - self.assertContains(response, "enrolled") - self.assertContains(response, "CertificateItem Status") - - def test_do_refund(self): - self._enroll() - pars = self.form_pars - pars['confirmed'] = 'true' - response = self.client.post('/support/refund/', pars) - self.assertTrue(response.status_code, 302) - response = self.client.get(response.get('location')) - - self.assertContains(response, u"Unenrolled %s from" % self.student) - self.assertContains(response, "Refunded 1.00 for order id") - - self.assertFalse(CourseEnrollment.is_enrolled(self.student, self.course_id)) diff --git a/lms/djangoapps/support/tests/test_views.py b/lms/djangoapps/support/tests/test_views.py index a899bc572a..babfa2f94f 100644 --- a/lms/djangoapps/support/tests/test_views.py +++ b/lms/djangoapps/support/tests/test_views.py @@ -128,7 +128,6 @@ class SupportViewAccessTests(SupportViewTestCase): in itertools.product(( 'support:index', 'support:certificates', - 'support:refund', 'support:enrollment', 'support:enrollment_list', 'support:manage_user', @@ -156,7 +155,6 @@ class SupportViewAccessTests(SupportViewTestCase): @ddt.data( "support:index", "support:certificates", - "support:refund", "support:enrollment", "support:enrollment_list", "support:manage_user", @@ -185,7 +183,6 @@ class SupportViewIndexTests(SupportViewTestCase): EXPECTED_URL_NAMES = [ "support:certificates", - "support:refund", "support:link_program_enrollments", ] diff --git a/lms/djangoapps/support/urls.py b/lms/djangoapps/support/urls.py index 6dcbe5a063..5ebd6a7bcb 100644 --- a/lms/djangoapps/support/urls.py +++ b/lms/djangoapps/support/urls.py @@ -13,7 +13,6 @@ from support.views.feature_based_enrollments import FeatureBasedEnrollmentsSuppo from support.views.index import index from support.views.manage_user import ManageUserDetailView, ManageUserSupportView from support.views.program_enrollments import LinkProgramEnrollmentSupportView, ProgramEnrollmentsInspectorView -from support.views.refund import RefundSupportView COURSE_ENTITLEMENTS_VIEW = EntitlementSupportView.as_view() @@ -21,7 +20,6 @@ app_name = 'support' urlpatterns = [ url(r'^$', index, name="index"), url(r'^certificates/?$', CertificatesSupportView.as_view(), name="certificates"), - url(r'^refund/?$', RefundSupportView.as_view(), name="refund"), url(r'^enrollment/?$', EnrollmentSupportView.as_view(), name="enrollment"), url(r'^course_entitlement/?$', COURSE_ENTITLEMENTS_VIEW, name="course_entitlement"), url(r'^contact_us/?$', ContactUsView.as_view(), name="contact_us"), diff --git a/lms/djangoapps/support/views/index.py b/lms/djangoapps/support/views/index.py index 409177ac6d..47d925d4e4 100644 --- a/lms/djangoapps/support/views/index.py +++ b/lms/djangoapps/support/views/index.py @@ -15,14 +15,6 @@ SUPPORT_INDEX_URLS = [ "name": _("Certificates"), "description": _("View and regenerate certificates."), }, - - # DEPRECATION WARNING: We can remove this end-point - # once shoppingcart has been replaced by the E-Commerce service. - { - "url": reverse_lazy("support:refund"), - "name": _("Manual Refund"), - "description": _("Track refunds issued directly through CyberSource."), - }, { "url": reverse_lazy("support:enrollment"), "name": _("Enrollment"), diff --git a/lms/djangoapps/support/views/refund.py b/lms/djangoapps/support/views/refund.py deleted file mode 100644 index b7385414b9..0000000000 --- a/lms/djangoapps/support/views/refund.py +++ /dev/null @@ -1,142 +0,0 @@ -""" -Views for manual refunds in the student support UI. - -This interface is used by the support team to track refunds -entered manually in CyberSource (our payment gateway). - -DEPRECATION WARNING: -We are currently in the process of replacing lms/djangoapps/shoppingcart -with an E-Commerce service that supports automatic refunds. Once that -transition is complete, we can remove this view. - -""" - - -import logging - -from django import forms -from django.contrib import messages -from django.contrib.auth.models import User -from django.http import HttpResponseRedirect -from django.utils.decorators import method_decorator -from django.utils.translation import ugettext as _ -from django.views.generic.edit import FormView - -from openedx.core.lib.courses import clean_course_id -from student.models import CourseEnrollment -from support.decorators import require_support_permission - -log = logging.getLogger(__name__) - - -class RefundForm(forms.Form): - """ - Form for manual refunds - """ - user = forms.EmailField(label=_("Email Address"), required=True) - course_id = forms.CharField(label=_("Course ID"), required=True) - confirmed = forms.CharField(widget=forms.HiddenInput, required=False) - - def clean_user(self): - """ - validate user field - """ - user_email = self.cleaned_data['user'] - try: - user = User.objects.get(email=user_email) - except User.DoesNotExist: - raise forms.ValidationError(_("User not found")) - return user - - def clean_course_id(self): - """ - Validate the course id - """ - return clean_course_id(self) - - def clean(self): - """ - clean form - """ - user, course_id = self.cleaned_data.get('user'), self.cleaned_data.get('course_id') - if user and course_id: - self.cleaned_data['enrollment'] = enrollment = CourseEnrollment.get_or_create_enrollment(user, course_id) - if enrollment.refundable(): - msg = _(u"Course {course_id} not past the refund window.").format(course_id=course_id) - raise forms.ValidationError(msg) - try: - self.cleaned_data['cert'] = enrollment.certificateitem_set.filter( - mode='verified', - status='purchased' - )[0] - except IndexError: - msg = _(u"No order found for {user} in course {course_id}").format(user=user, course_id=course_id) - raise forms.ValidationError(msg) - return self.cleaned_data - - def is_valid(self): - """ - returns whether form is valid - """ - is_valid = super(RefundForm, self).is_valid() - if is_valid and self.cleaned_data.get('confirmed') != 'true': - # this is a two-step form: first look up the data, then issue the refund. - # first time through, set the hidden "confirmed" field to true and then redisplay the form - # second time through, do the unenrollment/refund. - data = dict(list(self.data.items())) - self.cleaned_data['confirmed'] = data['confirmed'] = 'true' - self.data = data - is_valid = False - return is_valid - - -class RefundSupportView(FormView): - """ - Refund form view - """ - template_name = 'support/refund.html' - form_class = RefundForm - success_url = '/support/' - - @method_decorator(require_support_permission) - def dispatch(self, *args, **kwargs): - return super(RefundSupportView, self).dispatch(*args, **kwargs) - - def get_context_data(self, **kwargs): - """ - extra context data to add to page - """ - kwargs = super(RefundSupportView, self).get_context_data(**kwargs) - form = getattr(kwargs['form'], 'cleaned_data', {}) - if form.get('confirmed') == 'true': - kwargs['cert'] = form.get('cert') - kwargs['enrollment'] = form.get('enrollment') - return kwargs - - def form_valid(self, form): - """ - unenrolls student, issues refund - """ - user = form.cleaned_data['user'] - course_id = form.cleaned_data['course_id'] - enrollment = form.cleaned_data['enrollment'] - cert = form.cleaned_data['cert'] - enrollment.can_refund = True - enrollment.update_enrollment(is_active=False) - - log.info(u"%s manually refunded %s %s", self.request.user, user, course_id) - messages.success( - self.request, - _(u"Unenrolled {user} from {course_id}").format( - user=user, - course_id=course_id - ) - ) - messages.success( - self.request, - _(u"Refunded {cost} for order id {order_id}").format( - cost=cert.unit_cost, - order_id=cert.order.id - ) - ) - return HttpResponseRedirect('/support/refund/') diff --git a/lms/templates/support/refund.html b/lms/templates/support/refund.html deleted file mode 100644 index d45dd149f6..0000000000 --- a/lms/templates/support/refund.html +++ /dev/null @@ -1,83 +0,0 @@ -## mako - -<%page expression_filter="h"/> - -<%! -from django.utils.translation import ugettext as _ -from django.utils.html import escape -%> - -<%inherit file="../main.html"/> - -<%block name="title"> - -Manual Refund - - -<%block name="headextra"> - - - - - -<%block name="body"> -
-
-

${_("Manual Refund")}

- % if messages: - - % endif - -
- - ${form.as_p()} -

- -

-
- % if cert: -
-

- ${_("About to refund this order:")} -

-

- ${_("Order Id:")} ${cert.order.id} -

-

- ${_("Enrollment:")} ${escape(enrollment.course_id)} ${enrollment.mode} - (${_("enrolled") if enrollment.is_active else _("unenrolled")}) -

-

- ${_("Cost:")} ${cert.unit_cost} ${cert.currency} -

-

- ${_("CertificateItem Status:")} ${cert.status} -

-

- ${_("Order Status:")} ${cert.order.status} -

-

- ${_("Fulfilled Time:")} ${cert.fulfilled_time} -

-

- ${_("Refund Request Time:")} ${cert.refund_requested_time} -

-
- % endif -
-
-