Beispiel #1
0
 def test_make_reference_id(self):
     """
     make_reference_id should concatenate the reference prefix and the order id
     """
     course_run, user = create_purchasable_course_run()
     order = create_unfulfilled_order(course_run.edx_course_key, user)
     assert "MM-{}-{}".format(CYBERSOURCE_REFERENCE_PREFIX, order.id) == make_reference_id(order)
Beispiel #2
0
 def test_make_reference_id(self):
     """
     make_reference_id should concatenate the reference prefix and the order id
     """
     course_run, user = create_purchasable_course_run()
     order = create_unfulfilled_order(course_run.edx_course_key, user)
     assert "MM-{}-{}".format(CYBERSOURCE_REFERENCE_PREFIX, order.id) == make_reference_id(order)
Beispiel #3
0
    def test_not_accept(self, decision, should_send_email):
        """
        If the decision is not ACCEPT then the order should be marked as failed
        """
        course_run, user = create_purchasable_course_run()
        order = create_unfulfilled_order(course_run.edx_course_key, user)

        data = {
            'req_reference_number': make_reference_id(order),
            'decision': decision,
        }
        with patch(
            'ecommerce.views.IsSignedByCyberSource.has_permission',
            return_value=True
        ), patch(
            'ecommerce.views.MailgunClient.send_individual_email',
        ) as send_email:
            resp = self.client.post(reverse('order-fulfillment'), data=data)
        assert resp.status_code == status.HTTP_200_OK
        assert len(resp.content) == 0
        order.refresh_from_db()
        assert Order.objects.count() == 1
        assert order.status == Order.FAILED

        if should_send_email:
            assert send_email.call_count == 1
            assert send_email.call_args[0] == (
                'Order fulfillment failed, decision={decision}'.format(decision='something else'),
                'Order fulfillment failed for order {order}'.format(order=order),
                '*****@*****.**',
            )
        else:
            assert send_email.call_count == 0
    def test_not_accept(self, decision, should_send_email):
        """
        If the decision is not ACCEPT then the order should be marked as failed
        """
        course_run, user = create_purchasable_course_run()
        order = create_unfulfilled_order(course_run.edx_course_key, user)

        data = {
            'req_reference_number': make_reference_id(order),
            'decision': decision,
        }
        with patch('ecommerce.views.IsSignedByCyberSource.has_permission',
                   return_value=True), patch(
                       'ecommerce.views.MailgunClient.send_individual_email',
                   ) as send_email:
            resp = self.client.post(reverse('order-fulfillment'), data=data)
        assert resp.status_code == status.HTTP_200_OK
        assert len(resp.content) == 0
        order.refresh_from_db()
        assert Order.objects.count() == 1
        assert order.status == Order.FAILED

        if should_send_email:
            assert send_email.call_count == 1
            assert send_email.call_args[0] == (
                'Order fulfillment failed, decision={decision}'.format(
                    decision='something else'),
                'Order fulfillment failed for order {order}'.format(
                    order=order),
                '*****@*****.**',
            )
        else:
            assert send_email.call_count == 0
    def test_failed_enroll(self):
        """
        If we fail to enroll in edX, the order status should be fulfilled but an error email should be sent
        """
        course_run, user = create_purchasable_course_run()
        order = create_unfulfilled_order(course_run.edx_course_key, user)

        data = {}
        for _ in range(5):
            data[FAKE.text()] = FAKE.text()

        data['req_reference_number'] = make_reference_id(order)
        data['decision'] = 'ACCEPT'

        with patch(
                'ecommerce.views.IsSignedByCyberSource.has_permission',
                return_value=True), patch(
                    'ecommerce.views.enroll_user_on_success',
                    side_effect=KeyError), patch(
                        'ecommerce.views.MailgunClient.send_individual_email',
                    ) as send_email:
            self.client.post(reverse('order-fulfillment'), data=data)

        assert Order.objects.count() == 1
        # An enrollment failure should not prevent the order from being fulfilled
        order = Order.objects.first()
        assert order.status == Order.FULFILLED

        assert send_email.call_count == 1
        assert send_email.call_args[0][
            0] == 'Error occurred when enrolling user during order fulfillment'
        assert send_email.call_args[0][1].startswith(
            'Error occurred when enrolling user during order fulfillment for {order}. '
            'Exception: '.format(order=order, ))
        assert send_email.call_args[0][2] == '*****@*****.**'
