From 045e69f3c50f063b683a835594c7ec041f776f96 Mon Sep 17 00:00:00 2001
From: Julia Hansbrough <julia@edx.org>
Date: Thu, 17 Oct 2013 21:43:19 +0000
Subject: [PATCH] Can check verified-ness and expiration date

---
 common/djangoapps/course_modes/models.py      | 13 +++-
 .../course_modes/tests/factories.py           |  2 +
 .../course_modes/tests/test_models.py         | 11 +++-
 common/djangoapps/student/tests/tests.py      | 25 ++++++++
 common/djangoapps/student/views.py            | 61 ++++++++++++++-----
 lms/djangoapps/courseware/access.py           | 24 +++++++-
 .../courseware/tests/test_access.py           | 17 ++++++
 lms/djangoapps/shoppingcart/models.py         | 28 ++++++++-
 .../shoppingcart/tests/test_models.py         | 25 ++++++++
 lms/envs/dev.py                               |  3 +-
 lms/templates/dashboard.html                  | 22 +------
 .../dashboard/_dashboard_course_listing.html  | 33 +++++++++-
 12 files changed, 221 insertions(+), 43 deletions(-)

diff --git a/common/djangoapps/course_modes/models.py b/common/djangoapps/course_modes/models.py
index 8e475d1ec1c..b6849cd07bd 100644
--- a/common/djangoapps/course_modes/models.py
+++ b/common/djangoapps/course_modes/models.py
@@ -2,7 +2,7 @@
 Add and create new modes for running courses on this particular LMS
 """
 import pytz
-from datetime import datetime
+from datetime import datetime, date
 
 from django.db import models
 from collections import namedtuple
@@ -101,6 +101,17 @@ class CourseMode(models.Model):
         modes = cls.modes_for_course(course_id)
         return min(mode.min_price for mode in modes if mode.currency == currency)
 
+    @classmethod
+    def refund_expiration_date(cls, course_id, mode_slug):
+        """
+        Returns the expiration date for verified certificate refunds.  After this date, refunds are
+        no longer possible.  Note that this is currently set to be identical to the expiration date for
+        verified cert signups, but this could be changed in the future
+        """
+        print "TODO fix this"
+        return date(1990, 1, 1)
+        #return cls.mode_for_course(course_id,mode_slug).expiration_date
+
     def __unicode__(self):
         return u"{} : {}, min={}, prices={}".format(
             self.course_id, self.mode_slug, self.min_price, self.suggested_prices
diff --git a/common/djangoapps/course_modes/tests/factories.py b/common/djangoapps/course_modes/tests/factories.py
index 3e35b2f05c4..72f30bf3832 100644
--- a/common/djangoapps/course_modes/tests/factories.py
+++ b/common/djangoapps/course_modes/tests/factories.py
@@ -1,5 +1,6 @@
 from course_modes.models import CourseMode
 from factory import DjangoModelFactory
+import datetime
 
 # Factories don't have __init__ methods, and are self documenting
 # pylint: disable=W0232
@@ -11,3 +12,4 @@ class CourseModeFactory(DjangoModelFactory):
     mode_display_name = 'audit course'
     min_price = 0
     currency = 'usd'
+    expiration_date = datetime.date(1990, 1, 1)
diff --git a/common/djangoapps/course_modes/tests/test_models.py b/common/djangoapps/course_modes/tests/test_models.py
index 7a01c30dc49..36b1e72bdd0 100644
--- a/common/djangoapps/course_modes/tests/test_models.py
+++ b/common/djangoapps/course_modes/tests/test_models.py
@@ -5,7 +5,7 @@ when you run "manage.py test".
 Replace this with more appropriate tests for your application.
 """
 
-from datetime import datetime, timedelta
+from datetime import datetime, date, timedelta
 import pytz
 
 from django.test import TestCase
@@ -20,6 +20,7 @@ class CourseModeModelTest(TestCase):
     def setUp(self):
         self.course_id = 'TestCourse'
         CourseMode.objects.all().delete()
