微信公众号支付开发-JAVA版DEMO

来源:互联网 发布:mac qq如何上传群文件 编辑:程序博客网 时间:2024/06/06 08:45

1.准备工作

首先登录微信公众平台,获取并配置以下微信开发配置:

  • 开发者ID【AppID和AppSecret】
  • 服务器配置
    1.url服务器地址设置
    2.Token【自己设置,必须英文或数字】
    3.EncodingAESKey[自己随机生成,用于消息加解密]

然后登录微信商户平台,获取并配置以下微信支付配置:

  • 商户号(mchId)
  • API秘钥(key)
  • API证书(java版主要使用:apiclient_cert.p12)

 PS:需要微信公众号支付银行对私接口,请联系电话/微信17605918869

2.代码展示

提醒:此处粘贴出的代码为方便初学者比较直观的了解、学习微信公众号支付,部分代码并未按照编码规范封装成方法、工具类

将微信支付所有参数定义为 WeChatConfig.java

public class WeChatConfig {    /**公众号AppId*/    public static final APP_ID = "";        /**公众号AppSecret*/    public static final APP_SECRET = "";    /**微信支付商户号*/    public static final String MCH_ID = "";    /**微信支付API秘钥*/    public static final String KEY = "";    /**微信支付api证书路径*/    public static final String CERT_PATH = "***/apiclient_cert.p12";    /**微信统一下单url*/    public static final String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";    /**微信申请退款url*/    public static final String REFUND_URL = "https://api.mch.weixin.qq.com/secapi/pay/refund";    /**微信支付通知url*/    public static final String NOTIFY_URL = "此处url用于接收微信服务器发送的支付通知,并处理商家的业务";    /**微信交易类型:公众号支付*/    public static final String TRADE_TYPE_JSAPI = "JSAPI";    /**微信交易类型:原生扫码支付*/    public static final String TRADE_TYPE_NATIVE = "NATIVE";    /**微信甲乙类型:APP支付*/    public static final String TRADE_TYPE_APP = "APP";}

处理微信公众号支付请求的Controller:WeChatOrderController.java

@RequestMapping(value="/m/weChat/")@Controller("weChatOrderController")public class WeChatOrderController{    @Autowired    private OrderService orderService;    @Autowired    private WechatPayService wechatPayService;    @Autowired    private NotifyReturnService notifyReturnService;    @RequestMapping(value = "unifiedOrder")    public String unifiedOrder(HttpServletRequest request,Model model){        //用户同意授权,获得的code        String code = request.getParameter("code");        //请求授权携带的参数【根据自己需要设定值,此处我传的是订单id】        String state = request.getParameter("state");        Order order = orderService.get(state);//订单信息        //通过code获取网页授权access_token        AuthToken authToken = WeChatUtils.getTokenByAuthCode(code);        //构建微信统一下单需要的参数        Map<String,Object> map = Maps.newHashMap();        map.put("openId",authToken.getOpenid());//用户标识openId        map.put("remoteIp",request.getRemoteAddr());//请求Ip地址        //调用统一下单service        Map<String,Object> resultMap = WeChatPayService.unifiedOrder(order,map);        String returnCode = (String) resultMap.get("return_code");//通信标识        String resultCode = (String) resultMap.get("result_code");//交易标识        //只有当returnCode与resultCode均返回“success”,才代表微信支付统一下单成功        if (WeChatConstant.RETURN_SUCCESS.equals(resultCode)&&WeChatConstant.RETURN_SUCCESS.equals(returnCode)){            String appId = (String) resultMap.get("appid");//微信公众号AppId            String timeStamp = WeChatUtils.getTimeStamp();//当前时间戳            String prepayId = "prepay_id="+resultMap.get("prepay_id");//统一下单返回的预支付id            String nonceStr = WeChatUtils.getRandomStr(20);//不长于32位的随机字符串            SortedMap<String,Object> signMap = Maps.newTreeMap();//自然升序map            signMap.put("appId",appId);            signMap.put("package",prepayId);            signMap.put("timeStamp",timeStamp);            signMap.put("nonceStr",nonceStr);            signMap.put("signType","MD5");            model.addAttribute("appId",appId);            model.addAttribute("timeStamp",timeStamp);            model.addAttribute("nonceStr",nonceStr);            model.addAttribute("prepayId",prepayId);            model.addAttribute("paySign",WeChatUtils.getSign(signMap));//获取签名        }else {            logger.error("微信统一下单失败,订单编号:"+order.getOrderNumber()+",失败原因:"+resultMap.get("err_code_des"));            return "redirect:/m/orderList";//支付下单失败,重定向至订单列表        }        //将支付需要参数返回至页面,采用h5方式调用支付接口        return "/mobile/order/h5Pay";    }}

微信支付前端发起页面: weChatPayTest.jsp

  • 支付按钮href中的redirect_uri= http://自己服务的ip或者域名/m/weChat/unifiedOrder 强调部分需要进行uriEncode
  • 此处代码为在微信公众号内网页调用,故使用的是微信网页授权方式,将订单id通过支付接口中state参数进行传递
  • 微信网页授权说明
<!DOCTYPE HTML><%@ page contentType="text/html;charset=UTF-8" language="java" %><html><head>    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0">    <meta name="screen-orientation" content="portrait">    <meta name="x5-orientation" content="portrait">    <link rel="stylesheet" href="/static/weui/dist/style/weui.min.css">    <title>微信公众号支付测试</title></head><body><div class="container" id="container">    <a href="https://open.weixin.qq.com/connect/oauth2/authorizeappid=wx67e9c91f0bac335d&redirect_uri=http%3a%2f%2f***%2fm%2fweChat%2funifiedOrder&response_type=code&scope=snsapi_base&state=${order.id}#wechat_redirect" class="weui_btn weui_btn_primary">立即支付</a></div></body></html>

h5方式调用微信支付接口:h5Pay.jsp

  • WeixinJSBridge为微信公众号内置对象,所以必须在公众号内部网页使用
<%@ page contentType="text/html;charset=UTF-8" language="java" %><html><head>    <title>确认支付</title>    <script type="text/javascript" src="/static/jquery/jquery-1.11.3.min.js"></script>    <script type="text/javascript" src="/static/jquery-plugin/jquery.form.js"></script></head><body>    <input type="hidden" name="appId" value="${appId}">    <input type="hidden" name="nonceStr" value="${nonceStr}">    <input type="hidden" name="prepayId" value="${prepayId}">    <input type="hidden" name="paySign" value="${paySign}">    <input type="hidden" name="timeStamp" value="${timeStamp}"></body><script>    function onBridgeReady(){        var appId = $("input[name='appId']").val();        var nonceStr = $("input[name='nonceStr']").val();        var prepayId = $("input[name='prepayId']").val();        var paySign = $("input[name='paySign']").val();        var timeStamp = $("input[name='timeStamp']").val();        WeixinJSBridge.invoke(                'getBrandWCPayRequest', {                    "appId":appId,                    "timeStamp":timeStamp,                    "nonceStr":nonceStr,                    "package":prepayId,                    "signType":"MD5",                    "paySign":paySign                },            function(res){                if(res.err_msg == "get_brand_wcpay_request:ok" ) {                    location.href="支付成功返回商家自定义页面";                }else {//这里支付失败和支付取消统一处理                    alert("支付取消");                    location.href="支付失败返回商家自定义页面";                }            }        );    }    $(document).ready(function () {        if (typeof WeixinJSBridge == "undefined"){            if (document.addEventListener){                document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);            }else if (document.attachEvent){                document.attachEvent('WeixinJSBridgeReady', onBridgeReady);                document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);            }        }else {            onBridgeReady();        }    });</script></html>

微信支付订单Service:WeChatPayService.java

  • 微信支付API–统一下单 使用说明
/***微信支付统一下单**/public Map<String,Object> unifiedOrder(Order order, Map<String,Object> map){    Map<String,Object> resultMap;    try {            WxPaySendData paySendData = new WxPaySendData();            //构建微信支付请求参数集合            paySendData.setAppId(WeChatConstant.APP_ID);            paySendData.setAttach("微信订单支付:"+order.getOrderNumber());            paySendData.setBody("商品描述");            paySendData.setMchId(WeChatConfig.MCH_ID);            paySendData.setNonceStr(WeChatUtils.getRandomStr(32));            paySendData.setNotifyUrl(WeChatConfig.NOTIFY_URL);            paySendData.setDeviceInfo("WEB");            paySendData.setOutTradeNo(order.getOrderNumber());            paySendData.setTotalFee(order.getSumFee());            paySendData.setTradeType(WeChatConfig.TRADE_TYPE_JSAPI);            paySendData.setSpBillCreateIp((String) map.get("remoteIp"));            paySendData.setOpenId((String) map.get("openId"));            //将参数拼成map,生产签名            SortedMap<String,Object> params = buildParamMap(paySendData);            paySendData.setSign(WeChatUtils.getSign(params));            //将请求参数对象转换成xml            String reqXml = WeChatUtils.sendDataToXml(paySendData);            //发送请求            byte[] xmlData = reqXml.getBytes();            URL url = new URL(WeChatConfig.UNIFIED_ORDER_URL);            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();            urlConnection.setDoOutput(true);            urlConnection.setDoInput(true);            urlConnection.setUseCaches(false);            urlConnection.setRequestProperty("Content_Type","text/xml");            urlConnection.setRequestProperty("Content-length",String.valueOf(xmlData.length));            DataOutputStream outputStream = new DataOutputStream(urlConnection.getOutputStream());            outputStream.write(xmlData);            outputStream.flush();            outputStream.close();            resultMap = WeChatUtils.parseXml(urlConnection.getInputStream());        } catch (Exception e) {            throw new ServiceException("微信支付统一下单异常",e);        }        return resultMap;    /**     * 构建统一下单参数map 用于生成签名     * @param data     * @return SortedMap<String,Object>     */    private SortedMap<String,Object> buildParamMap(WxPaySendData data) {        SortedMap<String,Object> paramters = new TreeMap<String, Object>();        if (null != data){            if (StringUtils.isNotEmpty(data.getAppId())){                paramters.put("appid",data.getAppId());            }            if (StringUtils.isNotEmpty(data.getAttach())){                paramters.put("attach",data.getAttach());            }            if (StringUtils.isNotEmpty(data.getBody())){                paramters.put("body",data.getBody());            }            if (StringUtils.isNotEmpty(data.getDetail())){                paramters.put("detail",data.getDetail());            }            if (StringUtils.isNotEmpty(data.getDeviceInfo())){                paramters.put("device_info",data.getDeviceInfo());            }            if (StringUtils.isNotEmpty(data.getFeeType())){                paramters.put("fee_type",data.getFeeType());            }            if (StringUtils.isNotEmpty(data.getGoodsTag())){                paramters.put("goods_tag",data.getGoodsTag());            }            if (StringUtils.isNotEmpty(data.getLimitPay())){                paramters.put("limit_pay",data.getLimitPay());            }            if (StringUtils.isNotEmpty(data.getMchId())){                paramters.put("mch_id",data.getMchId());            }            if (StringUtils.isNotEmpty(data.getNonceStr())){                paramters.put("nonce_str",data.getNonceStr());            }            if (StringUtils.isNotEmpty(data.getNotifyUrl())){                paramters.put("notify_url",data.getNotifyUrl());            }            if (StringUtils.isNotEmpty(data.getOpenId())){                paramters.put("openid",data.getOpenId());            }            if (StringUtils.isNotEmpty(data.getOutTradeNo())){                paramters.put("out_trade_no",data.getOutTradeNo());            }            if (StringUtils.isNotEmpty(data.getSign())){                paramters.put("sign",data.getSign());            }            if (StringUtils.isNotEmpty(data.getSpBillCreateIp())){                paramters.put("spbill_create_ip",data.getSpBillCreateIp());            }            if (StringUtils.isNotEmpty(data.getTimeStart())){                paramters.put("time_start",data.getTimeStart());            }            if (StringUtils.isNotEmpty(data.getTimeExpire())){                paramters.put("time_expire",data.getTimeExpire());            }            if (StringUtils.isNotEmpty(data.getProductId())){                paramters.put("product_id",data.getProductId());            }            if (data.getTotalFee()>0){                paramters.put("total_fee",data.getTotalFee());            }            if (StringUtils.isNotEmpty(data.getTradeType())){                paramters.put("trade_type",data.getTradeType());            }            //申请退款参数            if (StringUtils.isNotEmpty(data.getTransactionId())){                paramters.put("transaction_id",data.getTransactionId());            }            if (StringUtils.isNotEmpty(data.getOutRefundNo())){                paramters.put("out_refund_no",data.getOutRefundNo());            }            if (StringUtils.isNotEmpty(data.getOpUserId())){                paramters.put("op_user_id",data.getOpUserId());            }            if (StringUtils.isNotEmpty(data.getRefundFeeType())){                paramters.put("refund_fee_type",data.getRefundFeeType());            }            if (null != data.getRefundFee() && data.getRefundFee()>0){                paramters.put("refund_fee",data.getRefundFee());            }        }        return paramters;    }}

微信工具类 WeChatUtils.java

public class WeChatUtils {    /**     * 根据code获取微信授权access_token     * @param request     */    public static AuthToken getTokenByAuthCode(String code){        AuthToken authToken;        StringBuilder json = new StringBuilder();        try {            URL url = new URL(WeChatConstant.GET_AUTHTOKEN_URL+"appid="+ WeChatConstant.APP_ID+"&secret="+ WeChatConstant.APP_SECRET+"&code="+code+"&grant_type=authorization_code");            URLConnection uc = url.openConnection();            BufferedReader in = new BufferedReader(new InputStreamReader(uc.getInputStream()));            String inputLine ;            while((inputLine=in.readLine())!=null){                json.append(inputLine);            }            in.close();            //将json字符串转成javaBean            ObjectMapper om = new ObjectMapper();            authToken = readValue(json.toString(),AuthToken.class);    } catch (Exception e) {            throw new ServiceException("微信工具类:根据授权code获取access_token异常",e);        }        return authToken;    }    /**     * 获取微信签名     * @param map 请求参数集合     * @return 微信请求签名串     */    public static String getSign(SortedMap<String,Object> map){        StringBuffer sb = new StringBuffer();        Set set = map.entrySet();        Iterator iterator = set.iterator();        while (iterator.hasNext()){            Map.Entry entry = (Map.Entry) iterator.next();            String k = (String) entry.getKey();            Object v = entry.getValue();            //参数中sign、key不参与签名加密            if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)){                sb.append(k + "=" + v + "&");            }        }        sb.append("key=" + WeChatPayConfig.KEY);        String sign = MD5.MD5Encode(sb.toString()).toUpperCase();        return sign;    }    /**     * 解析微信服务器发来的请求     * @param inputStream     * @return 微信返回的参数集合     */    public static SortedMap<String,Object> parseXml(InputStream inputStream) {        SortedMap<String,Object> map = Maps.newTreeMap();        try {            //获取request输入流            SAXReader reader = new SAXReader();            Document document = reader.read(inputStream);            //得到xml根元素            Element root = document.getRootElement();            //得到根元素所有节点            List<Element> elementList = root.elements();            //遍历所有子节点            for (Element element:elementList){                map.put(element.getName(),element.getText());            }            //释放资源            inputStream.close();        } catch (Exception e) {            throw new ServiceException("微信工具类:解析xml异常",e);        }        return map;    }    /**     * 扩展xstream,使其支持name带有"_"的节点     */    public static XStream xStream = new XStream(new DomDriver("UTF-8",new XmlFriendlyNameCoder("-_","_")));    /**     * 请求参数转换成xml     * @param data     * @return xml字符串     */    public static String sendDataToXml(WxPaySendData data){        xStream.autodetectAnnotations(true);        xStream.alias("xml", WxPaySendData.class);        return xStream.toXML(data);    }    /**     *  获取当前时间戳     * @return 当前时间戳字符串     */    public static String getTimeStamp(){        return String.valueOf(System.currentTimeMillis()/1000);    }    /**     * 获取指定长度的随机字符串     * @param length     * @return 随机字符串     */    public static String getRandomStr(int length){        String base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";        Random random = new Random();        StringBuffer sb = new StringBuffer();        for (int i = 0; i < length; i++) {            int number = random.nextInt(base.length());            sb.append(base.charAt(number));        }        return sb.toString();    }}

微信常量类 WeChatConstant.java

public class WeChatConstant {    /**Token*/    public static final String TOKEN = "";    /**EncodingAESKey*/    public static final String AES_KEY = "";    /**消息类型:文本消息*/    public static final String MESSAGE_TYPE_TEXT = "text";    /**消息类型:音乐*/    public static final String MESSAGE_TYPE_MUSIC = "music";    /**消息类型:图文*/    public static final String MESSAGE_TYPE_NEWS = "news";    /**消息类型:图片*/    public static final String MESSAGE_TYPE_IMAGE = "image";    /**消息类型:视频*/    public static final String MESSAGE_TYPE_VIDEO = "video";    /**消息类型:小视频*/    public static final String MESSAGE_TYPE_SHORTVIDEO = "shortvideo";    /**消息类型:链接*/    public static final String MESSAGE_TYPE_LINK = "link";    /**消息类型:地理位置*/    public static final String MESSAGE_TYPE_LOCATION = "location";    /**消息类型:音频*/    public static final String MESSAGE_TYPE_VOICE = "voice";    /**消息类型:事件推送*/    public static final String MESSAGE_TYPE_EVENT = "event";    /**事件类型:subscribe(订阅)*/    public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";    /**事件类型:unsubscribe(取消订阅)*/    public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";    /**事件类型:CLICK(自定义菜单点击事件)*/    public static final String EVENT_TYPE_CLICK = "CLICK";    /**返回消息类型:转发客服*/    public static final String TRANSFER_CUSTOMER_SERVICE="transfer_customer_service";    /**ACCESS_TOKEN*/    public static final String ACCESS_TOKEN_ENAME = "access_token";    /**返回成功字符串*/    public static final String RETURN_SUCCESS = "SUCCESS";    /**主动发送消息url*/    public static final String SEND_MESSAGE_URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=";    /**通过code获取授权access_token的URL*/    public static final String GET_AUTHTOKEN_URL = " https://api.weixin.qq.com/sns/oauth2/access_token?";}

其他微信对象:

封装微信授权返回的信息,此处属性均为小写【微信返回的是小写很不友好

public class AuthToken implements Serializable {    /**授权access_token*/    private String access_token;    /**有效期*/    private String expires_in;    /**刷新access_token*/    private String refresh_token;    /**用户OPENID*/    private String openid;    /**授权方式Scope*/    private String scope;    /**错误码*/    private String errcode;    /**错误消息*/    private String errmsg;    /**getter() and setter()*/}

微信请求参数对象【下单与退款均可使用此对象】

public class WxPaySendData {    /**公众账号ID 必须*/    @XStreamAlias("appid")    private String appId;    /**商户号 必须*/    @XStreamAlias("mch_id")    private String mchId;    /**设备号*/    @XStreamAlias("device_info")    private String deviceInfo;    /**随机字符串 必须*/    @XStreamAlias("nonce_str")    private String nonceStr;    /**签名 必须*/    @XStreamAlias("sign")    private String sign;    /**商品描述 必须*/    @XStreamAlias("body")    private String body;    /**商品详情*/    @XStreamAlias("detail")    private String detail;    /**附加数据*/    @XStreamAlias("attach")    private String attach;    /**商户订单号 必须*/    @XStreamAlias("out_trade_no")    private String outTradeNo;    /**货币类型*/    @XStreamAlias("fee_type")    private String feeType;    /**交易金额 必须[JSAPI,NATIVE,APP]*/    @XStreamAlias("total_fee")    private int totalFee;    /**交易类型 [必须]*/    @XStreamAlias("trade_type")    private String tradeType;    /**通知地址 [必须]*/    @XStreamAlias("notify_url")    private String notifyUrl;    /**终端Ip [必须]*/    @XStreamAlias("spbill_create_ip")    private String spBillCreateIp;    /**订单生成时间yyyyMMddHHmmss*/    @XStreamAlias("time_start")    private String timeStart;    /**订单失效时间yyyyMMddHHmmss 间隔>5min*/    @XStreamAlias("time_expire")    private String timeExpire;    /**用户标识 tradeType=JSAPI时必须*/    @XStreamAlias("openid")    private String openId;    /**商品标记*/    @XStreamAlias("goods_tag")    private String goodsTag;    /**商品ID tradeType=NATIVE时必须*/    @XStreamAlias("product_id")    private String productId;    /**指定支付方式*/    @XStreamAlias("limit_pay")    private String limitPay;    /**     *以下属性为申请退款参数     */    /**微信订单号 [商户订单号二选一]*/    @XStreamAlias("transaction_id")    private String transactionId;    /**商户退款单号 [必须]*/    @XStreamAlias("out_refund_no")    private String outRefundNo;    /**退款金额 [必须]*/    @XStreamAlias("refund_fee")    private Integer refundFee;    /**货币种类*/    @XStreamAlias("refund_fee_type")    private String refundFeeType;    /**操作员账号:默认为商户号 [必须]*/    @XStreamAlias("op_user_id")    private String opUserId;    /**getter() and setter()*/}
原创粉丝点击