天天看點

微信小程式、app內建微信支付

一、微信小程式支付

開發文檔:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_11&index=2

申請小程式開發者賬号,進行微信認證,擷取appid,開通微信支付,即綁定申請的微信支付商戶号。

1.小程式支付流程:

微信小程式、app內建微信支付

2.商戶系統和微信支付系統主要互動:

      1、小程式内調用登入接口,擷取到使用者的openid。

      2、商戶調用支付接口統一下單。

      3、商戶再次簽名後傳回發起支付需要的參數。

      4、商戶調起微信支付控件後付款,并接收支付通知處理賬單狀态。

3.微信統一下單:

接口連結:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1

URL位址:https://api.mch.weixin.qq.com/pay/unifiedorder

然後編寫我們自己的支付服務調用該下單接口。

支付controller:

/**
     * 付款
     *
     * @param  request
     * @param  openid   使用者辨別
     * @param  tradeType    交易類型 小程式取值如下:JSAPI  APP--app支付      
     * @return
     */
    @RequestMapping("/payment")
    public Object payment(String openid, String body, String tradeType, HttpServletRequest request) {
        //userId從session中擷取即可
        return payService.payment(userId, openid, body, tradeType, request);
    }
           

支付服務:

@Service
public class PayService  {

    @Resource
    private IPayDetailService payDetailService;

    /**
     * 小程式appid
     */
    @Value("${wx.applet.appid}")
    String wx_applet_appid;

    /**
     * APP移動應用端appid
     */
    @Value("${app.appid}")
    String app_appid;
    /**
     * 微信小程式商戶平台商戶号
     */
    @Value("${wx.sh.mch_id}")
    String mchId;

    /**
     * 微信app商戶平台商戶号
     */
    @Value("${wx.app.mch_id}")
    String appMchId;

    /**
     * 付款
     *
     * @param userId
     * @param openid
     * @param body
     * @param tradeType    交易類型 小程式取值如下:JSAPI  APP--app支付
     * @param request
     * @return
     */
    @Transactional
    @Override
    public ServiceResult<Object> payment(String userId, String openid, String body, String tradeType, HttpServletRequest request) {
        //生成訂單号
        String orderNumber = GenerateNum.getInstance().GenerateOrder();
                //微信支付調用統一下單接口
                //小程式端
                if (!StringUtils.isEmpty(tradeType) && PayConstant.TRADE_TYPE_WX_APPLET.equals(tradeType)) {
                    return payDetailService.wxPaybill(wx_applet_appid, mchId, orderNumber, PayConstant.WX_PAY_NOTIFY_URL, PayConstant.TRADE_TYPE_WX_APPLET,
                            openid, order.getActuallyPayMoney(), body, IPUtil.getIpAddr(request));
                    //app端
                } else if (!StringUtils.isEmpty(tradeType) && PayConstant.TRADE_TYPE_APP.equals(tradeType)) {
                    return payDetailService.wxPaybill(app_appid, appMchId, orderNumber, PayConstant.WX_PAY_NOTIFY_URL, PayConstant.TRADE_TYPE_APP,
                            null, order.getActuallyPayMoney(), body, IPUtil.getIpAddr(request));
                }
            } 
        return ServiceResultHelper.genResultWithFaild();
    }
           

下單服務:

