Custom Data in Braintree Hosted Fields
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:
- Install the
Braintree
NuGet package which is fully .NET Core compatible. - Add a new
BraintreeController
to the application. 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>" }; }
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 iframe
s 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>