微信APP支付實現記錄

這個就直接貼代碼了,反正暫時沒法驗證對錯
錯誤工具類

import plugins.pay.wechat.domain.WechatPayError;

import java.util.HashMap;
import java.util.Map;

/**
 * 錯誤工具類,這裏用枚舉類會更好,不過我真的懶得去看文檔改了,這個類也不是很重要,不搞了
 * @author zhang.shushan
 */
public class WeChatPayErrorUtil {

	private static Map<String, WechatPayError> errs = new HashMap<>();

	static {
		errs.put("NOAUTH", WechatPayError.builder().err_code_des("商戶無此接口權限").err_cause("商戶未開通此接口權限")
				.err_resolve("請商戶前往申請此接口權限").build());
		errs.put("NOTENOUGH", WechatPayError.builder().err_code_des("餘額不足").err_cause("用戶帳號餘額不足")
				.err_resolve("用戶帳號餘額不足,請用戶充值或更換支付卡後再支付").build());
		errs.put("ORDERPAID", WechatPayError.builder().err_code_des("商戶訂單已支付").err_cause("商戶訂單已支付,無需重複操作")
				.err_resolve("商戶訂單已支付,無需更多操作").build());
		errs.put("ORDERCLOSED", WechatPayError.builder().err_code_des("訂單已關閉").err_cause("當前訂單已關閉,無法支付")
				.err_resolve("當前訂單已關閉,請重新下單").build());
		errs.put("SYSTEMERROR",
				WechatPayError.builder().err_code_des("系統錯誤").err_cause("系統超時").err_resolve("系統異常,請用相同參數重新調用").build());
		errs.put("APPID_NOT_EXIST", WechatPayError.builder().err_code_des("APPID不存在").err_cause("參數中缺少APPID")
				.err_resolve("請檢查APPID是否正確").build());
		errs.put("MCHID_NOT_EXIST", WechatPayError.builder().err_code_des("MCHID不存在").err_cause("參數中缺少MCHID")
				.err_resolve("請檢查MCHID是否正確").build());
		errs.put("APPID_MCHID_NOT_MATCH", WechatPayError.builder().err_code_des("appid和mch_id不匹配")
				.err_cause("appid和mch_id不匹配").err_resolve("請確認appid和mch_id是否匹配").build());
		errs.put("LACK_PARAMS",
				WechatPayError.builder().err_code_des("缺少參數").err_cause("缺少必要的請求參數").err_resolve("請檢查參數是否齊全").build());
		errs.put("OUT_TRADE_NO_USED", WechatPayError.builder().err_code_des("商戶訂單號重複 ").err_cause("同一筆交易不能多次提交")
				.err_resolve("請覈實商戶訂單號是否重複提交").build());
		errs.put("SIGNERROR", WechatPayError.builder().err_code_des("簽名錯誤").err_cause("參數簽名結果不正確")
				.err_resolve("請覈實商戶訂單號是否重複提交").build());
		errs.put("XML_FORMAT_ERROR", WechatPayError.builder().err_code_des("XML格式錯誤").err_cause("XML格式錯誤")
				.err_resolve("請檢查XML參數格式是否正確").build());
		errs.put("REQUIRE_POST_METHOD", WechatPayError.builder().err_code_des("請使用post方法").err_cause("未使用post傳遞參數")
				.err_resolve("請檢查請求參數是否通過post方法提交").build());
		errs.put("POST_DATA_EMPTY", WechatPayError.builder().err_code_des("post數據爲空").err_cause("post數據不能爲空")
				.err_resolve("請檢查post數據是否爲空").build());
		errs.put("NOT_UTF8", WechatPayError.builder().err_code_des("編碼格式錯誤").err_cause("未使用指定編碼格式")
				.err_resolve("請使用NOT_UTF8編碼格式").build());

	}
	
	public static String getErrorMsg(String result_code) {
		// TODO Auto-generated method stub
		return errs.get(result_code).getErr_cause()+":"+errs.get(result_code).getErr_resolve();
	}
}

