实现微信支付(Native支付),使用WebSocket进行推送——3.创建支付订单,接收付款结果

实现微信支付(Native支付),使用WebSocket进行推送——3.创建支付订单,接收付款结果

注:本实验使用springboot框架

一、创建订单

1.流程

实现微信支付(Native支付),使用WebSocket进行推送——3.创建支付订单,接收付款结果_第1张图片

2.创建支付订单所需参数

2. API接口

大家可以用浏览器访问微信支付的API手册( https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1), 查阅具体的调用规范。下面是本次项目所使用到的参数

参数 含义 类型 例子
appid 公众号ID String wxd678efh567hg6787
mch_id 商户号ID String 1230000109
nonce_str 随机字符串 String 5K8264ILTKCH16CQ2502SI8ZNMTM67VS
sign 签名 String C380BEC2BFD727A4B6845133519F3AD6
body 商品订单描述 String 腾讯充值中心-QQ会员充值
out_trade_no 商品订单号 String 20150806125346
total_fee 订单金额,单位为分 int 150
spbill_create_ip 终端IP(用户的客户端IP ) String 123.12.12.123
notify_url 通知地址 String http://140.143.132.225:8000/project-1/doc-110/
trade_type 交易类型 String NATIVE

微信支付平台给我们返回的响应中包含了下面这些参数。

参数 含义 类型 例子
return_code 通信状态码 String SUCCESS
result_code 业务状态码 String SUCCESS
app_id 微信公众账号APPID String wxd678efh567hg6787
mch_id 商户ID String 1230000109
nonce_str 随机字符串 String 5K8264ILTKCH16CQ2502SI8ZNMTM67VS
sign 数字签名 String C380BEC2BFD727A4B6845133519F3AD6
trade_type 交易类型 String NATIVE
prepay_id 支付订单ID String wx201410272009395522657a690389285100

2.1 本实验使用值注入的方式,在application.yaml中配置相关参数

wx:
  app-id: 企业身份APPID
  app-secret: 企业身份密钥
  mch-id: 企业身份商户号ID
  key: 企业身份微信支付的密钥
  cert-path: 企业身份微信支付的数字证书路径

3、持久层

3.1 sql语句

1.searchAmectByCondition:根据id,用户id,(或状态码)查询罚款单的uuid(做商品编号),amount(罚款金额),prepay_id(支付订单),status(是否支付成功)

2.updatePrepayId:更新支付订单(用于向微信服务器请求生成订单成功后保存订单编号,方便之后主动查询支付状态)

<select id="searchAmectByCondition" parameterType="HashMap" resultType="HashMap">
    SELECT uuid,
           amount,
           prepay_id AS prepayId,
           `status`
    FROM tb_amect
    WHERE id = #{amectId}
    AND user_id = #{userId}
    <if test="status!=null">
        AND `status` = #{status}
    if>
select>

<update id="updatePrepayId" parameterType="HashMap">
    UPDATE tb_amect
    SET prepay_id = #{prepayId}
    WHERE id = #{amectId} AND status = 1
update>

3.2 DAO层

public interface TbAmectDao {
    ……
    public HashMap searchAmectByCondition(HashMap param);
    public int updatePrepayId(HashMap param);
}

4、业务层代码

public interface AmectService {
    ……
    public String createNativeAmectPayOrder(HashMap param);
}

public class AmectServiceImpl implements AmectService {
    @Autowired
    private MyWXPayConfig myWXPayConfig;
    