/**
     * 微信下單
     *
     * @param appId            APP端或者小程式appid
     * @param mchId            商戶号
     * @param orderNumber      訂單編号
     * @param notifyUrl        通知url
     * @param tradeType        交易類型
     * @param openid           使用者辨別
     * @param totalFee         訂單總金額,機關為分
     * @param body             商品描述
     * @param spbill_create_ip APP和網頁支付送出使用者端ip
     * @return
     */
    @Override
    public ServiceResult<Object> wxPaybill(String appId, String mchId, String orderNumber, String notifyUrl, String tradeType,
                                           String openid, BigDecimal totalFee, String body, String spbill_create_ip) {
        try {
            if (StringUtils.isEmpty(orderNumber)) {
                return ServiceResultHelper.genResultWithFaild("訂單号不能為空", -1);
            }
            //生成的随機字元串
            String nonce_str = CharacterUtil.getRandomString(32);
            //組裝參數,使用者生成統一下單接口的簽名
            Map<String, Object> packageParams = new HashMap<>();
            if (!StringUtils.isEmpty(openid)) {
                packageParams.put("openid", openid);
            }
            packageParams.put("appid", appId);
            packageParams.put("mch_id", mchId);
            packageParams.put("nonce_str", nonce_str);
            packageParams.put("body", body);
            packageParams.put("out_trade_no", orderNumber);//訂單号
            packageParams.put("total_fee", totalFee.toString());//支付金額,這邊需要轉成字元串類型,否則後面的簽名會失敗
            packageParams.put("spbill_create_ip", spbill_create_ip);
            packageParams.put("notify_url", notifyUrl);//支付成功後的回調位址
            packageParams.put("trade_type", tradeType);//支付方式

            String prestr = PayUtil.createLinkString(packageParams); // 把數組所有元素,按照“參數=參數值”的模式用“&”字元拼接成字元串

            //MD5運算生成簽名,這裡是第一次簽名,用于調用統一下單接口
            String sign = PayUtil.sign(prestr, PayConstant.WX_PAY_PAY_KEY, "utf-8").toUpperCase();

            //拼接統一下單接口使用的xml資料,要将上一步生成的簽名一起拼接進去
            packageParams.put("sign", sign);
            String xml = MapXmlUtil.map2Xmlstring(packageParams);
            LOG.info("調用統一下單接口請求XML資料:" + xml);

            //調用統一下單接口,并接受傳回的結果
            String result = PayUtil.httpRequest(PayConstant.WX_PAY_BILL_URL, "POST", xml);

            LOG.info("調用統一下單接口傳回XML資料:" + result);
            if (StringUtils.isEmpty(result)) {
                return ServiceResultHelper.genResultWithFaild("調用統一下單接口傳回結果為空", -1);
            }

            // 将解析結果存儲在HashMap中
            Map map = MapXmlUtil.xmlString2Map(result);
            String return_code = (String) map.get("return_code");//傳回狀态碼
            Map<String, Object> data = new HashMap<>();//傳回給小程式端需要的參數
            String prepay_id = (String) map.get("prepay_id");//傳回的預付單資訊
            if (PayConstant.PAY_ORDER_SUCCESS.equals(return_code)) {
                //小程式的傳回值
                if (!StringUtils.isEmpty(openid)) {
                    data.put("appId", appId);
                    data.put("nonceStr", nonce_str);
                    data.put("package", "prepay_id=" + prepay_id);
                    data.put("signType", "MD5");
                    Long timeStamp = System.currentTimeMillis() / 1000;
                    data.put("timeStamp", timeStamp + "");//這邊要将傳回的時間戳轉化成字元串,不然小程式端調用wx.requestPayment方法會報簽名錯誤
                    //拼接簽名需要的參數
                    String stringSignTemp = PayUtil.createLinkString(data);
                    //再次簽名,這個簽名用于小程式端調用wx.requesetPayment方法
                    String paySign = PayUtil.sign(stringSignTemp, PayConstant.WX_PAY_PAY_KEY, "utf-8").toUpperCase();
                    data.put("paySign", paySign);
                } else {
                    //app的傳回值
                    data.put("appid", appId);
                    data.put("partnerid", mchId);
                    data.put("prepayid", prepay_id);
                    data.put("noncestr", nonce_str);
                    Long timeStamp = System.currentTimeMillis() / 1000;
                    data.put("timestamp", timeStamp + "");
                    data.put("package", "Sign=WXPay");
                    String stringSignTemp = PayUtil.createLinkString(data);
                    String appSign = PayUtil.sign(stringSignTemp, PayConstant.WX_PAY_PAY_KEY, "utf-8").toUpperCase();
                    data.put("sign", appSign);
                }
                return ServiceResultHelper.genResultWithSuccess(data);
            }
            return ServiceResultHelper.genResultWithFaild(map.get("return_msg") != null ? map.get("return_msg").toString() : null, -1);
        } catch (Exception e) {
            LOG.info("下單異常:" + e.getMessage());
            return ServiceResultHelper.genResultWithFaild(e.getMessage(), -1);
        }
           

調用微信下單接口傳回結果:

字段名 變量名 必填 類型 示例值 描述
傳回狀态碼 return_code String(16) SUCCESS

SUCCESS/FAIL

此字段是通信辨別,非交易辨別,交易是否成功需要檢視result_code來判斷

傳回資訊 return_msg String(128) 簽名失敗

傳回資訊,如非空,為錯誤原因

簽名失敗

參數格式校驗錯誤

paySign為再次簽名,這個簽名用于小程式端調用wx.requesetPayment方法,調起微信支付控件。

requesetPayment 參數說明:

參數 類型 必填 說明
timeStamp String 時間戳從1970年1月1日00:00:00至今的秒數,即目前的時間
nonceStr String 随機字元串,長度為32個字元以下。
package String 統一下單接口傳回的 prepay_id 參數值,送出格式如:prepay_id=*
signType String 簽名類型,預設為MD5,支援HMAC-SHA256和MD5。注意此處需與統一下單的簽名類型一緻
paySign String 簽名,具體簽名方案參見微信公衆号支付幫助文檔;
success Function 接口調用成功的回調函數
fail Function 接口調用失敗的回調函數
complete Function 接口調用結束的回調函數(調用成功、失敗都會執行)

調用wx.requestPayment(OBJECT)發起微信支付。是以在調用下單接口成功後要傳回需要的參數。詳見下單服務代碼小程式傳回參數部分。

擷取ip位址工具:

import java.net.InetAddress;
import java.net.UnknownHostException;
import javax.servlet.http.HttpServletRequest;

public class IPUtil {
    public IPUtil() {
    }

    public static String getIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Forwarded-For");
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
            if (ip.equals("127.0.0.1") || ip.equals("0:0:0:0:0:0:0:1")) {
                InetAddress inet = null;

                try {
                    inet = InetAddress.getLocalHost();
                } catch (UnknownHostException var4) {
                    var4.printStackTrace();
                }

                ip = inet.getHostAddress();
            }
        }

        if (ip != null && ip.length() > 15 && ip.indexOf(",") > 0) {
            ip = ip.substring(0, ip.indexOf(","));
        }

        return ip;
    }
}
           

