In this blog, we will be discussing how to implement the Khalti wallet API in unity to make a store with items that can be purchased through real money. We will be discussing the process in four parts :

  1. Overview of the idea
  2. Khalti Web SDK implementation
  3. Server-side transaction validation
  4. Unity side implementation with socket

Here’s a demo video of how it actually looks likes:

  1. Overview :

Khalti doesn’t have official support for unity, I had two ways I could implement this in Unity. One is using the native Android SDK of Khalti and another one is using the Khalti web SDK. I researched for weeks about developing this feature using the native android Khalti SDK with unity but later the implementation of the android SDK turned out to be more complex and harder so I decided to do it another way which was implementing the Khalti SDK in a web server and then viewing the web page created in unity with the help of a web view plugin. Doing this much would allow the user to view the client-side page. In order to make the unity client interact with it and place orders, I used socket io to send the server-side validation messages to unity. This would help me send my success and error messages to unity client where it could listen to the events and do actions accordingly.

2. Khalti SDK Implementation

The first thing you need to do is get yourself a merchant account for Khalti which is free of cost. After your merchant id is created you will be provided with your test server keys which will be used to test your transactions during the development phase. I used the web SDK which has very detailed instructions in Khalti’s documentation.

You can check Khalti’s web documentation here. Now after doing that you can create a web page by copying and pasting Khalti’s web SDK client-side page code. It should look like this :

<html>
   <head>
      <script src="https://khalti.s3.ap-south-1.amazonaws.com/KPG/dist/2020.12.17.0.0.0/khalti-checkout.iffe.js"></script>
      <script src="/node_modules/socket.io/node_modules/socket.io-client/dist/socket.io.js"></script>
   </head>
   <body>
      <script>  
         var socket = io("http://localhost:8818"); //The port you are listening your socket to
         
         var config = {
             // replace the publicKey with yours
             "publicKey": "testkey",
             "productIdentity": "1",
             "productName": "Item1",
             "productUrl": "http://www.prerakgiri.com",
             "paymentPreference": [
                 "KHALTI",
                 "EBANKING",
                 "MOBILE_BANKING",
                 "CONNECT_IPS",
                 "SCT",
                 ],
             "eventHandler": {
                 onSuccess (payload) {
                     // hit merchant api for initiating verification
                     socket.emit("data",payload);
                     console.log(payload);
                 },
                 onError (error) {
                     console.log(error);
                 },
                 onClose () {
                     console.log('widget is closing');
                 }
             }
         };
         
         var checkout = new KhaltiCheckout(config);
         var btn = document.getElementById("payment-button");
         
             // minimum transaction amount must be 10, i.e 1000 in paisa.
         checkout.show({amount: 1000});
         
      </script>
      ...
   </body>
</html>

Now you can host your webpage anywhere. I hosted the webserver in my localhost using apache and hosted the socket server with Nginx. Here, you might get an issue while listening to the same port using the Nginx and apache server so you have to get into the Nginx server config and change the port differently to the apache server. The config file is located at : /etc/nginx/sites-enabled/default.

This will make your base client-side ready and you will get OTP on login if your provided keys are correct. After doing this we will need to validate our client’s payment by contacting our server with the Khalti server.

3. Server-side transaction validation

For this, we will again visit Khalti’s documentation for the verification part. So here I created a simple socket server with express and a validator class that uses Axios for the transaction verification. After some tweaks from Khalt’s verification node.js, it looked like this:

const io = require('socket.io-client');
var socket = io("http://Localhost:8818");

