Exemplo n.º 1
0
    def test_commerce_configuration(self):
        """
        Test that commerce configuration is created properly.
        """
        call_command("configure_commerce", )

        # Verify commerce configuration is enabled with appropriate values
        commerce_configuration = CommerceConfiguration.current()

        self.assertTrue(commerce_configuration.enabled)
        self.assertTrue(commerce_configuration.checkout_on_ecommerce_service)
        self.assertEqual(commerce_configuration.single_course_checkout_page,
                         "/basket/single-item/")
        self.assertEqual(commerce_configuration.cache_ttl, 0)

        # Verify commerce configuration can be disabled from command
        call_command(
            "configure_commerce",
            '--disable',
        )

        commerce_configuration = CommerceConfiguration.current()
        self.assertFalse(commerce_configuration.enabled)

        # Verify commerce configuration can be disabled from command
        call_command(
            "configure_commerce",
            '--disable-checkout-on-ecommerce',
        )

        commerce_configuration = CommerceConfiguration.current()
        self.assertFalse(commerce_configuration.checkout_on_ecommerce_service)
    def test_commerce_configuration(self):
        """
        Test that commerce configuration is created properly.
        """
        call_command(
            "configure_commerce",
        )

        # Verify commerce configuration is enabled with appropriate values
        commerce_configuration = CommerceConfiguration.current()

        self.assertTrue(commerce_configuration.enabled)
        self.assertTrue(commerce_configuration.checkout_on_ecommerce_service)
        self.assertEqual(commerce_configuration.single_course_checkout_page, "/basket/single-item/")
        self.assertEqual(commerce_configuration.cache_ttl, 0)

        # Verify commerce configuration can be disabled from command
        call_command(
            "configure_commerce",
            '--disable',
        )

        commerce_configuration = CommerceConfiguration.current()
        self.assertFalse(commerce_configuration.enabled)

        # Verify commerce configuration can be disabled from command
        call_command(
            "configure_commerce",
            '--disable-checkout-on-ecommerce',
        )

        commerce_configuration = CommerceConfiguration.current()
        self.assertFalse(commerce_configuration.checkout_on_ecommerce_service)
Exemplo n.º 3
0
def get_user_orders(user):
    """Given a user, get the detail of all the orders from the Ecommerce service.

    Args:
        user (User): The user to authenticate as when requesting ecommerce.

    Returns:
        list of dict, representing orders returned by the Ecommerce service.
    """
    no_data = []
    user_orders = []
    commerce_configuration = CommerceConfiguration.current()
    user_query = {'username': user.username}

    use_cache = commerce_configuration.is_cache_enabled
    cache_key = commerce_configuration.CACHE_KEY + '.' + str(user.id) if use_cache else None
    api = ecommerce_api_client(user)
    commerce_user_orders = get_edx_api_data(
        commerce_configuration, 'orders', api=api, querystring=user_query, cache_key=cache_key
    )

    for order in commerce_user_orders:
        if order['status'].lower() == 'complete':
            date_placed = datetime.strptime(order['date_placed'], "%Y-%m-%dT%H:%M:%SZ")
            order_data = {
                'number': order['number'],
                'price': order['total_excl_tax'],
                'order_date': strftime_localized(date_placed, 'SHORT_DATE'),
                'receipt_url': EcommerceService().get_receipt_page_url(order['number']),
                'lines': order['lines'],
            }
            user_orders.append(order_data)

    return user_orders