錯誤封裝對象

public class WechatPayError {
	
	private String err_code_des;
	private String err_cause;
	private String err_resolve;
	
	public WechatPayError(String err_code_des,String err_cause,String err_resolve){
		this.err_code_des=err_code_des;
		this.err_cause=err_cause;
		this.err_resolve=err_resolve;
	}
	
	public static WechatPayErrorBuilder builder(){
		return new WechatPayErrorBuilder();
	}
	
	public static class WechatPayErrorBuilder{
		private String err_code_des;
		private String err_cause;
		private String err_resolve;
		
		public WechatPayErrorBuilder err_code_des(String err_code_des){
			this.err_code_des=err_code_des;
			return this;
		}
		
		public WechatPayErrorBuilder err_cause(String err_cause){
			this.err_cause=err_cause;
			return this;
		}
		
		public WechatPayErrorBuilder err_resolve(String err_resolve){
			this.err_resolve = err_resolve;
			return this;
		}

		@Override
		public String toString(){
			return "WechatPayErrorBuilder[err_code_des="+this.err_code_des+",err_cause="+this.err_cause+",err_resolve="+err_resolve+"]";
		}
		
		public WechatPayError build(){
			return new WechatPayError(this.err_code_des, this.err_cause, this.err_resolve);
		}
	}
	
	public String getErr_code_des() {
		return err_code_des;
	}
	public void setErr_code_des(String err_code_des) {
		this.err_code_des = err_code_des;
	}
	public String getErr_cause() {
		return err_cause;
	}
	public void setErr_cause(String err_cause) {
		this.err_cause = err_cause;
	}
	public String getErr_resolve() {
		return err_resolve;
	}
	public void setErr_resolve(String err_resolve) {
		this.err_resolve = err_resolve;
	}

}

XML工具類

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

/**
 * 2018/7/3
 */
public final class WXPayXmlUtil {
    public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        documentBuilderFactory.setXIncludeAware(false);
        documentBuilderFactory.setExpandEntityReferences(false);

        return documentBuilderFactory.newDocumentBuilder();
    }

    public static Document newDocument() throws ParserConfigurationException {
        return newDocumentBuilder().newDocument();
    }
}

微信訂單返回信息封裝

/**
 * 微信預支付訂單返回信息
 * @author zhang.shushan
 * @date 2018年1月19日
 */
public class WechatPreOrderInfo {

	//返回狀態碼
	private String return_code;
	//返回信息
	private String return_msg;
	//應用APPID
	private String appid;
	//商戶號
	private String mch_id;
	//設備號
	private String device_info;
	//隨機字符串
	private String nonce_str;
	//簽名
	private String sign;
	//業務結果
	private String result_code;
	//錯誤代碼
	private String err_code;
	//錯誤代碼描述
	private String err_code_des;
	//交易類型
	private String trade_type;
	//預支付交易會話標識
	private String prepay_id;
	//掃碼支付返回字段,用於生成二維碼
	private String code_url;
	
	/**
	 * 連接是否成功
	 * @return
	 */
	public boolean isContact(){
		return "SUCCESS".equals(this.return_code);
	}
	
	/**
	 * 業務是否成功
	 * @return
	 */
	public boolean isSuccess(){
		if(isContact()){
			return "SUCCESS".equals(this.result_code);
		}
		return false;
	}

	/**
	 * 固定字段
	 * @return
	 */
	public String getPackage(){
		return "Sign=WXPay";
	}

	/**
	 * 時間戳
	 * @return
	 */
	public Long getTimestamp(){
		return System.currentTimeMillis()/1000;
	}

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

	public String getAppid() {
		return appid;
	}

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

	public String getMch_id() {
		return mch_id;
	}

	public void setMch_id(String mch_id) {
		this.mch_id = mch_id;
	}

	public String getDevice_info() {
		return device_info;
	}

