Remove obsolete shoppingcart refund page - DEPR-43 (#24063)

As part of the deprecated shoppingcart code removal, remove the old refund support page (which was only ever used for shoppingcart purchases, not ecommerce transactions).

I checked in New Relic for good measure, and this view hasn't been accessed in the past week.
This commit is contained in:
Jeremy Bowman
2020-05-27 15:26:08 -04:00
committed by GitHub
parent 436e3cf51d
commit 49fd99acfb
6 changed files with 0 additions and 368 deletions

View File

@@ -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))

View File

@@ -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",
]

View File

@@ -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"),

View File

@@ -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"),

View File

@@ -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/')

View File

@@ -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">
<title>
Manual Refund
</title>
</%block>
<%block name="headextra">
<style type="text/css">
.errorlist,.messages {
color: red;
}
.success {
color: green;
}
strong {
padding-right: 10px;
}
</style>
</%block>
<%block name="body">
<div class="content-wrapper" id="content">
<div class="container about">
<h1>${_("Manual Refund")}</h1>
% if messages:
<ul class="messages">
% for message in messages:
<li class="${message.tags if message.tags else ''}">${message}</li>
% endfor
</ul>
% endif
<form method="POST" id="refund_form">
<input type="hidden" id="csrf_token" name="csrfmiddlewaretoken" value="${csrf_token}" />
${form.as_p()}
<p>
<input type="button" value="Cancel" onclick="javascript:location=location"/> <input type="submit" value="${'Refund' if cert else 'Confirm'}" />
</p>
</form>
% if cert:
<section class="content-wrapper">
<h2>
${_("About to refund this order:")}
</h2>
<p>
<strong>${_("Order Id:")}</strong> ${cert.order.id}
</p>
<p>
<strong>${_("Enrollment:")}</strong> ${escape(enrollment.course_id)} ${enrollment.mode}
(${_("enrolled") if enrollment.is_active else _("unenrolled")})
</p>
<p>
<strong>${_("Cost:")}</strong> ${cert.unit_cost} ${cert.currency}
</p>
<p>
<strong>${_("CertificateItem Status:")}</strong> ${cert.status}
</p>
<p>
<strong>${_("Order Status:")}</strong> ${cert.order.status}
</p>
<p>
<strong>${_("Fulfilled Time:")}</strong> ${cert.fulfilled_time}
</p>
<p>
<strong>${_("Refund Request Time:")}</strong> ${cert.refund_requested_time}
</p>
</section>
% endif
</div>
</div>
</%block>