This guide will take you through the steps to create a campaign contribution form. The framework can also be used for any site that takes contributions, such as non-profit organizations. This assumes that your server can run PHP, and that you either have or can add an SSL certificate for your domain.

Before you get started

There are a few setup steps to go through first.

  1. Create a Stripe account
  2. Obtain an SSL certificate for your website
  3. Set up Google Analytics goals (optional)

Setting up your Stripe account

Stripe is a credit card payment processor that allows you to process payments directly on your site. It has an intutive API and no setup or monthly costs, which is great for non-profits or or campaigns. To begin, create your account on their site. You will have to complete some basic information while the campaign is in test mode and later need to complete more formal documents to receive payments and put the site in “Live” mode.

Once you have a Stripe account, obtain the API keys from the settings page. You will need both the testing and production keys.

Stripe API Keys

Obtaining an SSL certificate for your website

This is a bit more of a challenge, and it assumes you have your own domain. Most hosting providers will have different ways for you to go about this, but the general workflow will be:

  1. Generate a Certificate Signing Request (CSR) for the server (manually or through a control panel section from your hosting provider).
  2. Request your SSL certificate using the CSR
  3. Receive the certificate (.crt file) and the certificate authority chain (.pem or other extension)
  4. Install the new credentials to your server (manually or through a control panel section from your hosting provider)

StartCom provides free Class 1 certificates and low-cost Class 2 and 3 certificates. Your hosting provider or domain registrar may offer a discount on the first year’s purchase of an SSL certificate as well.

SSL GUI

Set up Google Analytics goals

This is probably an overlooked piece of having a donations form, but it is important to know what kind of campaigns or messaging generated a conversion (e.g. somebody made a donation). If you already using Google Analytics on your site (free), you can go to the Admin tab and edit the Goals section. Create an event-based goal called “Donations” and set the category and action to the values below. You can changes these values, but they should match up with what you will use later on your donations form.

GA Goal Settings

Creating your donation form

