java實現微信企業付款到個人賬戶
阿新 • • 發佈:2019-02-08
-
微信官方提供了微信企業賬戶付款到微信個人零錢介面,提供企業向用戶付款的功能,支援企業通過API介面付款,或通過微信支付商戶平臺網頁功能操作付款。該介面並不是直接所有的商戶都擁有,企業要開啟必須滿足以下兩個條件:
1、商戶號已入駐90日
2、商戶號有30天連續正常交易
滿足以上條件就可登入微信支付商戶平臺-產品中心,開通企業付款。
呼叫的連結地址:介面連結:https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers
微信官方介面文件提供的呼叫微信企業付款的引數:
引數中最重要是獲取使用者openid和呼叫介面ip,獲取openid可以通過公眾號獲取,app端可以直接獲取。具體的程式碼實現如下:
封裝請求微信企業付款的實體類Transfers:public class Transfers implements Serializable{ private static final long serialVersionUID = 1L; /** 商戶賬號appid*/ public String mch_appid; /** 微信支付商戶號*/ public String mchid; /** 隨機串*/ public String nonce_str; /** 簽名*/ public String sign; /** 商戶訂單號*/ public String partner_trade_no; /** 使用者id*/ public String openid; /** 是否校驗使用者姓名 NO_CHECK:不校驗真實姓名 FORCE_CHECK:強校驗真實姓名*/ public String check_name; /** 金額 單位:分*/ public Integer amount; /** 企業付款描述資訊*/ public String desc; /** ip地址*/ public String spbill_create_ip; public String getMch_appid() { return mch_appid; } public void setMch_appid(String mch_appid) { this.mch_appid = mch_appid; } public String getMchid() { return mchid; } public void setMchid(String mchid) { this.mchid = mchid; } 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 getPartner_trade_no() { return partner_trade_no; } public void setPartner_trade_no(String partner_trade_no) { this.partner_trade_no = partner_trade_no; } public String getOpenid() { return openid; } public void setOpenid(String openid) { this.openid = openid; } public String getCheck_name() { return check_name; } public void setCheck_name(String check_name) { this.check_name = check_name; } public Integer getAmount() { return amount; } public void setAmount(Integer amount) { this.amount = amount; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } 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; } }
介面部分程式碼:
private Transfers transfers = new Transfers(); // 構造簽名的map private SortedMap<Object, Object> parameters = new TreeMap<Object, Object>(); // 微信的引數 private WeixinConfigUtils config = new WeixinConfigUtils(); /** * 微信提現(企業付款) */ @Action("weixinWithdraw") public String weixinWithdraw(){ String openId = request.getParameter("openid"); String ip = request.getParameter("ip"); String money = request.getParameter("money"); String doctorId = request.getParameter("doctorId"); if (StringUtils.isNotBlank(money) && StringUtils.isNotBlank(ip) && StringUtils.isNotBlank(openId) && StringUtils.isNotBlank(doctorId)) { // 引數組 String appid = config.appid; String mch_id = config.mch_id; String nonce_str = RandCharsUtils.getRandomString(16); //是否校驗使用者姓名 NO_CHECK:不校驗真實姓名 FORCE_CHECK:強校驗真實姓名 String checkName ="NO_CHECK"; //等待確認轉賬金額,ip,openid的來源 Integer amount = Integer.valueOf(money); String spbill_create_ip = ip; String partner_trade_no = UuIdUtils.getUUID(); //描述 String desc = "健康由我醫師助手提現"+amount/100+"元"; // 引數:開始生成第一次簽名 parameters.put("appid", appid); parameters.put("mch_id", mch_id); parameters.put("partner_trade_no", partner_trade_no); parameters.put("nonce_str", nonce_str); parameters.put("openId", openId); parameters.put("checkName", checkName); parameters.put("amount", amount); parameters.put("spbill_create_ip", spbill_create_ip); parameters.put("desc", desc); String sign = WXSignUtils.createSign("UTF-8", parameters); transfers.setAmount(amount); transfers.setCheck_name(checkName); transfers.setDesc(desc); transfers.setMch_appid(appid); transfers.setMchid(mch_id); transfers.setNonce_str(nonce_str); transfers.setOpenid(openId); transfers.setPartner_trade_no(partner_trade_no); transfers.setSign(sign); transfers.setSpbill_create_ip(spbill_create_ip); String xmlInfo = HttpXmlUtils.transferXml(transfers); try { CloseableHttpResponse response = HttpUtil.Post(weixinConstant.WITHDRAW_URL, xmlInfo, true); String transfersXml = EntityUtils.toString(response.getEntity(), "utf-8"); Map<String, String> transferMap = HttpXmlUtils.parseRefundXml(transfersXml); if (transferMap.size()>0) { if (transferMap.get("result_code").equals("SUCCESS") && transferMap.get("return_code").equals("SUCCESS")) { //成功需要進行的邏輯操作, } } System.out.println("成功"); } catch (Exception e) { log.error(e.getMessage()); throw new BasicRuntimeException(this, "企業付款異常" + e.getMessage()); } }else { System.out.println("失敗"); } return NONE; }
產生隨機串部分程式碼:
public class RandCharsUtils {
private static SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
public static String getRandomString(int length) { //length表示生成字串的長度
String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
Random random = new Random();
StringBuffer sb = new StringBuffer();
int number = 0;
for (int i = 0; i < length; i++) {
number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
}
生成簽名:
public class WXSignUtils {
/**
* 微信支付簽名演算法sign
* @param characterEncoding
* @param parameters
* @return
*/
@SuppressWarnings("rawtypes")
public static String createSign(String characterEncoding,SortedMap<Object,Object> parameters){
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();//所有參與傳參的引數按照accsii排序(升序)
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
Object v = entry.getValue();
if(null != v && !"".equals(v)
&& !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + weixinConstant.KEY);
String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
return sign;
}
}
md5部分程式碼:
import java.security.MessageDigest;
public class MD5Util {
private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++)
resultSb.append(byteToHexString(b[i]));
return resultSb.toString();
}
private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
n += 256;
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
public static String MD5Encode(String origin, String charsetname) {
String resultString = null;
try {
resultString = new String(origin);
MessageDigest md = MessageDigest.getInstance("MD5");
if (charsetname == null || "".equals(charsetname))
resultString = byteArrayToHexString(md.digest(resultString
.getBytes()));
else
resultString = byteArrayToHexString(md.digest(resultString
.getBytes(charsetname)));
} catch (Exception exception) {
}
return resultString;
}
private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
}
構造xml:
/**
* 構造企業付款xml引數
* @param xml
* @return
*/
public static String transferXml(Transfers transfers){
xStream.autodetectAnnotations(true);
xStream.alias("xml", Transfers.class);
return xStream.toXML(transfers);
}
向微信傳送xml請求(驗證證書)部分程式碼:
public class HttpUtil {
/**
* 傳送post請求
*
* @param url
* 請求地址
* @param outputEntity
* 傳送內容
* @param isLoadCert
* 是否載入證書
*/
public static CloseableHttpResponse Post(String url, String outputEntity, boolean isLoadCert) throws Exception {
HttpPost httpPost = new HttpPost(url);
// 得指明使用UTF-8編碼,否則到API伺服器XML的中文不能被成功識別
httpPost.addHeader("Content-Type", "text/xml");
httpPost.setEntity(new StringEntity(outputEntity, "UTF-8"));
if (isLoadCert) {
// 載入含有證書的http請求
return HttpClients.custom().setSSLSocketFactory(CertUtil.initCert()).build().execute(httpPost);
} else {
return HttpClients.custom().build().execute(httpPost);
}
}
}
載入證書部分程式碼:(載入證書需要注意證書放置位置在專案下的webapp中建資料夾,linux單獨防止只要地址配置正確即可。)
import java.io.File;
import java.io.FileInputStream;
import java.security.KeyStore;
import javax.net.ssl.SSLContext;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.ssl.SSLContexts;
/**
* 載入證書的類
* @author
* @since 2017/08/16
*/
@SuppressWarnings("deprecation")
public class CertUtil {
private static WeixinConfigUtils config = new WeixinConfigUtils();
/**
* 載入證書
*/
public static SSLConnectionSocketFactory initCert() throws Exception {
FileInputStream instream = null;
KeyStore keyStore = KeyStore.getInstance("PKCS12");
instream = new FileInputStream(new File(weixinConstant.PATH));
keyStore.load(instream, config.mch_id.toCharArray());
if (null != instream) {
instream.close();
}
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore,config.mch_id.toCharArray()).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
return sslsf;
}
}
載入配置檔案部分程式碼:
@SuppressWarnings("unused")
public class WeixinConfigUtils {
private static final Log log = LogFactory.getLog(WeixinConfigUtils.class);
public static String appid;
public static String mch_id;
public static String notify_url;
public static String order_notify_url;
public static String doctor_notify_url;
static {
try{
InputStream is = WeixinConfigUtils.class.getResourceAsStream("/weixin.properties");
Properties properties = new Properties();
properties.load(is);
appid = properties.getProperty("weixin.appid");
mch_id = properties.getProperty("weixin.mch_id");
notify_url = properties.getProperty("weixin.notify_url");
order_notify_url = properties.getProperty("weixin.order_notify_url");
doctor_notify_url = properties.getProperty("weixin.doctor_notify_url");
}catch(Exception ex){
log.debug("載入配置檔案:"+ex.getMessage());
}
}
}
獲取返回的xml引數並解析為map:
/**
* 解析申請退款之後微信返回的值並進行存庫操作
* @throws IOException
* @throws JDOMException
*/
public static Map<String, String> parseRefundXml(String refundXml) throws JDOMException, IOException{
ParseXMLUtils.jdomParseXml(refundXml);
StringReader read = new StringReader(refundXml);
// 建立新的輸入源SAX 解析器將使用 InputSource 物件來確定如何讀取 XML 輸入
InputSource source = new InputSource(read);
// 建立一個新的SAXBuilder
SAXBuilder sb = new SAXBuilder();
// 通過輸入源構造一個Document
org.jdom.Document doc;
doc = (org.jdom.Document) sb.build(source);
org.jdom.Element root = doc.getRootElement();// 指向根節點
List<org.jdom.Element> list = root.getChildren();
Map<String, String> refundOrderMap = new HashMap<String, String>();
if(list!=null&&list.size()>0){
for (org.jdom.Element element : list) {
refundOrderMap.put(element.getName(), element.getText());
}
return refundOrderMap;
}
return null;
}
呼叫時候主要是獲取openid和調起介面的ip(ip十分重要,微信在收到xml後會校驗傳過去的ip和微信獲取的調起介面ip是否一致)
在呼叫時候當返回錯誤碼為“SYSTEMERROR”時,一定要使用原單號重試,否則可能造成重複支付等資金風險。
微信官方文件提供有相關的引數錯誤碼https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2。