Java微信开发之公众号支付接口

1、设置支付路径

使用微信公众号支付接口,必须在微信公众号管理后台中设置支付路径。这个微信接口文档说得很清楚,请参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_3

2、公众号支付业务流程

Java微信开发之公众号支付接口

简单的描述就是:

首先需要根据我们自己的订单(4),也就是需要支付的明细,然后使用统一下单API向微信请求生成微信看得懂的订单(5),然后生成支付参数及签名(6),在支付页面根据支付配置及微信统一订单的prepay_id,发起微信支付(7)。用户输入密码支付后,微信会通过异步的方式,通知我们的系统,告诉支付的结果(10)。我们在系统后台处理根据支付的结果,处理我们的业务后,返回信息告诉向往,此订单已确定(11)。

3、统一下单

我们生成订单后,通过统一下单API,把微信的订单与我们的订单关联起来。

微信的统一下单接口:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1

统一订单实体类:

package com.benben.timetable.wechat.entity;

/**
 * 统一下单
 * 
 * @ClassName: UnifiedOrder
 * @Description: TODO
 * @author 潘广伟(笨笨)
 * @date 2015年9月18日
 *
 */
public class UnifiedOrder {
    /**
     * 公众账号ID
     */
    private String appid;
    /**
     * 商户号
     */
    private String mch_id;
    /**
     * 附加数据(说明)
     */
    private String attach;
    /**
     * 商品描述
     */
    private String body;
    /**
     * 随机串
     */
    private String nonce_str;
    /**
     * 通知地址
     */
    private String notify_url;
    /**
     * 用户标识
     */
    private String openid;
    /**
     * 商户订单号
     */
    private String out_trade_no;
    /**
     * 终端IP(用户)
     */
    private String spbill_create_ip;
    /**
     * 总金额
     */
    private Integer total_fee;
    /**
     * 交易类型
     */
    private String trade_type;
    /**
     * 签名
     */
    private String sign;
    /**
     * WEB
     */
    private String device_info;

    public UnifiedOrder(){
        this.trade_type = "JSAPI";
        this.device_info = "WEB";
    }

    public String getAppid() {
        return appid;
    }

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

    public String getAttach() {
        return attach;
    }

    public void setAttach(String attach) {
        this.attach = attach;
    }

    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;
    }
}

统一下单代码中使用到的参数:

    /**
     * 应用编号(微信公众号编号)
     */
    private @Value("${app_id}") String appId;
    /**
     * 商户号码
     */
    private @Value("${mch_id}") String mchId;
    /**
     * 支付码
     */
    private @Value("${pay_key}") String payKey;
    /**
     * 统一下单URL
     */
    private @Value("${pay_unifiedorder_url}") String unifiedOrderUrl;

统一下单,并返回获取prepay_id:

    /**
     * 统一下单
     * @Title: unifiedOrder 
     * @Description: TODO 
     * @param: @param openId 微信用户openId
     * @param: @param orderId 订单ID
     * @param: @param money 订单总价,单位:分
     * @param: @param callbackUrl 回调路径
     * @param: @return
     * @param: @throws Exception
     * @return: String
     */
    public String unifiedOrder(String openId,String orderId,int money,String callbackUrl) throws Exception{
        UnifiedOrder unifiedOrder = new UnifiedOrder();
        unifiedOrder.setAppid(appId);
        unifiedOrder.setAttach("hehedesk");

        unifiedOrder.setBody("hehedesk");
        unifiedOrder.setMch_id(mchId);

        String nonce = UUID.randomUUID().toString().substring(0, 30);
        unifiedOrder.setNonce_str(nonce);
        unifiedOrder.setNotify_url(callbackUrl);

        unifiedOrder.setOpenid(openId);
        unifiedOrder.setOut_trade_no(orderId);

        unifiedOrder.setSpbill_create_ip("14.23.150.211");
        unifiedOrder.setTotal_fee(money);

        String sign = createUnifiedOrderSign(unifiedOrder);
        unifiedOrder.setSign(sign);

        /**
         * 转成XML格式
         */
        xmlUtil.getXstreamInclueUnderline().alias("xml", unifiedOrder.getClass());
        String xml = xmlUtil.getXstreamInclueUnderline().toXML(unifiedOrder);

        String response = httpConnection.post(unifiedOrderUrl, xml);
        logger.info("unifiedOrder");
        logger.info(response);
        Map<String, String> responseMap = xmlUtil.parseXml(response);

        return responseMap.get("prepay_id");
    }

