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

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

注:本实验使用springboot框架

一、创建订单

1.流程

在这里插入图片描述

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

2. API接口

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

参数含义类型例子
appid公众号IDStringwxd678efh567hg6787
mch_id商户号IDString1230000109
nonce_str随机字符串String5K8264ILTKCH16CQ2502SI8ZNMTM67VS
sign签名StringC380BEC2BFD727A4B6845133519F3AD6
body商品订单描述String腾讯充值中心-QQ会员充值
out_trade_no商品订单号String20150806125346
total_fee订单金额,单位为分int150
spbill_create_ip终端IP(用户的客户端IP )String123.12.12.123
notify_url通知地址Stringhttp://140.143.132.225:8000/project-1/doc-110/
trade_type交易类型StringNATIVE

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

参数含义类型例子
return_code通信状态码StringSUCCESS
result_code业务状态码StringSUCCESS
app_id微信公众账号APPIDStringwxd678efh567hg6787
mch_id商户IDString1230000109
nonce_str随机字符串String5K8264ILTKCH16CQ2502SI8ZNMTM67VS
sign数字签名StringC380BEC2BFD727A4B6845133519F3AD6
trade_type交易类型StringNATIVE
prepay_id支付订单IDStringwx201410272009395522657a690389285100
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通信状态码StringSUCCESS
result_code业务状态吗StringSUCCESS
sign数字签名StringC380BEC2BFD727A4B6845133519F3AD6
out_trade_no商品订单IDString1212321211201407033568112322
transaction_id支付订单IDString1217752501201407033233368018
total_fee订单金额int150
time_end支付的时间String20141030133525
<xml>
  <appid><![CDATA[wx2421b1c4370ec43b]]></appid>
  <attach><![CDATA[支付测试]]></attach>
  <bank_type><![CDATA[CFT]]></bank_type>
  <fee_type><![CDATA[CNY]]></fee_type>
  <is_subscribe><![CDATA[Y]]></is_subscribe>
  <mch_id><![CDATA[10000100]]></mch_id>
  <nonce_str><![CDATA[5d2b6c2a8db53831f7eda20af46e531c]]></nonce_str>
  <openid><![CDATA[oUpF8uMEb4qRXf22hE3X68TekukE]]></openid>
  <out_trade_no><![CDATA[1409811653]]></out_trade_no>
  <result_code><![CDATA[SUCCESS]]></result_code>
  <return_code><![CDATA[SUCCESS]]></return_code>
  <sign><![CDATA[B552ED6B279343CB493C5DD0D78AB241]]></sign>
  <time_end><![CDATA[20140903131540]]></time_end>
  <total_fee>1</total_fee>
  <coupon_fee><![CDATA[10]]></coupon_fee>
  <coupon_count><![CDATA[1]]></coupon_count>
  <coupon_type><![CDATA[CASH]]></coupon_type>
  <coupon_id><![CDATA[10000]]></coupon_id>
  <trade_type><![CDATA[JSAPI]]></trade_type>
  <transaction_id><![CDATA[1004400740201409030005092168]]></transaction_id>
</xml> 

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

<xml>
  <return_code><![CDATA[SUCCESS]]></return_code>
  <return_msg><![CDATA[OK]]></return_msg>
</xml> 

修改一处代码

修改 WXPayUtil.isSignatureValid(xml, key) -》改为WXPayUtil.isSignatureValid(data,key, WXPayConstants.SignType.HMACSHA256)
添加Map<String, String> 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<String, String> 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("<xml><return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[OK]]></return_msg></xml>");
                    bufferedWriter.close();
                    writer.close();
                } else {
                    log.error("更新订单状态失败");
                    response.sendError(500, "更新订单状态失败");
                }
            }

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

微信支付系列其他部分

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值