Merge pull request #5122 from edx/will/ecom-218
Change shopping cart status once sent to Cybersource (ECOM-218)
This commit is contained in:
@@ -40,8 +40,18 @@ from microsite_configuration import microsite
|
||||
log = logging.getLogger("shoppingcart")
|
||||
|
||||
ORDER_STATUSES = (
|
||||
# The user is selecting what he/she wants to purchase.
|
||||
('cart', 'cart'),
|
||||
|
||||
# The user has been sent to the external payment processor.
|
||||
# At this point, the order should NOT be modified.
|
||||
# If the user returns to the payment flow, he/she will start a new order.
|
||||
('paying', 'paying'),
|
||||
|
||||
# The user has successfully purchased the items in the order.
|
||||
('purchased', 'purchased'),
|
||||
|
||||
# The user's order has been refunded.
|
||||
('refunded', 'refunded'),
|
||||
)
|
||||
|
||||
@@ -129,6 +139,22 @@ class Order(models.Model):
|
||||
"""
|
||||
self.orderitem_set.all().delete()
|
||||
|
||||
@transaction.commit_on_success
|
||||
def start_purchase(self):
|
||||
"""
|
||||
Start the purchase process. This will set the order status to "paying",
|
||||
at which point it should no longer be modified.
|
||||
|
||||
Future calls to `Order.get_cart_for_user()` will filter out orders with
|
||||
status "paying", effectively creating a new (empty) cart.
|
||||
"""
|
||||
if self.status == 'cart':
|
||||
self.status = 'paying'
|
||||
self.save()
|
||||
|
||||
for item in OrderItem.objects.filter(order=self).select_subclasses():
|
||||
item.start_purchase()
|
||||
|
||||
def purchase(self, first='', last='', street1='', street2='', city='', state='', postalcode='',
|
||||
country='', ccnum='', cardtype='', processor_reply_dump=''):
|
||||
"""
|
||||
@@ -269,6 +295,14 @@ class OrderItem(models.Model):
|
||||
self.fulfilled_time = datetime.now(pytz.utc)
|
||||
self.save()
|
||||
|
||||
def start_purchase(self):
|
||||
"""
|
||||
Start the purchase process. This will set the order item status to "paying",
|
||||
at which point it should no longer be modified.
|
||||
"""
|
||||
self.status = 'paying'
|
||||
self.save()
|
||||
|
||||
def purchased_callback(self):
|
||||
"""
|
||||
This is called on each inventory item in the shopping cart when the
|
||||
|
||||
@@ -102,6 +102,38 @@ class OrderTest(ModuleStoreTestCase):
|
||||
self.assertEquals(cart.orderitem_set.count(), len(course_costs))
|
||||
self.assertEquals(cart.total_cost, sum(cost for _course, cost in course_costs))
|
||||
|
||||
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_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
|
||||
|
||||
@@ -31,8 +31,8 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
from student.tests.factories import UserFactory
|
||||
from student.models import CourseEnrollment
|
||||
from course_modes.tests.factories import CourseModeFactory
|
||||
from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE
|
||||
from course_modes.models import CourseMode
|
||||
from shoppingcart.models import Order, CertificateItem
|
||||
from verify_student.views import render_to_response
|
||||
from verify_student.models import SoftwareSecurePhotoVerification
|
||||
from reverification.tests.factories import MidcourseReverificationWindowFactory
|
||||
@@ -66,8 +66,8 @@ class StartView(TestCase):
|
||||
self.assertHttpForbidden(self.client.get(self.start_url()))
|
||||
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
|
||||
class TestCreateOrderView(TestCase):
|
||||
@override_settings(MODULESTORE=MODULESTORE_CONFIG)
|
||||
class TestCreateOrderView(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests for the create_order view of verified course registration process
|
||||
"""
|
||||
@@ -75,7 +75,7 @@ class TestCreateOrderView(TestCase):
|
||||
self.user = UserFactory.create(username="rusty", password="test")
|
||||
self.client.login(username="rusty", password="test")
|
||||
self.course_id = 'Robot/999/Test_Course'
|
||||
CourseFactory.create(org='Robot', number='999', display_name='Test Course')
|
||||
self.course = CourseFactory.create(org='Robot', number='999', display_name='Test Course')
|
||||
verified_mode = CourseMode(
|
||||
course_id=SlashSeparatedCourseKey("Robot", "999", 'Test_Course'),
|
||||
mode_slug="verified",
|
||||
@@ -156,6 +156,14 @@ class TestCreateOrderView(TestCase):
|
||||
self.assertTrue(json_response.get('success'))
|
||||
self.assertIsNotNone(json_response.get('orderNumber'))
|
||||
|
||||
# Verify that the order exists and is configured correctly
|
||||
order = Order.objects.get(user=self.user)
|
||||
self.assertEqual(order.status, 'paying')
|
||||
item = CertificateItem.objects.get(order=order)
|
||||
self.assertEqual(item.status, 'paying')
|
||||
self.assertEqual(item.course_id, self.course.id)
|
||||
self.assertEqual(item.mode, 'verified')
|
||||
|
||||
|
||||
@override_settings(MODULESTORE=MODULESTORE_CONFIG)
|
||||
class TestVerifyView(ModuleStoreTestCase):
|
||||
|
||||
@@ -227,6 +227,15 @@ def create_order(request):
|
||||
enrollment_mode = current_mode.slug
|
||||
CertificateItem.add_to_order(cart, course_id, 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")
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user