专栏首页独行猫a的沉淀积累总结支付宝二维码脱机认证库在android的app下测试过程记录

支付宝二维码脱机认证库在android的app下测试过程记录

今天收到了第三方库(支付宝的二维码脱机认证库,用来脱机验证支付宝的支付二维码),于是兴奋着调用一下试试,如果测试成功,那么做应用自然也没太大问题。

以下为测试过程记录。正好又练手一下在android平台调用动态库及调用第三方动态库的过程。

说明:第三方库要放置于:eclipse工程下libs-armeabi目录下: 包括:libalipayqrcode.so和libposoffline.so,load先后顺序不能错。 其中: libalipayqrcode.so使用ndk编译。稍后附带android.mk文件 libposoffline.so为第三方库(支付宝二维码脱机认证库)

先附个测试结果截图:

过程记录:

android.mk文件如下:

LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := alipayqrcode LOCAL_SRC_FILES := alipayqrcode.c LOCAL_LDLIBS := -llog LOCAL_LDFLAGS += libposoffline.so include $(BUILD_SHARED_LIBRARY)

alipayqrcode.c文件内容:

#include <jni.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include "pos_crypto.h"
#include "alipayqrcode.h"

#include "android/log.h"
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO,  LOG_TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, fmt, ##args)

#define LOG_TAG "alipayqrcode"

char print_buf[1024] = {0};

int hex_string_to_bytes(
	char* hex_string, 
	int hex_string_len, 
	unsigned char* bytes, 
	int bytes_len);
char* bytes_to_hex_string(
	char* print_buf, 
	int print_buf_len, 
	const unsigned char* bytes, 
	int len);
void mock_qrcode(unsigned char* qrcode, int* qrcode_len);

unsigned char hex_of_char(char c);

/**
 * 验证二维码例程
 *
 * 本例程演示了如何使用支付宝离线安全库对二维码进行验证
 * 例程中使用了mock_qrcode函数生成一个合法的二维码,并验证该二维码的有效性
 * 
 * */
