CARD91 NSDL AADHAAR SDK Flutter Implementation for Android

Card91 NSDL AADHAAR KYC Android Library Integration with Flutter Android Apps

Description

Purpose of this document is to illustrate the method to add Card91 ‘Card91AadhaarKYC’ android library to any flutter android application written in java and dart.

Card91 Card91AadhaarKYC library allows developers to launch the Card91KYCActivity activity from their application to process and complete the aadhaar XML full KYC for NSDL cards.

Developers need to launch Card91KYCActivity from their android native class on the flutter application with some required parameters mentioned below for completing the aadhaar KYC for the NSDL cards.

Installation (Gradle and Manifest Configuration) Android

Android Minimum Requirements :

Gradle Version - 7.3.3
minSdkVersion - 26
compileSdkVersion - 33
targetSdkVersion - 33

Android Permission on Manifest:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        tools:ignore="ScopedStorage" />

Reference gradle-wrapper.properties

distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip

Gradle dependencies

Add the provided "aadhaarsdk2-1.0.7.aar","nsdlkyc_card91.aar" aar (AAR files will be provide by the Card91 team) file SDK on the given location of the react application (android folder).
Path: src/main/libs (This path must be same as that defined in the project)

dependencies {
    //CARD SDK
    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    
    /*SDK DEPENDENCY*/
    //HTTP CALL
    implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.3'
    //JSON CONVERTOR
    implementation('com.squareup.moshi:moshi-kotlin:1.12.0')
    //kotlin reflect
    implementation 'org.jetbrains.kotlin:kotlin-reflect:1.6.10'
    
    implementation "androidx.security:security-crypto:1.1.0-alpha03"

    implementation platform('com.google.firebase:firebase-bom:29.3.1')
    implementation 'com.google.firebase:firebase-crashlytics'
    implementation 'com.google.firebase:firebase-analytics'
    implementation "com.google.firebase:firebase-auth:17.0.0"
    implementation 'com.google.firebase:firebase-iid'
    implementation 'com.squareup.retrofit2:retrofit:2.3.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.1.0'
    implementation 'com.google.code.gson:gson:2.9.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'
    implementation(name: 'aadhaarsdk2-1.0.7', ext: 'aar' ) /// NSDL SDK
    implementation(name: 'nsdlkyc_card91', ext: 'aar' ) // Card91 NSDL Aadhaar KYC SDK 
    implementation 'com.scottyab:rootbeer-lib:0.1.0'
    implementation "androidx.room:room-runtime:2.4.3"
    annotationProcessor "androidx.room:room-compiler:2.4.3"
    implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"


    /// required for SDK
    implementation 'com.github.bumptech.glide:glide:4.12.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
    implementation 'com.github.bastiaanjansen:jwt-java:1.2.0'
    implementation 'com.auth0:java-jwt:4.0.0'
    implementation 'com.auth0.android:jwtdecode:2.0.1'
    implementation 'com.squareup.retrofit2:converter-scalars:2.4.0'
}

📘

Note

Clean and Build the android app to get the dependencies added on to the application
And minifyEnable is true on you app build.gradle and add below line on your ProGuard rules file

-keep class com.card91.nsdlkyc.model.** { *; }

USAGE

Launch Card91KYCActivity (Aadhaar SDK) from flutter application android native module class with the help of MethodChannel (https://api.flutter.dev/flutter/services/MethodChannel-class.html)
MethodChannel is used to communicate between Dart widget and Android Native Activity Class.

First create a stateful widget on flutter application from where you want to invoke the SDK (aar class).

Declare the MethodChannel on the widget.

static const platform = MethodChannel('flutter.native/intent');

Then invoke the function of the native android activity which has been exposed with a matching key.

var dataRespForGetToken = await platform.invokeMethod('invoke_card91SDK', {  
                  'MOBILE_NUMBER': '91805**\*\***', //Should start with 91 (12 digit)  
                  'AUTH_TOKEN': "C91CHVdNSU1jWVQWxIoXWoDZgWcBAp7U5cfrE**\***",// Card holder auth token  
                  'ENV': 'PROD_SANDBOX', // "PROD_SANDBOX" for production sandbox and "PROD" for PRODUCTION environment  
                  'PAN': 'AJHPL**\*\***' /// User real PAN needs to be passed here  
                  'ORG_ID':'230901080404946ID1OID909***' ///Card org id
                });

Dart widget example from where the native android activity is called .

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class InvokeAadhaarSDK extends StatefulWidget {
  const InvokeAadhaarSDK({Key? key}) : super(key: key);

  @override
  State<InvokeAadhaarSDK> createState() => _InvokeAadhaarSDKState();
}

class _InvokeAadhaarSDKState extends State<InvokeAadhaarSDK> {
  static const platform = MethodChannel('flutter.native/intent');

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: Center(
          child: ElevatedButton(
              onPressed: () async {
                var dataRespForGetToken = await platform.invokeMethod('invoke_card91SDK', {
                  'MOBILE_NUMBER': '91805******', //Should start with 91 (12 digit)
                  'AUTH_TOKEN': "C91CHVdNSU1jWVQWxIoXWoDZgWcBAp7U5cfrE*****",// Card holder auth token
                  'ENV': 'PROD_SANDBOX', // "PROD_SANDBOX" for production sandbox and "PROD" for PRODUCTION environment
                  'PAN': 'AJHPL******' /// User real PAN needs to be passed here
                  'ORG_ID':'230901080404946ID1OID909***' ///Card org id
                });
                print(
                    "dataRespForGetToken--->${dataRespForGetToken.toString()}");
              },
              child: Text("Invoke Aadhaar SDK")),
        ),
      ),
    );
  }
}

