4

Where does one start with matching the style of the Stripe payment form to the rest of the site?

Using Stripe for Craft Commerce Plug-In

 {{ plan.getGateway().getPaymentFormHtml({})|raw }}

This enables the form with Card Holder info and credit card details.

2
  • I couldn't find any docs about this at all..from what I gathered so far one has to manually copy the HTML generated by getPaymentFormHtml() to the checkout template, and maybe attach some JS to make it all work. Did you find out anything since you posted this?
    – Udo
    Commented Jul 17, 2019 at 10:07
  • I have not made any progress on this but still interested in this issue.
    – SoHo
    Commented Jul 18, 2019 at 21:36

2 Answers 2

5

The following code allows the customisation of the fields using Stripe Elements. This code will replace the payment form with gateway.getPaymentFormHtml({}).

The thing to note here is this is provided unsupported as it is custom code and does not use the payment form template or javascript file that is in the Commerce Stripe plugin. This also means that it will not benefit from any updates to the plugin that change those files.

{% set cart = craft.commerce.carts.cart %}
{% set gateway = cart.gateway %}
{% set scenario = 'payment' %}
{% set billingAddress = cart.billingAddress ?? null %}

<form method="POST" id="payment-form">
  <input type="hidden" name="action" value="commerce/payments/pay"/>
  {{ redirectInput('/shop/customer/order?number='~cart.number) }}
  <input type="hidden" name="cancelUrl" value="{{ '/shop/checkout/payment'|hash }}"/>
  <input type="hidden" name="orderEmail" value="{{ cart.email }}"/>
  <input type="hidden" name="gatewayId" value="{{ cart.gatewayId }}"/>
  {{ csrfInput() }}

  <div class="stripe-payment-intents-form"
    {% if clientSecret is defined %}data-client-secret="{{clientSecret}}" {% endif %}
    {% if scenario is defined %}data-scenario="{{scenario}}" {% endif %}
  >
    <div>
      <input type="text"
             name='firstName'
             maxlength="70"
             placeholder="{{'First Name'|t('commerce')}}"
             autocomplete="false"
             class="card-holder-first-name{{(paymentForm is defined and paymentForm.getErrors('firstName') ? ' error')}}"
             value="{{paymentForm is defined and paymentForm ? paymentForm.firstName : ''}}"
      >
      <input type="text"
             name="lastName"
             maxlength=70",
             placeholder="{{'Last Name'|t('commerce')}}"
             autocomplete="false"
             class="card-holder-last-name{{ (paymentForm is defined and paymentForm.getErrors('lastName') ? ' error') }}"
             value="{{paymentForm is defined and paymentForm ? paymentForm.lastName : ''}}"
      >
    </div>

    {% set errors = paymentForm is defined and paymentForm ? paymentForm.getErrors() : [] %}
    {% if errors|length %}
      <ul class="errors">
        {% for error in errors %}
          <li>{{ error|join('<br>')|raw }}</li>
        {% endfor %}
      </ul>
    {% endif %}


    <div id="card-number"></div>
    <div id="card-expiry"></div>
    <div id="card-cvc"></div>

    <div id="card-errors" role="alert"></div>
  </div>
  <input type="submit">
</form>

