def _display_steps(self, always_show_payment, already_verified, already_paid, course_mode): """Determine which steps to display to the user. Includes all steps by default, but removes steps if the user has already completed them. Arguments: always_show_payment (bool): If True, display the payment steps even if the user has already paid. already_verified (bool): Whether the user has submitted a verification request recently. already_paid (bool): Whether the user is enrolled in a paid course mode. Returns: list """ display_steps = self.ALL_STEPS remove_steps = set() if already_verified or not CourseMode.is_verified_mode(course_mode): remove_steps |= set(self.VERIFICATION_STEPS) if already_paid and not always_show_payment: remove_steps |= set(self.PAYMENT_STEPS) else: # The "make payment" step doubles as an intro step, # so if we're showing the payment step, hide the intro step. remove_steps |= set([self.INTRO_STEP]) return [ { 'name': step, 'title': six.text_type(self.STEP_TITLES[step]), } for step in display_steps if step not in remove_steps ]
def get(self, request, course_id, always_show_payment=False, current_step=None, message=FIRST_TIME_VERIFY_MSG): """ Render the payment and verification flow. Arguments: request (HttpRequest): The request object. course_id (unicode): The ID of the course the user is trying to enroll in. Keyword Arguments: always_show_payment (bool): If True, show the payment steps even if the user has already paid. This is useful for users returning to the flow after paying. current_step (string): The current step in the flow. message (string): The messaging to display. Returns: HttpResponse Raises: Http404: The course does not exist or does not have a verified mode. """ # Parse the course key # The URL regex should guarantee that the key format is valid. course_key = CourseKey.from_string(course_id) course = modulestore().get_course(course_key) # Verify that the course exists if course is None: log.warning(u"Could not find course with ID %s.", course_id) raise Http404 # Check whether the user has access to this course # based on country access rules. redirect_url = embargo_api.redirect_if_blocked( course_key, user=request.user, ip_address=get_ip(request), url=request.path) if redirect_url: return redirect(redirect_url) # If the verification deadline has passed # then show the user a message that he/she can't verify. # # We're making the assumptions (enforced in Django admin) that: # # 1) Only verified modes have verification deadlines. # # 2) If set, verification deadlines are always AFTER upgrade deadlines, because why would you # let someone upgrade into a verified track if they can't complete verification? # verification_deadline = VerificationDeadline.deadline_for_course( course.id) response = self._response_if_deadline_passed( course, self.VERIFICATION_DEADLINE, verification_deadline) if response is not None: log.info(u"Verification deadline for '%s' has passed.", course.id) return response # Retrieve the relevant course mode for the payment/verification flow. # # WARNING: this is technical debt! A much better way to do this would be to # separate out the payment flow and use the product SKU to figure out what # the user is trying to purchase. # # Nonetheless, for the time being we continue to make the really ugly assumption # that at some point there was a paid course mode we can query for the price. relevant_course_mode = self._get_paid_mode(course_key) # If we can find a relevant course mode, then log that we're entering the flow # Otherwise, this course does not support payment/verification, so respond with a 404. if relevant_course_mode is not None: if CourseMode.is_verified_mode(relevant_course_mode): log.info( u"Entering payment and verification flow for user '%s', course '%s', with current step '%s'.", request.user.id, course_id, current_step) else: log.info( u"Entering payment flow for user '%s', course '%s', with current step '%s'", request.user.id, course_id, current_step) else: # Otherwise, there has never been a verified/paid mode, # so return a page not found response. log.warning( u"No paid/verified course mode found for course '%s' for verification/payment flow request", course_id) raise Http404 # If the user is trying to *pay* and the upgrade deadline has passed, # then they shouldn't be able to enter the flow. # # NOTE: This should match the availability dates used by the E-Commerce service # to determine whether a user can purchase a product. The idea is that if the service # won't fulfill the order, we shouldn't even let the user get into the payment flow. # user_is_trying_to_pay = message in [ self.FIRST_TIME_VERIFY_MSG, self.UPGRADE_MSG ] if user_is_trying_to_pay: upgrade_deadline = relevant_course_mode.expiration_datetime response = self._response_if_deadline_passed( course, self.UPGRADE_DEADLINE, upgrade_deadline) if response is not None: log.info(u"Upgrade deadline for '%s' has passed.", course.id) return response # Check whether the user has verified, paid, and enrolled. # A user is considered "paid" if he or she has an enrollment # with a paid course mode (such as "verified"). # For this reason, every paid user is enrolled, but not # every enrolled user is paid. # If the course mode is not verified(i.e only paid) then already_verified is always True already_verified = (self._check_already_verified( request.user) if CourseMode.is_verified_mode(relevant_course_mode) else True) already_paid, is_enrolled = self._check_enrollment( request.user, course_key) # Redirect the user to a more appropriate page if the # messaging won't make sense based on the user's # enrollment / payment / verification status. sku_to_use = relevant_course_mode.sku purchase_workflow = request.GET.get('purchase_workflow', 'single') if purchase_workflow == 'bulk' and relevant_course_mode.bulk_sku: sku_to_use = relevant_course_mode.bulk_sku redirect_response = self._redirect_if_necessary( message, already_verified, already_paid, is_enrolled, course_key, user_is_trying_to_pay, request.user, sku_to_use) if redirect_response is not None: return redirect_response display_steps = self._display_steps(always_show_payment, already_verified, already_paid, relevant_course_mode) # Override the actual value if account activation has been disabled # Also see the reference to this parameter in context dictionary further down user_is_active = self._get_user_active_status(request.user) requirements = self._requirements(display_steps, user_is_active) if current_step is None: current_step = display_steps[0]['name'] # Allow the caller to skip the first page # This is useful if we want the user to be able to # use the "back" button to return to the previous step. # This parameter should only work for known skip-able steps if request.GET.get( 'skip-first-step') and current_step in self.SKIP_STEPS: display_step_names = [step['name'] for step in display_steps] current_step_idx = display_step_names.index(current_step) if (current_step_idx + 1) < len(display_steps): current_step = display_steps[current_step_idx + 1]['name'] courseware_url = "" if not course.start or course.start < now(): courseware_url = reverse( 'course_root', kwargs={'course_id': six.text_type(course_key)}) full_name = (request.user.profile.name if request.user.profile.name else "") # If the user set a contribution amount on another page, # use that amount to pre-fill the price selection form. contribution_amount = request.session.get( 'donation_for_course', {}).get(six.text_type(course_key), '') # Remember whether the user is upgrading # so we can fire an analytics event upon payment. request.session['attempting_upgrade'] = (message == self.UPGRADE_MSG) # Determine the photo verification status verification_good_until = self._verification_valid_until(request.user) # get available payment processors processors = ecommerce_api_client( request.user).payment.processors.get() # Render the top-level page context = { 'contribution_amount': contribution_amount, 'course': course, 'course_key': six.text_type(course_key), 'checkpoint_location': request.GET.get('checkpoint'), 'course_mode': relevant_course_mode, 'courseware_url': courseware_url, 'current_step': current_step, 'disable_courseware_js': True, 'display_steps': display_steps, 'is_active': json.dumps(user_is_active), 'user_email': request.user.email, 'message_key': message, 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'processors': processors, 'requirements': requirements, 'user_full_name': full_name, 'verification_deadline': verification_deadline or "", 'already_verified': already_verified, 'verification_good_until': verification_good_until, 'capture_sound': staticfiles_storage.url("audio/camera_capture.wav"), 'nav_hidden': True, 'is_ab_testing': 'begin-flow' in request.path, } return render_to_response("verify_student/pay_and_verify.html", context)