银联商务扫码支付-被扫业务

最近在业务系统中集成了银联商务扫码支付-被扫业务产品,最开始还错以为是银联,殊不知此银联非彼银联,银联商务跟银联好像没什么太大关系,网上搜到的资料也寥寥无几,在此记录一下集成的过程。

准备工作

  • 平台地址:https://open.chinaums.com/index
  • 接口文档地址:https://open.chinaums.com/resources/?code=331548310690296&url=0f3739ca-ae2a-4a2d-b83d-e5dec596bbd1
  • sdk下载:https://open.chinaums.com/resources/?code=101528298407846&url=8838eff5-3809-4f8e-aae2-2c5b00ef265e
  • 测试环境参数:appId,appKey,mId(商户号),tId(终端号)可从《接口测试报告模板》中获取

开放平台仿真测试工具

从银联商务平台网站上下载仿真测试工具,直接安装到谷歌浏览器,提示不能使用,可按照以下方法使用
安装插件

安装成功后,可在浏览器右上角看到如下标志


安装成功
仿真测试工具

sdk是否满足需求

sdk中封装了token的获取,request类中封装了请求地址,如果集成被扫业务,需要添加需要调用的相关接口的request,或者写一个公用的request,在请求前替换请求地址。


sdk源码

如果使用request方式,封装请求参数是包含在data中,类似如图方式
request封装的请求参数格式

而实际被扫业务中接口访问是不需要封装到data中的,由于我没有想到更好更简洁的方式使用sdk中方法,所以我放弃使用了sdk,未用sdk中封装的获取token的方法

接口对接

被扫业务的接口其实很简单,我总结我认为我集成过程中比较困扰我的点

  1. 因为看过银联接口文档,在下单的的请求参数会添加回调路径参数,以通知交易结果,下单返回成功只代表下单成功,并不代表交易已成功,银联商务的返回码是00,注释为交易成功,经过与技术支持沟通,
  2. 接口文档的最后有一句“,须在当天进行查询操作,仅当查询返回明确订单状态,该笔订单支持以商户订单号的方式进行退款。”——什么情况下需要调用查询接口?订单状态不明确又指的什么?
  3. 请求头设置,访问前需要提供一个token,直接封装一个工具类放在里面即可,下面我把工具类代码放在下面
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;

import java.util.Date;
import java.util.UUID;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509TrustManager;


/**
 * @author zhangjz
 * @date 2019/6/3
 */
public class PayUtil {
    private static Logger logger = LoggerFactory.getLogger(PayUtil.class); // 日志记录
    private static RequestConfig requestConfig = null;
    static
    {
        // 设置请求和传输超时时间
        requestConfig = RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(10000).build();
    }
    public static String send(String url, String entity, String appId, String appKey){
        CloseableHttpResponse response = null;
        String resStr = null;
        CloseableHttpClient httpClient=null;
        if(isHttps(url)){
            httpClient = HttpClients.custom().setSSLContext(generateSSLContext()).build();
        }else{
            httpClient = HttpClients.createDefault();
        }
        try {
            String authorization = getOpenBodySig(appId, appKey, entity);
            HttpPost httpPost = new HttpPost(url);
            httpPost.setConfig(requestConfig);
            httpPost.addHeader("Authorization", authorization);
            httpPost.setHeader("Content-type", "application/json; charset=utf-8");
            StringEntity se = new StringEntity(entity,"UTF-8");
            se.setContentType("application/json");
            httpPost.setEntity(se);
            response = httpClient.execute(httpPost);
            HttpEntity entity1 = response.getEntity();
            resStr = null;
            if(entity1 != null){
                resStr = EntityUtils.toString(entity1, "UTF-8");
            }
        } catch (Exception e) {
            logger.error("post请求提交失败:url{},参数{}" ,url,entity, e);
            return null;
        }finally{
            if (response!=null){
                try {
                    httpClient.close();
                    response.close();
                } catch (IOException e) {
                    logger.info("链接释放失败",e);
                }
            }
        }
        return resStr;
    }
    public static String getOpenBodySig(String appId, String appKey, String body) throws Exception{
        String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
        String nonce = UUID.randomUUID().toString().replace("-", "");
        byte[] data = body.getBytes("UTF-8");
//        System.out.println("data:\n" + body);
        InputStream is = new ByteArrayInputStream(data);
        String bodyDigest = testSHA256(is);
        String str1_C = appId+timestamp+nonce+bodyDigest;

        byte[] localSignature = hmacSHA256(str1_C.getBytes(), appKey.getBytes());

        String localSignatureStr = Base64.encodeBase64String(localSignature);
        logger.info("Authorization:\n" + "OPEN-BODY-SIG AppId="+"\""+appId+"\""+", Timestamp="+"\""+timestamp+"\""+", Nonce="+"\""+nonce+"\""+", Signature="+"\""+localSignatureStr+"\"");
        return ("OPEN-BODY-SIG AppId="+"\""+appId+"\""+", Timestamp="+"\""+timestamp+"\""+", Nonce="+"\""+nonce+"\""+", Signature="+"\""+localSignatureStr+"\"");
    }

    private static String testSHA256(InputStream is) {
        try {
            return DigestUtils.sha256Hex(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static byte[] hmacSHA256(byte[] data, byte[] key) throws NoSuchAlgorithmException, InvalidKeyException {
        String algorithm = "HmacSHA256";
        Mac mac = Mac.getInstance(algorithm);
        mac.init(new SecretKeySpec(key, algorithm));
        return mac.doFinal(data);
    }

    /**
     * 检测是否https
     *
     * @param url
     */
    private static boolean isHttps(String url) {
        return url.startsWith("https");
    }

    /*为了访问https接口*/
    private static SSLContext generateSSLContext(){
        SSLContext sslContext = null;
        try {
            sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, new X509TrustManager[]{new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                }

                @Override
                public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                }

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            }}, null);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
        return sslContext;
    }
}

总结

第一次集成第三方支付产品,还是所获颇丰的,如有说的不到位的地方,望小伙伴们能够指正。

你可能感兴趣的:(银联商务扫码支付-被扫业务)