微信支付之公众号支付

经过近一周的敲代码,终于把公众号支付和H5支付实现完成并测试通过,特此分享一些流程,一方面自己记录另一方面给新入门的一点思路

【本文介绍普通商户的公众号支付】

一、基本信息和配置

公众号支付的前提是要有一个拥有支付功能的公众号和一个已经通过ICP备案的域名,这里不再赘述,如果你申请支付成功,将会收到以下样子的邮件:
这里写图片描述
接下来,你就可以通过登陆账号和密码进入到微信支付商户平台,配置基本信息。

1.设置API密钥。
初次登陆没有密钥的需要设置一个具体在 账户中心>API安全,下图示:
这里写图片描述

先装个证书然后可以设置密钥(生成一个32位的UUID设为密钥即可,注意:密钥不能查看只能设置),以后在接口签名时需要这个东西,设置完成如下图示:
这里写图片描述

2.设置开发配置。
在产品中心>开发配置中添加公众号支付授权目录,注意是“目录”,并非域名,
你需要将项目发起支付的接口最后一个斜杠之前的地址配置进去,
如:http://www.bbcd.com/projectName/phone/gotoPayJS.do ,
你就需要添加http://www.bbcd.com/projectName/phone/这个地址
这里写图片描述
3.登陆微信公众平台,在 公众号设置>功能设置下配置网页授权域名,注意这里是域名。

通过以上三步配置就结束了,接下来就是敲代码了。

二、开发支付

强烈建议参考微信公众号开发文档
(https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1)
总结下来微信公众号支付开发主要两步骤:
1. 通过统一下单接口发起请求,并获得prepay_id(预支付交易会话标识),这个标示是你接下来想微信提交支付的关键数据;
2. 在微信浏览器内H5调起支付。
是不是感觉很简单呀,接下来两步骤解说。

一)发起统一下单接口

接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder
需求参数:

