Integrating CCAvenue in an Android App

Integrating CCAvenue in Android app

We recently made an Android app and a web-app for our client (Kabra Express) with CCAvenue as the payment gateway. Kabra Express is one of the largest travel company in Gujarat and has operations throughout the country.

CCAvenue is one of the biggest payment gateways in India. It supports multiple payment options, mobile payments (via WebView) and is highly secure.

However, the lack of proper documentation makes its integration with mobile apps a bit troublesome. Looking at how many people use CCAvenue, we decided to help the community by writing an article on it’s integration with Android.

Prerequisites

  1. Get your public IP address whitelisted at CCAvenue in both production and staging environment (mail them about it, mentioning your merchant ID)
  2. Get access code and encryption key from the dashboard. For this, you’ll need to ask the support to generate these for your hosts (like localhost or example.com etc).

The whole mechanism relies on RSA. RSA (Rivest–Shamir–Adleman) is one of the first public-key cryptosystems and is widely used for secure data transmission. In such a cryptosystem, the encryption key is public and it is different from the decryption key which is kept secret (private).

The request/response flow between Android app and servers
The request/response flow between Android app and servers

This request/response flow, as it happens

  1. Mobile app sends a request to the PHP server for RSA keys.
  2. The PHP server calls the CCAvenue server, with the access code and a unique order ID.
  3. CCAvenue replies with a text string which is the RSA key.
  4. This RSA keys is then sent back to the Android app in the response.
  5. Using the RSA key, the mobile app encrypts the payment information like customer info, order id, amount etc and performs a POST request using WebView to reach the CCAvenue payment page. The fields that are encrypted includes amount, currency, billing_tel (phone number), billing_email, billing_name. The user is presented with the CCAvenue payment page.
  6. Depending on the payment status (success/failure), CCAvenue redirects the user to the respective URL on the PHP server.
  7. The server, after parsing the gateway response, returns an HTML page (to the WebView) which has the Java-JS bridging code. The Java code is executed from the JS (inside the HTML page) and the user is sent to the activity in the app accordingly.

Explaining the flow

Step 1, 2, 3 and 4: All these steps happen in a single request-response cycle, initiated by the mobile app. The PHP server gets a request for RSA key from the app, it calls CCAvenue API using cURL. An example code is below:

public function getRSAPublicKey($orderId) {
       $url = "https://secure.ccavenue.com/transaction/getRSAKey";
       $accessCode = "access-code";
       $fields = array(
           'access_code' => $accessCode,
           'order_id' => $orderId
       );

       $postvars = '';
       $sep = '';
       foreach ($fields as $key => $value) {
           $postvars .= $sep . urlencode($key) . '=' . urlencode($value);
           $sep = '&';
       }

       $ch = curl_init();

       curl_setopt($ch, CURLOPT_URL, $url);
       curl_setopt($ch, CURLOPT_POST, count($fields));
       //curl_setopt($ch, CURLOPT_CAINFO, 'replace this with your cacert.pem file path here');
       curl_setopt($ch, CURLOPT_POSTFIELDS, $postvars);
       curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

       curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
       $result = curl_exec($ch);

       return $result;
   }

The response sent by the server will have the RSA key (from the above code), order id (must be unique and a whole number), access code, merchant id, redirect (success) and cancel URLs.

As soon as the app gets this RSA key (and other parameters in response), it encrypts amount, currency, billing_tel (phone number), billing_email, billing_name using this key. The Java code for encryption method is below:

public static String encrypt(String plainText, String key) {
 try {
  PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(Base64.decode(key, Base64.DEFAULT)));
  Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
  cipher.init(Cipher.ENCRYPT_MODE, publicKey);
  return Base64.encodeToString(cipher.doFinal(plainText.getBytes("UTF-8")), Base64.DEFAULT);
 } catch (Exception e) {
  e.printStackTrace();
 }
 return null;
}

The string returned by this method is POSTed to CCAvenue’s transaction URL via a WebView (code provided below) along with other parameters received from the server. The payment page is now presented to the user. Depending on the success or failure of the payment, the server and then the app will get the response.

Now comes the interesting part. You must be wondering- how does our app know if the payment was successful or failure? After all, the WebView is completely detached from the normal flow of the app as it renders HTML pages. The magic lies in the JS-Java bridge that allows execution of Java from JavaScript in Android! You can read more about it here.

Here’s how it happens (assuming that the payment has been made successfully):

The success URL sent to CCAvenue is our server’s URL. This URL when hit on redirection, will cause a series of events: response decryption, transaction status checks, updating the database, sending relevant emails and finally returning an HTML page in the response (Keep in mind that it’s the WebView that’s getting redirected along with the POST data from CCAvenue). This HTML is extremely simple:

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="utf-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
   <meta name="description" content="">
   <meta name="author" content="">
   <title>Please wait..</title>
</head>

<body>
</body>
<script type="text/javascript">
   Payment.onSuccess('');
</script>
</html>

See the line Payment.onSucess()? This is the magic sauce.

We attach something called as a JavaScript interface to the WebView. This interface is given a name (Payment, in our case). This interface’s object is made available to JS and it calls the methods of its equivalent Java class. A sample code is below:

public class CCAvenueWebViewActivity extends BaseActivity {

 private android.webkit.WebView webView;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_ccavenue_webview);
  webView = findViewById(R.id.ccavenue_webview);

  String url = getIntent().getStringExtra("url");
  webView.setWebViewClient(new WebViewClient());
  webView.getSettings().setJavaScriptEnabled(true);
  webView.getSettings().setDomStorageEnabled(true);
  webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);

  webView.addJavascriptInterface(new Payment(this), "Payment");
  webView.postUrl(url, getIntent().getStringExtra("params").getBytes());

 }

 public class Payment {
  Context mContext;
  Intent intent;

  Payment(Context c) {
   mContext = c;
   intent = new Intent();
  }

  @JavascriptInterface
  public void onSuccess(final String result) {
   intent.putExtra("result", result);
   setResult(RESULT_OK, intent);
   finish();
  }

  @JavascriptInterface
  public void onFailure(final String result) {
   intent.putExtra("result", result);
   setResult(RESULT_CANCELED, intent);
   finish();
  }
 }
}

As you can see, “Payment” is just a simple class with two methods and @JavascriptInterface annotation. This annotation tells the WebView to bind JS-Java objects together and hence, calling Payment.onSuccess from JS is similar to calling it from Java!

onSucess() sets the result code, and finishes the WebView activity. This way our app know if the payment was successful or failure.

This concludes the journey of integrating CCAvenue in an Android app in case of non-seamless integration. We almost always prefer this way when integrating other payment gateways too (if they don’t have an SDK). We also almost always go with non-seamless integration as it requires little adherence to compliance.