function Validate(token, amount) {

    const axios = require('axios');
    let data = {
        "token": token,
        "amount": amount
    };

    console.log("Your token is " + token + "Amount is " + amount)

    let config = {
        headers: {
            'Authorization': 'Key your key}

        };

        axios.post("https://khalti.com/api/v2/payment/verify/", data, config)
        .then(response => {
            socket.emit("PaySuccess", "");
            console.log(response.data);
        })
        .catch(error => {
            socket.emit("Payerror", "");
            console.log(error);
        });
    }

    module.exports = {
        Validate
    };

So basically here what I am doing is I am listening to the post made by Axios to the Khalti API server and waiting for the response. On success I emit my socket’s success event and on error I emit my socket’s payerror to the client. We will be later using these events to handle actions on the unity side. This is what the server script looks likes:

const app = require('express')();
const validator = require('./validator');
const server = require('http').createServer(app);
const io = require("socket.io")(server, {
    cors: {
        origin: "*"
    }
});

io.on("connection", function(socket) {
    console.log("connected " + socket.id);
    socket.emit("online")
    socket.on("data", function(data) {
        console.log(data);
        validator.Validate(data.token, data.amount);
    });
    socket.on("Payerror", function(err) {

        io.emit("Payerror");
        console.log("err");
    });
    socket.on("success", function(data) {

        console.log("success event fired");

    });
    socket.on("ping", function() {
        console.log("pong")
        socket.emit("pung");
    })
})

function sendSocket(io, eventName) {
    io.emit(eventName);
}


app.use(function(req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    next();
});


server.listen(8818, () => console.log("active at port 8818"))

module.exports = {
    sendSocket
};

Here I have enabled all CORS requests to be accepted because of a CORS limitation error. If you were to host this on a live server PLEASE DON’T.

In this script, I have created a simple server using Axios which is active at port 8818. Now, let's move into the unity section.

4. Unity side implementation with socket

Unity side implementation is quite simple and easy, for the socket events handling I used besthttp to create and sockethandler to handle all the incoming events and in order to view the website in live web view I used a plugin called UniWebView which made it very easy and simple to display/close web view in unity natively. Then I created a simple scene with an image, a button to call an event text. Here’s what the socket handler looks like:

using System;
using System.Collections;
using System.Collections.Generic;
using BestHTTP.SocketIO;
using ToastPlugin;
using UnityEngine;
using UnityEngine.Events;

public class SocketHandler: MonoBehaviour {
        private SocketManager _manager;
        public static SocketHandler instance;
        public UnityEvent SuccessEvent;
        private void Awake() {
            if (instance == null) {
                instance = this;
            }
        }

        void Start() {
            Connect();
        }



        // Update is called once per frame
        void Connect() {
            SocketOptions options = new SocketOptions();
            options.ServerVersion = SupportedSocketIOVersions.v2;
            options.AutoConnect = true;
            options.ConnectWith = BestHTTP.SocketIO.Transports.TransportTypes.WebSocket;
            options.ReconnectionDelay = TimeSpan.FromSeconds(1 f);


            _manager = new SocketManager(new Uri("http://localhost:8818/socket.io/"), options);

            _manager.Socket.On("online", OnConnect);
            _manager.Socket.On("PaySuccess", OnSuccess);
            _manager.Socket.On("Payerror", OnError);
            _manager.Socket.On("error", OnSocketError);
            _manager.Socket.On("pung", OnPung);
            _manager.Socket.Emit("ping");
            _manager.Open();
        }

        private void OnPung(Socket socket, Packet packet, object[] args) {
            Debug.Log("Pung");
        }

        private void OnSocketError(Socket socket, Packet packet, object[] args) {
            Debug.Log(args[0]);
        }

        private void OnSuccess(Socket socket, Packet packet, object[] args) {
            Debug.Log("Success payment");
            WebviewManager.instance.CloseWebView();
            StartCoroutine(showToast("Payment is successful"));
            SuccessEvent?.Invoke();
        }

        private void OnError(Socket socket, Packet packet, object[] args) {
            WebviewManager.instance.CloseWebView();
            StartCoroutine(showToast("Payment is unsuccessful"));
            Debug.Log("Error occured during payment");
        }

        private void OnConnect(Socket socket, Packet packet, object[] args) {
            Debug.Log("Connected to the socket");
        }

In this script, we initialize a socket handler with our localhost and you can see we are listening to success and error events emitted from the server to our client. So in the OnSuccess we close the webview and invoke our success event which completes our purchase and updates the fields. Here’s what our webviewmanager looks like:

using UnityEngine;


public class WebviewManager: MonoBehaviour {
    public static WebviewManager instance;
    private UniWebView webView;
    public string url;

    private void Awake() {
        if (instance == null) {
            instance = this;
        }
    }


    public void ShowWebView() {
        var webViewGameObject = new GameObject("UniWebView");
        webView = webViewGameObject.AddComponent < UniWebView > ();
        webView.Frame = new Rect(0, 30, Screen.width, Screen.height);
        webView.Load("localhost:8080/");

        // webView.SetBackButtonEnabled(false);
        Debug.Log("Launched web view");
        webView.Show();
        webView.Reload();
    }

    public void CloseWebView() {
        Debug.Log("Closed web view");
        Destroy(webView);
    }


}

In this we create an instance of our script then in the ShowWebView function we create a gameobject with the uniwebview gameobject attached, add its settings and load it with our local apache server website.

This is all I needed to do to implement the Khalti API in my unity game. You can also use this to implement other wallet’s APIs like eSewa, IME Pay etc. Also if you want your transaction to be successful, you will have to use your live server keys which will be available after your first test transaction is completed.

Thank you for reading and learning with me. Please leave comments if you have any regarding this article.