refund.py 5.13 KB
Newer Older
1
"""
2 3 4 5 6 7 8 9 10 11
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.

12 13 14 15 16 17 18 19 20
"""
import logging

from django.contrib.auth.models import User
from django.views.generic.edit import FormView
from django.utils.translation import ugettext as _
from django.http import HttpResponseRedirect
from django.contrib import messages
from django import forms
21 22
from django.utils.decorators import method_decorator

23 24 25 26
from student.models import CourseEnrollment
from opaque_keys.edx.keys import CourseKey
from opaque_keys import InvalidKeyError
from opaque_keys.edx.locations import SlashSeparatedCourseKey
27
from support.decorators import require_support_permission
28 29 30 31

log = logging.getLogger(__name__)


32
class RefundForm(forms.Form):
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
    """
    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 course id field
        """
        course_id = self.cleaned_data['course_id']
        try:
            course_key = CourseKey.from_string(course_id)
        except InvalidKeyError:
            try:
                course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
            except InvalidKeyError:
                raise forms.ValidationError(_("Invalid course id"))
        return course_key

    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():
73 74
                msg = _("Course {course_id} not past the refund window.").format(course_id=course_id)
                raise forms.ValidationError(msg)
75
            try:
76 77 78 79
                self.cleaned_data['cert'] = enrollment.certificateitem_set.filter(
                    mode='verified',
                    status='purchased'
                )[0]
80
            except IndexError:
81 82
                msg = _("No order found for {user} in course {course_id}").format(user=user, course_id=course_id)
                raise forms.ValidationError(msg)
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
        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(self.data.items())
            self.cleaned_data['confirmed'] = data['confirmed'] = 'true'
            self.data = data
            is_valid = False
        return is_valid


101
class RefundSupportView(FormView):
102 103 104
    """
    Refund form view
    """
105
    template_name = 'support/refund.html'
106 107 108
    form_class = RefundForm
    success_url = '/support/'

109 110 111 112
    @method_decorator(require_support_permission)
    def dispatch(self, *args, **kwargs):
        return super(RefundSupportView, self).dispatch(*args, **kwargs)

113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
    def get_context_data(self, **kwargs):
        """
        extra context data to add to page
        """
        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)
135 136 137 138 139 140 141 142 143 144 145 146 147 148
        messages.success(
            self.request,
            _("Unenrolled {user} from {course_id}").format(
                user=user,
                course_id=course_id
            )
        )
        messages.success(
            self.request,
            _("Refunded {cost} for order id {order_id}").format(
                cost=cert.unit_cost,
                order_id=cert.order.id
            )
        )
149
        return HttpResponseRedirect('/support/refund/')