    ……
    @Override
    public String createNativeAmectPayOrder(HashMap param) {
        int userId = MapUtil.getInt(param, "userId");
        int amectId = MapUtil.getInt(param, "amectId");
        //根据罚款单ID和用户ID查询罚款单记录
        HashMap map = amectDao.searchAmectByCondition(param);
        if (map != null && map.size() > 0) {
            //将元转为分
            String amount = new BigDecimal(MapUtil.getStr(map, "amount")).multiply(new BigDecimal("100")).intValue() + "";
            try {
                WXPay wxPay = new WXPay(myWXPayConfig);
                param.clear();
                param.put("nonce_str", WXPayUtil.generateNonceStr()); //随机字符串
                param.put("body", "缴纳罚款");
                param.put("out_trade_no", MapUtil.getStr(map, "uuid"));
                param.put("total_fee", amount);
                param.put("spbill_create_ip", "127.0.0.1");
                param.put("notify_url", "http://s1.nsloop.com:35750/emos-api/amect/recieveMessage");
                param.put("trade_type", "NATIVE");
                String sign = WXPayUtil.generateSignature(param, myWXPayConfig.getKey());
                param.put("sign", sign);
                
                Map<String, String> result = wxPay.unifiedOrder(param); //创建支付订单
                String prepayId = result.get("prepay_id");  //微信订单ID
                String codeUrl = result.get("code_url");   //支付连接,需要生成二维码让手机扫码
                if (prepayId != null) {
                    param.clear();
                    param.put("prepayId", prepayId);
                    param.put("amectId", amectId);
                    int rows = amectDao.updatePrepayId(param);
                    if (rows != 1) {
                        throw new EmosException("更新罚款单的支付订单ID失败");
                    }

                    //把支付订单的URL生成二维码
                    QrConfig qrConfig = new QrConfig();
                    qrConfig.setWidth(255);
                    qrConfig.setHeight(255);
                    qrConfig.setMargin(2);
                    String qrCodeBase64 = QrCodeUtil.generateAsBase64(codeUrl, qrConfig, "jpg");
                    return qrCodeBase64;
                } else {
                    log.error("创建支付订单失败", result);
                    throw new EmosException("创建支付订单失败");
                }
            } catch (Exception e) {
                log.error("创建支付订单失败", e);
                throw new EmosException("创建支付订单失败");
            }

        } else {
            throw new EmosException("没有找到罚款单");
        }
    }
}

5、WEB层代码

@Data
@Schema(description = "创建Native支付罚款订单表单")
public class CreateNativeAmectPayOrderForm {
    @NotNull(message = "amectId不能为空")
    @Min(value = 1, message = "amectId不能小于1")
    @Schema(description = "罚款单ID")
    private Integer amectId;
}

public class AmectController {
    ……
    @PostMapping("/createNativeAmectPayOrder")
    @Operation(summary = "创建Native支付罚款订单")
    @SaCheckLogin
    public R createNativeAmectPayOrder(@Valid @RequestBody CreateNativeAmectPayOrderForm form) {
        int userId = StpUtil.getLoginIdAsInt();
        int amectId = form.getAmectId();
        HashMap param = new HashMap() {{
            put("amectId", amectId);
            put("userId", userId);
        }};
        String qrCodeBase64 = amectService.createNativeAmectPayOrder(param);
        //R是一个统一的返回类,你也可以用hashmap返回
        return R.ok().put("qrCodeBase64", qrCodeBase64);
    }
}

6、前端

会有相应的罚款列表,点击缴纳罚款,发送ajax请求,提交罚款订单id,调用createNativeAmectPayOrder接口,返回的二维码使用img标签显示,记为支付码,使用微信扫码支付即可

二、接收付款结果

1.持久层

修改罚款状态

<update id="updateStatus" parameterType="HashMap">
    UPDATE tb_amect
    SET status = #{status}
    WHERE uuid = #{uuid}
</update>

public interface TbAmectDao {
    ……
    public int updateStatus(HashMap param);
}

2.业务层代码

public interface AmectService {
    ……
    public int updateStatus(HashMap param);
}

public class AmectServiceImpl implements AmectService {
    ……
    @Override
    public int updateStatus(HashMap param) {
        int rows = amectDao.updateStatus(param);
        return rows;
    }
}

3.编写Web层代码

微信平台发送付款结果通知的Request里面是XML格式的数据,所以我们要从中提取出我们需要的关键数据,这部分的文档规范,大家可以查阅微信支付的官方资料( https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7&index=8)。 而且这个请求并不是只发送一次,如果商户系统没有接收到请求,微信平台会每隔一小段时间再发送一次付款结果。

参数 含义 类型 示例
return_code 通信状态码 String SUCCESS
result_code 业务状态吗 String SUCCESS
sign 数字签名 String C380BEC2BFD727A4B6845133519F3AD6
out_trade_no 商品订单ID String 1212321211201407033568112322
transaction_id 支付订单ID String 1217752501201407033233368018
total_fee 订单金额 int 150
time_end 支付的时间 String 20141030133525
<xml>
  <appid>appid>
  <attach>attach>
  <bank_type>bank_type>
  <fee_type>fee_type>
  <is_subscribe>is_subscribe>
  <mch_id>mch_id>
  <nonce_str>nonce_str>
  <openid>openid>
  <out_trade_no>out_trade_no>
  <result_code>result_code>
  <return_code>return_code>
  <sign>sign>
  <time_end>time_end>
  <total_fee>1total_fee>
  <coupon_fee>coupon_fee>
  <coupon_count>coupon_count>
  <coupon_type>coupon_type>
  <coupon_id>coupon_id>
  <trade_type>trade_type>
  <transaction_id>transaction_id>
xml> 

返回给微信平台的响应内容也必须是XML格式的,否则微信平台就会认为你没收到付款结果通知。

<xml>
  <return_code>return_code>
  <return_msg>return_msg>
xml> 

修改一处代码

修改 WXPayUtil.isSignatureValid(xml, key) -》改为WXPayUtil.isSignatureValid(data,key, WXPayConstants.SignType.HMACSHA256)
添加Map data = WXPayUtil.xmlToMap(xml);

原因是WXPayUtil.isSignatureValid(xml, key) 的签名验证使用MD5加密,源码如下,
而我们真实的微信支付使用的是HMACSHA256,所以签名验证怎么都不会对,
所以要改一下签名验证的方法

之前我没考虑到这个,以为是只能使用MD5,所以我直接修改了微信支付工具类WXPay,
直接改为用MD5,后来觉得使用MD5不需要密钥很不安全,重新debug后发现原来
是这个原因。现在改回用HMACSHA256,需要修改三处代码,这是其中一处

另外两处:
实现微信支付(Native支付),使用WebSocket进行推送——2.微信支付工具类:中的二、官网给的工具类 的 2.1 WXPay类 核心类

以及:实现微信支付(Native支付),使用WebSocket进行推送 ——4.配置SpringBoot支持WebSocket,推送结果:中的五、主动查询付款结果 的 5.2 业务层代码

//源码
    public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
        Map<String, String> data = xmlToMap(xmlStr);
        if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
            return false;
        }
        String sign = data.get(WXPayConstants.FIELD_SIGN);
        return generateSignature(data, key).equals(sign);
    }


    public static String generateSignature(final Map<String, String> data, String key) throws Exception {
        return generateSignature(data, key, SignType.MD5);
    }

之前创建订单时提交给微信服务端的回调url,notify_url,支付成功后微信服务器会调用该接口,发送回调通知

public class AmectController {
    @Value("${wx.key}")
    private String key;
    ……
   @Operation(summary = "接收消息通知")
    @RequestMapping("/receiveMessage")
    public void receiveMessage(HttpServletRequest request, HttpServletResponse response) throws Exception {
        request.setCharacterEncoding("utf-8");
        Reader reader = request.getReader();
        BufferedReader buffer = new BufferedReader(reader);
        String line = buffer.readLine();
        StringBuffer temp = new StringBuffer();
        while (line!= null){
            temp.append(line);
            line = buffer.readLine();
        }
        buffer.close();
        reader.close();
        String xml = temp.toString();
        //利用数字证书验证收到的响应内容,避免有人伪造付款结果发送给Web方法。

        //修改该处代码 WXPayUtil.isSignatureValid(xml, key) -》改为WXPayUtil.isSignatureValid(data,key, WXPayConstants.SignType.HMACSHA256)
        //添加Map data = WXPayUtil.xmlToMap(xml);
        Map<String, String> data = WXPayUtil.xmlToMap(xml);

        if(WXPayUtil.isSignatureValid(data,key, WXPayConstants.SignType.HMACSHA256)){
            Map<String,String> map = WXPayUtil.xmlToMap(xml);
            String resultCode = map.get("result_code");
            String returnCode = map.get("return_code");
            if ("SUCCESS".equals(resultCode) && "SUCCESS".equals(returnCode)) {
                String outTradeNo = map.get("out_trade_no");    //罚款单UUID
                //更新订单状态
                HashMap param = new HashMap() {{
                    put("status", 2);
                    put("uuid", outTradeNo);
                }};
                int rows = amectService.updateStatus(param);
                if (rows == 1) {
                    //向前端页面推送付款结果
                    //根据罚款单ID查询用户ID
                    int userId = amectService.searchUserIdByUUID(outTradeNo);
                    //向用户推送结果
                    WebSocketService.sendInfo("收款成功", userId + "");


                    //给微信平台返回响应
                    response.setCharacterEncoding("utf-8");
                    response.setContentType("application/xml");
                    Writer writer = response.getWriter();
                    BufferedWriter bufferedWriter = new BufferedWriter(writer);
                    bufferedWriter.write(" ");
                    bufferedWriter.close();
                    writer.close();
                } else {
                    log.error("更新订单状态失败");
                    response.sendError(500, "更新订单状态失败");
                }
            }

        }else {
            log.error("数字签名异常");
            response.sendError(500, "数字签名异常");
        }
    }
}

微信支付系列其他部分

你可能感兴趣的:(微信支付,笔记,websocket,微信)