Commit 8f4cf682 by Mike Dikan

Adding support for UTM Tracking to apply to free purhases

ECOM-6450

Updating commerce api endpoint to capture user UTM cookies and resend them in the server request to the ecommerce IDA.
Currently, the non-free purchases are tracked because the user makes the request directly on ecommerce and user cookie
contains UTM data, but for free purchases, the UTM cookie isn't sent in the background server request to the ecommerce
IDA.
parent a56b3049
...@@ -31,6 +31,11 @@ from student.tests.tests import EnrollmentEventTestMixin ...@@ -31,6 +31,11 @@ from student.tests.tests import EnrollmentEventTestMixin
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from commerce.api.v0.views import SAILTHRU_CAMPAIGN_COOKIE from commerce.api.v0.views import SAILTHRU_CAMPAIGN_COOKIE
UTM_COOKIE_NAME = 'edx.test.utm'
UTM_COOKIE_CONTENTS = {
'utm_source': 'test-source'
}
@attr(shard=1) @attr(shard=1)
@ddt.ddt @ddt.ddt
...@@ -39,7 +44,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) ...@@ -39,7 +44,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
""" """
Tests for the commerce orders view. Tests for the commerce orders view.
""" """
def _post_to_view(self, course_id=None, marketing_email_opt_in=False): def _post_to_view(self, course_id=None, marketing_email_opt_in=False, include_utm_cookie=False):
""" """
POST to the view being tested. POST to the view being tested.
...@@ -55,6 +60,8 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) ...@@ -55,6 +60,8 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
payload["email_opt_in"] = True payload["email_opt_in"] = True
self.client.cookies[SAILTHRU_CAMPAIGN_COOKIE] = 'sailthru id' self.client.cookies[SAILTHRU_CAMPAIGN_COOKIE] = 'sailthru id'
if include_utm_cookie:
self.client.cookies[UTM_COOKIE_NAME] = json.dumps(UTM_COOKIE_CONTENTS)
return self.client.post(self.url, payload) return self.client.post(self.url, payload)
def assertResponseMessage(self, response, expected_msg): def assertResponseMessage(self, response, expected_msg):
...@@ -160,12 +167,12 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) ...@@ -160,12 +167,12 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
self.assertValidEcommerceInternalRequestErrorResponse(response) self.assertValidEcommerceInternalRequestErrorResponse(response)
self.assertUserNotEnrolled() self.assertUserNotEnrolled()
def _test_successful_ecommerce_api_call(self, is_completed=True): def _test_successful_ecommerce_api_call(self, is_completed=True, utm_tracking_present=False):
""" """
Verifies that the view contacts the E-Commerce API with the correct data and headers. Verifies that the view contacts the E-Commerce API with the correct data and headers.
""" """
with mock.patch('commerce.api.v0.views.audit_log') as mock_audit_log: with mock.patch('commerce.api.v0.views.audit_log') as mock_audit_log:
response = self._post_to_view() response = self._post_to_view(include_utm_cookie=utm_tracking_present)
# Verify that an audit message was logged # Verify that an audit message was logged
self.assertTrue(mock_audit_log.called) self.assertTrue(mock_audit_log.called)
...@@ -177,9 +184,15 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) ...@@ -177,9 +184,15 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
else: else:
self.assertResponsePaymentData(response) self.assertResponsePaymentData(response)
# make sure ecommerce API call forwards Sailthru cookie # Make sure ecommerce API call forwards Sailthru cookie
self.assertIn('{}=sailthru id'.format(SAILTHRU_CAMPAIGN_COOKIE), httpretty.last_request().headers['cookie']) self.assertIn('{}=sailthru id'.format(SAILTHRU_CAMPAIGN_COOKIE), httpretty.last_request().headers['cookie'])
# Check that UTM tracking cookie is passed along in request to ecommerce for attribution
if utm_tracking_present:
cookie_string = '{cookie_name}={cookie_contents}'.format(
cookie_name=UTM_COOKIE_NAME, cookie_contents=json.dumps(UTM_COOKIE_CONTENTS))
self.assertIn(cookie_string, httpretty.last_request().headers['cookie'])
@ddt.data(True, False) @ddt.data(True, False)
def test_course_with_honor_seat_sku(self, user_is_active): def test_course_with_honor_seat_sku(self, user_is_active):
""" """
...@@ -193,7 +206,14 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) ...@@ -193,7 +206,14 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
return_value = {'id': TEST_BASKET_ID, 'payment_data': None, 'order': {'number': TEST_ORDER_NUMBER}} return_value = {'id': TEST_BASKET_ID, 'payment_data': None, 'order': {'number': TEST_ORDER_NUMBER}}
with mock_create_basket(response=return_value): with mock_create_basket(response=return_value):
# Test that call without utm tracking works
self._test_successful_ecommerce_api_call() self._test_successful_ecommerce_api_call()
with mock.patch('student.models.RegistrationCookieConfiguration.current') as config:
instance = config.return_value
instance.utm_cookie_name = UTM_COOKIE_NAME
# Test that call with cookie passes cookie along
self._test_successful_ecommerce_api_call(utm_tracking_present=True)
@ddt.data(True, False) @ddt.data(True, False)
def test_course_with_paid_seat_sku(self, user_is_active): def test_course_with_paid_seat_sku(self, user_is_active):
...@@ -207,7 +227,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) ...@@ -207,7 +227,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
return_value = {'id': TEST_BASKET_ID, 'payment_data': TEST_PAYMENT_DATA, 'order': None} return_value = {'id': TEST_BASKET_ID, 'payment_data': TEST_PAYMENT_DATA, 'order': None}
with mock_create_basket(response=return_value): with mock_create_basket(response=return_value):
self._test_successful_ecommerce_api_call(False) self._test_successful_ecommerce_api_call(is_completed=False)
def _test_course_without_sku(self, enrollment_mode=CourseMode.DEFAULT_MODE_SLUG): def _test_course_without_sku(self, enrollment_mode=CourseMode.DEFAULT_MODE_SLUG):
""" """
...@@ -334,7 +354,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase) ...@@ -334,7 +354,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
self.assertIsNotNone(get_enrollment(self.user.username, unicode(self.course.id))) self.assertIsNotNone(get_enrollment(self.user.username, unicode(self.course.id)))
with mock_create_basket(): with mock_create_basket():
self._test_successful_ecommerce_api_call(False) self._test_successful_ecommerce_api_call(is_completed=False)
@mock.patch('commerce.api.v0.views.update_email_opt_in') @mock.patch('commerce.api.v0.views.update_email_opt_in')
@ddt.data(*itertools.product((False, True), (False, True), (False, True))) @ddt.data(*itertools.product((False, True), (False, True), (False, True)))
......
...@@ -22,7 +22,7 @@ from openedx.core.djangoapps.commerce.utils import ecommerce_api_client ...@@ -22,7 +22,7 @@ from openedx.core.djangoapps.commerce.utils import ecommerce_api_client
from openedx.core.djangoapps.user_api.preferences.api import update_email_opt_in from openedx.core.djangoapps.user_api.preferences.api import update_email_opt_in
from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser
from openedx.core.lib.log_utils import audit_log from openedx.core.lib.log_utils import audit_log
from student.models import CourseEnrollment from student.models import CourseEnrollment, RegistrationCookieConfiguration
from util.json_request import JsonResponse from util.json_request import JsonResponse
...@@ -150,13 +150,11 @@ class BasketsView(APIView): ...@@ -150,13 +150,11 @@ class BasketsView(APIView):
# Make the API call # Make the API call
try: try:
# Pass along Sailthru campaign id # Pass along Sailthru campaign id
campaign_cookie = request.COOKIES.get(SAILTHRU_CAMPAIGN_COOKIE) self._add_request_cookie_to_api_session(api_session, request, SAILTHRU_CAMPAIGN_COOKIE)
if campaign_cookie:
cookie = {SAILTHRU_CAMPAIGN_COOKIE: campaign_cookie} # Pass along UTM tracking info
if api_session.cookies: utm_cookie_name = RegistrationCookieConfiguration.current().utm_cookie_name
requests.utils.add_dict_to_cookiejar(api_session.cookies, cookie) self._add_request_cookie_to_api_session(api_session, request, utm_cookie_name)
else:
api_session.cookies = requests.utils.cookiejar_from_dict(cookie)
response_data = api.baskets.post({ response_data = api.baskets.post({
'products': [{'sku': default_enrollment_mode.sku}], 'products': [{'sku': default_enrollment_mode.sku}],
...@@ -194,6 +192,18 @@ class BasketsView(APIView): ...@@ -194,6 +192,18 @@ class BasketsView(APIView):
self._handle_marketing_opt_in(request, course_key, user) self._handle_marketing_opt_in(request, course_key, user)
return response return response
def _add_request_cookie_to_api_session(self, server_session, request, cookie_name):
""" Add cookie from user request into server session """
user_cookie = None
if cookie_name:
user_cookie = request.COOKIES.get(cookie_name)
if user_cookie:
server_cookie = {cookie_name: user_cookie}
if server_session.cookies:
requests.utils.add_dict_to_cookiejar(server_session.cookies, server_cookie)
else:
server_session.cookies = requests.utils.cookiejar_from_dict(server_cookie)
class BasketOrderView(APIView): class BasketOrderView(APIView):
""" Retrieve the order associated with a basket. """ """ Retrieve the order associated with a basket. """
......
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