openid相关介绍:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_4

实体对象转XML介绍:http://blog.csdn.net/jrainbow/article/details/49779277

关联我们的订单与微信订单的prepay_id:

String url = "http://letus.xyz/mall/order/wechat_notify";
String prepayId = wechatMerchantFactory.unifiedOrder(order.getUser().getOpenId(), order.getId(), (int) (100*order.getTotal()),url );
order.setPrepayId(prepayId);

回调路径callbackUrl是指支付成功后,微信向我们系统服务器回复支付结果信息的路径。支付成功后的订单处理逻辑一这个路径中写。

关联了订单与prepay_id后,我们就可以使用prepay_id来获取我们的订单及处理后续业务。

统一下单的签名:

    /**
     * 获取统一下单签名
     * @Title: createUnifiedOrderSign
     * @Description: TODO
     * @param @param unifiedOrder
     * @param @return    
     * @return String    
     * @throws
     */
    public String createUnifiedOrderSign(UnifiedOrder unifiedOrder){
        StringBuffer sign = new StringBuffer();
        sign.append("appid=").append(unifiedOrder.getAppid());
        sign.append("&attach=").append(unifiedOrder.getAttach());
        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());
        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(payKey);

        return DigestUtils.md5Hex(sign.toString()).toUpperCase();
    }

签名算法请参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3

签名特别注意以下重要规则(微信文档中也有):
◆ 参数名ASCII码从小到大排序(字典序);
◆ 如果参数的值为空不参与签名;
◆ 参数名区分大小写;
◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。
◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段

因为微信商户相关的开发中,多次用到排序后的字符签名,后面我们会写一个排序的工具类,以避免每次都是手写拼接字符串来实现。

4、网页端调起支付API

支付相关文档参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6

4.1、生成支付参数及签名

支付配置实体:

package com.benben.timetable.wechat.entity;

/**
 * JS-SDK使用的配置信息
 * @ClassName: JsAPIConfig 
 * @Description: TODO
 * @author 潘广伟 
 * @date 2015年8月17日 下午3:12:35 
 *
 */
public class JsAPIConfig {
    private boolean debug;
    private String appId;
    private String timestamp;
    private String nonce;
    private String signature;
    private String title;
    private String link;
    private String signType;
    private String packageName;

    public JsAPIConfig(){
        signType = "MD5";
    }

    public boolean isDebug() {
        return debug;
    }

    public void setDebug(boolean debug) {
        this.debug = debug;
    }

    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 getNonce() {
        return nonce;
    }

    public void setNonce(String nonce) {
        this.nonce = nonce;
    }

    public String getSignature() {
        return signature;
    }

    public void setSignature(String signature) {
        this.signature = signature;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getLink() {
        return link;
    }

    public void setLink(String link) {
        this.link = link;
    }

    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;
    }   
}