Exemplo n.º 4
0
def get_user_orders(user):
    """Given a user, get the detail of all the orders from the Ecommerce service.

    Args:
        user (User): The user to authenticate as when requesting ecommerce.

    Returns:
        list of dict, representing orders returned by the Ecommerce service.
    """
    no_data = []
    user_orders = []
    commerce_configuration = CommerceConfiguration.current()
    user_query = {'username': user.username}

    use_cache = commerce_configuration.is_cache_enabled
    cache_key = commerce_configuration.CACHE_KEY + '.' + str(user.id) if use_cache else None
    api = ecommerce_api_client(user)
    commerce_user_orders = get_edx_api_data(
        commerce_configuration, 'orders', api=api, querystring=user_query, cache_key=cache_key
    )

    for order in commerce_user_orders:
        if order['status'].lower() == 'complete':
            date_placed = datetime.strptime(order['date_placed'], "%Y-%m-%dT%H:%M:%SZ")
            order_data = {
                'number': order['number'],
                'price': order['total_excl_tax'],
                'order_date': strftime_localized(date_placed, 'SHORT_DATE'),
                'receipt_url': EcommerceService().get_receipt_page_url(order['number']),
                'lines': order['lines'],
            }
            user_orders.append(order_data)

    return user_orders
Exemplo n.º 5
0
def get_user_orders(user):
    """Given a user, get the detail of all the orders from the Ecommerce service.

    Args:
        user (User): The user to authenticate as when requesting ecommerce.

    Returns:
        list of dict, representing orders returned by the Ecommerce service.
    """
    no_data = []
    user_orders = []
    allowed_course_modes = ['professional', 'verified', 'credit']
    commerce_configuration = CommerceConfiguration.current()
    user_query = {'username': user.username}

    use_cache = commerce_configuration.is_cache_enabled
    cache_key = commerce_configuration.CACHE_KEY + '.' + str(
        user.id) if use_cache else None
    api = ecommerce_api_client(user)
    commerce_user_orders = get_edx_api_data(commerce_configuration,
                                            user,
                                            'orders',
                                            api=api,
                                            querystring=user_query,
                                            cache_key=cache_key)

    for order in commerce_user_orders:
        if order['status'].lower() == 'complete':
            for line in order['lines']:
                product = line.get('product')
                if product:
                    for attribute in product['attribute_values']:
                        if attribute[
                                'name'] == 'certificate_type' and attribute[
                                    'value'] in allowed_course_modes:
                            try:
                                date_placed = datetime.strptime(
                                    order['date_placed'], "%Y-%m-%dT%H:%M:%SZ")
                                order_data = {
                                    'number':
                                    order['number'],
                                    'price':
                                    order['total_excl_tax'],
                                    'title':
                                    order['lines'][0]['title'],
                                    'order_date':
                                    strftime_localized(
                                        date_placed.replace(tzinfo=pytz.UTC),
                                        'SHORT_DATE'),
                                    'receipt_url':
                                    EcommerceService().get_receipt_page_url(
                                        order['number'])
                                }
                                user_orders.append(order_data)
                            except KeyError:
                                log.exception('Invalid order structure: %r',
                                              order)
                                return no_data

    return user_orders
Exemplo n.º 6
0
 def test_get_checkout_page_url(self, skus):
     """ Verify the checkout page URL is properly constructed and returned. """
     url = EcommerceService().get_checkout_page_url(*skus)
     config = CommerceConfiguration.current()
     expected_url = '{root}{basket_url}?{skus}'.format(
         basket_url=config.MULTIPLE_ITEMS_BASKET_PAGE_URL,
         root=settings.ECOMMERCE_PUBLIC_URL_ROOT,
         skus=urlencode({'sku': skus}, doseq=True),
     )
     self.assertEqual(url, expected_url)
Exemplo n.º 7
0
    def test_is_enabled(self):
        """Verify that is_enabled() returns True when ecomm checkout is enabled. """
        is_enabled = EcommerceService().is_enabled(self.user)
        self.assertTrue(is_enabled)

        config = CommerceConfiguration.current()
        config.checkout_on_ecommerce_service = False
        config.save()
        is_not_enabled = EcommerceService().is_enabled(self.user)
        self.assertFalse(is_not_enabled)
Exemplo n.º 8
0
    def test_is_enabled(self):
        """Verify that is_enabled() returns True when ecomm checkout is enabled. """
        is_enabled = EcommerceService().is_enabled(self.request)
        self.assertTrue(is_enabled)

        config = CommerceConfiguration.current()
        config.checkout_on_ecommerce_service = False
        config.save()
        is_not_enabled = EcommerceService().is_enabled(self.request)
        self.assertFalse(is_not_enabled)
