身份证云识别服务

对接调试一共分为两部分,一部分为安卓应用对接的SDK,另一部分为应用服务器与商米服务器直接的云对接API。

应用软件集成身份证识别sdk,应用软件后端服务器集成云端api(集成云端api用于保护用户身份信息,保证整个数据链路中,只有应用软件可获取身份证明文信息)

1、接入方 APP 通过 SDK 获取 reqid(有效期5分钟)
2、接入方云通过 reqid 从商米云获取证件信息

注:身份证云识别依赖网络,要求每次网络交互过程不得超过200ms,数据交互延迟高于110ms会降低解码成功率(sdk返回9100X错误),高于200ms会造成读卡失败。使用前请确定网络环境满足要求,sdk提供了延迟检测接口,用户可使用延迟检测接口测试网络。不建议使用物联网卡。

1、APP设计和开发

APP设计

APP设计注意要点:android NFC或者读卡器识别到卡片后会有声音提示,身份证读卡操作是个耗时操作,当声音提示后,不能直接拿走卡片,需要等到读卡成功或者失败再拿开卡片。界面上需要提示用户在有明确反馈前不要拿走卡片,建议:loading+文字提示

APP开发

身份证云识别的Android SDK 的集成、初始化、使用流程、网络延迟检测等。

应用发布前,请确保现场网络环境已达要求,否则将影响识别成功率。

1.1、集成

在模块的build.gradle文件中添加依赖:

dependencies {
    implementation "com.sunmi:SunmiEID-SDK:1.3.2"
}

1.2、接口文档

1.2.1、API 列表

SDK方法列表:

API 说明
EidSDK.init(Context context, String appid, EidCall initCallback);初始化sdk
EidSDK.destroy();销毁sdk,释放资源
EidSDK.getSunmiEidSDKVersion();获取sdk版本
EidSDK.getEidSDKVersion();获取sdk读卡模块版本
EidSDK.startCheckCard(Activity act, EidCall call)开始识别身份证
Bitmap EidSDK.parseCardPhoto(String photo)解密获取身份证后的图片
EidSDK.stopCheckCard(Activity act)停止读身份证
EidSDK.getEidReaderForNfc(int readTimes, EidCall call); 不建议使用获取eidReader,用来读取卡片信息。
返回EidReader实例。
需在初始化成功后使用

EidReader方法列表:不建议使用

API说明
void nfcReadCard(Intent var1);识别身份证或通用类型
void readCard(int var1, final EidReadCardCallBack var2);识别电子身份证

EidCall:

public interface EidCall {
    //code为EidConstants.EID_INIT_SUCCESS时初始化成功,其他code见错误码描述
    void onCallData(int code, String message);
}

EidReadCardCallBack:

public interface EidReadCardCallBack {
    byte[] transceiveTypeB(byte[] var1);

    byte[] transceiveTypeA(byte[] var1);
}

1.2.1、初始化

  • 推荐在Activity中调用SDK的初始化(调用时需保证设备已联网):
public class IApp extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        //Step 1 初始化SDK 传入AppId
        EidSDK.init(this, Constant.APP_ID, new EidCall() {
            @Override
            public void onCallData(int code, String msg) {
                switch (code) {
                    case EidConstants.EID_INIT_SUCCESS:
                        //初始化成功
                        break;
                    default:
                        //初始化失败,只有参数错误时会失败,回调其他code请检查传入参数。
                        break;
                }
            }
        });
    }

}