+        #todo use different default date
 
     def create_mode(self, mode_slug, mode_name, min_price=0, suggested_prices='', currency='usd'):
         """
@@ -31,7 +32,7 @@ class CourseModeModelTest(TestCase):
             mode_slug=mode_slug,
             min_price=min_price,
             suggested_prices=suggested_prices,
-            currency=currency
+            currency=currency,
         )
 
     def test_modes_for_course_empty(self):
@@ -112,3 +113,9 @@ class CourseModeModelTest(TestCase):
 
         modes = CourseMode.modes_for_course('second_test_course')
         self.assertEqual([CourseMode.DEFAULT_MODE], modes)
+
+    def test_refund_expiration_date(self):
+        self.create_mode('verified', 'Verified Certificate')
+        modes = CourseMode.modes_for_course(self.course_id)
+        mode = Mode(u'verified', u'Verified Certificate', 0, '', 'usd')
+        self.assertEqual(CourseMode.refund_expiration_date(self.course_id, 'verified'), date(1990, 1, 1))
diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py
index 315b6e92851..ed8237870ff 100644
--- a/common/djangoapps/student/tests/tests.py
+++ b/common/djangoapps/student/tests/tests.py
@@ -422,3 +422,28 @@ class PaidRegistrationTest(ModuleStoreTestCase):
         self.assertEqual(response.content, reverse('shoppingcart.views.show_cart'))
         self.assertTrue(shoppingcart.models.PaidCourseRegistration.contained_in_order(
             shoppingcart.models.Order.get_cart_for_user(self.user), self.course.id))
+
+
+@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
+class CertificateItemTest(ModuleStoreTestCase):
+    """
+    Tests for paid certificate functionality (verified student), involves shoppingcart
+    """
+    # test data
+    COURSE_SLUG = "100"
+    COURSE_NAME = "test_course"
+    COURSE_ORG = "EDX"
+
+    def setUp(self):
+        # Create course
+        self.req_factory = RequestFactory()
+        self.course = CourseFactory.create(org=self.COURSE_ORG, display_name=self.COURSE_NAME, number=self.COURSE_SLUG)
+        self.assertIsNotNone(self.course)
+        self.user = User.objects.create(username="test", email="test@test.org")
+
+    def test_unenroll_and_refund(self):
+        request = self.req_factory.post(reverse('change_enrollment'), {'course_id': self.course.id, 'enrollment_action': 'unenroll'})
+        request.user = self.user
+        response = change_enrollment(request)
+        self.assertEqual(response.status_code, 200)
+        # add more later; see if this even works
diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py
index 75c9b758213..6b30927c462 100644
--- a/common/djangoapps/student/views.py
+++ b/common/djangoapps/student/views.py
@@ -2,6 +2,7 @@
 Student Views
 """
 import datetime
+from datetime import date
 import json
 import logging
 import random
@@ -65,6 +66,7 @@ import external_auth.views
 
 from bulk_email.models import Optout, CourseAuthorization
 import shoppingcart
+from shoppingcart.models import (Order, OrderItem, CertificateItem)
 
 import track.views
 
@@ -300,6 +302,7 @@ def dashboard(request):
     # exist (because the course IDs have changed). Still, we don't delete those
     # enrollments, because it could have been a data push snafu.
     courses = []
+    refund_status = []
     for enrollment in CourseEnrollment.enrollments_for_user(user):
         try:
             courses.append((course_from_id(enrollment.course_id), enrollment))
@@ -335,15 +338,19 @@ def dashboard(request):
             CourseAuthorization.instructor_email_enabled(course.id)
         )
     )
+
     # Verification Attempts
     verification_status, verification_msg = SoftwareSecurePhotoVerification.user_status(user)
+
+    show_refund_option_for = frozenset(course.id for course, _enrollment in courses
+        if (has_access(request.user, course, 'refund') and (_enrollment.mode == "verified")))
+
     # get info w.r.t ExternalAuthMap
     external_auth_map = None
     try:
         external_auth_map = ExternalAuthMap.objects.get(user=user)
     except ExternalAuthMap.DoesNotExist:
         pass
-
     context = {'courses': courses,
                'course_optouts': course_optouts,
                'message': message,
@@ -356,6 +363,7 @@ def dashboard(request):
                'show_email_settings_for': show_email_settings_for,
                'verification_status': verification_status,
                'verification_msg': verification_msg,
+               'show_refund_option_for': show_refund_option_for,
                }
 
     return render_to_response('dashboard.html', context)
@@ -424,6 +432,8 @@ def change_enrollment(request):
                         .format(user.username, course_id))
             return HttpResponseBadRequest(_("Course id is invalid"))
 
+        course = course_from_id(course_id)
+
         if not has_access(user, course, 'enroll'):
             return HttpResponseBadRequest(_("Enrollment is closed"))
 
