java實(shí)現(xiàn)微信企業(yè)付款到個(gè)人功能
微信官方提供了微信企業(yè)賬戶付款到微信個(gè)人零錢接口,提供企業(yè)向用戶付款的功能,支持企業(yè)通過API接口付款,或通過微信支付商戶平臺(tái)網(wǎng)頁功能操作付款。該接口并不是直接所有的商戶都擁有,企業(yè)要開啟必須滿足以下兩個(gè)條件:
1、商戶號(hào)已入駐90日
2、商戶號(hào)有30天連續(xù)正常交易
滿足以上條件就可登錄微信支付商戶平臺(tái)-產(chǎn)品中心,開通企業(yè)付款。
調(diào)用的鏈接地址:接口鏈接:https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers
微信官方接口文檔提供的調(diào)用微信企業(yè)付款的參數(shù):

參數(shù)中最重要是獲取用戶openid和調(diào)用接口ip,獲取openid可以通過公眾號(hào)獲取,app端可以直接獲取。具體的代碼實(shí)現(xiàn)如下:
封裝請(qǐng)求微信企業(yè)付款的實(shí)體類Transfers:
public class Transfers implements Serializable{
private static final long serialVersionUID = 1L;
/** 商戶賬號(hào)appid*/
public String mch_appid;
/** 微信支付商戶號(hào)*/
public String mchid;
/** 隨機(jī)串*/
public String nonce_str;
/** 簽名*/
public String sign;
/** 商戶訂單號(hào)*/
public String partner_trade_no;
/** 用戶id*/
public String openid;
/** 是否校驗(yàn)用戶姓名 NO_CHECK:不校驗(yàn)真實(shí)姓名 FORCE_CHECK:強(qiáng)校驗(yàn)真實(shí)姓名*/
public String check_name;
/** 金額 單位:分*/
public Integer amount;
/** 企業(yè)付款描述信息*/
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();
// 構(gòu)造簽名的map
private SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
// 微信的參數(shù)
private WeixinConfigUtils config = new WeixinConfigUtils();
/**
* 微信提現(xiàn)(企業(yè)付款)
*/
@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)) {
// 參數(shù)組
String appid = config.appid;
String mch_id = config.mch_id;
String nonce_str = RandCharsUtils.getRandomString(16);
//是否校驗(yàn)用戶姓名 NO_CHECK:不校驗(yàn)真實(shí)姓名 FORCE_CHECK:強(qiáng)校驗(yàn)真實(shí)姓名
String checkName ="NO_CHECK";
//等待確認(rèn)轉(zhuǎn)賬金額,ip,openid的來源
Integer amount = Integer.valueOf(money);
String spbill_create_ip = ip;
String partner_trade_no = UuIdUtils.getUUID();
//描述
String desc = "健康由我醫(yī)師助手提現(xiàn)"+amount/100+"元";
// 參數(shù):開始生成第一次簽名
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")) {
//成功需要進(jìn)行的邏輯操作,
}
}
System.out.println("成功");
} catch (Exception e) {
log.error(e.getMessage());
throw new BasicRuntimeException(this, "企業(yè)付款異常" + e.getMessage());
}
}else {
System.out.println("失敗");
}
return NONE;
}
產(chǎn)生隨機(jī)串部分代碼:
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();//所有參與傳參的參數(shù)按照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" };
}
構(gòu)造xml:
/**
* 構(gòu)造企業(yè)付款xml參數(shù)
* @param xml
* @return
*/
public static String transferXml(Transfers transfers){
xStream.autodetectAnnotations(true);
xStream.alias("xml", Transfers.class);
return xStream.toXML(transfers);
}
向微信發(fā)送xml請(qǐng)求(驗(yàn)證證書)部分代碼:
public class HttpUtil {
/**
* 發(fā)送post請(qǐng)求
*
* @param url
* 請(qǐng)求地址
* @param outputEntity
* 發(fā)送內(nèi)容
* @param isLoadCert
* 是否加載證書
*/
public static CloseableHttpResponse Post(String url, String outputEntity, boolean isLoadCert) throws Exception {
HttpPost httpPost = new HttpPost(url);
// 得指明使用UTF-8編碼,否則到API服務(wù)器XML的中文不能被成功識(shí)別
httpPost.addHeader("Content-Type", "text/xml");
httpPost.setEntity(new StringEntity(outputEntity, "UTF-8"));
if (isLoadCert) {
// 加載含有證書的http請(qǐng)求
return HttpClients.custom().setSSLSocketFactory(CertUtil.initCert()).build().execute(httpPost);
} else {
return HttpClients.custom().build().execute(httpPost);
}
}
}
加載證書部分代碼:(加載證書需要注意證書放置位置在項(xiàng)目下的webapp中建文件夾,linux單獨(dú)防止只要地址配置正確即可。)
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參數(shù)并解析為map:
/**
* 解析申請(qǐng)退款之后微信返回的值并進(jìn)行存庫操作
* @throws IOException
* @throws JDOMException
*/
public static Map<String, String> parseRefundXml(String refundXml) throws JDOMException, IOException{
ParseXMLUtils.jdomParseXml(refundXml);
StringReader read = new StringReader(refundXml);
// 創(chuàng)建新的輸入源SAX 解析器將使用 InputSource 對(duì)象來確定如何讀取 XML 輸入
InputSource source = new InputSource(read);
// 創(chuàng)建一個(gè)新的SAXBuilder
SAXBuilder sb = new SAXBuilder();
// 通過輸入源構(gòu)造一個(gè)Document
org.jdom.Document doc;
doc = (org.jdom.Document) sb.build(source);
org.jdom.Element root = doc.getRootElement();// 指向根節(jié)點(diǎn)
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;
}
調(diào)用時(shí)候主要是獲取openid和調(diào)起接口的ip(ip十分重要,微信在收到xml后會(huì)校驗(yàn)傳過去的ip和微信獲取的調(diào)起接口ip是否一致)
在調(diào)用時(shí)候當(dāng)返回錯(cuò)誤碼為“SYSTEMERROR”時(shí),一定要使用原單號(hào)重試,否則可能造成重復(fù)支付等資金風(fēng)險(xiǎn)。
微信官方文檔提供有相關(guān)的參數(shù)錯(cuò)誤碼
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
springboot如何通過不同的策略動(dòng)態(tài)調(diào)用不同的實(shí)現(xiàn)類
這篇文章主要介紹了springboot如何通過不同的策略動(dòng)態(tài)調(diào)用不同的實(shí)現(xiàn)類,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02
SpringBoot整合Druid數(shù)據(jù)庫連接池的方法
Druid是Java語言中最好的數(shù)據(jù)庫連接池。Druid能夠提供強(qiáng)大的監(jiān)控和擴(kuò)展功能。這篇文章主要介紹了SpringBoot整合Druid數(shù)據(jù)庫連接池的方法,需要的朋友可以參考下2020-07-07
mybatis plus 的動(dòng)態(tài)表名的配置詳解
這篇文章主要介紹了mybatis plus 的動(dòng)態(tài)表名的配置詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
Spring?Boot?使用?SSE?方式向前端推送數(shù)據(jù)詳解
這篇文章主要介紹了Spring?Boot?使用SSE方式向前端推送數(shù)據(jù)詳解,SSE簡單的來說就是服務(wù)器主動(dòng)向前端推送數(shù)據(jù)的一種技術(shù),它是單向的,也就是說前端是不能向服務(wù)器發(fā)送數(shù)據(jù)的2022-08-08
SpringBoot中實(shí)現(xiàn)異步調(diào)用@Async詳解
這篇文章主要介紹了SpringBoot中實(shí)現(xiàn)異步調(diào)用@Async詳解,在SpringBoot的日常開發(fā)中,一般都是同步調(diào)用的,但實(shí)際中有很多場景非常適合使用異步來處理,需要的朋友可以參考下2024-01-01
Spring Boot文件上傳原理與實(shí)現(xiàn)詳解
這篇文章主要介紹了Spring Boot 文件上傳原理與實(shí)現(xiàn)詳解,前端文件上傳是面向多用戶的,多用戶之間可能存在上傳同一個(gè)名稱、類型的文件;為了避免文件沖突導(dǎo)致的覆蓋問題這些應(yīng)該在后臺(tái)進(jìn)行解決,需要的朋友可以參考下2024-01-01