注:appid需在商米合作伙伴平台(https://partner.sunmi.com)上申请。

1.2.2、开始读取身份证

private void startCheckCard() {
    //Step 2 开启读卡 -> 调用startCheckCard方法,通过回调结果处理业务逻辑
    //注:默认循环读卡,只会回调一次EidConstants.READ_CARD_READY
    EidSDK.startCheckCard(this, new EidCall() {
        @Override
        public void onCallData(int code, String msg) {
            Log.e(TAG, "onCallData-" + code + " , " + msg);
            switch (code) {
                case EidConstants.ERR_NFC_NOT_SUPPORT:
                    Log.e(TAG, "机器不支持NFC");
                    // 该机器不支持NFC功能,无法使用SDK
                    break;
                case EidConstants.ERR_NETWORK_NOT_CONNECTED:
                    Log.e(TAG, "网络未连接,连接网络后重新调用 startCheckCard 方法");
                    setEditText(mState, String.format(Locale.getDefault(), "网络未连接,请联网后重试"));
                    // *** 异常处理: 连接网络后,需要重新调用 startCheckCard 方法 (手动触发,非自动)***
                    break;
                case EidConstants.ERR_NFC_CLOSED:
                    Log.e(TAG, "NFC 未打开,打开后重试 :" + msg);
                    setEditText(mState, String.format(Locale.getDefault(), "NFC未打开,请打开后重试"));
                    //  *** 异常处理: 打开NFC后,需要重新调用 startCheckCard 方法 (手动触发,非自动)***
                    break;
                case EidConstants.READ_CARD_READY:
                    //Step 3 读卡准备完成 -> 业务方可以引导用户开始进行刷卡操作
                    Log.e(TAG, "SDK准备完成,请刷卡");
                    clearData();
                    setEditText(mState, "请刷卡,刷卡时请勿移动卡片");
                    break;
                case EidConstants.READ_CARD_START:
                    //Step 4 读卡中 -> 业务方可以提醒用户"读卡中,请勿移动卡片"
                    Log.e(TAG, "开始读卡,请勿移动");
                    clearData();
                    setEditText(mState, "开始读卡,请勿移动");
                    break;
                case EidConstants.READ_CARD_SUCCESS:
                    //Step 5 读卡成功 -> 返回的msg为reqId,通过 reqId 业务方走云对云方案获取身份证信息
                    //注:如不需要循环读卡,可在此处调用stopCheckCard方法
                    Log.e(TAG, "读卡成功,reqId:" + msg);
                    setEditText(mRequestId, String.format("reqId:%s", msg));
                    mockServerDecode(msg);
                    break;
                case EidConstants.READ_CARD_FAILED:
                    //*** 异常处理: 读卡失败,请重新读卡 ***
                    Log.e(TAG, "读卡失败:" + msg);
                    setEditText(mState, String.format(Locale.getDefault(), "读卡错误,请重新贴卡:%s", msg));
                    break;
                default:
                    //*** 异常处理: 其他失败 - code为错误码,msg为详细错误原因 需要重新调用 startCheckCard 方法 (手动触发,非自动)***
                    Log.e(TAG, "读卡失败:code:" + code + ",msg:" + msg);
                    setEditText(mState, String.format(Locale.getDefault(), "其他错误:%d,%s", code, msg));
                    break;
            }
        }
    });
}

1.2.3、停止读身份证

EidSDK.stopCheckCard(ReadCardActivity.this);

1.2.4、解析身份证头像

通过身份证服务 API 接口获取到返回参数后,其中的 picture 参数为加密数据,需要按照如下步骤解码成图片格式。

picture 作为参数传入到解码方法

Bitmap photo = EidSDK.parseCardPhoto(picture);

注:其他端解码方式详见下方zip包

1.2.5、释放SDK资源

@Override
protected void onDestroy() {
    super.onDestroy();
    EidSDK.destroy();
}

1.2.6、获取 EidReader 实例

private void initEidReader() {
    Log.d("Eid", String.format("商米SDK.Ver:%s, 读卡模块Ver:%s", EidSDK.getSunmiEidSDKVersion(), EidSDK.getEidSDKVersion()));
    //读卡次数建议传3 
    EidReader eid = EidSDK.getEidReaderForNfc(3, this);
}

1.2.7、实现 EidCall 接口

@Override
public void onCallData(int code, String msg) {
    switch (code) {
        case EidConstants.READ_CARD_START:
            mState.setText("开始读卡,请勿移动");
            Log.i(TAG, "开始读卡,请勿移动");
            break;
        case EidConstants.READ_CARD_SUCCESS:
            closeNFCReader();//电子身份证需要关闭
            Log.e("TAG", String.format(Locale.getDefault(), "正在获取身份信息(%s),请稍等.....", msg));
            mState.setText(String.format(Locale.getDefault(), "正在获取身份信息(%s),请稍等.....", msg));
            File file = new File(fileNameBase, "zp.bmp");
            if (file.exists()) {
                file.deleteOnExit();
            }
            getIDCardInfo(msg); //通过card_id请求识读卡片的信息
            break;
        case EidConstants.READ_CARD_FAILED:
            closeNFCReader();//电子身份证需要关闭
            Log.i(TAG, String.format(Locale.getDefault(), "读卡错误,请重新贴卡:%s", msg));
            mState.setText(String.format(Locale.getDefault(), "读卡错误,请重新贴卡:%s", msg));
            break;
        case EidConstants.READ_CARD_DELAY:
            Log.e("TAG", String.format(Locale.getDefault(), "延迟 %sms", msg));
            mState.setText(String.format(Locale.getDefault(), "延迟 %sms", msg));
            break;
        default:
            break;
    }
}

1.2.6、调用NFC识读卡片

  • 识读身份证:
@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    eid.nfcReadCard(intent);
 }
  • 识读电子身份证:
@Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        try {
           Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
           try {
              isodep = IsoDep.get(tagFromIntent);
              isodep.connect();
              if (isodep.isConnected()) {
                  eid.readCard(IDCardType.ECCARD, new EidReadCardCallBack() {
                      @Override
                      public byte[] transceiveTypeB(byte[] data) {
                          return data;
                      }
                      @Override
                      public byte[] transceiveTypeA(byte[] data) {
                          byte[] outData = new byte[data.length];
                          try {
                              outData = isodep.transceive(data);
                          } catch (Exception e) {
                              e.printStackTrace();
                          }
                          return outData;
                      }
                  });
             } else {
                  closeNFCReader()
             }
        } catch (Exception e) {
             e.printStackTrace();
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
 }

    private void closeNFCReader() {
        if (isodep != null) {
            try {
                isodep.close();
                isodep = null;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

1.3、参考DEMO

获取 reqid 方式详见:EidCardDemo-OpenApi (develop_v2_new分支)

获取到 reqid 后需要调用身份证服务 API 接口进行解析,详见下文。

1.4、错误码ErrorCode

错误码错误信息建议操作
4001 初始化-appId参数错误 确认初始化的appId
4003 初始化-网络连接异常 检查网络后重试
4008 初始化-网络未连接 检查网络后重试
4009 初始化-获取设备信息失败 更新系统版本
4010设备不支持NFC机型不支持,更换机型
4011未打开NFC打开NFC
4012缺少权限给APP权限
4013缺少金融读卡SDK安装金融读卡服务
4015未先调用init方法先调用init方法
4016旅行证件参数错误检查旅行证件参数
5001初始化-权限错误
(无身份证服务套餐)
请在parter平台上
购买套餐
-1 读卡错误/初始化-服务
器错误
读卡时请勿移动,
请将证件对准NFC区域,
重新读卡
-3 读卡错误,sdk返回为空 请尝试重新读卡解决
-13001 未获取到客户ID 请填写正确的CID或APPID
-13002 未获取到设备标识 请检查动态权限,开启“android.permission.
READ_PHONE_STATE”权限
-13003 获取到的客户ID无效 填写正确的CID或APPID
-13004 获取到的设备标识无效 获取设备标识无效,
如多次尝试仍无法使用,
请联系客服
-13008 输入参数为空 对照开发文档,检查所填写参数
-13009 输入参数无效 对照开发文档,检查所填写参数
-13010 解析域名异常 填写正确的域名并检查网络是否连接
-13011 重复次数无效 填写重复次数无效,建议
重复次数范围1-5次
-13012 客户端数据错误 读取错误,如多次尝试仍无法
使用,请联系客服
-13013 获取到的客户ID无效 请填写正确的CID或APPID
-13014 SN号无效 设备未授权,请联系客服
-13015 无法获取APP包名 APP未授权,请联系客服
-20001 网络连接异常 请确保网络连接正常,重新读卡
-20002 网络异常中断 请确保网络连接正常,重新读卡
-20003 连接时延服务超时 请确保网络连接正常,重新读卡
-20004 连接时延服务异常 请确保网络连接正常,重新读卡
-20005 连接后台服务超时 请确保网络连接正常,重新读卡
-22003 数据传输错误 请确保网络连接正常,重新读卡
-23001 调用应用服务端地址
访问异常(前端返电子证照)
请确认提供的应用服务端地址正确
-31001 实证服务未接收到数据 请确保网络连接正常,重新读卡
-31006 读取证件失败 请尝试重新读卡解决
-33001 电子证照外部认证失败 请尝试重新读卡解决
-33003 获取eIDReqPacket失败 请尝试重新读卡解决
-33004 获取eidSignPacket失败 请尝试重新读卡解决
-35001 重复请求 重复请求,请重新读卡
-35002 活体鉴权失败 对照开发文档,检查所填写参数
-35003 eid电子证照内部认证失败 eid电子证照认证失败,请重试
-35005 未获得服务端数据 请重试,如多次尝试仍无
法使用,请联系客服
-35006 服务端数据不正确 请重试,如多次尝试仍无
法使用,请联系客服
-35007 后台指令执行失败 请重试,如多次尝试仍无
法使用,请联系客服
-35008 签名验证失败 请重试,如多次尝试仍无
法使用,请联系客服
-36001 前端返电子证照获取
pass包失败(应用侧)
调试错误,请应用对照开
发文档进行检查
-43003 未获取到电子证照个
人信息
未获取到电子证照个人信息,
如多次尝试仍无法使用,请
联系客服
-45001 环境识别码配置错误 请检查环境识别码是否正确
-45003 P10加密失败  
-53001 旅行证件读取异常 请核对三要素是否正确
-53002 NFC-B连接异常 NFC读取异常,请重试
-53006 intent对象为空 请参考开发文档,正确使用sdk
-53007 Context对象为空 请参考开发文档,正确使用sdk
-53009 ip或域名为空 请参考开发文档,正确使用sdk
-53010 port错误 请参考开发文档,正确使用sdk
-53011 ip或域名格式错误 请参考开发文档,正确使用sdk
-53020 参数为空 请参考开发文档,正确使用sdk
-53030 蓝牙对象未初始化 请参考开发文档,正确使用sdk
-53032 IDOCRBTCallBack不能为空 请参考开发文档,正确使用sdk
-53033 BluetoothDevice不能为空 请参考开发文档,正确使用sdk
-53034 错误的蓝牙读卡器 请确保读卡器支持sdk功能
-53036 蓝牙未连接 请连接蓝牙读卡器再进行读卡
-53037 获取SN为空 不支持此设备,请使用实证
服务提供的读卡器
-53038 设备不可连接 检查设备是否已经被连接,
重启设备后尝试。
-54003 旅行证件读取失败 请重试,如多次尝试仍无
法使用,请联系客服
-91001 读取证件数据解析异常 请重试,如多次尝试仍无
法使用,请联系客服
-91005 实证服务器未收到读卡
信息或收到读卡信息已超时
请确保网络环境良好,可
尝试切换网络或移动位置
调整网络情况,重新读卡
-91006 服务繁忙,无可用资源 服务繁忙,请稍后重试
-91007 数据解析异常 请重试,如多次尝试仍无
法使用,请联系客服
-93001 实证服务未获取到应用
返回数据或数据为空
读取超时,请确保网络环境
正常,读取时请勿移动卡片
-93002 错误的卡类型 请使用正确的证件
-93003 读卡初始化异常
(050000失败)
寻找卡片失败,请检查读卡
设备是否支持
-93005 卡认证失败 请确保网络环境良好,可尝试切
换网络或移动位置调整网络情况,重新读卡
-93006 卡片异常返回 读取数据异常,请重新读卡
-93007 不支持的读卡器 读卡器不具备相应功能
-93008 不支持的设备 此设备不支持当前功能,请咨询客服
-99001 其他错误 请重试,如多次尝试仍无法使用,请联系客服
-99002 电子证照服务异常 请重试,如多次尝试仍无法使用,请联系客服
-99008 服务未开通 服务未开通,请联系客服
-99009 包年过期 设备未授权或已过期,请联系客服

2、身份证服务 API 接口

2.1、公共参数说明

2.1.1、概要

请求域名:https://openapi.sunmi.com

openapi的sdk:

golang 版本:https://github.com/sunmi-OS/sunmi-openapi-go-sdk

java 版本:https://github.com/sunmi-OS/sunmi-openapi-java-sdk

  1. 商米身份证服务的接口采用 HTTPS 协议
  2. 请求方式统一使用 POST
  3. 请求数据格式为 application/json

2.1.2、公共header说明

  1. 请求必传header

请求公共参数

参数名 类型 说明
Sunmi-Timestamp Integer 当前时间戳单位秒
Sunmi-Sign string 签名内容
Sunmi-Nonce Integer 6位随机数
Sunmi-Appid string 申请的APPID

返回公共参数

参数名类型说明
codeInteger返回码,参见公共错误码
dataobject正常返回,如有错误不返回
msgstring错误提示信息,如有错误此字段返回错误描述

2.1.3、签名生成说明

签名算法:hmac256

签名生成方式:

1、拼接stringA(即 stringA=json-body + Sunmi-Appid + Sunmi-Timestamp + Sunmi-Nonce)

2、获取申请的AppKey作为hmac256算法的密钥key

3、最后进行 hmac256(stringA, Appkey)最终获取到sign值

2.1.4、公共错误码

返回码说明
1成功返回
20000网关校验缺少参数
20001请求超过有效期
30000开发者身份验证失败
30001开发者权限不足
40000签名验证失败
40001签名失败
50000服务器异常
50001网关异常

2.2、身份证云识别接口

  • 接口地址:/v2/eid/eid/idcard/decode
  • 请求方式:POST

2.2.1、请求参数

参数名类型说明
request_idstringandroid端生成的 request_id
encrypt_factorstring加密因子(8 位大小写字母和数字组成的的随机字符串,建议每次访问随机生成)

2.2.2、返回参数

成功返回

参数名类型说明
codestring返回码,参见常用错误码表
dataobject返回身份证信息,参见下方 data 域内容
msgstring错误提示信息,如有错误此字段返回错误描述

成功返回 data 域内容

参数名类型说明
infostring身份证信息密文,解密方式参见解密说明

示例:

{
    "code": 1,
    "data": {
        "info": "xxxxxxxxxxxxx"
    },
    "msg": ""
}

失败返回

参数名类型说明
codeInteger返回码,参见常见业务错误码
datastring空字符串
msgstring错误提示信息,如有错误此字段返回错误描述
{
    "code": 20000,
    "data": "",
    "msg": "Missing required parameters"
}

2.2.3、解密说明

  • 加密方式:DES_CBC_PKCS5Padding 加密
  • 加密因子:传入的 encrypt_factor
  • 加密key:开发者平台申请的AppKey

解密流程:

  1. 对 info 字符串 base64 解码(standard 标准解码),解码完为 stringA
  2. 对 stringA 字符串进行 des 解密,截取AppKey前8位作为秘钥key,向量 iv 作为 encrypt_factor 加密因子,解密完为 stringB
  3. stringB 即是身份证信息的 json 格式,内容参见下方身份证云解码信息

身份证云解码信息:

参数名类型说明
base_infoobject身份证基础信息,参见下方身份证基础信息说明
dnstring指纹信息
picturestring身份证头像照片
appeidcodestring应用网络身份标记,同一个身份有一个编码

身份证基础信息:

参数名类型说明
namestring姓名
nationstring民族(如:汉)
sexstring性别(如:男)
idnumstring身份证号码
idTypestring证件类型,见下方证件类型说明
birthDatestring出生年月日(如:20010305)
addressstring身份证住址
beginTimestring身份证有效期限开始时间(如:20180305)
endTimestring身份证有效期限结束时间(如:20180305)
signingOrganizationstring签发机关

示例:

 "base_info": {
      "address": "xx省xx市xxxx路xx号",
      "beginTime": "20180305",
      "endTime": "20180305",
      "birthDate": "20010305",
      "idType": "01",
      "idnum": "xxxxxxx",
      "name": "孙小红",
      "nation": "汉",
      "sex": "男",
      "signingOrganization": "xx市xx公安局"
    },
    "dn": "xxxxxxxxxx",
    "picture": "xxxxxxxxx",
    "appeidcode": "xxxxxxxxx",

 2.2.4、常见业务错误码

返回码说明
50000服务器异常
14200没有权限(request_id异常)
14201sdk版本过低
14202无可使用次数
14203读卡错误
14204参数异常