Пример #1
0
 def setUp(self):
     super(InvoiceTests, self).setUp()
     self.basket = create_basket(owner=factories.UserFactory(), empty=True)
     self.basket.order = factories.OrderFactory()
     self.basket.save()
     self.invoice = Invoice.objects.create(order=self.basket.order,
                                           state='Paid')
Пример #2
0
    def test_handle_shipping_event_email_opt_in(self, expected_opt_in):
        """
        Verify that the shipping sends email opt in if specified.
        """
        basket = create_basket()
        product = factories.create_product()
        factories.create_stockrecord(product, num_in_stock=2)
        basket.add_product(product)

        order = create_order(basket=basket)
        lines = order.lines.all()

        with mock.patch(
                'ecommerce.extensions.order.processing.fulfillment_api.fulfill_order'
        ) as mock_fulfill:
            EventHandler().handle_shipping_event(
                order,
                self.shipping_event_type,
                lines,
                [1, 1],
                email_opt_in=expected_opt_in,
            )
            mock_fulfill.assert_called_once_with(order,
                                                 lines,
                                                 email_opt_in=expected_opt_in)
Пример #3
0
    def test_handle_payment_logging(self, __):
        """
        Ensure that we emit a log entry upon receipt of a payment notification, and create Source and PaymentEvent
        objects.
        """
        basket = create_basket(owner=self.user, site=self.site)

        mixin = EdxOrderPlacementMixin()
        mixin.payment_processor = DummyProcessor(self.site)
        processor_name = DummyProcessor.NAME
        total = basket.total_incl_tax
        reference = basket.id

        with LogCapture(LOGGER_NAME) as logger:
            mixin.handle_payment({}, basket)
            logger.check_present((
                LOGGER_NAME, 'INFO',
                'payment_received: amount="{}", basket_id="{}", currency="{}", '
                'processor_name="{}", reference="{}", user_id="{}"'.format(
                    total, basket.id, basket.currency, processor_name,
                    reference, self.user.id)))

        # pylint: disable=protected-access

        # Validate a payment Source was created
        source_type = SourceType.objects.get(code=processor_name)
        label = self.user.username
        self.assert_basket_matches_source(basket, mixin._payment_sources[-1],
                                          source_type, reference, label)

        # Validate the PaymentEvent was created
        paid_type = PaymentEventType.objects.get(code='paid')
        self.assert_valid_payment_event_fields(mixin._payment_events[-1],
                                               total, paid_type,
                                               processor_name, reference)
Пример #4
0
 def _create_basket_with_product(self):
     basket = create_basket(empty=True, site=self.site)
     course = CourseFactory()
     seat = course.create_or_update_seat('verified', True, 100,
                                         self.partner)
     basket.add_product(seat)
     return basket
Пример #5
0
    def test_no_successful_transaction(self):
        """ Test utility when basket have no successful payment processor response."""
        basket = create_basket(site=self.site)
        basket.status = 'Frozen'
        basket.save()

        assert not FulfillFrozenBaskets().fulfill_basket(basket.id, self.site)
Пример #6
0
    def test_notify_purchaser(self, mock_task):
        """ Verify the notification is scheduled if the site has notifications enabled
        and the refund is for a course seat.
        """
        site_configuration = self.site.siteconfiguration
        site_configuration.send_refund_notifications = True

        user = UserFactory()

        course = CourseFactory(partner=self.partner)
        price = Decimal(100.00)
        product = course.create_or_update_seat('verified', True, price)

        basket = create_basket(site=self.site, owner=user, empty=True)
        basket.add_product(product)

        order = create_order(basket=basket, user=user)
        order_url = get_receipt_page_url(site_configuration, order.number)

        refund = Refund.create_with_lines(order, order.lines.all())

        with LogCapture(REFUND_MODEL_LOGGER_NAME) as logger:
            refund._notify_purchaser()  # pylint: disable=protected-access

        msg = 'Course refund notification scheduled for Refund [{}].'.format(refund.id)
        logger.check_present(
            (REFUND_MODEL_LOGGER_NAME, 'INFO', msg)
        )

        amount = format_currency(order.currency, price)
        mock_task.assert_called_once_with(
            user.email, refund.id, amount, course.name, order.number, order_url, site_code=self.partner.short_code
        )
