Exemple #1
0
def send_course_purchase_email(sender, order=None, **kwargs):  # pylint: disable=unused-argument
    """Send course purchase notification email when a course is purchased."""
    if waffle.switch_is_active('ENABLE_NOTIFICATIONS'):
        # We do not currently support email sending for orders with more than one item.
        if len(order.lines.all()) == ORDER_LINE_COUNT:
            product = order.lines.first().product
            credit_provider_id = getattr(product.attr, 'credit_provider', None)
            if not credit_provider_id:
                logger.error(
                    'Failed to send credit receipt notification. Credit seat product [%s] has no provider.',
                    product.id)
                return
            elif product.is_seat_product:
                provider_data = get_credit_provider_details(
                    access_token=order.site.siteconfiguration.access_token,
                    credit_provider_id=credit_provider_id,
                    site_configuration=order.site.siteconfiguration)

                receipt_page_url = get_receipt_page_url(
                    order_number=order.number,
                    site_configuration=order.site.siteconfiguration)

                if provider_data:
                    send_notification(
                        order.user, 'CREDIT_RECEIPT', {
                            'course_title': product.title,
                            'receipt_page_url': receipt_page_url,
                            'credit_hours': product.attr.credit_hours,
                            'credit_provider': provider_data['display_name'],
                        }, order.site)

        else:
            logger.info(
                'Currently support receipt emails for order with one item.')
    def get(self, request, order_number):
        """Provide confirmation of payment."""
        order_id = decode_string(order_number)
        payment_status = self._get_payment_status(order_id)
        if payment_status.lower() != 'submitted':
            return render(request, 'payment/pending_status.html',
                          {'msg': payment_status})
        else:
            try:
                order = Order.objects.get(number=order_id)  # pylint: disable=unused-variable
            except Order.DoesNotExist:
                logger.info(
                    'Edupay Payment: No payment found for order [%s] ',
                    order_id,
                )
                return render(request, 'payment/pending_status.html',
                              {'msg': 'error'})

        basket_id = OrderNumberGenerator().basket_id(order_id)
        basket = Basket.objects.get(id=basket_id)

        receipt_url = get_receipt_page_url(
            order_number=basket.order_number,
            site_configuration=basket.site.siteconfiguration,
        )

        return redirect(receipt_url)
Exemple #3
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()
        price = Decimal(100.00)
        product = course.create_or_update_seat('verified', True, price, self.partner)

        basket = create_basket(empty=True)
        basket.site = self.site
        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 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, 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 #4
0
    def _notify_purchaser(self):
        """ Notify the purchaser that the refund has been processed. """
        site_configuration = self.order.site.siteconfiguration
        site_code = site_configuration.partner.short_code

        if not site_configuration.send_refund_notifications:
            logger.info(
                'Refund notifications are disabled for Partner [%s]. No notification will be sent for Refund [%d]',
                site_code, self.id
            )
            return

        # NOTE (CCB): The initial version of the refund email only supports refunding a single course.
        product = self.lines.first().order_line.product
        product_class = product.get_product_class().name

        if product_class != SEAT_PRODUCT_CLASS_NAME:
            logger.warning(
                ('No refund notification will be sent for Refund [%d]. The notification supports product lines '
                 'of type Course, not [%s].'),
                self.id, product_class
            )
            return

        course_name = self.lines.first().order_line.product.course.name
        order_number = self.order.number
        order_url = get_receipt_page_url(site_configuration, order_number)
        amount = format_currency(self.currency, self.total_credit_excl_tax)

        send_course_refund_email.delay(self.user.email, self.id, amount, course_name, order_number,
                                       order_url, site_code=site_code)
        logger.info('Course refund notification scheduled for Refund [%d].', self.id)
Exemple #5
0
 def send_email(self, order):
     """ Sends an email with enrollment code order information. """
     # Note (multi-courses): Change from a course_name to a list of course names.
     product = order.lines.first().product
     course = Course.objects.get(id=product.attr.course_key)
     receipt_page_url = get_receipt_page_url(
         order_number=order.number,
         site_configuration=order.site.siteconfiguration
     )
     send_notification(
         order.user,
         'ORDER_WITH_CSV',
         context={
             'contact_url': order.site.siteconfiguration.build_lms_url('/contact'),
             'course_name': course.name,
             'download_csv_link': order.site.siteconfiguration.build_ecommerce_url(
                 reverse('coupons:enrollment_code_csv', args=[order.number])
             ),
             'enrollment_code_title': product.title,
             'lms_url': order.site.siteconfiguration.build_lms_url(),
             'order_number': order.number,
             'partner_name': order.site.siteconfiguration.partner.name,
             'receipt_page_url': receipt_page_url,
         },
         site=order.site
     )
Exemple #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()
        price = Decimal(100.00)
        product = course.create_or_update_seat('verified', True, price, self.partner)

        basket = create_basket(empty=True)
        basket.site = self.site
        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 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, 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 get(self, request, pk):
        '''
        query order
        GET /payment/wechatpay/order_query/{basket.id}
        '''
        status = self.NOTPAY
        receipt_url = ''
        try:
            basket = Basket.objects.get(owner=request.user, id=pk)
            order = Order.objects.filter(number=basket.order_number,
                                         status='Complete')
            if basket.status == 'Submitted':
                status = self.PAID
            else:
                status, resp = self.wechatpay_query(basket)
                if status == self.PAID and not order:
                    post_data = {'original_data': json.dumps({'data': resp})}
                    requests.post(settings.ECOMMERCE_URL_ROOT +
                                  reverse('wechatpay:execute'),
                                  data=post_data)

            if status == self.PAID and order:
                receipt_url = get_receipt_page_url(
                    order_number=basket.order_number,
                    site_configuration=basket.site.siteconfiguration)
                status = self.SUCCESS
        except Exception, e:
            logger.exception(e)