@@ -464,19 +474,42 @@ def change_enrollment(request):
 
     elif action == "unenroll":
         try:
-            CourseEnrollment.unenroll(user, course_id)
-
-            org, course_num, run = course_id.split("/")
-            dog_stats_api.increment(
-                "common.student.unenrollment",
-                tags=["org:{0}".format(org),
-                      "course:{0}".format(course_num),
-                      "run:{0}".format(run)]
-            )
+            course = course_from_id(course_id)
+        except ItemNotFoundError:
+            log.warning("User {0} tried to unenroll from non-existent course {1}"
+                        .format(user.username, course_id))
+            return HttpResponseBadRequest(_("Course id is invalid"))
+
+        course = course_from_id(course_id)
+        verified = CourseEnrollment.enrollment_mode_for_user(user, course_id)
+        # did they sign up for verified certs?
+        if(verified):
+
+            # If the user is allowed a refund, do so
+            if has_access(user, course, 'refund'):
+                subject = _("[Refund] User-Requested Refund")
+                # todo: make this reference templates/student/refund_email.html
+                message = "Important info here."
+                to_email = [settings.PAYMENT_SUPPORT_EMAIL]
+                from_email = "support@edx.org"
+                try:
+                    send_mail(subject, message, from_email, to_email, fail_silently=False)
+                except:
+                    log.warning('Unable to send reimbursement request to billing', exc_info=True)
+                    js['value'] = _('Could not send reimbursement request.')
+                    return HttpResponse(json.dumps(js))
+                # email has been sent, let's deal with the order now
+                CertificateItem.refund_cert(user, course_id)
+        CourseEnrollment.unenroll(user, course_id)
 
-            return HttpResponse()
-        except CourseEnrollment.DoesNotExist:
-            return HttpResponseBadRequest(_("You are not enrolled in this course"))
+        org, course_num, run = course_id.split("/")
+        dog_stats_api.increment(
+            "common.student.unenrollment",
+            tags=["org:{0}".format(org),
+                  "course:{0}".format(course_num),
+                  "run:{0}".format(run)]
+        )
+        return HttpResponse()
     else:
         return HttpResponseBadRequest(_("Enrollment action is invalid"))
 
@@ -891,7 +924,7 @@ def create_account(request, post_override=None):
     subject = ''.join(subject.splitlines())
     message = render_to_string('emails/activation_email.txt', d)
 
-    # dont send email if we are doing load testing or random user generation for some reason
+    # don't send email if we are doing load testing or random user generation for some reason
     if not (settings.MITX_FEATURES.get('AUTOMATIC_AUTH_FOR_TESTING')):
         try:
             if settings.MITX_FEATURES.get('REROUTE_ACTIVATION_EMAIL'):
