• 微信支付JAVA V3APP下单整合


    1. 背景介绍

    v3版微信支付通过商户证书和平台证书加强了安全性,java版sdk包wechatpay-apache-httpclient内部封装了安全性相关的签名、验签、加密和解密工作,降低了开发难度。下面几个特性的实现,更方便了开发者。

    1. 平台证书自动更新,无需开发者关注平台证书有效性,无需手动下载更新;
    2. 执行请求时将自动携带身份认证信息,并检查应答的微信支付签名。

    如果文档中有错误的地方,需要路过的大佬指出,我会尽快更改。跪谢。。。

    2. API证书

    2.1 API 密钥设置( 一定要设置的!!!! )

    请登录商户平台进入【账户中心】->【账户设置】->【API安全】->【APIv3密钥】中设置 API 密钥。

    具体操作步骤请参见:什么是APIv3密钥?如何设置?

    2.2 获取 API 证书

    请登录商户平台进入【账户中心】->【账户设置】->【API安全】根据提示指引下载证书。

    具体操作步骤请参见:什么是API证书?如何获取API证书?

    简单叙述:(详细信息请看官方文档

    证书 描述 备注
    apiclient_cert.p12 包含了私钥信息的证书文件 是商户证书文件(双向证书)
    apiclient_cert.pem 从apiclient_cert.p12中导出证书部分的文件,为pem格式 简单理解:公钥
    apiclient_key.pem 从apiclient_key.pem中导出密钥部分的文件 简单理解:私钥

    3. 创建client(请参照官方文档

    import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
    import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
    import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
    import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
    import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
    import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
    import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.stereotype.Component;
    import javax.annotation.Resource;
    import java.io.IOException;
    import java.nio.charset.StandardCharsets;
    import java.security.GeneralSecurityException;
    import java.security.PrivateKey;
    import java.security.cert.X509Certificate;
    
    @Slf4j
    @Aspect
    @Component
    public class WxPayHttpClientFactory {
    
        public static CloseableHttpClient httpClient;
        public static Verifier verifier;
    
        @Before("execution(public * com.xxx.admin.web.controller.xxx.*Controller.*(..))" +
                "||execution(public * com.xxx.admin.web.controller.xxx.*Controller.*(..))")
        public void initWXPayClient() {
            try {
                // 加载商户私钥(privateKey:私钥字符串)
                PrivateKey merchantPrivateKey = PemUtil
                        .loadPrivateKey(new ClassPathResource("apiclient_key.pem classpath路径").getInputStream());
    
                X509Certificate certificate = PemUtil.loadCertificate(new ClassPathResource("apiclient_cert.pem classpath路径").getInputStream());
                String serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
                //merchantId:商户号,serialNo:商户证书序列号
                // 获取证书管理器实例
                CertificatesManager certificatesManager = CertificatesManager.getInstance();
                // 向证书管理器增加需要自动更新平台证书的商户信息
                certificatesManager.putMerchant("商户号", new WechatPay2Credentials("商户号",
                        new PrivateKeySigner(serialNo, merchantPrivateKey)), wechatAppPayConfig.api_v3.getBytes(StandardCharsets.UTF_8));
                // 从证书管理器中获取verifier
                //版本>=0.4.0可使用 CertificatesManager.getVerifier(mchId) 得到的验签器替代默认的验签器。
                // 它会定时下载和更新商户对应的微信支付平台证书 (默认下载间隔为UPDATE_INTERVAL_MINUTE)。
                verifier = certificatesManager.getVerifier("商户号");
    
                //创建一个httpClient
                httpClient = WechatPayHttpClientBuilder.create()
                        .withMerchant("商户号", serialNo, merchantPrivateKey)
                        .withValidator(new WechatPay2Validator(verifier)).build();
            } catch (IOException e) {
                e.printStackTrace();
                log.error("加载秘钥文件失败");
            } catch (GeneralSecurityException e) {
                e.printStackTrace();
                log.error("获取平台证书失败");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        @After("execution(public * com.xxx.admin.web.controller.xxx.*Controller.*(..))" +
                "||execution(public * com.xxx.admin.web.controller.xxx.*Controller.*(..))")
        public void closeWXClient() {
            if (httpClient != null) {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    官方文档指出 自动携带身份认证信息,并检查应答的微信支付签名 。所以上面的代码就没携带任何签名。

    1648619975768.png

    4. 生成APP支付订单(请参照官方文档

        /**
         * 微信支付POST请求
         *
         * @param reqUrl       请求地址 示例:https://api.mch.weixin.qq.com/v3/pay/transactions/app
         * @param paramJsonStr 请求体 json字符串 此参数与微信官方文档一致
         * @return 订单支付的参数
         * @throws Exception
         */
        public static String V3PayPost(String reqUrl, String paramJsonStr) throws Exception {
            //创建post方式请求对象
            HttpPost httpPost = new HttpPost(reqUrl);
            //装填参数
            StringEntity s = new StringEntity(paramJsonStr, "utf-8");
            s.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE,
                    "application/json"));
            //设置参数到请求对象中
            httpPost.setEntity(s);
            //指定报文头【Content-type】、【User-Agent】
            httpPost.setHeader("Content-type", "application/json");
            httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36");
            httpPost.setHeader("Accept", "application/json");
            //执行请求操作,并拿到结果(同步阻塞)
            CloseableHttpResponse response = WxPayHttpClientFactory.httpClient.execute(httpPost);
            int statusCode = response.getStatusLine().getStatusCode();
            //获取数据,并释放资源
            String body = closeHttpResponse(response);
            if (statusCode == 200) { //处理成功
                switch (reqUrl) {
                    case "https://api.mch.weixin.qq.com/v3/pay/transactions/app"://返回APP支付所需的参数
                        return JSONObject.parseObject(body).getString("prepay_id");
                    case "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"://返回APP退款结果
                        return body;
                }
            }
            return null;
        }
    
        /**
         * 获取数据,并释放资源
         *
         * @param response
         * @return
         * @throws IOException
         */
        public static String closeHttpResponse(CloseableHttpResponse response) throws IOException {
            String body = "";
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) { //处理成功
                //获取结果实体
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    //按指定编码转换结果实体为String类型
                    body = EntityUtils.toString(entity, "utf-8");
                }
                //EntityUtils.consume将会释放所有由httpEntity所持有的资源
                EntityUtils.consume(entity);
            }
            //释放链接
            response.close();
            return body;
        }
    

    至此就成功 在微信支付服务后台生成预支付交易单,并且拿到prepay_id,但是调起APP支付,还需其他字段,也就是所谓的二签。可以直接使用下面的方法WxAppPayTuneUp()

    5.APP调起支付(请参照官方文档

        /**
         * 微信调起支付参数
         * 返回参数如有不理解 请访问微信官方文档
         * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_4.shtml
         *
         * @param prepayId         微信下单返回的prepay_id
         * @param appId            应用ID
         * @param mch_id           商户号
         * @param private_key_path 私钥路径
         * @return 当前调起支付所需的参数
         * @throws Exception
         */
        public static String WxAppPayTuneUp(String prepayId, String appId, String mch_id, String private_key_path) throws Exception {
            if (StringUtils.isNotBlank(prepayId)) {
                long timestamp = System.currentTimeMillis() / 1000;
                String nonceStr = generateNonceStr();
                //加载签名
                String packageSign = sign(buildMessage(appId, timestamp, nonceStr, prepayId).getBytes(), private_key_path);
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("appId", appId);
                jsonObject.put("prepayId", prepayId);
                jsonObject.put("timeStamp", timestamp);
                jsonObject.put("nonceStr", nonceStr);
                jsonObject.put("package", "Sign=WXPay");
                jsonObject.put("signType", "RSA");
                jsonObject.put("sign", packageSign);
                jsonObject.put("partnerId", mch_id);
                return jsonObject.toJSONString();
            }
            return "";
        }
    
        public static String sign(byte[] message, String private_key_path) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeyException {
            //签名方式
            Signature sign = Signature.getInstance("SHA256withRSA");
            //私钥
            sign.initSign(PemUtil
                    .loadPrivateKey(new ClassPathResource(private_key_path).getInputStream()));
            sign.update(message);
    
            return Base64.getEncoder().encodeToString(sign.sign());
        }
    
    	/**
         * 按照前端签名文档规范进行排序,\n是换行
         *
         * @param appId     appId
         * @param timestamp 时间
         * @param nonceStr  随机字符串
         * @param prepay_id prepay_id
         * @return
         */
        public static String buildMessage(String appId, long timestamp, String nonceStr, String prepay_id) {
            return appId + "\n"
                    + timestamp + "\n"
                    + nonceStr + "\n"
                    + prepay_id + "\n";
        }
    
        protected static final SecureRandom RANDOM = new SecureRandom();
    	//生成随机字符串 微信底层的方法,直接copy出来了
        protected static String generateNonceStr() {
            char[] nonceChars = new char[32];
            for (int index = 0; index < nonceChars.length; ++index) {
                nonceChars[index] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".charAt(RANDOM.nextInt("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".length()));
            }
            return new String(nonceChars);
        }
    

    将WxAppPayTuneUp()返回的字符串,响应给前端即可

    6.支付通知(异步通知,请参照官方文档

        @PostMapping("/wxAppPayNotify")
        public JSONObject wxAppPayNotify(HttpServletRequest request, HttpServletResponse response) throws IOException, GeneralSecurityException {
            //从请求头获取验签字段
            String signature = request.getHeader("Wechatpay-Signature");
            String serial = request.getHeader("Wechatpay-Serial");
            ServletInputStream inputStream = request.getInputStream();
            StringBuilder sb = new StringBuilder();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String s;
            //读取回调请求体
            while ((s = bufferedReader.readLine()) != null) {
                sb.append(s);
            }
            String s1 = sb.toString();
            //按照文档要求拼接验签串
            String verifySignature = request.getHeader("Wechatpay-Timestamp") + "\n"
                    + request.getHeader("Wechatpay-Nonce") + "\n" + s1 + "\n";
    
            //使用官方验签工具进行验签
            boolean verify1 = WxPayHttpClientFactory.verifier.verify(serial, verifySignature.getBytes(), signature);
            //判断验签的结果
            if (!verify1) {
                //验签失败,应答接口
                //设置状态码
                response.setStatus(500);
                JSONObject jsonResponse = new JSONObject();
                jsonResponse.put("code", "FAIL");
                jsonResponse.put("message", "失败");
                return jsonResponse;
            }
    
            JSONObject parseObject = JSONObject.parseObject(s1);
            if ("TRANSACTION.SUCCESS".equals(parseObject.getString("event_type"))
                    && "encrypt-resource".equals(parseObject.getString("resource_type"))) {
                //通知的类型,支付成功通知的类型为TRANSACTION.SUCCESS
                //通知的资源数据类型,支付成功通知为encrypt-resource
                JSONObject resourceJson = JSONObject.parseObject(parseObject.getString("resource"));
                String associated_data = resourceJson.getString("associated_data");
                String nonce = resourceJson.getString("nonce");
                String ciphertext = resourceJson.getString("ciphertext");
    
                //解密,如果这里报错,就一定是APIv3密钥错误
                AesUtil aesUtil = new AesUtil(api_v3.getBytes());
                String aes = aesUtil.decryptToString(associated_data.getBytes(), nonce.getBytes(), ciphertext);
    		   System.out.println("解密后=" + aes);
                //dosomething 处理业务
                }
            }
    
            JSONObject jsonResponse = new JSONObject();
            jsonResponse.put("code", "SUCCESS");
            jsonResponse.put("message", "成功");
            //设置状态码
            response.setStatus(200);
            return jsonResponse;
        }
    

    以上是一套APP下单的流程【APP下单】->【APP调起支付】->【微信支付异步通知】

  • 相关阅读:
    [HDU2866] Special Prime (数论,公式)
    [骗分大法好] 信息学竞赛 骗分导论(论文搬运)
    flayway数据库管理
    RabbitMQ的基本概念与原理
    springboot+ideal实现远程调试
    盘点总结
    mysql查看进程命令
    Java字符串正则文本替换
    springboot代码级全局敏感信息加解密和脱敏方案
    使用PMD进行代码审查
  • 原文地址:https://www.cnblogs.com/cchilei/p/16077207.html
Copyright © 2020-2023  润新知