Exemple #8
0
    def test_duplicate_reference_code(self):
        """
        Verify that if CyberSource declines to charge for an existing order, we
        redirect to the receipt page for the existing order.
        """
        notification = self.generate_notification(self.basket, billing_address=self.billing_address)

        self.client.post(self.path, notification)

        # Validate that a new order exists in the correct state
        order = Order.objects.get(basket=self.basket)
        self.assertIsNotNone(order, 'No order was created for the basket after payment.')

        # Mutate the notification and re-use it to simulate a duplicate reference
        # number error from CyberSource.
        notification.update({
            'decision': 'ERROR',
            'reason_code': '104',
        })

        # Re-sign the response. This is necessary because we've tampered with fields
        # that have already been signed.
        notification['signature'] = self.generate_signature(self.processor.secret_key, notification)

        response = self.client.post(self.path, notification)

        expected_redirect = get_receipt_page_url(
            self.site.siteconfiguration,
            order_number=notification.get('req_reference_number'),
            disable_back_button=True,
        )

        self.assertRedirects(response, expected_redirect, fetch_redirect_response=False)
Exemple #9
0
    def _assert_execution_redirect(self, payer_info=None, url_redirect=None):
        """Verify redirection to Otto receipt page after attempted payment execution."""
        self.mock_oauth2_response()

        # Create a payment record the view can use to retrieve a basket
        self.mock_payment_creation_response(self.basket)
        self.processor.get_transaction_parameters(self.basket,
                                                  request=self.request)

        creation_response = self.mock_payment_creation_response(self.basket,
                                                                find=True)
        execution_response = self.mock_payment_execution_response(
            self.basket, payer_info=payer_info)

        response = self.client.get(reverse('paypal:execute'), self.RETURN_DATA)
        self.assertRedirects(
            response,
            url_redirect or get_receipt_page_url(
                order_number=self.basket.order_number,
                site_configuration=self.basket.site.siteconfiguration,
                disable_back_button=True,
            ),
            fetch_redirect_response=False)

        return creation_response, execution_response
    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 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, 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 #11
0
    def _notify_purchaser(self):
        """ Notify the purchaser that the refund has been processed. """
        site_configuration = self.order.site.siteconfiguration
        site_code = site_configuration.partner.short_code

        if not site_configuration.send_refund_notifications:
            logger.info(
                'Refund notifications are disabled for Partner [%s]. No notification will be sent for Refund [%d]',
                site_code, self.id
            )
            return

        # NOTE (CCB): The initial version of the refund email only supports refunding a single course.
        product = self.lines.first().order_line.product
        product_class = product.get_product_class().name

        if product_class not in [SEAT_PRODUCT_CLASS_NAME, COURSE_ENTITLEMENT_PRODUCT_CLASS_NAME]:
            logger.warning(
                ('No refund notification will be sent for Refund [%d]. The notification supports product lines '
                 'of type Course, not [%s].'),
                self.id, product_class
            )
            return

        if product_class == SEAT_PRODUCT_CLASS_NAME:
            course_name = self.lines.first().order_line.product.course.name
        else:
            course_name = self.lines.first().order_line.product.title
        order_number = self.order.number
        order_url = get_receipt_page_url(site_configuration, order_number)
        amount = format_currency(self.currency, self.total_credit_excl_tax)

        send_course_refund_email.delay(self.user.email, self.id, amount, course_name, order_number,
                                       order_url, site_code=site_code)
        logger.info('Course refund notification scheduled for Refund [%d].', self.id)
Exemple #12
0
    def post(self, request, *args, **kwargs):  # pylint: disable=unused-argument
        """Process a CyberSource merchant notification and place an order for paid products as appropriate."""
        try:
            notification = request.POST.dict()
            basket = self.validate_notification(notification)
        except (InvalidBasketError, InvalidSignatureError):
            return redirect(reverse('payment_error'))
        except (UserCancelled, TransactionDeclined, PaymentError):
            order_number = request.POST.get('req_reference_number')
            basket_id = OrderNumberGenerator().basket_id(order_number)
            basket = self._get_basket(basket_id)
            if basket:
                basket.thaw()

            messages.error(request, _('Your payment has been canceled.'))
            return redirect(reverse('basket:summary'))
        except:  # pylint: disable=bare-except
            return redirect(reverse('payment_error'))

        try:
            self.create_order(request, basket, notification)
            receipt_page_url = get_receipt_page_url(
                order_number=notification.get('req_reference_number'),
                site_configuration=self.request.site.siteconfiguration
            )
            self.request.session['fire_tracking_events'] = True
            return redirect(receipt_page_url)
        except:  # pylint: disable=bare-except
            return redirect(reverse('payment_error'))
Exemple #13
0
    def post(self, request, *args, **kwargs):  # pylint: disable=unused-argument
        """Process a CyberSource merchant notification and place an order for paid products as appropriate."""
        try:
            notification = request.POST.dict()
            basket = self.validate_notification(notification)
        except (InvalidBasketError, InvalidSignatureError):
            return redirect(reverse('payment_error'))
        except (UserCancelled, TransactionDeclined, PaymentError):
            order_number = request.POST.get('req_reference_number')
            basket_id = OrderNumberGenerator().basket_id(order_number)
            basket = self._get_basket(basket_id)
            if basket:
                basket.thaw()

            messages.error(request, _('Your payment has been canceled.'))
            return redirect(reverse('basket:summary'))
        except:  # pylint: disable=bare-except
            return redirect(reverse('payment_error'))

        try:
            self.create_order(request, basket, notification)
            receipt_page_url = get_receipt_page_url(
                order_number=notification.get('req_reference_number'),
                site_configuration=self.request.site.siteconfiguration)
            self.request.session['fire_tracking_events'] = True
            return redirect(receipt_page_url)
        except:  # pylint: disable=bare-except
            return redirect(reverse('payment_error'))
