From 5b133e2a3deff8b7c31a8dd5922711b8810a0e5b Mon Sep 17 00:00:00 2001 From: Francis Levasseur Date: Mon, 17 Jun 2019 11:33:43 -0400 Subject: [PATCH 1/7] clean old unused vars --- lib/rxp-hpp.js | 166 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 145 insertions(+), 21 deletions(-) diff --git a/lib/rxp-hpp.js b/lib/rxp-hpp.js index 13f8d4d..65133ca 100644 --- a/lib/rxp-hpp.js +++ b/lib/rxp-hpp.js @@ -1,4 +1,14 @@ /*jslint browser:true */ +Element.prototype.remove = function() { + this.parentElement.removeChild(this); +} +NodeList.prototype.remove = HTMLCollection.prototype.remove = function() { + for(var i = this.length - 1; i >= 0; i--) { + if(this[i] && this[i].parentElement) { + this[i].parentElement.removeChild(this[i]); + } + } +} var RealexHpp = (function () { 'use strict'; @@ -25,6 +35,105 @@ var RealexHpp = (function () { var redirectUrl; var internal = { + + base64:{ + encode:function(input) { + var keyStr = "ABCDEFGHIJKLMNOP" + + "QRSTUVWXYZabcdef" + + "ghijklmnopqrstuv" + + "wxyz0123456789+/" + + "="; + input = escape(input); + var output = ""; + var chr1, chr2, chr3 = ""; + var enc1, enc2, enc3, enc4 = ""; + var i = 0; + + do { + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + + output = output + + keyStr.charAt(enc1) + + keyStr.charAt(enc2) + + keyStr.charAt(enc3) + + keyStr.charAt(enc4); + chr1 = chr2 = chr3 = ""; + enc1 = enc2 = enc3 = enc4 = ""; + } while (i < input.length); + + return output; + }, + decode:function(input) { + if(typeof input == 'undefined') + return input; + var keyStr = "ABCDEFGHIJKLMNOP" + + "QRSTUVWXYZabcdef" + + "ghijklmnopqrstuv" + + "wxyz0123456789+/" + + "="; + var output = ""; + var chr1, chr2, chr3 = ""; + var enc1, enc2, enc3, enc4 = ""; + var i = 0; + + // remove all characters that are not A-Z, a-z, 0-9, +, /, or = + var base64test = /[^A-Za-z0-9\+\/\=]/g; + if (base64test.exec(input)) { + alert("There were invalid base64 characters in the input text.\n" + + "Valid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\n" + + "Expect errors in decoding."); + } + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + + do { + enc1 = keyStr.indexOf(input.charAt(i++)); + enc2 = keyStr.indexOf(input.charAt(i++)); + enc3 = keyStr.indexOf(input.charAt(i++)); + enc4 = keyStr.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + output = output + String.fromCharCode(chr1); + + if (enc3 != 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 != 64) { + output = output + String.fromCharCode(chr3); + } + + chr1 = chr2 = chr3 = ""; + enc1 = enc2 = enc3 = enc4 = ""; + + } while (i < input.length); + + return unescape(output); + } + }, + decodeAnswer:function(answer){ //internal.decodeAnswer + + var _r=JSON.parse(answer); + for(var r in _r){ + if(_r[r]) + _r[r]=internal.base64.decode(_r[r]) + } + return _r; + }, createFormHiddenInput: function (name, value) { var el = document.createElement("input"); el.setAttribute("type", "hidden"); @@ -291,12 +400,12 @@ var RealexHpp = (function () { if (!internal.isMessageFromHpp(event.origin, hppUrl)) { return; } - // check for iframe resize values - if (event.data && JSON.parse(event.data).iframe) { + var evtdata; + if (event.data && (evtdata=JSON.parse(event.data)).iframe) { if (!isMobileNewTab) { - var iframeWidth = JSON.parse(event.data).iframe.width; - var iframeHeight = JSON.parse(event.data).iframe.height; + var iframeWidth = evtdata.iframe.width; + var iframeHeight = evtdata.iframe.height; var iFrame; var resized = false; @@ -306,6 +415,7 @@ var RealexHpp = (function () { } else { iFrame = document.getElementById("rxp-frame-" + randomId); } + if(lightboxInstance.events && lightboxInstance.events.onResize) lightboxInstance.events.onResize(evtdata.iframe) if (iframeWidth === "390px" && iframeHeight === "440px") { iFrame.setAttribute("width", iframeWidth); @@ -340,25 +450,32 @@ var RealexHpp = (function () { } } } else { - if (isMobileNewTab && tabWindow) { - //Close the new window - tabWindow.close(); - } else { - //Close the lightbox - lightboxInstance.close(); - } + var _close=function(){ + if (isMobileNewTab && tabWindow) { + //Close the new window + tabWindow.close(); + } else { + //Close the lightbox + lightboxInstance.close(); + } + var overlay=document.getElementById("rxp-overlay-" + randomId) + if(overlay) overlay.remove(); + } var response = event.data; - + //allow the script to intercept the answer, instead of redirecting to another page. (which is really a 90s thing) + if(typeof merchantUrl=='function'){ + var answer=internal.decodeAnswer(response); + merchantUrl(answer,_close); + return; + } + _close(); //Create a form and submit the hpp response to the merchant's response url var form = document.createElement("form"); form.setAttribute("method", "POST"); form.setAttribute("action", merchantUrl); - form.appendChild(internal.createFormHiddenInput("hppResponse", response)); - document.body.appendChild(form); - form.submit(); } }; @@ -423,8 +540,12 @@ var RealexHpp = (function () { //Get the lightbox instance (it's a singleton) and set the sdk json var lightboxInstance = RxpLightbox.getInstance(serverSdkJson); + //if you want the form to load on function call, set to autoload + if(idOfLightboxButton=='autoload'){ + lightboxInstance.lightbox(); + } // Sets the event listener on the PAY button. The click will invoke the lightbox method - if (document.getElementById(idOfLightboxButton).addEventListener) { + else if (document.getElementById(idOfLightboxButton).addEventListener) { document.getElementById(idOfLightboxButton).addEventListener("click", lightboxInstance.lightbox, true); } else { document.getElementById(idOfLightboxButton).attachEvent('onclick', lightboxInstance.lightbox); @@ -488,17 +609,20 @@ var RealexHpp = (function () { //Set the hpp token instance.setToken(hppToken); - return instance; }, - init: function (idOfEmbeddedButton, idOfTargetIframe, merchantUrl, serverSdkJson) { + init: function (idOfEmbeddedButton, idOfTargetIframe, merchantUrl, serverSdkJson,events) { //Get the embedded instance (it's a singleton) and set the sdk json var embeddedInstance = RxpEmbedded.getInstance(serverSdkJson); + embeddedInstance.events=events; embeddedInstance.setIframe(idOfTargetIframe); - + //if you want the form to load on function call, set to autoload + if(idOfEmbeddedButton=='autoload'){ + embeddedInstance.embedded(); + } // Sets the event listener on the PAY button. The click will invoke the embedded method - if (document.getElementById(idOfEmbeddedButton).addEventListener) { + else if (document.getElementById(idOfEmbeddedButton).addEventListener) { document.getElementById(idOfEmbeddedButton).addEventListener("click", embeddedInstance.embedded, true); } else { document.getElementById(idOfEmbeddedButton).attachEvent('onclick', embeddedInstance.embedded); @@ -593,4 +717,4 @@ var RealexHpp = (function () { _internal: internal }; -}()); +}()); \ No newline at end of file From 35950d42d2b2121de87f97f7488e08ccc2225d29 Mon Sep 17 00:00:00 2001 From: Francis Levasseur Date: Mon, 17 Jun 2019 11:09:51 -0400 Subject: [PATCH 2/7] added links to examples --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 38b919d..eac0028 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,14 @@ RealexHpp.init(payButtonId, merchantUrl, jsonFromServerSdk); ### Consuming the resulting POST Once the payment has completed the Realex JSON response will be posted within to the supplied merchantUrl. The name of the field containing the JSON response is hppResponse. +If you prefer to handle response manually, provide your own callback function in "merchantUrl". The answer will be pre-parsed to an object ready to be used. + +## Examples +* [embedded iFrame](examples/hpp/process-a-payment-embedded.html) +* [embedded iFrame autoload](examples/hpp/process-a-payment-embedded-autoload.html) +* [embedded iFrame autoload, callback](examples/hpp/process-a-payment-embedded-autoload-callback.html) +* [lightbox/modal](examples/hpp/process-a-payment-lightbox.html) + ## Remote JS Library ### Validation functions From e3719cfa69affcd741ab8ba2e1f4e6836b0bdffe Mon Sep 17 00:00:00 2001 From: Francis Levasseur Date: Mon, 17 Jun 2019 10:52:56 -0400 Subject: [PATCH 3/7] added callback as merchantUrl, added options for modal to support some customization and handle resize events, added some examples --- README.md | 13 +- dist/rxp-js.js | 165 +++++++++++++++--- ...-a-payment-embedded-autoload-callback.html | 46 +++++ .../process-a-payment-embedded-autoload.html | 32 ++++ examples/hpp/process-a-payment-embedded.html | 11 +- examples/hpp/process-a-payment-lightbox.html | 6 +- 6 files changed, 245 insertions(+), 28 deletions(-) create mode 100644 examples/hpp/process-a-payment-embedded-autoload-callback.html create mode 100644 examples/hpp/process-a-payment-embedded-autoload.html diff --git a/README.md b/README.md index eac0028..5aea9df 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,17 @@ You can sign up for a Realex account at https://developer.realexpayments.com ## Hosted Payment Page (HPP) JS Library ### Usage -The Javascript required to initialise the library is below. This code must only be executed when the DOM is fully loaded. +The Javascript required to initialise the library is below. This code must only be executed when the DOM is fully loaded. (default method: lightbox) ```javascript -RealexHpp.init(payButtonId, merchantUrl, jsonFromServerSdk); +RealexHpp.init(payButtonId, merchantUrl, jsonFromServerSdk[, options]); ``` -* payButtonId - The ID of the button used to launch the lightbox. -* merchantUrl - The URL to which the JSON response from Realex will be posted. +* payButtonId - The ID of the button used to launch the lightbox. Set to "autoload" if you want to load without having to press a button +* merchantUrl - could be one of 2 types: + - string - The URL to which the JSON response from Realex will be posted. + - function - the callback function * jsonFromServerSdk - The JSON output from the Realex HPP Server SDK. +* options/events + - onResize (iframe embed) Send resize iframe events so the parent frame can be adjusted ### Consuming the resulting POST Once the payment has completed the Realex JSON response will be posted within to the supplied merchantUrl. The name of the field containing the JSON response is hppResponse. @@ -18,6 +22,7 @@ Once the payment has completed the Realex JSON response will be posted within to If you prefer to handle response manually, provide your own callback function in "merchantUrl". The answer will be pre-parsed to an object ready to be used. ## Examples + * [embedded iFrame](examples/hpp/process-a-payment-embedded.html) * [embedded iFrame autoload](examples/hpp/process-a-payment-embedded-autoload.html) * [embedded iFrame autoload, callback](examples/hpp/process-a-payment-embedded-autoload-callback.html) diff --git a/dist/rxp-js.js b/dist/rxp-js.js index f9efc4f..d03c6b4 100644 --- a/dist/rxp-js.js +++ b/dist/rxp-js.js @@ -3,6 +3,16 @@ * https://github.com/realexpayments/rxp-js * Licensed MIT */ +Element.prototype.remove = function() { + this.parentElement.removeChild(this); +} +NodeList.prototype.remove = HTMLCollection.prototype.remove = function() { + for(var i = this.length - 1; i >= 0; i--) { + if(this[i] && this[i].parentElement) { + this[i].parentElement.removeChild(this[i]); + } + } +} var RealexHpp = (function () { 'use strict'; @@ -29,6 +39,105 @@ var RealexHpp = (function () { var redirectUrl; var internal = { + + base64:{ + encode:function(input) { + var keyStr = "ABCDEFGHIJKLMNOP" + + "QRSTUVWXYZabcdef" + + "ghijklmnopqrstuv" + + "wxyz0123456789+/" + + "="; + input = escape(input); + var output = ""; + var chr1, chr2, chr3 = ""; + var enc1, enc2, enc3, enc4 = ""; + var i = 0; + + do { + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + + output = output + + keyStr.charAt(enc1) + + keyStr.charAt(enc2) + + keyStr.charAt(enc3) + + keyStr.charAt(enc4); + chr1 = chr2 = chr3 = ""; + enc1 = enc2 = enc3 = enc4 = ""; + } while (i < input.length); + + return output; + }, + decode:function(input) { + if(typeof input == 'undefined') + return input; + var keyStr = "ABCDEFGHIJKLMNOP" + + "QRSTUVWXYZabcdef" + + "ghijklmnopqrstuv" + + "wxyz0123456789+/" + + "="; + var output = ""; + var chr1, chr2, chr3 = ""; + var enc1, enc2, enc3, enc4 = ""; + var i = 0; + + // remove all characters that are not A-Z, a-z, 0-9, +, /, or = + var base64test = /[^A-Za-z0-9\+\/\=]/g; + if (base64test.exec(input)) { + alert("There were invalid base64 characters in the input text.\n" + + "Valid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\n" + + "Expect errors in decoding."); + } + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + + do { + enc1 = keyStr.indexOf(input.charAt(i++)); + enc2 = keyStr.indexOf(input.charAt(i++)); + enc3 = keyStr.indexOf(input.charAt(i++)); + enc4 = keyStr.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + output = output + String.fromCharCode(chr1); + + if (enc3 != 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 != 64) { + output = output + String.fromCharCode(chr3); + } + + chr1 = chr2 = chr3 = ""; + enc1 = enc2 = enc3 = enc4 = ""; + + } while (i < input.length); + + return unescape(output); + } + }, + decodeAnswer:function(answer){ //internal.decodeAnswer + + var _r=JSON.parse(answer); + for(var r in _r){ + if(_r[r]) + _r[r]=internal.base64.decode(_r[r]) + } + return _r; + }, createFormHiddenInput: function (name, value) { var el = document.createElement("input"); el.setAttribute("type", "hidden"); @@ -295,12 +404,12 @@ var RealexHpp = (function () { if (!internal.isMessageFromHpp(event.origin, hppUrl)) { return; } - // check for iframe resize values - if (event.data && JSON.parse(event.data).iframe) { + var evtdata; + if (event.data && (evtdata=JSON.parse(event.data)).iframe) { if (!isMobileNewTab) { - var iframeWidth = JSON.parse(event.data).iframe.width; - var iframeHeight = JSON.parse(event.data).iframe.height; + var iframeWidth = evtdata.iframe.width; + var iframeHeight = evtdata.iframe.height; var iFrame; var resized = false; @@ -310,6 +419,7 @@ var RealexHpp = (function () { } else { iFrame = document.getElementById("rxp-frame-" + randomId); } + if(lightboxInstance.events && lightboxInstance.events.onResize) lightboxInstance.events.onResize(evtdata.iframe) if (iframeWidth === "390px" && iframeHeight === "440px") { iFrame.setAttribute("width", iframeWidth); @@ -344,25 +454,32 @@ var RealexHpp = (function () { } } } else { - if (isMobileNewTab && tabWindow) { - //Close the new window - tabWindow.close(); - } else { - //Close the lightbox - lightboxInstance.close(); - } + var _close=function(){ + if (isMobileNewTab && tabWindow) { + //Close the new window + tabWindow.close(); + } else { + //Close the lightbox + lightboxInstance.close(); + } + var overlay=document.getElementById("rxp-overlay-" + randomId) + if(overlay) overlay.remove(); + } var response = event.data; - + //allow the script to intercept the answer, instead of redirecting to another page. (which is really a 90s thing) + if(typeof merchantUrl=='function'){ + var answer=internal.decodeAnswer(response); + merchantUrl(answer,_close); + return; + } + _close(); //Create a form and submit the hpp response to the merchant's response url var form = document.createElement("form"); form.setAttribute("method", "POST"); form.setAttribute("action", merchantUrl); - form.appendChild(internal.createFormHiddenInput("hppResponse", response)); - document.body.appendChild(form); - form.submit(); } }; @@ -375,6 +492,7 @@ var RealexHpp = (function () { function init() { var overlayElement; + var callback; var spinner; var iFrame; var closeButton; @@ -427,8 +545,12 @@ var RealexHpp = (function () { //Get the lightbox instance (it's a singleton) and set the sdk json var lightboxInstance = RxpLightbox.getInstance(serverSdkJson); + //if you want the form to load on function call, set to autoload + if(idOfLightboxButton=='autoload'){ + lightboxInstance.lightbox(); + } // Sets the event listener on the PAY button. The click will invoke the lightbox method - if (document.getElementById(idOfLightboxButton).addEventListener) { + else if (document.getElementById(idOfLightboxButton).addEventListener) { document.getElementById(idOfLightboxButton).addEventListener("click", lightboxInstance.lightbox, true); } else { document.getElementById(idOfLightboxButton).attachEvent('onclick', lightboxInstance.lightbox); @@ -492,17 +614,20 @@ var RealexHpp = (function () { //Set the hpp token instance.setToken(hppToken); - return instance; }, - init: function (idOfEmbeddedButton, idOfTargetIframe, merchantUrl, serverSdkJson) { + init: function (idOfEmbeddedButton, idOfTargetIframe, merchantUrl, serverSdkJson,events) { //Get the embedded instance (it's a singleton) and set the sdk json var embeddedInstance = RxpEmbedded.getInstance(serverSdkJson); + embeddedInstance.events=events; embeddedInstance.setIframe(idOfTargetIframe); - + //if you want the form to load on function call, set to autoload + if(idOfEmbeddedButton=='autoload'){ + embeddedInstance.embedded(); + } // Sets the event listener on the PAY button. The click will invoke the embedded method - if (document.getElementById(idOfEmbeddedButton).addEventListener) { + else if (document.getElementById(idOfEmbeddedButton).addEventListener) { document.getElementById(idOfEmbeddedButton).addEventListener("click", embeddedInstance.embedded, true); } else { document.getElementById(idOfEmbeddedButton).attachEvent('onclick', embeddedInstance.embedded); diff --git a/examples/hpp/process-a-payment-embedded-autoload-callback.html b/examples/hpp/process-a-payment-embedded-autoload-callback.html new file mode 100644 index 0000000..559904d --- /dev/null +++ b/examples/hpp/process-a-payment-embedded-autoload-callback.html @@ -0,0 +1,46 @@ + + + + HPP embed Demo + + + + + + +
+ + + diff --git a/examples/hpp/process-a-payment-embedded-autoload.html b/examples/hpp/process-a-payment-embedded-autoload.html new file mode 100644 index 0000000..d695c7d --- /dev/null +++ b/examples/hpp/process-a-payment-embedded-autoload.html @@ -0,0 +1,32 @@ + + + + HPP embed Demo + + + + + + + + + diff --git a/examples/hpp/process-a-payment-embedded.html b/examples/hpp/process-a-payment-embedded.html index 09d2f1c..a82e96a 100644 --- a/examples/hpp/process-a-payment-embedded.html +++ b/examples/hpp/process-a-payment-embedded.html @@ -1,7 +1,7 @@ - HPP Lightbox Demo + HPP embed Demo @@ -13,8 +13,13 @@ RealexHpp.embedded.init( "payButtonId", "targetIframe", - "https://dev.rlxcarts.com/mobileSDKsV2/response.php", - jsonFromServerSdk + "https://dev.rlxcarts.com/mobileSDKsV2/response.php", // merchant url + jsonFromServerSdk, // form data + { // options + onResize:function(data){ + $('#targetIframe').css(data) + } + } ); $('body').addClass('loaded'); }); diff --git a/examples/hpp/process-a-payment-lightbox.html b/examples/hpp/process-a-payment-lightbox.html index dad1621..90fe77b 100644 --- a/examples/hpp/process-a-payment-lightbox.html +++ b/examples/hpp/process-a-payment-lightbox.html @@ -10,7 +10,11 @@ // get the HPP JSON from the server-side SDK $(document).ready(function () { $.getJSON("/examples/hpp/proxy-request.php?slug=process-a-payment", function (jsonFromServerSdk) { - RealexHpp.lightbox.init("payButtonId", "https://dev.rlxcarts.com/mobileSDKsV2/response.php", jsonFromServerSdk); + RealexHpp.lightbox.init( + "payButtonId", + "https://dev.rlxcarts.com/mobileSDKsV2/response.php", // merchant url + jsonFromServerSdk //form data + ); $('body').addClass('loaded'); }); }); From 0823a70ed73d62cc64c2bce66352ed7682365297 Mon Sep 17 00:00:00 2001 From: Shane Logsdon Date: Mon, 29 Mar 2021 08:51:06 -0400 Subject: [PATCH 4/7] fix lint errors --- dist/rxp-js.js | 72 ++++++++++++++++++++++++---------------------- dist/rxp-js.min.js | 4 +-- lib/.jshintrc | 2 +- lib/rxp-hpp.js | 68 +++++++++++++++++++++++-------------------- package.json | 2 +- 5 files changed, 79 insertions(+), 69 deletions(-) diff --git a/dist/rxp-js.js b/dist/rxp-js.js index d03c6b4..33cd8ff 100644 --- a/dist/rxp-js.js +++ b/dist/rxp-js.js @@ -1,18 +1,18 @@ -/*! rxp-js - v1.3.1 - 2018-08-30 +/*! rxp-js - v1.4.0 - 2021-03-29 * The official Realex Payments JS Library * https://github.com/realexpayments/rxp-js * Licensed MIT */ Element.prototype.remove = function() { this.parentElement.removeChild(this); -} +}; NodeList.prototype.remove = HTMLCollection.prototype.remove = function() { for(var i = this.length - 1; i >= 0; i--) { if(this[i] && this[i].parentElement) { this[i].parentElement.removeChild(this[i]); } } -} +}; var RealexHpp = (function () { 'use strict'; @@ -52,23 +52,23 @@ var RealexHpp = (function () { var chr1, chr2, chr3 = ""; var enc1, enc2, enc3, enc4 = ""; var i = 0; - + do { chr1 = input.charCodeAt(i++); chr2 = input.charCodeAt(i++); chr3 = input.charCodeAt(i++); - + enc1 = chr1 >> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc4 = chr3 & 63; - + if (isNaN(chr2)) { enc3 = enc4 = 64; } else if (isNaN(chr3)) { enc4 = 64; } - + output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + @@ -77,12 +77,13 @@ var RealexHpp = (function () { chr1 = chr2 = chr3 = ""; enc1 = enc2 = enc3 = enc4 = ""; } while (i < input.length); - + return output; }, decode:function(input) { - if(typeof input == 'undefined') + if(typeof input === 'undefined') { return input; + } var keyStr = "ABCDEFGHIJKLMNOP" + "QRSTUVWXYZabcdef" + "ghijklmnopqrstuv" + @@ -92,7 +93,7 @@ var RealexHpp = (function () { var chr1, chr2, chr3 = ""; var enc1, enc2, enc3, enc4 = ""; var i = 0; - + // remove all characters that are not A-Z, a-z, 0-9, +, /, or = var base64test = /[^A-Za-z0-9\+\/\=]/g; if (base64test.exec(input)) { @@ -101,31 +102,31 @@ var RealexHpp = (function () { "Expect errors in decoding."); } input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); - + do { enc1 = keyStr.indexOf(input.charAt(i++)); enc2 = keyStr.indexOf(input.charAt(i++)); enc3 = keyStr.indexOf(input.charAt(i++)); enc4 = keyStr.indexOf(input.charAt(i++)); - + chr1 = (enc1 << 2) | (enc2 >> 4); chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); chr3 = ((enc3 & 3) << 6) | enc4; - + output = output + String.fromCharCode(chr1); - - if (enc3 != 64) { + + if (enc3 !== 64) { output = output + String.fromCharCode(chr2); } - if (enc4 != 64) { + if (enc4 !== 64) { output = output + String.fromCharCode(chr3); } - + chr1 = chr2 = chr3 = ""; enc1 = enc2 = enc3 = enc4 = ""; - + } while (i < input.length); - + return unescape(output); } }, @@ -133,8 +134,9 @@ var RealexHpp = (function () { var _r=JSON.parse(answer); for(var r in _r){ - if(_r[r]) - _r[r]=internal.base64.decode(_r[r]) + if(_r[r]) { + _r[r]=internal.base64.decode(_r[r]); + } } return _r; }, @@ -236,20 +238,20 @@ var RealexHpp = (function () { var form = document.createElement("form"); form.setAttribute("method", "POST"); form.setAttribute("action", hppUrl); - + var versionSet = false; - + for (var key in token) { if (key === "HPP_VERSION"){ versionSet = true; } form.appendChild(internal.createFormHiddenInput(key, token[key])); } - + if (versionSet === false){ form.appendChild(internal.createFormHiddenInput("HPP_VERSION", "2")); } - + if (ignorePostMessage) { form.appendChild(internal.createFormHiddenInput("MERCHANT_RESPONSE_URL", redirectUrl)); } else { @@ -419,7 +421,9 @@ var RealexHpp = (function () { } else { iFrame = document.getElementById("rxp-frame-" + randomId); } - if(lightboxInstance.events && lightboxInstance.events.onResize) lightboxInstance.events.onResize(evtdata.iframe) + if(lightboxInstance.events && lightboxInstance.events.onResize) { + lightboxInstance.events.onResize(evtdata.iframe); + } if (iframeWidth === "390px" && iframeHeight === "440px") { iFrame.setAttribute("width", iframeWidth); @@ -462,13 +466,15 @@ var RealexHpp = (function () { //Close the lightbox lightboxInstance.close(); } - var overlay=document.getElementById("rxp-overlay-" + randomId) - if(overlay) overlay.remove(); + var overlay=document.getElementById("rxp-overlay-" + randomId); + if(overlay) { + overlay.remove(); + } - } + }; var response = event.data; //allow the script to intercept the answer, instead of redirecting to another page. (which is really a 90s thing) - if(typeof merchantUrl=='function'){ + if(typeof merchantUrl==='function'){ var answer=internal.decodeAnswer(response); merchantUrl(answer,_close); return; @@ -492,7 +498,6 @@ var RealexHpp = (function () { function init() { var overlayElement; - var callback; var spinner; var iFrame; var closeButton; @@ -546,7 +551,7 @@ var RealexHpp = (function () { var lightboxInstance = RxpLightbox.getInstance(serverSdkJson); //if you want the form to load on function call, set to autoload - if(idOfLightboxButton=='autoload'){ + if(idOfLightboxButton==='autoload'){ lightboxInstance.lightbox(); } // Sets the event listener on the PAY button. The click will invoke the lightbox method @@ -623,7 +628,7 @@ var RealexHpp = (function () { embeddedInstance.setIframe(idOfTargetIframe); //if you want the form to load on function call, set to autoload - if(idOfEmbeddedButton=='autoload'){ + if(idOfEmbeddedButton==='autoload'){ embeddedInstance.embedded(); } // Sets the event listener on the PAY button. The click will invoke the embedded method @@ -723,7 +728,6 @@ var RealexHpp = (function () { }; }()); - var RealexRemote = (function() { 'use strict'; diff --git a/dist/rxp-js.min.js b/dist/rxp-js.min.js index b623ad5..835b3a0 100644 --- a/dist/rxp-js.min.js +++ b/dist/rxp-js.min.js @@ -1,7 +1,7 @@ -/*! rxp-js - v1.3.1 - 2018-08-30 +/*! rxp-js - v1.4.0 - 2021-03-29 * The official Realex Payments JS Library * https://github.com/realexpayments/rxp-js * Licensed MIT */ -var RealexHpp=function(){"use strict";var g,d,i,n,A,l="https://pay.realexpayments.com/pay",I=I||Math.random().toString(16).substr(2,8),e=/Windows Phone|IEMobile/.test(navigator.userAgent),t=/Android|iPad|iPhone|iPod/.test(navigator.userAgent),o=(0>2,o=(3&A)<<4|(n=e.charCodeAt(c++))>>4,r=(15&n)<<2|(d=e.charCodeAt(c++))>>6,s=63&d,isNaN(n)?r=s=64:isNaN(d)&&(s=64),a=a+t.charAt(i)+t.charAt(o)+t.charAt(r)+t.charAt(s),A=n=d="",i=o=r=s="",c> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc4 = chr3 & 63; - + if (isNaN(chr2)) { enc3 = enc4 = 64; } else if (isNaN(chr3)) { enc4 = 64; } - + output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + @@ -73,12 +73,13 @@ var RealexHpp = (function () { chr1 = chr2 = chr3 = ""; enc1 = enc2 = enc3 = enc4 = ""; } while (i < input.length); - + return output; }, decode:function(input) { - if(typeof input == 'undefined') + if(typeof input === 'undefined') { return input; + } var keyStr = "ABCDEFGHIJKLMNOP" + "QRSTUVWXYZabcdef" + "ghijklmnopqrstuv" + @@ -88,7 +89,7 @@ var RealexHpp = (function () { var chr1, chr2, chr3 = ""; var enc1, enc2, enc3, enc4 = ""; var i = 0; - + // remove all characters that are not A-Z, a-z, 0-9, +, /, or = var base64test = /[^A-Za-z0-9\+\/\=]/g; if (base64test.exec(input)) { @@ -97,31 +98,31 @@ var RealexHpp = (function () { "Expect errors in decoding."); } input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); - + do { enc1 = keyStr.indexOf(input.charAt(i++)); enc2 = keyStr.indexOf(input.charAt(i++)); enc3 = keyStr.indexOf(input.charAt(i++)); enc4 = keyStr.indexOf(input.charAt(i++)); - + chr1 = (enc1 << 2) | (enc2 >> 4); chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); chr3 = ((enc3 & 3) << 6) | enc4; - + output = output + String.fromCharCode(chr1); - - if (enc3 != 64) { + + if (enc3 !== 64) { output = output + String.fromCharCode(chr2); } - if (enc4 != 64) { + if (enc4 !== 64) { output = output + String.fromCharCode(chr3); } - + chr1 = chr2 = chr3 = ""; enc1 = enc2 = enc3 = enc4 = ""; - + } while (i < input.length); - + return unescape(output); } }, @@ -129,8 +130,9 @@ var RealexHpp = (function () { var _r=JSON.parse(answer); for(var r in _r){ - if(_r[r]) - _r[r]=internal.base64.decode(_r[r]) + if(_r[r]) { + _r[r]=internal.base64.decode(_r[r]); + } } return _r; }, @@ -232,20 +234,20 @@ var RealexHpp = (function () { var form = document.createElement("form"); form.setAttribute("method", "POST"); form.setAttribute("action", hppUrl); - + var versionSet = false; - + for (var key in token) { if (key === "HPP_VERSION"){ versionSet = true; } form.appendChild(internal.createFormHiddenInput(key, token[key])); } - + if (versionSet === false){ form.appendChild(internal.createFormHiddenInput("HPP_VERSION", "2")); } - + if (ignorePostMessage) { form.appendChild(internal.createFormHiddenInput("MERCHANT_RESPONSE_URL", redirectUrl)); } else { @@ -415,7 +417,9 @@ var RealexHpp = (function () { } else { iFrame = document.getElementById("rxp-frame-" + randomId); } - if(lightboxInstance.events && lightboxInstance.events.onResize) lightboxInstance.events.onResize(evtdata.iframe) + if(lightboxInstance.events && lightboxInstance.events.onResize) { + lightboxInstance.events.onResize(evtdata.iframe); + } if (iframeWidth === "390px" && iframeHeight === "440px") { iFrame.setAttribute("width", iframeWidth); @@ -458,13 +462,15 @@ var RealexHpp = (function () { //Close the lightbox lightboxInstance.close(); } - var overlay=document.getElementById("rxp-overlay-" + randomId) - if(overlay) overlay.remove(); + var overlay=document.getElementById("rxp-overlay-" + randomId); + if(overlay) { + overlay.remove(); + } - } + }; var response = event.data; //allow the script to intercept the answer, instead of redirecting to another page. (which is really a 90s thing) - if(typeof merchantUrl=='function'){ + if(typeof merchantUrl==='function'){ var answer=internal.decodeAnswer(response); merchantUrl(answer,_close); return; @@ -541,7 +547,7 @@ var RealexHpp = (function () { var lightboxInstance = RxpLightbox.getInstance(serverSdkJson); //if you want the form to load on function call, set to autoload - if(idOfLightboxButton=='autoload'){ + if(idOfLightboxButton==='autoload'){ lightboxInstance.lightbox(); } // Sets the event listener on the PAY button. The click will invoke the lightbox method @@ -618,7 +624,7 @@ var RealexHpp = (function () { embeddedInstance.setIframe(idOfTargetIframe); //if you want the form to load on function call, set to autoload - if(idOfEmbeddedButton=='autoload'){ + if(idOfEmbeddedButton==='autoload'){ embeddedInstance.embedded(); } // Sets the event listener on the PAY button. The click will invoke the embedded method diff --git a/package.json b/package.json index 632392a..010f20b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "rxp-js", "description": "The official Realex Payments JS Library", - "version": "1.3.1", + "version": "1.4.0", "homepage": "https://github.com/realexpayments/rxp-js", "author": { "name": "Realex Developer", From d808281f20b1043c0ccdabe6c491798941991ce7 Mon Sep 17 00:00:00 2001 From: Shane Logsdon Date: Mon, 17 Jul 2017 20:50:10 -0400 Subject: [PATCH 5/7] allow for changing of mobile xs screen size --- lib/rxp-hpp.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/rxp-hpp.js b/lib/rxp-hpp.js index 7066a25..015c487 100644 --- a/lib/rxp-hpp.js +++ b/lib/rxp-hpp.js @@ -21,15 +21,26 @@ var RealexHpp = (function () { hppUrl = url; }; + var mobileXSLowerBound = 360; + var setMobileXSLowerBound = function (lowerBound) { + mobileXSLowerBound = lowerBound; + }; + var isWindowsMobileOs = /Windows Phone|IEMobile/.test(navigator.userAgent); var isAndroidOrIOs = /Android|iPad|iPhone|iPod/.test(navigator.userAgent); - var isMobileXS = ( (((window.innerWidth > 0) ? window.innerWidth : screen.width) <= 360 ? true : false) || (((window.innerHeight > 0) ? window.innerHeight : screen.Height) <= 360 ? true : false)) ; + var isMobileXS = function () { + return (((window.innerWidth > 0) ? window.innerWidth : screen.width) <= mobileXSLowerBound ? true : false) || + (((window.innerHeight > 0) ? window.innerHeight : screen.Height) <= mobileXSLowerBound ? true : false); + }; // Display IFrame on WIndows Phone OS mobile devices var isMobileIFrame = isWindowsMobileOs; // For IOs/Android and small screen devices always open in new tab/window - var isMobileNewTab = !isWindowsMobileOs && (isAndroidOrIOs || isMobileXS); + var isMobileNewTab = function () { + return !isWindowsMobileOs && (isAndroidOrIOs || isMobileXS()); + }; + var tabWindow; var redirectUrl; @@ -405,7 +416,7 @@ var RealexHpp = (function () { // check for iframe resize values var evtdata; if (event.data && (evtdata=JSON.parse(event.data)).iframe) { - if (!isMobileNewTab) { + if (!isMobileNewTab()) { var iframeWidth = evtdata.iframe.width; var iframeHeight = evtdata.iframe.height; @@ -455,7 +466,7 @@ var RealexHpp = (function () { } } else { var _close=function(){ - if (isMobileNewTab && tabWindow) { + if (isMobileNewTab() && tabWindow) { //Close the new window tabWindow.close(); } else { @@ -510,7 +521,7 @@ var RealexHpp = (function () { return { lightbox: function () { - if (isMobileNewTab) { + if (isMobileNewTab()) { tabWindow = internal.openWindow(token); } else { overlayElement = internal.createOverlay(); @@ -720,6 +731,7 @@ var RealexHpp = (function () { init: RxpRedirect.init }, setHppUrl: setHppUrl, + setMobileXSLowerBound: setMobileXSLowerBound, _internal: internal }; From 36a8b779876c5b1e6d26a78060f6d4253bbe1760 Mon Sep 17 00:00:00 2001 From: Shane Logsdon Date: Mon, 29 Mar 2021 09:10:57 -0400 Subject: [PATCH 6/7] add lightbox / callback example --- dist/rxp-js.js | 22 +++++++--- dist/rxp-js.min.js | 2 +- .../process-a-payment-lightbox-callback.html | 40 +++++++++++++++++++ 3 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 examples/hpp/process-a-payment-lightbox-callback.html diff --git a/dist/rxp-js.js b/dist/rxp-js.js index 33cd8ff..6307609 100644 --- a/dist/rxp-js.js +++ b/dist/rxp-js.js @@ -25,15 +25,26 @@ var RealexHpp = (function () { hppUrl = url; }; + var mobileXSLowerBound = 360; + var setMobileXSLowerBound = function (lowerBound) { + mobileXSLowerBound = lowerBound; + }; + var isWindowsMobileOs = /Windows Phone|IEMobile/.test(navigator.userAgent); var isAndroidOrIOs = /Android|iPad|iPhone|iPod/.test(navigator.userAgent); - var isMobileXS = ( (((window.innerWidth > 0) ? window.innerWidth : screen.width) <= 360 ? true : false) || (((window.innerHeight > 0) ? window.innerHeight : screen.Height) <= 360 ? true : false)) ; + var isMobileXS = function () { + return (((window.innerWidth > 0) ? window.innerWidth : screen.width) <= mobileXSLowerBound ? true : false) || + (((window.innerHeight > 0) ? window.innerHeight : screen.Height) <= mobileXSLowerBound ? true : false); + }; // Display IFrame on WIndows Phone OS mobile devices var isMobileIFrame = isWindowsMobileOs; // For IOs/Android and small screen devices always open in new tab/window - var isMobileNewTab = !isWindowsMobileOs && (isAndroidOrIOs || isMobileXS); + var isMobileNewTab = function () { + return !isWindowsMobileOs && (isAndroidOrIOs || isMobileXS()); + }; + var tabWindow; var redirectUrl; @@ -409,7 +420,7 @@ var RealexHpp = (function () { // check for iframe resize values var evtdata; if (event.data && (evtdata=JSON.parse(event.data)).iframe) { - if (!isMobileNewTab) { + if (!isMobileNewTab()) { var iframeWidth = evtdata.iframe.width; var iframeHeight = evtdata.iframe.height; @@ -459,7 +470,7 @@ var RealexHpp = (function () { } } else { var _close=function(){ - if (isMobileNewTab && tabWindow) { + if (isMobileNewTab() && tabWindow) { //Close the new window tabWindow.close(); } else { @@ -514,7 +525,7 @@ var RealexHpp = (function () { return { lightbox: function () { - if (isMobileNewTab) { + if (isMobileNewTab()) { tabWindow = internal.openWindow(token); } else { overlayElement = internal.createOverlay(); @@ -724,6 +735,7 @@ var RealexHpp = (function () { init: RxpRedirect.init }, setHppUrl: setHppUrl, + setMobileXSLowerBound: setMobileXSLowerBound, _internal: internal }; diff --git a/dist/rxp-js.min.js b/dist/rxp-js.min.js index 835b3a0..a844598 100644 --- a/dist/rxp-js.min.js +++ b/dist/rxp-js.min.js @@ -4,4 +4,4 @@ * Licensed MIT */ -Element.prototype.remove=function(){this.parentElement.removeChild(this)},NodeList.prototype.remove=HTMLCollection.prototype.remove=function(){for(var e=this.length-1;0<=e;e--)this[e]&&this[e].parentElement&&this[e].parentElement.removeChild(this[e])};var RealexHpp=function(){"use strict";var h,d,n,i,A,C="https://pay.realexpayments.com/pay",E=E||Math.random().toString(16).substr(2,8),e=/Windows Phone|IEMobile/.test(navigator.userAgent),t=/Android|iPad|iPhone|iPod/.test(navigator.userAgent),o=(0>2,o=(3&A)<<4|(n=e.charCodeAt(c++))>>4,r=(15&n)<<2|(d=e.charCodeAt(c++))>>6,s=63&d,isNaN(n)?r=s=64:isNaN(d)&&(s=64),a=a+t.charAt(i)+t.charAt(o)+t.charAt(r)+t.charAt(s),A=n=d="",i=o=r=s="",cAll set!'); + //success + success(answer); + } + else{ + //error + $('.paymentResult').html('
'+answer.MESSAGE+'
'); + //would you retry? This part should be handled at the rxp side, stay in the modal/iframe... TODO + } + }, + jsonFromServerSdk //form data + ); + $('body').addClass('loaded'); + }); + }); + + + + + + From 8d97d9c93ab724fdda5448634b6cce440a581c08 Mon Sep 17 00:00:00 2001 From: Shane Logsdon Date: Mon, 29 Mar 2021 09:25:10 -0400 Subject: [PATCH 7/7] capture hpp error response messages --- dist/rxp-js.js | 23 ++++++++++++++++------- dist/rxp-js.min.js | 2 +- lib/rxp-hpp.js | 23 ++++++++++++++++------- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/dist/rxp-js.js b/dist/rxp-js.js index 6307609..54f0f1e 100644 --- a/dist/rxp-js.js +++ b/dist/rxp-js.js @@ -108,7 +108,7 @@ var RealexHpp = (function () { // remove all characters that are not A-Z, a-z, 0-9, +, /, or = var base64test = /[^A-Za-z0-9\+\/\=]/g; if (base64test.exec(input)) { - alert("There were invalid base64 characters in the input text.\n" + + throw new Error("There were invalid base64 characters in the input text.\n" + "Valid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\n" + "Expect errors in decoding."); } @@ -143,12 +143,21 @@ var RealexHpp = (function () { }, decodeAnswer:function(answer){ //internal.decodeAnswer - var _r=JSON.parse(answer); - for(var r in _r){ - if(_r[r]) { - _r[r]=internal.base64.decode(_r[r]); - } + var _r; + + try { + _r=JSON.parse(answer); + } catch (e) { + _r = { error: true, message: answer }; } + + try { + for(var r in _r){ + if(_r[r]) { + _r[r]=internal.base64.decode(_r[r]); + } + } + } catch (e) { /** */ } return _r; }, createFormHiddenInput: function (name, value) { @@ -419,7 +428,7 @@ var RealexHpp = (function () { } // check for iframe resize values var evtdata; - if (event.data && (evtdata=JSON.parse(event.data)).iframe) { + if (event.data && (evtdata=internal.decodeAnswer(event.data)).iframe) { if (!isMobileNewTab()) { var iframeWidth = evtdata.iframe.width; var iframeHeight = evtdata.iframe.height; diff --git a/dist/rxp-js.min.js b/dist/rxp-js.min.js index a844598..c048d82 100644 --- a/dist/rxp-js.min.js +++ b/dist/rxp-js.min.js @@ -4,4 +4,4 @@ * Licensed MIT */ -Element.prototype.remove=function(){this.parentElement.removeChild(this)},NodeList.prototype.remove=HTMLCollection.prototype.remove=function(){for(var e=this.length-1;0<=e;e--)this[e]&&this[e].parentElement&&this[e].parentElement.removeChild(this[e])};var RealexHpp=function(){"use strict";var h,d,n,i,A,C="https://pay.realexpayments.com/pay",E=E||Math.random().toString(16).substr(2,8),t=360,e=/Windows Phone|IEMobile/.test(navigator.userAgent),o=/Android|iPad|iPhone|iPod/.test(navigator.userAgent),u=e,m=function(){return!e&&(o||(0>2,o=(3&A)<<4|(n=e.charCodeAt(c++))>>4,r=(15&n)<<2|(d=e.charCodeAt(c++))>>6,s=63&d,isNaN(n)?r=s=64:isNaN(d)&&(s=64),a=a+t.charAt(i)+t.charAt(o)+t.charAt(r)+t.charAt(s),A=n=d="",i=o=r=s="",c