微信公众号支付【Java版】

微信公众号支付【Java版】

说明:

① 本文主要讲解的是微信公众号内(商城)支付部分,如需了解其他微信公众号开发内容,请访问:http://blog.csdn.net/lyq8479/article/details/8944988 【柳峰的博客 内容为2013year的有些内容已经改变,如需交流,请留言】

② 部分内容来源于我学习时参考的博客 :http://www.oschina.net/code/snippet_1754599_49966 【非常感谢 zyjason91 】

③ 本文由个人的印象笔记导出,如你也在使用印象笔记,我可以直接把笔记分享给你,我的印象笔记:dabingryan@gmail.com

喜欢的小伙伴欢迎关注我的公众号:Java实战。

1.准备工作

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

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

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

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

 

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

/**
*微信支付统一下单
**/
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()*/
}

转载于:https://my.oschina.net/firstbing/blog/684810

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值