Exemple #14
0
 def send_email(self, order):
     """ Sends an email with enrollment code order information. """
     # Note (multi-courses): Change from a course_name to a list of course names.
     product = order.lines.first().product
     course = Course.objects.get(id=product.attr.course_key)
     receipt_page_url = get_receipt_page_url(
         order_number=order.number,
         site_configuration=order.site.siteconfiguration)
     send_notification(
         order.user,
         'ORDER_WITH_CSV',
         context={
             'contact_url':
             order.site.siteconfiguration.build_lms_url('/contact'),
             'course_name':
             course.name,
             'download_csv_link':
             order.site.siteconfiguration.build_ecommerce_url(
                 reverse('coupons:enrollment_code_csv',
                         args=[order.number])),
             'enrollment_code_title':
             product.title,
             'lms_url':
             order.site.siteconfiguration.build_lms_url(),
             'order_number':
             order.number,
             'partner_name':
             order.site.siteconfiguration.partner.name,
             'receipt_page_url':
             receipt_page_url,
             'order_history_url':
             order.site.siteconfiguration.build_lms_url('account/settings'),
         },
         site=order.site)
    def test_valid_request(self):
        """ Verify the view completes the transaction if the request is valid. """
        basket = self._create_valid_basket()
        data = self._generate_data(basket.id)
        order_number = OrderNumberGenerator().order_number_from_basket_id(
            self.site.siteconfiguration.partner,
            basket.id,
        )
        # The view has already been created, so patch and assert that its reference to basket_add_organization_attribute
        # has been called
        with mock.patch(
                "ecommerce.extensions.payment.views.cybersource.basket_add_organization_attribute"
        ) as mock_basket_add_org:

            # This response has been pruned to only the needed data.
            self._prep_request_success(
                """{"links":{"_self":{"href":"/pts/v2/payments/6031827608526961004260","method":"GET"}},"id":"6031827608526961004260","submit_time_utc":"2020-10-20T08:32:44Z","status":"AUTHORIZED","client_reference_information":{"code":"%s"},"processor_information":{"approval_code":"307640","transaction_id":"380294307616695","network_transaction_id":"380294307616695","response_code":"000","avs":{"code":"G","code_raw":"G"},"card_verification":{"result_code":"M","result_code_raw":"M"},"consumer_authentication_response":{"code":"99"}},"payment_information":{"tokenized_card":{"type":"001"},"account_features":{"category":"F"}},"order_information":{"amount_details":{"total_amount":"5.00","authorized_amount":"5.00","currency":"USD"}}}"""
                % order_number  # pylint: disable=line-too-long
            )
            response = self.client.post(self.path, data)

            assert response.status_code == 201
            assert response['content-type'] == JSON
            assert json.loads(
                response.content)['receipt_page_url'] == get_receipt_page_url(
                    self.site.siteconfiguration,
                    order_number=order_number,
                    disable_back_button=True,
                )

            # Ensure the basket is Submitted
            basket = Basket.objects.get(pk=basket.pk)
            self.assertEqual(basket.status, Basket.SUBMITTED)

        mock_basket_add_org.assert_called_once()
Exemple #16
0
    def get(self, request):
        """Handle an incoming user returned to us by PayPal after approving payment."""
        payment_id = request.GET.get('paymentId')
        payer_id = request.GET.get('PayerID')
        logger.info(u"Payment [%s] approved by payer [%s]", payment_id,
                    payer_id)

        paypal_response = request.GET.dict()
        basket = self._get_basket(payment_id)

        if not basket:
            return redirect(self.payment_processor.error_url)

        receipt_url = get_receipt_page_url(
            order_number=basket.order_number,
            site_configuration=basket.site.siteconfiguration,
            disable_back_button=True,
        )

        try:
            with transaction.atomic():
                try:
                    self.handle_payment(paypal_response, basket)
                except PaymentError:
                    return redirect(self.payment_processor.error_url)
        except:  # pylint: disable=bare-except
            logger.exception(
                'Attempts to handle payment for basket [%d] failed.',
                basket.id)
            return redirect(receipt_url)

        self.call_handle_order_placement(basket, request)

        return redirect(receipt_url)
    def redirect_to_receipt_page(self, notification):
        receipt_page_url = get_receipt_page_url(
            self.request.site.siteconfiguration,
            order_number=notification.get('req_reference_number')
        )

        return redirect(receipt_page_url)
Exemple #18
0
    def get_redirect_url(self, *args, **kwargs):
        request = self.request
        site = request.site
        basket = Basket.get_basket(request.user, site)

        if not basket.is_empty:
            # Need to re-apply the voucher to the basket.
            Applicator().apply(basket, request.user, request)
            if basket.total_incl_tax != Decimal(0):
                raise BasketNotFreeError(
                    'Basket [{}] is not free. User affected [{}]'.format(
                        basket.id, basket.owner.id))

            order = self.place_free_order(basket)

            if has_enterprise_offer(basket):
                # Skip the receipt page and redirect to the LMS
                # if the order is free due to an Enterprise-related offer.
                program_uuid = get_program_uuid(order)
                if program_uuid:
                    url = get_lms_program_dashboard_url(program_uuid)
                else:
                    course_run_id = order.lines.all()[:1].get(
                    ).product.course.id
                    url = get_lms_courseware_url(course_run_id)
            else:
                receipt_path = get_receipt_page_url(
                    order_number=order.number,
                    site_configuration=order.site.siteconfiguration)
                url = site.siteconfiguration.build_lms_url(receipt_path)
        else:
            # If a user's basket is empty redirect the user to the basket summary
            # page which displays the appropriate message for empty baskets.
            url = reverse('basket:summary')
        return url
Exemple #19
0
    def assert_redirects_to_receipt_page(self, code=COUPON_CODE, consent_token=None):
        response = self.redeem_coupon(code=code, consent_token=consent_token)

        order = Order.objects.first()
        receipt_page_url = get_receipt_page_url(self.site.siteconfiguration)
        expected_url = format_url(base=receipt_page_url, params={'order_number': order.number})

        self.assertRedirects(response, expected_url, status_code=302, fetch_redirect_response=False)
