支付宝二维码脱机认证库在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调用过程,在另一篇文章中记录。


相关文章
|
2月前
|
XML Java 数据库
安卓项目:app注册/登录界面设计
本文介绍了如何设计一个Android应用的注册/登录界面,包括布局文件的创建、登录和注册逻辑的实现,以及运行效果的展示。
212 0
安卓项目:app注册/登录界面设计
|
1月前
|
Java 测试技术 持续交付
【入门思路】基于Python+Unittest+Appium+Excel+BeautifulReport的App/移动端UI自动化测试框架搭建思路
本文重点讲解如何搭建App自动化测试框架的思路,而非完整源码。主要内容包括实现目的、框架设计、环境依赖和框架的主要组成部分。适用于初学者,旨在帮助其快速掌握App自动化测试的基本技能。文中详细介绍了从需求分析到技术栈选择,再到具体模块的封装与实现,包括登录、截图、日志、测试报告和邮件服务等。同时提供了运行效果的展示,便于理解和实践。
112 4
【入门思路】基于Python+Unittest+Appium+Excel+BeautifulReport的App/移动端UI自动化测试框架搭建思路
|
2月前
|
安全 Java 网络安全
Android远程连接和登录FTPS服务代码(commons.net库)
Android远程连接和登录FTPS服务代码(commons.net库)
34 1
|
2月前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
116 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
3月前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
151 15
一个Android App最少有几个线程?实现多线程的方式有哪些?
|
2月前
|
Ubuntu Shell API
Ubuntu 64系统编译android arm64-v8a 的openssl静态库libssl.a和libcrypto.a
Ubuntu 64系统编译android arm64-v8a 的openssl静态库libssl.a和libcrypto.a
|
3月前
|
存储 开发工具 Android开发
使用.NET MAUI开发第一个安卓APP
【9月更文挑战第24天】使用.NET MAUI开发首个安卓APP需完成以下步骤:首先,安装Visual Studio 2022并勾选“.NET Multi-platform App UI development”工作负载;接着,安装Android SDK。然后,创建新项目时选择“.NET Multi-platform App (MAUI)”模板,并仅针对Android平台进行配置。了解项目结构,包括`.csproj`配置文件、`Properties`配置文件夹、平台特定代码及共享代码等。
291 2
|
3月前
|
XML Android开发 数据格式
🌐Android国际化与本地化全攻略!让你的App走遍全球无障碍!🌍
在全球化背景下,实现Android应用的国际化与本地化至关重要。本文以一款旅游指南App为例,详细介绍如何通过资源文件拆分与命名、适配布局与方向、处理日期时间及货币格式、考虑文化习俗等步骤,完成多语言支持和本地化调整。通过邀请用户测试并收集反馈,确保应用能无缝融入不同市场,提升用户体验与满意度。
127 3
|
3月前
|
测试技术
基于LangChain手工测试用例转App自动化测试生成工具
在传统App自动化测试中,测试工程师需手动将功能测试用例转化为自动化用例。市面上多数产品通过录制操作生成测试用例,但可维护性差。本文探讨了利用大模型直接生成自动化测试用例的可能性,介绍了如何使用LangChain将功能测试用例转换为App自动化测试用例,大幅节省人力与资源。通过封装App底层工具并与大模型结合,记录执行步骤并生成自动化测试代码,最终实现高效自动化的测试流程。
|
2月前
|
安全 网络安全 Android开发
深度解析:利用Universal Links与Android App Links实现无缝网页至应用跳转的安全考量
【10月更文挑战第2天】在移动互联网时代,用户经常需要从网页无缝跳转到移动应用中。这种跳转不仅需要提供流畅的用户体验,还要确保安全性。本文将深入探讨如何利用Universal Links(仅限于iOS)和Android App Links技术实现这一目标,并分析其安全性。
389 0

热门文章

最新文章