{% do view.registerJsFile('https://js.stripe.com/v3/') %}
{% js %}
// Create a Stripe client.
var stripe = Stripe('{{ gateway.publishableKey ?? '' }}');

  // Create an instance of Elements.
  var elements = stripe.elements();

  // Custom styling can be passed to options when creating an Element.
  // (Note that this demo uses a wider set of styles than the guide below.)
  var style = {
    base: {
      color: '#000000',
      fontSize: '16px',
      '::placeholder': {
        color: '#CCCCCC'
      }
    },
    invalid: {
      color: '#fa755a',
      iconColor: '#fa755a'
    }
  };

  var classes = {
    base: 'StripeElement',
    complete: 'StripeElement--complete',
    empty: 'StripeElement--empty',
    focus: 'StripeElement--focus',
    invalid: 'StripeElement--invalid',
    webkitAutoFill: 'StripeElement--webkit-autofill'
  };

  var card = elements.create('cardNumber', {
    style: style,
    classes: classes
  });
  card.mount('#card-number');

  var expiry = elements.create('cardExpiry', {
    style: style,
    classes: classes
  });
  expiry.mount('#card-expiry');

  var cvc = elements.create('cardCvc', {
    style: style,
    classes: classes
  });
  cvc.mount('#card-cvc');

  // Handle real-time validation errors from the card Element.
  function cardErrors(event) {
    var displayError = document.getElementById('card-errors');
    if (event.error) {
      displayError.textContent = event.error.message;
    } else {
      displayError.textContent = '';
    }
  };

  card.addEventListener('change', cardErrors);
  expiry.addEventListener('change', cardErrors);
  cvc.addEventListener('change', cardErrors);

  // Handle form submission.
  var form = document.getElementById('payment-form');
  form.addEventListener('submit', function(event) {
    event.preventDefault();
    var firstName = document.querySelector('.card-holder-first-name');
    var lastName = document.querySelector('.card-holder-last-name');

    var paymentData = {
      billing_details: {
        name: firstName.value + ' ' + lastName.value,
        email: '{{ cart.email }}',
        address: {
          line1: '{{ billingAddress.address1 ?? '' }}',
          city: '{{ billingAddress.city ?? '' }}',
          state: '{{ billingAddress.state ?? '' }}',
          postal_code: '{{ billingAddress.zipCode ?? '' }}',
          country: '{{ billingAddress.country.iso ?? '' }}'
        }
      }
    };

    stripe.createPaymentMethod('card', card, paymentData).then(function(result) {
      if (result.error) {
      // Inform the user if there was an error.
      var errorElement = document.getElementById('card-errors');
        errorElement.textContent = result.error.message;
      } else {
          var form = document.getElementById('payment-form');
          var hiddenInput = document.createElement('input');
          hiddenInput.setAttribute('type', 'hidden');
          hiddenInput.setAttribute('name', 'paymentMethodId');
          hiddenInput.setAttribute('value', result.paymentMethod.id);
          form.appendChild(hiddenInput);

          // Submit the form
          form.submit();
      }
    });
  });

{% endjs %}
2

OK so here's how to go about it.

  1. On checkout, you'll need at minimum a form with the following elements:
<form method="post" id="card-info">
<input type="hidden" name="action" value="commerce/payments/pay">
<input type="hidden" name="paymentMethodId" value="[fill this via JS]">
<input type="hidden" name="orderEmail" value="[your customer email address]">
<input type="hidden" name="gatewayId" value="[your chosen gateway id - add js to have the user select one if you have multiple gateaways available]">
{{ redirectInput('/order?number='~cart.number) }}
{{ csrfInput() }}
<input name="firstName" type="text" placeholder="First Name">
<input name="lastName" type="text" placeholder="Last Name">
<div id="card-data" class="border border-grey-lighter rounded bg-grey-lighter text-grey-darker p-3"></div>
<button type="submit">Complete Purchase</button>
</form>

The real magic happens, of course, via JS.

First, include the stripe library: <script src="https://js.stripe.com/v3/"></script>

Add this to your main JS file to display the stripe credit card input:

let stripe = Stripe(`your public sharing key`),
    elements = stripe.elements(),
    card = undefined;

let style = {
    base: {
        // Add your base input styles here. For example:
        fontSize: '16px',
        color: '#32325d',
        // Attn: The number of usable styles is very limited. Refer to https://stripe.com/docs/stripe-js/reference#the-elements-object
    }
}
card = elements.create('card', {style: style});
card.addEventListener('change', updateErrorMessage);
card.mount(document.querySelector('card-data'));

function updateErrorMessage (event) {
    if (event.error) {
        // Do something with event.error.message
    }
}

Finally, you'll need to add code to make a request to the stripe API before the form is submitted, and fill in the value for the paymentMethodId hidden input. Refer to lines 66 to 94 here to get an idea how it's done: https://github.com/craftcms/commerce-stripe/blob/develop/src/web/assets/intentsform/js/paymentForm.js#L66

In addition, before you submit, make sure the cart has a valid value for "email". If you do a one page checkout like me, you may have to do an ajax post to /actions/commerce/cart/update-cart in order to save it first.

I implemented all of this in vuejs, so the above is untested simplified code. On request I can share my vuejs version but I thought to better keep it framework agnostic in this answer.

1
  • Curious if anything has changed on this since it was posted? I cannot get this to work. Stripe shows no email address coming to it in the POST and I cannot fathom what the issue could be. Yes, I have an email on the cart and am posting it via orderEmail.
    – Mike
    Commented Nov 26, 2019 at 1:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.