Пример #7
0
    def test_successful_order_for_bulk_purchase(self):
        """
        Verify the view redirects to the Receipt page when the Order has been
        successfully placed for bulk purchase and also that the order is linked
        to the provided business client.
        """
        toggle_switch(ENROLLMENT_CODE_SWITCH, True)

        course = CourseFactory()
        course.create_or_update_seat('verified', True, 50, create_enrollment_code=True)
        enrollment_code = Product.objects.get(product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME)
        self.basket = create_basket(owner=self.user, site=self.site)
        self.basket.add_product(enrollment_code, quantity=1)

        # The basket should not have an associated order if no payment was made.
        self.assertFalse(Order.objects.filter(basket=self.basket).exists())

        request_data = self.generate_notification(
            self.basket,
            billing_address=self.billing_address,
        )
        request_data.update({'organization': 'Dummy Business Client'})
        request_data.update({PURCHASER_BEHALF_ATTRIBUTE: "False"})
        # Manually add organization and purchaser attributes on the basket for testing
        basket_add_organization_attribute(self.basket, request_data)

        response = self.client.post(self.path, request_data)
        self.assertTrue(Order.objects.filter(basket=self.basket).exists())
        self.assertEqual(response.status_code, 302)

        # Now verify that a new business client has been created and current
        # order is now linked with that client through Invoice model.
        order = Order.objects.filter(basket=self.basket).first()
        business_client = BusinessClient.objects.get(name=request_data['organization'])
        assert Invoice.objects.get(order=order).business_client == business_client
Пример #8
0
 def setUp(self):
     super(SDNCheckViewSetTests, self).setUp()
     user = self.create_user()
     self.client.login(username=user.username, password=self.password)
     self.site.siteconfiguration.enable_sdn_check = True
     self.site.siteconfiguration.save()
     self.basket = create_basket(owner=user, site=self.site)
Пример #9
0
    def test_request_apple_pay_authorization(self):
        """ The method should authorize and settle an Apple Pay payment with CyberSource. """
        basket = create_basket(owner=self.create_user(), site=self.site)

        billing_address = factories.BillingAddressFactory()
        payment_token = {
            'paymentData': {
                'version': 'EC_v1',
                'data': 'fake-data',
                'signature': 'fake-signature',
                'header': {
                    'ephemeralPublicKey': 'fake-key',
                    'publicKeyHash': 'fake-hash',
                    'transactionId': 'abc123'
                }
            },
            'paymentMethod': {
                'displayName': 'AmEx 1086',
                'network': 'AmEx',
                'type': 'credit'
            },
            'transactionIdentifier': 'DEADBEEF'
        }

        self.mock_cybersource_wsdl()
        self.mock_authorization_response(accepted=True)

        actual = self.processor.request_apple_pay_authorization(
            basket, billing_address, payment_token)
        self.assertEqual(actual.total, basket.total_incl_tax)
        self.assertEqual(actual.currency, basket.currency)
        self.assertEqual(actual.card_number, 'Apple Pay')
        self.assertEqual(actual.card_type, 'american_express')
Пример #10
0
    def test_non_free_basket_order(self, __):
        """ Verify an error is raised for non-free basket. """
        basket = create_basket(empty=True)
        basket.add_product(ProductFactory(stockrecords__price_excl_tax=10))

        with self.assertRaises(BasketNotFreeError):
            EdxOrderPlacementMixin().place_free_order(basket)
Пример #11
0
    def test_flush_without_product(self):
        """ Verify the method does not fireSegment event when basket is empty """
        basket = create_basket(empty=True, site=self.site)

        with mock.patch.object(Client, 'track') as mock_track:
            basket.flush()
            self.assertEqual(mock_track.call_count, 0)
Пример #12
0
    def test_request_apple_pay_authorization_rejected(self):
        """ The method should raise GatewayError if CyberSource rejects the payment. """
        self.mock_cybersource_wsdl()
        self.mock_authorization_response(accepted=False)

        basket = create_basket(site=self.site, owner=self.create_user())

        billing_address = factories.BillingAddressFactory()
        payment_token = {
            'paymentData': {
                'version': 'EC_v1',
                'data': 'fake-data',
                'signature': 'fake-signature',
                'header': {
                    'ephemeralPublicKey': 'fake-key',
                    'publicKeyHash': 'fake-hash',
                    'transactionId': 'abc123'
                }
            },
            'paymentMethod': {
                'displayName': 'AmEx 1086',
                'network': 'AmEx',
                'type': 'credit'
            },
            'transactionIdentifier': 'DEADBEEF'
        }

        with self.assertRaises(GatewayError):
            self.processor.request_apple_pay_authorization(
                basket, billing_address, payment_token)
    def test_notify_purchaser_course_entielement(self, mock_task):
        """ Verify the notification is scheduled if the site has notifications enabled
        and the refund is for a course entitlement.
        """
        site_configuration = self.site.siteconfiguration
        site_configuration.send_refund_notifications = True

        user = UserFactory()

        course_entitlement = create_or_update_course_entitlement(
            'verified', 100, self.partner, '111-222-333-444',
            'Course Entitlement')
        basket = create_basket(site=self.site, owner=user, empty=True)
        basket.add_product(course_entitlement, 1)

        order = create_order(number=1, basket=basket, user=user)
        order_url = get_receipt_page_url(site_configuration, order.number)

        refund = Refund.create_with_lines(order, order.lines.all())

        with LogCapture(REFUND_MODEL_LOGGER_NAME) as l:
            refund._notify_purchaser()  # pylint: disable=protected-access

        msg = 'Course refund notification scheduled for Refund [{}].'.format(
            refund.id)
        l.check((REFUND_MODEL_LOGGER_NAME, 'INFO', msg))

        amount = format_currency(order.currency, 100)
        mock_task.assert_called_once_with(user.email,
                                          refund.id,
                                          amount,
                                          course_entitlement.title,
                                          order.number,
                                          order_url,
                                          site_code=self.partner.short_code)
Пример #14
0
 def test_failure(self):
     basket = create_basket(site=self.site)
     PaymentProcessorResponse.objects.create(basket=basket)
     assert refund_basket_transactions(self.site, [basket.id]) == (
         0,
         1,
     )
    def test_post(self):
        """ The view should authorize and settle payment at CyberSource, and create an order. """
        data = self.generate_post_data()
        basket = create_basket(owner=self.user, site=self.site)
        basket.strategy = Selector().strategy()

        self.mock_cybersource_wsdl()
        self.mock_authorization_response(accepted=True)
        response = self.client.post(self.url, json.dumps(data), JSON)

        self.assertEqual(response.status_code, 201)
        PaymentProcessorResponse.objects.get(basket=basket)

        order = Order.objects.all().first()
        total = order.total_incl_tax
        self.assertEqual(
            response.data,
            OrderSerializer(order, context={
                'request': self.request
            }).data)
        order.payment_events.get(event_type__code='paid', amount=total)
        Source.objects.get(source_type__name=Cybersource.NAME,
                           currency=order.currency,
                           amount_allocated=total,
                           amount_debited=total,
                           label='Apple Pay')
        PaymentEvent.objects.get(event_type__name=PaymentEventTypeName.PAID,
                                 amount=total,
                                 processor_name=Cybersource.NAME)
Пример #16
0
    def test_order_exist_exception(self):
        basket = create_basket(site=self.site)
        basket.status = 'Frozen'
        basket.save()
        card_number = '4111111111111111'
        response = {
            'req_card_number': card_number,
            'req_card_type': CARD_TYPES['visa']['cybersource_code']
        }
        PaymentProcessorResponse.objects.create(basket=basket,
                                                transaction_id='abc',
                                                processor_name='cybersource',
                                                response=response)

        shipping_method = Free()
        shipping_charge = shipping_method.calculate(basket)
        total = OrderTotalCalculator().calculate(basket, shipping_charge)
        number = OrderNumberGenerator().order_number(basket)
        with transaction.atomic():
            OrderCreator().place_order(order_number=number,
                                       user=basket.owner,
                                       basket=basket,
                                       shipping_address=None,
                                       shipping_method=shipping_method,
                                       shipping_charge=shipping_charge,
                                       billing_address=None,
                                       total=total)

            basket.set_as_submitted()

        assert not FulfillFrozenBaskets().fulfill_basket(basket.id, self.site)
