
Payment Gateway Braintree 3D Secure Integration Braintree 3Dセキュア統合
For the past two weeks I’ve been developing payment procedures for some company’s international business. Basically this company has branches in 5 different countries spanning across different time zones and using different currencies, of course. They decided to use Braintree from PayPal as their payment platform. And according to the newest legislation 3D Secure feature has to be in place.
Braintree has clear developer documentation that everyone can follow for the payment gateway integration. But to understand the payment workflow and the design logic behind it is always much more important than knowing how to do it.
Hereby I would just showcase some basic concepts, signal examples and sample code for better understanding of payment gateway integration.
Drop-in UI comes first. It is a predefined UI used to collect payee’s information.

Then some sample code to setup Drop-in UI below.
var button = $('#submit-button');
braintree.client.create({
authorization: '{{$client_token}}'
}, function (err, clientInstance) {
// Creation of any other components...
braintree.dataCollector.create({
client: clientInstance,
paypal: true
}, function (err, dataCollectorInstance) {
if (err) {
// Handle error in creation of data collector
return;
}
// At this point, you should access the dataCollectorInstance.deviceData value and provide it
// to your server, e.g. by injecting it into your form as a hidden input.
var deviceData = dataCollectorInstance.deviceData;
$('#device-data').val(deviceData);
});
});
var threeDSecureParameters = {
amount: '{{ $amount }}',
email: '{{ $email }}',
billingAddress: {
...
},
additionalInformation: {
workPhoneNumber: '',
shippingGivenName: '',
shippingSurname: '',
...
},
};
braintree.dropin.create({
authorization: '{{$client_token}}',
container: '#dropin-container',
threeDSecure: true
}, function (createErr, instance) {
if (createErr) {
// Handle any errors that might've occurred when creating Drop-in
console.error(createErr);
return;
}
button.on('click', function (event) {
event.preventDefault();
instance.requestPaymentMethod({
threeDSecure: threeDSecureParameters
},function (err, payload) {
// Submit payload.nonce to your server
if(err)
{
console.error(err)
return;
}
else {
payload['device_data'] = $('#device-data').val();
$.ajax.send('/payment/pay', payload,
function (rsp) {
if (rsp.success && rsp.data['redirect']) window.location.href = rsp.data['redirect_url'];
},
function (rsp) {
if(rsp.error.hasOwnProperty(code) && rsp.error.code == 422) {
$.each(rsp.data.fields, function(index, element) {
})
} else {
showPopupMsg(rsp.error.msg, false);
}
});
}
});
});
});
Note that a client token
will be generated from Server side and inject into Drop-in UI so that when doing the actual payment( sell() action in Braintree), the gateway can then identify and authenticate exactly which braintree account(identified by Merchant ID
in Braintree) and what currency is used(identified by Merchant Accounts
in Braintree).


