Java微信支付开发之公众号支付(微信内H5调起支付)

官方文档

准备工作:已通过微信认证的公众号,必须通过ICP备案域名(否则会报支付失败)

借鉴了很多大神的文章,在此先谢过了

整个支付流程,看懂就很好写了

微信内网页支付时序图

一、设置支付目录

在微信公众平台设置您的公众号支付支付目录,设置路径见下图。公众号支付在请求支付的时候会校验请求来源是否有在公众平台做了配置,所以必须确保支付目录已经正确的被配置,否则将验证失败,请求支付不成功。

支付授权目录就是指支付方法的请求全路径

支付目录配置

 

二、设置授权域名

开发公众号支付时,在统一下单接口中要求必传用户openid,而获取openid则需要您在公众平台设置获取openid的域名,只有被设置过的域名才是一个有效的获取openid的域名,否则将获取失败。具体界面如下图所示:

 

微信网页授权域名

 

三、授权进入支付页面

授权部分,我是用的静默授权,拿到openid就好了

授权进入支付页面方法

/** 
 * 静默授权进入H5支付页面 
 * @return 
 * @throws Exception 
 */  
@GetMapping("preJsPay")
public String jsPay( ) throws Exception {  
    AuthAccessToken authAccessToken = null;  
    String code = this.getRequest().getParameter("code");
    if(StringUtils.isEmpty(code)){
    	return "error";
    }
    String state = this.getRequest().getParameter("state");  
    if(state.equals(MD5Util.MD5Encode("ceshi", ""))){
        AuthTokenParams authTokenParams = new AuthTokenParams();  
        authTokenParams.setAppid(WechatConfig.APP_ID);
        authTokenParams.setSecret(WechatConfig.APP_SECRET);  
        authTokenParams.setCode(code);
        authAccessToken = wechatAuthService.getAuthAccessToken(authTokenParams, null);
        logger.debug("正在支付的openid {} "	,authAccessToken.getOpenid());  
        return "wxpay/jspay";
    }
    return "error";
}  