	public void setDevice_info(String device_info) {
		this.device_info = device_info;
	}

	public String getNonce_str() {
		return nonce_str;
	}

	public void setNonce_str(String nonce_str) {
		this.nonce_str = nonce_str;
	}

	public String getSign() {
		return sign;
	}

	public void setSign(String sign) {
		this.sign = sign;
	}

	public String getResult_code() {
		return result_code;
	}

	public void setResult_code(String result_code) {
		this.result_code = result_code;
	}

	public String getErr_code() {
		return err_code;
	}

	public void setErr_code(String err_code) {
		this.err_code = err_code;
	}

	public String getErr_code_des() {
		return err_code_des;
	}

	public void setErr_code_des(String err_code_des) {
		this.err_code_des = err_code_des;
	}

	public String getTrade_type() {
		return trade_type;
	}

	public void setTrade_type(String trade_type) {
		this.trade_type = trade_type;
	}

	public String getPrepay_id() {
		return prepay_id;
	}

	public void setPrepay_id(String prepay_id) {
		this.prepay_id = prepay_id;
	}

	public String getCode_url() {
		return code_url;
	}

	public void setCode_url(String code_url) {
		this.code_url = code_url;
	}

}

微信支付返回信息封裝

/**
 * 微信支付返回信息類
 * @author zhang.shushan
 * @date 2018年1月24日
 */
public class WechatPayRet {

	//返回狀態碼
	private String return_code;
	//返回信息
	private String return_msg;
	//應用ID
	private String appid;
	//商戶號
	private String mch_id;
	//設備號
	private String device_info;
	//隨機字符串
	private String nonce_str;
	//簽名
	private String sign;
	//業務結果
	private String result_code;
	//錯誤代碼
	private String err_code;
	//錯誤代碼描述
	private String err_des;
	//用戶標識
	private String openid;
	//是否關注公衆賬號
	private String is_subscribe;
	//交易類型
	private String trade_type;
	//付款銀行
	private String bank_type;
	//總金額
	private int total_fee;
	//貨幣種類
	private String fee_type;
	//現金支付金額
	private int cash_fee;
	//現金支付貨幣類型
	private String cash_fee_type;
	//代金券金額
	private int coupon_fee;
	//代金券使用數量
	private int coupon_count;
	//微信支付訂單號
	private String transaction_id;
	//商戶訂單號
	private String out_trade_no;
	//商家數據包
	private String attach;
	//支付完成時間
	@JSONField(format="yyyy-MM-dd HH:mm:ss")
	private Date time_end;
	
	/**
	 * 連接是否成功
	 * @return
	 */
	public boolean isContact(){
		return "SUCCESS".equals(this.return_code);
	}
	
	/**
	 * 業務是否成功
	 * @return
	 */
	public boolean isSuccess(){
		if(isContact()){
			return "SUCCESS".equals(this.result_code);
		}
		return false;
	}

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

	public String getAppid() {
		return appid;
	}

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

	public String getMch_id() {
		return mch_id;
	}

	public void setMch_id(String mch_id) {
		this.mch_id = mch_id;
	}

	public String getDevice_info() {
		return device_info;
	}

	public void setDevice_info(String device_info) {
		this.device_info = device_info;
	}

	public String getNonce_str() {
		return nonce_str;
	}

	public void setNonce_str(String nonce_str) {
		this.nonce_str = nonce_str;
	}

	public String getSign() {
		return sign;
	}

	public void setSign(String sign) {
		this.sign = sign;
	}

	public String getResult_code() {
		return result_code;
	}

	public void setResult_code(String result_code) {
		this.result_code = result_code;
	}

	public String getErr_code() {
		return err_code;
	}

	public void setErr_code(String err_code) {
		this.err_code = err_code;
	}

	public String getErr_des() {
		return err_des;
	}

	public void setErr_des(String err_des) {
		this.err_des = err_des;
	}

