This post will cover alternate payment methods in Braintree. The example in this post will focus specifically with PayPal, though other payment methods follow similar processes. In this case, at least PayPal, ApplePay, and Google Pay have the following in common:
- A client token for Braintree that will need to be generated by your server
- The token will need to be passed into the JavaScript code for the Braintree Client
- A successful authorization through the payment method interface generates a payment nonce from Braintree
- That payment nonce is passed into the transaction sale and Braintree handles the rest
The examples provided here will be in two parts, the client interface page and the transaction page. The client interface page has the server generate a client token and is passed into a JavaScript function that generates the PayPal checkout button which brings up the PayPal Checkout Page when clicked on. Also included is a form that contains the payment nonce generated when the PayPal Checkout is authorized. The transaction page then takes the payment nonce and finalizes the transaction.
Basic documentation for the client interface is at https://developers.braintreepayments.com/guides/paypal/checkout-with-paypal/javascript/v3.
You'll need to have a PayPal Merchant ID Account and have that account linked to your Braintree Account which can be done via your Braintree Control Panel. The same would need to be done for your sandbox environments for both PayPal and Braintree as well as get a PayPal sandbox test buyer. After that is setup, you'll be able to set-up your test pages.
To start, the following cfobjects will need to be declared:
<cfobject type="java" class="com.braintreegateway.Environment" name="environment" />
<cfobject type="java" class="com.braintreegateway.BraintreeGateway" name="gateway" />
The Briantree Gateway will need to be initialized.
<cfset j = gateway.init(Environment.SANDBOX, '#variables.merchantID#', '#variables.publicKey#', '#variables.privateKey#')>
Remember that when going live to replace 'Environment.SANDBOX' with 'Environment.PRODUCTION' and use the correct merchant ID and keys with those from your Braintree Production Control Panel.
The next step is to generate the client token.
<cfset variables.clientToken = gateway.clientToken().generate() />
Include the Braintree libraries provided in the above documentation.
<!-- Load PayPal's checkout.js Library. -->
<script src="https://www.paypalobjects.com/api/checkout.js" data-version-4 log-level="warn"></script>
<!-- Load the client component. -->
<script src="https://js.braintreegateway.com/web/3.44.2/js/client.min.js"></script>
<!-- Load the PayPal Checkout component. -->
<script src="https://js.braintreegateway.com/web/3.44.2/js/paypal-checkout.min.js"></script>
Here's the code for the PayPal button div tag and the form with the payment nonce and transaction ID that would be submitted to the transaction page.
<div id="paypal-button"></div>
<form id="ppFrm" method="post" action="ppBTFinal.cfm">
<input type="hidden" id="ppNonce" name="ppNonce" />
<input type="hidden" id="ppTr" name="ppTr" />
</form>
The following JavaScript code handles the Braintree client for PayPal and is modified to receive the client token generated by the server.
<script>
// Create a client.
braintree.client.create({
authorization: '<cfoutput>#variables.clientToken#</cfoutput>'
}, function (clientErr, clientInstance) {
// Stop if there was a problem creating the client.
// This could happen if there is a network error or if the authorization
// is invalid.
if (clientErr) {
console.error('Error creating client:', clientErr);
return;
}
// Create a PayPal Checkout component.
braintree.paypalCheckout.create({
client: clientInstance
}, function (paypalCheckoutErr, paypalCheckoutInstance) {
// Stop if there was a problem creating PayPal Checkout.
// This could happen if there was a network error or if it's incorrectly
// configured.
if (paypalCheckoutErr) {
console.error('Error creating PayPal Checkout:', paypalCheckoutErr);
return;
}
// Set up PayPal with the checkout.js library
paypal.Button.render({
env: 'sandbox', //'production' when live
payment: function () {
return paypalCheckoutInstance.createPayment({
flow: 'checkout',
amount: '1.99',
currency: 'USD',
intent: 'authorize' // this value must either be `capture` or match the intent passed into the PayPal SDK intent query parameter
});
},
onAuthorize: function (data, actions) {
return paypalCheckoutInstance.tokenizePayment(data, function (err, payload) {
// Submit `payload.nonce` to your server.
document.getElementById("ppNonce").value=payload.nonce;
document.getElementById("ppTr").value=data.orderID;
document.getElementById("ppFrm").submit();
});
},
onCancel: function (data) {
console.log('checkout.js payment cancelled', JSON.stringify(data, 0, 2));
},
onError: function (err) {
console.error('checkout.js error', err);
}
}, '#paypal-button').then(function () {
// The PayPal button will be rendered in an html element with the id
// `paypal-button`. This function will be called when the PayPal button
// is set up and ready to be used.
});
});
});
</script>
Please note that "intent: 'authorize'" can be used as long as you submit the PayPal transaction for settlement along with the payment nonce.
Here is the code for the client interface in it's entirety (ppTest.cfm):
<!--- DECLARE JAVA OBJECTS --->
<cfobject type="java" class="com.braintreegateway.Environment" name="environment" />
<cfobject type="java" class="com.braintreegateway.BraintreeGateway" name="gateway" />
<cfset variables.merchantID = "1z2y3x4w5v" />
<cfset variables.publicKey = "9a8b7c6d5e" />
<cfset variables.privateKey = "1q2w3e4r5t" />
<!--- SANDBOX INITIALIZATION FOR TEST TRANSACTIONS --->
<cfset j = gateway.init(Environment.SANDBOX, '#variables.merchantID#', '#variables.publicKey#', '#variables.privateKey#')>
<cfset variables.clientToken = gateway.clientToken().generate() />
<!-- Load PayPal's checkout.js Library. -->
<script src="https://www.paypalobjects.com/api/checkout.js" data-version-4 log-level="warn"></script>
<!-- Load the client component. -->
<script src="https://js.braintreegateway.com/web/3.44.2/js/client.min.js"></script>
<!-- Load the PayPal Checkout component. -->
<script src="https://js.braintreegateway.com/web/3.44.2/js/paypal-checkout.min.js"></script>
<div id="paypal-button"></div>
<form id="ppFrm" method="post" action="ppBTFinal.cfm">
<input type="hidden" id="ppNonce" name="ppNonce" />
<input type="hidden" id="ppTr" name="ppTr" />
</form>
<script>
// Create a client.
braintree.client.create({
authorization: '<cfoutput>#variables.clientToken#</cfoutput>'
}, function (clientErr, clientInstance) {
// Stop if there was a problem creating the client.
// This could happen if there is a network error or if the authorization
// is invalid.
if (clientErr) {
console.error('Error creating client:', clientErr);
return;
}
// Create a PayPal Checkout component.
braintree.paypalCheckout.create({
client: clientInstance
}, function (paypalCheckoutErr, paypalCheckoutInstance) {
// Stop if there was a problem creating PayPal Checkout.
// This could happen if there was a network error or if it's incorrectly
// configured.
if (paypalCheckoutErr) {
console.error('Error creating PayPal Checkout:', paypalCheckoutErr);
return;
}
// Set up PayPal with the checkout.js library
paypal.Button.render({
env: 'sandbox', //'production' when live
payment: function () {
return paypalCheckoutInstance.createPayment({
flow: 'checkout',
amount: '1.99',
currency: 'USD',
intent: 'authorize' // this value must either be `capture` or match the intent passed into the PayPal SDK intent query parameter
});
},
onAuthorize: function (data, actions) {
return paypalCheckoutInstance.tokenizePayment(data, function (err, payload) {
// Submit `payload.nonce` to your server.
document.getElementById("ppNonce").value=payload.nonce;
document.getElementById("ppTr").value=data.orderID;
document.getElementById("ppFrm").submit();
});
},
onCancel: function (data) {
console.log('checkout.js payment cancelled', JSON.stringify(data, 0, 2));
},
onError: function (err) {
console.error('checkout.js error', err);
}
}, '#paypal-button').then(function () {
// The PayPal button will be rendered in an html element with the id
// `paypal-button`. This function will be called when the PayPal button
// is set up and ready to be used.
});
});
});
</script>
Basic documentation for the transaction page is at https://developers.braintreepayments.com/guides/paypal/server-side/java.
The following cfobjects would need to be declared for the transaction page followed by the Environment initialization:
<cfobject type="java" class="com.braintreegateway.Environment" name="environment" />
<cfobject type="java" class="com.braintreegateway.TransactionRequest" name="transactionRequest" />
<cfobject type="java" class="com.braintreegateway.BraintreeGateway" name="gateway" />
<cfset j = gateway.init(Environment.SANDBOX, '#variables.merchantID#', '#variables.publicKey#', '#variables.privateKey#')>
Similar to the credit card transactions, the function process a transaction for PayPal is similar, but all is needed is the payment nonce. The PayPal Transaction/Order ID isn't required but could be useful in storing for reference.
Here's the transaction request followed by the sale provided that the nonce and other fields necessary for the transaction exist:
<cfif structKeyExists(form, 'ppNonce') And structKeyExists(form, 'ppTr') And structKeyExists(Session, 'amt')>
<cfset variables.request = transactionRequest.amount(JavaCast('bigdecimal','#Session.amt#')).paymentMethodNonce("#form.ppNonce#").orderID('#form.ppTr#').options().submitForSettlement(true).storeInVaultOnSuccess(false).done()>
<cfset variables.zed = gateway.transaction().sale(variables.request)>
</cfif>
You'll notice that the amount is passed into the function as a Session variable. This would be better so that the user doesn't try to sneak a different amount into the form submission. You won't have to be concerned about the user changing the amount in the javascript as Braintree encodes the amount into the checkout button that it generates.
For 'options()', 'submitForSettlement(true)' will settle the payment at the same time as the sale is processed. ' storeInVaultOnSuccess(false)' keeps that transaction out of the vault since a customer account wasn't created. I'll cover creating a customer account in a future post.
The following code will check the transaction for success and return the Braintree transaction ID. This is useful to store in your database for referencing.
<cfif variables.zed.isSuccess()>
<cfset variables.TransID = variables.zed.getTarget().getId() />
<cfoutput>Transaction ID: #variables.TransID#</cfoutput>
</cfif>
Here's the transaction in it's entirety with the amount stored as Session.amt (ppBTFinal.cfm):
<!--- DECLARE JAVA OBJECTS --->
<cfobject type="java" class="com.braintreegateway.Environment" name="environment" />
<cfobject type="java" class="com.braintreegateway.TransactionRequest" name="transactionRequest" />
<cfobject type="java" class="com.braintreegateway.BraintreeGateway" name="gateway" />
<cfset variables.merchantID = "1z2y3x4w5v" />
<cfset variables.publicKey = "9a8b7c6d5e" />
<cfset variables.privateKey = "1q2w3e4r5t" />
<!--- SANDBOX INITIALIZATION FOR TEST TRANSACTIONS --->
<cfset j = gateway.init(Environment.SANDBOX, '#variables.merchantID#', '#variables.publicKey#', '#variables.privateKey#')>
<cfif structKeyExists(form, 'ppNonce') And structKeyExists(form, 'ppTr') And structKeyExists(Session, 'amt')>
<!--- ADD TRANSACTION --->
<cfset variables.request = transactionRequest.amount(JavaCast('bigdecimal','#Session.amt#')).paymentMethodNonce("#form.ppNonce#").orderID('#form.ppTr#').options().submitForSettlement(true).storeInVaultOnSuccess(false).done()>
<cfset variables.zed = gateway.transaction().sale(variables.request)>
<cfif variables.zed.isSuccess()>
<cfset variables.TransID = variables.zed.getTarget().getId() />
<cfoutput>Transaction ID: #variables.TransID#</cfoutput>
</cfif>
</cfif>
In a future post, I'll go over creating a Customer Account in Braintree using Coldfusion and setting up a recurring payment plan via Braintree Subscription.