支付页面的的body(这是原先的jsp页面,目前我改成了thymeleaf,或者改写成vue、ng)

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<c:set var="ctx" value="${pageContext.request.contextPath}" />
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<title>微信支付</title>
</head>
<body>
	<h3 class="demos-title" style="margin-bottom: 50px; margin-top: 50px">测试</h3>
	<div class="weui_cell"></div>
	<div class="weui_btn_area" style="margin-top: 80px">
	   <input class="weui_btn weui_btn_primary" type="button" value="点击支付" onclick="pay()"/>
	</div>
	<script type="text/javascript">
	var prepay_id ;
    var paySign ;
    var appId ;
    var timeStamp ;
    var nonceStr ;
    var packageStr ;
    var signType ;
    function pay(){
        var url = '${ctx}/wxpay/jspay';
        $.ajax({
        type:"post",
        url:url,
        dataType:"json",
        data:{openId:'${openId}'}, //自行拼接body,total_fee等参数
        success:function(data) {
        	if(data.data.resultCode == 'SUCCESS'){
        		appId = data.appId;
        		paySign = data.paySign;
        		timeStamp = data.timeStamp;
        		nonceStr = data.nonceStr;
        		packageStr = data.packageStr;
        		signType = data.signType;
                callpay();
            }else{
            	alert("统一下单失败");
            }
        }
    }); 
    }
    
    function onBridgeReady(){
        WeixinJSBridge.invoke(
            'getBrandWCPayRequest', {
                 "appId":appId,     //公众号名称,由商户传入
                 "paySign":paySign,         //微信签名
                 "timeStamp":timeStamp, //时间戳,自1970年以来的秒数
                 "nonceStr":nonceStr , //随机串
                 "package":packageStr,  //预支付交易会话标识
                 "signType":signType     //微信签名方式
             },
             function(res){
            	 if(res.err_msg == "get_brand_wcpay_request:ok" ) {
             //window.location.replace("index.html");
             alert('支付成功');
         }else if(res.err_msg == "get_brand_wcpay_request:cancel"){
             alert('支付取消');
         }else if(res.err_msg == "get_brand_wcpay_request:fail" ){
            alert('支付失败');
         } //使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回    ok,但并不保证它绝对可靠。
             }
        );
    }
    function callpay(){
        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>
</body>
</html>

四、统一下单并获取prepay_id并返回页面支付参数

统一下单的官方文档,prepay_id获取到就是成功了一半了

由于我是用的restcontroller,返回的类型你可以改成JsPayResult

 /**
     * 微信内H5调起支付
     *
     * @param params
     * @return
     * @throws Exception
     */
    @PostMapping("js")
    public Map<String, Object> js(HttpServletRequest request, @ModelAttribute(value = "params") UnifiedOrderParams params) {
        Map<String, Object> data = new HashMap<>();
        if (StringUtils.isEmpty(params) || StringUtils.isEmpty(params.getOpenid())) {
            data.put("code", -1);
            data.put("msg", "支付数据错误");
            return data;
        }
        log.debug("****正在支付的openId****{}", params.getOpenid());
        // 统一下单
        String out_trade_no = PayHelper.createOutTradeNo();
        // int total_fee = 1; // 产品价格1分钱,用于测试
        String spbill_create_ip = HttpUtil.getRemortIP(request);
        log.debug("支付IP {} ", spbill_create_ip);
        String nonce_str = PayHelper.createNonceStr(); // 随机数据
        // 参数组装
        UnifiedOrderParams unifiedOrderParams = new UnifiedOrderParams();
        unifiedOrderParams.setAppid(wechatAccountConfig.getAppid());// 必须
        unifiedOrderParams.setMch_id(PayConstant.MCH_ID);// 必须
        unifiedOrderParams.setOut_trade_no(out_trade_no);// 必须
        unifiedOrderParams.setBody(params.getBody());// 必须 微信支付-支付测试
        unifiedOrderParams.setTotal_fee(params.getTotal_fee()); // 必须
        unifiedOrderParams.setNonce_str(nonce_str); // 必须
        unifiedOrderParams.setSpbill_create_ip(spbill_create_ip); // 必须
        unifiedOrderParams.setTrade_type("JSAPI"); // 必须
        unifiedOrderParams.setOpenid(params.getOpenid());
        unifiedOrderParams.setNotify_url(wechatPayConfig.getNotifyUrl());// 异步通知url
        // 统一下单 请求的Xml(正常的xml格式)
        String unifiedXmL = PayHelper.toPayXml(unifiedOrderParams);// 签名并入util
        // 进行签名校验
        try {
            // 返回<![CDATA[SUCCESS]]>格式的XML
            String unifiedOrderResultXmL = HttpUtil.doPost(
                    wechatPayConfig.getUnifiedOrderUrl(), null, unifiedXmL);
            if (SignatureUtil.checkValidPaySign(unifiedOrderResultXmL, null)) {
                String timeStamp = PayHelper.createTimeStamp();
                // 统一下单响应
                UnifiedOrderResult unifiedOrderResult = XmlUtil.fromXml(unifiedOrderResultXmL,
                        UnifiedOrderResult.class);
                JsPayResult result = new JsPayResult();
                result.setAppId(wechatAccountConfig.getAppid());
                result.setTimeStamp(timeStamp);
                result.setNonceStr(unifiedOrderResult.getNonce_str());// 直接用返回的
                /**** prepay_id 2小时内都有效,再次支付方法自己重写 ****/
                result.setPackageStr("prepay_id=" + unifiedOrderResult.getPrepay_id());
                /**** 用对象进行签名 ****/
                String paySign = SignatureUtil.md5Hex(result, PayConstant.API_KEY,
                        null);
                result.setPaySign(paySign);
                result.setResultCode(unifiedOrderResult.getResult_code());
                data.put("code", 0);
                data.put("msg", "支付成功");
                data.put("data", result);
            } else {
                data.put("code", -1);
                data.put("msg", "支付签名验证错误");
                log.debug("签名验证错误");
            }
        } catch (Exception e) {
            data.put("code", -1);
            data.put("msg", "支付失败");
            log.debug(e.getMessage());
        }
        return data;
    }

五、完成支付并通知支付结果

支付通知结果

package com.phil.wechat.pay.controller;

import com.phil.modules.result.ResultState;
import com.phil.modules.util.SignatureUtil;
import com.phil.modules.util.XmlUtil;
import com.phil.wechat.pay.model.response.PayNotifyResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.util.Objects;

/**
 * 微信支付结果通知(统一下单参数的notify_url)
 *
 * @author phil
 * @date 2017年6月27日
 */
@RestController
@RequestMapping("api/wxpay")
@Slf4j
public class WechatPayNotifyController {

    @RequestMapping("notify")
    public ResultState notice(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ResultState resultState = new ResultState();
        log.debug("开始处理支付返回的请求");
        String resXml = ""; // 反馈给微信服务器
        String notifyXml = IOUtils.toString(request.getInputStream());// 微信支付系统发送的数据(<![CDATA[product_001]]>格式)
        log.debug("微信支付系统发送的数据" + notifyXml);
        // 验证签名
        if (SignatureUtil.checkValidPaySign(notifyXml, null)) {
            PayNotifyResult notify = XmlUtil.fromXml(notifyXml, PayNotifyResult.class);
            log.debug("支付结果 {}", notify.toString());
            if (Objects.equals("SUCCESS", notify.getResult_code())) {
                resultState.setErrcode(0);// 表示成功
                resultState.setErrmsg(notify.getResult_code());
                /**** 业务逻辑 保存openid之类的 ****/
                // 通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了
                resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
                        + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
            } else {
                resultState.setErrcode(-1);// 支付失败
                resultState.setErrmsg(notify.getErr_code_des());
                log.debug("支付失败,错误信息:" + notify.getErr_code_des());
                resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA["
                        + notify.getErr_code_des() + "]]></return_msg>" + "</xml> ";
            }
        } else {
            resultState.setErrcode(-1);// 支付失败
            resultState.setErrmsg("签名验证错误");
            log.debug("签名验证错误");
            resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                    + "<return_msg><![CDATA[签名验证错误]]></return_msg>" + "</xml> ";
        }
        try (BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream())) {
            out.write(resXml.getBytes());out.flush();
        } catch (Exception e) {

            log.error(e.getMessage());
        }
        return resultState;
    }
}

具体源码:https://github.com/philjing/my_wechat/tree/master/src/main/java/com/phil/wechat/pay

扫一扫加群

 

posted @ 2017-06-02 21:08  phil_jing  Views(55)  Comments(0Edit  收藏  举报