最近对接银联的云闪付小程序,发现有很多地方文档写的都不是很全,今天给大家说一下对接需要避免哪些坑。

前端项目

引入Js

接入方使用云闪付 APP 小程序 API 前,需在调用页面(必须为HTTPS)引入插件 upsdk.js 文件

// 普通项目引入
<script type="text/javascript" src="https://open.95516.com/s/open/js/upsdk.js"></script>


// vue项目引入
npm install upsdk-vue

版本要求:[云闪付 APP 安卓 8.0.5 及以上,IOS8.0.4 及以上]

初始化

upsdk.config({
  // 必填,接入方的唯一标识
  appId: "",
  // 必填,生成签名的时间戳  单位是秒
  timestamp: "",
  // 必填,生成签名的随机串  16位字符串
  nonceStr: "",
  // 必填,签名因子包括 appId、frontToken、nonceStr、 timestamp、url
  signature: "",
  // true:开启,云闪付APP会将调试信息toast
  debug: true
})

唤起支付

TN号的生成, 手机支付控件demo 传送

// 如果是Vue需要在Main.js引入以下代码
import upsdk from 'upsdk-vue'

upsdk.pay({
    tn: '支付流水号 TN 号',
    success: function(){
        // 支付成功, 开发者执行后续操作。
    },
    fail: function(err){
        // 支付失败, err.msg 是失败原因描述, 比如 TN 号不合法, 或者用户取消了交易等等。
    }
});

后端项目

FrontToken的获取

/**
     * 获取云闪付 frontToken
     *
     * {"resp":"00","msg":"成功","params":{"frontToken":"zOOrOQmURU2t7aJgUEDJxA==","expiresIn":7200}
     * @return
     */
    public String getYsfFrontToken() {
        Map<String, String> sortedParams = new HashMap<>();
        sortedParams.put("appId", "");
        sortedParams.put("secret", "");
        // 16位随机字符串
        sortedParams.put("nonceStr", getRandomString(16));
        // 以秒为单位
        sortedParams.put("timestamp", System.currentTimeMillis() / 1000 + "");
        // 签名值,签名因子包括(appId,nonceStr, secret, timestamp)
        String signContent = SignUtils.getSignContent(sortedParams);
        // 删除secret
        sortedParams.remove("secret");
        // appId,nonceStr, secret, timestamp
        sortedParams.put("signature", sha256(signContent).getBytes());
        String strJson = new JSONObject(sortedParams).toString();
        String result =  postJson("https://open.95516.com/open/access/1.0/frontToken", "application/json", strJson);
        logger.info("获取云闪付frontToken===》" + result);
        JSONObject resultJson = new JSONObject(result);
        if ("00".equals(resultJson.getStr("resp"))) {
            JSONObject paramsJson = new JSONObject(resultJson.getStr("params"));
            return paramsJson.getStr("frontToken");
        }
        return null;
    }

注意: 这个frontToken 有有效期,建议存放在Redis中。

获取 upsdk.config 参数

Map<String, String> map = new HashMap<String, String>();
map.put("appId", "");
map.put("frontToken", "");
map.put("url", url);
// 16位随机字符串
map.put("nonceStr", YSRequestUtil.getRandomString(16));
// 以秒为单位
map.put("timestamp", System.currentTimeMillis() / 1000 + "");
// 排序
String signContent = com.ydzy.common.util.yspay.util.SignUtils.getSignContent(map);
// 签名: appId、frontToken、nonceStr、 timestamp、url
map.put("signature", sha256(signContent).getBytes());
// 这个frontToken不需要传递给前端
map.remove("frontToken");

upsdk.config({
  // 必填,接入方的唯一标识
  appId: "",
  // 必填,生成签名的时间戳  单位是秒
  timestamp: "",
  // 必填,生成签名的随机串  16位字符串
  nonceStr: "",
  // 必填,签名因子包括 appId、frontToken、nonceStr、 timestamp、url
  signature: "",
  // true:开启,云闪付APP会将调试信息toast
  debug: true
})

相关公共方法

生成指定长度的字符串工具方法

/**
     * 生成指定长度的字符串
     *
     * @param length 字符串长度
     * @return
     */
    public static String getRandomString(int length) {
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(3);
            long result = 0;
            switch (number) {
                case 0:
                    result = Math.round(Math.random() * 25 + 65);
                    sb.append(String.valueOf((char) result));
                    break;
                case 1:
                    result = Math.round(Math.random() * 25 + 97);
                    sb.append(String.valueOf((char) result));
                    break;
                case 2:
                    sb.append(String.valueOf(new Random().nextInt(10)));
                    break;
            }
        }
        return sb.toString();
    }

Map排序工具方法

public static String getSignContent(Map<String, String> sortedParams) {
        StringBuffer content = new StringBuffer();
        List<String> keys = new ArrayList<>(sortedParams.keySet());
        Collections.sort(keys);
        int index = 0;
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = sortedParams.get(key);
            if (StringUtils.areNotEmpty(key, value)) {
                content.append((index == 0 ? "" : "&") + key + "=" + value);
                index++;
            }
        }
        return content.toString();
    }

sha256工具方法

public static String sha256(byte[] data) {
         try {
             MessageDigest md = MessageDigest.getInstance("SHA-256");
             return bytesToHex(md.digest(data));
         } catch (Exception ex) {
             logger.info("Never happen.", ex);
             return null;
         }
}

Http工具方法

public static String postJson(String generalUrl, String contentType, String params) {
        try {
            URL url = new URL(generalUrl);
            // 打开和URL之间的连接
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            // 设置通用的请求属性
            connection.setRequestProperty("Content-Type", contentType);
            connection.setRequestProperty("Connection", "Keep-Alive");
            connection.setUseCaches(false);
            connection.setDoOutput(true);
            connection.setDoInput(true);
            // 得到请求的输出流对象
            DataOutputStream out = new DataOutputStream(connection.getOutputStream());
            out.write(params.getBytes("UTF-8"));
            out.flush();
            out.close();

            // 建立实际的连接
            connection.connect();
            // 获取所有响应头字段
            Map<String, List<String>> headers = connection.getHeaderFields();
            // 定义 BufferedReader输入流来读取URL的响应
            BufferedReader in = null;
            in = new BufferedReader(
                    new InputStreamReader(connection.getInputStream(), "UTF-8"));
            String result = "";
            String getLine;
            while ((getLine = in.readLine()) != null) {
                result += getLine;
            }
            in.close();
            return result;
        } catch (Exception e) {
            log.info("发送请求失败:异常信息:" + e.getMessage());
            e.printStackTrace();
        }
        return null;
    }

相关文档

https://qxwouffjun.feishu.cn/docs/doccnZIjijXe0aDbP98mQ0Dvxhd#avGcJU

https://qxwouffjun.feishu.cn/docs/doccnKWnxdjnbNj2JZHhdsE0COf

https://qxwouffjun.feishu.cn/docs/doccntIbkpdwdce2meNup1Yk2Fb

目前云闪付最新的这个文档,没有获取frontToken接口。

云闪付小程序官方技术交流群:457767672,不懂的可以进群资讯。

以上代码写完记得上传发布,然后通过云闪付App进行测试,不要在 云闪付开发者工具 里面进行测试,这个开发者工具无法唤起支付。