Пример #17
0
    def test_valid_payment_segment_logging(self, mock_track):
        """
        Verify the "Payment Info Entered" Segment event is fired after payment info is validated
        """
        basket = create_basket(owner=self.user, site=self.site)

        mixin = EdxOrderPlacementMixin()
        mixin.payment_processor = DummyProcessor(self.site)
        properties = {'checkout_id': basket.order_number}

        user_tracking_id, lms_client_id, lms_ip = parse_tracking_context(
            self.user)
        context = {
            'ip': lms_ip,
            'Google Analytics': {
                'clientId': lms_client_id
            }
        }

        mixin.handle_payment({}, basket)
        # ensure that the only two Segment events fired are 'Product Added' and 'Payment Info Entered'
        self.assertEqual(mock_track.call_count, 2)
        mock_track.assert_called_with(user_tracking_id,
                                      'Payment Info Entered',
                                      properties,
                                      context=context)
Пример #18
0
    def test_payment_error_with_duplicate_payment_id(self):
        """
        Verify that we fail gracefully when PayPal sends us the wrong payment ID,
        logging the exception and redirecting the user to an LMS checkout error page.
        """
        logger_name = 'ecommerce.extensions.payment.views.paypal'
        with LogCapture(logger_name) as l:
            self.mock_oauth2_response()

            # Create payment records with different baskets which will have same payment ID
            self.mock_payment_creation_response(self.basket)
            self.processor.get_transaction_parameters(self.basket,
                                                      request=self.request)

            dummy_basket = create_basket()
            self.mock_payment_creation_response(dummy_basket)
            self.processor.get_transaction_parameters(dummy_basket,
                                                      request=self.request)

            self._assert_error_page_redirect()
            l.check(
                (logger_name, 'INFO',
                 'Payment [{payment_id}] approved by payer [{payer_id}]'.
                 format(payment_id=self.PAYMENT_ID, payer_id=self.PAYER_ID)),
                (
                    logger_name,
                    'WARNING',
                    'Duplicate payment ID [{payment_id}] received from PayPal.'
                    .format(payment_id=self.PAYMENT_ID),
                ),
            )
Пример #19
0
    def test_create_assignments_for_multi_use_per_customer(self, __):
        """
        Verify the `create_assignments_for_multi_use_per_customer` works as expected for `MULTI_USE_PER_CUSTOMER`.
        """
        coupon_max_global_applications = 10
        enterprise_offer = EnterpriseOfferFactory(
            max_global_applications=coupon_max_global_applications)
        voucher = VoucherFactory(usage=Voucher.MULTI_USE_PER_CUSTOMER)
        voucher.offers.add(enterprise_offer)
        basket = create_basket(owner=self.user, site=self.site)
        basket.vouchers.add(voucher)
        order = create_order(user=self.user, basket=basket)

        assert OfferAssignment.objects.all().count() == 0

        EdxOrderPlacementMixin().create_assignments_for_multi_use_per_customer(
            order)
        EdxOrderPlacementMixin().update_assigned_voucher_offer_assignment(
            order)

        assert OfferAssignment.objects.all().count(
        ) == coupon_max_global_applications
        assert OfferAssignment.objects.filter(
            offer=enterprise_offer,
            code=voucher.code,
            user_email=basket.owner.email,
            status=OFFER_ASSIGNED).count() == 9
        assert OfferAssignment.objects.filter(
            offer=enterprise_offer,
            code=voucher.code,
            user_email=basket.owner.email,
            status=OFFER_REDEEMED).count() == 1
