def test_start_purchase(self): # Start the purchase, which will mark the cart as "paying" cart = Order.get_cart_for_user(user=self.user) CertificateItem.add_to_order(cart, self.course_key, self.cost, 'honor', currency='usd') cart.start_purchase() self.assertEqual(cart.status, 'paying') for item in cart.orderitem_set.all(): self.assertEqual(item.status, 'paying') # Starting the purchase should be idempotent cart.start_purchase() self.assertEqual(cart.status, 'paying') for item in cart.orderitem_set.all(): self.assertEqual(item.status, 'paying') # If we retrieve the cart for the user, we should get a different order next_cart = Order.get_cart_for_user(user=self.user) self.assertNotEqual(cart, next_cart) self.assertEqual(next_cart.status, 'cart') # Complete the first purchase cart.purchase() self.assertEqual(cart.status, 'purchased') for item in cart.orderitem_set.all(): self.assertEqual(item.status, 'purchased') # Starting the purchase again should be a no-op cart.start_purchase() self.assertEqual(cart.status, 'purchased') for item in cart.orderitem_set.all(): self.assertEqual(item.status, 'purchased')
def test_get_cart_for_user(self): # create a cart cart = Order.get_cart_for_user(user=self.user) # add something to it CertificateItem.add_to_order(cart, self.course_id, self.cost, 'verified') # should return the same cart cart2 = Order.get_cart_for_user(user=self.user) self.assertEquals(cart2.orderitem_set.count(), 1)
def test_billing_info_storage_off(self): cart = Order.get_cart_for_user(self.user) cart = Order.get_cart_for_user(self.user) self.purchase_with_data(cart) self.assertNotEqual(cart.bill_to_first, '') self.assertNotEqual(cart.bill_to_last, '') self.assertNotEqual(cart.bill_to_city, '') self.assertNotEqual(cart.bill_to_state, '') self.assertNotEqual(cart.bill_to_country, '') self.assertNotEqual(cart.bill_to_postalcode, '') # things we expect to be missing when the feature is off self.assertEqual(cart.bill_to_street1, '') self.assertEqual(cart.bill_to_street2, '') self.assertEqual(cart.bill_to_ccnum, '') self.assertEqual(cart.bill_to_cardtype, '')
def setUp(self): patcher = patch('student.models.tracker') self.mock_tracker = patcher.start() self.user = UserFactory.create() self.user.set_password('password') self.user.save() self.instructor = AdminFactory.create() self.cost = 40 self.coupon_code = 'abcde' self.reg_code = 'qwerty' self.percentage_discount = 10 self.course = CourseFactory.create(org='MITx', number='999', display_name='Robot Super Course') self.course_key = self.course.id self.course_mode = CourseMode(course_id=self.course_key, mode_slug="honor", mode_display_name="honor cert", min_price=self.cost) self.course_mode.save() #Saving another testing course mode self.testing_cost = 20 self.testing_course = CourseFactory.create(org='edX', number='888', display_name='Testing Super Course') self.testing_course_mode = CourseMode(course_id=self.testing_course.id, mode_slug="honor", mode_display_name="testing honor cert", min_price=self.testing_cost) self.testing_course_mode.save() verified_course = CourseFactory.create(org='org', number='test', display_name='Test Course') self.verified_course_key = verified_course.id self.cart = Order.get_cart_for_user(self.user) self.addCleanup(patcher.stop)
def setUp(self): patcher = patch("student.models.tracker") self.mock_tracker = patcher.start() self.user = UserFactory.create() self.user.set_password("password") self.user.save() self.instructor = AdminFactory.create() self.cost = 40 self.coupon_code = "abcde" self.reg_code = "qwerty" self.percentage_discount = 10 self.course = CourseFactory.create(org="MITx", number="999", display_name="Robot Super Course") self.course_key = self.course.id self.course_mode = CourseMode( course_id=self.course_key, mode_slug="honor", mode_display_name="honor cert", min_price=self.cost ) self.course_mode.save() # Saving another testing course mode self.testing_cost = 20 self.testing_course = CourseFactory.create(org="edX", number="888", display_name="Testing Super Course") self.testing_course_mode = CourseMode( course_id=self.testing_course.id, mode_slug="honor", mode_display_name="testing honor cert", min_price=self.testing_cost, ) self.testing_course_mode.save() verified_course = CourseFactory.create(org="org", number="test", display_name="Test Course") self.verified_course_key = verified_course.id self.cart = Order.get_cart_for_user(self.user) self.addCleanup(patcher.stop)
def test_student_used_enrollment_code_for_course_enrollment(self): """ test to check the user enrollment source and payment status in the enrollment detailed report """ student = UserFactory() self.client.login(username=student.username, password='******') student_cart = Order.get_cart_for_user(student) paid_course_reg_item = PaidCourseRegistration.add_to_order(student_cart, self.course.id) # update the quantity of the cart item paid_course_reg_item resp = self.client.post(reverse('shoppingcart.views.update_user_cart'), {'ItemId': paid_course_reg_item.id, 'qty': '4'}) self.assertEqual(resp.status_code, 200) student_cart.purchase() course_reg_codes = CourseRegistrationCode.objects.filter(order=student_cart) redeem_url = reverse('register_code_redemption', args=[course_reg_codes[0].code]) response = self.client.get(redeem_url) self.assertEquals(response.status_code, 200) # check button text self.assertTrue('Activate Course Enrollment' in response.content) response = self.client.post(redeem_url) self.assertEquals(response.status_code, 200) task_input = {'features': []} with patch('instructor_task.tasks_helper._get_current_task'): result = upload_enrollment_report(None, None, self.course.id, task_input, 'generating_enrollment_report') self.assertDictContainsSubset({'attempted': 1, 'succeeded': 1, 'failed': 0}, result) self._verify_cell_data_in_csv(student.username, 'Enrollment Source', 'Used Registration Code') self._verify_cell_data_in_csv(student.username, 'Payment Status', 'purchased')
def test_purchase_item_email_boto_failure(self, error_logger): cart = Order.get_cart_for_user(user=self.user) CertificateItem.add_to_order(cart, self.course_key, self.cost, 'honor') with patch.object(EmailMessage, 'send') as mock_send: mock_send.side_effect = BotoServerError("status", "reason") cart.purchase() self.assertTrue(error_logger.called)
def test_cart_clear(self): cart = Order.get_cart_for_user(user=self.user) CertificateItem.add_to_order(cart, self.course_id, self.cost, 'verified') CertificateItem.add_to_order(cart, 'test/course1', self.cost, 'verified') self.assertEquals(cart.orderitem_set.count(), 2) cart.clear() self.assertEquals(cart.orderitem_set.count(), 0)
def test_single_item_template(self): cart = Order.get_cart_for_user(user=self.user) cert_item = CertificateItem.add_to_order(cart, self.course_key, self.cost, 'verified') self.assertEquals(cert_item.single_item_receipt_template, 'shoppingcart/receipt.html') cert_item = CertificateItem.add_to_order(cart, self.course_key, self.cost, 'honor') self.assertEquals(cert_item.single_item_receipt_template, 'shoppingcart/receipt.html')
def setUp(self): """ Create a user and an order. """ self.user = UserFactory() self.order = Order.get_cart_for_user(self.user) self.order_item = OrderItem.objects.create( order=self.order, user=self.user, unit_cost=self.COST, line_cost=self.COST )
def test_purchase_twice(self): cart = Order.get_cart_for_user(self.user) CertificateItem.add_to_order(cart, self.course_id, self.cost, 'honor') # purchase the cart more than once cart.purchase() cart.purchase() self.assertEquals(len(mail.outbox), 1)
def test_refund_cert_callback_before_expiration(self): # If the expiration date has not yet passed on a verified mode, the user can be refunded many_days = datetime.timedelta(days=60) course = CourseFactory.create(org="refund_before_expiration", number="test", display_name="one") course_key = course.id course_mode = CourseMode( course_id=course_key, mode_slug="verified", mode_display_name="verified cert", min_price=self.cost, expiration_datetime=(datetime.datetime.now(pytz.utc) + many_days), ) course_mode.save() CourseEnrollment.enroll(self.user, course_key, "verified") cart = Order.get_cart_for_user(user=self.user) CertificateItem.add_to_order(cart, course_key, self.cost, "verified") cart.purchase() CourseEnrollment.unenroll(self.user, course_key) target_certs = CertificateItem.objects.filter( course_id=course_key, user_id=self.user, status="refunded", mode="verified" ) self.assertTrue(target_certs[0]) self.assertTrue(target_certs[0].refund_requested_time) self.assertEquals(target_certs[0].order.status, "refunded")
def test_refund_cert_callback_before_expiration_email(self): """ Test that refund emails are being sent correctly. """ course = CourseFactory.create() course_key = course.id many_days = datetime.timedelta(days=60) course_mode = CourseMode(course_id=course_key, mode_slug="verified", mode_display_name="verified cert", min_price=self.cost, expiration_datetime=datetime.datetime.now(pytz.utc) + many_days) course_mode.save() CourseEnrollment.enroll(self.user, course_key, 'verified') cart = Order.get_cart_for_user(user=self.user) CertificateItem.add_to_order(cart, course_key, self.cost, 'verified') cart.purchase() mail.outbox = [] with patch('shoppingcart.models.log.error') as mock_error_logger: CourseEnrollment.unenroll(self.user, course_key) self.assertFalse(mock_error_logger.called) self.assertEquals(len(mail.outbox), 1) self.assertEquals('[Refund] User-Requested Refund', mail.outbox[0].subject) self.assertEquals(settings.PAYMENT_SUPPORT_EMAIL, mail.outbox[0].from_email) self.assertIn('has requested a refund on Order', mail.outbox[0].body)
def test_orderItem_purchased_callback(self): """ This tests that calling purchased_callback on the base OrderItem class raises NotImplementedError """ item = OrderItem(user=self.user, order=Order.get_cart_for_user(self.user)) with self.assertRaises(NotImplementedError): item.purchased_callback()
def test_refund_cert_callback_before_expiration(self): # If the expiration date has not yet passed on a verified mode, the user can be refunded many_days = datetime.timedelta(days=60) course = CourseFactory.create() self.course_key = course.id course_mode = CourseMode(course_id=self.course_key, mode_slug="verified", mode_display_name="verified cert", min_price=self.cost, expiration_datetime=(datetime.datetime.now(pytz.utc) + many_days)) course_mode.save() # need to prevent analytics errors from appearing in stderr with patch('sys.stderr', sys.stdout.write): CourseEnrollment.enroll(self.user, self.course_key, 'verified') cart = Order.get_cart_for_user(user=self.user) CertificateItem.add_to_order(cart, self.course_key, self.cost, 'verified') cart.purchase() CourseEnrollment.unenroll(self.user, self.course_key) target_certs = CertificateItem.objects.filter(course_id=self.course_key, user_id=self.user, status='refunded', mode='verified') self.assertTrue(target_certs[0]) self.assertTrue(target_certs[0].refund_requested_time) self.assertEquals(target_certs[0].order.status, 'refunded') self._assert_refund_tracked()
def _create_cart(self): """Creates a cart and adds a CertificateItem to it""" cart = Order.get_cart_for_user(UserFactory.create()) item = CertificateItem.add_to_order( cart, self.course_key, 10, 'honor', currency='usd' ) return cart, item
def checkout_with_shoppingcart(request, user, course_key, course_mode, amount): """ Create an order and trigger checkout using shoppingcart.""" cart = Order.get_cart_for_user(user) cart.clear() enrollment_mode = course_mode.slug CertificateItem.add_to_order(cart, course_key, amount, enrollment_mode) # Change the order's status so that we don't accidentally modify it later. # We need to do this to ensure that the parameters we send to the payment system # match what we store in the database. # (Ordinarily we would do this client-side when the user submits the form, but since # the JavaScript on this page does that immediately, we make the change here instead. # This avoids a second AJAX call and some additional complication of the JavaScript.) # If a user later re-enters the verification / payment flow, she will create a new order. cart.start_purchase() callback_url = request.build_absolute_uri( reverse("shoppingcart.views.postpay_callback") ) payment_data = { 'payment_processor_name': settings.CC_PROCESSOR_NAME, 'payment_page_url': get_purchase_endpoint(), 'payment_form_data': get_signed_purchase_params( cart, callback_url=callback_url, extra_data=[unicode(course_key), course_mode.slug] ), } return payment_data
def test_confirmation_email_error(self): CourseMode.objects.create( course_id=self.course_key, mode_slug="verified", mode_display_name="Verified", min_price=self.cost ) cart = Order.get_cart_for_user(self.user) CertificateItem.add_to_order(cart, self.course_key, self.cost, 'verified') # Simulate an error when sending the confirmation # email. This should NOT raise an exception. # If it does, then the implicit view-level # transaction could cause a roll-back, effectively # reversing order fulfillment. with patch.object(mail.message.EmailMessage, 'send') as mock_send: mock_send.side_effect = Exception("Kaboom!") cart.purchase() # Verify that the purchase completed successfully self.assertEqual(cart.status, 'purchased') # Verify that the user is enrolled as "verified" mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course_key) self.assertTrue(is_active) self.assertEqual(mode, 'verified')
def test_payment_separate_from_verification_email(self): cart = Order.get_cart_for_user(user=self.user) item = CertificateItem.add_to_order(cart, self.course_key, self.cost, 'honor') cart.purchase() self.assertEquals(len(mail.outbox), 1) # Verify that the verification reminder appears in the sent email. self.assertIn(item.additional_instruction_text(), mail.outbox[0].body)
def add_to_cart(self): """ Adds content to self.user's cart """ course = CourseFactory.create(org='MITx', number='999', display_name='Robot Super Course') CourseModeFactory.create(course_id=course.id) cart = Order.get_cart_for_user(self.user) PaidCourseRegistration.add_to_order(cart, course.id)
def test_retire_order_cart(self): """Test that an order in cart can successfully be retired""" cart = Order.get_cart_for_user(user=self.user) CertificateItem.add_to_order(cart, self.course_key, self.cost, 'honor', currency='usd') cart.retire() self.assertEqual(cart.status, 'defunct-cart') self.assertEqual(cart.orderitem_set.get().status, 'defunct-cart')
def test_user_cart_has_items(self): anon = AnonymousUser() self.assertFalse(Order.user_cart_has_items(anon)) self.assertFalse(Order.user_cart_has_items(self.user)) cart = Order.get_cart_for_user(self.user) item = OrderItem(order=cart, user=self.user) item.save() self.assertTrue(Order.user_cart_has_items(self.user))
def test_cart_clear(self): cart = Order.get_cart_for_user(user=self.user) CertificateItem.add_to_order(cart, self.course_key, self.cost, 'honor') CertificateItem.add_to_order(cart, SlashSeparatedCourseKey('org', 'test', 'Test_Course_1'), self.cost, 'honor') self.assertEquals(cart.orderitem_set.count(), 2) self.assertTrue(cart.has_items()) cart.clear() self.assertEquals(cart.orderitem_set.count(), 0) self.assertFalse(cart.has_items())
def test_cart_clear(self): cart = Order.get_cart_for_user(user=self.user) CertificateItem.add_to_order(cart, self.course_id, self.cost, 'honor') CertificateItem.add_to_order(cart, 'org/test/Test_Course_1', self.cost, 'honor') self.assertEquals(cart.orderitem_set.count(), 2) self.assertTrue(cart.has_items()) cart.clear() self.assertEquals(cart.orderitem_set.count(), 0) self.assertFalse(cart.has_items())
def test_user_cart_has_both_items(self): """ This test exists b/c having both CertificateItem and PaidCourseRegistration in an order used to break PaidCourseRegistration.contained_in_order """ cart = Order.get_cart_for_user(self.user) CertificateItem.add_to_order(cart, self.course_key, self.cost, 'honor') PaidCourseRegistration.add_to_order(self.cart, self.course_key) self.assertTrue(PaidCourseRegistration.contained_in_order(cart, self.course_key))
def test_existing_enrollment(self): 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') # verify that we are still enrolled self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course_id)) cart.purchase() enrollment = CourseEnrollment.objects.get(user=self.user, course_id=self.course_id) self.assertEquals(enrollment.mode, u'verified')
def _enroll(self, purchase=True): # pylint: disable=missing-docstring 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 create_order(request): """ Submit PhotoVerification and create a new Order for this verified cert """ if not SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user): attempt = SoftwareSecurePhotoVerification(user=request.user) b64_face_image = request.POST['face_image'].split(",")[1] b64_photo_id_image = request.POST['photo_id_image'].split(",")[1] attempt.upload_face_image(b64_face_image.decode('base64')) attempt.upload_photo_id_image(b64_photo_id_image.decode('base64')) attempt.mark_ready() attempt.save() course_id = request.POST['course_id'] course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) donation_for_course = request.session.get('donation_for_course', {}) current_donation = donation_for_course.get(course_id, decimal.Decimal(0)) contribution = request.POST.get("contribution", donation_for_course.get(course_id, 0)) try: amount = decimal.Decimal(contribution).quantize(decimal.Decimal('.01'), rounding=decimal.ROUND_DOWN) except decimal.InvalidOperation: return HttpResponseBadRequest(_("Selected price is not valid number.")) if amount != current_donation: donation_for_course[course_id] = amount request.session['donation_for_course'] = donation_for_course # prefer professional mode over verified_mode current_mode = CourseMode.verified_mode_for_course(course_id) if current_mode.slug == 'professional': amount = current_mode.min_price # make sure this course has a verified mode if not current_mode: return HttpResponseBadRequest(_("This course doesn't support verified certificates")) if amount < current_mode.min_price: return HttpResponseBadRequest(_("No selected price or selected price is below minimum.")) # I know, we should check this is valid. All kinds of stuff missing here cart = Order.get_cart_for_user(request.user) cart.clear() enrollment_mode = current_mode.slug CertificateItem.add_to_order(cart, course_id, amount, enrollment_mode) callback_url = request.build_absolute_uri( reverse("shoppingcart.views.postpay_callback") ) params = get_signed_purchase_params( cart, callback_url=callback_url ) return HttpResponse(json.dumps(params), content_type="text/json")
def test_purchase_item_failure(self): # once again, we're testing against the specific implementation of # CertificateItem cart = Order.get_cart_for_user(user=self.user) CertificateItem.add_to_order(cart, self.course_id, self.cost, 'verified') with patch('shoppingcart.models.CertificateItem.save', side_effect=DatabaseError): with self.assertRaises(DatabaseError): cart.purchase() # verify that we rolled back the entire transaction self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course_id))
def test_user_cart_has_items(self): anon = AnonymousUser() self.assertFalse(Order.user_cart_has_items(anon)) self.assertFalse(Order.user_cart_has_items(self.user)) cart = Order.get_cart_for_user(self.user) item = OrderItem(order=cart, user=self.user) item.save() self.assertTrue(Order.user_cart_has_items(self.user)) self.assertFalse(Order.user_cart_has_items(self.user, [CertificateItem])) self.assertFalse(Order.user_cart_has_items(self.user, [PaidCourseRegistration]))
def test_lookup_valid_redeemed_registration_code(self): """ test to lookup for the valid and redeemed registration code and then mark that registration code as un_redeemed which will unenroll the user and delete the redemption entry from the database. """ student = UserFactory() self.client.login(username=student.username, password='******') cart = Order.get_cart_for_user(student) cart.order_type = 'business' cart.save() CourseRegCodeItem.add_to_order(cart, self.course.id, 2) cart.purchase() reg_code = CourseRegistrationCode.objects.filter(order=cart)[0] enrollment = CourseEnrollment.enroll(student, self.course.id) RegistrationCodeRedemption.objects.create(registration_code=reg_code, redeemed_by=student, course_enrollment=enrollment) self.client.login(username=self.instructor.username, password='******') data = {'registration_code': reg_code.code} response = self.client.get(self.lookup_code_url, data) self.assertEqual(response.status_code, 200) json_dict = json.loads(response.content) self.assertTrue(json_dict['is_registration_code_valid']) self.assertTrue(json_dict['is_registration_code_redeemed']) # now mark the registration code as unredeemed # this will unenroll the user and removed the redemption entry from # the database. data = { 'registration_code': reg_code.code, 'action_type': 'unredeem_registration_code' } response = self.client.post(self.registration_code_detail_url, data) self.assertEqual(response.status_code, 200) json_dict = json.loads(response.content) message = _('This enrollment code has been marked as unused.') self.assertEqual(message, json_dict['message']) redemption = RegistrationCodeRedemption.get_registration_code_redemption( reg_code.code, self.course.id) self.assertIsNone(redemption) # now the student course enrollment should be false. enrollment = CourseEnrollment.get_enrollment(student, self.course.id) self.assertEqual(enrollment.is_active, False)
def create_order(request): """ Submit PhotoVerification and create a new Order for this verified cert """ if not SoftwareSecurePhotoVerification.user_has_valid_or_pending( request.user): attempt = SoftwareSecurePhotoVerification(user=request.user) b64_face_image = request.POST['face_image'].split(",")[1] b64_photo_id_image = request.POST['photo_id_image'].split(",")[1] attempt.upload_face_image(b64_face_image.decode('base64')) attempt.upload_photo_id_image(b64_photo_id_image.decode('base64')) attempt.mark_ready() attempt.save() course_id = request.POST['course_id'] course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) donation_for_course = request.session.get('donation_for_course', {}) current_donation = donation_for_course.get(course_id, decimal.Decimal(0)) contribution = request.POST.get("contribution", donation_for_course.get(course_id, 0)) try: amount = decimal.Decimal(contribution).quantize( decimal.Decimal('.01'), rounding=decimal.ROUND_DOWN) except decimal.InvalidOperation: return HttpResponseBadRequest(_("Selected price is not valid number.")) if amount != current_donation: donation_for_course[course_id] = amount request.session['donation_for_course'] = donation_for_course verified_mode = CourseMode.modes_for_course_dict(course_id).get( 'verified', None) # make sure this course has a verified mode if not verified_mode: return HttpResponseBadRequest( _("This course doesn't support verified certificates")) if amount < verified_mode.min_price: return HttpResponseBadRequest( _("No selected price or selected price is below minimum.")) # I know, we should check this is valid. All kinds of stuff missing here cart = Order.get_cart_for_user(request.user) cart.clear() CertificateItem.add_to_order(cart, course_id, amount, 'verified') params = get_signed_purchase_params(cart) return HttpResponse(json.dumps(params), content_type="text/json")
def test_generate_receipt_instructions_callchain(self): """ This tests the generate_receipt_instructions call chain (ie calling the function on the cart also calls it on items in the cart """ cart = Order.get_cart_for_user(self.user) item = OrderItem(user=self.user, order=cart) item.save() self.assertTrue(cart.has_items()) with patch.object(OrderItem, 'generate_receipt_instructions', self.mock_gen_inst): cart.generate_receipt_instructions() self.mock_gen_inst.assert_called_with()
def test_purchase_item_failure(self): # once again, we're testing against the specific implementation of # CertificateItem cart = Order.get_cart_for_user(user=self.user) CertificateItem.add_to_order(cart, self.course_id, self.cost, 'verified') with patch('shoppingcart.models.CertificateItem.save', side_effect=DatabaseError): with self.assertRaises(DatabaseError): cart.purchase() # verify that we rolled back the entire transaction self.assertFalse( CourseEnrollment.is_enrolled(self.user, self.course_id))
def test_retire_order_already_retired(self, status): """ Check that orders that have already been retired noop when the method is called on them again. """ cart = Order.get_cart_for_user(user=self.user) item = CertificateItem.add_to_order(cart, self.course_key, self.cost, 'honor', currency='usd') cart.status = item.status = status cart.save() item.save() cart.retire() self.assertEqual(cart.status, status) self.assertEqual(item.status, status)
def setUp(self): self.user = UserFactory.create() self.user.set_password('password') self.user.save() self.course_id = "MITx/999/Robot_Super_Course" self.cost = 40 self.course = CourseFactory.create(org='MITx', number='999', display_name='Robot Super Course') self.course_mode = CourseMode(course_id=self.course_id, mode_slug="honor", mode_display_name="honor cert", min_price=self.cost) self.course_mode.save() self.cart = Order.get_cart_for_user(self.user)
def setUp(self): super(PaidCourseRegistrationTest, self).setUp() self.user = UserFactory.create() self.cost = 40 self.course = CourseFactory.create() self.course_key = self.course.id self.course_mode = CourseMode(course_id=self.course_key, mode_slug="honor", mode_display_name="honor cert", min_price=self.cost) self.course_mode.save() self.cart = Order.get_cart_for_user(self.user)
def _create_and_purchase_verified(self, student, course_id): """ Creates a verified mode for the course and purchases it for the student. """ course_mode = CourseMode(course_id=course_id, mode_slug='verified', mode_display_name='verified cert', min_price=50) course_mode.save() # When there is no expiration date on a verified mode, the user can always get a refund cart = Order.get_cart_for_user(user=student) CertificateItem.add_to_order(cart, course_id, 50, 'verified') cart.purchase()
def test_billing_info_storage_on(self): cart = Order.get_cart_for_user(self.user) self.purchase_with_data(cart) self.assertNotEqual(cart.bill_to_first, '') self.assertNotEqual(cart.bill_to_last, '') self.assertNotEqual(cart.bill_to_street1, '') self.assertNotEqual(cart.bill_to_street2, '') self.assertNotEqual(cart.bill_to_postalcode, '') self.assertNotEqual(cart.bill_to_ccnum, '') self.assertNotEqual(cart.bill_to_cardtype, '') self.assertNotEqual(cart.bill_to_city, '') self.assertNotEqual(cart.bill_to_state, '') self.assertNotEqual(cart.bill_to_country, '')
def test_retire_order_paying(self): """Test that an order in "paying" can successfully be retired""" cart = Order.get_cart_for_user(user=self.user) CertificateItem.add_to_order(cart, self.course_key, self.cost, 'honor', currency='usd') cart.start_purchase() cart.retire() self.assertEqual(cart.status, 'defunct-paying') self.assertEqual(cart.orderitem_set.get().status, 'defunct-paying')
def test_existing_enrollment(self): CourseEnrollment.enroll(self.user, self.course_key) cart = Order.get_cart_for_user(user=self.user) CertificateItem.add_to_order(cart, self.course_key, self.cost, 'verified') # verify that we are still enrolled self.assertTrue( CourseEnrollment.is_enrolled(self.user, self.course_key)) self.mock_tracker.reset_mock() cart.purchase() enrollment = CourseEnrollment.objects.get(user=self.user, course_id=self.course_key) self.assertEqual(enrollment.mode, u'verified')
def setUp(self): super(ItemizedPurchaseReportTest, self).setUp() self.user = UserFactory.create() self.cost = 40 self.course = CourseFactory.create(org='MITx', number='999', display_name=u'Robot Super Course') self.course_key = self.course.id course_mode = CourseMode(course_id=self.course_key, mode_slug="honor", mode_display_name="honor cert", min_price=self.cost) course_mode.save() course_mode2 = CourseMode(course_id=self.course_key, mode_slug="verified", mode_display_name="verified cert", min_price=self.cost) course_mode2.save() self.annotation = PaidCourseRegistrationAnnotation( course_id=self.course_key, annotation=self.TEST_ANNOTATION) self.annotation.save() self.course_reg_code_annotation = CourseRegCodeItemAnnotation( course_id=self.course_key, annotation=self.TEST_ANNOTATION) self.course_reg_code_annotation.save() self.cart = Order.get_cart_for_user(self.user) self.reg = PaidCourseRegistration.add_to_order( self.cart, self.course_key, mode_slug=course_mode.mode_slug) self.cert_item = CertificateItem.add_to_order(self.cart, self.course_key, self.cost, 'verified') self.cart.purchase() self.now = datetime.datetime.now(pytz.UTC) paid_reg = PaidCourseRegistration.objects.get( course_id=self.course_key, user=self.user) paid_reg.fulfilled_time = self.now paid_reg.refund_requested_time = self.now paid_reg.save() cert = CertificateItem.objects.get(course_id=self.course_key, user=self.user) cert.fulfilled_time = self.now cert.refund_requested_time = self.now cert.save() self.CORRECT_CSV = dedent(""" Purchase Time,Order ID,Status,Quantity,Unit Cost,Total Cost,Currency,Description,Comments {time_str},1,purchased,1,40.00,40.00,usd,Registration for Course: Robot Super Course,Ba\xc3\xbc\xe5\x8c\x85 {time_str},1,purchased,1,40.00,40.00,usd,verified cert for course Robot Super Course, """.format(time_str=str(self.now)))
def change_enrollment(strategy, user=None, *args, **kwargs): """Enroll a user in a course. If a user entered the authentication flow when trying to enroll in a course, then attempt to enroll the user. We will try to do this if the pipeline was started with the querystring param `enroll_course_id`. In the following cases, we can't enroll the user: * The course does not have an honor mode. * The course has an honor mode with a minimum price. * The course is not yet open for enrollment. * The course does not exist. If we can't enroll the user now, then skip this step. For paid courses, users will be redirected to the payment flow upon completion of the authentication pipeline (configured using the ?next parameter to the third party auth login url). """ enroll_course_id = strategy.session_get('enroll_course_id') if enroll_course_id: course_id = CourseKey.from_string(enroll_course_id) modes = CourseMode.modes_for_course_dict(course_id) # If the email opt in parameter is found, set the preference. email_opt_in = strategy.session_get(AUTH_EMAIL_OPT_IN_KEY) if email_opt_in: opt_in = email_opt_in.lower() == 'true' profile.update_email_opt_in(user.username, course_id.org, opt_in) if CourseMode.can_auto_enroll(course_id, modes_dict=modes): try: CourseEnrollment.enroll(user, course_id, check_access=True) except CourseEnrollmentException: pass except Exception as ex: logger.exception(ex) # Handle white-label courses as a special case # If a course is white-label, we should add it to the shopping cart. elif CourseMode.is_white_label(course_id, modes_dict=modes): try: cart = Order.get_cart_for_user(user) PaidCourseRegistration.add_to_order(cart, course_id) except (CourseDoesNotExistException, ItemAlreadyInCartException, AlreadyEnrolledInCourseException): pass # It's more important to complete login than to # ensure that the course was added to the shopping cart. # Log errors, but don't stop the authentication pipeline. except Exception as ex: logger.exception(ex)
def test_refund_cert_callback_no_expiration(self): # When there is no expiration date on a verified mode, the user can always get a refund CourseEnrollment.enroll(self.user, self.course_id, 'verified') cart = Order.get_cart_for_user(user=self.user) CertificateItem.add_to_order(cart, self.course_id, self.cost, 'verified') cart.purchase() CourseEnrollment.unenroll(self.user, self.course_id) target_certs = CertificateItem.objects.filter(course_id=self.course_id, user_id=self.user, status='refunded', mode='verified') self.assertTrue(target_certs[0])
def test_already_in_cart(self): """ This makes sure if a user has this course in the cart, that the expected message appears """ self.setup_user() cart = Order.get_cart_for_user(self.user) PaidCourseRegistration.add_to_order(cart, self.course.id) url = reverse('about_course', args=[text_type(self.course.id)]) resp = self.client.get(url) self.assertEqual(resp.status_code, 200) self.assertIn("This course is in your", resp.content) self.assertNotIn("Add buyme to Cart <span>($10 USD)</span>", resp.content)
def test_total_cost(self): cart = Order.get_cart_for_user(user=self.user) # add items to the order course_costs = [('org/test/Test_Course_1', 30), ('org/test/Test_Course_2', 40), ('org/test/Test_Course_3', 10), ('org/test/Test_Course_4', 20)] for course, cost in course_costs: CertificateItem.add_to_order( cart, SlashSeparatedCourseKey.from_deprecated_string(course), cost, 'honor') self.assertEquals(cart.orderitem_set.count(), len(course_costs)) self.assertEquals(cart.total_cost, sum(cost for _course, cost in course_costs))
def test_purchase_item_failure(self): # once again, we're testing against the specific implementation of # CertificateItem cart = Order.get_cart_for_user(user=self.user) CertificateItem.add_to_order(cart, self.course_key, self.cost, 'honor') with patch('lms.djangoapps.shoppingcart.models.CertificateItem.save', side_effect=DatabaseError): with self.assertRaises(DatabaseError): cart.purchase() # verify that we rolled back the entire transaction self.assertFalse( CourseEnrollment.is_enrolled(self.user, self.course_key)) # verify that e-mail wasn't sent self.assertEqual(len(mail.outbox), 0)
def test_billing_info_storage_off(self): cart = Order.get_cart_for_user(self.user) self.purchase_with_data(cart) self.assertNotEqual(cart.bill_to_first, '') self.assertNotEqual(cart.bill_to_last, '') self.assertNotEqual(cart.bill_to_city, '') self.assertNotEqual(cart.bill_to_state, '') self.assertNotEqual(cart.bill_to_country, '') self.assertNotEqual(cart.bill_to_postalcode, '') # things we expect to be missing when the feature is off self.assertEqual(cart.bill_to_street1, '') self.assertEqual(cart.bill_to_street2, '') self.assertEqual(cart.bill_to_ccnum, '') self.assertEqual(cart.bill_to_cardtype, '')
def test_get_list_price(self): """ This tests the get_list_price() method of the OrderItem """ cart = Order.get_cart_for_user(self.user) item = OrderItem(user=self.user, order=cart) item.list_price = None item.unit_cost = 100 self.assertEqual(item.get_list_price(), item.unit_cost) item.list_price = 200 item.unit_cost = 100 self.assertEqual(item.get_list_price(), item.list_price)
def test_generate_receipt_instructions_callchain(self): """ This tests the generate_receipt_instructions call chain (ie calling the function on the cart also calls it on items in the cart """ mock_gen_inst = MagicMock(return_value=(OrderItemSubclassPK(OrderItem, 1), set([]))) cart = Order.get_cart_for_user(self.user) item = OrderItem(user=self.user, order=cart) item.save() self.assertTrue(cart.has_items()) with patch.object(OrderItem, 'generate_receipt_instructions', mock_gen_inst): cart.generate_receipt_instructions() mock_gen_inst.assert_called_with()
def test_billing_info_storage_on(self, render): cart = Order.get_cart_for_user(self.user) self.purchase_with_data(cart) self.assertNotEqual(cart.bill_to_first, '') self.assertNotEqual(cart.bill_to_last, '') self.assertNotEqual(cart.bill_to_street1, '') self.assertNotEqual(cart.bill_to_street2, '') self.assertNotEqual(cart.bill_to_postalcode, '') self.assertNotEqual(cart.bill_to_ccnum, '') self.assertNotEqual(cart.bill_to_cardtype, '') self.assertNotEqual(cart.bill_to_city, '') self.assertNotEqual(cart.bill_to_state, '') self.assertNotEqual(cart.bill_to_country, '') ((_, context), _) = render.call_args self.assertTrue(context['has_billing_info'])
def test_purchase(self): # This test is for testing the subclassing functionality of OrderItem, but in # order to do this, we end up testing the specific functionality of # CertificateItem, which is not quite good unit test form. Sorry. cart = Order.get_cart_for_user(user=self.user) self.assertFalse( CourseEnrollment.is_enrolled(self.user, self.course_id)) CertificateItem.add_to_order(cart, self.course_id, self.cost, 'verified') # course enrollment object should be created but still inactive self.assertFalse( CourseEnrollment.is_enrolled(self.user, self.course_id)) cart.purchase() self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course_id))
def test_purchase(self): # This test is for testing the subclassing functionality of OrderItem, but in # order to do this, we end up testing the specific functionality of # CertificateItem, which is not quite good unit test form. Sorry. cart = Order.get_cart_for_user(user=self.user) self.assertFalse( CourseEnrollment.is_enrolled(self.user, self.course_key)) item = CertificateItem.add_to_order(cart, self.course_key, self.cost, 'honor') # Course enrollment object should be created but still inactive self.assertFalse( CourseEnrollment.is_enrolled(self.user, self.course_key)) # Analytics client pipes output to stderr when using the default client with patch('sys.stderr', sys.stdout.write): cart.purchase() self.assertTrue( CourseEnrollment.is_enrolled(self.user, self.course_key)) # Test email sending self.assertEqual(len(mail.outbox), 1) self.assertEqual('Order Payment Confirmation', mail.outbox[0].subject) self.assertIn(settings.PAYMENT_SUPPORT_EMAIL, mail.outbox[0].body) self.assertIn(six.text_type(cart.total_cost), mail.outbox[0].body) self.assertIn(item.additional_instruction_text(), mail.outbox[0].body) # Verify Google Analytics event fired for purchase self.mock_tracker.track.assert_called_once_with( self.user.id, 'Completed Order', { 'orderId': 1, 'currency': 'usd', 'total': '40.00', 'revenue': '40.00', # value for revenue field is same as total. 'products': [{ 'sku': u'CertificateItem.honor', 'name': six.text_type(self.course_key), 'category': six.text_type(self.course_key.org), 'price': '40.00', 'id': 1, 'quantity': 1 }] }, )
def test_retire_order_error(self, order_status, item_status, exception): """ Test error cases for retiring an order: 1) Order item has a different status than the order 2) The order's status isn't in "cart" or "paying" """ cart = Order.get_cart_for_user(user=self.user) item = CertificateItem.add_to_order(cart, self.course_key, self.cost, 'honor', currency='usd') cart.status = order_status cart.save() item.status = item_status item.save() with self.assertRaises(exception): cart.retire()
def test_refund_cert_callback_no_expiration(self): # When there is no expiration date on a verified mode, the user can always get a refund # need to prevent analytics errors from appearing in stderr with patch('sys.stderr', sys.stdout.write): CourseEnrollment.enroll(self.user, self.course_key, 'verified') cart = Order.get_cart_for_user(user=self.user) CertificateItem.add_to_order(cart, self.course_key, self.cost, 'verified') cart.purchase() CourseEnrollment.unenroll(self.user, self.course_key) target_certs = CertificateItem.objects.filter(course_id=self.course_key, user_id=self.user, status='refunded', mode='verified') self.assertTrue(target_certs[0]) self.assertTrue(target_certs[0].refund_requested_time) self.assertEquals(target_certs[0].order.status, 'refunded') self._assert_refund_tracked()
def test_show_receipt_404s(self): PaidCourseRegistration.add_to_order(self.cart, self.course_key) CertificateItem.add_to_order(self.cart, self.verified_course_key, self.cost, 'honor') self.cart.purchase() user2 = UserFactory.create() cart2 = Order.get_cart_for_user(user2) PaidCourseRegistration.add_to_order(cart2, self.course_key) cart2.purchase() self.login_user() resp = self.client.get(reverse('shoppingcart.views.show_receipt', args=[cart2.id])) self.assertEqual(resp.status_code, 404) resp2 = self.client.get(reverse('shoppingcart.views.show_receipt', args=[1000])) self.assertEqual(resp2.status_code, 404)
def test_no_refund_on_cert_callback(self): # If we explicitly skip refunds, the unenroll action should not modify the purchase. CourseEnrollment.enroll(self.user, self.course_key, 'verified') cart = Order.get_cart_for_user(user=self.user) CertificateItem.add_to_order(cart, self.course_key, self.cost, 'verified') cart.purchase() CourseEnrollment.unenroll(self.user, self.course_key, skip_refund=True) target_certs = CertificateItem.objects.filter( course_id=self.course_key, user_id=self.user, status='purchased', mode='verified') self.assertTrue(target_certs[0]) self.assertFalse(target_certs[0].refund_requested_time) self.assertEqual(target_certs[0].order.status, 'purchased')
def test_is_discounted(self): """ This tests the is_discounted property of the OrderItem """ cart = Order.get_cart_for_user(self.user) item = OrderItem(user=self.user, order=cart) item.list_price = None item.unit_cost = 100 self.assertFalse(item.is_discounted) item.list_price = 100 item.unit_cost = 100 self.assertFalse(item.is_discounted) item.list_price = 100 item.unit_cost = 90 self.assertTrue(item.is_discounted)
def setUp(self): patcher = patch('student.models.tracker') self.mock_tracker = patcher.start() self.user = UserFactory.create() self.user.set_password('password') self.user.save() self.cost = 40 self.course = CourseFactory.create(org='MITx', number='999', display_name='Robot Super Course') self.course_key = self.course.id self.course_mode = CourseMode(course_id=self.course_key, mode_slug="honor", mode_display_name="honor cert", min_price=self.cost) self.course_mode.save() verified_course = CourseFactory.create(org='org', number='test', display_name='Test Course') self.verified_course_key = verified_course.id self.cart = Order.get_cart_for_user(self.user) self.addCleanup(patcher.stop)
def test_purchase(self): # This test is for testing the subclassing functionality of OrderItem, but in # order to do this, we end up testing the specific functionality of # CertificateItem, which is not quite good unit test form. Sorry. cart = Order.get_cart_for_user(user=self.user) self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course_key)) item = CertificateItem.add_to_order(cart, self.course_key, self.cost, 'honor') # course enrollment object should be created but still inactive self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course_key)) cart.purchase() self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course_key)) # test e-mail sending self.assertEquals(len(mail.outbox), 1) self.assertEquals('Order Payment Confirmation', mail.outbox[0].subject) self.assertIn(settings.PAYMENT_SUPPORT_EMAIL, mail.outbox[0].body) self.assertIn(unicode(cart.total_cost), mail.outbox[0].body) self.assertIn(item.additional_instruction_text, mail.outbox[0].body)