From d7225f026afcc518696e38108f692cd0ced8eb51 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Mon, 26 Aug 2013 12:25:10 -0400 Subject: [PATCH 1/2] Only store certain bits of information behind a flag. --- lms/djangoapps/shoppingcart/models.py | 14 +++--- .../processors/tests/test_CyberSource.py | 4 -- .../shoppingcart/tests/test_models.py | 48 +++++++++++++++++++ .../shoppingcart/tests/test_views.py | 1 - lms/envs/common.py | 5 +- 5 files changed, 60 insertions(+), 12 deletions(-) diff --git a/lms/djangoapps/shoppingcart/models.py b/lms/djangoapps/shoppingcart/models.py index 1ad71ff625..ce80f47121 100644 --- a/lms/djangoapps/shoppingcart/models.py +++ b/lms/djangoapps/shoppingcart/models.py @@ -2,6 +2,7 @@ import pytz import logging from datetime import datetime from django.db import models +from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.contrib.auth.models import User from django.utils.translation import ugettext as _ @@ -102,15 +103,16 @@ class Order(models.Model): self.purchase_time = datetime.now(pytz.utc) self.bill_to_first = first self.bill_to_last = last - self.bill_to_street1 = street1 - self.bill_to_street2 = street2 self.bill_to_city = city self.bill_to_state = state - self.bill_to_postalcode = postalcode self.bill_to_country = country - self.bill_to_ccnum = ccnum - self.bill_to_cardtype = cardtype - self.processor_reply_dump = processor_reply_dump + self.bill_to_postalcode = postalcode + if settings.MITX_FEATURES['STORE_BILLING_INFO']: + self.bill_to_street1 = street1 + self.bill_to_street2 = street2 + self.bill_to_ccnum = ccnum + self.bill_to_cardtype = cardtype + self.processor_reply_dump = processor_reply_dump # save these changes on the order, then we can tell when we are in an # inconsistent state self.save() diff --git a/lms/djangoapps/shoppingcart/processors/tests/test_CyberSource.py b/lms/djangoapps/shoppingcart/processors/tests/test_CyberSource.py index de9e5939f0..c88d0ca5e0 100644 --- a/lms/djangoapps/shoppingcart/processors/tests/test_CyberSource.py +++ b/lms/djangoapps/shoppingcart/processors/tests/test_CyberSource.py @@ -116,14 +116,10 @@ class CyberSourceTests(TestCase): order2 = Order.get_cart_for_user(student2) record_purchase(params_cc, order1) record_purchase(params_nocc, order2) - self.assertEqual(order1.bill_to_ccnum, '1234') - self.assertEqual(order1.bill_to_cardtype, 'Visa') self.assertEqual(order1.bill_to_first, student1.first_name) self.assertEqual(order1.status, 'purchased') order2 = Order.objects.get(user=student2) - self.assertEqual(order2.bill_to_ccnum, '####') - self.assertEqual(order2.bill_to_cardtype, 'MasterCard') self.assertEqual(order2.bill_to_first, student2.first_name) self.assertEqual(order2.status, 'purchased') diff --git a/lms/djangoapps/shoppingcart/tests/test_models.py b/lms/djangoapps/shoppingcart/tests/test_models.py index 75789964b1..65483d7404 100644 --- a/lms/djangoapps/shoppingcart/tests/test_models.py +++ b/lms/djangoapps/shoppingcart/tests/test_models.py @@ -6,6 +6,7 @@ from factory import DjangoModelFactory from mock import patch from django.test import TestCase from django.test.utils import override_settings +from django.conf import settings from django.db import DatabaseError from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory @@ -87,6 +88,53 @@ class OrderTest(TestCase): # verify that we rolled back the entire transaction self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course_id)) + def purchase_with_data(self, cart): + """ purchase a cart with billing information """ + CertificateItem.add_to_order(cart, self.course_id, self.cost, 'verified') + cart.purchase( + first='John', + last='Smith', + street1='11 Cambridge Center', + street2='Suite 101', + city='Cambridge', + state='MA', + postalcode='02412', + country='US', + ccnum='1111', + cardtype='001', + ) + + @patch.dict(settings.MITX_FEATURES, {'STORE_BILLING_INFO': True}) + 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, '') + + @patch.dict(settings.MITX_FEATURES, {'STORE_BILLING_INFO': False}) + 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, '') class OrderItemTest(TestCase): def setUp(self): diff --git a/lms/djangoapps/shoppingcart/tests/test_views.py b/lms/djangoapps/shoppingcart/tests/test_views.py index 25ee914ce6..3ff3a25524 100644 --- a/lms/djangoapps/shoppingcart/tests/test_views.py +++ b/lms/djangoapps/shoppingcart/tests/test_views.py @@ -191,7 +191,6 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): resp = self.client.get(reverse('shoppingcart.views.show_receipt', args=[self.cart.id])) self.assertEqual(resp.status_code, 200) self.assertIn('FirstNameTesting123', resp.content) - self.assertIn('StreetTesting123', resp.content) self.assertIn('80.00', resp.content) ((template, context), _) = render_mock.call_args diff --git a/lms/envs/common.py b/lms/envs/common.py index 8181f97789..0e659a1ca1 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -156,7 +156,10 @@ MITX_FEATURES = { 'ENABLE_CHAT': False, # Toggle the availability of the shopping cart page - 'ENABLE_SHOPPING_CART': False + 'ENABLE_SHOPPING_CART': False, + + # Toggle storing detailed billing information + 'STORE_BILLING_INFO': False } # Used for A/B testing From 1b5fde9dae001ff1742effca7bdd9b7547ac60b1 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Mon, 26 Aug 2013 17:20:28 -0400 Subject: [PATCH 2/2] Send email on purchase success. --- lms/djangoapps/shoppingcart/models.py | 18 ++++++++++++++++-- .../shoppingcart/tests/test_models.py | 9 +++++++++ .../emails/order_confirmation_email.txt | 15 +++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 lms/templates/emails/order_confirmation_email.txt diff --git a/lms/djangoapps/shoppingcart/models.py b/lms/djangoapps/shoppingcart/models.py index ce80f47121..a80388f971 100644 --- a/lms/djangoapps/shoppingcart/models.py +++ b/lms/djangoapps/shoppingcart/models.py @@ -1,5 +1,6 @@ import pytz import logging +import smtplib from datetime import datetime from django.db import models from django.conf import settings @@ -9,7 +10,9 @@ from django.utils.translation import ugettext as _ from django.db import transaction from model_utils.managers import InheritanceManager from courseware.courses import get_course_about_section +from django.core.mail import send_mail +from mitxmako.shortcuts import render_to_string from xmodule.modulestore.django import modulestore from xmodule.course_module import CourseDescriptor @@ -121,6 +124,17 @@ class Order(models.Model): orderitems = OrderItem.objects.filter(order=self).select_subclasses() for item in orderitems: item.purchase_item() + # send confirmation e-mail + subject = _("Order Payment Confirmation") + message = render_to_string('emails/order_confirmation_email.txt', { + 'order': self, + 'order_items': orderitems + }) + try: + send_mail(subject, message, + settings.DEFAULT_FROM_EMAIL, [self.user.email]) + except smtplib.SMTPException: + log.error('Failed sending confirmation e-mail for order %d', self.id) class OrderItem(models.Model): @@ -307,8 +321,8 @@ class CertificateItem(OrderItem): item.status = order.status item.qty = 1 item.unit_cost = cost - item.line_desc = "{mode} certificate for course {course_id}".format(mode=item.mode, - course_id=course_id) + item.line_desc = _("{mode} certificate for course {course_id}").format(mode=item.mode, + course_id=course_id) item.currency = currency order.currency = currency order.save() diff --git a/lms/djangoapps/shoppingcart/tests/test_models.py b/lms/djangoapps/shoppingcart/tests/test_models.py index 65483d7404..42df02267d 100644 --- a/lms/djangoapps/shoppingcart/tests/test_models.py +++ b/lms/djangoapps/shoppingcart/tests/test_models.py @@ -6,6 +6,7 @@ from factory import DjangoModelFactory from mock import patch from django.test import TestCase from django.test.utils import override_settings +from django.core import mail from django.conf import settings from django.db import DatabaseError from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase @@ -77,6 +78,12 @@ class OrderTest(TestCase): cart.purchase() self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course_id)) + # 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) + def test_purchase_item_failure(self): # once again, we're testing against the specific implementation of # CertificateItem @@ -87,6 +94,8 @@ class OrderTest(TestCase): cart.purchase() # verify that we rolled back the entire transaction self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course_id)) + # verify that e-mail wasn't sent + self.assertEquals(len(mail.outbox), 0) def purchase_with_data(self, cart): """ purchase a cart with billing information """ diff --git a/lms/templates/emails/order_confirmation_email.txt b/lms/templates/emails/order_confirmation_email.txt new file mode 100644 index 0000000000..4bbc70491b --- /dev/null +++ b/lms/templates/emails/order_confirmation_email.txt @@ -0,0 +1,15 @@ +<%! from django.utils.translation import ugettext as _ %> + +${_("Thank you for your order! Payment was successful, and you should be able to see the results on your dashboard.")} + +${_("Your order number is: {order_number}").format(order_number=order.id)} + +${_("Items in your order:")} + +${_("Quantity - Description - Price")} +%for order_item in order_items: + ${order_item.qty} - ${order_item.line_desc} - $(order_item.line_cost} +%endfor +${_("Total: {total_cost}").format(total_cost=order.total_cost)} + +${_("If you have any issues, please contact us at {billing_email}").format(billing_email=settings.PAYMENT_SUPPORT_EMAIL)}