Exemplo n.º 9
0
def checkout_receipt(request):
    """ Receipt view. """

    page_title = _('Receipt')
    is_payment_complete = True
    payment_support_email = configuration_helpers.get_value('payment_support_email', settings.PAYMENT_SUPPORT_EMAIL)
    payment_support_link = '<a href=\"mailto:{email}\">{email}</a>'.format(email=payment_support_email)

    is_cybersource = all(k in request.POST for k in ('signed_field_names', 'decision', 'reason_code'))
    if is_cybersource and request.POST['decision'] != 'ACCEPT':
        # Cybersource may redirect users to this view if it couldn't recover
        # from an error while capturing payment info.
        is_payment_complete = False
        page_title = _('Payment Failed')
        reason_code = request.POST['reason_code']
        # if the problem was with the info submitted by the user, we present more detailed messages.
        if is_user_payment_error(reason_code):
            error_summary = _("There was a problem with this transaction. You have not been charged.")
            error_text = _(
                "Make sure your information is correct, or try again with a different card or another form of payment."
            )
        else:
            error_summary = _("A system error occurred while processing your payment. You have not been charged.")
            error_text = _("Please wait a few minutes and then try again.")
        for_help_text = _("For help, contact {payment_support_link}.").format(payment_support_link=payment_support_link)
    else:
        # if anything goes wrong rendering the receipt, it indicates a problem fetching order data.
        error_summary = _("An error occurred while creating your receipt.")
        error_text = None  # nothing particularly helpful to say if this happens.
        for_help_text = _(
            "If your course does not appear on your dashboard, contact {payment_support_link}."
        ).format(payment_support_link=payment_support_link)

    commerce_configuration = CommerceConfiguration.current()
    # user order cache should be cleared when a new order is placed
    # so user can see new order in their order history.
    if is_payment_complete and commerce_configuration.enabled and commerce_configuration.is_cache_enabled:
        cache_key = commerce_configuration.CACHE_KEY + '.' + str(request.user.id)
        cache.delete(cache_key)

    context = {
        'page_title': page_title,
        'is_payment_complete': is_payment_complete,
        'platform_name': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME),
        'verified': SoftwareSecurePhotoVerification.verification_valid_or_pending(request.user).exists(),
        'error_summary': error_summary,
        'error_text': error_text,
        'for_help_text': for_help_text,
        'payment_support_email': payment_support_email,
        'username': request.user.username,
        'nav_hidden': True,
        'is_request_in_themed_site': is_request_in_themed_site()
    }
    return render_to_response('commerce/checkout_receipt.html', context)
Exemplo n.º 10
0
def get_user_orders(user):
    """Given a user, get the detail of all the orders from the Ecommerce service.

    Args:
        user (User): The user to authenticate as when requesting ecommerce.

    Returns:
        list of dict, representing orders returned by the Ecommerce service.
    """
    no_data = []
    user_orders = []
    commerce_configuration = CommerceConfiguration.current()
    user_query = {'username': user.username}

    use_cache = commerce_configuration.is_cache_enabled
    cache_key = commerce_configuration.CACHE_KEY + '.' + str(
        user.id) if use_cache else None
    api = ecommerce_api_client(user)
    commerce_user_orders = get_edx_api_data(commerce_configuration,
                                            user,
                                            'orders',
                                            api=api,
                                            querystring=user_query,
                                            cache_key=cache_key)

    for order in commerce_user_orders:
        if order['status'].lower() == 'complete':
            date_placed = datetime.strptime(order['date_placed'],
                                            "%Y-%m-%dT%H:%M:%SZ")
            order_data = {
                'number':
                order['number'],
                'price':
                order['total_excl_tax'],
                'order_date':
                strftime_localized(date_placed, 'SHORT_DATE'),
                'receipt_url':
                EcommerceService().get_receipt_page_url(order['number']),
                'lines':
                order['lines'],
            }

            # If the order lines contain a product that is not a Seat
            # we do not want to display the Order Details button. It
            # will break the receipt page if used.
            for order_line in order['lines']:
                product = order_line.get('product')

                if product and product.get('product_class') != 'Seat':
                    order_data['receipt_url'] = ''
                    break
            user_orders.append(order_data)

    return user_orders
