how to receive payment online using iwallet in python

Hello this is another sister post of the how to receive payment using iWallet in java. This is the post that caters for the same thing but using python 2.7 this time.

So we are going to learn how to use iWallet one of the best secured, stable payment processor in the sub region. The plan of action was to avoid the use of any complex framework that would rather cloud the showcasing of the flow itself. But going down that line seems not be so pythonic since it’s more natural to use python for web development. So to avoid going the long way , we will rather pick one of the numerous python web framework to run with. In this tutorial we will use the following tools/libraries/frameworks:

  • Mysqldb python library to handle the datalayer part
  • suds python library to handle or SOAP API calls
  • mysql database
  • Flask framework for everything web. display, querystrings etc
  • IntelliJ Ultimate as IDE(You can use any PyCharms, Netbeans,sublime,PyDev)

Couple of assumption here, in this tutorial , I assume you already know how to work with python and how to install python packages using either pip or easy_install

Creation of a Merchant Account

That said, welcome to konohashop, kohona is the hidden ninja village of the leaf, I guess kohonamaru, the third grand son was not satisfied with the fact that Naruto walks all over the leaf to buy his ramen from the old man Teuchi the shop owner. To ease that process and enable people do their purchasing at the comfort of their house ,kohonamaru (apparently a techy ninja) contacted iWallet in order to accept payment on the web application built for sale purposes using iWallet. They were asked to create a merchant account.

After that quickly set up, iWallet team verified the konohashop authenticity and then authorized the newly created account to do business on iWallet (So to be a merchant you need to have a merchant account that needs approval or authorization). If you don’t have any by now then pause for 5mn , head to https://www.i-walletlive.com to create a merchant account.

iwallet signup page

iWallet signup page

According to the API at iWallet payLIVE API we need to have :

  • Our Merchant Email
  • Our Merchant Key
  • Set Integration Mode On
  • Set Callback to Our konohashop app

Below is how to get the merchant key, and set the callback url. After iWallet takes the money out of the end users, iWallet will use that callback url to contact our konohashop to inform us of the state of affairs.

iwallet account dashboard

iWallet Dashboard

flask callback

setting up flask callback url for konohashop

Now that we have our account created and validated, we can proceed with integrating the payment into our system. But we do not have the system yet. We will not attempt in creating an actual shopping cart but will build a mimic of it. Here is a proposal of a data model:

konohashop data model

sql file for the schema used is available here on pastie.org . In order to configure a user to use the database, run the following mysql command:

mysql> GRANT ALL PRIVILEGES ON konohashop.* TO 'teuchi'@localhost IDENTIFIED BY 'shoppu';
mysql> FLUSH PRIVILEGES;

Creating the shop web project

In IntelliJ , create a python project iwallet-konohashop-python and select flask support.

In our project what we will be doing is basically captured in this following flowchart. The flowchart depicts all the key steps with an effort to be as concise as possible.

iWallet Integration flow

iWallet Integration flow

Setting up dependencies

Aside main dependency which is actually the Integration classs located on github called iwallet-python-connector, we will need to worry about other dependency python itself needs to get our work done without complains. In case you are using any debian like Operating System the following would suffice :

$ sudo apt-get install python-mysqldb 
$ sudo pip install suds

That said let’s create a folder called classed in the main source folder and put the Integrator.py downloaded from the referred link above in it

Creating Needed classes

For the sake of simplicity , this project will not use a typical implementation of a cart using session etc but will rely on simple form and python files to simulate checkout process et al. Let’s begin with our Data layer class, in classes folder create a python class DbLayer.py and make sure it looks like the following:

__author__ = 'joseph kodjo-kuma Djomeda'

import MySQLdb
import datetime

class DbLayer:

    def __init__(self, username, password,host, database):

        self.username = username
        self.password = password
        self.host = host
        self.database = database
        self.db = self.getConnection()


    def getConnection(self):
        return MySQLdb.connect(self.host, self.username, self.password, self.database)


    def setFixtures(self):

        self.db = self.getConnection()
        cursor = self.db.cursor()

        cursor.execute("insert into category(id,name,description) values(1,'food','all sort of comestible item'),(2,'ninja tools','any sort of tool according to konoha classifications')")
        cursor.execute("insert into product(id,category_id,product_id,name,price,in_stock,comment) values(1,1,'ra_0001','ramen',30,20,''),(2,2,'we_0001','shuriken',120,100,''),(3,2,'we_0002','kunai',62,95,'')")

        self.db.commit()
        self.db.close()


    def tearDown(self):

        self.db = self.getConnection()
        cursor = self.db.cursor()

        cursor.execute("SET FOREIGN_KEY_CHECKS = 0")
        cursor.execute("truncate table product")
        cursor.execute("truncate table category")
        cursor.execute("truncate table order_product_map")
        cursor.execute(&quot;truncate table <code>order</code>&quot;)
        cursor.execute(&quot;SET FOREIGN_KEY_CHECKS = 1&quot;)

        self.db.commit()
        self.db.close()

    def getAllItems(self):
        # if(not self.db):

        self.db = self.getConnection()

        cursor = self.db.cursor()
        cursor.execute(&quot;select c.name as category_name, p.name ,p.id, p.price,p.product_id ,p.comment from product p inner join category c on c.id = p.category_id where in_stock &lt;&gt; 0&quot;)
        result = cursor.fetchall()

        self.db.close()
        return result

    def getProductById(self, productId):
        self.db = self.getConnection()

        cursor = self.db.cursor()
        cursor.execute(&quot;select * from product where id=%s&quot; ,(productId))
        result = cursor.fetchall()
        self.db.close()
        return result


    def updateOrder(self, orderId, paymentTransactionId, paymentStatus):
        self.db = self.getConnection()

        cursor = self.db.cursor()
        cursor.execute(&quot;update <code>order</code> set payment_common_id='{0}', order_status='{1}', date_modified='{2}' where order_id='{3}'&quot;.format(paymentTransactionId,paymentStatus,datetime.datetime.now(),orderId))
        self.db.commit()
        self.db.close()

    def createOrder(self,orderId, paymentToken, productIdList):

        self.db = self.getConnection()
        cursor = self.db.cursor()
        self.db.autocommit(False)
        order_product_map_query = self.orderProductMapQueryBuilder(orderId,productIdList)
        try:
            cursor.execute(&quot;insert into <code>order</code>(order_id,payment_token) values ('{0}', '{1}')&quot;.format(orderId,paymentToken))
            cursor.execute(&quot;insert into order_product_map values&quot; + order_product_map_query)
            self.db.commit()
        except:
            self.db.rollback()
            self.db.close()


    def orderProductMapQueryBuilder(self,orderId, productIdList):

        queryPartString = &quot;&quot;

        for id in productIdList:
            queryPartString +=&quot;('{0}',{1}),&quot;.format(orderId,id)

        if(queryPartString):
            queryPartString = queryPartString[:-1]

        return queryPartString


    def countValidTransaction(self,paymentToken):

        self.db = self.getConnection()
        cursor = self.db.cursor()
        cursor.execute(&quot;select order_id from <code>order</code> where payment_token= '{0}' and order_status='PENDING'&quot;.format(paymentToken))
        result = cursor.fetchone()
        self.db.close()
        return result


Showing Items : /viewitems

Now DbLayer.py and Integrator.py are siblings of classes folder. By now we assume the database is created with its user and the sql file has been run to populate our database. Upon creation of the flask project there should be an app file called ‘iwallet-konohashop-python.py’ if you chose that name as project name.

Since flask is the web framework we used , what happens is that all urls of our application are in fact defined with the route engine of the framework eg. @app.route(‘/letshavefriedrice’). The annotation is put on top of a method that would be responsible of handling requests to localhost:5000/letshavefriedrice. So in the same way /viewitems is defined to show all products and /checkout for processing it.

Before we start calling our DbLayer class let’s put in place our “properties files”. Create a folder called config in the main source folder and put a python file config.py in a way that looks like shown below:


HOST ="localhost"
USERNAME = "teuchi"
PASSWORD = "shoppu"
DBNAME ="konohashop"


SHIPPINGCOST = 30.00
TAXES = 17


API_IWALLET_MERCHANT_EMAIL = "[email protected]" # use your merchant email here
API_IWALLET_MERCHANT_KEY= "merchantkey" #use your merchant key here
API_IWALLET_INTEGRATION_MODE=1
API_IWALLET_REDIRECT_URL= "https://i-walletlive.com/payLIVE/detailsnew.aspx?pay_token="
API_IWALLET_WSDL="https://i-walletlive.com/webservices/paymentservice.asmx?wsdl"
API_IWALLET_SERVICE_CLASS="com.i_walletlive.paylive.PaymentServiceSoap"
API_IWALLET_NAMESPACE = "http://www.i-walletlive.com/payLIVE"
API_IWALLET_SERVICE_TYPE = "C2B"
API_IWALLET_VERSION =1.4

Let’s populate our database with the /fixtures page .Let’s make our iwallet-konohashop-python.py looks like shown below:

from flask import Flask, request, session, g, redirect, url_for, abort, render_template
from classes.DbLayer import DbLayer

app = Flask(__name__)
app.config.from_pyfile("config/config.py")
db = DbLayer(app.config['USERNAME'],app.config['PASSWORD'],app.config['HOST'],app.config['DBNAME'])

@app.route('/fixtures')
def runfixtures():
    db.tearDown()
    db.setFixtures()
    return "Fixures run"

if __name__ == '__main__':
    app.run(debug=True)

let’s run our project and type http://localhost:5000/fixtures in the browser to populate our database.

Once that is done let’s focus on pulling data into viewitem page. In the iwallet-konohashop-python.py let’s put the code shown below

from flask import Flask, request, session, g, redirect, url_for, abort, render_template
from classes.DbLayer import DbLayer

app = Flask(__name__)
app.config.from_pyfile("config/config.py")
db = DbLayer(app.config['USERNAME'],app.config['PASSWORD'],app.config['HOST'],app.config['DBNAME'])

@app.route('/viewitems')
def showitems():
    items = db.getAllItems()
    return render_template("viewitems.html",rows=items)

@app.route('/fixtures')
def runfixtures():
    db.tearDown()
    db.setFixtures()
    return "Fixures run"
if __name__ == '__main__':
    app.run(debug=True)

In the render_template, data items has been passed to the view under the variable called “rows”. Let’s set up the templating system according to flask documentation. In the source folder let’s create another folder called “templates”, in there let’s create the html file viewitems.html like shown below:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<form action="{{url_for('processCheckout')}}" method="post">
    <table>

        {%
             for row in rows:
        %}
        <tr>
            <td width="130px"><input type="checkbox" name="orderItems" value="{{row[2] }}"/> {{row[1]}}</td>
            <td width="130px"> {{row[3]}}</td>
            <td width="130px"> {{row[5]}}</td>
        </tr>
        {% endfor %}
        <tr>
            <td colspan="1"><input type="reset" value="Cancel"/></td>
            <td colspan="2"><input type="submit" value="Checkout"/></td>
        </tr>
    </table>
</form>
</body>
</html>

Let’s run this to see whether everything is fine. One should see the image shown below if everything is properly wired:

View Item page

View Item page

configuring simple index page

The index page is just made up with a single link to viewitems page. There is technically no need for this step but just that it give us reason to believe that we have a full application 😀

Let’s modify our iwallet-konohashop-python to look like shown below:

from flask import Flask, request, session, g, redirect, url_for, abort, render_template
from classes.DbLayer import DbLayer

app = Flask(__name__)
app.config.from_pyfile("config/config.py")
db = DbLayer(app.config['USERNAME'],app.config['PASSWORD'],app.config['HOST'],app.config['DBNAME'])


@app.route('/')
def index():
    return render_template("index.html")


@app.route('/viewitems')
def showitems():
    items = db.getAllItems()
    return render_template("viewitems.html",rows=items)

@app.route('/fixtures')
def runfixtures():
    db.tearDown()
    db.setFixtures()
    return "Fixures run"

if __name__ == '__main__':
    app.run(debug=True)

Let’s create the template index.html in the templates folder :

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>Welcome to konohashop</title>
</head>
<body>
 <a href="{{ url_for('showitems') }}">viewitems</a>
</body>
</html>

Processing of the orders : /checkout

In the following part we will show how to :

  1. submit the order to iWallet
  2. receive a payment token from iWallet
  3. use that token to construct the redirect url
  4. redirect to iWallet with valid token for end user to make payment

Let’s modify the app file iwalelt-konohashop-python.py like shown below to process form submitted in /viewitems :

from flask import Flask, request, session, g, redirect, url_for, abort, render_template
from classes.DbLayer import DbLayer
import uuid,decimal
from classes.Integrator import Integrator


app = Flask(__name__)
app.config.from_pyfile("config/config.py")
db = DbLayer(app.config['USERNAME'],app.config['PASSWORD'],app.config['HOST'],app.config['DBNAME'])
iWallet = Integrator(app.config["API_IWALLET_NAMESPACE"], app.config["API_IWALLET_WSDL"], app.config["API_IWALLET_VERSION"], app.config["API_IWALLET_MERCHANT_EMAIL"], app.config["API_IWALLET_MERCHANT_KEY"], app.config["API_IWALLET_SERVICE_TYPE"], app.config["API_IWALLET_INTEGRATION_MODE"])

@app.route('/')
def index():
    return render_template("index.html")


@app.route('/viewitems')
def showitems():
    items = db.getAllItems()
    return render_template("viewitems.html",rows=items)

@app.route('/fixtures')
def runfixtures():
    db.tearDown()
    db.setFixtures()
    return "Fixures run"

@app.route("/checkout", methods=['POST'])
def processCheckout():

    listOfIds = request.form.getlist("orderItems")
    result = doWork(listOfIds)

    if ' ' in result:
        return result
    else:
        return redirect(result,302)

def doWork(listOfProductIds):

    arrayOfOrderItem = iWallet.buildAPIObject('ArrayOfOrderItem')

    for productId in listOfProductIds:
        dbProducts = db.getProductById(productId)
        fetchedItem = dbProducts[0]
        orderItem = iWallet.buildOrderItem(fetchedItem[2],fetchedItem[3],fetchedItem[4],1,fetchedItem[4])
        arrayOfOrderItem.OrderItem.append(orderItem)

    return proccessIwalletOrder(listOfProductIds,arrayOfOrderItem)

def proccessIwalletOrder(productIds, arrayOfOrderItems):

    order_id= uuid.uuid1()
    grandSubTotal = grandSubTotalCalculator(arrayOfOrderItems)
    flatShippingCost = app.config["SHIPPINGCOST"]
    tax = app.config["TAXES"]
    taxAmount = grandSubTotal * tax/100
    D = decimal.Decimal

    total = grandSubTotal+ D(taxAmount)+D(flatShippingCost)
    result = iWallet.processPaymentOrder(order_id,grandSubTotal,flatShippingCost,taxAmount,total,"konohashop items","Tutorial shop",arrayOfOrderItems)
    paymentToken = result

    if ' ' in paymentToken:
       message = "Payment Not Successful !"
       return message
    else:
        db.createOrder(order_id,paymentToken,productIds)
        redirectUrl = "{0}{1}".format(app.config["API_IWALLET_REDIRECT_URL"],paymentToken)
        return redirectUrl


def grandSubTotalCalculator(arrayOfOrderItems):
    subtotal = 0
    for orderItem in arrayOfOrderItems.OrderItem:
        subtotal += orderItem.SubTotal
    return subtotal

Processing callback from iwallet : /receivecallback

With our callback configured on iwallet, We will also need to take care of the following:

  1. pull callback parameters
  2. verify authenticity of the payment
  3. process the order (what was paid for)
  4. complete the transaction by confirming or cancelling payment whether it’s success or failure

Let’s modify the application file iwallet-konohashop-python.py to look like the file below:

from flask import Flask, request, session, g, redirect, url_for, abort, render_template
from classes.DbLayer import DbLayer
import uuid,decimal
from classes.Integrator import Integrator


app = Flask(__name__)
app.config.from_pyfile("config/config.py")
db = DbLayer(app.config['USERNAME'],app.config['PASSWORD'],app.config['HOST'],app.config['DBNAME'])
iWallet = Integrator(app.config["API_IWALLET_NAMESPACE"], app.config["API_IWALLET_WSDL"], app.config["API_IWALLET_VERSION"], app.config["API_IWALLET_MERCHANT_EMAIL"], app.config["API_IWALLET_MERCHANT_KEY"], app.config["API_IWALLET_SERVICE_TYPE"], app.config["API_IWALLET_INTEGRATION_MODE"])

@app.route('/')
def index():
    return render_template("index.html")


@app.route('/viewitems')
def showitems():
    items = db.getAllItems()
    return render_template("viewitems.html",rows=items)

@app.route("/checkout", methods=['POST'])
def processCheckout():


    listOfIds = request.form.getlist("orderItems")
    result = doWork(listOfIds)

    if ' ' in result:
        return result
    else:
        return redirect(result,302)

@app.route('/fixtures')
def runfixtures():
    db.tearDown()
    db.setFixtures()
    return "Fixures run"


@app.route('/receivecallback')
def parsecallback():

    status_code = request.args.get("status")
    transaction_id = request.args.get("transac_id")
    order_id = request.args.get("cust_ref")
    payment_token = request.args.get("pay_token")

    if None == status_code or None == order_id  or None == payment_token:
        return "Not good, details are missing or someone is messing with you"

    payment_status = parseTransactionStatusCode(status_code)

    if None == transaction_id or len(transaction_id) == 0:
        db.updateOrder(order_id,"","FAILED")
        return "Empty or Null Transaction Id"

    if not checkValidity(payment_token,order_id):
        return "There is no transaction corresponding to the received payment token. Please contact iWallet support"

    order_result = iWallet.verifyMobilePayment(order_id)

    if order_result.success:
        db.updateOrder(order_id,transaction_id,payment_status)
        iWallet.confirmTransaction(payment_token,transaction_id)
        return "Yatta!! Your order is on the way"
    else:
        iWallet.cancelTransaction(payment_token,transaction_id)
        return "Something seems to be wrong with your order, Kindly start afresh"

    return "{0}".format(order_result.success)



def doWork(listOfProductIds):

    arrayOfOrderItem = iWallet.buildAPIObject('ArrayOfOrderItem')

    for productId in listOfProductIds:
        dbProducts = db.getProductById(productId)
        fetchedItem = dbProducts[0]
        orderItem = iWallet.buildOrderItem(fetchedItem[2],fetchedItem[3],fetchedItem[4],1,fetchedItem[4])
        arrayOfOrderItem.OrderItem.append(orderItem)

    return proccessIwalletOrder(listOfProductIds,arrayOfOrderItem)


def proccessIwalletOrder(productIds, arrayOfOrderItems):

    order_id= uuid.uuid1()
    grandSubTotal = grandSubTotalCalculator(arrayOfOrderItems)
    flatShippingCost = app.config["SHIPPINGCOST"]
    tax = app.config["TAXES"]
    taxAmount = grandSubTotal * tax/100
    D = decimal.Decimal

    total = grandSubTotal+ D(taxAmount)+D(flatShippingCost)
    result = iWallet.processPaymentOrder(order_id,grandSubTotal,flatShippingCost,taxAmount,total,"konohashop items","Tutorial shop",arrayOfOrderItems)
    paymentToken = result

    if ' ' in paymentToken:
       message = "Payment Not Successful !"
       return message
    else:
        db.createOrder(order_id,paymentToken,productIds)
        redirectUrl = "{0}{1}".format(app.config["API_IWALLET_REDIRECT_URL"],paymentToken)
        return redirectUrl


def grandSubTotalCalculator(arrayOfOrderItems):
    subtotal = 0
    for orderItem in arrayOfOrderItems.OrderItem:
        subtotal += orderItem.SubTotal
    return subtotal


def parseTransactionStatusCode(status_code):

     status_code = int(status_code)

     if status_code == 0:
         return "success"
     elif status_code == -2:
         return "cancelled"
     elif status_code == -1:
         return "error"
     else:
         return "unknown"


def checkValidity(paymentToken,orderId):

    savedOrderIdString = db.countValidTransaction(paymentToken)
    if None == savedOrderIdString or len(savedOrderIdString[0]) == 0:
        return False
    if orderId != savedOrderIdString[0]:
        return False

    return  True

if __name__ == '__main__':
    app.run(debug=True)

The project folder structure should look like shown below

folder structure

folder structure

Testing the full flow

Before running this , let’s fix what we intentionally left out. We did not put and url for the action for the form in viewitems file. Let’s make sure it now looks like the following:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<form action="{{url_for('processCheckout')}}" method="post">
    <table>

        {%
             for row in rows:
        %}
        <tr>
            <td width="130px"><input type="checkbox" name="orderItems" value="{{row[2] }}"/> {{row[1]}}</td>
            <td width="130px"> {{row[3]}}</td>
            <td width="130px"> {{row[5]}}</td>
        </tr>
        {% endfor %}
        <tr>
            <td colspan="1"><input type="reset" value="Cancel"/></td>
            <td colspan="2"><input type="submit" value="Checkout"/></td>
        </tr>
    </table>
</form>
</body>
</html>

After running the application let’s head to /viewitems and choose items like : kunai and ramen and click on checkout:

kunai and ramen selected

kunai and ramen selected

After the checkout button is clicked, our checkout method would have called iWallet web service , gotten a valid token and redirects us to the following page which will notify us being in test or integration mode

iWallet testing mode notification

iWallet testing mode notification

After clicking on I am a tester, we are now allowed to see the order form page see the total cost of our purchases. According to the API document we ought to use :
username: testpaddyi-walletlive.com
password: paddypaddy

iWallet order form page

iWallet order form page

But just in Case we are curious about what makes our bill, a click on the cart icon would reveal details of the bill as shown below

iWallet cart payment details

iWallet cart payment details

After entering credentials as expected, we are redirected to our konohashop and we get a success message. According to the API document the credentials should be :

username: [email protected]
password: paddypaddy

iwallet callback

iwallet callback

Voila , I hope this tutorial gave you insight on how to use and interact with iWallet and its SOAP payLIVE API. Project files are available on github.com

2 Comments

  1. Samuel Sowah

    Now DbLayer.py and Integrator.py are siblings of classes folder.

    Besides this statement, there’s no mention of Integrator.py anywhere in the tutorial again but it seems to be an essential part of the project. Can you please provide the content of Integrator.py?

    Samuel.

    Reply
  2. kodjo-kuma djomeda (Post author)

    Hello Samuel,

    Kindly read again properly, there are many occurrences in this post where I mentioned not only the need of Integrator.py but also where to find it under the setting up dependencies section.

    Reply

Leave a Comment

Your email address will not be published. Required fields are marked *

captcha * Time limit is exhausted. Please reload the CAPTCHA.