Exemple #20
0
 def assert_successful_order_response(self, response, order_number):
     assert response.status_code == 201
     receipt_url = get_receipt_page_url(
         self.site_configuration,
         order_number,
         disable_back_button=True,
     )
     assert response.json() == {'url': receipt_url}
Exemple #21
0
    def redirect_to_receipt_page(self):
        receipt_page_url = get_receipt_page_url(
            self.request.site.siteconfiguration,
            order_number=self.order_number,
            disable_back_button=True,
        )

        return redirect(receipt_page_url)
 def redirect_to_receipt_page(self):
     receipt_page_url = get_receipt_page_url(
         self.request.site.siteconfiguration,
         order_number=self.order_number,
         disable_back_button=True,
     )
     return JsonResponse({
         'receipt_page_url': receipt_page_url,
     }, status=201)
Exemple #23
0
    def get(self, request):
        """Handle an incoming user returned to us by PayPal after approving payment."""
        payment_id = request.GET.get('paymentId')
        payer_id = request.GET.get('PayerID')
        logger.info(u"Payment [%s] approved by payer [%s]", payment_id,
                    payer_id)

        paypal_response = request.GET.dict()
        basket = self._get_basket(payment_id)

        if not basket:
            return redirect(self.payment_processor.error_url)

        receipt_url = get_receipt_page_url(
            order_number=basket.order_number,
            site_configuration=basket.site.siteconfiguration)

        try:
            with transaction.atomic():
                try:
                    self.handle_payment(paypal_response, basket)
                except PaymentError:
                    return redirect(self.payment_processor.error_url)
        except:  # pylint: disable=bare-except
            logger.exception(
                'Attempts to handle payment for basket [%d] failed.',
                basket.id)
            return redirect(receipt_url)

        try:
            shipping_method = NoShippingRequired()
            shipping_charge = shipping_method.calculate(basket)
            order_total = OrderTotalCalculator().calculate(
                basket, shipping_charge)

            user = basket.owner
            # Given a basket, order number generation is idempotent. Although we've already
            # generated this order number once before, it's faster to generate it again
            # than to retrieve an invoice number from PayPal.
            order_number = basket.order_number

            order = self.handle_order_placement(
                order_number=order_number,
                user=user,
                basket=basket,
                shipping_address=None,
                shipping_method=shipping_method,
                shipping_charge=shipping_charge,
                billing_address=None,
                order_total=order_total,
                request=request)
            self.handle_post_order(order)

            return redirect(receipt_url)
        except:  # pylint: disable=bare-except
            logger.exception(self.order_placement_failure_msg, basket.id)
            return redirect(receipt_url)
Exemple #24
0
    def get(self, request):
        """
        Handles an incoming user returned to us by Paystack after approving payment.
        It redirects user to order receipt page after completing all payment flow
        of a successfull Paystack transaction.
        """
        reference = request.GET.get('reference')
        success, response = self.payment_processor.paystack_client.handler(
            VERIFY_TRANSACTION_CODE, reference)

        if success:
            data = response.get('data')
            metadata = data.get('metadata')

            order_number = metadata.get('order_number')
            basket_id = metadata.get('basket_id')
            transaction_id = data.get('id')
            logger.info(
                "Received Paystack payment notification for transaction: %s, associated with basket: %d.",
                transaction_id, int(basket_id))

            basket = self.get_basket(basket_id)
            if not basket:
                logger.error(
                    "Received Paystack response for non-existent basket: %d.",
                    basket_id)
                raise InvalidBasketError
            if basket.status != Basket.FROZEN:
                logger.info(
                    "Received Paystack response for basket [%d] which is in a non-frozen state, [%s].",
                    basket.id, basket.status)

            self.payment_processor.record_processor_response(
                response, transaction_id=reference, basket=basket)

            receipt_url = get_receipt_page_url(
                order_number=order_number,
                site_configuration=basket.site.siteconfiguration)

            try:
                with transaction.atomic():
                    self.handle_payment(data, basket)
                    self.call_handle_order_placement(basket, request)

            except Exception:  # pylint: disable=broad-except
                logger.exception(
                    "Attempts to handle payment for basket [%d] failed.",
                    basket.id)
                self.log_order_placement_exception(order_number, basket.id)
            else:
                return redirect(receipt_url)

        logger.error(
            "Unable to process Paystack transaction with reference: %s.",
            reference)
        return redirect(self.payment_processor.error_url)
Exemple #25
0
    def get(self, request):
        """Handle an incoming user returned to us by PayPal after approving payment."""
        payment_id = request.GET.get('paymentId')
        payer_id = request.GET.get('PayerID')
        logger.info(u"Payment [%s] approved by payer [%s]", payment_id, payer_id)

        paypal_response = request.GET.dict()
        basket = self._get_basket(payment_id)

        if not basket:
            return redirect(self.payment_processor.error_url)

        receipt_url = get_receipt_page_url(
            order_number=basket.order_number,
            site_configuration=basket.site.siteconfiguration
        )

        try:
            with transaction.atomic():
                try:
                    self.handle_payment(paypal_response, basket)
                except PaymentError:
                    return redirect(self.payment_processor.error_url)
        except:  # pylint: disable=bare-except
            logger.exception('Attempts to handle payment for basket [%d] failed.', basket.id)
            return redirect(receipt_url)

        try:
            shipping_method = NoShippingRequired()
            shipping_charge = shipping_method.calculate(basket)
            order_total = OrderTotalCalculator().calculate(basket, shipping_charge)

            user = basket.owner
            # Given a basket, order number generation is idempotent. Although we've already
            # generated this order number once before, it's faster to generate it again
            # than to retrieve an invoice number from PayPal.
            order_number = basket.order_number

            self.handle_order_placement(
                order_number=order_number,
                user=user,
                basket=basket,
                shipping_address=None,
                shipping_method=shipping_method,
                shipping_charge=shipping_charge,
                billing_address=None,
                order_total=order_total,
                request=request
            )

            return redirect(receipt_url)
        except:  # pylint: disable=bare-except
            logger.exception(self.order_placement_failure_msg, basket.id)
            return redirect(receipt_url)
