Appery.io + Paypal



  1. Database has only one custom collection Payments. Collection Payments has following columns: paymentId - Paypal id of payment (unique identifier of payment generated by Paypal); status - payment, status can be CREATED, COMPLETED, CANCELED and other; meaning of columns currency and total is clear
  2. Create EncodeBase64_library Server Code library


function encodeBase64(input) {
 var keyStr = 'ABCDEFGHIJKLMNOP' + 'QRSTUVWXYZabcdef' + 'ghijklmnopqrstuv' + 'wxyz0123456789+/' + '=';
 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;
}


  1. Create PayPal_Config Server Code library which depends from EncodeBase64_library


Apperyio.Paypal = {};
Apperyio.Paypal.Config = {
 
 IS_PRODUCTION: false,
 PAYPAL_URL: this.IS_PRODUCTION ? "https://api.paypal.com" : "https://api.sandbox.paypal.com",
 
 CLIENT_ID: "AXuNiHVRvV8tCWgFMeM2snD2rNQW63v3sPcue7olajTC_5hwYmmPBnKbskzDvfsoILg-M_hr7-MjL2WC",
 SECRET: "EPkYjYasjyfqvEvZWqQtjeL8b0_3YRjMOezlZ3MJb4dKDEp4pwcB9J0-tSXUD-Pejo-LC8J6PQRjxawl",
 
 DATABASE : {
  DATABASE_ID: "583d6a8de4b0d0eda8dd997a",
  MASTER_KEY: 'ced4ce13-cdc8-488f-a53b-a3fd8d45e987',
  COLLECTION_NAME: 'Payments'
 },
 
 APP : {
  CANCEL_URL: "https://appery.io/app/view/b846e238-1433-47c9-9ab4-51d76feeb133/#/PayPal_Home.html",
  RETURN_URL: "https://appery.io/app/view/b846e238-1433-47c9-9ab4-51d76feeb133/#/PayPal_Home.html",
  EXECUTE_URL: "https://api.appery.io/rest/1/code/d3a582b9-7bcc-4b8a-8af9-3619c2699cb1/exec"
 }
};


Apperyio.Paypal.Config.AUTHORIZATION_HEADER = 'Basic ' + encodeBase64(Apperyio.Paypal.Config.CLIENT_ID + ":" + Apperyio.Paypal.Config.SECRET);


  1. Create PayPal_Library Server Code library which depends from PayPal_Config


Apperyio.Paypal.getToken = function() {
 console.log(Apperyio.Paypal.Config.AUTHORIZATION_HEADER);
 console.log(Apperyio.Paypal.Config.PAYPAL_URL);
 var XHRResponse = Apperyio.XHR2.send("POST", Apperyio.Paypal.Config.PAYPAL_URL + "/v1/oauth2/token", {
   "headers": {
     'Authorization': Apperyio.Paypal.Config.AUTHORIZATION_HEADER,
     'Accept': 'application/json',
     'Content-Type': 'application/x-www-form-urlencoded'
   },
   'body': 'grant_type=client_credentials'
 });
 
 var status = XHRResponse.status;
 if (status >= 200 && status < 300 && XHRResponse.body.access_token) {
   return XHRResponse.body.access_token;
 } else {
   throw new Error("Couldn't get token from PayPal server. XHR status: " + status);
 }
};


Apperyio.Paypal.createPayment = function(currency, total, description) {
 var payment = {
   "transactions": [{
     "amount": {
       "currency": currency,
       "total": total
     },
     "description": description
   }],
   "payer": {
     "payment_method": "paypal"
   },
   "intent": "sale",
   "redirect_urls": {
     "cancel_url": Apperyio.Paypal.Config.APP.CANCEL_URL,
     "return_url": Apperyio.Paypal.Config.APP.EXECUTE_URL
   }
 };
 
 var XHRResponse = Apperyio.XHR2.send("POST", Apperyio.Paypal.Config.PAYPAL_URL + "/v1/payments/payment", {
   "headers": {
     'Authorization': 'Bearer ' + this.getToken(),
     'Content-Type': 'application/json',
   },
   'body': payment
 });
 
 var status = XHRResponse.status;
 if (status >= 200 && status < 300) {
   var payment = {
     "paymentId": XHRResponse.body.id
   };
   console.log("Payment created: " + payment.paymentId);
   for (var i = 0; i < XHRResponse.body.links.length; i++) {
     if (XHRResponse.body.links[i].rel == "approval_url") {
       payment.approvalUrl = XHRResponse.body.links[i].href;
       return payment;
     }
   }
   throw new Error("Couldn't find approval_url in XHRResponse");
 } else {
   var errorMessage = "Couldn't create payment. XHR status: " + status;
   console.log(errorMessage);
   throw new Error(errorMessage);
 }
};