字段名 变量名 类型 示例值 描述
公众账号ID appid String(32) wxd678efh567hg6787 微信支付分配的公众账号ID(企业号corpid即为此appId)
商户号 mch_id String(32) 1230000109 微信支付分配的商户号
随机字符串 nonce_str String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,长度要求在32位以内。推荐随机数生成算法
签名 sign String(32) C380BEC2BFD727A4B6845133519F3AD6 通过签名算法计算得出的签名值,详见签名生成算法
商品描述 body String(128) 腾讯充值中心-QQ会员充值 商品简单描述,该字段请按照规范传递,具体请见参数规定
商户订单号 out_trade_no String(32) 20150806125346 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-
标价金额 total_fee Int 88 订单总金额,单位为分,详见支付金额
终端IP spbill_create_ip String(16) 123.12.12.123 APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。
通知地址 notify_url String(256) http://www.weixin.qq.com/wxpay/pay.php 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
交易类型 trade_type String(16) JSAPI 取值如下:JSAPI,NATIVE,APP等,说明详见参数规定
用户标识 openid String(128) oUpF8uMuAJO_M2pxb1Q9zNjWeS6o trade_type=JSAPI时(即公众号支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识。openid如何获取,可参考【获取openid】。企业号请使用【企业号OAuth2.0接口】获取企业号内成员userid,再调用【企业号userid转openid接口】进行转换
设备号 device_info String(32) 013467007045764 自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传”WEB”

我定义了一个bean存储,API里有各种字段,在这里我们只保留自己需要的,主要构造方法里我加上了几个固定字段的初始化,公众号,商户号,随机数,设备类型(”WEB”),通知地址。
这里我着重说下通知地址这个字段,它是公众号支付完成后微信端向我们发起回调的地址,告诉我们交易的结果,这个地址不能加参数,注意此回调必须向微信端做出回应,否则微信会认为我们没有正确接收结果,会持续回调多次(下文我会细说回应)

package com.wtp.wechat.bean;

import com.wtp.wechat.util.CommonUtil;
import com.wtp.wechat.util.WechatPayUtil;
import com.wtp.wechat.util.WechatUtil;

/**
 * 
 * @ClassName: UnifiedOrder 
 * @Description: 统一下单
 * @author tianpengw 
 * @date 2017年10月12日 上午9:57:39 
 *
 */
public class UnifiedOrder {
    /**
     * 公众账号ID
     */
    private String appid;
    /**
     * 商户号
     */
    private String mch_id;
    /**
     * 随机串
     */
    private String nonce_str;
    /**
     * 签名
     */
    private String sign;
    /**
     * 商品描述
     */
    private String body;
    /**
     * 商户订单号
     */
    private String out_trade_no;
    /**
     * 总金额
     */
    private Integer total_fee;
    /**
     * 终端IP(用户)
     */
    private String spbill_create_ip;
    /**
     * 通知地址
     */
    private String notify_url;
    /**
     * 交易类型
     */
    private String trade_type;
    /**
     * 用户标识
     */
    private String openid;
    /**
     * WEB
     */
    private String device_info;

    public UnifiedOrder(){
        this.appid = WechatUtil.appid;
        this.mch_id = WechatPayUtil.mchId;
        this.nonce_str = CommonUtil.getUUID();
        this.device_info = WechatPayUtil.deviceInfo;
        this.notify_url = WechatPayUtil.notifyUrl;
    }

    public String getAppid() {
        return appid;
    }

    public void setAppid(String appid) {
        this.appid = appid;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }

    public String getMch_id() {
        return mch_id;
    }

    public void setMch_id(String mch_id) {
        this.mch_id = mch_id;
    }

    public String getNonce_str() {
        return nonce_str;
    }

    public void setNonce_str(String nonce_str) {
        this.nonce_str = nonce_str;
    }

    public String getNotify_url() {
        return notify_url;
    }

    public void setNotify_url(String notify_url) {
        this.notify_url = notify_url;
    }

    public String getOpenid() {
        return openid;
    }

    public void setOpenid(String openid) {
        this.openid = openid;
    }

    public String getOut_trade_no() {
        return out_trade_no;
    }

    public void setOut_trade_no(String out_trade_no) {
        this.out_trade_no = out_trade_no;
    }

    public String getSpbill_create_ip() {
        return spbill_create_ip;
    }

    public void setSpbill_create_ip(String spbill_create_ip) {
        this.spbill_create_ip = spbill_create_ip;
    }

    public Integer getTotal_fee() {
        return total_fee;
    }

    public void setTotal_fee(Integer total_fee) {
        this.total_fee = total_fee;
    }

    public String getTrade_type() {
        return trade_type;
    }

    public void setTrade_type(String trade_type) {
        this.trade_type = trade_type;
    }

    public String getSign() {
        return sign;
    }

    public void setSign(String sign) {
        this.sign = sign;
    }

    public String getDevice_info() {
        return device_info;
    }

    public void setDevice_info(String device_info) {
        this.device_info = device_info;
    }
}

下面是微信支付相关的几个方法

package com.wtp.wechat.util;

import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.wtp.wechat.bean.UnifiedOrder;
/**
 * 
 * @ClassName: WechatPayUtil 
 * @Description: 微信支付工具类
 * @author tianpengw 
 * @date 2017年10月12日 下午3:38:25 
 *
 */
public class WechatPayUtil {

    private static Logger log = LogManager.getLogger(WechatPayUtil.class);
    /**
     * 微信支付分配的商户号
     */
    public static final String mchId = PropertiesUtil.getProperties("pay.mchid");
    /**
     * 商户平台密钥
     */
    public static final String apiKey = PropertiesUtil.getProperties("pay.apikey");
    /**
     * 微信支付分配的商户号
     */
    public static final String notifyUrl = PropertiesUtil.getProperties("pay.notifyUrl");
    /**
     * 终端设备号(门店号或收银设备ID),注意:PC网页或公众号内支付请传"WEB"
     */
    public static String deviceInfo = "WEB";
    /**
     * JSAPI -- 公众号支付
     */
    public static String tradeTypeJs = "JSAPI";
    /**
     * MWEB -- H5支付
     */
    public static String tradeTypeH5 = "MWEB";
    /**
     * 统一订单地址
     */
    private static String unifiedUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";

    /**
     * 
     * @Description: 公众号支付-统一订单类型获得prepay_id
     * @author tianpengw 
     * @param uo
     * @return
     */
    public static String getUnifiedOrderPId(UnifiedOrder uo){
        String sign = createUnifiedOrderSign(uo);
        uo.setSign(sign);
        String xml = XMLBeanUtils.objectToXMLStr(uo);
        log.info("公账号支付统一订单请求参数:"+xml);
        String res = HttpHelper.httpsRequest(unifiedUrl,"POST",xml);
        log.info("公账号支付统一订单返回结果:"+res);
        Map<String, String> responseMap = XMLBeanUtils.readStringXmlOut(res);
        return responseMap.get("prepay_id");
    }

    /**
     * 
     * @Description: 微信支付 -统一订单类型获得mweb_url
     * @author tianpengw 
     * @param uo
     * @return
     */
    public static String getUnifiedOrderMWebUrl(UnifiedOrder uo){
        String sign = createUnifiedOrderSign(uo);
        uo.setSign(sign);
        String xml = XMLBeanUtils.objectToXMLStr(uo);
        log.info("H5支付统一订单请求参数:"+xml);
        String res = HttpHelper.httpsRequest(unifiedUrl,"POST",xml);
        log.info("H5支付统一订单返回结果:"+res);
        Map<String, String> responseMap = XMLBeanUtils.readStringXmlOut(res);
        return responseMap.get("mweb_url");
    }

    /**
     * 获取统一下单签名
     * @param unifiedOrder
     * @return
     */
    private static String createUnifiedOrderSign(UnifiedOrder unifiedOrder){
        StringBuffer sign = new StringBuffer();
        sign.append("appid=").append(unifiedOrder.getAppid());
        sign.append("&body=").append(unifiedOrder.getBody());
        sign.append("&device_info=").append(unifiedOrder.getDevice_info());
        sign.append("&mch_id=").append(unifiedOrder.getMch_id());
        sign.append("&nonce_str=").append(unifiedOrder.getNonce_str());
        sign.append("&notify_url=").append(unifiedOrder.getNotify_url());
        /**
         * H5支付签名时没有用户的openId
         */
        if(!CommonUtil.isEmpty(unifiedOrder.getOpenid())){
            sign.append("&openid=").append(unifiedOrder.getOpenid());
        }
        sign.append("&out_trade_no=").append(unifiedOrder.getOut_trade_no());
        sign.append("&spbill_create_ip=").append(unifiedOrder.getSpbill_create_ip());
        sign.append("&total_fee=").append(unifiedOrder.getTotal_fee());
        sign.append("&trade_type=").append(unifiedOrder.getTrade_type());
        sign.append("&key=").append(apiKey);

        return SignatureUtil.MD5(sign.toString()).toUpperCase();
    }
}

getUnifiedOrderPId是获得prepareId的方法
getUnifiedOrderMWebUrl是获得微信H5支付的地址,这里先不用管,下章再说
createUnifiedOrderSign这个方法是获得sign签名的方法,apiKey就是上面商户平台设置的API密钥。

用到几个工具类的方法我也列举出来:
1)xstream 生成xml格式的字符串和解析字符串类xml为map的方法