Exemple #26
0
    def test_duplicate_payment(self):
        """ Verify the view errors as expected if there is a duplicate payment attempt after a successful payment. """
        # This unit test describes the current state of the code, but I'm not sure if there are other
        # situations that would result in a duplicate payment that we should handle differently.

        basket = self._create_valid_basket()
        data = self._generate_data(basket.id)
        order_number = OrderNumberGenerator().order_number_from_basket_id(
            self.site.siteconfiguration.partner,
            basket.id,
        )
        # This response has been pruned to only the needed data.
        self._prep_request_success(
            """{"id":"6028635251536131304003","status":"AUTHORIZED","client_reference_information":{"code":"%s"},"processor_information":{"approval_code":"831000","transaction_id":"558196000003814","network_transaction_id":"558196000003814","card_verification":{"result_code":"3"}},"payment_information":{"tokenized_card":{"type":"001"},"account_features":{"category":"A"}},"order_information":{"amount_details":{"total_amount":"99.00","authorized_amount":"99.00","currency":"USD"}}}"""
            % order_number  # pylint: disable=line-too-long
        )
        response = self.client.post(self.path, data)

        assert response.status_code == 201
        assert response['content-type'] == JSON
        assert json.loads(
            response.content)['receipt_page_url'] == get_receipt_page_url(
                self.site.siteconfiguration,
                order_number=order_number,
                disable_back_button=True,
            )

        # Ensure the basket is Submitted
        basket = Basket.objects.get(pk=basket.pk)
        self.assertEqual(basket.status, Basket.SUBMITTED)

        # This response has been pruned to only the needed data.
        self._prep_request_invalid(
            """{"submitTimeUtc":"2020-09-30T18:53:23Z","status":"INVALID_REQUEST","reason":"DUPLICATE_REQUEST","message":"Declined - The\u00a0merchantReferenceCode\u00a0sent with this authorization request matches the merchantReferenceCode of another authorization request that you sent in the last 15 minutes."}""",  # pylint: disable=line-too-long
            '6028635251536131304003')
        response = self.client.post(self.path, data)

        # The original basket is frozen, and the new basket is empty, so currentyl this triggers an error response
        assert response.status_code == 400
        assert response['content-type'] == JSON
        assert json.loads(response.content) == {
            'error':
            'There was a problem retrieving your basket. Refresh the page to try again.',
            'field_errors': {
                'basket':
                'There was a problem retrieving your basket. Refresh the page to try again.'
            }
        }

        # Ensure the basket is frozen
        basket = Basket.objects.get(pk=basket.pk + 1)
        self.assertEqual(basket.status, Basket.OPEN)
Exemple #27
0
    def test_successful_redirect(self):
        """ Verify redirect to the receipt page. """
        self.prepare_basket(0)
        self.assertEqual(Order.objects.count(), 0)
        response = self.client.get(self.path)
        self.assertEqual(Order.objects.count(), 1)

        order = Order.objects.first()
        expected_url = get_receipt_page_url(
            order_number=order.number,
            site_configuration=order.site.siteconfiguration
        )
        self.assertRedirects(response, expected_url, fetch_redirect_response=False)
Exemple #28
0
    def test_successful_redirect(self):
        """ Verify redirect to the receipt page. """
        self.prepare_basket(0)
        self.assertEqual(Order.objects.count(), 0)
        response = self.client.get(self.path)
        self.assertEqual(Order.objects.count(), 1)

        order = Order.objects.first()
        expected_url = get_receipt_page_url(
            order_number=order.number,
            site_configuration=order.site.siteconfiguration
        )
        self.assertRedirects(response, expected_url, fetch_redirect_response=False)
Exemple #29
0
 def get(self, request):
     """
     """
     try:
         out_trade_no = request.GET.get('out_trade_no')
         basket = PaymentProcessorResponse.objects.get(
             transaction_id=out_trade_no).basket
         receipt_url = get_receipt_page_url(
             order_number=basket.order_number,
             site_configuration=basket.site.siteconfiguration)
         return redirect(receipt_url)
     except Exception, e:
         logger.exception(e)
Exemple #30
0
    def test_post_checkout_callback(self):
        """
        When the post_checkout signal is emitted, the receiver should attempt
        to fulfill the newly-placed order and send receipt email.
        """
        credit_provider_id = 'HGW'
        credit_provider_name = 'Hogwarts'
        body = {'display_name': credit_provider_name}
        httpretty.register_uri(
            httpretty.GET,
            self.site.siteconfiguration.build_lms_url(
                'api/credit/v1/providers/{credit_provider_id}/'.format(credit_provider_id=credit_provider_id)
            ),
            body=json.dumps(body),
            content_type='application/json'
        )

        order = self.prepare_order('credit', credit_provider_id=credit_provider_id)
        self.mock_access_token_response()
        send_course_purchase_email(None, user=self.user, order=order)
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].from_email, order.site.siteconfiguration.from_email)
        self.assertEqual(mail.outbox[0].subject, 'Order Receipt')
        self.assertEqual(
            mail.outbox[0].body,
            '\nPayment confirmation for: {course_title}'
            '\n\nDear {full_name},'
            '\n\nThank you for purchasing {credit_hours} credit hours from {credit_provider_name} for {course_title}. '
            'A charge will appear on your credit or debit card statement with a company name of "{platform_name}".'
            '\n\nTo receive your course credit, you must also request credit at the {credit_provider_name} website. '
            'For a link to request credit from {credit_provider_name}, or to see the status of your credit request, '
            'go to your {platform_name} dashboard.'
            '\n\nTo explore other credit-eligible courses, visit the {platform_name} website. '
            'We add new courses frequently!'
            '\n\nTo view your payment information, visit the following website.'
            '\n{receipt_url}'
            '\n\nThank you. We hope you enjoyed your course!'
            '\nThe {platform_name} team'
            '\n\nYou received this message because you purchased credit hours for {course_title}, '
            'an {platform_name} course.\n'.format(
                course_title=order.lines.first().product.title,
                full_name=self.user.get_full_name(),
                credit_hours=2,
                credit_provider_name=credit_provider_name,
                platform_name=self.site.name,
                receipt_url=get_receipt_page_url(
                    order_number=order.number,
                    site_configuration=order.site.siteconfiguration
                )
            )
        )
