本文主要是接前面2篇微信V3支付参数准备和微信V3支付整合进项目中的后续之微信支付后的回调。

一、回调验签流程介绍

二、核心流程操作


本文主要是接前面2篇微信V3支付参数准备和微信V3支付整合进项目中的后续之微信支付后的回调。

一文带你学会微信V3版本下单支付、退款、关单流程代码实操_8年开发工作经验的老王,积极分享工作中遇到的问题~-CSDN博客_wechatpay-apache-httpclient本文介绍微信支付中的Native支付方式,版本是APIv3,其中Native和JSAPI的区别如下Native支付:商家在系统中按微信支付协议生成支付二维码,用户扫码拉起微信收银台,确认并完成付款JSAPI支付:商家张贴收款码物料,用户打开扫一扫,扫码后输入金额,完成付款以下博文没有掺杂过多业务逻辑,对象数据都是固定写的,方便将注意力集中在微信支付的接口使用上。正式对接到项目里面,只需要改动支付的部分业务参数即可。微信支付的文档还是很详细的,建议多看几遍,以下博文也都是根据官方文档的描述来操https://blog.csdn.net/wnn654321/article/details/122933324微信V3版本支付下单、查询支付订单状态、订单退款接入正式项目中并引入策略模式实操_8年开发工作经验的老王,积极分享工作中遇到的问题~-CSDN博客本文介绍微信支付中的Native支付方式,版本是APIv3,其中Native和JSAPI的区别如下Native支付:商家在系统中按微信支付协议生成支付二维码,用户扫码拉起微信收银台,确认并完成付款JSAPI支付:商家张贴收款码物料,用户打开扫一扫,扫码后输入金额,完成付款以下内容增加了支付接口调和策略模式的使用,支付的VO类,大家可以参照自己公司的VO字段,需要改动支付的部分业务参数即可。注意:微信支付个人无法对接操作,需要有公司商户账号,一般开发过程中是产品经理或者相关负责人提供。https://blog.csdn.net/wnn654321/article/details/123192971

一、回调验签流程介绍

    官方文档 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_5.shtml
                    https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml
注意:
    同样的通知可能会多次发送给商户系统,商户系统必须能够正确处理重复的通知
    确保回调URL是外部可正常访问的,且不能携带后缀参数

微信回调通知重复问题(不一定准确按照时间间隔推送,需要保证幂等性处理)
    重复通知的时候,微信的请求id是一样的,用这个做请求幂等性处理
    响应给微信的内容不规范 或者 超过5秒没响应
    测试的问题:如果有多个未响应的,则测试的请求id,可能有之前的请求继续回调过来 

二、核心流程操作

第一步 获取报文

第二步 验证签名(确保是微信传输过来的)

第三步 解密(AES对称解密出原始数据)

第四步 处理业务逻辑

第五步 响应请求 

#支付成功,回调通知
pay.wechat.callback-url= http://mbnny9.natappfree.cc/api/callback/order/v1/wechat

注意:这个http://mbnny9.natappfree.cc需要是外网可以访问的,比如说 启动的微信支付服务是在自己电脑本地的服务,但是微信支付是在外网环境的,这时候 微信回调自己本地电脑的服务就是不通的,也就没法回调。但是可以使用小米球或者NATAPP这些内网穿透工具。本文就是使用的NATAPP。

pom.xml引入:

     <!--微信支付扩展包-->
<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-apache-httpclient</artifactId>
    <version>0.3.0</version>
</dependency>
package net.wnn.controller;

import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.auth.ScheduledUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import net.wnn.config.WechatPayConfig;
import net.wnn.service.ProductOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;



@Controller
@RequestMapping("/api/callback/order/v1/")
@Slf4j
public class PayCallbackController {


    @Autowired
    private WechatPayConfig wechatPayConfig;


    @Autowired
    private ProductOrderService productOrderService;


    @Autowired
    private ScheduledUpdateCertificatesVerifier verifier;


    /**
     * * 获取报文
     * <p>
     * * 验证签名(确保是微信传输过来的)
     * <p>
     * * 解密(AES对称解密出原始数据)
     * <p>
     * * 处理业务逻辑
     * <p>
     * * 响应请求
     *
     * @param request
     * @param response
     * @return
     */
    @RequestMapping("wechat")
    @ResponseBody
    public Map<String, String> wehcatPayCallback(HttpServletRequest request, HttpServletResponse response) {

        //获取报文
        String body = getRequestBody(request);

        //随机串
        String nonceStr = request.getHeader("Wechatpay-Nonce");

        //微信传递过来的签名
        String signature = request.getHeader("Wechatpay-Signature");

        //证书序列号(微信平台)
        String serialNo = request.getHeader("Wechatpay-Serial");

        //时间戳
        String timestamp = request.getHeader("Wechatpay-Timestamp");

        //构造签名串

        //应答时间戳\n
        //应答随机串\n
        //应答报文主体\n
        String signStr = Stream.of(timestamp, nonceStr, body).collect(Collectors.joining("\n", "", "\n"));

        Map<String, String> map = new HashMap<>(2);
        try {
            //验证签名是否通过
            boolean result = verifiedSign(serialNo, signStr, signature);

            if(result){
                //解密数据
                String plainBody = decryptBody(body);
                log.info("解密后的明文:{}",plainBody);

                Map<String, String> paramsMap = convertWechatPayMsgToMap(plainBody);
                //处理业务逻辑 TODO

                //响应微信
                map.put("code", "SUCCESS");
                map.put("message", "成功");
            }



        } catch (Exception e) {
            log.error("微信支付回调异常:{}", e);
        }

        return map;

    }


    /**
     * 转换body为map
     * @param plainBody
     * @return
     */
    private Map<String,String> convertWechatPayMsgToMap(String plainBody){

        Map<String,String> paramsMap = new HashMap<>(2);

        JSONObject jsonObject = JSONObject.parseObject(plainBody);

        //商户订单号
        paramsMap.put("out_trade_no",jsonObject.getString("out_trade_no"));

        //交易状态
        paramsMap.put("trade_state",jsonObject.getString("trade_state"));

        //附加数据
        paramsMap.put("account_no",jsonObject.getJSONObject("attach").getString("accountNo"));

        return paramsMap;

    }



    /**
     * 解密body的密文
     *
     * "resource": {
     *         "original_type": "transaction",
     *         "algorithm": "AEAD_AES_256_GCM",
     *         "ciphertext": "",
     *         "associated_data": "",
     *         "nonce": ""
     *     }
     *
     * @param body
     * @return
     */
    private String decryptBody(String body) throws UnsupportedEncodingException, GeneralSecurityException {

        AesUtil aesUtil = new AesUtil(wechatPayConfig.getApiV3Key().getBytes("utf-8"));

        JSONObject object = JSONObject.parseObject(body);
        JSONObject resource = object.getJSONObject("resource");
        String ciphertext = resource.getString("ciphertext");
        String associatedData = resource.getString("associated_data");
        String nonce = resource.getString("nonce");

        return aesUtil.decryptToString(associatedData.getBytes("utf-8"),nonce.getBytes("utf-8"),ciphertext);

    }



    /**
     * 验证签名
     *
     * @param serialNo  微信平台-证书序列号
     * @param signStr   自己组装的签名串
     * @param signature 微信返回的签名
     * @return
     * @throws UnsupportedEncodingException
     */
    private boolean verifiedSign(String serialNo, String signStr, String signature) throws UnsupportedEncodingException {
        return verifier.verify(serialNo, signStr.getBytes("utf-8"), signature);
    }


    /**
     * 读取请求数据流
     *
     * @param request
     * @return
     */
    private String getRequestBody(HttpServletRequest request) {

        StringBuffer sb = new StringBuffer();

        try (ServletInputStream inputStream = request.getInputStream();
             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        ) {
            String line;

            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }

        } catch (IOException e) {
            log.error("读取数据流异常:{}", e);
        }

        return sb.toString();

    }


}

 下单后再用postman支付调用下,很快就可以在回调接口中收到断点啦 

 这个是我本地电脑用来做内网穿透的NATAPP工具

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