package com.wtp.wechat.util;

import java.io.InputStream;

import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.naming.NoNameCoder;
import com.thoughtworks.xstream.io.xml.XppDriver;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.core.util.QuickWriter;

public class XMLBeanUtils {

    private static XStream xStream = new XStream(new XppDriver(new NoNameCoder()){
        @Override
        public HierarchicalStreamWriter createWriter(Writer out) {
            return new PrettyPrintWriter(out) {
                // 对所有xml节点的转换都增加CDATA标记
                boolean cdata = true;

                @Override
                @SuppressWarnings("rawtypes")
                public void startNode(String name, Class clazz) {
                    super.startNode(name, clazz);
                }

                @Override
                public String encodeNode(String name) {
                    return name;
                }


                @Override
                protected void writeText(QuickWriter writer, String text) {
                    if (cdata) {
                        writer.write("<![CDATA[");
                        writer.write(text);
                        writer.write("]]>");
                    } else {
                        writer.write(text);
                    }
                }
            };
        }
    });

    /**
     * 
     * @Description: bean转xml字符串
     * @author tianpengw 
     * @param obj
     * @return
     */
    public static String objectToXMLStr(Object obj){
        xStream.alias("xml", obj.getClass());
        return xStream.toXML(obj);
    }

     /** 
     * 解析微信发来的请求(XML) 
     *  
     * @param request 
     * @return 
     * @throws Exception 
     */  
    @SuppressWarnings("unchecked")  
    public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {  
        // 将解析结果存储在HashMap中  
        Map<String, String> map = new HashMap<String, String>();  

        // 从request中取得输入流  
        InputStream inputStream = request.getInputStream();  
        // 读取输入流  
        SAXReader reader = new SAXReader();  
        Document document = reader.read(inputStream);  
        // 得到xml根元素  
        Element root = document.getRootElement();  
        // 得到根元素的所有子节点  
        List<Element> elementList = root.elements();  
        // 遍历所有子节点  
        for (Element e : elementList){
            map.put(e.getName(), e.getText());  
        }
        // 释放资源  
        inputStream.close();  
        inputStream = null;  

        return map;  
    }

