Now that you've got your API keys setup in Charge, and we can talk to Stripe, the next step is to setup a basic payment form.
To start with we're just going to start with the most basic payment form.
TLDR; The full basic payment form is included in the demo templates as part of the download. See it in action here
Just to make sure it's all working, we'll setup a form with a fixed, one-time payment amount, with the minimum of card inputs. Once you've got that working you can extend and expand.
Our payment page needs to have a few things. Specifically :
charge-form
, a method of post
, and an action
input with a value of charge/charge
. Optionally you can include the stripe public key on the form via a data-stripeKey
attribute.data-stripe
attributesOur form needs to have a few parts. It should look something like this:
<form id="charge-form" method="post" data-publicKey="{{ craft.charge.publicKey }}">
<input type="hidden" name="action" value="charge/charge"/>
..
</form>
Charge uses an encrypted hidden input that's included as part of the form post to pass all the details about the payment request to our controller. It's very simple to setup in our form:
<form id="charge-form" method="post" data-publicKey="{{ craft.charge.publicKey }}">
<input type="hidden" name="action" value="charge/charge"/>
{% set options = {
'planAmount' : 99.99
} %}
{{ craft.charge.setPaymentOptions(options) }}
..
</form>
Here we're just setting the planAmount
to be 99.99
(in our default currency). This will mean Charge will create a one-time payment of $99.99
when the payment request is submitted.
The {{ craft.charge.setPaymentOptions(options) }}
simply encrypts this simple bit of information, and includes it as a hidden input on the form. Regardless of how complicated your payment setups might get later, this basic concept stays the same - set the payment details in an array, and use setPaymentOptions
to encrypt and include the details on the payment form.
For our most basic of payment forms, we just want to include a customerEmail
input. This is key, and is the minimum Stripe requires.
If your user is logged in, you might want to pre-populate this for them too. We'll add this to the form like this:
<form id="charge-form" method="post" data-publicKey="{{ craft.charge.publicKey }}">
<input type="hidden" name="action" value="charge/charge"/>
{% set options = {
'planAmount' : 99.99
} %}
{{ craft.charge.setPaymentOptions(options) }}
<label for="customerEmail">Receipt Email</label>
<input type="text" name="customerEmail" value="{{ currentUser ? currentUser.email }}" id="customerEmail"/>
..
</form>
What about returning customers? Under the hood Charge will create and manage customer accounts for all your payment requests. If the user is logged in while a payment attempt is made, we'll try to reuse any existing customer accounts for them. For guests we'll always use a new customer account on Stripe.
Stripe uses javascript tokenisation of payment cards to keep things secure. Your server never sees the submitted card details, and the raw card details never leave the payment page, they're tokenised using your public key right on the page before any processing takes place.
To make this happen, we need to add a few extra attributes to the card inputs, and exclude the name attributes on the card inputs.
At minimum, we just need a card number, expiry month and year, and cvc.
Charge includes some additional helpers to enable neat card input formatting, automatically handle the expiry inputs as a single input, validate the card format and more, all without you doing any extra work. Our card inputs just need to look like this:
<form id="charge-form" method="post" data-publicKey="{{ craft.charge.publicKey }}">
<input type="hidden" name="action" value="charge/charge"/>
{% set options = {
'planAmount' : 99.99
} %}
{{ craft.charge.setPaymentOptions(options) }}
<label for="customerEmail">Receipt Email</label>
<input type="text" name="customerEmail" value="{{ currentUser ? currentUser.email }}" id="customerEmail"/>
<label for="cardNumber">Card Number</label>
<input type="text" data-stripe="number" placeholder="•••• •••• •••• ••••" id="cardNumber"/>
<label for="cardExpiry">Card Expiry</label>
<input type="text" data-stripe="exp" placeholder="mm / yy" id="cardExpiry"/>
<label for="cardCvc">Card CVC</label>
<input type="text" data-stripe="cvc" placeholder="123" id="cardCvc"/>
..
</form>
Exclude name, include data-stripe It's critical that you include the data-stripe
attributes on the card inputs, and exclude the name
attribute. The tokenisation js keys against the data attributes, and by excluding the name inputs, we can be sure the actual values will never submit to the server, regardless of any browser issues.
The whole form is reliant on the payment javascript. We bundle a ready to go helper javascript library as part of Charge. Included in that library is a full set of input helpers, along with our specific form handling.
The payment form needs 3 javascript libraries, and one init block:
Including everything together will look something like this:
You've got two options to define your Stripe public key We recommend adding it as a data attribute on the charge form, so your js can be kept really clean. Alternatively you can define it explicitly via Stripe.setPublishableKey('{{ craft.charge.getPublicKey() }}');
on your page's js.
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="{{ resourceUrl('charge/js/stripe_v2.min.js') }}"></script>
<script src="{{ resourceUrl('charge/js/jquery.charge.js') }}"></script>
<script>
(function () {
$(this).charge();
})();
</script>
Need to customise the javascript behaviour? The included jquery.charge.js covers most of the basic requirements, but it can't handle every custom situation. You're free to use your own javascript, or customise jquery.charge.js by creating your own copy. Just be careful not to overwrite the version bundled with charge to make sure you don't loose your changes in a later update.
Finally we have to anticipate the cases where a payment might fail, or the customer enters invalid data on the form.
There's actually 2 separate classes of error that might occur, and we need to expose the information about both. First we have errors that occur during the stripe tokenisation of the payment method. These are added by the javascript before the page submits. For that, we just need to add an empty container with an id of payment-errors
. Like so:
<div id="payment-errors"></div>
In addition, we can have errors relating to the actual payment handling. Customer data errors, validation failures and the like. For that, we need to use twig to show the errors. If the page does return with errors, we'll also have a charge object we can use to repopulate the form values with data.
What about repopulating card data? We don't get the full card data back because, by design, we never received it on the server. Instead we can use the card_token
in it's place. In addition to the card token, we have information about the card we can use to help the customer
When we've added all the error handling, repopulation and card token data, our form will look something like this:
<form id="charge-form" method="post" data-stripeKey="{{ craft.charge.publicKey }}">
<input type="hidden" name="action" value="charge/charge"/>
{% set options = {
'planAmount' : 99.99
} %}
{{ craft.charge.setPaymentOptions(options) }}
<div id="payment-errors"></div>
<label for="customerEmail">Receipt Email</label>
<input type="text" name="customerEmail" value="{{ charge is defined ? charge.customerEmail : currentUser ? currentUser.email }}" id="customerEmail"/>
{% if charge is defined %}{% for error in charge.getErrors('customerEmail') %}{{ error }}{% endfor %}{% endif %}
{% if charge is defined and charge.cardToken %}
{# We already have the card details #}
Pay with : {{ charge.cardType }} - {{ charge.cardLast4 }}
<input type="hidden" name="cardToken" value="{{ charge.cardToken }}" data-stripe="token"/>
<input type="hidden" name="cardLast4" value="{{ charge.cardLast4 }}"/>
<input type="hidden" name="cardType" value="{{ charge.cardType }}"/>
<input type="hidden" name="cardName" value="{{ charge.cardName }}"/>
<input type="hidden" name="cardExpMonth" value="{{ charge.cardExpMonth }}"/>
<input type="hidden" name="cardExpMonth" value="{{ charge.cardExpMonth }}"/>
<input type="hidden" name="cardExpYear" value="{{ charge.cardExpYear }}"/>
{% else %}
<label for="cardNumber">Card Number</label>
<input type="text" data-stripe="number" placeholder="•••• •••• •••• ••••" id="cardNumber"/>
<label for="cardExpiry">Card Expiry</label>
<input type="text" data-stripe="exp" placeholder="mm / yy" id="cardExpiry"/>
<label for="cardCvc">Card CVC</label>
<input type="text" data-stripe="cvc" placeholder="123" id="cardCvc"/>
{% endif %}
<input type="submit" value="Pay with Stripe"/>
</form>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="{{ resourceUrl('charge/js/stripe_v2.min.js') }}"></script>
<script src="{{ resourceUrl('charge/js/jquery.charge.js') }}"></script>
<script>
(function () {
Stripe.setPublishableKey('{{ craft.charge.getPublicKey() }}');
$(this).charge();
})();
</script>
That's all there is to it.
To sum up what that covers -
charge-form
, that performs a post
request, with a hidden action input to charge/charge
. data-stripeKey
.planAmount
of $99.99customerEmail
input, along with error output and repopulationdata-stripe
attributes, along with handling for returning formsYou can see a fully working version of exactly this at demos.squarebit.co.uk/charge/basic, and the fully working template is included in the plugin download, in the examples
folder.
This only covers the most basic setup, but is a good introduction to the core concepts. Once you have the payment form setup and working, most of the behavioural changes are all controlled by changing the paymentOptions
array.
Dive deeper into the options for all that, along with triggering additional success, recurring and failure actions, how to setup recurring payments, multiple plan options that the user can change, extra options for payments including saved cards and more. All the information is detailed in these docs.