	public String getOpenid() {
		return openid;
	}

	public void setOpenid(String openid) {
		this.openid = openid;
	}

	public String getIs_subscribe() {
		return is_subscribe;
	}

	public void setIs_subscribe(String is_subscribe) {
		this.is_subscribe = is_subscribe;
	}

	public String getTrade_type() {
		return trade_type;
	}

	public void setTrade_type(String trade_type) {
		this.trade_type = trade_type;
	}

	public String getBank_type() {
		return bank_type;
	}

	public void setBank_type(String bank_type) {
		this.bank_type = bank_type;
	}

	public int getTotal_fee() {
		return total_fee;
	}

	public void setTotal_fee(int total_fee) {
		this.total_fee = total_fee;
	}

	public String getFee_type() {
		return fee_type;
	}

	public void setFee_type(String fee_type) {
		this.fee_type = fee_type;
	}

	public int getCash_fee() {
		return cash_fee;
	}

	public void setCash_fee(int cash_fee) {
		this.cash_fee = cash_fee;
	}

	public String getCash_fee_type() {
		return cash_fee_type;
	}

	public void setCash_fee_type(String cash_fee_type) {
		this.cash_fee_type = cash_fee_type;
	}

	public int getCoupon_fee() {
		return coupon_fee;
	}

	public void setCoupon_fee(int coupon_fee) {
		this.coupon_fee = coupon_fee;
	}

	public int getCoupon_count() {
		return coupon_count;
	}

	public void setCoupon_count(int coupon_count) {
		this.coupon_count = coupon_count;
	}

	public String getTransaction_id() {
		return transaction_id;
	}

	public void setTransaction_id(String transaction_id) {
		this.transaction_id = transaction_id;
	}

	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 getAttach() {
		return attach;
	}

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

	public Date getTime_end() {
		return time_end;
	}

	public void setTime_end(Date time_end) {
		this.time_end = time_end;
	}
	
}

下單信息封裝

import com.alibaba.fastjson.JSON;

import java.io.IOException;

/**
 * 微信支付信息實體類
 * @date 2018年1月19日
 */
public class WechatPayInfo {

    // 應用ID
    private String appid;
    // 商戶號
    private String mch_id;
    // 終端設備號(門店號或收銀設備ID),默認請傳"WEB"
    private String device_info = "WEB";
    // 隨機字符串
    private String nonce_str;
    // 簽名,信息填充完整後使用工具類設置簽名
    private String sign;
    // 簽名類型,目前支持HMAC-SHA256和MD5,默認爲MD5
    private String sign_type = "MD5";
    /**
     * 商品描述交易字段格式根據不同的應用場景按照以下格式:
     * APP——需傳入應用市場上的APP名字-實際商品名稱,
     * 天天愛消除-遊戲充值。
     */
    private String body;
    // 附加數據,在查詢API和支付通知中原樣返回,該字段主要用於商戶攜帶訂單的自定義數據
    private String attach;
    // 商戶系統內部訂單號,要求32個字符內,只能是數字、大小寫字母_-|*@ ,且在同一個商戶號下唯一
    private String out_trade_no;
    // 符合ISO 4217標準的三位字母代碼,默認人民幣:CNY,其他值列表詳見
    private String fee_type = "CNY";
    // 訂單總金額,單位爲分
    private int total_fee;
    //訂單詳情,用於單個商品的優惠,設置成對象的json
    private String detail;
    // 用戶端實際ip
    private String spbill_create_ip;
    // 接收微信支付異步通知回調地址,通知url必須爲直接可訪問的url,不能攜帶參數。
    private String notify_url;
    // 交易類型
    private String trade_type;
    // 該字段用於統一下單時上報場景信息,目前支持上報實際門店信息,設置成對象的json
    private String scene_info;
    //微信公衆號支付必填
    private String openid;
    //限制使用信用卡支付
    private String limit_pay;

    public String getOpenid() {
        return openid;
    }

