微信支付之公众号支付

2023-11-05

经过近一周的敲代码,终于把公众号支付和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里的方法即可,省去不少开发时间。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

微信支付之公众号支付 的相关文章

  • js判断是否是微信浏览器或者支付宝浏览器

    微信浏览器验证1 let ua window navigator userAgent toLowerCase if ua match MicroMessenger i micromessenger 判断微信 console log 微信浏览
  • 抖音微信消息推送情侣告白浪漫(简易版)

    抖音微信消息推送情侣告白浪漫 简易版 一 首先去微信公众平台用微信扫码登录 登录后会自动生成属于自己的appId appSecret 二 在上述登录后页面中下拉 用一个手机扫码会生成user id 即微信消息推送的接收方 三 点击下方的新增
  • 微信小程序强制更新

    目录 冷启动 热启动 小程序更新机制 强制更新方案 如何测试 冷启动 热启动 冷启动 如果用户首次打开 或小程序销毁后被用户再次打开 此时小程序需要重新加载启动 即冷启动 热启动 如果用户已经打开过某小程序 然后在一定时间内再次打开该小程序
  • 微信小游戏使用three.js开发总结2023.9.12

    微信小程序开发总结 1技术选择 我这里使用的是three js 进行的开发 目前开发了 酒馆卡牌 3D决对 两款微信小游戏 其中 酒馆卡牌 模仿的是iphone 美区的游戏 注重看在游戏没有复杂的战斗 在没有服务器的情况下依旧可以运行 由于
  • 微信小程序绘制二维码

    一 前言 在日常的小程序项目中 会经常遇到需要动态绘制二维码的需求 使用场景很多 例如绘制在海报上 例如制作票务码 核销码等等 这篇文章是应一位好友的需求而写的 也希望能够给有需要的同学一些帮助 二 实现原理 使用微信小程序的canvas组
  • 小程序微信支付功能逻辑

    官方的思维图在下看不懂 自己整理一份以备后用 1 打开Pay付款页面 2 用订单号 查看订单信息前端展示 3 点击付款按钮 提交订单ID到后台 创建微信预支付交易订单 用JSAPI下单 4 返回创建后的 预支付订单编号信息 存入数据表 5
  • 微信h5分享好友和朋友圈功能

    在开发公众号H5项目时 如果想和小程序一样有分享朋友圈和好友功能时发现会不一样 开发微信小程序时做分享有会有onShareAppMessage 这个方法 因为H5有许多限制 所以在做微信H5分享时就比较麻烦了 首先明确一点 微信H5分享是没
  • 微信H5如何关闭浏览器(如何监听手机的物理返回键)

    一 背景 背景是这样的 该项目进入h5时会通过 location replace xxx 或 location href xxx 跳转到某个地址 该地址会请求获得微信 openId 获取成功后再重定向到h5首页 那么问题来了 重定向会在微信
  • 对接微信支付(二)统一下单API

    原创文章 对接微信支付 二 统一下单API 编程屋 大家可以先想一下 大家平时在PC端发起的支付都需要什么 是不是你选好商品之后 点击支付 然后PC端弹出来一个二维码 你扫码付款 付款完成之后就OK了 当然这只是针对我们用户来说的 对于我们
  • 小程序运行在微信和企业微信

    获取当前运行环境 如果开发者的小程序需要同时运行在微信和企业微信端 那么对于开发者来说 第一件事情则是需要知道小程序当前的运行环境 开发者可以通过调用异步接口 wx getSystemInfo 或者同步接口 wx getSystemInfo
  • 微信公众号实现微信支付(含前后端完整代码)

    刚做完公众号微信支付 记录一下 获取微信支付之前 要先获取用户的基本信息哦 前端使用uniapp开发的H5 小伙伴们可以照着改一下对应语法 首先来个微信支付的工具类 wxApi js 这里我放到了项目下的common目录下 代码如下 微信
  • 微信JS-SDK获取signature签名以及config配置(微信转发分享页面需要)

    Js代码 wx config debug true 开启调试模式 调用的所有api的返回值会在客户端alert出来 若要查看传入的参数 可以在pc端打开 参数信息会通过log打出 仅在pc端时才会打印 appId 必填 公众号的唯一标识 t
  • H5微信分享记录

    最近做H5微信分享 用的微信jssdk来做 现记录下一些过程和遇到的问题 一 公众号配置 微信官方文档 已经说明了使用步骤 公众号配置比较模糊 主要是要配置ip白名单和绑定js接口安全域名 1 检查分享接口权限是否已获得 在微信公众号的 设
  • 测试用例:微信发红包测试用例(最新版)

    测试 核心 重点 功能 界面 安全性 易用性 兼容性 性能 一 功能测试 1 一对一红包 一对一发出去的红包自己不能领取 一对一红包金额 最多200 2 群发红包 1 拼手气红包 1 是否可以正常选择拼手气红包 2 红包个数 只能是数字 且
  • 解放双手!拼多多商家最新秘密武器,微信端批量私信rpa机器人来袭!

    在拼多多开店的卖家们都知道 拼多多商家在进行拓客引流工作时 需要频繁进行微信端私信发送操作 耗费大量时间和精力 为了解决这一问题 商家希望可以通过rpa机器人来自动完成私信发送操作 保证每一笔订单都能做好全面的维护和管理 八爪鱼rpa作为一
  • 小程序项目基于微信点单小程序系统

    前言 本微信点单小程序是根据当前的实际情况开发的 在系统语言选择上我们使用的Java语言 数据库是小巧灵活的MySQL数据库 框架方便使用的是当前最主流的SPRING BOOT框架 本系统的开发可以极大的满足了人们在线点单的需求 微信点单小
  • 1-创建小程序项目

    注册 打开https mp weixin qq com 点击 立即注册 选择小程序 获取APPID 登录小程序在 开发管理 gt 开发设置 获取 APPID 开发工具 登录小程序在 开发工具 gt 开发者工具 获取 微信开发者工具 创建小程
  • 微信扫一扫,ios系统扫码失效解决

    问题场景 调用微信扫一扫的 sdk时 安卓系统没有问题 苹果系统怎么点击都没反应 解决一 扫一扫的页面 是需要给接口传递当前页面地址生成签名的 ios系统不行 不能访问根路径 的地址 访问根路径 微信会用根路径签名 签名会过不去 必须用当前
  • 微信公众号-订阅通知

    第一步 公众号需要实名认证 完成以后 设置 开发里找到基本配置 开发者ID AppID xxxxxxxxxxxxxxxxxxxxxxxxx 开发者密码 AppSecret xxxxxxxxxxxxxxxxxxxxxxxxx 白名单IP也要填
  • AI壁纸画展头像表情包流量主微信抖音小程序开源版开发

    AI壁纸画展头像表情包流量主微信抖音小程序开源版开发 以下是AI壁纸画展头像表情包流量主微信抖音小程序开源版的开发功能列表 用户注册和登录 实现用户注册和登录功能 包括手机号登录 第三方登录等方式 图片上传和展示 用户可以上传自己的图片或选