Exemplo n.º 11
0
    def test_get_receipt_page_url_with_site_configuration(self):
        order_number = 'ORDER1'
        config = CommerceConfiguration.current()
        config.use_ecommerce_receipt_page = True
        config.save()

        receipt_page_url = EcommerceService().get_receipt_page_url(order_number)
        expected_url = '{ecommerce_root}{receipt_page_url}{order_number}'.format(
            ecommerce_root=settings.ECOMMERCE_PUBLIC_URL_ROOT,
            order_number=order_number,
            receipt_page_url=TEST_SITE_CONFIGURATION['ECOMMERCE_RECEIPT_PAGE_URL']
        )
        self.assertEqual(receipt_page_url, expected_url)
Exemplo n.º 12
0
    def test_get_receipt_page_url_with_site_configuration(self):
        order_number = 'ORDER1'
        config = CommerceConfiguration.current()
        config.use_ecommerce_receipt_page = True
        config.save()

        receipt_page_url = EcommerceService().get_receipt_page_url(
            order_number)
        expected_url = '{ecommerce_root}{receipt_page_url}{order_number}'.format(
            ecommerce_root=settings.ECOMMERCE_PUBLIC_URL_ROOT,
            order_number=order_number,
            receipt_page_url=TEST_SITE_CONFIGURATION[
                'ECOMMERCE_RECEIPT_PAGE_URL'])
        self.assertEqual(receipt_page_url, expected_url)
Exemplo n.º 13
0
def get_user_orders(user):
    """Given a user, get the detail of all the orders from the Ecommerce service.

    Arguments:
        user (User): The user to authenticate as when requesting ecommerce.

    Returns:
        list of dict, representing orders returned by the Ecommerce service.
    """
    no_data = []
    user_orders = []
    allowed_course_modes = ['professional', 'verified', 'credit']
    commerce_configuration = CommerceConfiguration.current()
    user_query = {'username': user.username}

    use_cache = commerce_configuration.is_cache_enabled
    cache_key = commerce_configuration.CACHE_KEY + '.' + str(user.id) if use_cache else None
    api = ecommerce_api_client(user)
    commerce_user_orders = get_edx_api_data(
        commerce_configuration, user, 'orders', api=api, querystring=user_query, cache_key=cache_key
    )

    for order in commerce_user_orders:
        if order['status'].lower() == 'complete':
            for line in order['lines']:
                product = line.get('product')
                if product:
                    for attribute in product['attribute_values']:
                        if attribute['name'] == 'certificate_type' and attribute['value'] in allowed_course_modes:
                            try:
                                date_placed = datetime.strptime(order['date_placed'], "%Y-%m-%dT%H:%M:%SZ")
                                order_data = {
                                    'number': order['number'],
                                    'price': order['total_excl_tax'],
                                    'title': order['lines'][0]['title'],
                                    'order_date': strftime_localized(
                                        date_placed.replace(tzinfo=pytz.UTC), 'SHORT_DATE'
                                    ),
                                    'receipt_url': commerce_configuration.receipt_page + order['number']
                                }
                                user_orders.append(order_data)
                            except KeyError:
                                log.exception('Invalid order structure: %r', order)
                                return no_data

    return user_orders
Exemplo n.º 14
0
def get_user_orders(user):
    """Given a user, get the detail of all the orders from the Ecommerce service.

    Arguments:
        user (User): The user to authenticate as when requesting ecommerce.

    Returns:
        list of dict, representing orders returned by the Ecommerce service.
    """
    no_data = []
    user_orders = []
    allowed_course_modes = ["professional", "verified", "credit"]
    commerce_configuration = CommerceConfiguration.current()
    user_query = {"username": user.username}

    use_cache = commerce_configuration.is_cache_enabled
    cache_key = commerce_configuration.CACHE_KEY + "." + str(user.id) if use_cache else None
    api = ecommerce_api_client(user)
    commerce_user_orders = get_edx_api_data(
        commerce_configuration, user, "orders", api=api, querystring=user_query, cache_key=cache_key
    )

    for order in commerce_user_orders:
        if order["status"].lower() == "complete":
            for line in order["lines"]:
                product = line.get("product")
                if product:
                    for attribute in product["attribute_values"]:
                        if attribute["name"] == "certificate_type" and attribute["value"] in allowed_course_modes:
                            try:
                                date_placed = datetime.strptime(order["date_placed"], "%Y-%m-%dT%H:%M:%SZ")
                                order_data = {
                                    "number": order["number"],
                                    "price": order["total_excl_tax"],
                                    "title": order["lines"][0]["title"],
                                    "order_date": strftime_localized(
                                        date_placed.replace(tzinfo=pytz.UTC), "SHORT_DATE"
                                    ),
                                    "receipt_url": commerce_configuration.receipt_page + order["number"],
                                }
                                user_orders.append(order_data)
                            except KeyError:
                                log.exception("Invalid order structure: %r", order)
                                return no_data

    return user_orders
Exemplo n.º 15
0
    def setUp(self):
        super(TestRefundSignal, self).setUp()

        # Ensure the E-Commerce service user exists
        UserFactory(username=settings.ECOMMERCE_SERVICE_WORKER_USERNAME, is_staff=True)

        self.requester = UserFactory(username="******")
        self.student = UserFactory(
            username="******",
            email="*****@*****.**",
        )
        self.course_enrollment = CourseEnrollmentFactory(
            user=self.student,
            course_id=CourseKey.from_string('course-v1:org+course+run'),
            mode=CourseMode.VERIFIED,
        )
        self.course_enrollment.refundable = mock.Mock(return_value=True)

        self.config = CommerceConfiguration.current()
        self.config.enable_automatic_refund_approval = True
        self.config.save()
Exemplo n.º 16
0
    def setUp(self):
        super(TestRefundSignal, self).setUp()

        # Ensure the E-Commerce service user exists
        UserFactory(username=settings.ECOMMERCE_SERVICE_WORKER_USERNAME,
                    is_staff=True)

        self.requester = UserFactory(username="******")
        self.student = UserFactory(
            username="******",
            email="*****@*****.**",
        )
        self.course_enrollment = CourseEnrollmentFactory(
            user=self.student,
            course_id=CourseKey.from_string('course-v1:org+course+run'),
            mode=CourseMode.VERIFIED,
        )
        self.course_enrollment.refundable = mock.Mock(return_value=True)

        self.config = CommerceConfiguration.current()
        self.config.enable_automatic_refund_approval = True
        self.config.save()
Exemplo n.º 17
0
 def __init__(self):
     self.config = CommerceConfiguration.current()