Beispiel #6
0
    def test_failed_enroll(self):
        """
        If we fail to enroll in edX, the order status should be fulfilled but an error email should be sent
        """
        course_run, user = create_purchasable_course_run()
        order = create_unfulfilled_order(course_run.edx_course_key, user)

        data = {}
        for _ in range(5):
            data[FAKE.text()] = FAKE.text()

        data['req_reference_number'] = make_reference_id(order)
        data['decision'] = 'ACCEPT'

        with patch('ecommerce.views.IsSignedByCyberSource.has_permission', return_value=True), patch(
            'ecommerce.views.enroll_user_on_success', side_effect=KeyError
        ), patch(
            'ecommerce.views.MailgunClient.send_individual_email',
        ) as send_email:
            self.client.post(reverse('order-fulfillment'), data=data)

        assert Order.objects.count() == 1
        # An enrollment failure should not prevent the order from being fulfilled
        order = Order.objects.first()
        assert order.status == Order.FULFILLED

        assert send_email.call_count == 1
        assert send_email.call_args[0][0] == 'Error occurred when enrolling user during order fulfillment'
        assert send_email.call_args[0][1].startswith(
            'Error occurred when enrolling user during order fulfillment for {order}. '
            'Exception: '.format(
                order=order,
            )
        )
        assert send_email.call_args[0][2] == '*****@*****.**'
Beispiel #7
0
 def test_get_new_order_by_reference_number(self):
     """
     get_new_order_by_reference_number returns an Order with status created
     """
     course_run, user = create_purchasable_course_run()
     order = create_unfulfilled_order(course_run.edx_course_key, user)
     same_order = get_new_order_by_reference_number(make_reference_id(order))
     assert same_order.id == order.id
Beispiel #8
0
 def test_get_new_order_by_reference_number(self):
     """
     get_new_order_by_reference_number returns an Order with status created
     """
     course_run, user = create_purchasable_course_run()
     order = create_unfulfilled_order(course_run.edx_course_key, user)
     same_order = get_new_order_by_reference_number(make_reference_id(order))
     assert same_order.id == order.id
Beispiel #9
0
def gen_fake_receipt_data(order=None):
    """
    Helper function to generate a fake signed piece of data
    """
    data = {}
    for _ in range(10):
        data[FAKE.text()] = FAKE.text()
    keys = sorted(data.keys())
    data['signed_field_names'] = ",".join(keys)
    data['unsigned_field_names'] = ''
    data['req_reference_number'] = make_reference_id(order) if order else ''
    data['signature'] = generate_cybersource_sa_signature(data)
    return data
Beispiel #10
0
def gen_fake_receipt_data(order=None):
    """
    Helper function to generate a fake signed piece of data
    """
    data = {}
    for _ in range(10):
        data[FAKE.text()] = FAKE.text()
    keys = sorted(data.keys())
    data['signed_field_names'] = ",".join(keys)
    data['unsigned_field_names'] = ''
    data['req_reference_number'] = make_reference_id(order) if order else ''
    data['signature'] = generate_cybersource_sa_signature(data)
    return data
