Nella realizzazione di applicazioni web B2C per la vendita di prodotti e servizi verso gli utenti finali è sempre necessario integrare uno o più sistemi di pagamento.
Esistono molti sistemi di pagamento online ed Instant Developer Cloud ne ha già integrato uno tra i suoi componenti del framework. Si tratta di StripePackage, che permette di utilizzare stripe.com, la ben nota infrastruttura di pagamenti per Internet.
E se ti occorre un altro sistema? Se nella tua applicazione devi integrare PayPal, come puoi aggiungerlo?
In questo articolo vedrai come implementare il pagamento mediante i classici pulsanti di PayPal all’interno di una videata di un’applicazione web realizzata con Instant Developer Cloud.
Le istruzioni per comporre il wrapper tra gli script dell’SDK di PayPal ed Instant Developer sono comunque valide, con poche modifiche, anche per essere utilizzate in Instant Developer Foundation.
La difficoltà maggiore per integrare PayPal o altri componenti di terze parti simili in un software è spesso rappresentata dal reperire le informazioni nella documentazione di riferimento del sistema da integrare.
Questa volta lo sforzo di recupero delle spiegazioni lo abbiamo fatto noi di Instant Developer. A te non resta che seguire le indicazioni di questo articolo per portare a compimento l’integrazione del pagamento con PayPal.
Seguendo i passi descritti di seguito sarai infatti in grado di implementare, in modo semplice, il pagamento con PayPal nella tua applicazione web.
In fondo all’articolo trovi il link al progetto di esempio che ho realizzato per te.
Come preparare quello che serve su PayPal
PayPal mette a disposizione Paypal Sandbox, un ambiente di test con le stesse funzionalità del vero sito PayPal che può essere utilizzato per testare le applicazioni con account fittizi senza coinvolgere utenti e conti correnti reali.
Per poter utilizzare il servizio è necessario registrarsi come sviluppatore PayPal all’indirizzo developer.paypal.com.
Una volta eseguito il login con il proprio account, viene visualizzata la dashboard di gestione dell’account sviluppatore e qui occorre cliccare sul pulsante Apps & Credentials, dove sono gestiti i dati di autenticazione al sistema di test di PayPal.
Viene presentata la videata di gestione delle credenziali di autenticazione delle API e per crearne una nuova si deve cliccare il pulsante Create App.
Le credenziali per fare i test si compongono di un Client ID e di una Secret Key, che serviranno per testare i pagamenti di prova dall’ambiente PayPal Sandbox. Per andare online nell’ambiente di produzione occorre un account business di PayPal.
Cliccando sul pulsante Create App viene presentata la videata di inserimento dei dati.
Si deve impostare un nome per l’applicazione che utilizzerà queste credenziali, selezionare Merchant per indicare che l’utilizzo è per ricevere pagamenti come commerciante, selezionare l’account di Sandbox da utilizzare ed infine cliccare sul pulsante Create App.
Alla conferma viene creato un accesso all’API e visualizzata la videata di dettaglio delle credenziali dalla quale è possibile copiare il Client ID e la Secret Key da utilizzare poi nell’applicazione.
Nella videata sono presenti molti altri parametri che possono essere lasciati al valore di default. È sempre possibile tornare a modificare questi parametri in un secondo momento.
Come preparare il backend
Nel progetto di Instant Developer Cloud si deve aggiungere una classe apposita per gestire i pulsanti di pagamento di PayPal, che può essere posizionata nella libreria dei documenti dell’applicazione, oppure è possibile crearne una specifica.
Nel nostro caso si procede creando la libreria PayPalLib che deve essere di tipo Applicazione ed al suo interno si crea la classe PayPal che estende ApplicationLibrary.Element e deve essere di tipo Elemento. Questo oggetto verrà utilizzato nelle videate dell’applicazione per visualizzare i pulsanti di pagamento di PayPal.
Alla classe PayPal va aggiunta una risorsa di tipo Script client con nome sdk nella cui proprietà Url va indicato:
https://www.paypal.com/sdk/js?client-id=client-id-account-paypal¤cy=EUR
La costante client-id-account-paypal va sostituita con il Client ID recuperato dalle credenziali API su PayPal.
Se non si desidera impostare in modo fisso il Client ID è possibile utilizzare una variabile impostando la proprietà Url come indicato di seguito:
https://www.paypal.com/sdk/js?client-id=$clientIDPayPal¤cy=EUR
Questa seconda modalità visualizzerà un errore nella risorsa ma questo non rappresenta un problema perché verrà impostata in seguito nell’applicazione in modo che a run time tutto funzioni correttamente. Le variabili impostate in questo modo fanno riferimento ad app.theme dell’applicazione.
Per fare in modo che la risorsa venga caricata anche dentro l’editor delle videate e quindi che si vedano i pulsanti di pagamento PayPal, è possibile aggiungere una variabile di tema. Questa sarà posizionata fuori dalla libreria PayPalLib, che in questo modo può andare a far parte di un componente senza che esso contenga il Client ID. La proprietà di tema deve avere lo stesso nome indicato nella risorsa, escluso il carattere $ (quindi clientIDPayPal) e deve essere statica.
Quindi si aggiunge una seconda risorsa di tipo Script client con nome elementScript, dove sarà posizionato il codice che crea gli oggetti del DOM di Instant Developer Cloud e di interfaccia agli eventi e alle proprietà necessarie al funzionamento dell’elemento.
In questa risorsa sarà presente il codice che implementa il costruttore, quello che definisce le proprietà, quello che implementa la chiamata alla parte back office dell’applicazione e che poi scrive l’ordine su PayPal ed infine quello che mappa i metodi necessari a PayPal su Instant Developer Cloud.
Si devono aggiungere alla classe le seguenti proprietà:
- urlServerResponse di tipo String – indica l’url dell’applicazione lato server che PayPal richiamerà per processare il pagamento,
- referenceID di tipo String – indica l’id di riferimento dell’ordine che verrà pagato tramite PayPal.
Le proprietà della classe PayPal devono avere il flag Design time impostato a true.
Devono quindi essere aggiunti i seguenti eventi:
- onComplete con il parametro data di tipo Object – notificato quando il pagamento è completato con successo,
- onCancel con il parametro data di tipo Object – notificato quando il pagamento viene annullato dall’utente,
- onError con il parametro err di tipo Object – notificato quando il pagamento va in errore.
Per aggiungere un evento occorre utilizzare il comando Aggiungi Metodo e poi modificarne il tipo in Evento. Gli eventi devono avere la proprietà Design time impostata a true.
Da dove si parte per scrivere il codice della risorsa elementScript? Per prima cosa si può consultare il manuale Integrazione di componenti esterni della documentazione di Instant Developer Cloud al capitolo Integrazione di componenti JavaScript.
Nella risorsa saranno presenti queste sezioni:
- il costruttore della risorsa
- il metodo che aggiorna le proprietà della risorsa
- il metodo che carica gli elementi necessari
- il metodo che si occupa di implementare gli eventi onComplete, onCancel e onError
Vediamo come ognuno di questi è implementato.
Il codice che segue è il costruttore del componente PayPal che prepara il div con id paypal-button-container e lo attacca al DOM nella videata che conterrà i pulsanti di pagamento.
Client.PayPal = function (element, parent, view)
{
// Call base class
Client.Element.call(this, element, parent, view);
//
// Every element must have a dom element stored in this property
this.domObj = document.createElement("div");
this.paypalDiv = document.createElement("div");
this.paypalDiv.id = "paypal-button-container";
this.domObj.appendChild(this.paypalDiv);
//
// Copy property from design time values
this.updateElement(element);
//
// Attach server events
this.attachEvents(element.events);
//
// Put the DOM object in the document
parent.appendChildObject(this, this.domObj);
//
this.loadPayPal();
};
Poi si estende la classe base.
// Extend base class
Client.PayPal.prototype = new Client.Element();
Nel codice seguente è presente la funzione che imposta le proprietà dell’elemento PayPal.
Client.PayPal.prototype.updateElement = function (el)
{
// Remove bounced properties (for telecollaboration)
this.purgeMyProp(el);
//
// update specific properties
if (el.urlServerResponse !== undefined) {
if (typeof el.urlServerResponse === "string") {
this.urlServerResponse = el.urlServerResponse;
}
//this.updatePayPal();
delete el.urlServerResponse;
}
if (el.referenceID !== undefined) {
if (typeof el.referenceID === "number") {
this.referenceID = el.referenceID;
}
//this.updatePayPal();
delete el.referenceID;
}
//
// Call base class
Client.Element.prototype.updateElement.call(this, el);
};
Quindi si definisce il codice che prepara i pulsanti di PayPal a video.
In particolare, è gestita la chiamata al back end dell’applicazione per la creazione dell’ordine su PayPal (createOrder) per avere i dati dell’ordine da pagare con importo e valuta. La variabile pthis.urlServerResponse viene valorizzata con la proprietà urlServerResponse definita sulla classe PayPal.
Poi è presente il codice che gestisce l’approvazione del pagamento dell’ordine (onApprove) che in caso di operazione completata con successo richiama l’evento onComplete dal lato del componente di Instant Developer Cloud.
In caso di annullamento delle operazioni di pagamento (onCancel) viene richiamato l’evento onCancel dal lato del componente Instant Developer Cloud.
Client.PayPal.prototype.loadPayPal = function () {
let pthis = this;
//let appUrl = pthis.urlServerResponse;
// Render the PayPal button into #paypal-button-container
paypal.Buttons({
// Call your server to set up the transaction
createOrder: function (data, actions) {
console.log("data", data);
console.log("actions", actions);
//
return fetch(pthis.urlServerResponse + "?mode=rest&cmd=createOrder&paymentsource=" + data.paymentSource + "&referenceID=" + pthis.referenceID, {
method: 'post'
}).then(function (res) {
console.log(res);
return res.json();
}).then(function (orderData) {
return orderData.id;
});
},
// Call your server to finalize the transaction
onApprove: function (data, actions) {
console.log("data", data);
console.log("actions", actions);
return fetch(pthis.urlServerResponse + "?mode=rest&cmd=onApprove&id=" + data.orderID, {
method: 'post'
}).then(function (res) {
console.log("res", res);
return res.json();
}).then(function (orderData) {
var errorDetail = Array.isArray(orderData.details) && orderData.details[0];
//
if (errorDetail && errorDetail.issue === 'INSTRUMENT_DECLINED') {
console.log("Error: annullato");
return actions.restart(); // Recoverable state
}
if (errorDetail) {
var msg = 'Sorry, your transaction could not be processed.';
if (errorDetail.description) msg += '\n\n' + errorDetail.description;
if (orderData.debug_id) msg += ' (' + orderData.debug_id + ')';
console.log("Error msg", msg);
pthis.onError(msg);
return;
}
// Pagamento confermato
pthis.onComplete(orderData);
}.bind(this));
},
onCancel: function (data) {
console.log("data", data);
pthis.onCancel(data);
},
onError: function (err) {
console.log("err", err);
pthis.onError(err);
}
}).render('#paypal-button-container');
};
Il codice precedente si basa sull’esempio disponibile nella documentazione di PayPal a questo link https://developer.paypal.com/demo/checkout/#/pattern/server
Infine, questo è il codice che invia gli eventi al framework di Instant Developer Cloud:
Client.PayPal.prototype.onComplete = function (data)
{
let e = [{obj: this.id, id: "onComplete", content: data}];
Client.mainFrame.sendEvents(e);
};
Client.PayPal.prototype.onCancel = function (data)
{
let e = [{obj: this.id, id: "onCancel", content: data}];
Client.mainFrame.sendEvents(e);
};
Client.PayPal.prototype.onError = function (err)
{
let e = [{obj: this.id, id: "onError", content: err}];
Client.mainFrame.sendEvents(e);
};
Come preparare l’applicazione
Nell’applicazione, presente all’interno del progetto di Instant Developer Cloud, occorre aggiungere una videata per realizzare il pagamento che visualizzi i pulsanti di PayPal definiti nel back end. Inoltre, è necessario implementare l’evento onCommand dell’applicazione per rispondere alle chiamate REST di PayPal.
Per prima cosa è necessario aggiungere le seguenti proprietà statiche dell’applicazione:
- clientIDPayPal – contiene il Client ID di PayPal
- secretKeyPayPal – contiene la Secret Key di PayPal
- urlCheckoutPayPal – contiene l’url dove effettuare il check out del pagamento su PayPal (se nella sandbox o in effettivo)
- urlServerResponsePayPal – contiene l’url dell’applicazione che risponde alle chiamate REST di PayPal
Queste variabili possono essere impostate in un apposito metodo da richiamare nell’evento onStart dell’applicazione.
All’applicazione dovrà essere applicato il tema dove è stata posizionata la proprietà clientIDPayPal, quella referenziata nella risorsa sdk, se si è optato per non mettere il Client ID di PayPal direttamente nella risorsa.
Per impostare la proprietà urlCheckoutPayPal in modo che funzioni sia in ambiente di sviluppo (nell’IDE) che in produzione, si può utilizzare questo codice:
App.urlCheckoutPayPal = app.inPreview() ? "https://api-m.sandbox.paypal.com/v2/checkout/orders" : "https://api-m.paypal.com/v2/checkout/orders";
All’interno dell’IDE, quando l’applicazione va in esecuzione ha un url particolare che cambia ad ogni sessione e quindi per impostare la proprietà urlServerResponsePayPal si può utilizzare questo codice:
if (app.inPreview()) {
// Ad ogni avvio dell'IDE il sessionid è diverso e quindi lo devo recuperare in maniera dinamica
// Recupero l'indirizzo di esecuzione dell'applicazione sul server IDE e preparo l'url del server che risponde a PayPal
let urlParts = app.requestConnectionUrl().split("/");
let url = urlParts[0] + "//" + urlParts[2] + "/" + urlParts[6] + "/" + urlParts[7] + "/run";
App.urlServerResponsePayPal = url;
}
else {
// url dell'applicazione web che gestisce la risposta sul server di produzione
App.urlServerResponsePayPal = "IMPOSTARE URL DELL'APPLICAZIONE IN PRODUZIONE";
}
Di seguito il codice dell’evento onCommand, che viene notificato quando l’applicazione viene richiamata in modalità REST (lo fa PayPal utilizzando l’url impostato nella proprietà urlServerResponse):
console.log("PG - onCommand", request);
//
// Remove unexpected uploaded files
request.files.forEach(f => f.remove());
app.settingPayPal();
//
if (request.query.cmd === "createOrder") {
console.log("PG - request.query.referenceID", request.query.referenceID);
let referenceID = request.query.referenceID;
let order = yield App.NwindLib.Order.loadByKey(app, referenceID);
let importo = yield order.calcolaImporto();
//console.log("createOrder sendResponse");
//
// Creo l'ordine su Paypal
let url = app.fs.url(App.urlCheckoutPayPal);
let auth = App.clientIDPayPal + ":" + App.secretKeyPayPal;
let headers = {"Authorization" : "Basic " + App.Utils.toBase64(app, auth), "Content-Type" : "application/json"};
let body = {};
body.purchase_units = [
{
"reference_id" : referenceID,
"amount" : {
"currency_code" : "EUR",
"value" : importo
}
}
];
body.intent = "CAPTURE";
let res = yield url.post({headers : headers, body : body});
console.log("PG - res", res);
//console.log("res", JSON.parse(res.body));
//
if (res.status < 400) {
let response = JSON.parse(res.body);
let idPayPal = response.id;
let pag = yield App.NwindLib.Payment.loadByKey(app, {PayPalID : idPayPal});
if (!pag) {
pag = new App.NwindLib.Payment(app);
pag.inserted = true;
pag.OrderID = referenceID;
pag.PayPalID = response.id;
pag.Importo = importo;
pag.TipoPagamento = request.query.paymentsource;
}
yield pag.save();
app.sendResponse(200, {id : response.id});
}
else
app.sendResponse(500, "Error");
}
else if (request.query.cmd === "onApprove") {
console.log("onApprove", request.query.id);
console.log("PG - url", App.urlCheckoutPayPal + request.query.id + "/capture");
let url = app.fs.url(App.urlCheckoutPayPal + "/" + request.query.id + "/capture");
let auth = App.clientIDPayPal + ":" + App.secretKeyPayPal;
let headers = {"Authorization" : "Basic " + App.Utils.toBase64(app, auth), "Content-Type" : "application/json"};
let res = yield url.post({headers : headers});
console.log("res", JSON.parse(res.body));
//
if (res.status < 400) {
let response = JSON.parse(res.body);
let pag = yield App.NwindLib.Payment.loadByKey(app, {PayPalID : response.id});
if (pag) {
pag.Completed = true;
yield pag.save();
app.sendResponse(200, {details : "OK"});
}
else
app.sendResponse(500, {error : "Error"});
}
else
app.sendResponse(500, {error : "Error"});
}
In questo evento è disponibile il codice che riceve la richiesta dei dati dell’ordine e crea l’ordine su PayPal (quello dove c’è la costante createOrder).
Dove è gestita la costante onApprove viene eseguita la gestione della conferma del pagamento con passaggio di Client ID e Secret Key nella risposta a PayPal.
Nella videata per effettuare il pagamento viene aggiunto l’elemento PayPal che a video renderizza i pulsanti di pagamento.
Nell’immagine si può notare che sono stati anche aggiunti gli eventi onComplete, onCancel ed onError del componente PayPal.
In questi eventi verranno gestiti la conferma del pagamento, l’annullamento dello stesso e un eventuale manifestarsi di un errore durante il pagamento. Il codice di questi eventi è strettamente legato all’applicazione nella quale si implementa il pagamento.
All’evento onLoad della videata del pagamento verrà anche impostata la proprietà $payPal.urlServerResponse.
Per provare le transazioni di pagamento nell’ambiente sandbox si può fare riferimento ai dati delle carte di credito presenti a questo link:
https://developer.paypal.com/tools/sandbox/card-testing/
Per l’accesso al pagamento PayPal di test nella dashboard del developer (https://developer.paypal.com/dashboard/applications/sandbox) dopo essersi autenticati con il proprio login, è possibile trovare anche i Sandbox Account per fare i test.
L’applicazione in esecuzione visualizza la lista degli ordini nella videata seguente.
Nella lista, per ogni record, sono presenti le informazioni dell’ordine, un pulsante per visualizzare le informazioni del pagamento già effettuato e un pulsante per pagare un ordine.
Da questa lista, cliccando sul pulsante giallo si accede alla videata dei pulsanti di pagamento PayPal e, se si clicca sul pulsante Carta di debito o credito, viene aperta la videata di pagamento corrispondente come mostrato nell’immagine seguente.
A questo punto è possibile procedere al pagamento.
Guarda l’esempio
Puoi trovare un progetto che realizza questa implementazione tra i progetti di esempio di Instant Developer Cloud a questo link .
Puoi anche fare un clone del progetto di esempio e quindi modificarlo per adattarlo alle tue esigenze.
Il progetto di esempio non contiene le chiavi di PayPal (Client ID e Secret Key). Per effettuare le prove dei pagamenti, dovrai quindi immettere le tue chiavi dopo averle recuperate dal tuo account PayPal.
Nel progetto è presente il file README-FIRST, con le istruzioni su come impostare i parametri per poterlo mandare in esecuzione e provare i pagamenti dopo avere clonato il progetto nel tuo account di Instant Developer Cloud.