Exemplo n.º 18
0
def checkout_receipt(request):
    """ Receipt view. """

    page_title = _('Receipt')
    is_payment_complete = True
    payment_support_email = configuration_helpers.get_value(
        'payment_support_email', settings.PAYMENT_SUPPORT_EMAIL)
    payment_support_link = '<a href=\"mailto:{email}\">{email}</a>'.format(
        email=payment_support_email)

    is_cybersource = all(k in request.POST
                         for k in ('signed_field_names', 'decision',
                                   'reason_code'))
    if is_cybersource and request.POST['decision'] != 'ACCEPT':
        # Cybersource may redirect users to this view if it couldn't recover
        # from an error while capturing payment info.
        is_payment_complete = False
        page_title = _('Payment Failed')
        reason_code = request.POST['reason_code']
        # if the problem was with the info submitted by the user, we present more detailed messages.
        if is_user_payment_error(reason_code):
            error_summary = _(
                "There was a problem with this transaction. You have not been charged."
            )
            error_text = _(
                "Make sure your information is correct, or try again with a different card or another form of payment."
            )
        else:
            error_summary = _(
                "A system error occurred while processing your payment. You have not been charged."
            )
            error_text = _("Please wait a few minutes and then try again.")
        for_help_text = _("For help, contact {payment_support_link}.").format(
            payment_support_link=payment_support_link)
    else:
        # if anything goes wrong rendering the receipt, it indicates a problem fetching order data.
        error_summary = _("An error occurred while creating your receipt.")
        error_text = None  # nothing particularly helpful to say if this happens.
        for_help_text = _(
            "If your course does not appear on your dashboard, contact {payment_support_link}."
        ).format(payment_support_link=payment_support_link)

    commerce_configuration = CommerceConfiguration.current()
    # user order cache should be cleared when a new order is placed
    # so user can see new order in their order history.
    if is_payment_complete and commerce_configuration.enabled and commerce_configuration.is_cache_enabled:
        cache_key = commerce_configuration.CACHE_KEY + '.' + str(
            request.user.id)
        cache.delete(cache_key)

    context = {
        'page_title':
        page_title,
        'is_payment_complete':
        is_payment_complete,
        'platform_name':
        configuration_helpers.get_value('platform_name',
                                        settings.PLATFORM_NAME),
        'verified':
        SoftwareSecurePhotoVerification.verification_valid_or_pending(
            request.user).exists(),
        'error_summary':
        error_summary,
        'error_text':
        error_text,
        'for_help_text':
        for_help_text,
        'payment_support_email':
        payment_support_email,
        'username':
        request.user.username,
        'nav_hidden':
        True,
        'is_request_in_themed_site':
        is_request_in_themed_site()
    }
    return render_to_response('commerce/checkout_receipt.html', context)
Exemplo n.º 19
0
def refund_seat(course_enrollment):
    """
    Attempt to initiate a refund for any orders associated with the seat being unenrolled, using the commerce service.

    Arguments:
        course_enrollment (CourseEnrollment): a student enrollment

    Returns:
        A list of the external service's IDs for any refunds that were initiated
            (may be empty).

    Raises:
        exceptions.SlumberBaseException: for any unhandled HTTP error during communication with the E-Commerce Service.
        exceptions.Timeout: if the attempt to reach the commerce service timed out.
    """
    User = get_user_model()  # pylint:disable=invalid-name
    course_key_str = unicode(course_enrollment.course_id)
    enrollee = course_enrollment.user

    service_user = User.objects.get(username=settings.ECOMMERCE_SERVICE_WORKER_USERNAME)
    api_client = ecommerce_api_client(service_user)

    log.info('Attempting to create a refund for user [%s], course [%s]...', enrollee.id, course_key_str)

    refund_ids = api_client.refunds.post({'course_id': course_key_str, 'username': enrollee.username})

    if refund_ids:
        log.info('Refund successfully opened for user [%s], course [%s]: %r', enrollee.id, course_key_str, refund_ids)

        config = CommerceConfiguration.current()

        if config.enable_automatic_refund_approval:
            refunds_requiring_approval = []

            for refund_id in refund_ids:
                try:
                    # NOTE: Approve payment only because the user has already been unenrolled. Additionally, this
                    # ensures we don't tie up an additional web worker when the E-Commerce Service tries to unenroll
                    # the learner
                    api_client.refunds(refund_id).process.put({'action': 'approve_payment_only'})
                    log.info('Refund [%d] successfully approved.', refund_id)
                except:  # pylint: disable=bare-except
                    log.exception('Failed to automatically approve refund [%d]!', refund_id)
                    refunds_requiring_approval.append(refund_id)
        else:
            refunds_requiring_approval = refund_ids

        if refunds_requiring_approval:
            # XCOM-371: this is a temporary measure to suppress refund-related email
            # notifications to students and support for free enrollments.  This
            # condition should be removed when the CourseEnrollment.refundable() logic
            # is updated to be more correct, or when we implement better handling (and
            # notifications) in Otto for handling reversal of $0 transactions.
            if course_enrollment.mode != 'verified':
                # 'verified' is the only enrollment mode that should presently
                # result in opening a refund request.
                log.info(
                    'Skipping refund email notification for non-verified mode for user [%s], course [%s], mode: [%s]',
                    course_enrollment.user.id,
                    course_enrollment.course_id,
                    course_enrollment.mode,
                )
            else:
                try:
                    send_refund_notification(course_enrollment, refunds_requiring_approval)
                except:  # pylint: disable=bare-except
                    # don't break, just log a warning
                    log.warning('Could not send email notification for refund.', exc_info=True)
    else:
        log.info('No refund opened for user [%s], course [%s]', enrollee.id, course_key_str)

    return refund_ids
Exemplo n.º 20
0
def refund_seat(course_enrollment):
    """
    Attempt to initiate a refund for any orders associated with the seat being unenrolled, using the commerce service.

    Arguments:
        course_enrollment (CourseEnrollment): a student enrollment

    Returns:
        A list of the external service's IDs for any refunds that were initiated
            (may be empty).

    Raises:
        exceptions.SlumberBaseException: for any unhandled HTTP error during communication with the E-Commerce Service.
        exceptions.Timeout: if the attempt to reach the commerce service timed out.
    """
    User = get_user_model()  # pylint:disable=invalid-name
    course_key_str = unicode(course_enrollment.course_id)
    enrollee = course_enrollment.user

    service_user = User.objects.get(
        username=settings.ECOMMERCE_SERVICE_WORKER_USERNAME)
    api_client = ecommerce_api_client(service_user)

    log.info('Attempting to create a refund for user [%s], course [%s]...',
             enrollee.id, course_key_str)

    refund_ids = api_client.refunds.post({
        'course_id': course_key_str,
        'username': enrollee.username
    })

    if refund_ids:
        log.info('Refund successfully opened for user [%s], course [%s]: %r',
                 enrollee.id, course_key_str, refund_ids)

        config = CommerceConfiguration.current()

        if config.enable_automatic_refund_approval:
            refunds_requiring_approval = []

            for refund_id in refund_ids:
                try:
                    # NOTE: Approve payment only because the user has already been unenrolled. Additionally, this
                    # ensures we don't tie up an additional web worker when the E-Commerce Service tries to unenroll
                    # the learner
                    api_client.refunds(refund_id).process.put(
                        {'action': 'approve_payment_only'})
                    log.info('Refund [%d] successfully approved.', refund_id)
                except:  # pylint: disable=bare-except
                    log.exception(
                        'Failed to automatically approve refund [%d]!',
                        refund_id)
                    refunds_requiring_approval.append(refund_id)
        else:
            refunds_requiring_approval = refund_ids

        if refunds_requiring_approval:
            # XCOM-371: this is a temporary measure to suppress refund-related email
            # notifications to students and support for free enrollments.  This
            # condition should be removed when the CourseEnrollment.refundable() logic
            # is updated to be more correct, or when we implement better handling (and
            # notifications) in Otto for handling reversal of $0 transactions.
            if course_enrollment.mode != 'verified':
                # 'verified' is the only enrollment mode that should presently
                # result in opening a refund request.
                log.info(
                    'Skipping refund email notification for non-verified mode for user [%s], course [%s], mode: [%s]',
                    course_enrollment.user.id,
                    course_enrollment.course_id,
                    course_enrollment.mode,
                )
            else:
                try:
                    send_refund_notification(course_enrollment,
                                             refunds_requiring_approval)
                except:  # pylint: disable=bare-except
                    # don't break, just log a warning
                    log.warning(
                        'Could not send email notification for refund.',
                        exc_info=True)
    else:
        log.info('No refund opened for user [%s], course [%s]', enrollee.id,
                 course_key_str)

    return refund_ids