    public void setOpenid(String openid) {
        this.openid = openid;
    }

    public String getLimit_pay() {
        return limit_pay;
    }

    public void setLimit_pay(String limit_pay) {
        this.limit_pay = limit_pay;
    }

    /**
     * 設置限制使用信用卡
     */
    public void configureLimitPay(){
        this.limit_pay="no_credit";
    }

    /**
     * 設置必填的自定義參數
     *
     * @param body
     * @param out_trade_no
     * @param total_fee
     * @param suffix
     * @param trade_type
     * @throws IOException
     */
    public WechatPayInfo(String body, String out_trade_no, String suffix,
                         int total_fee,
                         String trade_type, String spbill_create_ip) throws IOException {
        this.body = "開放平臺APP name" + "-" + body;
        this.out_trade_no = out_trade_no;
        this.notify_url = "商戶後臺回調接口地址";
        this.trade_type = trade_type;
        this.spbill_create_ip = spbill_create_ip;
        this.total_fee = total_fee;
    }

    /**
     * 設置必填的自定義參數
     *
     * @param body
     * @param out_trade_no
     * @param total_fee
     * @param notify_url
     * @param trade_type
     * @throws IOException
     */
    public WechatPayInfo(String body, String out_trade_no,
                         int total_fee, String notify_url,
                         String trade_type, String spbill_create_ip) throws IOException {
        this.body = 開放平臺APP name + "-" + body;
        this.out_trade_no = out_trade_no;
        this.notify_url = notify_url;
        this.trade_type = trade_type;
        this.spbill_create_ip = spbill_create_ip;
        this.total_fee = total_fee;
    }

    /**
     * 設置單品優惠信息
     *
     * @param orderInfos
     */
    public void configDetail(OrderInfos orderInfos) {
        this.detail = JSON.toJSONString(orderInfos);
    }

    /**
     * 設置實際門店信息
     *
     * @param sceneInfo
     */
    public void configScene_info(SceneInfo sceneInfo) {
        this.scene_info = JSON.toJSONString(sceneInfo);
    }

    /**
     * 設置必填的自定義參數
     *
     * @param body
     * @param out_trade_no
     * @param total_fee
     * @param notify_url
     * @param trade_type
     * @throws IOException
     */
    public void configMajorParameters(String body, String out_trade_no,
                                      int total_fee, String notify_url,
                                      String trade_type, String spbill_create_ip) throws IOException {
        this.body = "開放平臺APP name" + "-" + body;
        this.out_trade_no = out_trade_no;
        this.notify_url = notify_url;
        this.trade_type = trade_type;
        this.spbill_create_ip = spbill_create_ip;
        this.total_fee = total_fee;
    }

    public String getAppid() {
        return appid;
    }

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

    public String getMch_id() {
        return mch_id;
    }

    public void setMch_id(String mch_id) {
        this.mch_id = mch_id;
    }

    public String getDevice_info() {
        return device_info;
    }

    public void setDevice_info(String device_info) {
        this.device_info = device_info;
    }

    public String getNonce_str() {
        return nonce_str;
    }

    public void setNonce_str(String nonce_str) {
        this.nonce_str = nonce_str;
    }

    public String getSign() {
        return sign;
    }

    public void setSign(String sign) {
        this.sign = sign;
    }

    public String getSign_type() {
        return sign_type;
    }

    public void setSign_type(String sign_type) {
        this.sign_type = sign_type;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }

    public String getAttach() {
        return attach;
    }

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

    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 getFee_type() {
        return fee_type;
    }

    public void setFee_type(String fee_type) {
        this.fee_type = fee_type;
    }

    public String getDetail() {
        return detail;
    }

    public void setDetail(String detail) {
        this.detail = detail;
    }

    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 String getNotify_url() {
        return notify_url;
    }

    public void setNotify_url(String notify_url) {
        this.notify_url = notify_url;
    }

    public String getTrade_type() {
        return trade_type;
    }