訂單号生成工具:

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 訂單号生成工具
 */
public class GenerateNum {

    // 使用單例模式,不允許直接建立執行個體
    private GenerateNum() {
    }

    // 建立一個空執行個體對象,類需要用的時候才指派
    private static GenerateNum g = null;

    // 單例模式--懶漢模式
    public static synchronized GenerateNum getInstance() {
        if (g == null) {
            g = new GenerateNum();
        }
        return g;
    }

    // 全局自增數
    private static int count = 0;
    // 每毫秒秒最多生成多少訂單(最好是像9999這種準備進位的值)
    private static final int total = 9999;
    // 格式化的時間字元串
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");

    // 擷取目前時間年月日時分秒毫秒字元串
    private static String getNowDateStr() {
        return sdf.format(new Date());
    }

    // 記錄上一次的時間,用來判斷是否需要遞增全局數
    private static String now = null;

    /*
     * 生成一個訂單号
     */
    public synchronized String GenerateOrder() {
        String datastr = getNowDateStr();
        if (datastr.equals(now)) {
            count++;// 自增
        } else {
            count = 1;
            now = datastr;
        }
        int countInteger = String.valueOf(total).length() - String.valueOf(count).length();// 算補位
        String bu = "";// 補字元串
        for (int i = 0; i < countInteger; i++) {
            bu += "0";
        }
        bu += String.valueOf(count);
        if (count >= total) {
            count = 0;
        }
        return datastr + bu;
    }
}
           

簽名字元串工具:用于MD5運算生成簽名,調用統一下單接口,并接受傳回的結果

import org.apache.commons.codec.digest.DigestUtils;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.SignatureException;
import java.util.*;

public class PayUtil {
    /**
     * 簽名字元串
     *
     * @param text 需要簽名的字元串
     * @param key  密鑰
     * @param input_charset 編碼格式
     * @return 簽名結果
     */
    public static String sign(String text, String key, String input_charset) {
        text = text + "&key=" + key;
        return DigestUtils.md5Hex(getContentBytes(text, input_charset));
    }

