Sunday, November 20, 2016

Lipa Na MPESA Online Checkout - a Java Developer Toolkit & Startup Guide

Introduction 

There is heaps of excitement around digital finance following release of M-PESA API. Mobile payments for C2B online checkout. It's been a year since. As taunted there is expected  developers innovation around this.

I will provide a quick start guide for java developers geared towards integrating existing  Java applications.

Pre-requisites

JDK 7 or latest

Instruction steps


  • Download Full M-PESA API Guide here 
  • Read through Developers Guide C2B_OnlineCheckout_V2.0.doc
  • Generate JAX-WS artifacts . Your client will use this api to access the published  web service. 

Generate JAX-WS artifacts

We will use wsimport tool is used to parse checkout WSDL file and generate required files. 
  • >> mkdir src
  • 
    
    >> wsimport -XadditionalHeaders -clientjar safaricom-lipanampesa-onlinecheckout-wsdl.jar -s src -p ke.co.makara.integration.mpesa.lnmocheckout http://safaricom.co.ke/mpesa_online/lnmo_checkout_server.php?wsdl
    
  • Check wsimport --help to understand above options; 
Note the package directory for generated artefacts. WSDL is hosted on the lipa na M-PESA endpoint. For this purpose we use the production url. -clientjar option, new in Java 7, simplifies things. You would otherwise have to use -wsdllocation /META-INF/wsdl/Checkout.wsdl option. And copy the wsdl in META-INF after to mitigate the Java 6 wsimport limitation. See limitation below.
  • Optionally create source jar
While inside src folder run command. 


>> jar cvf safaricom-lipanampesa-onlinecheckout-source-wsdl.jar ke/


Next we use the web service artifacts to invoke the web service from a web service client.

Generated artifacts

  1. Service Endpoint Interface (SEI) - LNMOPortType.java
  2. Service class - LnmoCheckoutService.java
  3. If a wsdl:fault is present in the WSDL, an Exception class
  4. Java classes mapped from schema types eg ProcessCheckOutRequest.java
  5. If a wsdl:message is present in WSDL, asynchronous response beans eg ProcessCheckOutResponse.java

Web Service Client

Next we use the web service artifacts to invoke the web service from a web service client.
This can be a servlet invoked from front-end. Out of scope. For simplicity I will create a java class ~ a standalone console

  • Create a java class say LNMOCheckoutTester
  • In the Java client application, create an instance of the LnmoCheckoutService service


LnmoCheckoutService lnmoCheckoutService = new LnmoCheckoutService(); // lina na mpesa online checkout instance

  • The Service class will be created during the build.
  • Obtain a proxy to the service from the service using the getLnmoCheckout() method


LNMOPortType soap = lnmoCheckoutService.getLnmoCheckout();

 The port carries the protocol binding and service endpoint address information. 
  • Configure the service endpoint
Configure the request context properties on the javax.xml.ws.BindingProvider interface


((BindingProvider)soap).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, url + "lnmo_checkout_server.php"); // specifying this property on the RequestContext


JAX-WS provides support for the dynamic invocation of service endpoint operations.
I store the web service url in database and append the wsdl endpoint to this url. This configures the endpoint at runtime. 
  • Compose our client request M-PESA checkout message 
This is the message payload


ProcessCheckOutRequest checkOutRequest = objFactory.createProcessCheckOutRequest();
  checkOutRequest.setMERCHANTTRANSACTIONID("54635469064");
  checkOutRequest.setREFERENCEID("TD346534GH");
  checkOutRequest.setAMOUNT(13300);
  checkOutRequest.setMSISDN("0721XXXXXX");
  checkOutRequest.setENCPARAMS("");
  checkOutRequest.setCALLBACKURL("https://makara.co.ke:8443/odt/checkout");
  checkOutRequest.setCALLBACKMETHOD("GET");
  checkOutRequest.setTIMESTAMP(String.valueOf(date.getTime()));

  • Configure request headers
Follow business rules in Safaricom document to build the password. See attached code.
For String merchantId, String passkey, Date requestTimeStamp; Convert the concatenated string to bytes, Hash the bytes to get arbitary binary data and Convert the binary data to string use base64



CheckOutHeader requestHeader = objFactory.createCheckOutHeader();
  requestHeader.setMERCHANTID(MERCHANT_ID);
  Date timestamp = new Date();
  String encryptedPassword = getEncryptedPassword(MERCHANT_ID, PASSKEY, timestamp);
  requestHeader.setPASSWORD(encryptedPassword.toUpperCase());
  requestHeader.setTIMESTAMP(String.valueOf(timestamp.getTime()));


  • Invoke the service endpoint with the dispatch client
soap.processCheckOut(checkOutRequest, requestHeader);
  • Process the response message from the service as per your business requirement


  ProcessCheckOutResponse checkOutResponse = new ProcessCheckOutResponse();
  checkOutResponse.getRETURNCODE();
  checkOutResponse.getDESCRIPTION();
  checkOutResponse.getTRXID();
  checkOutResponse.getCUSTMSG();


  • Tracing SOAP Traffic.
One of the usual steps involved in the debugging of Web Services applications is to inspect the request and response SOAP messages
Configure client to dump requests and response with JAX-WS 


System.setProperty("com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.dump", "true");

  • Updated 22/11/2016 : Source code 
 
/*
 * LNMOCheckoutTester.java
 *
 * Nov 20, 2016 Joseph Makara -  Created File to tester Lina Na M-PESA Online checkout
 *
 *
 */
package testMe;

import java.io.*;
import java.security.*;
import java.util.*;
import javax.net.ssl.*;
import javax.xml.ws.*;
import ke.co.makara.integration.mpesa.lnmocheckout.*;
import org.apache.commons.codec.binary.*;

/**
 * @author Joseph Makara
 *
 */
public class LNMOCheckoutTester {

 private static final String PASSKEY = "234fdsghfsg5654dgfhgf6dsfdsafsd43dgfhdgfdgfh74567";
 private static final String MERCHANT_ID = "678fsgd54354";
 private static final String REFERENCE_ID = "";
 private static final String ENDPOINT_URL = "https://safaricom.co.ke/mpesa_online/";
 private static final String CALLBACK_URL = "https://makara.co.ke:8443/odt/checkout";
 private static final String CALLBACK_METHOD = "GET";


 static {
  HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
   @Override
   public boolean verify(String hostname, SSLSession session) {
    if (hostname.equals("safaricom.co.ke")) return true;
    return false;
   }
  });
 }

 /**
  * @param args
  */
 public static void main(String[] args) {

  LNMOPortType soap = outBoundLNMOCheckout(ENDPOINT_URL);

  ObjectFactory objFactory = new ObjectFactory();

  CheckOutHeader requestHeader = objFactory.createCheckOutHeader();
  requestHeader.setMERCHANTID(MERCHANT_ID);
  Date timestamp = new Date();
  String encryptedPassword = getEncryptedPassword(MERCHANT_ID, PASSKEY, timestamp);
  requestHeader.setPASSWORD(encryptedPassword);
  requestHeader.setTIMESTAMP(String.valueOf(timestamp.getTime()));

  ProcessCheckOutRequest checkOutRequest = objFactory.createProcessCheckOutRequest();
  checkOutRequest = processCheckOut(timestamp);

  soap.processCheckOut(checkOutRequest, requestHeader);

  ProcessCheckOutResponse checkOutResponse = new ProcessCheckOutResponse();
  checkOutResponse.getRETURNCODE();
  checkOutResponse.getDESCRIPTION();
  checkOutResponse.getTRXID();
  checkOutResponse.getENCPARAMS();
  checkOutResponse.getCUSTMSG();
 }

 private static ProcessCheckOutRequest processCheckOut(Date date){

  ProcessCheckOutRequest checkOutRequest = new ProcessCheckOutRequest();
  checkOutRequest.setMERCHANTTRANSACTIONID("54635469064");
  checkOutRequest.setREFERENCEID("TD346534GH");
  checkOutRequest.setAMOUNT(3.45);
  checkOutRequest.setMSISDN("0721826284");
  checkOutRequest.setENCPARAMS("");
  checkOutRequest.setCALLBACKURL(CALLBACK_URL);
  checkOutRequest.setCALLBACKMETHOD(CALLBACK_METHOD);
  checkOutRequest.setTIMESTAMP(String.valueOf(date.getTime()));

  return  checkOutRequest;
 }

 /**
  * Convert the concatenated string to bytes
  * Hash the bytes to get arbitary binary data
  * Convert the binary data to string use base64
  *
  * @param merchantId
  * @param passkey
  * @param date
  * @return
  */
 private static String getEncryptedPassword(String merchantId, String passkey, Date date) {
  String encodedPassword = null;
  StringBuilder builder = new StringBuilder(merchantId)
  .append(passkey)
  .append(date.getTime());

  try {
   String sha256 = getSHA256Hash(builder.toString());
   return new String(Base64.encodeBase64(sha256.getBytes("UTF-8")));;
  } catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) {
   ex.printStackTrace();
  }

  return encodedPassword;
 }

 private static LNMOPortType outBoundLNMOCheckout(String url) {
  System.setProperty("com.sun.xml.internal.ws.transport.http.client.HttpTransportPipe.dump", "true");
  LnmoCheckoutService lnmoCheckoutService = new LnmoCheckoutService();
  LNMOPortType soap = lnmoCheckoutService.getLnmoCheckout();

  ((BindingProvider)soap).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
   url + "lnmo_checkout_server.php");

  return soap;
 }

 private static String getSHA256Hash(String input) throws NoSuchAlgorithmException {
  MessageDigest mDigest = MessageDigest.getInstance("SHA-256");
  byte[] result = mDigest.digest(input.getBytes());
  StringBuffer sb = new StringBuffer();
  for (int i = 0; i < result.length; i++) {
   sb.append(Integer.toString((result[i] & 0xff) + 0x100, 16).substring(1));
  }
  return sb.toString();
 }

}