    public void setTrade_type(String trade_type) {
        this.trade_type = trade_type;
    }

    public String getScene_info() {
        return scene_info;
    }

    public void setScene_info(String scene_info) {
        this.scene_info = scene_info;
    }

    public void setTotal_fee(int total_fee) {
        this.total_fee = total_fee;
    }

}

下單和回調接口

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alipay.api.internal.util.StringUtils;
import plugins.pay.wechat.domain.WechatPayInfo;
import plugins.pay.wechat.domain.WechatPayRet;
import plugins.pay.wechat.domain.WechatPreOrderInfo;
import plugins.pay.wechat.sdk.WXPayXmlUtil;
import plugins.pay.wechat.utils.WeChatPayErrorUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URI;
import java.security.MessageDigest;
import java.util.*;

@RestController
@Api(tags="支付接口")
@RequestMapping("/Indent")
public class H5IndentController {

    @GetMapping("/addWechat")
    @ApiOperation(value = "微信新增訂單", notes = "微信新增訂單")
    public Map<String,Object> addWechat(HttpServletRequest request, HttpServletResponse response){

        // 返回結果
        Map<String,Object> map = null;

        try {
            //創建微信預支付返回對象,主要是爲了返回給APP時數據結構不變
            WechatPreOrderInfo wechatPreOrderInfo = new WechatPreOrderInfo();

            //創建預支付請求信息,18是價格
            WechatPayInfo payInfo = new WechatPayInfo("會員充值", "商戶訂單id",
                    18, "商戶回調接口地址",
                    "APP", request.getRemoteAddr());
            // 向微信發起請求,這裏需要調起各種配置信息
            wechatPreOrderInfo = preOrder(payInfo);
            // 加簽
            map = sign4App(wechatPreOrderInfo);

            // TODO 保存數據

            // 給返回結果添加訂單id
            map.put("indentId", "商戶訂單id");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }

    // 微信APP收款回調
    @PostMapping("/wechatNotify")
    public void wechatNotify(HttpServletRequest request, HttpServletResponse response)throws Exception{

        //解析數據
        WechatPayRet ret = parseRequest(request);

        String return_code = ret.getReturn_code();

        if(return_code.equals("SUCCESS")){
            // 開始進行訂單處理,ret.getOut_trade_no()就是商戶訂單號,delIndent()是業務處理邏輯自己完善
            String result = delIndent(ret.getOut_trade_no());

            System.out.println("----------------------------------------------------------------------交易完成!");

            if(result.equals("success")){
                //成功之後要應答,讓微信別調了。但是還是會有重入的可能,所以必須做好數據鎖
                echo(response);
            }else{
                return;
            }
        }else{
            return;
        }
    }

    // APP支付二次加簽
    public static Map<String,Object> sign4App(WechatPreOrderInfo preOrderInfo)throws Exception{
        TreeMap<String, Object> map = new TreeMap<>();
        map.put("appid", preOrderInfo.getAppid());
        map.put("partnerid", preOrderInfo.getMch_id());
        map.put("prepayid", preOrderInfo.getPrepay_id());
        map.put("package", preOrderInfo.getPackage());
        map.put("noncestr", generator(32, "QWERTYUIPASDFGHJKLZXCVBNMqwertyuipasdfghjklzxcvbnm01234567890".toCharArray()));
        map.put("timestamp", preOrderInfo.getTimestamp());
        map.put("sign", signMap(map));
        return map;
    }

    // 加簽
    private static String signMap(TreeMap<String,Object> map) throws Exception{
        Set<String> keys = map.keySet();
        StringBuilder sb = new StringBuilder();
        for (String key : keys) {
            sb.append(key).append("=").append(map.get(key)).append("&");
        }
        sb.append("key").append("=").append("商戶號密鑰");
        return DigestUtils.md5Hex(sb.toString()).toUpperCase();
    }

    // 生成數據和字母組成的隨機字符串
    private static String generator(int count, char[] arr) {
        if (count <= 0) {
            count = 6;
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < count; i++) {
            double d = Math.random();
            int index = (int) Math.floor(d * arr.length);
            sb.append(arr[index]);
        }

        return sb.toString();
    }

    public static WechatPreOrderInfo preOrder(WechatPayInfo payInfo) throws Exception {

        if(payInfo.getTrade_type().equals("JSAPI")&& StringUtils.isEmpty(payInfo.getOpenid())){
            throw new RuntimeException("公衆號支付openid不能爲空,請填入正確的openid");
        }

        payInfo.setAppid("開放平臺APPID");
        payInfo.setMch_id("商戶平臺商戶號");

        payInfo.setNonce_str(generator(32, "QWERTYUIPASDFGHJKLZXCVBNMqwertyuipasdfghjklzxcvbnm01234567890".toCharArray()).toUpperCase());
        payInfo.setSign(generateSign(payInfo));
        Document doc = convertMap2Xml(getSignMap(payInfo, WechatPayInfo.class));
        URI uri = new URIBuilder().setScheme("https").setHost("api.mch.weixin.qq.com").setPath("/pay/unifiedorder")
                .build();
        String xml = connectWithXMLByPost(uri, doc);
        WechatPreOrderInfo info = (WechatPreOrderInfo) convertXml2Bean(xml, WechatPreOrderInfo.class);
        if (!info.isContact()) {
            String msg = info.getReturn_code() + ":" + info.getReturn_msg();
            msg = new String(msg.getBytes("iso-8859-1"), "utf-8");
            throw new RuntimeException(msg);
        }
        if (!info.isSuccess()) {
            String msg = info.getResult_code() + ":" + WeChatPayErrorUtil.getErrorMsg(info.getErr_code());
            throw new RuntimeException(msg);
        }
        return info;
    }

    // xml字符串轉對象
    public static Object convertXml2Bean(String xmlString, Class<?> clazz) {
        Document document = null;
        try {
            document = DocumentHelper.parseText(xmlString);
        } catch (DocumentException e) {
            throw new RuntimeException("獲取Document異常" + xmlString);
        } // 獲取根節點
        return convertXml2Bean2(document, clazz);
    }

    // xml文檔Document轉對象
    @SuppressWarnings("unchecked")
    public static Object convertXml2Bean2(Document document, Class<?> clazz) {
        Map<String,String> map = new HashMap<>();
        // 獲取根節點
        org.dom4j.Element root = document.getRootElement();
        try {
            List<org.dom4j.Element> properties = root.elements();
            for (org.dom4j.Element pro : properties) {
                String propName = pro.getName();
                String propValue = pro.getText();
                map.put(propName, propValue);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        //處理map裏的JSON字符串字段,防止解析錯誤
        Map<String,Object> objMap = new TreeMap<>();
        Set<String> keys = map.keySet();
        for (String key : keys) {
            String str = map.get(key);

            try {
                //如果是JSON字符串,則轉換成對象,再添加到objMap中
                objMap.put(key, JSON.parse(str));
            } catch (JSONException e) {
                //如果不是JSON字符串,則直接添加到objMap中
                objMap.put(key, str);
            } catch (Exception e){
                //其餘錯誤拋出
                e.printStackTrace();
            }

        }

        return JSON.parseObject(JSON.toJSONString(map),clazz);
    }

    // 使用post方法發送xml
    public static String connectWithXMLByPost(URI uri, Document doc) throws Exception {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost post = new HttpPost(uri);
            post.addHeader("Content-Type", "text/xml;charset=UTF-8");
            StringEntity xml = new StringEntity(doc.asXML(), "UTF-8");
            post.setEntity(xml);
            try (CloseableHttpResponse response = httpClient.execute(post)) {
                HttpEntity entity = response.getEntity();
                return EntityUtils.toString(entity);
            } catch (Exception e) {
                e.printStackTrace();
                throw e;
            }

        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }

    // 將獲取的Map轉換成xml
    public static Document convertMap2Xml(Map<String,Object> map){
        Document doc = DocumentHelper.createDocument();
        try {
            org.dom4j.Element root = doc.addElement("xml");
            Set<String> keys = map.keySet();
            for (String key : keys) {
                org.dom4j.Element ele = root.addElement(key);
                ele.addCDATA(map.get(key).toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return doc;
    }

    // 設置簽名
    private static String generateSign(Object obj) throws Exception {
        TreeMap<String, Object> map = getSignMap(obj, obj.getClass());
        return signMap(map);
    }

    // 獲取按順序整理好的非空值字段
    @SuppressWarnings("unchecked")
    private static TreeMap<String, Object> getSignMap(Object obj, Class<?> clz) throws Exception {
        if (obj == null) {
            throw new RuntimeException("支付對象不能爲空");
        }
        Field[] fields = clz.getDeclaredFields();

        // 使用treeMap將字段按要求排序
        TreeMap<String, Object> map = new TreeMap<>();
        for (Field field : fields) {
            map.put(field.getName(), clz.getMethod("get" + uperFirst(field.getName())).invoke(obj));
        }

        // 使用fastjson過濾掉null的字段,愛死fastjson了
        String json = JSON.toJSONString(map);
        map = JSON.parseObject(json, TreeMap.class);
        return map;
    }

    // 首字母大寫
    private static String uperFirst(String name) {
        return name.substring(0, 1).toUpperCase() + name.substring(1);
    }

    // 應答微信回調
    public static void echo(HttpServletResponse response) throws Exception {
        response.setContentType("application/xml");
        ServletOutputStream os = response.getOutputStream();
        os.print("<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>");
    }

    // 解析微信後臺返回的數據
    public static WechatPayRet parseRequest(HttpServletRequest request) throws Exception {
        String xml = readXmlFromRequest(request);
        Map map = xmlToMap(xml);
        WechatPayRet ret = new WechatPayRet();
        ret.setReturn_code((String) map.get("result_code"));
        ret.setOut_trade_no((String) map.get("out_trade_no"));
        return ret;
    }

    // XML格式字符串轉換爲Map
    public static Map<String, String> xmlToMap(String strXML) throws Exception {
        try {
            Map<String, String> data = new HashMap<String, String>();
            DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            org.w3c.dom.Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();
            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
                    data.put(element.getNodeName(), element.getTextContent());
                }
            }
            try {
                stream.close();
            } catch (Exception ex) {
                // do nothing
            }
            return data;
        } catch (Exception ex) {
            getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
            throw ex;
        }

    }

    // 日誌
    public static Logger getLogger() {
        Logger logger = LoggerFactory.getLogger("wxpay java sdk");
        return logger;
    }

    // 從request讀取xml
    public static String readXmlFromRequest(HttpServletRequest request) {
        StringBuilder xmlSb = new StringBuilder();
        try(
                ServletInputStream in = request.getInputStream();
                InputStreamReader inputStream = new InputStreamReader(in);
                BufferedReader buffer = new BufferedReader(inputStream);
        ){
            String line = null;
            while((line=buffer.readLine())!=null){
                xmlSb.append(line);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return xmlSb.toString();
    }

    // MD5加密
    public static String md5(String key) {
        System.out.println(key);
        char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        try {
            byte[] btInput = key.getBytes("UTF-8");
            // 獲得MD5摘要算法的 MessageDigest 對象
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            // 使用指定的字節更新摘要
            mdInst.update(btInput);
            // 獲得密文
            byte[] md = mdInst.digest();
            // 把密文轉換成十六進制的字符串形式
            int j = md.length;
            char str[] = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            String s = new String(str);
            s = s.toLowerCase();
            return s;
        } catch (Exception e) {
            return null;
        }
    }
}

好了,我暫時沒法驗證是否有錯。請諒解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章