diff --git a/lms/djangoapps/courseware/access.py b/lms/djangoapps/courseware/access.py
index 7836ab8bbce..e8b527766d0 100644
--- a/lms/djangoapps/courseware/access.py
+++ b/lms/djangoapps/courseware/access.py
@@ -2,7 +2,7 @@
 Ideally, it will be the only place that needs to know about any special settings
 like DISABLE_START_DATES"""
 import logging
-from datetime import datetime, timedelta
+from datetime import datetime, timedelta, date
 from functools import partial
 
 from django.conf import settings
@@ -202,11 +202,33 @@ def _has_access_course_desc(user, course, action):
 
         return can_enroll() or can_load()
 
+    def can_refund():
+        """
+        For paid/verified certificates, students may receive a refund IFF the deadline
+        for refunds has not yet passed.  Note that this function *only* checks whether
+        or not that deadline has passed; checking whether the student actually *purchased*
+        a paid/verified certificate must be done elsewhere.
+        """
+        now = datetime.now(UTC())
+        course_start = course.enrollment_start
+        # If there *is* no start date, user can be refunded
+        if course_start is None:
+            return True
+        # Presently, refunds are only allowed up to two weeks after the course
+        # start date.
+        grace_period = timedelta(days=14)
+        refund_end = course_start + grace_period
+        if (now.date() <= refund_end.date()):
+            return True
+        return False
+
+
     checkers = {
         'load': can_load,
         'load_forum': can_load_forum,
         'enroll': can_enroll,
         'see_exists': see_exists,
+        'refund': can_refund,
         'staff': lambda: _has_staff_access_to_descriptor(user, course),
         'instructor': lambda: _has_instructor_access_to_descriptor(user, course),
         }
diff --git a/lms/djangoapps/courseware/tests/test_access.py b/lms/djangoapps/courseware/tests/test_access.py
index a1cd12ae241..22dd7e25fb6 100644
--- a/lms/djangoapps/courseware/tests/test_access.py
+++ b/lms/djangoapps/courseware/tests/test_access.py
@@ -106,3 +106,20 @@ class AccessTestCase(TestCase):
 
         # TODO:
         # Non-staff cannot enroll outside the open enrollment period if not specifically allowed
+
+    def test__has_access_refund(self):
+        u = Mock()
+        today = datetime.datetime.now(UTC())
+        grace_period = datetime.timedelta(days=14)
+        one_day_extra = datetime.timedelta(days=1)
+
+        # User is allowed to receive refund if it is within two weeks of course start date
+        c = Mock(enrollment_start=(today-one_day_extra), id='edX/tests/Whenever')
+        self.assertTrue(access._has_access_course_desc(u, c, 'refund'))
+
+        c = Mock(enrollment_start=(today-grace_period), id='edX/test/Whenever')
+        self.assertTrue(access._has_access_course_desc(u, c, 'refund'))
+
+        # After two weeks, user may no longer receive a refund
+        c = Mock(enrollment_start=(today-grace_period-one_day_extra), id='edX/test/Whenever')
+        self.assertFalse(access._has_access_course_desc(u, c, 'refund'))
diff --git a/lms/djangoapps/shoppingcart/models.py b/lms/djangoapps/shoppingcart/models.py
index 9ae11f6554a..761290d5574 100644
--- a/lms/djangoapps/shoppingcart/models.py
+++ b/lms/djangoapps/shoppingcart/models.py
@@ -9,7 +9,7 @@ from boto.exception import BotoServerError  # this is a super-class of SESError
 
 from django.db import models
 from django.conf import settings
-from django.core.exceptions import ObjectDoesNotExist
+from django.core.exceptions import (ObjectDoesNotExist, MultipleObjectsReturned)
 from django.core.mail import send_mail
 from django.contrib.auth.models import User
 from django.utils.translation import ugettext as _
@@ -22,7 +22,6 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
 
 from course_modes.models import CourseMode
 from mitxmako.shortcuts import render_to_string
-from student.views import course_from_id
 from student.models import CourseEnrollment
 from verify_student.models import SoftwareSecurePhotoVerification
 
@@ -34,13 +33,19 @@ log = logging.getLogger("shoppingcart")
 ORDER_STATUSES = (
     ('cart', 'cart'),
     ('purchased', 'purchased'),
-    ('refunded', 'refunded'),  # Not used for now
+    ('refunded', 'refunded'),
 )
 
 # we need a tuple to represent the primary key of various OrderItem subclasses
 OrderItemSubclassPK = namedtuple('OrderItemSubclassPK', ['cls', 'pk'])  # pylint: disable=C0103
 
 
+def course_from_id(course_id):
+    """Return the CourseDescriptor corresponding to this course_id"""
+    course_loc = CourseDescriptor.id_to_location(course_id)
+    return modulestore().get_instance(course_id, course_loc)
+
+
 class Order(models.Model):
     """
     This is the model for an order.  Before purchase, an Order and its related OrderItems are used
@@ -398,6 +403,23 @@ class CertificateItem(OrderItem):
     course_enrollment = models.ForeignKey(CourseEnrollment)
     mode = models.SlugField()
 
+    @classmethod
+    def refund_cert(cls, target_user, target_course_id):
+        try:
+            target_cert = CertificateItem.objects.get(course_id=target_course_id, user_id=target_user, status='purchased', mode='verified')
+            target_cert.status = 'refunded'
+            # todo return success
+            return target_cert
+        except MultipleObjectsReturned:
+            # this seems like a thing that shouldn't happen
+            log.exception("Multiple entries for single verified cert found")
+            # but we can recover; select one item and refund it
+            # todo
+        except ObjectDoesNotExist:
+            # todo log properly
+            log.exception("No certificate found")
+            # handle the exception
+
     @classmethod
     @transaction.commit_on_success
     def add_to_order(cls, order, course_id, cost, mode, currency='usd'):
diff --git a/lms/djangoapps/shoppingcart/tests/test_models.py b/lms/djangoapps/shoppingcart/tests/test_models.py
index 1224bb5093b..14d136853aa 100644
--- a/lms/djangoapps/shoppingcart/tests/test_models.py
+++ b/lms/djangoapps/shoppingcart/tests/test_models.py
@@ -20,6 +20,7 @@ from student.tests.factories import UserFactory
 from student.models import CourseEnrollment
 from course_modes.models import CourseMode
 from shoppingcart.exceptions import PurchasedCallbackException
+from django.core.exceptions import (ObjectDoesNotExist, MultipleObjectsReturned)
 
 
 @override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
@@ -360,3 +361,27 @@ class CertificateItemTest(ModuleStoreTestCase):
         cert_item = CertificateItem.add_to_order(cart, self.course_id, self.cost, 'honor')
         self.assertEquals(cert_item.single_item_receipt_template,
                           'shoppingcart/receipt.html')
+
+    def test_refund_cert_single_cert(self):
+        # enroll and buy; dup from test_existing_enrollment
+        CourseEnrollment.enroll(self.user, self.course_id)
+        cart = Order.get_cart_for_user(user=self.user)
+        CertificateItem.add_to_order(cart, self.course_id, self.cost, 'verified')
+        cart.purchase()
+        enrollment = CourseEnrollment.objects.get(user=self.user, course_id=self.course_id)
+        # now that it's there, let's try refunding it
+        order = CertificateItem.refund_cert(target_user=self.user, target_course_id=self.course_id)
+        self.assertEquals(order.status, 'refunded')
+
+    def test_refund_cert_no_cert_exists(self):
+        order = CertificateItem.refund_cert(target_user=self.user, target_course_id=self.course_id)
+        self.assertRaises(ObjectDoesNotExist)
+
+    def test_refund_cert_duplicate_certs_exist(self):
+        for i in range(0, 2):
+            CourseEnrollment.enroll(self.user, self.course_id)
+            cart = Order.get_cart_for_user(user=self.user)
+            CertificateItem.add_to_order(cart, self.course_id, self.cost, 'verified')
+            cart.purchase()
+            enrollment = CourseEnrollment.objects.get(user=self.user, course_id=self.course_id)
+        self.assertRaises(MultipleObjectsReturned)
diff --git a/lms/envs/dev.py b/lms/envs/dev.py
index e1eadf1331d..94362e055fd 100644
--- a/lms/envs/dev.py
+++ b/lms/envs/dev.py
@@ -20,6 +20,7 @@ USE_I18N = True
 TEMPLATE_DEBUG = True
 
 
+MITX_FEATURES['ENABLE_PAYMENT_FAKE'] = True
 MITX_FEATURES['DISABLE_START_DATES'] = False
 MITX_FEATURES['ENABLE_SQL_TRACKING_LOGS'] = True
 MITX_FEATURES['SUBDOMAIN_COURSE_LISTINGS'] = False  # Enable to test subdomains--otherwise, want all courses to show up
@@ -269,7 +270,7 @@ if SEGMENT_IO_LMS_KEY:
 CC_PROCESSOR['CyberSource']['SHARED_SECRET'] = os.environ.get('CYBERSOURCE_SHARED_SECRET', '')
 CC_PROCESSOR['CyberSource']['MERCHANT_ID'] = os.environ.get('CYBERSOURCE_MERCHANT_ID', '')
 CC_PROCESSOR['CyberSource']['SERIAL_NUMBER'] = os.environ.get('CYBERSOURCE_SERIAL_NUMBER', '')
-CC_PROCESSOR['CyberSource']['PURCHASE_ENDPOINT'] = os.environ.get('CYBERSOURCE_PURCHASE_ENDPOINT', '')
+CC_PROCESSOR['CyberSource']['PURCHASE_ENDPOINT'] = '/shoppingcart/payment_fake/'
 
 
 ########################## USER API ########################
diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html
index eb657d13e41..3a59ed1ba42 100644
--- a/lms/templates/dashboard.html
+++ b/lms/templates/dashboard.html
@@ -190,7 +190,8 @@
             <% cert_status = cert_statuses.get(course.id) %>
             <% show_email_settings = (course.id in show_email_settings_for) %>
             <% course_mode_info = all_course_modes.get(course.id) %>
-            <%include file='dashboard/_dashboard_course_listing.html' args="course=course, enrollment=enrollment, show_courseware_link=show_courseware_link, cert_status=cert_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info" />
+            <% show_refund_option = (course.id in show_refund_option_for) %>
+            <%include file='dashboard/dashboard_course_listing.html' args="course=course, enrollment=enrollment, show_courseware_link=show_courseware_link, cert_status=cert_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, show_refund_option = show_refund_option" />
       % endfor
 
       </ul>
