Exemple #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')
Exemple #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)
Exemple #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)
Exemple #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
    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)
    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
        )
Exemple #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
Exemple #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)
    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')
Exemple #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)
Exemple #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)
    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)
Exemple #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)
Exemple #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)
Exemple #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)
Exemple #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),
                ),
            )
Exemple #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
Exemple #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)
    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)
Exemple #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))
Exemple #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)
Exemple #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))
Exemple #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))
Exemple #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)
Exemple #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
Exemple #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
Exemple #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))
    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')