Once you have SSL enabled on the server (meaning, you can go to https://example.com and not receive any warnings or errors) and your Stripe account enabled, you can begin building your form.

Fortunately, there is already a working example to start from. We will walk through each step it is using to process the donations. Download that repository now, as we will be going through it.

Configuration

The first few lines in index.php are to include a configuration file. This is a best-practice to not store your credentials with your code repository (presuming you are using Git or Subversion) because those those credentials may be compromised later on. Copy the config/app-config.php.dist file to config/app-config.php and complete with the Testing credentials you received when setting up Stripe. You can refer to the account settings if you had not made a note of them.

if (file_exists('config/app-config.php')) {
  require_once 'config/app-config.php';
} else {
  print 'Missing config/app-config.php';
  exit;
}

Require SSL

As you saw in the configuration file, you can require SSL. This may not be necessary in development, but make sure when you put the page in production that it requires an SSL connection. Most internet savy users know it is not a good idea to ever provide credit card information unless their data is secured by SSL.

// Force HTTPS connections
if (STRIPE_REQUIRE_HTTPS === true && $_SERVER['HTTPS'] != 'on') {
  header("HTTP/1.1 301 Moved Permanently");
  header("Location: https://" . $_SERVER["SERVER_NAME"] . $_SERVER["REQUEST_URI"]);
  exit();
}

For simplicity, we are using Composer for third-party libraries (like the Stripe PHP SDK). This line runs the Composer autoloader.

// Composer
require_once 'vendor/autoload.php';

The next block of code should not need to be modified, unless you have different rules on what information must be provided. This example is from a political campaign, and they require that all donors be identified with a name, address and employer. If your campaign does not require those, you can comment out the verification checks for them.

if ($_POST) {
  \Stripe\Stripe::setApiKey(STRIPE_API_KEY);
  $error = '';
  $success = '';
  try {
    $amount = (int) $_POST['amount'];
    if (!isset($_POST['stripeToken'])) {
      throw new Exception("An error occurred processing your donation. Please try again.");
    }
    if ($amount < 1 || $amount > 2600) {
      throw new Exception("Online donations must be greater than $1 and less than or equal to $2,600.");
    }
    if (!$_POST['name'] || !$_POST['address'] || !$_POST['employer']) {
      throw new Exception("We must collect your name, address and employer to comply with the law.");
    }
    \Stripe\Charge::create(
      array(
        "amount" => $amount * 100,
        "description" => sprintf('%s - $%s on %s', $_POST['name'], number_format($_POST['amount'],2), date('m/d/Y')),
        "currency" => "usd",
        "card" => $_POST['stripeToken'],
        "metadata" => array(
          "name" => $_POST['name'],
          "address" => $_POST['address'],
          "city" => $_POST['city'],
          "state" => $_POST['state'],
          "zip" => $_POST['zip'],
          "employer" => $_POST['employer'],
          "occupation" => $_POST['occupation']
        ),
        "receipt_email" => $_POST['email']
      )
    );
    $success = true;
  }
  catch (Exception $e) {
    $error = $e->getMessage();
  }
}
?>

The next few lines set up your page and include the necessary JavaScript API code as well as your page <title> and meta information. Customize those to your needs. As this example is built on Bootstrap, a few lines are needed to make sure it works properly with mobile browser.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Support Our Campaign</title>
<meta name="description" content="Securely contribute to our campaign." />
<script type="text/javascript" src="//js.stripe.com/v2/"></script>
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>

The next block of code is what actually interacts with the Stripe API. The initial credit card processing is handled asynchronously, meaning that it sends and receives the data it needs without refreshing the page. The important parts of this read the relevant credit card data fields and sets the amount to be processed.

<script type="text/javascript">
$(function() {
  $('form.require-validation').bind('submit', function(e) {
    var $form         = $(e.target).closest('form'),
        inputSelector = ['input[type=email]', 'input[type=password]',
                         'input[type=text]', 'input[type=file]',
                         'textarea'].join(', '),
        $inputs       = $form.find('.required').find(inputSelector),
        $errorMessage = $form.find('div.error'),
        valid         = true;

    $errorMessage.addClass('hide');
    $('.has-error').removeClass('has-error');
    $inputs.each(function(i, el) {
      var $input = $(el);
      if ($input.val() === '') {
        $input.parent().addClass('has-error');
        $errorMessage.removeClass('hide');
        e.preventDefault(); // cancel on first error
      }
    });
  });
});

$(function() {

  // Ensure default is set
  $('input#auto_1').prop('checked', true);

  $('input[name="auto_select"]').change(function() {
    var amount;
    amount = $(this).val();
    if (amount == 'other') {
      showOtherAmount();
    } else {
      $('#amount').val(parseInt($(this).val(), 10));
    }
  });

  function showOtherAmount() {
    $('#auto_select').addClass('collapse');
    $('#other_amount').removeClass('collapse');
  }

  var $form = $("#payment-form");

  $form.on('submit', function(e) {
    if (!$form.data('cc-on-file')) {
      e.preventDefault();
      Stripe.setPublishableKey($form.data('stripe-publishable-key'));
      Stripe.createToken({
        name: $('.card-name').val(),
        number: $('.card-number').val(),
        cvc: $('.card-cvc').val(),
        exp_month: $('.card-expiry-month').val(),
        exp_year: $('.card-expiry-year').val()
      }, stripeResponseHandler);
    }
  });

  function stripeResponseHandler(status, response) {
    if (response.error) {
      $('.error')
        .removeClass('hide')
        .find('.alert')
        .text(response.error.message);
    } else {
      // token contains id, last4, and card type
      var token = response['id'];
      // insert the token into the form so it gets submitted to the server
      $form.find('input[type=text]').empty();
      $form.append("<input type='hidden' name='stripeToken' value='" + token + "'/>");
      $form.get(0).submit();
    }
  }
});
</script>

This block sets up the Google Analytics transaction. It records a single page view, as well as sends an event if the transaction was successful. Customize your code here with your Google Analytics tracking code (e.g. UA-123456-78).

<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');

ga('create', 'UA-000000-00', 'auto');
ga('send', 'pageview');
<?php if ($success): ?>
ga('send', 'event', 'donate', 'success', '<?php echo htmlentities(addslashes($_POST['email'])); ?>', <?php echo $amount; ?>);
<?php endif; ?>
</script>

The remaining parts of the <head> section load in the Bootstrap stylesheet as well as an override that can be found in assets/css/donation-form.css. This is particularly useful for changing the logo to a more appropriate one.

<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" />

<link rel="stylesheet" href="assets/css/donation-form.css" />

</head>

The remaining contents of the form can be customized, but will require a bit of tweaking in the JavaScript above if you want additional meta data to be submitted or omitted. Be particularly careful about the default amount, as that is pre-selected as the value of input#auto_1.

<body>

<div class="container">
    <div class='row'>
        <div class='col-lg-offset-3 col-lg-6'>
          <h1 class="logo">
            <span class="sr-only">Campaign Logo</span>
          </h1>
          <?php if (!$success && !$error): ?>
          <form class="require-validation" data-cc-on-file="false" data-stripe-publishable-key="<?php echo STRIPE_PUBLISHABLE_KEY; ?>" id="payment-form" method="post">
            <div class="row">
              <div class='col-xs-12 form-group required'>
                <label class="control-label">Full Name</label>
                <input class='form-control card-name' size="4" name="name" type="text" required>
              </div>
            </div>
            <div class="row">
              <div class="col-xs-12 form-group required">
                <label class="control-label">Addresss</label>
                <input class='form-control' size="4" name="address" type="text" required>
              </div>
            </div>
            <div class="row">
              <div class="col-xs-4 form-group required">
                <label class="control-label">City</label>
                <input class='form-control' size="4" name="city" type="text" required>
              </div>
              <div class="col-xs-4 form-group required">
                <label class="control-label">State</label>
                <input class='form-control' size="4" name="state" type="text" required>
              </div>
              <div class="col-xs-4 form-group required">
                <label class="control-label">Zip Code</label>
                <input class='form-control' size="4" name="zip" type="text" required>
              </div>
            </div>
            <div class="row">
              <div class="col-xs-12">
                <hr />
              </div>
            </div>
            <div class="row">
              <div class='col-xs-12 form-group card required'>
                <label class="control-label">Card Number</label>
                <input autocomplete="off" class="form-control card-number" size="20" type="text" required>
              </div>
            </div>
            <div class="row">
              <div class='col-xs-4 form-group cvc required'>
                <label class="control-label">CVC</label>
                <input autocomplete="off" class="form-control card-cvc" placeholder="ex. 311" size="4" type="text" required>
              </div>
              <div class='col-xs-4 form-group expiration required'>
                <label class="control-label">Expiration</label>
                <input class='form-control card-expiry-month' placeholder='MM' size="2" type="text" required>
              </div>
              <div class='col-xs-4 form-group expiration required'>
                <label class="control-label">&nbsp;</label>
                <input class='form-control card-expiry-year' placeholder='YYYY' size="4" type="text" required>
              </div>
            </div>
            <div class="row">
              <div class='col-xs-12'>
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <span class="panel-title">Choose an Amount</span>
                    </div>
                    <div class="panel-body">
                      <div id="auto_select">
                          <label class="radio-inline">
                            <input type="radio" name="auto_select" id="auto_1" value="25" checked="checked"> $25
                          </label>
                          <label class="radio-inline">
                            <input type="radio" name="auto_select" id="auto_2" value="50"> $50
                          </label>
                          <label class="radio-inline">
                            <input type="radio" name="auto_select" id="auto_3" value="100"> $100
                          </label>
                          <!-- <br /> -->
                          <label class="radio-inline">
                            <input type="radio" name="auto_select" id="auto_4" value="250"> $250
                          </label>
                          <label class="radio-inline">
                            <input type="radio" name="auto_select" id="other" value="other"> Other
                          </label>
                      </div>
                      <div id="other_amount" class="collapse">
                          <div class="input-group input-group-lg">
                            <span class="input-group-addon">$</span>
                            <input type="text" class="form-control" style="text-align:right" name="amount" id="amount" value="25">
                            <span class="input-group-addon">.00</span>
                          </div>
                      </div>
                    </div>
                </div>
              </div>
            </div>
            <div class="row">
              <div class="col-xs-6 form-group required">
                <label class="control-label">Employer</label>
                <input class='form-control' size="4" name="employer" type="text" required>
              </div>
              <div class='col-xs-6 form-group'>
                <label class="control-label">Occupation</label>
                <input class='form-control' size="4" name="occupation" type="text">
              </div>
            </div>
            <div class="row">
              <div class="col-xs-12 form-group required">
                <label class="control-label">Email (for receipt)</label>
                <input class='form-control' size="4" name="email" type="text">
              </div>
            </div>
            <div class="row">
              <div class="col-xs-12 form-group">
                <button class="btn btn-primary btn-lg submit-button" style="width: 100%;" type="submit">Donate</button>
              </div>
            </div>
            <div class="row">
              <div class="col-xs-12 error form-group hide">
                <div class="alert-danger alert">
                  Please correct the errors and try again.
                </div>
              </div>
            </div>
          </form>
          <?php elseif ($success): ?>
          <div class="alert alert-success">
            <h2>Thank you for your support!</h2>
            <p>We're processing your contribution to our campaign. Thank you for your support!</p>
          </div>
          <?php else: ?>
          <div class="alert alert-danger">
            <h2>We could not process your contribution.</h2>
            <p><?php echo $error; ?></p>
            <p><a href="/donate" class="btn btn-default">Try Again</a></p>
          </div>
          <?php endif; ?>
          <div class="footer">
            <p class="small">Contributions are not tax deductible. Contributions may only be received from U.S. residents. The person making this contribution agrees that they are at least 18 years of age and that this contribution is made from their own funds and not those of another.</p>
            <p class="footer-credit">
              Paid for by Friends of Our Campaign<br />
              Jane Doe, Treasurer.
            </p>
          </div>
        </div>
    </div>
</div>

Last step: Set Stripe to “Live” mode

It would not be worth all this effort if your account was not able to actually take donations! Set the account to “Live” in the Stripe user interface. Note that this will require you to fill out some additional information (including a bank account and tax documents). You then will want to swap out the credentials used in the config.php file for the set under “Production” keys in Account Settings.

That’s it!

Run a few low-dollar test transactions through to make sure that Stripe receives the live payment and that the Transfers are set up properly. After that, you will likely want to link to your new donations form conspicuously throughout the site, as well as in your email marketing. You can configure additional notifications for when a donation is processed. Because you set up Google Analytics goal tracking, you will also be able to see where visitors come from to make them ultimately make a donation.

Stripe Live Mode