Commit 4fbf23b6 by Diana Huang

Merge pull request #810 from edx/diana/email-receipt

Email Receipts and Billing Information
parents 4e7c309d 1b5fde9d
import pytz import pytz
import logging import logging
import smtplib
from datetime import datetime from datetime import datetime
from django.db import models from django.db import models
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.db import transaction from django.db import transaction
from model_utils.managers import InheritanceManager from model_utils.managers import InheritanceManager
from courseware.courses import get_course_about_section 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.modulestore.django import modulestore
from xmodule.course_module import CourseDescriptor from xmodule.course_module import CourseDescriptor
...@@ -102,15 +106,16 @@ class Order(models.Model): ...@@ -102,15 +106,16 @@ class Order(models.Model):
self.purchase_time = datetime.now(pytz.utc) self.purchase_time = datetime.now(pytz.utc)
self.bill_to_first = first self.bill_to_first = first
self.bill_to_last = last self.bill_to_last = last
self.bill_to_street1 = street1
self.bill_to_street2 = street2
self.bill_to_city = city self.bill_to_city = city
self.bill_to_state = state self.bill_to_state = state
self.bill_to_postalcode = postalcode
self.bill_to_country = country self.bill_to_country = country
self.bill_to_ccnum = ccnum self.bill_to_postalcode = postalcode
self.bill_to_cardtype = cardtype if settings.MITX_FEATURES['STORE_BILLING_INFO']:
self.processor_reply_dump = processor_reply_dump 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 # save these changes on the order, then we can tell when we are in an
# inconsistent state # inconsistent state
self.save() self.save()
...@@ -119,6 +124,17 @@ class Order(models.Model): ...@@ -119,6 +124,17 @@ class Order(models.Model):
orderitems = OrderItem.objects.filter(order=self).select_subclasses() orderitems = OrderItem.objects.filter(order=self).select_subclasses()
for item in orderitems: for item in orderitems:
item.purchase_item() 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): class OrderItem(models.Model):
...@@ -305,8 +321,8 @@ class CertificateItem(OrderItem): ...@@ -305,8 +321,8 @@ class CertificateItem(OrderItem):
item.status = order.status item.status = order.status
item.qty = 1 item.qty = 1
item.unit_cost = cost item.unit_cost = cost
item.line_desc = "{mode} certificate for course {course_id}".format(mode=item.mode, item.line_desc = _("{mode} certificate for course {course_id}").format(mode=item.mode,
course_id=course_id) course_id=course_id)
item.currency = currency item.currency = currency
order.currency = currency order.currency = currency
order.save() order.save()
......
...@@ -116,14 +116,10 @@ class CyberSourceTests(TestCase): ...@@ -116,14 +116,10 @@ class CyberSourceTests(TestCase):
order2 = Order.get_cart_for_user(student2) order2 = Order.get_cart_for_user(student2)
record_purchase(params_cc, order1) record_purchase(params_cc, order1)
record_purchase(params_nocc, order2) 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.bill_to_first, student1.first_name)
self.assertEqual(order1.status, 'purchased') self.assertEqual(order1.status, 'purchased')
order2 = Order.objects.get(user=student2) 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.bill_to_first, student2.first_name)
self.assertEqual(order2.status, 'purchased') self.assertEqual(order2.status, 'purchased')
......
...@@ -6,6 +6,8 @@ from factory import DjangoModelFactory ...@@ -6,6 +6,8 @@ from factory import DjangoModelFactory
from mock import patch from mock import patch
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
from django.core import mail
from django.conf import settings
from django.db import DatabaseError from django.db import DatabaseError
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.factories import CourseFactory
...@@ -76,6 +78,12 @@ class OrderTest(TestCase): ...@@ -76,6 +78,12 @@ class OrderTest(TestCase):
cart.purchase() cart.purchase()
self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course_id)) 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): def test_purchase_item_failure(self):
# once again, we're testing against the specific implementation of # once again, we're testing against the specific implementation of
# CertificateItem # CertificateItem
...@@ -86,7 +94,56 @@ class OrderTest(TestCase): ...@@ -86,7 +94,56 @@ class OrderTest(TestCase):
cart.purchase() cart.purchase()
# verify that we rolled back the entire transaction # verify that we rolled back the entire transaction
self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course_id)) 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 """
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): class OrderItemTest(TestCase):
def setUp(self): def setUp(self):
......
...@@ -191,7 +191,6 @@ class ShoppingCartViewsTests(ModuleStoreTestCase): ...@@ -191,7 +191,6 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
resp = self.client.get(reverse('shoppingcart.views.show_receipt', args=[self.cart.id])) resp = self.client.get(reverse('shoppingcart.views.show_receipt', args=[self.cart.id]))
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
self.assertIn('FirstNameTesting123', resp.content) self.assertIn('FirstNameTesting123', resp.content)
self.assertIn('StreetTesting123', resp.content)
self.assertIn('80.00', resp.content) self.assertIn('80.00', resp.content)
((template, context), _) = render_mock.call_args ((template, context), _) = render_mock.call_args
......
...@@ -156,7 +156,10 @@ MITX_FEATURES = { ...@@ -156,7 +156,10 @@ MITX_FEATURES = {
'ENABLE_CHAT': False, 'ENABLE_CHAT': False,
# Toggle the availability of the shopping cart page # 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 # Used for A/B testing
......
<%! 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)}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment