• Java微信支付开发之扫码支付模式一


    官方文档
    准备工作:已通过微信认证的公众号,必须通过ICP备案域名(否则会报支付失败)

    借鉴了很多大神的文章,在此先谢过了

    大体过程:先扫码(还没有确定实际要支付的金额),这个码是商品的二维码,再生成订单,适用于自动贩卖机之类固定金额的。

    模式一支付的流程如下图,稍微有点复杂

    原生支付接口模式一时序图

    业务流程说明:

    (1)商户后台系统根据微信支付规定格式生成二维码(规则见下文),展示给用户扫码。
    (2)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
    (3)微信支付系统收到客户端请求,发起对商户后台系统支付回调URL的调用。调用请求将带productid和用户的openid等参数,并要求商户系统返回交数据包

    (4)商户后台系统收到微信支付系统的回调请求,根据productid生成商户系统的订单。

    (5)商户系统调用微信支付【统一下单API】请求下单,获取交易会话标识(prepay_id)。
    (6)微信支付系统根据商户系统的请求生成预支付交易,并返回交易会话标识(prepay_id)。
    (7)商户后台系统得到交易会话标识prepay_id(2小时内有效)。
    (8)商户后台系统将prepay_id返回给微信支付系统。
    (9)微信支付系统根据交易会话标识,发起用户端授权支付流程。
    (10)用户在微信客户端输入密码,确认支付后,微信客户端提交支付授权。
    (11)微信支付系统验证后扣款,完成支付交易。
    (12)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
    (13)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
    (14)未收到支付通知的情况,商户后台系统调用【查询订单API】。
    (15)商户确认订单已支付后给用户发货。

    一、设置回调地址

    商户后台系统根据微信支付规则链接生成二维码,链接中带固定参数productid(可定义为产品标识或订单号)。用户扫码后,微信支付系统将productid和用户唯一标识(openid)回调商户后台系统(需要设置支付回调URL),商户后台系统根据productid生成支付交易,最后微信支付系统发起用户支付流程
    商户支付回调URL设置指引:进入公众平台-->微信支付-->开发配置-->扫码支付-->修改,如下图所示。

    这个支付回调的URL设置的作用是接收用户扫码后微信支付系统发送的数据,根据接收的数据生成商户系统的支付订单返回给微信支付系统,调用【统一下单API】提交支付交易。

    扫码支付参数设置栏目入口

    二、生成微信支付二维码

    参考文档https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_6

    二维码长链接示例:

    weixin://wxpay/bizpayurl?sign=XXXXX&appid=XXXXX&mch_id=XXXXX&product_id=XXXXXX&time_stamp=XXXXXX&nonce_str=XXXXX

    这样太冗长了,转换成短链接 weixin://wxpay/bizpayurl?pr=XXXX

     /**
         * 扫码支付模式一生成二维码
         *
         * @param productId 商品ID
         * @throws IOException
         */
        @GetMapping("M1")
        public Map<String, Object> payone(HttpServletRequest request, String productId) {
            Map<String, Object> data = new HashMap<>();
            String nonce_str = PayHelper.createNonceStr();
            // String product_id = "product_001"; // 推荐根据商品ID生成
            TreeMap<String, Object> packageParams = new TreeMap<>();
            packageParams.put("appid", wechatAccountConfig.getAppid());
            packageParams.put("mch_id", PayConstant.MCH_ID);
            packageParams.put("product_id", productId);
            packageParams.put("time_stamp", PayHelper.createTimeStamp());
            packageParams.put("nonce_str", nonce_str);
            String str_url = PayHelper.createPayImageUrl(packageParams);
            String sign = SignatureUtil.md5Hex(packageParams, PayConstant.API_KEY, null);
            packageParams.put("sign", sign);
            String payurl = "weixin://wxpay/bizpayurl?sign=" + sign + str_url;
            log.debug("payurl is {}", payurl);
            /**** 转成短链接 ****/
            PayShortUrlParams payShortUrlParams = new PayShortUrlParams();
            payShortUrlParams.setAppid(wechatAccountConfig.getAppid());
            payShortUrlParams.setMch_id(PayConstant.MCH_ID);
            payShortUrlParams.setLong_url(payurl);
            payShortUrlParams.setNonce_str(nonce_str);
            String urlSign = SignatureUtil.md5Hex(payShortUrlParams, PayConstant.API_KEY,
                    null);
            payShortUrlParams.setSign(urlSign);
            String longXml = XmlUtil.toXml(payShortUrlParams);
            String shortResult = HttpUtil.doPost(wechatPayConfig.getPayShortUrl(), null,
                    longXml);
            PayShortUrlResult payShortUrlResult = XmlUtil.fromXml(shortResult, PayShortUrlResult.class);
            if (Objects.equals("SUCCESS", payShortUrlResult.getReturn_code())) {
                payurl = payShortUrlResult.getShort_url();
            } else {
                log.debug("错误信息" + payShortUrlResult.getReturn_msg());
            }
            /**** 生成 二维码图片自行实现 ****/
            //定义savePath
            ZXingCodeUtil.toQRCode(payurl, null, "savePath", null);
            return data;
        }
    /**
         * 生成支付二维码URL
         *
         * @param params
         * @return
         */
        public static String createPayImageUrl(TreeMap<String, Object> params) {
            StringBuilder buffer = new StringBuilder();
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                if (entry.getValue() != null) {
                    buffer.append("&").append(entry.getKey()).append("=").append(entry.getValue());
                }
            }
            return buffer.toString();
        }

    三、回调商户支付URL

    接收用户扫码后微信支付系统发送的数据,根据接收的数据生成商户系统的支付订单返回给微信支付系统,调用统一下单API提交支付交易

    package com.phil.wechat.pay.controller;
    
    import com.phil.modules.config.WechatAccountConfig;
    import com.phil.modules.util.HttpUtil;
    import com.phil.modules.util.SignatureUtil;
    import com.phil.modules.util.XmlUtil;
    import com.phil.wechat.pay.config.WechatPayConfig;
    import com.phil.wechat.pay.constant.PayConstant;
    import com.phil.wechat.pay.model.request.PayCallBackParams;
    import com.phil.wechat.pay.model.request.UnifiedOrderParams;
    import com.phil.wechat.pay.model.response.PayCallBackResult;
    import com.phil.wechat.pay.model.response.UnifiedOrderResult;
    import com.phil.wechat.pay.util.PayHelper;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.io.IOUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.BufferedOutputStream;
    import java.util.Objects;
    
    /**
     * 扫码模式一回调
     *
     * @author phil
     * @date 2017年6月27日
     */
    @Controller
    @RequestMapping("api/wxpay")
    @Slf4j
    public class WechatPayCallBackController {
    
        private final WechatPayConfig wechatPayConfig;
    
        private final WechatAccountConfig wechatAccountConfig;
    
        @Autowired
        public WechatPayCallBackController(WechatPayConfig wechatPayConfig, WechatAccountConfig wechatAccountConfig) {
            this.wechatPayConfig = wechatPayConfig;
            this.wechatAccountConfig = wechatAccountConfig;
        }
    
        @RequestMapping("callback") // 仅仅是扫码模式一的
        public void callBack(HttpServletRequest request, HttpServletResponse response) throws Exception {
            String resXml = "";// 反馈给微信服务器
            // 微信支付系统发送的数据(<![CDATA[product_001]]>格式)
            String xml = IOUtils.toString(request.getInputStream());
            // logger.info("微信支付系统发送的数据"+xml);
            /**** 微信支付系统发送的数据其实就是回调地址输入的参数Xml ****/
            // 验证签名
            if (SignatureUtil.checkValidPaySign(xml, PayConstant.API_KEY)) {
                // 转换成输入参数,
                PayCallBackParams payCallBackParams = XmlUtil.fromXml(xml, PayCallBackParams.class);
                // appid openid mch_id is_subscribe nonce_str product_id sign
                // 统一下单
                String openid = payCallBackParams.getOpenid();
                String product_id = payCallBackParams.getProduct_id();
                /**** product_id 等 生成自己系统的订单 ****/
                int total_fee = 1; // 根据product_id算出价格
                String out_trade_no = PayHelper.createOutTradeNo(); // 生成订单号
                String body = product_id; // 商品名称设置为product_id
                String attach = "XXX店"; // 附加数据
                String nonce_str = PayHelper.createNonceStr();
                String spbill_create_ip = HttpUtil.getRemortIP(request);
                // 组装统一下单的请求参数
                UnifiedOrderParams unifiedOrderParams = new UnifiedOrderParams();
    //			unifiedOrderParams.setAppid(WechatConfig.APP_ID);// 必须
    //			unifiedOrderParams.setMch_id(PayConstant.MCH_ID);// 必须
                unifiedOrderParams.setOut_trade_no(out_trade_no);
                unifiedOrderParams.setBody(body);
                unifiedOrderParams.setAttach(attach);
                unifiedOrderParams.setTotal_fee(total_fee);// 必须
                unifiedOrderParams.setNonce_str(nonce_str); // 必须
                unifiedOrderParams.setSpbill_create_ip(spbill_create_ip); // 必须
                unifiedOrderParams.setTrade_type("NATIVE");// 必须
                unifiedOrderParams.setOpenid(openid);
                unifiedOrderParams.setNotify_url(wechatPayConfig.getNotifyUrl()); // 异步通知URL
                // 签名
                String sign = SignatureUtil.md5Hex(unifiedOrderParams, PayConstant.API_KEY, new String[] {"serialVersionUID"});
                unifiedOrderParams.setSign(sign);
                // 统一下单 请求的Xml
                String unifiedXmL = XmlUtil.toXml(unifiedOrderParams);
                // 统一下单 返回的xml
                String unifiedOrderResultXmL = HttpUtil.doPost(
                        wechatPayConfig.getUnifiedOrderUrl(), null, unifiedXmL);
                // 统一下单返回 验证签名
                if (SignatureUtil.checkValidPaySign(unifiedOrderResultXmL, PayConstant.API_KEY)) {
                    UnifiedOrderResult unifiedOrderResult = XmlUtil
                            .fromXml(unifiedOrderResultXmL, UnifiedOrderResult.class);
                    if (Objects.equals("SUCCESS", unifiedOrderResult.getReturn_code())
                            && Objects.equals("SUCCESS", unifiedOrderResult.getResult_code())) {
                        PayCallBackResult payCallBackResult = new PayCallBackResult();
                        payCallBackResult.setReturn_code(unifiedOrderResult.getReturn_code());
                        payCallBackResult.setAppid(wechatAccountConfig.getAppid());
                        payCallBackResult.setMch_id(PayConstant.MCH_ID);
                        payCallBackResult.setNonce_str(unifiedOrderResult.getNonce_str());// 直接用微信返回的
                        /**** prepay_id 2小时内都有效,根据product_id再次支付方法自己写 ****/
                        payCallBackResult.setPrepay_id(unifiedOrderResult.getPrepay_id());
                        payCallBackResult.setResult_code(unifiedOrderResult.getResult_code());
                        String callsign = SignatureUtil.md5Hex(payCallBackResult, PayConstant.API_KEY, null);
                        payCallBackResult.setSign(callsign);
                        resXml = XmlUtil.toXml(payCallBackResult).replace("__", "_");
                        // 将数据包返回给微信支付系统处理
                    }
                } else {
                    log.error("签名验证错误");
                    resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                            + "<return_msg><![CDATA[签名验证错误]]></return_msg>" + "</xml> ";
                }
            } else {
                log.error("签名验证错误");
                resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                        + "<return_msg><![CDATA[签名验证错误]]></return_msg>" + "</xml> ";
            }
            try (BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream())) {
                out.write(resXml.getBytes());
                out.flush();
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        }
    }

    四、完成支付并通知支付结果

    package com.phil.wechat.pay.controller;
    
    import com.phil.modules.result.ResultState;
    import com.phil.modules.util.SignatureUtil;
    import com.phil.modules.util.XmlUtil;
    import com.phil.wechat.pay.model.response.PayNotifyResult;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.io.IOUtils;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.BufferedOutputStream;
    import java.util.Objects;
    
    /**
     * 微信支付结果通知(统一下单参数的notify_url)
     *
     * @author phil
     * @date 2017年6月27日
     */
    @RestController
    @RequestMapping("api/wxpay")
    @Slf4j
    public class WechatPayNotifyController {
    
        @RequestMapping("notify")
        public ResultState notice(HttpServletRequest request, HttpServletResponse response) throws Exception {
            ResultState resultState = new ResultState();
            log.debug("开始处理支付返回的请求");
            String resXml = ""; // 反馈给微信服务器
            String notifyXml = IOUtils.toString(request.getInputStream());// 微信支付系统发送的数据(<![CDATA[product_001]]>格式)
            log.debug("微信支付系统发送的数据" + notifyXml);
            // 验证签名
            if (SignatureUtil.checkValidPaySign(notifyXml, null)) {
                PayNotifyResult notify = XmlUtil.fromXml(notifyXml, PayNotifyResult.class);
                log.debug("支付结果 {}", notify.toString());
                if (Objects.equals("SUCCESS", notify.getResult_code())) {
                    resultState.setErrcode(0);// 表示成功
                    resultState.setErrmsg(notify.getResult_code());
                    /**** 业务逻辑 保存openid之类的 ****/
                    // 通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了
                    resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
                            + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
                } else {
                    resultState.setErrcode(-1);// 支付失败
                    resultState.setErrmsg(notify.getErr_code_des());
                    log.debug("支付失败,错误信息:" + notify.getErr_code_des());
                    resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA["
                            + notify.getErr_code_des() + "]]></return_msg>" + "</xml> ";
                }
            } else {
                resultState.setErrcode(-1);// 支付失败
                resultState.setErrmsg("签名验证错误");
                log.debug("签名验证错误");
                resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                        + "<return_msg><![CDATA[签名验证错误]]></return_msg>" + "</xml> ";
            }
            try (BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream())) {
                out.write(resXml.getBytes());out.flush();
            } catch (Exception e) {
    
                log.error(e.getMessage());
            }
            return resultState;
        }
    }

    扫一扫加群

    本文为Phil Jing原创文章,未经博主允许不得转载,如有问题请直接回复或者加群。
  • 相关阅读:
    Jwt访问api提示401错误 Authorization has been denied for this request
    git commit的规范
    postman中如何使用OAuth
    在outlook中查找Skype的聊天记录
    nuget sources
    NuGet version
    Forcing restore from package sources
    同时打印多个worksheets
    Redis使用认证密码登录
    Linux wait函数详解
  • 原文地址:https://www.cnblogs.com/phil_jing/p/15615882.html
Copyright © 2020-2023  润新知