After setting up the dart file with MethodChannel while calling invokeMethod with key "invoke_card91SDK", now we have to setup the listener on the MainActivity.

Then invoke the function of the native android activity MainActivity which extends FlutterFragmentActivity and has been exposed with a matching key.

package com.card91.mobile.customerapp.new_flow_cust_app

import android.app.Activity
import android.content.Intent
import android.util.Log
import androidx.activity.result.contract.ActivityResultContracts
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
import org.json.JSONObject
import java.util.*
import android.widget.Toast
import java.security.InvalidKeyException

import java.security.NoSuchAlgorithmException
import com.card91.nsdlkyc.activities.Card91KYCActivity

import java.io.UnsupportedEncodingException
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.util.*
import java.util.Base64.Encoder

import android.os.Build
import androidx.annotation.RequiresApi

class MainActivity: FlutterFragmentActivity() {

    companion object {
        private const val CHANNEL = "flutter.native/intent"
    }

    var resp: MethodChannel.Result? = null
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler
        { call: MethodCall, result: MethodChannel.Result? ->
            ///Card from Dart file will be listened here and based on the invoke key "invoke_card91SDK" 
            //consition is put to launch the SDK
             if (call.method == "invoke_card91SDK") {
                 val intent = Intent(this@MainActivity, Card91KYCActivity::class.java)
                 intent.putExtra("MOBILE", Objects.requireNonNull(call.argument<Any>("MOBILE_NUMBER")).toString()) // User registered mobile number
                 intent.putExtra("AUTH_TOKEN", Objects.requireNonNull(call.argument<Any>("AUTH_TOKEN")).toString()) // Auth token of card holder
                 intent.putExtra("ENV", Objects.requireNonNull(call.argument<Any>("ENV")).toString()) // "PROD_SANDBOX" for sandbox and "PROD" production environment
                 intent.putExtra("PAN", Objects.requireNonNull(call.argument<Any>("PAN")).toString())// card holder PAN
								 intent.putExtra("ORG_ID",Objects.requireNonNull(call.argument<Any>("ORG_ID")).toString())// card orgnization ID
                 //start intent
                 aadhaarLauncher.launch(intent) // registerForActivityResult is added on the same
                 //activity to listen the callbacks evens
                 resp = result // This will send the result back to dart file.
             }

        }
    }



    private val aadhaarLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { res ->
        if (res.resultCode == Activity.RESULT_OK) {
            val data: Intent? = res.data
            print("res.data"+res.data);
            Log.e("res1", "error_msg---${data?.getStringExtra("COMPLETE_KYC_SUCCESS")}")
            resp?.success( data?.getStringExtra("COMPLETE_KYC_SUCCESS")) // SUCCESS on success result and "FAILED" when API failed 
            Log.e("res1", "KYC_RESPONSE_DATA---${data?.getStringExtra("KYC_RESPONSE_DATA")}") // Response data from the SDK
            Log.e("res1", "KYC_RESPONSE_CODE---${data?.getStringExtra("KYC_RESPONSE_CODE")}") // Response code 
        }
        if(res.resultCode === Activity.RESULT_CANCELED) {
        /// When operation is cancelled from the SDK
            Toast.makeText(this.applicationContext,
                "Operation Cancelled", Toast.LENGTH_SHORT).show()
        }
    }


}

In case there is following runtime exception:

java.lang.RuntimeException: Unable to start activity ComponentInfo{...../com.karza.aadhaarsdk.AadharActivity}: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.

Add a following line in AndroidManifest.xml under the application tag:

android:theme=“@style/Theme.AppCompat.Light”

INPUT PARAMETER DETAILS WHICH LAUNCHING THE SDK ACTIVITY

NameTypeRequiredDescription
MOBILEstringtrueRegistered card holder mobile number (12 digit) with 91
AUTH_TOKENstringtrueCard Holder auth token (Card91 API)
ENVstringtrue"PROD_SANDBOX" for sandbox and "PROD" production environment
PANstringfalseRegistered card holder valid pan number
ORG_IDstringtrueCard Organisation ID

DIFFERENT CALLBACK EVENTS

The events are as follows:

Event NameDescription
SUCCESSOn successful aadhaar KYC completion, developer needs to parse "bundle.getString("COMPLETE_KYC_SUCCESS")" for the same. In this scenario "KYC_RESPONSE_CODE" will comes as 00 and "COMPLETE_KYC_SUCCESS" key value will be "SUCCESS"
FAILEDOn un successful aadhaar KYC completion, developer needs to parse the key "COMPLETE_KYC_SUCCESS" from the bundle, value be "FAILED". In this scenario "KYC_RESPONSE_CODE" will not comes as 00 and detail reason will on the key "KYC_RESPONSE_DATA"
KYC_RESPONSE_DATAAPI service response body will be send back on this param
KYC_RESPONSE_CODEAPI service response code will be send back on this param