Beispiel #11
0
    def test_signed_payload(self):
        """
        A valid payload should be signed appropriately
        """
        course_run, user = create_purchasable_course_run()
        order = create_unfulfilled_order(course_run.edx_course_key, user)
        username = '******'
        transaction_uuid = 'hex'
        fake_user_ip = "194.100.0.1"

        now = now_in_utc()

        with patch('ecommerce.api.get_social_username', autospec=True, return_value=username):
            with patch('ecommerce.api.now_in_utc', autospec=True, return_value=now) as now_mock:
                with patch('ecommerce.api.uuid.uuid4', autospec=True, return_value=MagicMock(hex=transaction_uuid)):
                    payload = generate_cybersource_sa_payload(order, 'dashboard_url', fake_user_ip)
        signature = payload.pop('signature')
        assert generate_cybersource_sa_signature(payload) == signature
        signed_field_names = payload['signed_field_names'].split(',')
        assert signed_field_names == sorted(payload.keys())
        quoted_course_key = quote_plus(course_run.edx_course_key)

        assert payload == {
            'access_key': CYBERSOURCE_ACCESS_KEY,
            'amount': str(order.total_price_paid),
            'consumer_id': username,
            'currency': 'USD',
            'item_0_code': 'course',
            'item_0_name': '{}'.format(course_run.title),
            'item_0_quantity': 1,
            'item_0_sku': '{}'.format(course_run.edx_course_key),
            'item_0_tax_amount': '0',
            'item_0_unit_price': str(order.total_price_paid),
            'line_item_count': 1,
            'locale': 'en-us',
            'override_custom_cancel_page': 'dashboard_url?status=cancel&course_key={}'.format(quoted_course_key),
            'override_custom_receipt_page': "dashboard_url?status=receipt&course_key={}".format(quoted_course_key),
            'reference_number': make_reference_id(order),
            'profile_id': CYBERSOURCE_PROFILE_ID,
            'signed_date_time': now.strftime(ISO_8601_FORMAT),
            'signed_field_names': ','.join(signed_field_names),
            'transaction_type': 'sale',
            'transaction_uuid': transaction_uuid,
            'unsigned_field_names': '',
            'merchant_defined_data1': 'course',
            'merchant_defined_data2': '{}'.format(course_run.title),
            'merchant_defined_data3': '{}'.format(course_run.edx_course_key),
            "customer_ip_address": fake_user_ip if fake_user_ip else None,
        }
        assert now_mock.called
Beispiel #12
0
    def test_signed_payload(self):
        """
        A valid payload should be signed appropriately
        """
        course_run, user = create_purchasable_course_run()
        order = create_unfulfilled_order(course_run.edx_course_key, user)
        username = '******'
        transaction_uuid = 'hex'

        now = now_in_utc()

        with patch('ecommerce.api.get_social_username', autospec=True, return_value=username):
            with patch('ecommerce.api.now_in_utc', autospec=True, return_value=now) as now_mock:
                with patch('ecommerce.api.uuid.uuid4', autospec=True, return_value=MagicMock(hex=transaction_uuid)):
                    payload = generate_cybersource_sa_payload(order, 'dashboard_url')
        signature = payload.pop('signature')
        assert generate_cybersource_sa_signature(payload) == signature
        signed_field_names = payload['signed_field_names'].split(',')
        assert signed_field_names == sorted(payload.keys())
        quoted_course_key = quote_plus(course_run.edx_course_key)

        assert payload == {
            'access_key': CYBERSOURCE_ACCESS_KEY,
            'amount': str(order.total_price_paid),
            'consumer_id': username,
            'currency': 'USD',
            'item_0_code': 'course',
            'item_0_name': '{}'.format(course_run.title),
            'item_0_quantity': 1,
            'item_0_sku': '{}'.format(course_run.edx_course_key),
            'item_0_tax_amount': '0',
            'item_0_unit_price': str(order.total_price_paid),
            'line_item_count': 1,
            'locale': 'en-us',
            'override_custom_cancel_page': 'dashboard_url?status=cancel&course_key={}'.format(quoted_course_key),
            'override_custom_receipt_page': "dashboard_url?status=receipt&course_key={}".format(quoted_course_key),
            'reference_number': make_reference_id(order),
            'profile_id': CYBERSOURCE_PROFILE_ID,
            'signed_date_time': now.strftime(ISO_8601_FORMAT),
            'signed_field_names': ','.join(signed_field_names),
            'transaction_type': 'sale',
            'transaction_uuid': transaction_uuid,
            'unsigned_field_names': '',
            'merchant_defined_data1': 'course',
            'merchant_defined_data2': '{}'.format(course_run.title),
            'merchant_defined_data3': '{}'.format(course_run.edx_course_key),
        }
        assert now_mock.called
Beispiel #13
0
    def test_status(self):
        """
        get_order_by_reference_number should only get orders with status=CREATED
        """
        course_run, user = create_purchasable_course_run()
        order = create_unfulfilled_order(course_run.edx_course_key, user)

        order.status = Order.FAILED
        order.save()

        with self.assertRaises(EcommerceException) as ex:
            # change order number to something not likely to already exist in database
            order.id = 98765432
            assert not Order.objects.filter(id=order.id).exists()
            get_new_order_by_reference_number(make_reference_id(order))
        assert ex.exception.args[0] == "Unable to find order {}".format(order.id)
