Solved! Go to Solution
Hello,
We are looking into the below issue and will keep you posted soon.
Thanks,
Jai
Hi,
Since this involves a lot of back and forth troubleshooting, please reach out to Visa Checkout team @ developer-support@mail.digital.visa.com directly.
Thanks,
Jai
Since no solution has been posted to this request, I wanted to post the following approach:
JavaScript code can be loaded to your Angular component view dynamically. This is important because you will likely want to include multiple payment methods in the same Checkout page. With this in mind, the idea is to load the VISA Checkout JavaScript code only when VISA Checkout is selected by the consumer as the payment method.
Create a loader service[1] (e.g. dynamic-script-loader.service.ts) as the one below:
import { Injectable } from '@angular/core';
interface Scripts {
name: string;
src: string;
DOMElement: string;
}
export const ScriptStore: Scripts[] = [
/*
For production use, the path to the VISA JavaScript Library (sdk.js) is as follows:
{ name: 'visa', src: 'https://assets.secure.checkout.visa.com/checkout-widget/resources/js/integration/v1/sdk.js'},
*/
{ name: 'athmovil', src: 'https://www.athmovil.com/api/js/athmovil.js', DOMElement: 'head'},
{ name: 'initVISA', src: '../assets/js/initVISA.js', DOMElement: 'head'},
{ name: 'visa', src: 'https://sandbox-assets.secure.checkout.visa.com/checkout-widget/resources/js/integration/v1/sdk.js', DOMElement: 'body'},
{ name: 'initGPay', src: '../assets/js/initGPay.js', DOMElement: 'head'},
{ name: 'hostedGPay', src: 'https://pay.google.com/gp/p/js/pay.js', DOMElement: 'head'},
{ name: 'initGPayClient', src: '../assets/js/initGPayPaymentsClient.js', DOMElement: 'head'}
];
declare var document: any;
@Injectable()
export class DynamicScriptLoaderService {
public onLoadObject: any;
private scripts: any = {};
constructor() {
ScriptStore.forEach((script: any) => {
this.scripts[script.name] = {
loaded: false,
src: script.src,
DOMElement: script.DOMElement
};
});
}
load(...scripts: string[]) {
const promises: any[] = [];
scripts.forEach((script) => promises.push(this.loadScript(script)));
return Promise.all(promises);
}
loadScript(name: string) {
return new Promise((resolve, reject) => {
if (!this.scripts[name].loaded) {
//load script
let script = document.createElement('script');
script.type = 'text/javascript';
script.src=this.scripts[name].src;
if (script.readyState) { //IE
script.onreadystatechange = () => {
if (script.readyState === "loaded" || script.readyState === "complete") {
script.onreadystatechange = null;
this.scripts[name].loaded = true;
resolve({script: name, loaded: true, status: 'Loaded'});
}
};
} else { //Others
script.onload = () => {
this.scripts[name].loaded = true;
resolve({script: name, loaded: true, status: 'Loaded'});
};
}
script.onerror = (error: any) => resolve({script: name, loaded: false, status: 'Loaded'});
document.getElementsByTagName(this.scripts[name].DOMElement)[0].appendChild(script);
} else {
resolve({ script: name, loaded: true, status: 'Already Loaded' });
}
});
}
}
I have included a DOMElement parameter in order to control where the target script is inserted in the DOM. Remember that VISA Checkout instructs you to insert the onVisaCheckoutReady JavaScript function inside the <head> element of your Checkout page while the skd.js should be inserted at the end of the body.
In your Checkout component (e.g., checkout.component.ts), import your service:
Example:
import { DynamicScriptLoaderService } from '../dynamic-script-loader.service';
Include the service in the constructor:
constructor( private dynamicScriptLoader: DynamicScriptLoaderService ) {}
In order to have the flexibility of loading different JavaScript code depending on the payment method selected by the consumer, create a script-loading method in our Checkout component like the one below. The selected method will dependent on a “methodKey”.
private loadPaymentMethodScript(methodKey) {
// You can load multiple scripts by just providing the key as argument into load method of the service
this.dynamicScriptLoader.load(methodKey).then(data => {
// Script Loaded Successfully
})
.catch(
error => {
this.scriptLoadError = true;
console.log('ERROR while loading '+methodKey);
console.log(error)
}
);
}
I use a credit an array of card objects to keep track of the selection and loading status of different payment methods:
class creditCard {
id: string;
selectStatus: boolean = false;
scriptsLoaded: boolean = false;
}
initpaymentMethod() {
this.paymentMethod = new Array<creditCard>();
for (var i=0; i <= 3 ;i++) {
var card = new creditCard;
this.paymentMethod.push(card);
}
this.paymentMethod[0].id = "VISA";
this.paymentMethod[1].id = "ATHMovil";
this.paymentMethod[2].id = "AMEX";
this.paymentMethod[3].id = "GooglePay";
}
I call the above method from ngOnInit.
ngOnInit() {
this.initpaymentMethod();
}
You should build your own logic between your checkout component code and the associated html template in order to tell your component which of the payment methods was selected by the consumer. This should be done BEFORE ATTEMPTING TO LOAD any JavaScript code from either Visa Checkout or any other of the available methods offered by you. You can build the component property that stores this information any way you want. In my case I decided to store it in the object array that I mention above.
Once the payment method is selected, you allow your consumer to use a button that invokes the script loader:
<button (click) = proceedWithPmtMethod()>OK</button>
proceedWithPmtMethod(){
this.scriptLoadError = false;
let selectedPaymentMethod = this.getSelectedPaymentMethod();
switch (selectedPaymentMethod) {
case 'VISA':
console.log('Loading visa ...')
this.loadPaymentMethodScript('initVISA');
this.loadPaymentMethodScript('visa');
if (!this.scriptLoadError) { this.setLoadedScriptStatus(selectedPaymentMethod) }
break;
case "GooglePay":
this.loadPaymentMethodScript('initGPay');
this.loadPaymentMethodScript('hostedGPay');
if (!this.scriptLoadError) { this.setLoadedScriptStatus(selectedPaymentMethod) }
break;
case "ATHMovil" :
this.loadPaymentMethodScript('athmovil');
if (!this.scriptLoadError) { this.setLoadedScriptStatus(selectedPaymentMethod) }
break;
}
this.scriptLoadError = false;
}
getSelectedPaymentMethod(): string {
let rvalue = 'none';
let selectedCount = 0;
let currentID;
for (var i = 0; i < this.paymentMethod.length ; i++) {
if (this.paymentMethod[i].selectStatus) {
selectedCount++;
currentID = this.paymentMethod[i].id;
}
}
if (selectedCount == 1) {
rvalue = currentID;
}
return rvalue;
}
Notice that the id property on every creditCard object holds a key that is used to direct proceedWithPmtMethod in selecting any number of scripts to load. In the case of VISA Checkout, there are two scripts. Remember that those scripts are listed in your loader service (dynamic-script-loader.service.ts) and that the loader picks them from the ScriptStore using the name parameter.
You may pass the payment request object to the onVisaCheckoutReady function by first inserting it in the DOM.
In the following example, a hidden input field is used:
<input type="hidden" id="ffpr_id" value='{{getPR()}}'>
getPR(){
let sPRValue = JSON.stringify(this.VParam);
return sPRValue;
}
In order to test this, include a function right after the onVisaCheckoutReady function that returns the payment request JSON object to the V.init function.
function onVisaCheckoutReady() {
V.init( pr() );
V.on("payment.success", function(payment) {
document.write("payment.success: \n" + JSON.stringify(payment));
});
V.on("payment.cancel", function(payment) {
document.write("payment.cancel: \n" + JSON.stringify(payment));
});
V.on("payment.error", function(payment, error) {
document.write("payment.error: \n" +
JSON.stringify(payment) + "\n" +
JSON.stringify(error));
});
}
function pr() {
let sPRValue = document.getElementById("ffpr_id").value
prvalue = JSON.parse(sPRValue);
return prvalue;
};
I hope this solution helps Angular developers working with VISA Checkout integration. If anyone makes any improvement to this solution, please let us know.
Regards,
Freddie A. García-Nieves
References: