Supporting a Variable Amount in Checkout

    Add a field to your payments page that enables users to specify the amount to be charged.

    Stripe Checkout takes care of building an HTML form for securely collecting your customers’ card data. When loading the Checkout modal, you can specify the payment amount presented to the user. In many applications, this amount has already been determined when the page is rendered and is simply included as an attribute on Checkout’s <script> tag:

    <!-- Checkout with $20 amount -->
    <script
      src="https://checkout.stripe.com/checkout.js"
      class="stripe-button"
      data-amount="2000">
    </script>

    However, some applications prefer to let their users determine a specific amount of money to be charged. This is most common in businesses that accept donations or invite the user to pay what they think an item is worth. Checkout supports this approach, too. This recipe walks you through a sample implementation, in the following sections:

    1. Create a form with a text field for the donation amount, along with a Donate button.
    2. Define a JavaScript handler that validates the user input and launches Checkout with the user-specified amount.
    3. Write a controller on the server that processes the amount and makes the charge.

    This implementation uses Rails, but the concepts can be easily ported to other languages and frameworks. The JavaScript in this example uses jQuery.

    Set up the application

    The steps below prepare the scaffolding for a basic payments page. If you already have a payments page, there’s no need to change anything according to these instructions. You can easily adapt the rest of this recipe to your existing implementation.

    First, ensure that the Stripe gem is in your application’s Gemfile, and then run bundle install. Next, add an initializer that configures the Stripe client library:

    Rails.configuration.stripe = {
      :publishable_key => ENV['PUBLISHABLE_KEY'],
      :secret_key      => ENV['SECRET_KEY']
    }
    Stripe.api_key = Rails.configuration.stripe[:secret_key]

    Note that this example uses environment variables for the Stripe API keys. This prevents sensitive credentials from being accidentally submitted to your version control repository. The end of this recipe demonstrates launching the server with values provided for these environment variables.

    Next, generate a new Charges controller:

    rails g controller charges new create

    This creates a few files, depending on your setup. For this recipe, the files you’ll be working with are:

    • app/controllers/charges_controller.rb
    • app/views/charges/new.html.erb
    • app/views/charges/create.html.erb

    Last, edit routes.rb, replacing any Rails-generated routes for charges with:

    resources :charges

    Create the payment form and the confirmation page

    The application requires two view files. The first is the payments page, containing the form in which the user specifies the donation amount and a button that ultimately launches Checkout. The second view is a basic confirmation page to which the user is redirected after completing the payment. (After these views are in place, in the next section, you’ll add the JavaScript to properly communicate the donation amount to Checkout.)

    As a point of reference, for applications with a fixed payment amount, a basic Checkout page might be:

    <!-- Example Checkout page that *does not* support a variable amount! -->
    <%= form_tag charges_path do %>
      <div id="error_explanation">
        <% if flash[:error].present? %>
          <p><%= flash[:error] %></p>
        <% end %>
      </div>
      <article>
        <label class="amount">
          <span>Amount: <%= number_to_currency(@amount * 100) %></span>
        </label>
      </article>
      <script src="https://checkout.stripe.com/checkout.js" class="stripe-button"
      data-key="<%= Rails.configuration.stripe[:publishable_key] %>"
      data-description="One-time donation"
      data-amount="<%= @amount %>"
      data-locale="auto"></script>
    <% end %>

    Note that the page is rendered with a known amount, @amount. This is also the provided value for the data-amount attribute in the Checkout <script> tag. This correspondence enables the Checkout modal to display the amount to the user as they’re entering their card details. Note also that the Checkout script is responsible for rendering a button on the form.

    For this recipe’s donation example, you must immediately make two changes:

    • You want a text field where the user can specify an amount to donate.
    • You don’t want Checkout to render a button on the page. Instead, you’ll have your own Donate button. This way, when the user clicks your custom button, you can run some JavaScript that verifies the user’s input and then launches the Checkout modal.

    With those changes in mind, begin your form in new.html.erb:

    <%= form_tag charges_path do %>
      <div id="error_explanation">
        <% if flash[:error].present? %>
          <p><%= flash[:error] %></p>
        <% end %>
      </div>
      <article>
        <%= label_tag(:amount, 'Donation Amount:') %>
        <%= text_field_tag(:amount) %>
      </article>
      <article>
        <%= hidden_field_tag(:stripeToken) %>
      </article>
      <button id='donateButton'>Donate</button>
    <% end %>

    Along with the two changes already mentioned, there’s a new hidden field called stripeToken. This hidden field submits the stripeToken (generated by Checkout) to your server. Your JavaScript will set this field after Checkout has collected the user’s payment information and has received the corresponding token from Stripe.

    Now add the code for the other view, create.html.erb: the donation confirmation page. You can use the handy Rails helper number_to_currency() to format the amount, converting it from Stripe-compatible cents into human-friendly dollars:

    <h2>Thank you!</h2>
    <p>Your donation of <strong><%= number_to_currency(@amount * 0.01) %></strong>
     has been received.</p>

    Define the JavaScript

    The first bit of JavaScript to add to the payment view will configure basic Checkout functionality. You can give Checkout a function to call after the user has entered their payment information. This callback sets the stripeToken field on the form and submits the form to the server.

    At the bottom of new.html.erb, add a <script> tag to include the Checkout library as well as your custom JavaScript. This should go after the form’s <% end %> tag. Write the StripeCheckout handler first:

    <script src="https://checkout.stripe.com/checkout.js"></script>
    
    <script>
    var handler = StripeCheckout.configure({
      key: '<%= Rails.configuration.stripe[:publishable_key] %>',
      locale: 'auto',
      name: 'Sand Castles United',
      description: 'One-time donation',
      token: function(token) {
        $('input#stripeToken').val(token.id);
        $('form').submit();
      }
    });
    </script>

    The handler object is configured with a few options, including your public API key and a callback for the token. See the Checkout docs for a comprehensive list of all available configuration options.

    Next, clicking the Donate button needs to call a JavaScript function that verifies the user-specified amount. If the amount is valid, the function launches Checkout, passing in the amount. Still within the <script> tags, add the following click handler (explained below):

    $('#donateButton').on('click', function(e) {
      e.preventDefault();
    
      $('#error_explanation').html('');
    
      var amount = $('input#amount').val();
      amount = amount.replace(/\$/g, '').replace(/\,/g, '')
    
      amount = parseFloat(amount);
    
      if (isNaN(amount)) {
        $('#error_explanation').html('<p>Please enter a valid amount in USD ($).</p>');
      }
      else if (amount < 5.00) {
        $('#error_explanation').html('<p>Donation amount must be at least $5.</p>');
      }
      else {
        amount = amount * 100; // Needs to be an integer!
        handler.open({
          amount: Math.round(amount)
        })
      }
    });

    This code first adds an event handler to #donateButton. When that button is clicked, the function calls preventDefault() on the event to halt the form’s submission. Remember, you want Checkout to collect the user’s payment information and set the stripeToken before the form is submitted.

    The script next verifies the user’s input:

    • Any dollar signs or commas are removed.
    • parseFloat() then attempts to convert the string into a float.
    • If conversion to a float fails (by returning NaN), #error_explanation is set to inform the user.

    Note that this verification has some flexibility: 7.00, 7, and 07 are all treated as $7.

    This example also enforces a minimum donation amount of $5.00. This is not required, but given that a minimum of $0.50 is needed for a Stripe charge, you will want to enforce a minimum of at least $0.50 in your implementation.

    Note also that this code assumes a user-specified amount in US dollars. Applications in other countries, or with international audiences, will want to consider other strategies—discussed at the end of this recipe.

    The handler.open() call opens the Checkout modal. This method is dynamically provided with an amount, converted to integer cents as required by Checkout.

    Finally—still within the <script> tag, and right below the click handler—add an event handler for popstate. This ensures that the Checkout modal will close should the user attempt to navigate away from the page:

    // Close Checkout on page navigation
    $(window).on('popstate', function() {
      handler.close();
    });

    With the views and client-side code prepared, you can now turn your attention to setting up the server.

    Write the controller

    After the user has completed Checkout, your above code will submit the form to the server with the parameters amount and stripeToken. At this point, you want to process and verify the user-specified amount once more server-side, using the same logic as in the JavaScript.

    Add the following methods to charges_controller.rb:

    def new
    end
    
    def create
      @amount = params[:amount]
    
      @amount = @amount.gsub('$', '').gsub(',', '')
    
      begin
        @amount = Float(@amount).round(2)
      rescue
        flash[:error] = 'Charge not completed. Please enter a valid amount in USD ($).'
        redirect_to new_charge_path
        return
      end
    
      @amount = (@amount * 100).to_i # Must be an integer!
    
      if @amount < 500
        flash[:error] = 'Charge not completed. Donation amount must be at least $5.'
        redirect_to new_charge_path
        return
      end
    
      Stripe::Charge.create(
        :amount => @amount,
        :currency => 'usd',
        :source => params[:stripeToken],
        :description => 'Custom donation'
      )
    
      rescue Stripe::CardError => e
        flash[:error] = e.message
        redirect_to new_charge_path
      end
    end

    This controller code processes the amount submitted to the server in the same manner as the JavaScript did. Parsing the string using Float() raises errors if the string has any non-numeric characters. Again, the minimum donation amount of $5.00 (evaluated as 500 cents) is enforced. If the amount fails any of these checks, the controller does not make the charge, and alerts the user to the problem. Otherwise, it completes the charge.

    It’s important to repeat these validations on the server in case the user has manipulated or bypassed your JavaScript validations. The server-side validation is also necessary to ensure that your parsing and validation logic are robust. You want your client logic and your server logic to reach the same conclusion about what amount to charge the user. JavaScript’s parseFloat() function is fairly lenient, but Ruby’s Float() function is strict. Having this strictness on the server ensures that input with ambiguity (like ‘5 5’) is rejected before a charge is made.

    With everything in place, you’re ready to test your solution. Remember to boot your app with your Stripe credentials:

    PUBLISHABLE_KEY=pk_test_g6do5S237ekq10r65BnxO6S0 \\
    SECRET_KEY=sk_test_BQokikJOvBiI2HlWgH4olfQ2 bundle exec rails s

    We’ve placed random API keys in the code. Replace these with your actual API keys to test this code for yourself.

    Then, you can find your payments page in your web browser at http://localhost:3000/charges/new.

    Advanced features

    The recipe to this point explains all the basic functionality needed to accept user-defined payments through Checkout. As always, there are ways you can improve upon this approach. Below are a few examples.

    Supporting multiple currencies

    The above validation and parsing logic assumes that the payment is being made in US dollars. It ignores the dollar symbol ($) and the digit group separator used in the US (the comma). Should your payment form accept a different currency, you might need to modify this logic.

    If you’d like to support multiple currencies, one solution is to add a drop-down currency selector next to the input field for the amount. The currency selected in this drop-down would then be considered both by JavaScript and the server, perhaps using a switch statement to determine what parsing and validation logic to use.

    In this scenario, when launching Checkout, you would pass in the parameter currency. This is the 3-letter ISO code for the currency. For more information about this parameter and other configuration options for Checkout, see the Checkout documentation.

    Supporting coupons

    If you have your own coupon system, this sets up another scenario in which you might dynamically change the amount displayed in Checkout. We have a detailed recipe on supporting coupons for standalone charges. However, the solution in that recipe modifies only the server’s charge request, and not the amount displayed in Checkout to the user.

    Using the concepts from this recipe, you could use JavaScript to validate a user-submitted coupon code, by making an Ajax request to an endpoint on the server, such as /coupons. The endpoint would return a discount percentage, and the JavaScript would then launch Checkout with the appropriate discounted amount.

    Localizing error messages

    Checkout supports localization in multiple languages, whereas the application generates error messages only in English. If your site has a multilingual audience, you’ll want to localize the application’s error messages, too, for a better user experience.

    Questions?

    We're always happy to help with code or other questions you might have! Search our documentation, contact support, or connect with our sales team. You can also chat live with other developers in #stripe on freenode.

    Was this page helpful? Yes No

    Send

    Thank you for helping improve Stripe's documentation. If you need help or have any questions, please consider contacting support.