Apperyio.Paypal.executePayment = function(paymentId, payerID) {
 var XHRResponse = Apperyio.XHR2.send("POST", Apperyio.Paypal.Config.PAYPAL_URL + "/v1/payments/payment/" + paymentId + "/execute", {
   "headers": {
     'Authorization': 'Bearer ' + Apperyio.Paypal.getToken(),
     'Content-Type': 'application/json',
   },
   'body': {
     "payer_id": payerID
   }
 });
 
 var status = XHRResponse.status;
 if (status < 200 || status >= 300) {
   var errorMessage = "Couldn't execute payment. XHR status: " + status;
   console.log(errorMessage);
   throw new Error(errorMessage);
 }
};


  1. Create PayPal_createPayment Server code script which depends from PayPal_Library and PayPal_Config library


try {
 var requestBody = request.object();
 var payment = Apperyio.Paypal.createPayment(requestBody.currency, requestBody.total, requestBody.description);
 
 Collection.createObject(Apperyio.Paypal.Config.DATABASE.DATABASE_ID, Apperyio.Paypal.Config.DATABASE.COLLECTION_NAME, {
   "paymentId": payment.paymentId,
   "status": "CREATED",
   "currency": requestBody.currency,
   "total": requestBody.total
 }, Apperyio.Paypal.Config.DATABASE.MASTER_KEY);
 
 response.success({'approval_url': payment.approvalUrl}, 'application/json');
} catch (e) {
 console.log(e);
 response.error(e.message, 400);
}


  1. Create PayPal_executePayment Server code script which depends from PayPal_Library and PayPal_Config


try {
 var paymentId = request.get("paymentId");
 var payerID = request.get("PayerID");
 
 var collection = Collection.query(Apperyio.Paypal.Config.DATABASE.DATABASE_ID, Apperyio.Paypal.Config.DATABASE.COLLECTION_NAME, {
   "criteria": {
     "paymentId": paymentId
   }
 }, Apperyio.Paypal.Config.DATABASE.MASTER_KEY);
 
 // Filtered collection should contain only one object according to criteria
 if (collection.length != 1) {
   console.log("Count of payments in payments collection with paymentId: " + paymentId + " should be 1 but actual count is " + collection.length);
   response.error("Can't process request", 400);
 }
 if (collection[0].status != "CREATED") {
   console.log("Status of payment with paymentId " + paymentId + " should be CREATED but actual status is " + collection[0].status);
   response.error("Can't process request", 400);
 }
 
 var _id = collection[0]._id;
 
 Apperyio.Paypal.executePayment(paymentId, payerID);
 
 Collection.updateObject(Apperyio.Paypal.Config.DATABASE.DATABASE_ID, Apperyio.Paypal.Config.DATABASE.COLLECTION_NAME, _id, {
   "status": "COMPLETED"
 }, Apperyio.Paypal.Config.DATABASE.MASTER_KEY);
 console.log("Success execution payment");
 response.redirect(Apperyio.Paypal.Config.APP.RETURN_URL);
 
} catch (e) {
 console.log(e);
 response.error(e.message, 400);
}


  1. Server code scripts and libraries are depended in following way




  1. User needs to provide information about Paypal, Appery Database and Appery server code scripts in PayPal_Config library
  2. Then to specify correct url of PayPal_createPayment_service in App
  3. And of course to configure Paypal app
  4. App works by following algorithm:
    1. On the first page user press Make payment button
    1. Make payment button invokes Paypal_createPayment server code script. This script creates Paypal payment (invoke correspondent rest call to Paypal) and saves new record in Payments collection with status CREATED
    2. Paypal_createPayment returns to user Paypal approval_url
    3. App to redirect user on this approval_url
    4. On Paypal site user logins into Paypal (or select payment by providing information of credit card without login)


    1. After login user verify payment information and confirm payment (or decline) (press Continue button)
    1. After user’s confirmation Paypal invokes Paypal_executePayment server code script which verifies opportunity to perform user order and if everything is ok then invokes execute payment Paypal rest service and if money successfully charged from user’s card then to change Payment status on COMPLETED and returns http response with 308 http code (redirect) which can redirect user on confirmation screen for example. If Paypal can’t charge money or app can’t perform user’s order (for example there are not some items in store) status of payment can be changed on CANCELED and user can be redirected on corresponding cancel screen.

Comments