Exemple #31
0
    def test_execution_for_bulk_purchase(self):
        """
        Verify redirection to LMS receipt page after attempted payment
        execution if the Otto receipt page is disabled for bulk purchase and
        also that the order is linked to the provided business client..
        """
        toggle_switch(ENROLLMENT_CODE_SWITCH, True)
        self.mock_oauth2_response()

        course = CourseFactory(partner=self.partner)
        course.create_or_update_seat('verified',
                                     True,
                                     50,
                                     create_enrollment_code=True)
        self.basket = create_basket(owner=UserFactory(), site=self.site)
        enrollment_code = Product.objects.get(
            product_class__name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME)
        factories.create_stockrecord(enrollment_code,
                                     num_in_stock=2,
                                     price_excl_tax='10.00')
        self.basket.add_product(enrollment_code, quantity=1)

        # Create a payment record the view can use to retrieve a basket
        self.mock_payment_creation_response(self.basket)
        self.processor.get_transaction_parameters(self.basket,
                                                  request=self.request)
        self.mock_payment_execution_response(self.basket)
        self.mock_payment_creation_response(self.basket, find=True)

        # Manually add organization attribute on the basket for testing
        self.RETURN_DATA.update({'organization': 'Dummy Business Client'})
        self.RETURN_DATA.update({PURCHASER_BEHALF_ATTRIBUTE: 'False'})
        basket_add_organization_attribute(self.basket, self.RETURN_DATA)

        response = self.client.get(reverse('paypal:execute'), self.RETURN_DATA)
        self.assertRedirects(
            response,
            get_receipt_page_url(
                order_number=self.basket.order_number,
                site_configuration=self.basket.site.siteconfiguration,
                disable_back_button=True,
            ),
            fetch_redirect_response=False)

        # 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=self.RETURN_DATA['organization'])
        assert Invoice.objects.get(
            order=order).business_client == business_client
Exemple #32
0
    def test_post_checkout_callback(self):
        """
        When the post_checkout signal is emitted, the receiver should attempt
        to fulfill the newly-placed order and send receipt email.
        """
        credit_provider_id = 'HGW'
        credit_provider_name = 'Hogwarts'
        body = {'display_name': credit_provider_name}
        responses.add(
            responses.GET,
            self.site.siteconfiguration.build_lms_url(
                'api/credit/v1/providers/{credit_provider_id}/'.format(credit_provider_id=credit_provider_id)
            ),
            json=body,
            content_type='application/json'
        )

        order = self.prepare_order('credit', credit_provider_id=credit_provider_id)
        self.mock_access_token_response()
        send_course_purchase_email(None, user=self.user, order=order)
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].from_email, order.site.siteconfiguration.from_email)
        self.assertEqual(mail.outbox[0].subject, 'Order Receipt')
        self.assertEqual(
            mail.outbox[0].body,
            '\nPayment confirmation for: {course_title}'
            '\n\nDear {full_name},'
            '\n\nThank you for purchasing {credit_hours} credit hours from {credit_provider_name} for {course_title}. '
            'A charge will appear on your credit or debit card statement with a company name of "{platform_name}".'
            '\n\nTo receive your course credit, you must also request credit at the {credit_provider_name} website. '
            'For a link to request credit from {credit_provider_name}, or to see the status of your credit request, '
            'go to your {platform_name} dashboard.'
            '\n\nTo explore other credit-eligible courses, visit the {platform_name} website. '
            'We add new courses frequently!'
            '\n\nTo view your payment information, visit the following website.'
            '\n{receipt_url}'
            '\n\nThank you. We hope you enjoyed your course!'
            '\nThe {platform_name} team'
            '\n\nYou received this message because you purchased credit hours for {course_title}, '
            'an {platform_name} course.\n'.format(
                course_title=order.lines.first().product.title,
                full_name=self.user.get_full_name(),
                credit_hours=2,
                credit_provider_name=credit_provider_name,
                platform_name=self.site.name,
                receipt_url=get_receipt_page_url(
                    order_number=order.number,
                    site_configuration=order.site.siteconfiguration
                )
            )
        )
Exemple #33
0
    def form_valid(self, form):
        form_data = form.cleaned_data
        basket = form_data['basket']
        token = form_data['stripe_token']
        order_number = basket.order_number

        basket_add_organization_attribute(basket, self.request.POST)

        try:
            billing_address = self.payment_processor.get_address_from_token(
                token)
        except Exception:  # pylint: disable=broad-except
            logger.exception(
                'An error occurred while parsing the billing address for basket [%d]. No billing address will be '
                'stored for the resulting order [%s].', basket.id,
                order_number)
            billing_address = None

        try:
            self.handle_payment(token, basket)
        except Exception:  # pylint: disable=broad-except
            logger.exception(
                'An error occurred while processing the Stripe payment for basket [%d].',
                basket.id)
            return JsonResponse({}, status=400)

        shipping_method = NoShippingRequired()
        shipping_charge = shipping_method.calculate(basket)
        order_total = OrderTotalCalculator().calculate(basket, shipping_charge)

        order = self.handle_order_placement(order_number=order_number,
                                            user=basket.owner,
                                            basket=basket,
                                            shipping_address=None,
                                            shipping_method=shipping_method,
                                            shipping_charge=shipping_charge,
                                            billing_address=billing_address,
                                            order_total=order_total,
                                            request=self.request)
        self.handle_post_order(order)

        receipt_url = get_receipt_page_url(
            site_configuration=self.request.site.siteconfiguration,
            order_number=order_number,
            disable_back_button=True,
        )
        return JsonResponse({'url': receipt_url}, status=201)
