Perch Shop checkout with Braintree Hosted Fields

Perch Shop ships with built in support for Stripe, Braintree, PayPal Express and Worldpay. If your gateway of preference is Braintree, you can have the Braintree Drop-In UI checkout form up and running in no time following the instructions. While this is a great starting point and easy way to start receiving payments on a website, it also has certain limitations. Especially when working for non english content, Drop-In is just not an option since unlike the Android and iOS interfaces, the WEB one is only available in english.

Before going for a custom implementation you can consider the “Hosted Fields” solution. When using hosted fields, instead of loading a single iframe container for the whole checkout form, you load an iframe for each input field. Everything around the inputs is up to you to implement in the language you need and with absolute styling freedom.

The Braintree implementation consists of the Server side integration and the Client SDK. To switch from Drop-In UI to Hosted Fields we only need to modify the Client side. For my integration I used the v3 Javascript SDK (Hosted Fields are also available in v2 but the code is different).

I am going to assume that you already have a sandbox account with Braintree and set up the necessary information in perch/config/shop.php Also assuming that the default Braintree implementation is already in place. You should then have a checkout.php page with the code from the Perch Docs executing perch_shop_checkout() function when the member is already logged in and the page has received a payment_method_nonce from the client(browser).

<?php
    if (perch_member_logged_in() && perch_post('payment_method_nonce')) {

    // your 'success' return URL
    $return_url = 'http://mysite.com/payment/braintree';

    perch_shop_checkout('braintree', [
        'return_url' => $return_url,
        'token'      => perch_post('payment_method_nonce')
    ]);
    }
?>

and render the form:

    <?php
        perch_shop_payment_form('braintree');
    ?>

Now open /templates/shop/gateways/braintree_payment_form.html and replace the existing contents with these 3 pieces of code:

Create the html form with the basic structure Braintree needs in order to identify the input fields.

<form id="checkout-form" action="" method="post">
    <div id="error-message"></div>

    <label for="card-number">Numero de la tarjeta</label>
    <div class="hosted-field" id="card-number"></div>

    <label for="cvv">CVV</label>
    <div class="hosted-field" id="cvv"></div>

    <label for="expiration-date">Expiration Date</label>
    <div class="hosted-field" id="expiration-date"></div>

    <input type="hidden" name="payment_method_nonce">
    <input type="submit" value="Pay $10" disabled>
</form>

Load the 2 javascript libraries client.min.js and hosted-fields.min.js.

<script src="https://js.braintreegateway.com/web/3.0.2/js/client.min.js"></script>
<script src="https://js.braintreegateway.com/web/3.0.2/js/hosted-fields.min.js"></script>

Include the script which creates the final iframes and sends the payment_method_nonce to the server.

<script>
var submit = document.querySelector('input[type="submit"]');
var form = document.querySelector('#checkout-form');

braintree.client.create({
    authorization: '<perch:shop id="client_token" escape="true" />'
}, function (clientErr, clientInstance) {
    if (clientErr) {
        // Handle error in client creation
        return;
    }
    braintree.hostedFields.create({
        client: clientInstance,
        styles: {
            'input': {
                'font-size': '14pt'
            },
            'input.invalid': {
                'color': 'red'
            },
            'input.valid': {
                'color': 'green'
            }
        },
        fields: {
            number: {
                selector: '#card-number',
                placeholder: '4111 1111 1111 1111'
            },
            cvv: {
                selector: '#cvv',
                placeholder: '123'
            },
            expirationDate: {
                selector: '#expiration-date',
                placeholder: '10 / 2019'
            }
        }
    }, function (hostedFieldsErr, hostedFieldsInstance) {
        if (hostedFieldsErr) {
            // Handle error in Hosted Fields creation
            return;
        }

        submit.removeAttribute('disabled');

        form.addEventListener('submit', function (event) {
            event.preventDefault();

            hostedFieldsInstance.tokenize(function (tokenizeErr, payload) {
                if (tokenizeErr) {
                    switch (tokenizeErr.code) {
                        case 'HOSTED_FIELDS_FIELDS_EMPTY':
                            console.error('All fields are empty! Please fill out the form.');
                            break;
                        case 'HOSTED_FIELDS_FIELDS_INVALID':
                            console.error('Some fields are invalid:', tokenizeErr.details.invalidFieldKeys);
                            break;
                        case 'HOSTED_FIELDS_FAILED_TOKENIZATION':
                            console.error('Tokenization failed server side. Is the card valid?');
                            break;
                        case 'HOSTED_FIELDS_TOKENIZATION_NETWORK_ERROR':
                            console.error('Network error occurred when tokenizing.');
                            break;
                        default:
                            console.error('Something bad happened!', tokenizeErr);
                    }
                }

                // Put `payload.nonce` into the `payment-method-nonce` input, and then
                // submit the form. Alternatively, you could send the nonce to your server
                // with AJAX.
                document.querySelector('input[name="payment_method_nonce"]').value = payload.nonce;
                form.submit();
            });
        }, false);
    });
});
</script>

You should now be able to send payments to Braintree using the new setup. Login to your Dashboard and check.

The above is a very basic implementation. There are quite some things to do to have a production ready checkout form. I think their docs are not particularly well structured so make sure you understand the basic concepts before going deeper.

FIN

If you want to contact me for this post or any other you can use either twitter or email: