Skip to content
This repository has been archived by the owner on May 31, 2024. It is now read-only.

[CC-3184] Update implementation to support 3DS 2.0 #11

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "xendit-js-node",
"version": "1.0.0",
"version": "2.0.0",
"description": "JS client library for tokenizing credit cards as node modules",
"main": "./src/xendit.js",
"repository": {
Expand Down
177 changes: 148 additions & 29 deletions src/card.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
var RequestUtil = require('./utils/request_util');
var CreditCardUtil = require('./utils/credit_card_util');

var CLIENT_TYPE = 'XENDIT_JS';

if (!window.btoa) {
window.btoa = require('base-64').encode;
}
Expand All @@ -12,6 +14,7 @@ function Card (Xendit) {
}

Card.prototype.createToken = function (tokenData, callback) {
var self = this;
tokenData.is_multiple_use = tokenData.is_multiple_use !== undefined ? tokenData.is_multiple_use : false;
tokenData.should_authenticate = tokenData.should_authenticate !== undefined ? tokenData.should_authenticate : true;

Expand All @@ -35,12 +38,38 @@ Card.prototype.createToken = function (tokenData, callback) {
return callback({ error_code: 'VALIDATION_ERROR', message: 'Card CVN is invalid for this card type' });
}

this._createCreditCardToken(tokenData, function (err, creditCardCharge) {
this._createCreditCardToken(tokenData, function (err, creditCardToken) {
if (err) {
return callback(err);
}

callback(null, creditCardCharge);

if (RequestUtil.hasHigherOrEqualMajorVersion(creditCardToken.threeds_version, 2)) {
self.createAuthentication({
amount: tokenData.amount,
token_id: creditCardToken.id,
jwt: creditCardToken.jwt,
environment: creditCardToken.environment,
bin_number: tokenData.card_number.slice(0, 6)
}, function (err, authentication) {
if (err) {
return callback(err);
}

if (authentication.status === 'IN_REVIEW') {
callback(null, authentication);
}

callback(null, {
id: authentication.credit_card_token_id,
authentication_id: authentication.id,
masked_card_number: authentication.masked_card_number,
status: authentication.status,
metadata: authentication.metadata
});
});
} else {
callback(null, creditCardToken);
}
});
};

Expand Down Expand Up @@ -111,13 +140,22 @@ Card.prototype.createAuthentication = function (authenticationData, transactionM
return callback({ error_code: 'VALIDATION_ERROR', message: 'Token id must be a string' });
}

self._createAuthentication(authenticationData, transactionMetadata, function (err, authentication) {
if (err) {
return callback(err);
}
if (authenticationData.jwt) {
self._createAuthenticationEmv(authenticationData, function (err, authentication) {
if (err) {
return callback(err);
}
return callback(null, authentication);
});
} else {
self._createAuthentication(authenticationData, transactionMetadata, function (err, authentication) {
if (err) {
return callback(err);
}

callback(null, authentication);
});
callback(null, authentication);
});
}
};

Card.prototype.validateCardNumber = function (cardNumber) {
Expand All @@ -137,14 +175,12 @@ Card.prototype.validateCvnForCardType = function (cvn, cardNumber) {
};

Card.prototype._getTokenizationConfiguration = function (callback) {
var publicApiKey = this._xendit._getPublishableKey();
var basicAuthCredentials = 'Basic ' + window.btoa(publicApiKey + ':');
var xenditBaseURL = this._xendit._getXenditURL();
var option = RequestUtil.getServerOptions(this._xendit);

RequestUtil.request({
method: 'GET',
url: xenditBaseURL + '/credit_card_tokenization_configuration',
headers: { Authorization: basicAuthCredentials }
url: option.url + '/credit_card_tokenization_configuration',
headers: option.headers
}, callback);
};

Expand Down Expand Up @@ -206,9 +242,7 @@ Card.prototype._tokenizeCreditCard = function (tokenizationConfiguration, transa
};

Card.prototype._createCreditCardToken = function (tokenData, callback) {
var publicApiKey = this._xendit._getPublishableKey();
var basicAuthCredentials = 'Basic ' + window.btoa(publicApiKey + ':');
var xenditBaseURL = this._xendit._getXenditURL();
var option = RequestUtil.getServerOptions(this._xendit);

var body = {
is_single_use: !tokenData.is_multiple_use,
Expand All @@ -219,6 +253,8 @@ Card.prototype._createCreditCardToken = function (tokenData, callback) {
cvn: tokenData.card_cvn
},
should_authenticate: tokenData.should_authenticate,
billing_details: tokenData.billing_details,
customer: tokenData.customer
};

if(!body.is_single_use && body.card_data.cvn === '' || body.card_data.cvn === null) {
Expand All @@ -233,18 +269,22 @@ Card.prototype._createCreditCardToken = function (tokenData, callback) {
body.card_cvn = tokenData.card_cvn;
}

var headers = option.headers;

if(tokenData.on_behalf_of) {
headers['for-user-id'] = tokenData.on_behalf_of;
}

RequestUtil.request({
method: 'POST',
url: xenditBaseURL + '/v2/credit_card_tokens',
headers: { Authorization: basicAuthCredentials },
url: option.url + '/v2/credit_card_tokens',
headers: headers,
body: body
}, callback);
};

Card.prototype._createCreditCardTokenV1 = function (creditCardToken, transactionData, transactionMetadata, callback) {
var publicApiKey = this._xendit._getPublishableKey();
var basicAuthCredentials = 'Basic ' + window.btoa(publicApiKey + ':');
var xenditBaseURL = this._xendit._getXenditURL();
var option = RequestUtil.getServerOptions(this._xendit);

var body = {
is_authentication_bundled: !transactionData.is_multiple_use,
Expand All @@ -266,16 +306,14 @@ Card.prototype._createCreditCardTokenV1 = function (creditCardToken, transaction

RequestUtil.request({
method: 'POST',
url: xenditBaseURL + '/credit_card_tokens',
headers: { Authorization: basicAuthCredentials },
url: option.url + '/credit_card_tokens',
headers: option,headers,
body: body
}, callback);
};

Card.prototype._createAuthentication = function (authenticationData, transactionMetadata, callback) {
var publicApiKey = this._xendit._getPublishableKey();
var basicAuthCredentials = 'Basic ' + window.btoa(publicApiKey + ':');
var xenditBaseURL = this._xendit._getXenditURL();
var option = RequestUtil.getServerOptions(this._xendit);

var body = {
amount: authenticationData.amount
Expand All @@ -287,10 +325,91 @@ Card.prototype._createAuthentication = function (authenticationData, transaction

RequestUtil.request({
method: 'POST',
url: xenditBaseURL + '/credit_card_tokens/' + authenticationData.token_id + '/authentications',
headers: { Authorization: basicAuthCredentials },
url: option.url + '/credit_card_tokens/' + authenticationData.token_id + '/authentications',
headers: option.headers,
body: body
}, callback);
};

Card.prototype._createAuthenticationEmv = function(createAuthenticationData, callback) {
var self = this;
var option = RequestUtil.getServerOptions(this._xendit);

self._xendit.loadSongBird(createAuthenticationData.environment, function() {
self._createCardinalSession(createAuthenticationData, function(err, session_id) {
if (err) {
return callback(err);
}
var body = {
amount: createAuthenticationData.amount,
currency: createAuthenticationData.currency,
session_id: session_id
};

RequestUtil.request({
method: 'POST',
url: option.url + '/credit_card_tokens/' + createAuthenticationData.token_id + '/authentications',
headers: option.headers,
body: body
}, function(err, authenticationData) {
if (err) {
return callback(err);
}

var is3DS2Enrolled = RequestUtil.hasHigherOrEqualMajorVersion(authenticationData.threeds_version, 2);
if (authenticationData.status === 'FAILED' || authenticationData.status === 'VERIFIED' || !is3DS2Enrolled) {
return callback(null, authenticationData);
}
self._verifyAuthenticationEmv(authenticationData, callback);
});
});
});
};

Card.prototype._createCardinalSession = function(createCardinalSessionData, callback) {
window.Cardinal.on('payments.setupComplete', function (setupCompleteData) {

if (setupCompleteData) {
window.Cardinal.off('payments.setupComplete');
window.Cardinal.trigger('bin.process', createCardinalSessionData.bin_number);
setTimeout(function () {
callback(null, setupCompleteData.sessionId);
}, BIN_CHECK_TIMEOUT);
}
});

window.Cardinal.setup('init', {
jwt: createCardinalSessionData.jwt
});
};

Card.prototype._verifyAuthenticationEmv = function (authenticationData, callback) {
var option = RequestUtil.getServerOptions(this._xendit);

window.Cardinal.on('payments.validated', function () {
window.Cardinal.off('payments.validated');
var body = {
authentication_transaction_id: authenticationData.authentication_transaction_id
};

RequestUtil.request({
method: 'POST',
url: option.url + '/credit_card_authentications/' + authenticationData.id + '/verification',
headers: option.headers,
body: body
}, callback);
});
window.Cardinal.continue('cca',
{
AcsUrl: authenticationData.acs_url,
Payload: authenticationData.pa_req,
},
{
OrderDetails: {
TransactionId: authenticationData.authentication_transaction_id
}
}
);
};

module.exports = Card;
25 changes: 25 additions & 0 deletions src/utils/request_util.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
'use strict';

var pjson = require('../../package.json');

var RequestUtil = {};
var CLIENT_TYPE = 'XENDIT_JS';

RequestUtil.request = function (requestData, responseHandler) {
var request = new XMLHttpRequest();
Expand Down Expand Up @@ -34,4 +37,26 @@ RequestUtil.request = function (requestData, responseHandler) {
request.send(body);
};

RequestUtil.getServerOptions = function (xendit) {
var publicApiKey = xendit._getPublishableKey();
var basicAuthCredentials = 'Basic ' + window.btoa(publicApiKey + ':');
var xenditBaseURL = xendit._getXenditURL();

var headers = {
Authorization: basicAuthCredentials,
'client-type': CLIENT_TYPE,
'client-version': pjson.version
};

return {
headers: headers,
url: xenditBaseURL
};
};

RequestUtil.hasHigherOrEqualMajorVersion = function (versionString, majorVersion) {
var currentMajorVersion = versionString ? versionString.slice(0, versionString.indexOf('.')) : 1;
return Number(currentMajorVersion) >= majorVersion;
};

module.exports = RequestUtil;
20 changes: 20 additions & 0 deletions src/xendit.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ var Card = require('./card');
var STAGING_XENDIT_BASE_URL = 'https://api-staging.xendit.co';
var PRODUCTION_XENDIT_BASE_URL = 'https://api.xendit.co';

var PRODUCTION_SONG_BIRD_URL = 'https://songbird.cardinalcommerce.com/edge/v1/songbird.js';
var STAGING_SONG_BIRD_URL = 'https://songbirdstag.cardinalcommerce.com/edge/v1/songbird.js';

function Xendit() {}

Xendit.settings = {
Expand Down Expand Up @@ -36,5 +39,22 @@ Xendit._getEnvironment = function () {
return isKeyProduction ? 'PRODUCTION' : 'DEVELOPMENT';
};

Xendit.loadSongBird = function (environment, callback) {
scriptTag = document.createElement('script');
scriptTag.src = Xendit._getSongBirdUrl(environment);
document.body.appendChild(scriptTag);
scriptTag.onload = function () {
callback();
};
};

Xendit._getSongBirdUrl = function (environment) {
if (environment.toUpperCase() === 'PRODUCTION') {
return PRODUCTION_SONG_BIRD_URL;
} else {
return STAGING_SONG_BIRD_URL;
}
};

// global.Xendit = Xendit;
module.exports = Xendit;