The Payment Form

The payment form is the heart of Charge. There are a few required fields, and some optional ones too.


The payment form requires two hidden inputs - action and redirect.

The action to perform. For charge, this must have a value of 'charge/charge'. This tells Craft to direct the post to Charge
The redirect url after a successful purchase. ie. /payment/thanks. On that url, the successful charge object will be available. You can also add any variable from the charge object to the path and it will be parsed out.

<form id="charge-form" method="post" action="" accept-charset="UTF-8">
    <input type="hidden" name="action" value="charge/charge">
    <input type="hidden" name="redirect" value="payment/thanks/{hash}">
    <button type="submit"> Pay </button>


The basic form contains the following fields :

Customer Fields
customerName *required
The name for the customer
customerEmail *required
The email for the customer.
Plan Fields
planAmount *required
The amount for the payment, in whole units. ie. 5 == $5 (assuming the currency is usd)
Optional. The currency for the payment. If not set, the default currency as set in the settings will be used. The possible currencies can also be affected by your Stripe account settings.
The recurring interval. Value values are '' (blank), week, month or year. Only referenced if 'planIntervalCount' is >= 1
The interval count to recur payments on. 0 implies a one-off charge. 1 implies a recurring payment every planInterval - ie. every week/month/year. Set to different values to recur with different counts. ie. every 3 months.
planCoupon New in 1.3
An optional field to let your customers enter a coupon code. If supplied this code will be validated against all coupons on record. More about coupons.
Meta Fields

Note: Meta fields are passed over to stripe during processing and attached to the customer. You can use them to add any extra information you might need later to reconcile.

Meta data fields are very useful if you need to capture additional information from a user about a purchase or subscription. These meta fields are free form and can be named whatever you like, simply pass them as an array in the form. They're later available as a returned array on the charge elements.

The meta field array. Naming is freeform and can be any value you prefer.

Usage is simple, like :

<input type="text" name="meta[firstMetaField]"
    value="{% if charge is defined %}{{ charge.meta.firstMetaField }}{% endif %}"/>

<input type="text" name="meta[otherField]"
    value="{% if charge is defined %}{{ charge.meta.otherField }}{% endif %}"/>

<input type="text" name="meta[anythingElse]"
    value="{% if charge is defined %}{{ charge.meta.anythingElse }}{% endif %}"/>
Card Fields

Note: For these fields, we don't ever want to pass them to the server, so don't name the inputs. Instead, use a data attribute so Stripe.js can pick them up.

These fields need to be formatted slightly differently than normal fields. Like so :

<input type="text" data-stripe="number"/>

So - where you'd normally call the input 'name="cardNumber"', give it an attribute of 'data-stripe="number"' instead. The same for applies for all of the card* inputs

cardNumber *required passed as data-stripe="number"
The card number for the payment.
cardCvc *required passed as data-stripe="cvc"
The CVC for the card
cardExpMonth *required passed as data-stripe="exp_month"
The expiry month for the card
cardExpYear *required passed as data-stripe="exp_year"
The expiry year for the card
cardName passed as data-stripe="name"
cardAddressLine1 passed as data-stripe="address_line1"
cardAddressLine2 passed as data-stripe="address_line1"
cardAddressCity passed as data-stripe="address_city"
cardAddressState passed as data-stripe="address_state"
cardAddressZip passed as data-stripe="address_zip"
cardAddressCountry passed as data-stripe="address_country"
Optional extra card name and address fields. Can be useful to reduce fraud detections
Month & Year Helpers

For fields like cardExpMonth and cardExpYear you'll want to display a select input or years/months. Twig makes this easy :

<select data-stripe="exp_month">
    {% for month in craft.i18n.getLocaleData().getMonthNames() %}
        <option value="{{ loop.index }}" {% if now.month == loop.index %}selected="selected"{% endif %}>{{ month }}</option>
    {% endfor %}

and for years :

<select data-stripe="exp_year">
    {% for year in %}
        <option value="{{ year }}">{{ year }}</option>
    {% endfor %}

Form Re-population & Error handling

On submit validation is performed, and if an error is found, the page is reloaded with the charge object, with any errors attached. You can repopulate the form, and show any errors.


Standard fields can be directly repopulated like this :

<input type="text" name="customerName" value="{% if charge is defined %}{{ charge.customerName }}{% endif %}">

Card fields are handled differently. Charge never sees the direct card details, but instead gets a cardToken, cardLast4 and cardType from Stripe. Re-population is done like this :

{% if charge is defined and charge.cardToken is not null %}
    <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 }}"/>

    Payment card : {{ charge.formatCard() }}
{% else %}
    // Show the standard card fields
{% endif %}

Note: Use {{ charge.formatCard() }} to show the formatted version of the card number, similar to : ··· ···· ···· 4242


If validation fails, the page will return with the charge object with the errors attached.

There are 'general' errors, along with field specific errors. You can define a twig macro to output the errors, like so :

{% macro errorList(errors) %}
    {% if errors %}
        {% for error in errors %}
            {{ error }}
        {% endfor %}
    {% endif %}
{% endmacro %}

Then display the errors like so :

{% if charge is defined %}
    {{ _self.errorList(charge.getErrors('general')) }}
{% endif %}

And also, per field errors like :

{% if charge is defined %}
    {{ _self.errorList(charge.getErrors('customerName')) }}
{% endif %}

Where 'customerName' can be any of the fields above.

Note: Be sure to include the general error output in your form. If the payment fails on Stripe's side for any reason, that's where the error will be displayed

Full Example

View Complete Example Template →