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:
@@ -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))
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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/')
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user