Пример #20
0
    def test_flush_with_product(self):
        """
        Verify the method fires 'Product Removed' Segment event with the correct information when basket is not empty
        """
        self.site1.siteconfiguration = SiteConfigurationFactory()
        self.site1.siteconfiguration.segment_key = 'fake_key'
        basket = create_basket(empty=True)
        basket.owner = factories.UserFactory()
        basket.site = self.site1
        basket.save()

        course = CourseFactory()
        seat = course.create_or_update_seat('verified', True, 100, self.partner)
        basket.add_product(seat)

        properties = translate_basket_line_for_segment(basket.lines.first())
        user_tracking_id, lms_client_id, lms_ip = parse_tracking_context(basket.owner)
        context = {
            'ip': lms_ip,
            'Google Analytics': {
                'clientId': lms_client_id
            }
        }

        with mock.patch.object(Client, 'track') as mock_track:
            basket.flush()
            mock_track.assert_called_once_with(user_tracking_id, 'Product Removed', properties, context=context)
Пример #21
0
    def test_request_apple_pay_authorization_error(self):
        """ The method should raise GatewayError if an error occurs while authorizing payment. """
        basket = create_basket(site=self.site, owner=self.create_user())

        with mock.patch('zeep.Client.__init__', side_effect=Exception):
            with self.assertRaises(GatewayError):
                self.processor.request_apple_pay_authorization(basket, None, None)
Пример #22
0
    def test_check_condition_applicability_empty_basket(self):
        """
        Validate check_condition_applicability decorator returns False if the basket is empty.
        """
        basket = create_basket(self.user, self.site, empty=True)

        self.assertFalse(check_condition_applicability()(
            self.condition.is_satisfied)(self.condition, self.offer, basket))
Пример #23
0
    def test_place_free_order(self, __):
        """ Verify an order is placed and the basket is submitted. """
        basket = create_basket(empty=True)
        basket.add_product(ProductFactory(stockrecords__price_excl_tax=0))
        order = EdxOrderPlacementMixin().place_free_order(basket)

        self.assertIsNotNone(order)
        self.assertEqual(basket.status, Basket.SUBMITTED)
Пример #24
0
    def test_check_condition_applicability_site_mismatch(self):
        """
        Validate check_condition_applicability decorator returns False if the offer site and basket site do not match.
        """
        basket = create_basket(self.user, SiteConfigurationFactory().site)

        self.assertFalse(check_condition_applicability()(
            self.condition.is_satisfied)(self.condition, self.offer, basket))
Пример #25
0
    def test_check_condition_applicability_free_basket(self):
        """
        Validate check_condition_applicability decorator returns False if the basket is free.
        """
        basket = create_basket(self.user, self.site, price='0.00')

        self.assertFalse(check_condition_applicability()(
            self.condition.is_satisfied)(self.condition, self.offer, basket))
Пример #26
0
    def test_contains_coupon(self):
        self.assertFalse(self.order.contains_coupon)

        product = factories.create_product(product_class=COUPON_PRODUCT_CLASS_NAME)
        basket = create_basket(empty=True)
        factories.create_stockrecord(product, num_in_stock=1)
        basket.add_product(product)
        order = create_order(basket=basket)
        self.assertTrue(order.contains_coupon)
