var edx = edx || {}; (function($) { 'use strict'; edx.dashboard = edx.dashboard || {}; edx.dashboard.donation = {}; /** * View for making donations for a course. * @constructor * @param {Object} params * @param {Object} params.el - The container element. * @param {string} params.course - The ID of the course for the donation. */ edx.dashboard.donation.DonationView = function(params) { /** * Dynamically configure a form, which the client can submit * to the payment processor. * @param {Object} form - The form to modify. * @param {string} method - The HTTP method used to submit the form. * @param {string} url - The URL where the form data will be submitted. * @param {Object} params - Form data, included as hidden inputs. */ var configureForm = function(form, method, url, params) { $('input', form).remove(); form.attr('action', url); form.attr('method', method); _.each(params, function(value, key) { $('').attr({ type: 'hidden', name: key, value: value }).appendTo(form); }); }; /** * Fire an analytics event indicating that the user * is about to be sent to the external payment processor. * * @param {string} course - The course ID for the donation. */ var firePaymentAnalyticsEvent = function(course) { analytics.track( 'edx.bi.user.payment_processor.visited', { category: 'donations', label: course } ); }; /** * Add a donation to the user's cart. * * @param {string} amount - The amount of the donation (e.g. "23.45") * @param {string} course - The ID of the course. * @returns {Object} The promise from the AJAX call to the server, * which resolves with a data object of the form * { payment_url: , payment_params: } */ var addDonationToCart = function(amount, course) { return $.ajax({ url: '/shoppingcart/donation/', type: 'POST', data: { amount: amount, course_id: course } }); }; var view = { /** * Initialize the view. * * @param {Object} params * @param {JQuery selector} params.el - The container element. * @param {string} params.course - The ID of the course for the donation. * @returns {DonationView} */ initialize: function(params) { this.$el = params.el; this.course = params.course; _.bindAll(view, 'render', 'donate', 'startPayment', 'validate', 'startPayment', 'displayServerError', 'submitPaymentForm' ); return this; }, /** * Render the form for making a donation for a course. * * @returns {DonationView} */ render: function() { var html = _.template($('#donation-tpl').html())({}); this.$el.html(html); this.$amount = $('input[name="amount"]', this.$el); this.$submit = $('.action-donate', this.$el); this.$errorMsg = $('.donation-error-msg', this.$el); this.$paymentForm = $('.payment-form', this.$el); this.$submit.click(this.donate); return this; }, /** * Handle a click event on the "donate" button. * This will contact the LMS server to add the donation * to the user's cart, then send the user to the * external payment processor. * * @param {Object} event - The click event. */ donate: function(event) { // Prevent form submission if (event) { event.preventDefault(); } // Immediately disable the submit button to prevent duplicate submissions this.$submit.addClass('disabled'); if (this.validate()) { var amount = this.$amount.val(); addDonationToCart(amount, this.course) .done(this.startPayment) .fail(this.displayServerError); } else { // If an error occurred, allow the user to resubmit this.$submit.removeClass('disabled'); } }, /** * Send signed payment parameters to the external * payment processor. * * @param {Object} data - The signed payment data received from the LMS server. * @param {string} data.payment_url - The URL of the external payment processor. * @param {Object} data.payment_data - Parameters to send to the external payment processor. */ startPayment: function(data) { configureForm( this.$paymentForm, 'post', data.payment_url, data.payment_params ); firePaymentAnalyticsEvent(this.course); this.submitPaymentForm(this.$paymentForm); }, /** * Validate the donation amount and mark any validation errors. * * @returns {boolean} True iff the form is valid. */ validate: function() { var amount = this.$amount.val(); var isValid = this.validateAmount(amount); if (isValid) { this.$amount.removeClass('validation-error'); this.$errorMsg.text(''); } else { this.$amount.addClass('validation-error'); this.$errorMsg.text( gettext('Please enter a valid donation amount.') ); } return isValid; }, /** * Validate that the given amount is a valid currency string. * * @param {string} amount * @returns {boolean} True iff the amount is valid. */ validateAmount: function(amount) { var amountRegex = /^\d+.\d{2}$|^\d+$/i; if (!amountRegex.test(amount)) { return false; } if (parseFloat(amount) < 0.01) { return false; } return true; }, /** * Display an error message when we receive an error from the LMS server. */ displayServerError: function() { // Display the error message this.$errorMsg.text(gettext('Your donation could not be submitted.')); // Re-enable the submit button to allow the user to retry this.$submit.removeClass('disabled'); }, /** * Submit the payment from to the external payment processor. * This is a separate function so we can easily stub it out in tests. * * @param {Object} form - The dynamically constructed payment form. */ submitPaymentForm: function(form) { form.submit(); } }; view.initialize(params); return view; }; $(document).ready(function() { // There may be multiple donation forms on the page // (one for each newly enrolled course). // For each one, create a new donation view to handle // that form, and parameterize it based on the // "data-course" attribute (the course ID). $('.donate-container').each(function() { var $container = $(this); var course = $container.data('course'); var view = new edx.dashboard.donation.DonationView({ el: $container, course: course }).render(); }); }); }(jQuery));