Beispiel #14
0
    def test_status(self):
        """
        get_order_by_reference_number should only get orders with status=CREATED
        """
        course_run, user = create_purchasable_course_run()
        order = create_unfulfilled_order(course_run.edx_course_key, user)

        order.status = Order.FAILED
        order.save()

        with self.assertRaises(EcommerceException) as ex:
            # change order number to something not likely to already exist in database
            order.id = 98765432
            assert not Order.objects.filter(id=order.id).exists()
            get_new_order_by_reference_number(make_reference_id(order))
        assert ex.exception.args[0] == "Unable to find order {}".format(order.id)
    def test_ignore_duplicate_cancel(self):
        """
        If the decision is CANCEL and we already have a duplicate failed order, don't change anything.
        """
        course_run, user = create_purchasable_course_run()
        order = create_unfulfilled_order(course_run.edx_course_key, user)
        order.status = Order.FAILED
        order.save()

        data = {
            'req_reference_number': make_reference_id(order),
            'decision': 'CANCEL',
        }
        with patch('ecommerce.views.IsSignedByCyberSource.has_permission',
                   return_value=True):
            resp = self.client.post(reverse('order-fulfillment'), data=data)
        assert resp.status_code == status.HTTP_200_OK

        assert Order.objects.count() == 1
        assert Order.objects.get(id=order.id).status == Order.FAILED
    def test_error_on_duplicate_order(self, order_status, decision):
        """If there is a duplicate message (except for CANCEL), raise an exception"""
        course_run, user = create_purchasable_course_run()
        order = create_unfulfilled_order(course_run.edx_course_key, user)
        order.status = order_status
        order.save()

        data = {
            'req_reference_number': make_reference_id(order),
            'decision': decision,
        }
        with patch('ecommerce.views.IsSignedByCyberSource.has_permission',
                   return_value=True), self.assertRaises(
                       EcommerceException) as ex:
            self.client.post(reverse('order-fulfillment'), data=data)

        assert Order.objects.count() == 1
        assert Order.objects.get(id=order.id).status == order_status

        assert ex.exception.args[
            0] == "Order {id} is expected to have status 'created'".format(
                id=order.id, )
Beispiel #17
0
    def test_ignore_duplicate_cancel(self):
        """
        If the decision is CANCEL and we already have a duplicate failed order, don't change anything.
        """
        course_run, user = create_purchasable_course_run()
        order = create_unfulfilled_order(course_run.edx_course_key, user)
        order.status = Order.FAILED
        order.save()

        data = {
            'req_reference_number': make_reference_id(order),
            'decision': 'CANCEL',
        }
        with patch(
            'ecommerce.views.IsSignedByCyberSource.has_permission',
            return_value=True
        ):
            resp = self.client.post(reverse('order-fulfillment'), data=data)
        assert resp.status_code == status.HTTP_200_OK

        assert Order.objects.count() == 1
        assert Order.objects.get(id=order.id).status == Order.FAILED
    def test_order_fulfilled(self):
        """
        Test the happy case
        """
        course_run, user = create_purchasable_course_run()
        order = create_unfulfilled_order(course_run.edx_course_key, user)
        data_before = order.to_dict()

        data = {}
        for _ in range(5):
            data[FAKE.text()] = FAKE.text()

        data['req_reference_number'] = make_reference_id(order)
        data['decision'] = 'ACCEPT'

        with patch('ecommerce.views.IsSignedByCyberSource.has_permission',
                   return_value=True), patch(
                       'ecommerce.views.enroll_user_on_success',
                       autospec=True,
                   ) as enroll_user, patch(
                       'ecommerce.views.MailgunClient.send_individual_email',
                   ) as send_email:
            resp = self.client.post(reverse('order-fulfillment'), data=data)

        assert len(resp.content) == 0
        assert resp.status_code == status.HTTP_200_OK
        order.refresh_from_db()
        assert order.status == Order.FULFILLED
        assert order.receipt_set.count() == 1
        assert order.receipt_set.first().data == data
        enroll_user.assert_called_with(order)

        assert send_email.call_count == 0

        assert OrderAudit.objects.count() == 2
        order_audit = OrderAudit.objects.last()
        assert order_audit.order == order
        assert order_audit.data_before == data_before
        assert order_audit.data_after == order.to_dict()