    /**
     * 簽名字元串
     *
     * @param text 需要簽名的字元串
     * @param sign          簽名結果
     * @param key 密鑰
     * @param input_charset 編碼格式
     * @return 簽名結果
     */
    public static boolean verify(String text, String sign, String key, String input_charset) {
        text = text + key;
        String mysign = DigestUtils.md5Hex(getContentBytes(text, input_charset));
        if (mysign.equals(sign)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * @param content
     * @param charset
     * @return
     * @throws SignatureException
     * @throws UnsupportedEncodingException
     */
    public static byte[] getContentBytes(String content, String charset) {
        if (charset == null || "".equals(charset)) {
            return content.getBytes();
        }
        try {
            return content.getBytes(charset);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("MD5簽名過程中出現錯誤,指定的編碼集不對,您目前指定的編碼集是:" + charset);
        }
    }

    private static boolean isValidChar(char ch) {
        if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))
            return true;
        if ((ch >= 0x4e00 && ch <= 0x7fff) || (ch >= 0x8000 && ch <= 0x952f))
            return true;// 簡體中文漢字編碼
        return false;
    }

    /**
     * 除去數組中的空值和簽名參數
     *
     * @param sArray 簽名參數組
     * @return 去掉空值與簽名參數後的新簽名參數組
     */
    public static Map<String, String> paraFilter(Map<String, String> sArray) {
        Map<String, String> result = new HashMap<String, String>();
        if (sArray == null || sArray.size() <= 0) {
            return result;
        }
        for (String key : sArray.keySet()) {
            String value = sArray.get(key);
            if (value == null || value.equals("") || key.equalsIgnoreCase("sign")
                    || key.equalsIgnoreCase("sign_type")) {
                continue;
            }
            result.put(key, value);
        }
        return result;
    }

    /**
     * 把數組所有元素排序,并按照“參數=參數值”的模式用“&”字元拼接成字元串
     *
     * @param params 需要排序并參與字元拼接的參數組
     * @return 拼接後字元串
     */
    public static String createLinkString(Map<String, Object> params) {
        List<String> keys = new ArrayList<String>(params.keySet());
        Collections.sort(keys);
        String prestr = "";
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value =(String) params.get(key);
            if (i == keys.size() - 1) {// 拼接時,不包括最後一個&字元
                prestr = prestr + key + "=" + value;
            } else {
                prestr = prestr + key + "=" + value + "&";
            }
        }
        return prestr;
    }



    public static String httpRequest(String requestUrl, String requestMethod, String outputStr) {
        // 建立SSLContext
        StringBuffer buffer = null;
        try {
            URL url = new URL(requestUrl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod(requestMethod);
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.connect();
            //往伺服器端寫内容
            if (null != outputStr) {
                OutputStream os = conn.getOutputStream();
                os.write(outputStr.getBytes("utf-8"));
                os.close();
            }
            // 讀取伺服器端傳回的内容
            InputStream is = conn.getInputStream();
            InputStreamReader isr = new InputStreamReader(is, "utf-8");
            BufferedReader br = new BufferedReader(isr);
            buffer = new StringBuffer();
            String line = null;
            while ((line = br.readLine()) != null) {
                buffer.append(line);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return buffer.toString();
    }
}
           

Map和xml互相轉換工具:

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import java.util.*;

public class MapXmlUtil {

    /**
     * Map轉換成Xml
     *
     * @param map
     * @return
     */
    public static String map2Xmlstring(Map<String, Object> map) {
        StringBuffer sb = new StringBuffer("");
        sb.append("<xml>");

        Set<String> set = map.keySet();
        for (Iterator<String> it = set.iterator(); it.hasNext(); ) {
            String key = it.next();
            Object value = map.get(key);
            sb.append("<").append(key).append(">");
            sb.append(value);
            sb.append("</").append(key).append(">");
        }
        sb.append("</xml>");
        return sb.toString();
    }


    /**
     * Xml string轉換成Map
     *
     * @param xmlStr
     * @return
     */
    public static Map<String, Object> xmlString2Map(String xmlStr) {
        Map<String, Object> map = new HashMap<String, Object>();
        Document doc;
        try {
            doc = DocumentHelper.parseText(xmlStr);
            Element el = doc.getRootElement();
            map = recGetXmlElementValue(el, map);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return map;
    }

    /**
     * 循環解析xml
     *
     * @param ele
     * @param map
     * @return
     */
    @SuppressWarnings({"unchecked"})
    private static Map<String, Object> recGetXmlElementValue(Element ele, Map<String, Object> map) {
        List<Element> eleList = ele.elements();
        if (eleList.size() == 0) {
            map.put(ele.getName(), ele.getTextTrim());
            return map;
        } else {
            for (Iterator<Element> iter = eleList.iterator(); iter.hasNext(); ) {
                Element innerEle = iter.next();
                recGetXmlElementValue(innerEle, map);
            }
            return map;
        }
    }
}
           

常量配置:

public class PayConstant {

    /**
     * 标價币種 預設人民币:CNY
     */
    public static final String FEE_TYPE_DEFAULT = "CNY";

    /**
     * 交易類型 小程式取值如下:JSAPI  APP--app支付
     */
    public static final String TRADE_TYPE_WX_APPLET = "JSAPI";

    public static final String TRADE_TYPE_APP = "APP";

    /**
     * 統一下單調用接口
     */
    public static final String PAY_ORDER_SUCCESS = "SUCCESS";

    public static final String PAY_ORDER_MSG = "OK";

    public static final String PAY_ORDER_FAIL="FAIL";

    /**
     * 支付方式  1:微信  2:支付寶
     */
    public static final Integer PAY_WAY_WX = 1;
    public static final Integer PAY_WAY_ZFB = 2;

    /**
     * 支付結果
     */
    public static final Integer PAY_RESULT_SUCCESS = 1;
    public static final Integer PAY_RESULT_FAIL = 0;//失敗

    /**
     * 下單
     */
    public static final String WX_PAY_BILL_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";

    /**
     * 微信伺服器支付結果回調通知url
     */
    public static String WX_PAY_NOTIFY_URL="https://applet.cnyang.com/wx-api/pay/wxPayBillCallback";

    /**
     * 商戶平台API密鑰
     */
    public static final String WX_PAY_PAY_KEY="GURkraWQOG5rrQFeQdXuACLwsCEGievVasa";


    /**
     * 訂單狀态 0:删除 1:已完成 2:未付款  3:待發貨 4:待收貨 5:待評價 6:已取消
     */
    public static final Integer ORDER_STATUS_DEL = 0;
    public static final Integer ORDER_STATUS_FINISH = 1;
    public static final Integer ORDER_STATUS_NO_PAY = 2;
    public static final Integer ORDER_STATUS_WAIT_DELIVERY = 3;
    public static final Integer ORDER_STATUS_ALREADY_DELIVERY = 4;
    public static final Integer ORDER_STATUS_WAIT_EVALUATION = 5;
    public static final Integer ORDER_STATUS_CANCLE = 6;

}
           

二、app支付

app開發文檔:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_1

微信開放平台申請移動應用:

微信小程式、app內建微信支付

認證通過并申請開通微信支付接口調用權限:

微信小程式、app內建微信支付

商戶在APP內建微信支付後,會跳轉到微信中完成支付,支付完後跳回到商戶APP内,最後展示支付結果。

1.app支付流程圖:

微信小程式、app內建微信支付

商戶系統和微信支付系統主要互動說明:

步驟1:使用者在商戶APP中選擇商品,送出訂單,選擇微信支付。

步驟2:商戶背景收到使用者支付單,調用微信支付統一下單接口。

步驟3:統一下單接口傳回正常的prepay_id,再按簽名規範重新生成簽名後,将資料傳輸給APP。參與簽名的字段名為appid,partnerid,prepayid,noncestr,timestamp,package。注意:package的值格式為Sign=WXPay

步驟4:商戶APP調起微信支付。

步驟5:商戶背景接收支付通知。

步驟6:商戶背景查詢支付結果

app支付調用下單服務時openid傳值null,openid用于微信小程式下單。

2.支付服務、下單服務

這部分的代碼和小程式的代碼放在一起做了封裝,詳見上文。

app端調起支付控件所需的參數和小程式不一樣。

下單成功後所需參數傳回。

APP端調起支付的參數清單

字段名 變量名 類型 必填 示例值 描述
應用ID appid String(32) wx8888888888888888 微信開放平台稽核通過的應用APPID
商戶号 partnerid String(32) 1900000109 微信支付配置設定的商戶号
預支付交易會話ID prepayid String(32) WX1217752501201407033233368018 微信傳回的支付交易會話ID
擴充字段 package String(128) Sign=WXPay 暫填寫固定值Sign=WXPay
随機字元串 noncestr String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随機字元串,不長于32位。推薦随機數生成算法
時間戳 timestamp String(10) 1412000000 時間戳,請見接口規則-參數規定
簽名 sign String(32) C380BEC2BFD727A4B6845133519F3AD6 簽名,詳見簽名生成算法注意:簽名方式一定要與統一下單接口使用的一緻

詳見app端開發步驟:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_5 。

三、微信支付回調接口

PayResultXMLEntity :

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

/**
 * 微信支付結果異步推送XML資料包實體
 */

@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class PayResultXMLEntity {

    // 傳回狀态碼
    @XmlElement(name = "return_code")
    protected String returnCode;

    // 傳回資訊
    @XmlElement(name = "return_msg")
    protected String returnMsg;

    // 業務結果
    @XmlElement(name = "result_code")
    protected String resultCode;

    // 商戶訂單号
    @XmlElement(name = "out_trade_no")
    protected String outTradeNo;

    // 支付完成時間
    @XmlElement(name = "time_end")
    protected String timeEnd;

    // 微信支付訂單号
    @XmlElement(name = "transaction_id")
    protected String transactionId;

    // 微信支付訂單号
    @XmlElement(name = "total_fee")
    protected String totalFee;

    // 微信支付訂單号
    @XmlElement(name = "mch_id")
    protected String mchId;

    public void setReturnCode(String returnCode) {
        this.returnCode = returnCode;
    }

    public String getReturnMsg() {
        return returnMsg;
    }

    public void setReturnMsg(String returnMsg) {
        this.returnMsg = returnMsg;
    }

    public String getReturnCode() {
        return returnCode;
    }

    public String getResultCode() {
        return resultCode;
    }

    public void setResultCode(String resultCode) {
        this.resultCode = resultCode;
    }

    public String getOutTradeNo() {
        return outTradeNo;
    }

    public void setOutTradeNo(String outTradeNo) {
        this.outTradeNo = outTradeNo;
    }

    public String getTimeEnd() {
        return timeEnd;
    }

    public void setTimeEnd(String timeEnd) {
        this.timeEnd = timeEnd;
    }

    public String getTransactionId() {
        return transactionId;
    }

    public void setTransactionId(String transactionId) {
        this.transactionId = transactionId;
    }

    public String getTotalFee() {
        return totalFee;
    }

    public void setTotalFee(String totalFee) {
        this.totalFee = totalFee;
    }

    public String getMchId() {
        return mchId;
    }

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

    @Override
    public String toString() {
        return "PayResultXMLEntity{" +
                "returnCode='" + returnCode + '\'' +
                ", returnMsg='" + returnMsg + '\'' +
                ", resultCode='" + resultCode + '\'' +
                ", outTradeNo='" + outTradeNo + '\'' +
                ", timeEnd='" + timeEnd + '\'' +
                ", transactionId='" + transactionId + '\'' +
                ", totalFee='" + totalFee + '\'' +
                ", mchId='" + mchId + '\'' +
                '}';
    }
}


           
/**
     * 微信支付回調
     *
     * @param resultXMLEntity 微信支付結果異步推送XML資料包實體
     * @return
     */
    @Transactional
    @Override
    public String wxPayBillCallback(PayResultXMLEntity resultXMLEntity) {
        Map<String, Object> result = new HashMap<>();
        LOG.info("resultXMLEntity:" + resultXMLEntity.toString());
        if (resultXMLEntity == null) {
            LOG.error("微信支付結果異步推送XML資料包實體為空");
            result.put("return_code", PayConstant.PAY_ORDER_FAIL);
            result.put("return_msg", "微信支付結果異步推送XML資料包實體為空");
            return MapXmlUtil.map2Xmlstring(result);
        }
        if (!StringUtils.isEmpty(resultXMLEntity.getOutTradeNo())) {
            ServiceResult<PayDetailAO> payDetailRet = getPayDetailByOrderNumber(resultXMLEntity.getOutTradeNo());
            if (payDetailRet != null && payDetailRet.isSucceed() && payDetailRet.getData() != null) {
                //已經支付成功,再次通知則不做處理
                if (payDetailRet.getData().getPayStatus() != null && payDetailRet.getData().getPayStatus().equals(PayConstant.PAY_RESULT_SUCCESS)) {
                    result.put("return_code", PayConstant.PAY_ORDER_SUCCESS);
                    result.put("return_msg", PayConstant.PAY_ORDER_MSG);
                    return MapXmlUtil.map2Xmlstring(result);
                }
            } else {
                result.put("return_code", PayConstant.PAY_ORDER_FAIL);
                result.put("return_msg", "訂單号不存在");
                return MapXmlUtil.map2Xmlstring(result);
            }
        }
        if (!StringUtils.isEmpty(resultXMLEntity.getReturnCode())) {
            //生成訂單号
            PayDetailAO payDetail = new PayDetailAO();
            if (resultXMLEntity.getReturnCode().equals(PayConstant.PAY_ORDER_SUCCESS)) {
                //更新訂單狀态
                orderService.updateOrder(resultXMLEntity.getOutTradeNo(), PayConstant.ORDER_STATUS_WAIT_DELIVERY, resultXMLEntity.getTimeEnd());
                //更新支付狀态
                payDetail.setPayStatus(PayConstant.PAY_RESULT_SUCCESS);
            } else if (resultXMLEntity.getReturnCode().equals(PayConstant.PAY_ORDER_FAIL)) {
                payDetail.setPayStatus(PayConstant.PAY_RESULT_FAIL);
            }
            payDetail.setSerialNumber(resultXMLEntity.getTransactionId());//支付流水号
            payDetail.setUpdateTime(!StringUtils.isEmpty(resultXMLEntity.getTimeEnd()) ?
                    DateUtil.parseStrToDate(resultXMLEntity.getTimeEnd(), DateUtil.DATE_FORMAT_6) : null);
            //更新支付資訊
            PayDetailCriteria criteria = new PayDetailCriteria();
            criteria.createCriteria().andOrderNumberEqualTo(resultXMLEntity.getOutTradeNo());
            ServiceResult<Integer> payOrderRet = updateByCriteriaSelective(payDetail, criteria);
            if (payOrderRet != null && payOrderRet.isSucceed() && payOrderRet.getData() > 0) {
                LOG.info("微信支付成功");
                result.put("return_code", PayConstant.PAY_ORDER_SUCCESS);
                result.put("return_msg", PayConstant.PAY_ORDER_MSG);
                return MapXmlUtil.map2Xmlstring(result);
            }
        }
        result.put("return_code", PayConstant.PAY_ORDER_FAIL);
        result.put("return_msg", "微信支付結果通知處理失敗");
        return MapXmlUtil.map2Xmlstring(result);
    }
           

收到微信支付結果通知後,按照示例傳回參數給微信支付:

字段名 變量名 必填 類型 示例值 描述
傳回狀态碼 return_code String(16) SUCCESS 請按示例值填寫
傳回資訊 return_msg String(128) OK 請按示例值填寫

舉例如下:

<xml>

  <return_code><![CDATA[SUCCESS]]></return_code>

  <return_msg><![CDATA[OK]]></return_msg>

</xml>

四.測試

微信小程式支付:

微信小程式、app內建微信支付

   根據傳回的參數即可在微信小程式端調起控件發起支付:

微信小程式、app內建微信支付

app支付:

微信小程式、app內建微信支付

  根據傳回的參數即可在app端調起控件發起支付:

微信小程式、app內建微信支付

注意:微信小程式和app簽名參數的大小寫不同。

繼續閱讀