    /**
     * @description 将xml字符串转换成map
     * @param xml
     * @return Map
     */
    public static Map<String, String> readStringXmlOut(String xml) {
        Map<String, String> map = new HashMap<String, String>();
        Document doc = null;
        try {
            doc = DocumentHelper.parseText(xml); // 将字符串转为XML
            Element rootElt = doc.getRootElement(); // 获取根节点
            @SuppressWarnings("unchecked")
            List<Element> list = rootElt.elements();// 获取根节点下所有节点
            for (Element element : list) { // 遍历节点
                map.put(element.getName(), element.getText()); // 节点的name为map的key,text为map的value
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }

}

2)发起Http请求方法

    /**
      * 
      * @Description: 发起Http请求
      * @author tianpengw 
      * @param requestUrl 请求地址
      * @param requestMethod 请求方式 GET/POST
      * @param outputStr 请求参数,如果没有置 null
      * @return
      */
    public static String httpsRequest(String requestUrl, String requestMethod, String outputStr){    
        try {    
            URL url = new URL(requestUrl);    
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();    

            conn.setDoOutput(true);    
            conn.setDoInput(true);    
            conn.setUseCaches(false);    
            // 设置请求方式(GET/POST)    
            conn.setRequestMethod(requestMethod);    
            conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");    
            // 当outputStr不为null时向输出流写数据    
            if (null != outputStr) {    
                OutputStream outputStream = conn.getOutputStream();    
                // 注意编码格式    
                outputStream.write(outputStr.getBytes("UTF-8"));    
                outputStream.close();    
            }    
            // 从输入流读取返回内容    
            InputStream inputStream = conn.getInputStream();    
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");    
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);    
            String str = null;  
            StringBuffer buffer = new StringBuffer();    
            while ((str = bufferedReader.readLine()) != null) {    
                buffer.append(str);    
            }    
            // 释放资源    
            bufferedReader.close();    
            inputStreamReader.close();    
            inputStream.close();    
            inputStream = null;    
            conn.disconnect();    
            return buffer.toString();    
        } catch (Exception e) {
            e.printStackTrace();
        }    
        return null;    
    } 

3)MD5加密方法

   /**
     * 
     * @Description: 生成 MD5
     * @author tianpengw 
     * @param data
     * @return
     */
    public static String MD5(String data){
        StringBuilder sb = new StringBuilder();
        try {
            java.security.MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] array = md.digest(data.getBytes("UTF-8"));
            sb = new StringBuilder();
            for (byte item : array) {
                sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sb.toString();
    }

4)获得客户端IP方法

   /**
     * 
     * @Description: 获取客户端地址
     * @author tianpengw 
     * @param request
     * @return
     */
    public static String getClientIP(HttpServletRequest request) {
        String ipAddress = request.getHeader("x-forwarded-for");  
        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {  
            ipAddress = request.getHeader("Proxy-Client-IP");  
        }  
        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {  
            ipAddress = request.getHeader("WL-Proxy-Client-IP");  
        }  
        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {  
            ipAddress = request.getRemoteAddr();  
            if(ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")){  
                //根据网卡取本机配置的IP  
                InetAddress inet=null;  
                try {  
                    inet = InetAddress.getLocalHost();  
                } catch (UnknownHostException e) {  
                    e.printStackTrace();  
                }  
                ipAddress= inet.getHostAddress();  
            }  
        }  
        //对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割  
        if(ipAddress!=null && ipAddress.length()>15){ //"***.***.***.***".length() = 15  
            if(ipAddress.indexOf(",")>0){  
                ipAddress = ipAddress.substring(0,ipAddress.indexOf(","));  
            }  
        }  
        return ipAddress;
    }

5)获得UUID

   /**
     *   
     * @Description: 获得一个UUID
     * @author tianpengw 
     * @return String
     */
    public static String getUUID(){  
        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);  
    }

6)timstamp生成

String timestamp = Long.toString(System.currentTimeMillis() / 1000);

7)是否是微信浏览器判断

   /**
     * 
     * @Description: 判断是否是微信浏览器发起的请求,如果是返回true,反正返回false
     * @author tianpengw 
     * @param req
     * @return
     */
    public static boolean isWechatBrowser(HttpServletRequest req){
        String ua = req.getHeader("user-agent").toLowerCase();  
        if (ua.indexOf("micromessenger") >= 0) {// 是微信浏览器  
            return true;  
        } 
        return false;
    }

方法我都列出来,至于业务相关的订单id,商品body(注意内容不要太长限制为128,本人在这里吃过亏)等字段都需要自己加入自此获得了宝贵的prepared_id。

二)微信浏览器内调起支付

带着上文的prepared_id我们开始向页面出发,再出发之前要把通关文件都一一准备好,让我们看下一个通关类:

package com.wtp.wechat.bean;
/**
 * 
 * @ClassName: JSAPIConfig 
 * @Description: JSAPI使用的配置信息
 * @author tianpengw 
 * @date 2017年10月12日 下午3:44:48 
 *
 */
public class JSAPIConfig {

    /**
     * 公众号id:商户注册具有支付权限的公众号成功后即可获得
     */
    private String appId;
    /**
     * 时间戳
     */
    private String timeStamp;
    /**
     * 随机字符串,不长于32位
     */
    private String nonceStr;
    /**
     * 签名
     */
    private String paySign;
    /**
     * 签名算法,暂支持MD5
     */
    private String signType;
    /**
     * 订单详情扩展字符串-package
     * 统一下单接口返回的prepay_id参数值,提交格式如:prepay_id=***
     */
    private String packageName;

    public JSAPIConfig(){
        this.signType = "MD5";
    }
    public String getAppId() {
        return appId;
    }
    public void setAppId(String appId) {
        this.appId = appId;
    }
    public String getTimeStamp() {
        return timeStamp;
    }
    public void setTimeStamp(String timeStamp) {
        this.timeStamp = timeStamp;
    }
    public String getNonceStr() {
        return nonceStr;
    }
    public void setNonceStr(String nonceStr) {
        this.nonceStr = nonceStr;
    }
    public String getPaySign() {
        return paySign;
    }
    public void setPaySign(String paySign) {
        this.paySign = paySign;
    }
    public String getSignType() {
        return signType;
    }
    public void setSignType(String signType) {
        this.signType = signType;
    }
    public String getPackageName() {
        return packageName;
    }
    public void setPackageName(String packageName) {
        this.packageName = packageName;
    }
}

此bean因微信的内置对象WeixinJSBridge而出现,接口示例如下:

在微信浏览器里面打开H5网页中执行JS调起支付。接口输入输出数据格式为JSON。
注意:WeixinJSBridge内置对象在其他浏览器中无效。
WeixinJSBridge.invoke(
   'getBrandWCPayRequest', {
       "appId":"wx2421b1c4370ec43b",     //公众号名称,由商户传入     
       "timeStamp":"1395712654",         //时间戳,自1970年以来的秒数     
       "nonceStr":"e61463f8efa94090b1f366cccfbbb444", //随机串     
       "package":"prepay_id=u802345jgfjsdfgsdg888",     
       "signType":"MD5",         //微信签名方式:     
       "paySign":"70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名 
   },
   function(res){     
       if(res.err_msg == "get_brand_wcpay_request:ok" ) {}     
       // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。 
   }
   ); 

这几个字段里唯一注意的是paySign签名,我为了省事,通过拼字符串来进行MD5加密(其他用到的方法我上面列举的都有):

StringBuffer sign = new StringBuffer();
sign.append("appId=").append(WechatUtil.appid);
sign.append("&nonceStr=").append(nonce);
sign.append("&package=").append(packageName);
sign.append("&signType=").append(config.getSignType());
sign.append("&timeStamp=").append(timestamp);
sign.append("&key=").append(WechatPayUtil.apiKey);

String signature = SignatureUtil.MD5(sign.toString()).toUpperCase();

我部分代码如下:

后台部分代码:
这里写图片描述
前台部分代码:

//对浏览器的UserAgent进行正则匹配,不含有微信独有标识的则为其他浏览器
var useragent = navigator.userAgent;
if (useragent.match(/MicroMessenger/i) == 'MicroMessenger') {
     $.ajax({
        type : "POST",
        url : "gotoJSPayJS.json",
        dataType : "json",
        contentType : 'application/json;charset=UTF-8',
        data : JSON.stringify({orderId:orderId}),
        success : function(res){
            if("success" == res.errCode){
                var obj = res.data;
                if (typeof WeixinJSBridge == "undefined"){
                    jQAlert("请在微信浏览器发起支付!");
                }else{
                    WeixinJSBridge.invoke('getBrandWCPayRequest',{  
                        "appId" : obj.appId, //公众号名称,由商户传入  
                        "timeStamp":obj.timeStamp,//时间戳,自1970年以来的秒数  
                        "nonceStr" : obj.nonceStr,//随机串  
                        "package" : obj.packageName,//商品包信息  
                        "signType" : obj.signType,//微信签名方式
                        "paySign" : obj.paySign//微信签名  
                        },function(res){      
                            if(res.err_msg == "get_brand_wcpay_request:ok"){
                                window.location.href="payComplete.do?orderId="+orderId;
                            }else if(res.err_msg == "get_brand_wcpay_request:cancel"){
                                jQAlert("支付取消!");
                            }else if(res.err_msg == "get_brand_wcpay_request:fail"){
                                jQAlert("支付失败:"+res.err_desc);
                            }else{
                                jQAlert("支付异常");
                            }
                    }); 
                }

            }else{
                jQAlert("支付操作异常,请稍后再试!");
            }

        },
        error : function(XMLHttpRequest, textStatus,
                errorThrown) {
            jQAlert('支付异常,请联系客服!');
        },
        complete : function(XMLHttpRequest, textStatus) {
        }
    });

支付返回成功后,几乎可以保证微信会立刻调用你前面设置的notifyUrl配置的回调地址,接口里你就需要实现业务逻辑的方法比如更新订单状态,修改支付记录的状态,变更商品的数量等等业务逻辑,为了细说这个PayCallback我也列出我的部分代码:

   /**
     * 支付完成回调函数
     * @param request
     * @return
     */
    @RequestMapping(value="payNotify.do")
    public void wechatPayNotify(HttpServletRequest request,HttpServletResponse resp){
        log.info("++++++进入支付回调界面+++++");
        try {
            log.info("++++++订单:" + orderId + ",完成回调 :" + result +(("SUCCESS").equals(result)?"":",结果描述:"+resultDes));
        } catch (Exception e) {
            e.printStackTrace();
        }

        //处理完成需要返回微信已经支付完成回复
        try {
            resp.getWriter().write(XMLBeanUtils.objectToXMLStr(new PayCallback()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

再看下PayCallback对象:

package com.wtp.wechat.bean;
/**
 * 
 * @ClassName: PayCallback 
 * @Description: 回调结束返回微信内容
 * @author tianpengw 
 * @date 2017年10月12日 下午4:06:27 
 *
 */
public class PayCallback {

    private String return_code;
    private String return_msg;

    public PayCallback() {
        this.return_code = "SUCCESS";
        this.return_msg = "OK";
    }

    public String getReturn_code() {
        return return_code;
    }

    public void setReturn_code(String return_code) {
        this.return_code = return_code;
    }

    public String getReturn_msg() {
        return return_msg;
    }

    public void setReturn_msg(String return_msg) {
        this.return_msg = return_msg;
    }
}

三、特殊情况-微信浏览器访问网页之公众号支付(补充)

有这样一种场景,某个商品在朋友圈或通过其他朋友分享,用户在进入商品详情里购买时将使用到的支付方式就是公众号支付(从API里也能体现出这里不支持H5支付),此时作为服务端的我们,需要的是获得用户的openid,由于我们并不能确定该用户是否是关注用户,所以这里我们需要通过网页授权获得当前用户的微信信息(至少需得到openid)

本人采用的是静默授权的方式
部分代码片段:

if(HttpHelper.isWechatBrowser(req)){
            /*
             * 静默授权获得openid,这里不查询的原因是防止已经绑定过公众号的手机号在其他手机号的微信上进行支付操作,这里只完成支付操作不进行信息的变更
             */
            String code = req.getParameter("code");
            if(!MyStringUtils.isEmpty(code)){//认证后回调
                Oauth2AccessToken oat = WechatUtil.getUserOpenId(code);
                if(null != oat && !MyStringUtils.isEmpty(oat.getOpenid())){
                    String openId = (String) HttpUtils.getObjectFromSession(req, HttpUtils.openid_name);
                    if(!oat.getOpenid().equals(openId)){
                        mv.addObject("openId", oat.getOpenid());
                    }
                }
            }else{
                //此处开始进行微信认证
                String url = WechatUtil.getOauth2Url(WechatUtil.scope_base, URLEncoder.encode(MyConstants.product_address + "/phone/checkStand.do?orderId="+orderId,"utf-8"));
                mv.setViewName("redirect:"+url);
            }
        }

这样通过静默授权获得当前微信在服务号里的唯一的openId,然后完成此后的支付操作,

注:可能网友会想这种情况下手机号虽已经绑定了自己的微信openId,但在其他微信里登陆自己的手机号也是能完成支付的。是的,这里我的控制逻辑如此,账号唯一绑定的是用户的手机号,微信主要作为辅助支付和自动登陆,当然你可以有自己的的业务逻辑控制,按照自己实际需求来实现。

至此整个普通商户的公众号支付已经写完。

如果感兴趣请关注另一个博客【微信支付之H5支付】,最后会把我总结出的微信相关的一些接口调用封装起来,做成jar包发出来,以后在微信大版本不变的情况下 只需引用jar里的方法即可,省去不少开发时间。

发布了22 篇原创文章 · 获赞 15 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/niaoer2010/article/details/78226231