Beispiel #19
0
    def test_error_on_duplicate_order(self, order_status, decision):
        """If there is a duplicate message (except for CANCEL), raise an exception"""
        course_run, user = create_purchasable_course_run()
        order = create_unfulfilled_order(course_run.edx_course_key, user)
        order.status = order_status
        order.save()

        data = {
            'req_reference_number': make_reference_id(order),
            'decision': decision,
        }
        with patch(
            'ecommerce.views.IsSignedByCyberSource.has_permission',
            return_value=True
        ), self.assertRaises(EcommerceException) as ex:
            self.client.post(reverse('order-fulfillment'), data=data)

        assert Order.objects.count() == 1
        assert Order.objects.get(id=order.id).status == order_status

        assert ex.exception.args[0] == "Order {id} is expected to have status 'created'".format(
            id=order.id,
        )
Beispiel #20
0
    def test_order_fulfilled(self):
        """
        Test the happy case
        """
        course_run, user = create_purchasable_course_run()
        order = create_unfulfilled_order(course_run.edx_course_key, user)
        data_before = order.to_dict()

        data = {}
        for _ in range(5):
            data[FAKE.text()] = FAKE.text()

        data['req_reference_number'] = make_reference_id(order)
        data['decision'] = 'ACCEPT'

        with patch('ecommerce.views.IsSignedByCyberSource.has_permission', return_value=True), patch(
            'ecommerce.views.enroll_user_on_success', autospec=True,
        ) as enroll_user, patch(
            'ecommerce.views.MailgunClient.send_individual_email',
        ) as send_email:
            resp = self.client.post(reverse('order-fulfillment'), data=data)

        assert len(resp.content) == 0
        assert resp.status_code == status.HTTP_200_OK
        order.refresh_from_db()
        assert order.status == Order.FULFILLED
        assert order.receipt_set.count() == 1
        assert order.receipt_set.first().data == data
        enroll_user.assert_called_with(order)

        assert send_email.call_count == 0

        assert OrderAudit.objects.count() == 2
        order_audit = OrderAudit.objects.last()
        assert order_audit.order == order
        assert order_audit.data_before == data_before
        assert order_audit.data_after == order.to_dict()
Beispiel #21
0
    def test_signed_payload(self):
        """
        A valid payload should be signed appropriately
        """
        course_run, user = create_purchasable_course_run()
        order = create_unfulfilled_order(course_run.edx_course_key, user)
        username = '******'
        transaction_uuid = 'hex'

        now = datetime.utcnow()

        with patch('ecommerce.api.get_social_username', autospec=True, return_value=username):
            with patch('ecommerce.api.datetime', autospec=True, utcnow=MagicMock(return_value=now)):
                with patch('ecommerce.api.uuid.uuid4', autospec=True, return_value=MagicMock(hex=transaction_uuid)):
                    payload = generate_cybersource_sa_payload(order)
        signature = payload.pop('signature')
        assert generate_cybersource_sa_signature(payload) == signature
        signed_field_names = payload['signed_field_names'].split(',')
        assert signed_field_names == sorted(payload.keys())

        assert payload == {
            'access_key': CYBERSOURCE_ACCESS_KEY,
            'amount': str(order.total_price_paid),
            'consumer_id': username,
            'currency': 'USD',
            'locale': 'en-us',
            'override_custom_cancel_page': 'https://micromasters.mit.edu?cancel',
            'override_custom_receipt_page': "https://micromasters.mit.edu?receipt",
            'reference_number': make_reference_id(order),
            'profile_id': CYBERSOURCE_PROFILE_ID,
            'signed_date_time': now.strftime(ISO_8601_FORMAT),
            'signed_field_names': ','.join(signed_field_names),
            'transaction_type': 'sale',
            'transaction_uuid': transaction_uuid,
            'unsigned_field_names': '',
        }