Sample Soap Request



[HTTP request - https://safaricom.co.ke/mpesa_online/lnmo_checkout_server.php]---
Accept: text/xml, multipart/related
Content-Type: text/xml; charset=utf-8
SOAPAction: ""
User-Agent: JAX-WS RI 2.2.9-b130926.1035 svn-revision#5f6196f2b90e9460065a4c2f4e30e065b245e51e
<?xml version="1.0" ?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
    <S:Header>
        <ns2:CheckOutHeader xmlns:ns2="tns:ns">
            <MERCHANT_ID>F2678987M</MERCHANT_ID>
            <PASSWORD>QQFSOITZB5OJMJTW073/SCWLN5WMDL6LO0FP6DJZ8TQ=</PASSWORD>
            <TIMESTAMP>1479116220855</TIMESTAMP>
        </ns2:CheckOutHeader>
    </S:Header>
    <S:Body>
        <ns2:processCheckOutRequest xmlns:ns2="tns:ns">
            <MERCHANT_TRANSACTION_ID>54635469064</MERCHANT_TRANSACTION_ID>
            <REFERENCE_ID>TD346534GH</REFERENCE_ID>
            <AMOUNT>3.45</AMOUNT>
            <MSISDN>0721826284</MSISDN>
            <ENC_PARAMS></ENC_PARAMS>
            <CALL_BACK_URL>https://makara.co.ke:8443/odt/checkout</CALL_BACK_URL>
            <CALL_BACK_METHOD>lnmo</CALL_BACK_METHOD>
            <TIMESTAMP>1479116220855</TIMESTAMP>
        </ns2:processCheckOutRequest>
    </S:Body>
</S:Envelope>



Attachments

Soap Message Request

source code

client jar

source jar



You will only need to contact Safaricom to get Demo test org details (Merchant ID and PassKey) . No need for VPN setup following the new API

Stock take and Future

  1. There is more Safaricom can do to streamline things, Create proper and complete API Guide ..

This developer guide provides a good head start for any one familiar with web services integration. Instead of ranting about horrible state of things get your hands dirty and do stuff

Community frustrations

Just How Open is Safaricom’s Open API?

Safaricom Integration Nightmare part 2


Java 6 wsimport limitation

The problem with Java 6 wsimport is that the JAX-WS runtime needs to fetch the WSDLs from the endpoint each time a service instance is created, which could incur a network overhead. The WSDL location is saved in the generated artifacts and the JAX-WS runtime fetches the metadata, which is useful if the endpoint policy or the service definition has changed. In the absence of the runtime fetch of the metadata, the clients would need to be regenerated if the endpoint policy or the service definition have changed.

What is new in Java 7 wsimport?

Java 7 supports Java API for XML Web Services (JAX-WS) 2.2.4, which has introduced a new (since JAX-WS 2.2.2) wsimport option called - clientjar as shown in the following sample command:
wsimport -clientjar wsclient.jar
http://example.com/service/hello?WSDL

The -clientjar option fetches the WSDLs and the schemas and packages them with the generated client-side artifacts into a JAR file. By including the generated JAR file in the classpath of the web service client, there is no need to fetch the WSDLs from the endpoint each time a service instance is created, thus saving on network overhead.


Tuesday, April 27, 2010

Alliance Technologies

http://www.at.co.ke

Deploy Alfresco Record Management v3.3 in Existing Tomcat 6 Server


The solution is to implement Alfresco Record Management. 

I will summarize the how to steps on deploying alfresco.war, share.war in Apache Tomcat 6 Server in any platform, Configuring share for Records Management using alfresco mmt to install RM AMPs.

Assumptions
I assumes the following are installed and running properly, if not so, try googling on how to do so
a. Java 1.6 b. MySQL 5.x c. Apache Tomcat 6.x

Steps
1. Get Alfresco Community Edition
Download alfresco-community-war-3.3.tar.gz and unpack the archive. It contains the alfresco.war and share.war web applications that you’ll need to get Alfresco Share up and running.

2. Create db and db user
On Mac/Linux open a terminal/shell, on Windows open the command line. Log on to MySQL with the root user:
> mysql -u root -p
Next, create the database and a user for Alfresco and grant the according rights to the database user:
mysql> create database alfresco default character set utf8;
mysql> create user alfresco identified by 'alfresco';
mysql> grant all on alfresco.* to 'alfresco'@'localhost' identified by 'alfresco' with grant option;
mysql> grant all on alfresco.* to 'alfresco'@'localhost.localdomain' identified by 'alfresco' with grant option;

3. Modifying Tomcat 6 Server
Make sure that your Tomcat installation disposes of the directories /shared/classes and <TOMCAT_HOME>/shared/lib. Tomcat 6 does not get distributed with these directories by default. If they are missing, please create them. Next, open the file /conf/catalina.properties and change the value of shared.loader= to:
shared.loader=${catalina.home}/shared/classes,${catalina.home}/shared/lib/*.jar
That way, Tomcat loads for all web applications any classes/property files and libraries that are located in these directories.
Alfresco does not get shipped with a JDBC driver for MySQL. You can download the current driver from http://dev.mysql.com/downloads/connector/j/5.1.html. Unpack the archive and copy the driver mysql-connector-java-5.1.12-bin.jar (at the time of writing this post this driver was the current one) to /shared/lib.
Copy from your dowloaded unpacked alfresco-community-war-3.2.tar.gz archive the file /extensions/extension/alfresco-global.properties to /shared/classes. Open the file alfresco-global.properties and change the value of db.driver= to (line 29):
db.driver=com.mysql.jdbc.Driver
Before we can start deploying Alfresco, make sure your Tomcat installation has a configured heap size of at least 256 MB. By default, Tomcat is started with the default heap size of your JVM. Usually, with these default settings the deployment of Alfresco will fail.
To increase Tomcat’s heap size on a Unix/Linux system open the file /bin/startup.sh and add following line at the beginning of the file:
export CATALINA_OPTS="-Xms256m -Xmx512m -XX:MaxPermSize=196m"
On Windows based systems open the file /bin/startup.cmd and add the following line at the beginning of the file:
set CATALINA_OPTS="-Xms256m -Xmx512m -XX:MaxPermSize=196m"
In case Tomcat was running you have to restart it in order the heap size changes take effect.


Another work around for the memory issues is to edit catalina.sh / catalina.bat found in $TOMCAT_HOME/bin. Simply add the following:
JAVA_OPTS="-Djava.awt.headless=true -Dfile.encoding=UTF-8  -server -Xms1536m -Xmx1536m
-XX:NewSize=256m -XX:MaxNewSize=256m -XX:PermSize=256m
-XX:MaxPermSize=256m -XX:+DisableExplicitGC"


4. Deploy Alfresco war files
Stop Tomcat by executing the start script (Unix/Linux systems) or (Windows systems).
Simply copy the file /alfresco.war to /webapps.
Next, copy the file /share.war to /webapps.

5. Install Alfresco DoD Records Management Module
 Download  Alfresco Module Management tool alfresco-mmt.jar ,Alfresco Records Management repository AMP alfresco-dod5015.amp and Alfresco Records Management Share AMP modules alfresco-dod5015-share.amp .
Place the three files in a directory called RM in your home directory, say /home/joseph/RM, such that - joseph@linux-9fbm:~/RM> ls
alfresco-dod5015.amp  alfresco-dod5015-share.amp  alfresco-mmt.jar  java
On the terminal/shell/command prompt, navigate to the directory RM


The following commands install the two amps to the alfresco.war and share.war respectively:-

 java -jar alfresco-mmt.jar install -verbose
 Example
joseph@linux-9fbm:~/RM> java -jar alfresco-mmt.jar install alfresco-dod5015.amp /home/joseph/Documents/KEY_4_GB/apache-tomcat-6.0.26/webapps/alfresco.war -verbose

joseph@linux-9fbm:~/RM> java -jar alfresco-mmt.jar install alfresco-dod5015-share.amp /home/joseph/Documents/KEY_4_GB/apache-tomcat-6.0.26/webapps/share.war -verbose
NB: tweak both cmds appropriate to fit your case


Let us now test to see what is installed, run
java -jar alfresco-mmt.jar list
This command provides a detailed listing of all the modules currently installed in the WAR file
specified.
Example
joseph@linux-9fbm:~/RM> java -jar alfresco-mmt.jar list /home/joseph/Documents/KEY_4_GB/apache-tomcat-6.0.26/webapps/alfresco.warModule 'org_alfresco_module_dod5015' installed in '/home/joseph/Documents/KEY_4_GB/apache-tomcat-6.0.26/webapps/alfresco.war'
   -    Title:        DOD 5015 Records Management
   -    Version:      1.0
   -    Install Date: Mon Apr 26 21:24:02 EAT 2010
   -    Desription:   Alfresco DOD 5015 Record Management Extension
joseph@linux-9fbm:~/RM> java -jar alfresco-mmt.jar list /home/joseph/Documents/KEY_4_GB/apache-tomcat-6.0.26/webapps/share.war
Module 'org_alfresco_module_dod5015_share' installed in '/home/joseph/Documents/KEY_4_GB/apache-tomcat-6.0.26/webapps/share.war'
   -    Title:        DOD 5015 Records Management Client
   -    Version:      1.0
   -    Install Date: Mon Apr 26 21:25:14 EAT 2010
   -    Desription:   Alfresco DOD 5015 Record Management Share Extension

Start Tomcat by executing the start script (Unix/Linux systems) or (Windows systems).
As long as you have not changed Tomcat’s default deploy settings, Tomcat will deploy the web archive automatically. Have a look at Tomcat’s logfile located at /logs/catalina.out. At the end of the log file there should be an entry similar to
18:52:46,528 User:System INFO  [service.descriptor.DescriptorService] Alfresco started (Community): Current version 3.2.0 (2039) schema 2019 - Installed version 3.2.0 (2039) schema 2019 
At the end of the Tomcat’s log file you should see an entry like
23:56:58,258  INFO  [web.site.FrameworkHelper] Successfully Initialized Web Framework
Well done, we are about to complete. Let’s open Alfresco Share in your favorite browser. Therefore, type http://localhost:8080/share in the address bar. 
 Login with username admin and password admin. Proceed with step 6

6.  Adding the Records Management dashlet
1. Log in to Alfresco Share.
2. Click Customize Dashboard.
3. Click Add Dashlets.
4. Locate the Records Management config dashlet in the Add Dashlets list.
5. Drag the Records Management config dashlet to the desired column.
6. Click OK.
The Records Management dashlet displays in the selected column in your Records Management site dashboard.
 
The following documents and links may be of help to supplement this tutorial
a. Install instructions on > Linux , WIndows , Mac 
c. Alfresco Community Wiki
d.Alfresco Community Edtion 3.3 tutorials 

Well done, we are through. Get yourself Getting_Started_with_Alfresco_Records_Management_for_Community_Edition_3_3.pdf
and enjoy!


Joseph
Software Engineer
Alliance Technologies Ltd

Monday, June 9, 2008

where the money is!! lets do सोमेथिंग थिंकिंग - Jose

Ok fellas,so the price of oil,gold,aluminium,platinum,copper (all metals in general),grains,cereals,etc,etc (read commodities) have gone up and continue going up. So these companies are making a lot of turnover and hopefully huge profits.


Now my questions is,do they spend? if they do,what do they buy and where do they buy? because wherever they will spend that money is guaranteed to make capital gains as well,and that my dear brothers and sisters is where money is going to be in the very near future....we need to identify those places and invest there!


Example..take the booming construction / infrustruture sector in kenya (east africa). Construction companies will be smiling all the way to the bank.....but are these really the big players in the game? the answer is NO. the big boys is where these jamaas buy materials and equipments from,eg. ARM. So ARM is a nice buy and will be for some time. But is cemment the only material / equipment? so who supplies tar? does anyone make fertilizers locally? many many questions here to be answered..bring them up plz!


Think,think,think,think....the key is commodities and you do not have to own them!! who services their operations or supplies materials?


The ball is in your court

About Me

Melbourne, VIC, Australia
Bsc. Software Engineering