Exemple #34
0
    def get(self, request):
        """Handle an incoming user returned to us by PayPal after approving payment."""
        payment_id = request.GET.get('paymentId')
        payer_id = request.GET.get('PayerID')
        logger.info(u"Payment [%s] approved by payer [%s]", payment_id,
                    payer_id)

        paypal_response = request.GET.dict()
        basket = self._get_basket(payment_id)

        if not basket:
            return redirect(self.payment_processor.error_url)

        receipt_url = get_receipt_page_url(
            order_number=basket.order_number,
            site_configuration=basket.site.siteconfiguration,
            disable_back_button=True,
        )

        try:
            with transaction.atomic():
                try:
                    self.handle_payment(paypal_response, basket)
                except PaymentError:
                    return redirect(self.payment_processor.error_url)
        except:  # pylint: disable=bare-except
            logger.exception(
                'Attempts to handle payment for basket [%d] failed.',
                basket.id)
            return redirect(receipt_url)

        try:
            order = self.create_order(request, basket)
        except Exception:  # pylint: disable=broad-except
            # any errors here will be logged in the create_order method. If we wanted any
            # Paypal specific logging for this error, we would do that here.
            return redirect(receipt_url)

        try:
            self.handle_post_order(order)
        except Exception:  # pylint: disable=broad-except
            self.log_order_placement_exception(basket.order_number, basket.id)

        return redirect(receipt_url)
Exemple #35
0
    def test_execution_redirect_to_lms(self):
        """
        Verify redirection to LMS receipt page after attempted payment execution if the Otto receipt page is disabled.
        """
        self.mock_oauth2_response()

        # Create a payment record the view can use to retrieve a basket
        self.mock_payment_creation_response(self.basket)
        self.processor.get_transaction_parameters(self.basket,
                                                  request=self.request)
        self.mock_payment_execution_response(self.basket)

        response = self.client.get(reverse('paypal:execute'), self.RETURN_DATA)
        self.assertRedirects(
            response,
            get_receipt_page_url(
                order_number=self.basket.order_number,
                site_configuration=self.basket.site.siteconfiguration),
            fetch_redirect_response=False)
Exemple #36
0
def send_confirm_purchase_email(sender, order=None, request=None, **kwargs):
    product = order.lines.first().product
    recipient = request.POST.get('req_bill_to_email', order.user.email) if request else order.user.email
    receipt_page_url = get_receipt_page_url(
        order_number=order.number,
        site_configuration=order.site.siteconfiguration
    )
    send_notification(
        order.user,
        'CREDIT_RECEIPT',
        {
            'course_title': "Test",
            'receipt_page_url': receipt_page_url,
            'credit_hours': 20,
            'credit_provider': "test",
        },
            order.site,
            recipient
    )
Exemple #37
0
    def get(self, request):
        """Handle an incoming user returned to us by Alipay after approving payment."""
        payment_id = request.GET.get('out_trade_no')
        logger.info(u"Payment [%s] approved", payment_id)

        alipay_response = request.GET.dict()
        basket = self._get_basket(payment_id)

        if not basket:
            return redirect(self.payment_processor.error_url)

        receipt_url = get_receipt_page_url(
            order_number=basket.order_number,
            site_configuration=basket.site.siteconfiguration
        )

        try:
            shipping_method = NoShippingRequired()
            shipping_charge = shipping_method.calculate(basket)
            order_total = OrderTotalCalculator().calculate(basket, shipping_charge)

            user = basket.owner
            # Given a basket, order number generation is idempotent. Although we've already
            # generated this order number once before, it's faster to generate it again
            # than to retrieve an invoice number from Alipay.
            order_number = basket.order_number

            self.handle_order_placement(
                order_number=order_number,
                user=user,
                basket=basket,
                shipping_address=None,
                shipping_method=shipping_method,
                shipping_charge=shipping_charge,
                billing_address=None,
                order_total=order_total,
                request=request
            )

            return redirect(receipt_url)
        except:  # pylint: disable=bare-except
            logger.exception(self.order_placement_failure_msg, basket.id)
            return redirect(receipt_url)
Exemple #38
0
    def test_execution_redirect_to_lms(self):
        """
        Verify redirection to LMS receipt page after attempted payment execution if Otto receipt page waffle
        switch is disabled.
        """
        self.site.siteconfiguration.enable_otto_receipt_page = False
        self.mock_oauth2_response()

        # Create a payment record the view can use to retrieve a basket
        self.mock_payment_creation_response(self.basket)
        self.processor.get_transaction_parameters(self.basket, request=self.request)
        self.mock_payment_execution_response(self.basket)

        response = self.client.get(reverse('paypal_execute'), self.RETURN_DATA)
        self.assertRedirects(
            response,
            get_receipt_page_url(
                order_number=self.basket.order_number,
                site_configuration=self.basket.site.siteconfiguration
            ),
            fetch_redirect_response=False
        )
Exemple #39
0
def send_course_purchase_email(sender, order=None, **kwargs):  # pylint: disable=unused-argument
    """Send course purchase notification email when a course is purchased."""
    if waffle.switch_is_active('ENABLE_NOTIFICATIONS'):
        # We do not currently support email sending for orders with more than one item.
        if len(order.lines.all()) == ORDER_LINE_COUNT:
            product = order.lines.first().product
            credit_provider_id = getattr(product.attr, 'credit_provider', None)
            if not credit_provider_id:
                logger.error(
                    'Failed to send credit receipt notification. Credit seat product [%s] has no provider.', product.id
                )
                return
            elif product.get_product_class().name == 'Seat':
                provider_data = get_credit_provider_details(
                    access_token=order.site.siteconfiguration.access_token,
                    credit_provider_id=credit_provider_id,
                    site_configuration=order.site.siteconfiguration
                )

                receipt_page_url = get_receipt_page_url(
                    order_number=order.number,
                    site_configuration=order.site.siteconfiguration
                )

                if provider_data:
                    send_notification(
                        order.user,
                        'CREDIT_RECEIPT',
                        {
                            'course_title': product.title,
                            'receipt_page_url': receipt_page_url,
                            'credit_hours': product.attr.credit_hours,
                            'credit_provider': provider_data['display_name'],
                        },
                        order.site
                    )

        else:
            logger.info('Currently support receipt emails for order with one item.')
Exemple #40
0
    def _assert_execution_redirect(self, payer_info=None, url_redirect=None):
        """Verify redirection to Otto receipt page after attempted payment execution."""
        self.mock_oauth2_response()

        # Create a payment record the view can use to retrieve a basket
        self.mock_payment_creation_response(self.basket)
        self.processor.get_transaction_parameters(self.basket, request=self.request)

        creation_response = self.mock_payment_creation_response(self.basket, find=True)
        execution_response = self.mock_payment_execution_response(self.basket, payer_info=payer_info)

        response = self.client.get(reverse('paypal_execute'), self.RETURN_DATA)
        self.assertRedirects(
            response,
            url_redirect or get_receipt_page_url(
                order_number=self.basket.order_number,
                site_configuration=self.basket.site.siteconfiguration
            ),
            fetch_redirect_response=False
        )

        return creation_response, execution_response
Exemple #41
0
    def get_redirect_url(self, *args, **kwargs):
        basket = Basket.get_basket(self.request.user, self.request.site)
        if not basket.is_empty:
            # Need to re-apply the voucher to the basket.
            Applicator().apply(basket, self.request.user, self.request)
            if basket.total_incl_tax != Decimal(0):
                raise BasketNotFreeError(
                    "Basket [{}] is not free. User affected [{}]".format(
                        basket.id,
                        basket.owner.id
                    )
                )

            order = self.place_free_order(basket)
            receipt_path = get_receipt_page_url(
                order_number=order.number,
                site_configuration=order.site.siteconfiguration
            )
            url = get_lms_url(receipt_path)
        else:
            # If a user's basket is empty redirect the user to the basket summary
            # page which displays the appropriate message for empty baskets.
            url = reverse('basket:summary')
        return url
Exemple #42
0
 def _get_receipt_url(self):
     """DRY helper for getting receipt page URL."""
     return get_receipt_page_url(site_configuration=self.site.siteconfiguration)
Exemple #43
0
    def _generate_parameters(self, basket, use_sop_profile, **kwargs):
        """ Generates the parameters dict.

        A signature is NOT included in the parameters.

         Arguments:
            basket (Basket): Basket from which the pricing and item details are pulled.
            use_sop_profile (bool, optional): Indicates if the Silent Order POST profile should be used.
            **kwargs: Additional parameters to add to the generated dict.

         Returns:
             dict: Dictionary containing the payment parameters that should be sent to CyberSource.
        """
        site = basket.site

        access_key = self.access_key
        profile_id = self.profile_id

        if use_sop_profile:
            access_key = self.sop_access_key
            profile_id = self.sop_profile_id

        parameters = {
            'access_key': access_key,
            'profile_id': profile_id,
            'transaction_uuid': uuid.uuid4().hex,
            'signed_field_names': '',
            'unsigned_field_names': '',
            'signed_date_time': datetime.datetime.utcnow().strftime(ISO_8601_FORMAT),
            'locale': self.language_code,
            'transaction_type': 'sale',
            'reference_number': basket.order_number,
            'amount': str(basket.total_incl_tax),
            'currency': basket.currency,
            'override_custom_receipt_page': get_receipt_page_url(
                site_configuration=site.siteconfiguration,
                order_number=basket.order_number,
                override_url=site.siteconfiguration.build_ecommerce_url(
                    reverse('cybersource_redirect')
                )
            ),
            'override_custom_cancel_page': self.cancel_page_url,
        }
        # Level 2/3 details
        if self.send_level_2_3_details:
            parameters['amex_data_taa1'] = site.name
            parameters['purchasing_level'] = '3'
            parameters['line_item_count'] = basket.lines.count()
            # Note (CCB): This field (purchase order) is required for Visa;
            # but, is not actually used by us/exposed on the order form.
            parameters['user_po'] = 'BLANK'

            for index, line in enumerate(basket.lines.all()):
                parameters['item_{}_code'.format(index)] = line.product.get_product_class().slug
                parameters['item_{}_discount_amount '.format(index)] = str(line.discount_value)
                # Note (CCB): This indicates that the total_amount field below includes tax.
                parameters['item_{}_gross_net_indicator'.format(index)] = 'Y'
                parameters['item_{}_name'.format(index)] = clean_field_value(line.product.title)
                parameters['item_{}_quantity'.format(index)] = line.quantity
                parameters['item_{}_sku'.format(index)] = line.stockrecord.partner_sku
                parameters['item_{}_tax_amount'.format(index)] = str(line.line_tax)
                parameters['item_{}_tax_rate'.format(index)] = '0'
                parameters['item_{}_total_amount '.format(index)] = str(line.line_price_incl_tax_incl_discounts)
                # Note (CCB): Course seat is not a unit of measure. Use item (ITM).
                parameters['item_{}_unit_of_measure'.format(index)] = 'ITM'
                parameters['item_{}_unit_price'.format(index)] = str(line.unit_price_incl_tax)

        # Only send consumer_id for hosted payment page
        if not use_sop_profile:
            parameters['consumer_id'] = basket.owner.username

        # Add the extra parameters
        parameters.update(kwargs.get('extra_parameters', {}))

        # Mitigate PCI compliance issues
        signed_field_names = parameters.keys()
        if any(pci_field in signed_field_names for pci_field in self.PCI_FIELDS):
            raise PCIViolation('One or more PCI-related fields is contained in the payment parameters. '
                               'This service is NOT PCI-compliant! Deactivate this service immediately!')

        return parameters