@@ -244,26 +245,7 @@
   </div>
 </section>
 
-<section id="unenroll-modal" class="modal unenroll-modal" aria-hidden="true">
-  <div class="inner-wrapper" role="alertdialog" aria-labelledy="unenrollment-modal-title">
-    <button class="close-modal">&#10005; <span class="sr">${_('Close Modal')}</span></button>
-
-    <header>
-      <h2 id="unenrollment-modal-title">${_('Are you sure you want to unregister from {course_number}?').format(course_number='<span id="unenroll_course_number"></span>')}<span class="sr">, ${_("modal open")}</span></h2>
-      <hr/>
-    </header>
-
-    <div id="unenroll_error" class="modal-form-error"></div>
 
-    <form id="unenroll_form" method="post" data-remote="true" action="${reverse('change_enrollment')}">
-      <input name="course_id" id="unenroll_course_id" type="hidden" />
-      <input name="enrollment_action" type="hidden" value="unenroll" />
-      <div class="submit">
-        <input name="submit" type="submit" value="${_('Unregister')}" />
-      </div>
-    </form>
-  </div>
-</section>
 
 <section id="password_reset_complete" class="modal" aria-hidden="true">
   <div class="inner-wrapper" role="dialog" aria-labelledby="password-reset-email">
diff --git a/lms/templates/dashboard/_dashboard_course_listing.html b/lms/templates/dashboard/_dashboard_course_listing.html
index c43e7e79de0..40dae64cbac 100644
--- a/lms/templates/dashboard/_dashboard_course_listing.html
+++ b/lms/templates/dashboard/_dashboard_course_listing.html
@@ -1,4 +1,4 @@
-<%page args="course, enrollment, show_courseware_link, cert_status, show_email_settings, course_mode_info" />
+<%page args="course, enrollment, show_courseware_link, cert_status, show_email_settings, course_mode_info, show_refund_option" />
 
 <%! from django.utils.translation import ugettext as _ %>
 <%!
@@ -143,10 +143,41 @@
 
       <a href="#unenroll-modal" class="unenroll" rel="leanModal" data-course-id="${course.id}" data-course-number="${course.number}">${_('Unregister')}</a>
 
+
+
 % if show_email_settings:
         <a href="#email-settings-modal" class="email-settings" rel="leanModal" data-course-id="${course.id}" data-course-number="${course.number}" data-optout="${course.id in course_optouts}">${_('Email Settings')}</a>
 % endif
 
+
+
   </section>
 </article>
 </li>
+
+<section id="unenroll-modal" class="modal unenroll-modal" aria-hidden="true">
+  <div class="inner-wrapper" role="alertdialog" aria-labelledy="unenrollment-modal-title">
+    <button class="close-modal">&#10005; <span class="sr">${_('Close Modal')}</span></button>
+
+    <header>
+      % if enrollment.mode != "verified":
+      <h2 id="unenrollment-modal-title">${_('Are you sure you want to unregister from {course_number}?').format(course_number='<span id="unenroll_course_number"></span>')}<span class="sr">, ${_("modal open")}</span></h2>
+      % elif show_refund_option:
+      <h2 id="unenrollment-modal-title">${_('Are you sure you want to unregister from {course_number}?  You will be refunded for the amount paid for the verified certificate.').format(course_number='<span id="unenroll_course_number"></span>')}<span class="sr">, ${_("modal open")}</span></h2>
+      % else:
+      <h2 id="unenrollment-modal-title">${_('Are you sure you want to unregister from {course_number}?  The deadline for verified certificate refunds has passed, so you will not receive any money back.').format(course_number='<span id="unenroll_course_number"></span>')}<span class="sr">, ${_("modal open")}</span></h2>
+      % endif
+      <hr/>
+    </header>
+
+    <div id="unenroll_error" class="modal-form-error"></div>
+
+    <form id="unenroll_form" method="post" data-remote="true" action="${reverse('change_enrollment')}">
+      <input name="course_id" id="unenroll_course_id" type="hidden" />
+      <input name="enrollment_action" type="hidden" value="unenroll" />
+      <div class="submit">
+        <input name="submit" type="submit" value="${_('Unregister')}" />
+      </div>
+    </form>
+  </div>
+</section>
-- 
GitLab