Custom Data in Braintree Hosted Fields

March 3rd 2017 Braintree ASP.NET Core

Braintree's Hosted Fields offering is a great compromise between payment form customization and PCI SAQ A compliance requirements. Although it is not immediately evident from the documentation, the payment form can also easily be extended with custom data fields that your business might require. This post demonstrates how to do it in an ASP.NET Core application.

Generate a Client Token for the Payment Form

To set up the server side, we can mostly follow the official guide for setting up a .NET server. These are the steps to follow:

  1. Install the Braintree NuGet package which is fully .NET Core compatible.
  2. Add a new BraintreeController to the application.
  3. Create a helper method to instantiate a new BraintreeGateway instance (to get your own sandbox merchant id and keys, you will need to sign up):

     private BraintreeGateway GetGateway()
     {
         return new BraintreeGateway
         {
             Environment = Environment.SANDBOX,
             MerchantId = "<your merchant id>",
             PublicKey = "<your public key>",
             PrivateKey = "<your private key>"
         };
     }
    
  4. In the action method, generate the client token and pass it to the view:

     public ActionResult Index()
     {
         var gateway = GetGateway();
    
         var clientToken = gateway.ClientToken.generate();
    
         return View((object)clientToken);
     }
    

Implement the Payment Form

For the payment form we will switch to the official guide for setting up a JavaScript client. Since the ASP.NET Core project template is Bootstrap based, we will style the official example accordingly:

<form asp-controller="Braintree" asp-action="Process" method="post"
      class="panel-body" id="credit-card-info">
    <div class="row">
        <div class="form-group">
            <label class="control-label">Card Number</label>
            <div class="form-control" id="card-number"></div>
        </div>
    </div>
    <div class="row">
        <div class="form-group">
            <label class="control-label">CVV</label>
            <div class="form-control" id="cvv"></div>
        </div>
    </div>
    <div class="row">
        <div class="form-group">
            <label class="control-label">Expiration Date</label>
            <div class="form-control" id="expiration-date"></div>
        </div>
    </div>

    <input type="hidden" name="nonce" id="nonce" />
    <button value="submit" id="submit-button"
            class="btn btn-success btn-lg center-block">Pay</button>
</form>

You can notice that instead of all input elements there are div placeholders, which the Braintree SDK will replace with its own iframe hosted input fields.

For our custom data, we'll add an input element directly to the form (just below the expiration date):

<div class="row">
    <div class="form-group">
        <label class="control-label">Email</label>
        <input type="email" class="form-control" name="email" id="email"
               placeholder="foo@bar.com">
    </div>
</div>

To include the SDK, we will reference two Braintree hosted scripts:

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

The following JavaScript script will take care of the three steps involved in payment processing:

  • initializing the hosted fields,
  • sending the payment details to Braintree to get the corresponding nonce value,
  • posting the nonce together with our custom form field to our server.
var form = document.querySelector('#credit-card-info');
var nonce = document.querySelector('#nonce');
var submitButton = document.querySelector('#submit-button');

braintree.client.create(
    { authorization: '@Model' },
    function (clientError, clientInstance) {
        if (clientError) {
            console.error(clientError);
            return;
        }

        braintree.hostedFields.create({
            client: clientInstance,
            styles: {
                'input': {
                    'font-size': '14px'
                },
                '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(hostedFieldsError, hostedFieldsInstance) {
            if (hostedFieldsError) {
                console.error(hostedFieldsError);
                return;
            }

            submitButton.removeAttribute('disabled');

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

                hostedFieldsInstance.tokenize(function (tokenizeErr, payload) {
                    if (tokenizeErr) {
                        console.error(tokenizeErr);
                        return;
                    }

                    nonce.value = payload.nonce;
                    form.submit();
                });
            }, false);
        });
    });

We assign @Model to authorization property in order to pass the generated client token to the SDK.

To perform all the processing with a single click of the submit button, we intercept the submit event with a listener to first send the payment data to Braintree (by calling hostedFieldsInstance.tokenize), and only perform the actual submit after we receive the nonce value and assign it to a hidden field in the form. Since this is a regular form post, it will automatically post all form fields to our server: the hidden nonce and our custom input element. Braintree fields will be ignored because they are inside their own iframes and not really a part of the form.

Process the Payment on the Server

Form data is posted to the Process action method of our BraintreeController:

public ActionResult Process(string nonce, string email)
{
    var gateway = GetGateway();

    var request = new TransactionRequest
    {
        Amount = 49.99M,
        PaymentMethodNonce = nonce,
        Options = new TransactionOptionsRequest
        {
            SubmitForSettlement = true
        }
    };

    var result = gateway.Transaction.Sale(request);

    // act on the received payment before showing the confirmation page

    return View((object)email);
}

The method parameters match the fields in our form. We validate the nonce value with Braintree as described in the documentation. We can use the other fields according to our business needs (i.e. to send a confirmation email to the customer). As the last step we pass the email to the view where we can show it as a part of the confirmation page:

<div class="row">
    <div class="col-md-12">
        <h2>Payment Successful</h2>
        <p>Confirmation email sent to @Model.</p>
    </div>
</div>
Copyright
Creative Commons License