随机推荐

  • vb.net如何递归treeview所有节点

    Public Class Form1 Dim targetStr As String a123 查询目标节点名 Dim allNodes As New ArrayList 遍历并定位节点 Private Sub Button1 Click
  • 第8章 K8s基础篇-配置管理

    8 1 云原生要素 配置分离 杜宽老师k8s课程学习笔记 ConfigMap 存储明文配置 Secret 存储密文 敏感配置 各种密码 配置更新直接同步容器 热加载 无需重启pod或者容器 镜像和配置分离 可单独修改发布 8 2 创建Con
  • 接口测试用例应包含哪些内容

    1 用例ID 如 storm 001 2 接口名称 如 获取用户信息 3 用例标题 如 GET请求 获取用户信息成功 GET请求 获取不存在的用户信息 GET请求 不传递参数 4 请求URL 如 http xxxx 8080 xxxx 5
  • Spark Core解析《五》

    一 Spark核心概念 1 Spark运行架构 2 重要概念 Client 客户端进程 负责提交作业 Application 提交一个作业就是一个Application 一个Application只有一个SparkContext Maste
  • 后台提交数据被限制个数?没准这个可以帮到你

    每家网站都是一些稀奇古怪的要求 提交数据个数也受限 真真是相当棒 渣 你是甲方爸爸你最牛 为了生活我妥协了 你有要求 我有对策 虽然折腾了点 但耗得起 谢谢您锻炼了我嘞 当你手头有一批十几万的数据 需要500为一批次的提交 只能用逗号隔开不
  • 基础学习——python 归一化、反归一化、标准化、反标准化、python输出数据显示不完全怎么解决

    python 归一化 反归一化 标准化 反标准化 python输出数据显示不完全怎么解决 文章目录 python 归一化 反归一化 标准化 反标准化 python输出数据显示不完全怎么解决 前言 1 最大值归一化 反归一化 2 线性函数归一
  • 经典数据算法:折半查找法

    折半查找 二分查找也叫做折半查找 查找的对象是已经排好序的序列 一般默认为升序 让我们来看看原理 顾名思义 就是先将中间数和目标key比较 如果相等则返回其索引 否则把序列分成两半 根据大小判断所查找的key在哪一半中 对这一半序列再重复上
  • JS实现子窗口传值到父窗口

    以前很少用JS 通过这次学习MVC碰到不少问题 比如子窗口要传值给父窗口 方法大致有两种 此处说当前使用的这种方法 父窗口打开子窗口时 使用window open方式打开 以下摘抄了两个使得子窗口居中的JS方法 第一个是子窗口屏幕居中 第二
  • 最新3D GAN可生成三维几何数据了!模型速度提升7倍,英伟达&斯坦福出品

    明敏 发自 凹非寺量子位 报道 公众号 QbitAI 2D图片变3D 还能给出3D几何数据 英伟达和斯坦福大学联合推出的这个GAN 真是刷新了3D GAN的新高度 而且生成画质也更高 视角随便摇 面部都没有变形 与过去传统的方法相比 它在速
  • case when嵌套子查询_SQL语句 - 嵌套查询

    嵌套查询的意思是 一个查询语句 select from where 查询语句块可以嵌套在另外一个查询块的where子句中 称为嵌套查询 其中外层查询也称为父查询 主查询 内层查询也称子查询 从查询 嵌套查询的工作方式是 先处理内查询 由内向
  • 【C语言】while((ch=getchar()!=EOF))相关问题详解(结束、安全)

    本篇文章主要介绍一下while ch getchar EOF 相关问题 1 分别介绍getchar和EOF 2 while ch getchar EOF 和while ch getchar EOF 的区别 3 while ch getcha
  • 人类思维的逻辑结构和算法实现

    本文将从最基本的逻辑分析的角度来分析人类思维的起源和基本过程的逻辑结构等基本问题 以便对于人工智能的设计提供指导性作用 首先分析了现有的人工智能研究所面临的问题 然后提出解决这些问题的办法 然后将分析人类的意识的最基本逻辑表达和人类思维中的
  • Linux基本操作与命令

    Linux基本操作与命令 一 命令模式下命令的执行 1 命令行提示字符 2 执行命令 3 基础命令的操作 切换用户 su 查看当前主机的完整名称 hostname 临时设置主机名 永久设置主机名 查看当前系统版本信息 查看当前内核版本 重启
  • 《LeetCode力扣练习》第15题 C语言版 (做出来就行,别问我效率。。。。)

    库你急哇 哈集美马戏特 一题二写 三数之和 题解四瞅五瞄六瞧 水平还七上八下九流 十分辣鸡 十推九敲 八种思路 用光七情六欲五感 在这里四覆三翻二挠 一拳爆屏 十赢九输 赢了八千 七百六十五万 打了四个三带二 爽的一批 一天两道 三月打卡
  • css 每个样式单行显示,单行文字溢出和多行文字溢出省略号显示的CSS样式

    JavaScript高级程序设计 读书笔记 3 引用类型 ECMAScript从技术来说是一门面向对象的语言 但不具备传统的面向对象语言所支持的类和接口等基本结构 虽然引用类型与类看起来类似 但是他们并不是相同的概念 引用类型有时也被成为对
  • 用element-plus+vue3+ts实现搜索下拉框

  • 查看kafka是否正常_K8S环境快速部署Kafka(K8S外部可访问)

    迎访问我的GitHub 这里分类和汇总了欣宸的全部原创 含配套源码 https github com zq2599 blog demos 如何快速部署 借助Helm 只需少量操作即可部署kafka kafka和zookeeper对存储都有需
  • Anaconda中安装opencv-python

    直接安装opencv python pip install opencv python 直接安装opencv python总是失败 因此选择在清华源中下载对应的opencv python版本 我这里是python3 6 下载到相应目录下进行
  • Python中with open () as f

    with open log txt w as log f for i in range 4 print i i 0 1 2 3 log f write Epoch d Total Loss d Val Loss d i i i log f
  • 微信支付之公众号支付

    经过近一周的敲代码 终于把公众号支付和H5支付实现完成并测试通过 特此分享一些流程 一方面自己记录另一方面给新入门的一点思路 本文介绍普通商户的公众号支付 一 基本信息和配置 公众号支付的前提是要有一个拥有支付功能的公众号和一个已经通过IC