生成支付配置:


    /**
     * 获取支付配置
     * @Title: createPayConfig
     * @Description: TODO
     * @param @param preayId 统一下单prepay_id
     * @param @return
     * @param @throws Exception    
     * @return JsAPIConfig    
     * @throws
     */
    public JsAPIConfig createPayConfig(String prepayId) throws Exception {
        JsAPIConfig config = new JsAPIConfig();

        String nonce = UUID.randomUUID().toString();
        String timestamp = Long.toString(System.currentTimeMillis() / 1000);
        String packageName = "prepay_id="+prepayId;
        StringBuffer sign = new StringBuffer();
        sign.append("appId=").append(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(payKey);
        String signature = DigestUtils.md5Hex(sign.toString()).toUpperCase();

        config.setAppId(appId);
        config.setNonce(nonce);
        config.setTimestamp(timestamp);
        config.setPackageName(packageName);
        config.setSignature(signature);

        return config;
    }

4.2、页面调用JSAPI来进行支付

$('#pay-button').on('click',function(){
    $.ajax({
        url:'wechat/mall/pay/'+$('#prepay_id').html(),
        type : "get",
        data : {
            "timestamp" : new Date().getTime()
        },
        success : function(response) {
            var data = eval('(' + response + ')'); 
            var obj = data.msg;
            WeixinJSBridge.invoke('getBrandWCPayRequest',{  
                "appId" : obj.appId,                  //公众号名称,由商户传入  
                "timeStamp":obj.timestamp,          //时间戳,自 1970 年以来的秒数  
                "nonceStr" : obj.nonce,         //随机串  
                "package" : obj.packageName,      //商品包信息</span>  
                "signType" : obj.signType,        //微信签名方式
                "paySign" : obj.signature           //微信签名  
                },function(res){      

                if(res.err_msg == "get_brand_wcpay_request:ok" ) {  
                    alert('支付成功');  
                }
            });  
        }     
    });         
});

单点击支付按钮时,首先获取JSAPI需要的配置参数,然后再调用WeixinJSBridge来使用getBrandWCPayRequest请求进行支付。

5、支付结果回调

当用户输入密码,支付动作完成后。微信会通过前面我们设置的callbackUrl来返回支付结果。

相关资料参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7

下面是http://letus.xyz/mall/order/wechat_notify回调路径的内容:


    /**
     * 微信支付回调页面
     * @Title: wechatPayNotify
     * @Description: TODO
     * @param @param request
     * @param @param trade_status
     * @param @param out_trade_no
     * @param @param trade_no    
     * @return void    
     * @throws
     */
    @ResponseBody 
    @RequestMapping(value="wechat_notify")
    public String wechatPayNotify(HttpServletRequest request){
        try {
             Map<String, String> map = getCallbackParams(request);
             if (map.get("result_code").toString().equalsIgnoreCase("SUCCESS")) {
                 String orderId = map.get("out_trade_no");
                 //这里写成功后的业务逻辑
                 orderService.updateConfirm(orderId);
             }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return wechatMerchantFactory.getPayCallback(); 
    }
    /**
     * 获取请求参数
     * @Title: getCallbackParams
     * @Description: TODO
     * @param @param request
     * @param @return
     * @param @throws Exception    
     * @return Map<String,String>    
     * @throws
     */
    public Map<String, String> getCallbackParams(HttpServletRequest request)
            throws Exception {
        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);
        }
        System.out.println("~~~~~~~~~~~~~~~~付款成功~~~~~~~~~");
        outSteam.close();
        inStream.close();
        String result = new String(outSteam.toByteArray(), "utf-8");
        return xmlUtil.parseXml(result);
    }

注:这里的out_trade_no指的不是前面说的prepay_id。而是我们系统中的orderId。

支付结果确认收到后,我们还需要告知微信。

return wechatMerchantFactory.getPayCallback(); 

PayCallback:

package com.benben.timetable.wechat.entity;
/**
 * 支付成功回复
 * @ClassName: PayCallBack
 * @Description: TODO
 * @author 潘广伟(笨笨)
 * @date 2015年11月12日
 *
 */
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;
    }

}

getPayCallback方法:

    /**
     * 生成收到支付结果的确认信息
     * @Title: getPayCallback
     * @Description: TODO
     * @param @return    
     * @return String    
     * @throws
     */
    public String getPayCallback(){
        PayCallback callback = new PayCallback();
        xmlUtil.xstream().alias("xml", callback.getClass());
        String xml = xmlUtil.xstream().toXML(callback);

        return xml;
    }

注:这里记得一定要回复确认信息,微信会可能会多次发送同样的通知给商户系统。商户系统必须能够正确处理重复的通知。

HttpConnection:https://code.csdn.net/snippets/1361856

上一篇:ECMAScript 2018(ES9)新特性简介


下一篇:编写更加稳定、可读性强的JavaScript代码