Пример #27
0
    def test_create_offer_assignments_for_updated_max_uses(self, __):
        """
        Verify the `create_assignments_for_multi_use_per_customer` works as expected for
        `MULTI_USE_PER_CUSTOMER` when `max_global_applications` is updated for existing voucher.
        """
        coupon_max_global_applications = 1
        enterprise_offer = EnterpriseOfferFactory(
            max_global_applications=coupon_max_global_applications)
        voucher = VoucherFactory(usage=Voucher.MULTI_USE_PER_CUSTOMER)
        voucher.offers.add(enterprise_offer)
        basket = create_basket(owner=self.user, site=self.site)
        basket.vouchers.add(voucher)
        order = create_order(user=self.user, basket=basket)

        assert OfferAssignment.objects.all().count() == 0

        EdxOrderPlacementMixin().create_assignments_for_multi_use_per_customer(
            order)
        EdxOrderPlacementMixin().update_assigned_voucher_offer_assignment(
            order)

        assert OfferAssignment.objects.all().count(
        ) == coupon_max_global_applications
        assert OfferAssignment.objects.filter(
            offer=enterprise_offer,
            code=voucher.code,
            user_email=basket.owner.email,
            status=OFFER_REDEEMED).count() == 1

        # update max_global_applications
        coupon_new_max_global_applications = 5
        enterprise_offer.max_global_applications = coupon_new_max_global_applications
        enterprise_offer.save()

        assert voucher.enterprise_offer.max_global_applications == coupon_new_max_global_applications

        EdxOrderPlacementMixin().create_assignments_for_multi_use_per_customer(
            order)

        assert OfferAssignment.objects.all().count(
        ) == coupon_new_max_global_applications
        assert OfferAssignment.objects.filter(
            offer=enterprise_offer,
            code=voucher.code,
            user_email=basket.owner.email,
            status=OFFER_ASSIGNED).count() == 4
        assert OfferAssignment.objects.filter(
            offer=enterprise_offer,
            code=voucher.code,
            user_email=basket.owner.email,
            status=OFFER_REDEEMED).count() == 1

        # call once again to verify nothing is created because all available slots are assigned
        EdxOrderPlacementMixin().create_assignments_for_multi_use_per_customer(
            order)
        assert OfferAssignment.objects.all().count(
        ) == coupon_new_max_global_applications
Пример #28
0
    def test_successful_payment_for_bulk_purchase(self):
        """
        Verify that when a Order has been successfully placed for bulk
        purchase then that order is linked to the provided business client.
        """
        toggle_switch(ENROLLMENT_CODE_SWITCH, True)

        course = CourseFactory()
        course.create_or_update_seat('verified',
                                     True,
                                     50,
                                     self.partner,
                                     create_enrollment_code=True)
        basket = create_basket(owner=self.user, site=self.site)
        enrollment_code = Product.objects.get(
            product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME)
        basket.add_product(enrollment_code, quantity=1)
        basket.strategy = Selector().strategy()

        data = self.generate_form_data(basket.id)
        data.update({'organization': 'Dummy Business Client'})

        # Manually add organization attribute on the basket for testing
        basket_add_organization_attribute(basket, data)

        card_type = 'American Express'
        label = '1986'
        charge = stripe.Charge.construct_from(
            {
                'id': '2404',
                'source': {
                    'brand': card_type,
                    'last4': label,
                },
            }, 'fake-key')

        billing_address = BillingAddressFactory()
        with mock.patch.object(Stripe,
                               'get_address_from_token') as address_mock:
            address_mock.return_value = billing_address

            with mock.patch.object(stripe.Charge, 'create') as charge_mock:
                charge_mock.return_value = charge
                response = self.client.post(self.path, data)

            address_mock.assert_called_once_with(data['stripe_token'])

        self.assert_successful_order_response(response, basket.order_number)
        self.assert_order_created(basket, billing_address, card_type, label)

        # Now verify that a new business client has been created and current
        # order is now linked with that client through Invoice model.
        order = Order.objects.filter(basket=basket).first()
        business_client = BusinessClient.objects.get(name=data['organization'])
        assert Invoice.objects.get(
            order=order).business_client == business_client
Пример #29
0
    def test_check_condition_applicability(self, mock_is_satisfied):
        """
        Validate check_condition_applicability decorator returns True if it is applicable.
        """
        mock_is_satisfied.return_value = True
        mock_is_satisfied.__name__ = 'is_satisfied'
        basket = create_basket(self.user, self.site)

        self.assertTrue(check_condition_applicability()(
            self.condition.is_satisfied)(self.condition, self.offer, basket))
Пример #30
0
    def test_get_transaction_parameters_with_quoted_product_title(self):
        """ Verify quotes are removed from item name """
        course = CourseFactory(id='a/b/c/d', name='Course with "quotes"')
        product = course.create_or_update_seat(self.CERTIFICATE_TYPE, False, 20)

        basket = create_basket(owner=UserFactory(), site=self.site, empty=True)
        basket.add_product(product)

        response = self.processor.get_transaction_parameters(basket)
        self.assertEqual(response['item_0_name'], 'Seat in Course with quotes with test-certificate-type certificate')