void check_qrcode_demo(){

	int ret = 0;
	const char* pos_param = NULL;
	int qrcode_len = 0;
	unsigned char qrcode[512] = {0};
	const char* key_list = KEY_LIST;
	const char* card_type_list = CARD_TYPE_LIST;
	LOGI("===========准备数据================\n");
	
	LOGI("============进行POS初始化=============\n");	
	/**
	 * 请在POS启动时执行POS初始化
	 * 初始化时请提供
	 * 1. 从支付宝处申请得到的秘钥簇 json列表 形式
	 * 2. POS机器支持结算的卡类型 json列表 此处以卡类型 【WH000001 ANT00001】 示例
	 * 注:支持卡类型【ANT00001】代表支持支付宝公交付款
	 */
	ret = init_pos_verify(key_list, card_type_list);
	if(ret != SUCCESS){
		LOGI("初始化POS失败!\n");
		switch(ret){
			case ILLEGAL_PARAM:
				LOGI("初始化参数格式错误!请检查参数是否符合json列表格式且各字段正确。\n");
			break;
			case NO_ENOUGH_MEMORY:
				LOGI("内存不足,极端错误,请检查程序运行空间是否足够。\n");
			break;
			case SYSTEM_ERROR:
				LOGI("系统异常!请联系支付宝技术人员。\n");
			break;
			default:
			break;
		}
		return;
	}

	/**
	 * mock一个用户传入的二维码数据qrcode
	 * 开发者应当从扫码头获取用户离线公交码
	 */
	mock_qrcode(qrcode, &qrcode_len);

	/**
	 * pos_param中填入商户pos相关信息 至少包括:
	 *		- pos_id	(商户下唯一的pos号)
	 *		- type		(脱机记录类型,只刷一次闸机计费的场景下,类型为"SINGLE")
	 * 		- subject	(脱机记录标题,建议放入公交路线)
	 *		- record_id	(记录id,商户下本次脱机记录唯一id号,record_id必须保证商户唯一,建议通过POS,时间等信息拼装)
	 * 注意:pos_param的长度不能大于1024字节!
     */
	pos_param = "{\"pos_id\":\"sh001\",\"type\":\"SINGLE\",\"subject\":\"bus192\",\"record_id\":\"sh001_20160514140218_000001\"}";
	LOGI("===========准备数据结束================\n");
	
	LOGI("===========校验二维码开始================\n");
	//拼装验证请求
	VERIFY_REQUEST_V2 verify_request;
	//装入二进制格式的二维码
	verify_request.qrcode = qrcode;
	//装入二进制二维码长度
	verify_request.qrcode_len = qrcode_len;
	//装入pos_param
	verify_request.pos_param = pos_param;
	//装入本次消费金额 如果生成脱机记录时还无法确定消费金额 装入0(单位:分)
	verify_request.amount_cent = AMOUNT_CENT;
	
	VERIFY_RESPONSE_V2 verify_response;
	verify_response.uid = (char*)malloc(17);
	verify_response.uid_len = 17;
	verify_response.record = (char*)malloc(2048);
	verify_response.record_len = 2048;
	verify_response.card_no = (char*)malloc(32);
	verify_response.card_no_len = 32;
	verify_response.card_data = (unsigned char*)malloc(128);
	verify_response.card_data_len = 128;
	verify_response.card_type = (char*)malloc(16);
	verify_response.card_type_len = 16;
	/**
	 * 调用接口验证二维码的有效性
	 */
	ret = verify_qrcode_v2(&verify_request, &verify_response);

	/**
	 * 处理返回的结果
	 */
	if(ret != SUCCESS){
		switch(ret){
			case MALFORMED_QRCODE:
				LOGI("二维码格式错误!请提示用户二维码错误。\n");
			break;
			case QRCODE_INFO_EXPIRED:
				LOGI("二维码过期!请提示用户刷新二维码。\n");
			break;
			case QRCODE_KEY_EXPIRED:
				LOGI("二维码密钥过期!请提示用户联网后刷新二维码再使用。\n");
			break;
			case POS_PARAM_ERROR:
				LOGI("商户传入的pos_param错误,请检查传入的pos_param。\n");
			break;
			case QUOTA_EXCEEDED:
				LOGI("单笔额度超限!请提示用户由于额度限制无法过闸机。\n");
			break;
			case NO_ENOUGH_MEMORY:
				LOGI("内存不足,极端错误,请检查程序运行空间是否足够。\n");
			break;
			case QRCODE_DUPLICATED:
				LOGI("二维码重复!验证失败。\n");
			break;
			case SYSTEM_ERROR:
				LOGI("系统异常!请联系支付宝技术人员。\n");
			break;
			default:
			break;
		}
		LOGI("二维码校验结束!验证失败,不放行!\n");
		LOGI("===========验证二维码例程 结束================\n");
		free(verify_response.uid);
		free(verify_response.record);
		free(verify_response.card_no);
		free(verify_response.card_data);
		free(verify_response.card_type);
		return;
	}
	LOGI("从二维码中获取到的uid: %s\n", verify_response.uid);
	LOGI("验证成功后,返还的脱机记录: %s\n", verify_response.record);
	LOGI("二维码中的卡类型为: %s\n",verify_response.card_type);
	LOGI("二维码中的卡号为: %s\n", verify_response.card_no);

	bytes_to_hex_string(print_buf, sizeof(print_buf), 
						verify_response.card_data, verify_response.card_data_len);
	LOGI("二维码中的二进制卡数据(hex string形式):%s\n", print_buf);

	/**
	 * 1.商户可以根据uid判断是否为同一用户重复交易
	 */
	
	/**
	 * 2.商户可以根据qrcode判断是否为重复二维码
	 *   此判断也可以放在校验二维码前执行,商户可以自行选择
	 */

	/**
	 * 3.商户需要根据卡类型、卡号、卡数据 综合判断该卡的合法性、以及是否受理该卡
	 * 请商户保留 可受理 的脱机记录
	 */
	 
	free(verify_response.uid);
	free(verify_response.record);
	free(verify_response.card_no);
	free(verify_response.card_data);
	free(verify_response.card_type);

	LOGI("验证成功,请放行!\n");
	LOGI("===========验证二维码例程 结束================\n");
}


/**
 * mock一个用户传入的二维码数据qrcode
 * 此处是使用QRCODE_HEX_DATA mock出的用户二维码数据 
 * 开发者测试时请使用二维码工具生成一个新的QRCODE_HEX_DATA后
 * 装入宏定义中QRCODE_HEX_DATA,再执行mock
 */
void mock_qrcode(unsigned char* qrcode, int* qrcode_len){
	
	char qrcode_hex[] = QRCODE_HEX_DATA;
	int qrcode_hex_len = strlen(qrcode_hex);
	*qrcode_len = strlen(qrcode_hex)/2;
	hex_string_to_bytes(qrcode_hex, qrcode_hex_len, qrcode, *qrcode_len);
}
/**
* 字节数组转hex格式字符串
* @param print_buf: 十六进制字符串buffer
* @param print_buf_len: 十六进制字符串buffer长度
* @param bytes: 二进制数据
* @param bytes_len: 二进制数据长度
*/
char* bytes_to_hex_string(
	char* print_buf, 
	int print_buf_len, 
	const unsigned char* bytes, 
	int len) {

	int i = 0;

	/**
	* 入参校验
	*/ 
	if(print_buf == NULL || bytes == NULL || (len * 2 + 1) > print_buf_len) {
		return NULL;
	}

	for(i = 0; i < len; i++) {
		print_buf[i * 2] = g_hex_map_table[(bytes[i] >> 4) & 0x0F];
		print_buf[i * 2 + 1] = g_hex_map_table[(bytes[i]) & 0x0F];
	}
	/**
	* 填充字符串结束符
	*/
	print_buf[i * 2] = '\0';
	/**
	* 返回目标地址
	*/
	return print_buf;
}
/**
* hex格式字符串转字节数组
* @param hex_string: 十六进制字符串
* @param hex_string_len: 十六进制字符串长度
* @param bytes: 二进制数据存储空间
* @param bytes_len: 目标空间长度
*/
int hex_string_to_bytes(
	char* hex_string, 
	int hex_string_len, 
	unsigned char* bytes, 
	int bytes_len) {
	
	int i = 0;

	/**
	* 校验十六进制字符串长度必须偶数,并且目标存储空间必须足够存放转换后的二进制数据
	*/
	if((hex_string_len % 2 != 0) || (bytes_len * 2 < hex_string_len)) {
		return -1;
	}
	
	for(i = 0; i < hex_string_len; i += 2) {
		bytes[i/2] = ((hex_of_char(hex_string[i]) << 4) & 0xF0) | 
					(hex_of_char(hex_string[i + 1]) & 0x0F);
	}
	return 1;
}
/**
* hex格式char转二进制
*/
unsigned char hex_of_char(char c) {
	unsigned char tmp = 0;
	if(c >= '0' && c <= '9') {
		tmp = (c - '0');
	}
	else if(c >= 'A' && c <= 'F') {
		tmp = (c - 'A' + 10);
	}
	else if(c >= 'a' && c <= 'f') {
		tmp = (c - 'a' + 10);
	}
	return tmp;
}


int test( void) {
	check_qrcode_demo();
	return 0;
}


jint JNICALL jni_check_qrcode_test(void)
{
	LOGD("%s>>> ..%s..%d..enter",LOG_TAG,__FUNCTION__,__LINE__);
	test();
	return 0;
}


//定义批量注册的数组,是注册的关键部分
static const JNINativeMethod gMethods[] = { 
    {"qrcode_test",        /* func2是在java中声明的native函数名 */
     "()I",          /* "()V"是函数的签名,可以通过javah获取。*/
     (void*)jni_check_qrcode_test
    }
};

// extern "C" {
	JNIEXPORT jint JNI_OnLoad(JavaVM* vm,void *reserved)
{
	JNIEnv *env =NULL;
	jint result = -1;
	static const char* kClassName="com/example/ndktest/Test";
	jclass clazz;
	
	LOGD("%s>>> ..%s..%d..enter",LOG_TAG,__FUNCTION__,__LINE__);
	
	if( (*vm)->GetEnv(vm,(void**)&env,JNI_VERSION_1_4) != JNI_OK )
	{
		return result;
	}

	clazz = (*env)->FindClass(env,kClassName);
	if( clazz == NULL )
	{
		return -1;
	}

	if( (*env)->RegisterNatives( env,clazz, gMethods, sizeof(gMethods)/sizeof(gMethods[0]) ) != JNI_OK )
	{
		return -1;
	}

	return JNI_VERSION_1_4;
}
// }

需要注意的地方,定义批量注册的数组,是注册的关键部分。其中的 "()I" 知道是干啥的吗?因为我这的qrcode_test不带参数,所以签名是()I,

如果我的qrcode_test方法带两个参数,这里签名应该是 (II)I, 否则java层通过JNI调用时,会报找不到方法的。

JNI_Onload函数,当启动程序的时候,会加载库文件,就会调用这个函数。接着在onload函数中,注册了nativemethods。 methods数组中第一个和第三个参数比较好理解,那么第二个参数呢?其实第二个参数可以参考头文件,一模一样拉过来就好了。其中的意思就是()里的表示函数的参数,()表示没有参数,(II)表示两个参数,都是int。后面跟的Ljava/lang/String表示返回值是String类型的,I表示的是int类型。

还有个地方要注意了,包名一定不能错,我的是 static const char* kClassName="com/example/ndktest/Test"

另外一点需注意的是应用层注意load顺序,先load第三方库,再load自己的库....

至此,调用第三方支付宝库的测试就完成了,总共尝试了三种不同的测试方法。(本来测一个就够了,就是要举一反三,多练习练习)

在linux_x86平台下的测试过程在另一篇中记录。

单纯的在android平台通过adb shell调用过程,在另一篇文章中记录。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 支付宝二维码脱机认证库测试过程记录(andorid平台adb shell验证)

    最近在调试支付宝给提供的二维码脱机认证库,他们给提供了几个文档和 libposoffline.so库文件。

    特立独行的猫a
  • 动手写物联网服务平台(一、前言)

    用雷布斯的话说,"5G+AI+IoT就是下一代超级互联网。”,雷军给出了他对于互联网未来发展的判断。在雷军看来,5G不仅将给智能终端带来巨大的机遇,更为重要的是...

    特立独行的猫a
  • 调试支付宝脱机认证接口遇到的问题总结

    通过 android的JNI调用支付宝脱机认证库本地接口时,我欲返回一个类的实例,但是却报了几个错,最后查出来了原因。在此总结下。

    特立独行的猫a
  • 支付宝二维码脱机认证库测试过程记录(andorid平台adb shell验证)

    最近在调试支付宝给提供的二维码脱机认证库,他们给提供了几个文档和 libposoffline.so库文件。

    特立独行的猫a
  • Python 技术篇-音频mp3格式转wav格式,高保真

    从微信下载下来的语音是 mp3 格式的,想调用百度语音 api,发现不支持 mp3,支持 wav。 准备: 需要安装 pydub 库,直接pip insta...

    小蓝枣
  • Fluentd输入插件:in_forward用法详解

    in_forward插件通常用于从其他节点接收日志事件,这些节点包括其他Fluentd实例、fluent-cat命令行或者Fluentd客户端程序。这是目前效率...

    Fluentd中文网
  • 前端之HTML DOM操作

    当网页被加载时,浏览器会创建页面的文档对象模型(Document Object Model)。

    山河木马
  • netty案例,netty4.1基础入门篇五《NettyServer字符串编码器》

    netty通信就像一个流水channel管道,我们可以在管道的中间插入一些‘挡板’为我们服务。比如字符串的编码解码,在前面我们使用new StringDecod...

    小傅哥
  • netty案例,netty4.1基础入门篇六《NettyServer群发消息》

    在微信或者QQ的聊天中我们经常会用到一些群聊,把你的信息发送给所有用户。那么为了实现群发消息,在netty中我们可以使用ChannelGroup方式进行群发消息...

    小傅哥
  • 深度解析云计算的12个顶级安全威胁

    导读: 越来越多企业数据和应用程序正在转向于云计算,这造成了云上面临更多的安全挑战。以下是使用云服务时,所要面对的12个顶级安全威胁。 云计算不断改变企业在使用...

    企鹅号小编

扫码关注云+社区

领取腾讯云代金券