公众号微信支付开发

Posted 晨子语

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了公众号微信支付开发相关的知识,希望对你有一定的参考价值。

公众号微信支付开发

1.第一步:设置微信支付目录,这个地址指到支付页面的上一级即可。

例如:支付页面的地址是http://www.baidu.com/wechat/pay/shopping,只需填写http://www.baidu.com/wechat/pay/,

一定要以"/"(左斜杆)符号结尾。

 

2.第二步:设置授权域名,授权域名是为了获取支付中不可缺少的参数openid。每个用户对于每个公众号的openid都是不同的且是唯一的,即是说一个用户在不同的公众号中,他的openid是不同的,并且一直不变。在开发中可以事先获取你自己的在这个公众号(正式公众号,具有支付权限的)的openid,然后就可以跳过授权过程,直接开发并测试支付功能。

 

3.第三步:引入微信开发jar包,这是别人已经封装好的微信支付API,当然也可以使用官方的微信支付SDK,不过为了方便快速开发,

所以这里我使用了封装好的别人封装好的API,这是API文档的github地址:https://github.com/wechat-group/weixin-java-tools/wiki ,里面有具体的使用方法和开发步骤,如果你嫌看文档麻烦的话可以直接看我的开发步骤:

这是基于springboot开发的,首先引入jar包:

        <dependency>
            <groupId>com.github.binarywang</groupId>
            <artifactId>weixin-java-pay</artifactId>
            <version>3.1.4.BETA</version>
        </dependency>    

 

4.加入微信开发包两个基本类:

(1)在springboot配置文件application.properties中加入微信支付基本参数:

 

#微信公众号或者小程序等的appid
wechatpay.appId =

#微信支付商户号
wechatpay.mchId =

#微信支付商户密钥
wechatpay.mchKey=

#服务商模式下的子商户公众账号ID,用不上就注释掉
#wechatpay.subAppId=

#服务商模式下的子商户号,用不上就注释掉
#wechatpay.subMchId=

# p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)用不上就注释掉
#wechatpay.keyPath=

 

(2)添加支付配置文件参数类WxPayProperties:

 

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * wxpay pay properties
 *
 * @author Binary Wang
 */
@ConfigurationProperties(prefix = "wechatpay")
public class WxPayProperties {
    /**
     * 设置微信公众号的appid
     */
    private String appId;

    /**
     * 微信支付商户号
     */
    private String mchId;

    /**
     * 微信支付商户密钥
     */
    private String mchKey;

    /**
     * 服务商模式下的子商户公众账号ID,普通模式请不要配置,请在配置文件中将对应项删除
     */
    private String subAppId;

    /**
     * 服务商模式下的子商户号,普通模式请不要配置,最好是请在配置文件中将对应项删除
     */
    private String subMchId;

    /**
     * apiclient_cert.p12文件的绝对路径,或者如果放在项目中,请以classpath:开头指定
     */
    private String keyPath;

    public String getAppId() {
        return this.appId;
    }

    public void setAppId(String appId) {
        this.appId = appId;
    }

    public String getMchId() {
        return mchId;
    }

    public void setMchId(String mchId) {
        this.mchId = mchId;
    }

    public String getMchKey() {
        return mchKey;
    }

    public void setMchKey(String mchKey) {
        this.mchKey = mchKey;
    }

    public String getSubAppId() {
        return subAppId;
    }

    public void setSubAppId(String subAppId) {
        this.subAppId = subAppId;
    }

    public String getSubMchId() {
        return subMchId;
    }

    public void setSubMchId(String subMchId) {
        this.subMchId = subMchId;
    }

    public String getKeyPath() {
        return this.keyPath;
    }

    public void setKeyPath(String keyPath) {
        this.keyPath = keyPath;
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this,
                ToStringStyle.MULTI_LINE_STYLE);
    }
}

 

(3)初始化微信支付配置类WxPayConfiguration

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;

/**
 * @author Binary Wang
 */
@Configuration
@ConditionalOnClass(WxPayService.class)
@EnableConfigurationProperties(WxPayProperties.class)
public class WxPayConfiguration {
    private WxPayProperties properties;

    @Autowired
    public WxPayConfiguration(WxPayProperties properties) {
        this.properties = properties;
    }

    @Bean
    @ConditionalOnMissingBean
    public WxPayService wxService() {
        WxPayConfig payConfig = new WxPayConfig();
        
        payConfig.setAppId(StringUtils.trimToNull(this.properties.getAppId()));
        payConfig.setMchId(StringUtils.trimToNull(this.properties.getMchId()));
        payConfig.setMchKey(StringUtils.trimToNull(this.properties.getMchKey()));
        payConfig.setSubAppId(StringUtils.trimToNull(this.properties.getSubAppId()));
        payConfig.setSubMchId(StringUtils.trimToNull(this.properties.getSubMchId()));
        payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath()));

        WxPayService wxPayService = new WxPayServiceImpl();
        wxPayService.setConfig(payConfig);
        return wxPayService;
    }

}

5.生成支付订单:

import com.alibaba.fastjson.JSONObject;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.park.pojo.park.ParkingRecord;
import com.park.service.Interface.ParkService;
import com.park.utils.parkInterfaceUtil.ParkInterfaceRequestParam;
import com.park.utils.parkInterfaceUtil.ParkProtocolParamUtil;
import com.park.utils.wechatUtil.ConstantUtil;
import com.park.utils.wechatUtil.HttpRequest;
import com.park.utils.wechatUtil.SignUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 微信支付接口
 */
@Slf4j
@RestController
@RequestMapping(value = "/wechat/pay")
public class WechatPayController {

    @Autowired
    private WxPayService wxService;

    /**
     * 统一下单
     * 在发起微信支付前,需要调用统一下单接口,返回微信支付的所需要的参数
     * 接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder
     *
     * @param request 请求对象,注意一些参数如appid、mchid等不用设置,方法内会自动从配置对象中获取到(前提是对应配置中已经设置)
     */
    @PostMapping("/getPayInfo")
    public WxPayMpOrderResult getPayInfo(@RequestBody WxPayUnifiedOrderRequest request , HttpServletRequest httpServletRequest) throws WxPayException,Exception {

        request.setOutTradeNo(SignUtil.buildRandom(10));                                         //随机字订单号
        request.setSpbillCreateIp(ConstantUtil.getRemortIP(httpServletRequest));                    //用户ip
        request.setTradeType("JSAPI");                                                              //公众号支付
        request.setNonceStr(SignUtil.createNonceStr());                                             //随机字符串
        request.setNotifyUrl("http://qwrerewrwqewq/wechat/pay/PayResultNotify");                //回调通知支付结果地址(必须外网能访问的地址)
        request.setDeviceInfo("WEB");                                                               //客户终端类型
        request.setSignType("MD5");                                                                 //加密方式(必须参数,虽然官方文档说是非必需,亲测不加一直报签名错误)

        return this.wxService.createOrder(request);
    }

    /**
     * 接受微信支付返回通知
     * @param request
     * @param response
     * @throws IOException
     */
    @RequestMapping("/PayResultNotify")
    public void PayResultNotify(HttpServletRequest request, HttpServletResponse response) throws IOException {
        log.info("微信支付返回通知函数开始---------------------");

        InputStream inStream = request.getInputStream();
        ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inStream.read(buffer)) != -1) {
            outSteam.write(buffer, 0, len);
        }
        outSteam.close();
        inStream.close();
        String result = new String(outSteam.toByteArray(), "utf-8");
        boolean isPayOk =false;
        WxPayOrderNotifyResult wxPayOrderNotifyResult =null;

        // 此处调用订单查询接口验证是否交易成功
        try {
            wxPayOrderNotifyResult = wxService.parseOrderNotifyResult(result);
            if("SUCCESS".equals(wxPayOrderNotifyResult.getResultCode())){
                isPayOk=true;
            }
            log.info("解析数据:"+wxPayOrderNotifyResult.toString());
        } catch (WxPayException e) {
            e.printStackTrace();
        }

        String noticeStr="";


        // 支付成功,商户处理后同步返回给微信参数
        PrintWriter writer = response.getWriter();
        if (isPayOk) {
          //建议在这里处理付款完成的业务(虽然前端也可以处理后续业务,但是前端处理并不安全,例如:客户突然关闭浏览器了等情况,付款成功后续的业务将中断)
                System.out.println("===============付款成功,业务处理完毕==============");
                // 通知微信已经收到消息,不要再给我发消息了,否则微信会8连击调用本接口
                noticeStr = setXML("SUCCESS", "OK");
                log.info("收到通知返回给微信api信息:-----------"+noticeStr);
                writer.write(noticeStr);
                writer.flush();
            
        } else {

            // 支付失败, 记录流水失败
            noticeStr = setXML("FAIL", "");
            writer.write(noticeStr);
            writer.flush();
            System.out.println("===============支付失败==============");
        }



    }

    public static String setXML(String return_code, String return_msg) {
        return "<xml><return_code><![CDATA[" + return_code + "]]></return_code><return_msg><![CDATA[" + return_msg + "]]></return_msg></xml>";
    }
}

 

6.所用到的工具类:

import javax.servlet.http.HttpServletRequest;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Date;
import java.util.UUID;

public class SignUtil {
    private static String token = "weixin";

    /**
     * 获取acassToken校验
     * @param signature
     * @param timestamp
     * @param nonce
     * @return
     */
    public static boolean checkSignature(String signature, String timestamp, String nonce) {
        boolean result = false;

        // 对token、timestamp和nonce按字典序排序
        String[] array = new String[]{token, timestamp, nonce};
        Arrays.sort(array);

        // 将三个参数字符拼接成一个字符串
        String str = array[0].concat(array[1]).concat(array[2]);

        String sha1Str = null;
        try {
            // 对拼接后的字符串进行sha1加密
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            byte[] digest = md.digest(str.getBytes());
            sha1Str = byte2str(digest);
        }
        catch(Exception e) {
        }

        if(sha1Str != null &&  sha1Str.equals(signature)) {
            result = true;
        }

        return result;
    }

    /*
     * 将字节数组转换成字符串
     */
    public static String byte2str(byte[] array) {
        StringBuffer hexstr = new StringBuffer();
        String shaHex="";
        for(int i = 0; i < array.length; i++) {
            shaHex = Integer.toHexString(array[i] & 0xFF);
            if(shaHex.length() < 2) {
                hexstr.append(0);
            }
            hexstr.append(shaHex);
        }
        return hexstr.toString();
    }

    /**
     * 获取acaccessToken
     * @param grant_type
     * @param appid
     * @param secret
     * @return
     */
    public static String getAcaccessToken(String grant_type,String appid,String secret){

        String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type="+grant_type+"&appid="+appid+"&secret="+secret;
        String Str = HttpRequest.httpRequestGet(url);
        return Str;
    }

    /**
     * 创建随机字符串
     * @return
     */
    public static String createNonceStr() {
        return UUID.randomUUID().toString().substring(0, 20);
    }

    /**
     * 获取时间戳
     * @return
     */
    public static String getCurrTime(){

        long times = new Date().getTime();
        return times+"";
    }

    /**
     * 随机数
     * @param length
     * @return
     */
    public static String buildRandom(int length){
        long times = new Date().getTime();
        int randomNum = (int)((Math.random()*9+1)*(10*length));

        return randomNum+""+times;
    }

    /**
     * 获取用户IP
     * @param request
     * @return
     */
    public static String getRemortIP(HttpServletRequest request) {
        if (request.getHeader("x-forwarded-for") == null) {
            return request.getRemoteAddr();
        }
        String ipListStr = request.getHeader("x-forwarded-for");
        if(!(ipListStr.indexOf(",")<0)){
            String [] list = ipListStr.split(",");
            return list[0];                                 //当服务部署使用代理,其获取到的IP地址将会是多个,取第一个
        }else {
            return ipListStr ;
        }

    }
}

7.前端代码:

我的前端是用vue写的,看下逻辑就可以了:

getPayInfo(params){                                              //调用统一下单接口获取js支付参数  参数:金额、商品名称等 具体可以看统一接口的接收参数类WxPayUnifiedOrderRequest
        this.$api.gotoPay(params).then(res=>{  
          if(res!=null){
            this.payInfo.appId = res.data.appId;
            this.payInfo.timeStamp = res.data.timeStamp;
            this.payInfo.nonceStr = res.data.nonceStr;
            this.payInfo.packages = res.data.packages;
            this.payInfo.sign = res.data.sign;
          }
        })
      },
      onBridgeReady(){                                //使用微信浏览器内置的对象调起微信支付插件,并传入统一接口返回的参数
        WeixinJSBridge.invoke(
          \'getBrandWCPayRequest\', {
            "appId":this.payInfo.appId,                 //公众号名称,由商户传入
            "timeStamp":this.payInfo.timeStamp,         //时间戳,自1970年以来的秒数
            "nonceStr":this.payInfo.nonceStr,           //随机字符串
            "package":this.payInfo.packages,            //支付验证pay_id
            "signType":"MD5",                           //微信签名方式
            "paySign":this.payInfo.sign                 //微信签名
          },
          function(res){
            if(res.err_msg == "get_brand_wcpay_request:ok" ){
              // 使用以上方式判断前端返回,微信团队郑重提示:
              //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
            }
          });
      }
      ,
      Gopay(){                                            //点击付款按钮开始支付
        console.log("开始支付")
        if (typeof WeixinJSBridge == "undefined"){
          if( document.addEventListener ){
            document.addEventListener(\'WeixinJSBridgeReady\', this.onBridgeReady(), false);
          }else if (document.attachEvent){
            document.attachEvent(\'WeixinJSBridgeReady\', this.onBridgeReady());
            document.attachEvent(\'onWeixinJSBridgeReady\', this.onBridgeReady());
          }
        }else{
          this.onBridgeReady();
        }
      }

(8)测试支付功能:

可以将支付页面的地址发送到正式的公众号(必须要正式的公众号才行,而且后台支付相关的参数也要用正式,同时要用传入的openid的那个用户点击才能测试,一般是本人的),然后点击开始测试支付。

 

 

 


 


 

 

 

以上是关于公众号微信支付开发的主要内容,如果未能解决你的问题,请参考以下文章

企业号微信支付 公众号支付 H5调起支付API示例代码 JSSDK C# .NET

公众号微信支付

大商创微信公众号微信支付失败报错

微信支付-小程序H5 公众号 Payment SDK

手把手教你接入微信支付

微信公众号支付H5调用支付详解