Of course, you can see there’s some create
Javascript function inside Drop-in UI which will not only request for a customer’s nounce
(instead of using e.g your VISA card number/ACC) and also trigging 3D Secure lookup/challenge procedure. If the creation is successful on Braintree gateway and a successful payload of the create
should be sent to your own server’s backend to create the payment transaction.
braintree.dropin.create({
authorization: '{{$client_token}}',
container: '#dropin-container',
threeDSecure: true
}
Creating a Braintree’s transaction is as simple and easy as below.
$params = [
'amount' => number_format($amount, 2, '.', ''),
'merchantAccountId' => config('braintree.merchantAccounts.' . config('country_code')),
'paymentMethodNonce' => $paymentMethodNonce,
'deviceData' => $deviceData,
'options' => [
'submitForSettlement' => true
],
'customFields' => $customFields
];
try {
$result = Transaction::sale($params);
....
If the sale
transaction is successful, then you should receive a result from payment gateway.
A 3D Secure transaction’s successful result sample is as below. Note that there’s threeDSecureInfo
object with combination of liabilityShifted
and liabilityShiftPossible
status meaning different transaction results.
"threeDSecureInfo": {
"liabilityShifted": true,
"liabilityShiftPossible": true,
{
"success": true,
"message": "Approved",
"transaction": {
"id": "7qmg076f",
"status": "settling",
"type": "sale",
"currencyIsoCode": "NZD",
"amount": "1400.00",
"merchantAccountId": "hok",
"subMerchantAccountId": null,
"masterMerchantAccountId": null,
"orderId": null,
"createdAt": {
"date": "2020-09-06 09:57:33.000000",
"timezone_type": 3,
"timezone": "UTC"
},
"updatedAt": {
"date": "2020-09-06 09:57:34.000000",
"timezone_type": 3,
"timezone": "UTC"
},
"customer": {
"id": null,
"firstName": null,
"lastName": null,
"company": null,
"email": null,
"website": null,
"phone": null,
"fax": null
},
"billing": {
"id": null,
"firstName": null,
"lastName": null,
"company": null,
"streetAddress": null,
"extendedAddress": null,
"locality": null,
"region": null,
"postalCode": null,
"countryName": null,
"countryCodeAlpha2": null,
"countryCodeAlpha3": null,
"countryCodeNumeric": null
},
"refundId": null,
"refundIds": [],
"refundedTransactionId": null,
"partialSettlementTransactionIds": [],
"authorizedTransactionId": null,
"settlementBatchId": "2020-09-06_hok_n70nrrgc",
"shipping": {
"id": null,
"firstName": null,
"lastName": null,
"company": null,
"streetAddress": null,
"extendedAddress": null,
"locality": null,
"region": null,
"postalCode": null,
"countryName": null,
"countryCodeAlpha2": null,
"countryCodeAlpha3": null,
"countryCodeNumeric": null
},
"customFields": null,
"avsErrorResponseCode": null,
"avsPostalCodeResponseCode": "I",
"avsStreetAddressResponseCode": "I",
"cvvResponseCode": "M",
"gatewayRejectionReason": null,
"processorAuthorizationCode": "264NPF",
"processorResponseCode": "1000",
"processorResponseText": "Approved",
"additionalProcessorResponse": null,
"voiceReferralNumber": null,
"purchaseOrderNumber": null,
"taxAmount": null,
"taxExempt": false,
"processedWithNetworkToken": false,
"creditCard": {
"token": null,
"bin": "411111",
"last4": "1111",
"cardType": "Visa",
"expirationMonth": "01",
"expirationYear": "2022",
"customerLocation": "US",
"cardholderName": null,
"imageUrl": "https:\/\/assets.braintreegateway.com\/payment_method_logo\/visa.png?environment=sandbox",
"prepaid": "Unknown",
"healthcare": "Unknown",
"debit": "Unknown",
"durbinRegulated": "Unknown",
"commercial": "Unknown",
"payroll": "Unknown",
"issuingBank": "Unknown",
"countryOfIssuance": "Unknown",
"productId": "Unknown",
"globalId": null,
"accountType": null,
"uniqueNumberIdentifier": null,
"venmoSdk": false
},
"statusHistory": [{}, {}, {}],
"planId": null,
"subscriptionId": null,
"subscription": {
"billingPeriodEndDate": null,
"billingPeriodStartDate": null
},
"addOns": [],
"discounts": [],
"descriptor": {},
"recurring": false,
"channel": null,
"serviceFeeAmount": null,
"escrowStatus": null,
"disbursementDetails": {},
"disputes": [],
"authorizationAdjustments": [],
"paymentInstrumentType": "credit_card",
"processorSettlementResponseCode": null,
"processorSettlementResponseText": null,
"networkResponseCode": null,
"networkResponseText": null,
"threeDSecureInfo": {
"liabilityShifted": true,
"liabilityShiftPossible": true,
"status": "authenticate_successful",
"enrolled": "Y",
"cavv": "AAABAWFlmQAAAABjR=",
"xid": "czI1VWVkWDA=",
"acsTransactionId": null,
"dsTransactionId": null,
"eciFlag": "05",
"paresStatus": "Y",
"threeDSecureAuthenticationId": "s5g9ymmbh",
"threeDSecureServerTransactionId": null,
"threeDSecureVersion": "1.0.2",
"lookup": {
"transStatus": null,
"transStatusReason": null
},
"authentication": {
"transStatus": null,
"transStatusReason": null
}
},
"shipsFromPostalCode": null,
"shippingAmount": null,
"discountAmount": null,
"networkTransactionId": "020200906095733",
"processorResponseType": "approved",
"authorizationExpiresAt": {
"date": "2020-09-13 09:57:33.000000",
"timezone_type": 3,
"timezone": "UTC"
},
"refundGlobalIds": [],
"partialSettlementTransactionGlobalIds": [],
"refundedTransactionGlobalId": null,
"authorizedTransactionGlobalId": null,
"globalId": "dHJhbnNFtZzA3NmY",
"retryIds": [],
"retriedTransactionId": null,
"retrievalReferenceNumber": "1234567",
"creditCardDetails": {},
"customerDetails": {},
"billingDetails": {},
"shippingDetails": {},
"subscriptionDetails": {},
"graphQLId": "dHJhbnNhY3RZzA3NmY"
}
}
Remember to save the transaction result for future payment reconciliation. And don’t forget to send payment successful feedback to the customer who is still sitting in front of your Drop-in UI~