This commit is contained in:
地平线
2015-08-17 15:00:16 +08:00
parent 9ea9285152
commit f9ceb1afc4
3 changed files with 332 additions and 1 deletions

View File

@@ -21,7 +21,7 @@ import javax.annotation.*;
*
* @author zhangjx
*/
public class WeiXinMPService {
public class WeiXinMPService implements Service{
protected static final Type MAPTYPE = new TypeToken<Map<String, String>>() {
}.getType();

View File

@@ -0,0 +1,77 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.wentch.redkale.service.weixin;
import com.wentch.redkale.service.*;
/**
*
* @author zhangjx
*/
public class WeiXinPayResult extends RetResult<String> {
//待支付
public static final short PAYSTATUS_UNPAY = 10;
//已支付
public static final short PAYSTATUS_PAYOK = 30;
private long orderid;
private long payid;
private long payedmoney;
private short paystatus;
public WeiXinPayResult() {
}
public WeiXinPayResult(int retcode) {
super(retcode);
}
public WeiXinPayResult(long orderid, long payid, short paystatus, long payedmoney, String resultcontent) {
this.orderid = orderid;
this.payid = payid;
this.paystatus = paystatus;
this.payedmoney = payedmoney;
this.setResult(resultcontent);
}
public long getOrderid() {
return orderid;
}
public void setOrderid(long orderid) {
this.orderid = orderid;
}
public long getPayid() {
return payid;
}
public void setPayid(long payid) {
this.payid = payid;
}
public long getPayedmoney() {
return payedmoney;
}
public void setPayedmoney(long payedmoney) {
this.payedmoney = payedmoney;
}
public short getPaystatus() {
return paystatus;
}
public void setPaystatus(short paystatus) {
this.paystatus = paystatus;
}
}

View File

@@ -0,0 +1,254 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.wentch.redkale.service.weixin;
import com.wentch.redkale.convert.json.*;
import com.wentch.redkale.service.*;
import static com.wentch.redkale.service.weixin.WeiXinPayResult.*;
import com.wentch.redkale.util.*;
import java.security.*;
import java.text.*;
import java.util.*;
import java.util.logging.*;
import java.util.regex.*;
import javax.annotation.*;
/**
*
* @author zhangjx
*/
public class WeiXinPayService implements Service {
private static final DateFormat FORMAT = new SimpleDateFormat("yyyyMMddHHmmss");
private static final Pattern PAYXML = Pattern.compile("<([^/>]+)>(.+)</.+>"); // "<([^/>]+)><!\\[CDATA\\[(.+)\\]\\]></.+>"
public static final int PAY_WX_ERROR = 4012101;//微信支付失败
public static final int PAY_FALSIFY_ORDER = 4012017;//交易签名被篡改
public static final int PAY_STATUS_ERROR = 4012018;//订单或者支付状态不正确
protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName());
protected final boolean fine = logger.isLoggable(Level.FINE);
protected final boolean finer = logger.isLoggable(Level.FINER);
protected final boolean finest = logger.isLoggable(Level.FINEST);
@Resource(name = "property.wxpay.appid") //公众账号ID
protected String wxpayappid = "wxYYYYYYYYYYYY";
@Resource(name = "property.wxpay.mchid") //商户ID
protected String wxpaymchid = "xxxxxxxxxxx";
@Resource(name = "property.wxpay.sdbmchid") //子商户ID受理模式必填
protected String wxpaysdbmchid = "";
@Resource(name = "property.wxpay.key") //签名算法需要用到的秘钥
protected String wxpaykey = "##########################";
@Resource(name = "property.wxpay.certpwd")
protected String wxpaycertpwd = "xxxxxxxxxx"; //HTTP证书的密码默认等于MCHID
@Resource(name = "property.wxpay.certpath") //HTTP证书在服务器中的路径用来加载证书用
protected String wxpaycertpath = "apiclient_cert.p12";
@Resource
protected JsonConvert convert;
/**
* <xml><return_code><![CDATA[SUCCESS]]></return_code>
* + "<return_msg><![CDATA[OK]]></return_msg>
* + "<appid><![CDATA[wx4ad12c89818dd981]]></appid>
* + "<mch_id><![CDATA[1241384602]]></mch_id>
* + "<nonce_str><![CDATA[RpGucJ6wKtPgpTJy]]></nonce_str>
* + "<sign><![CDATA[DFD99D5DA7DCA4FB5FB79ECAD49B9369]]></sign>
* + "<result_code><![CDATA[SUCCESS]]></result_code>
* + "<prepay_id><![CDATA[wx2015051518135700aaea6bc30284682518]]></prepay_id>
* + "<trade_type><![CDATA[JSAPI]]></trade_type>
* + "</xml>
* <p>
* @param orderid
* @param payid
* @param orderpayid
* @param paymoney
* @param clientAddr
* @param notifyurl
* @param map
* @return
*/
public RetResult<Map<String, String>> paying(long orderid, long payid, long orderpayid, long paymoney, String clientAddr, String notifyurl, Map<String, String> map) {
RetResult result = null;
try {
if (!(map instanceof SortedMap)) map = new TreeMap<>(map);
map.put("appid", wxpayappid);
map.put("mch_id", wxpaymchid);
map.put("nonce_str", Long.toHexString(System.currentTimeMillis()) + Long.toHexString(System.nanoTime()));
map.put("body", "三万英尺服务套餐");
map.put("attach", "" + payid);
map.put("out_trade_no", "" + orderpayid);
map.put("total_fee", "" + paymoney);
map.put("spbill_create_ip", clientAddr);
synchronized (FORMAT) {
map.put("time_expire", FORMAT.format(new Date(System.currentTimeMillis() + 10 * 60 * 60 * 1000)));
}
map.put("notify_url", notifyurl);
{
final StringBuilder sb = new StringBuilder();
map.forEach((x, y) -> sb.append(x).append('=').append(y).append('&'));
sb.append("key=").append(wxpaykey);
map.put("sign", Utility.binToHexString(MessageDigest.getInstance("MD5").digest(sb.toString().getBytes())).toUpperCase());
}
if (finest) logger.finest("weixinpaying2: " + orderid + " -> unifiedorder.map =" + map);
Map<String, String> wxresult = formatXMLToMap(Utility.postHttpContent("https://api.mch.weixin.qq.com/pay/unifiedorder", formatMapToXML(map)));
if (finest) logger.finest("weixinpaying3: " + orderid + " -> unifiedorder.callback =" + wxresult);
if (!"SUCCESS".equals(wxresult.get("return_code"))) return new RetResult<>(PAY_WX_ERROR);
if (!checkSign(wxresult)) return new RetResult(PAY_FALSIFY_ORDER);
/**
* "appId" : "wx2421b1c4370ec43b", //公众号名称,由商户传入 "timeStamp":" 1395712654", //时间戳自1970年以来的秒数 "nonceStr" : "e61463f8efa94090b1f366cccfbbb444", //随机串 "package" :
* "prepay_id=u802345jgfjsdfgsdg888", "signType" : "MD5", //微信签名方式: "paySign" : "70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名
*/
Map<String, String> rs = new TreeMap<>();
rs.put("appId", this.wxpayappid);
rs.put("timeStamp", Long.toString(System.currentTimeMillis() / 1000));
rs.put("nonceStr", Long.toHexString(System.currentTimeMillis()) + Long.toHexString(System.nanoTime()));
rs.put("package", "prepay_id=" + wxresult.get("prepay_id"));
rs.put("signType", "MD5");
{
final StringBuilder sb2 = new StringBuilder();
rs.forEach((x, y) -> sb2.append(x).append('=').append(y).append('&'));
sb2.append("key=").append(wxpaykey);
rs.put("paySign", Utility.binToHexString(MessageDigest.getInstance("MD5").digest(sb2.toString().getBytes())).toUpperCase());
}
if (finest) logger.finest("weixinpaying4: " + orderid + " -> unifiedorder.result =" + rs);
RetResult rr = new RetResult(rs);
rr.setRetinfo("" + orderpayid);
return rr;
} catch (Exception e) {
logger.log(Level.WARNING, "paying error.", e);
}
return result;
}
public RetResult closepay(long orderpayid) {
RetResult result = null;
try {
Map<String, String> map = new TreeMap<>();
map.put("appid", wxpayappid);
map.put("mch_id", wxpaymchid);
map.put("nonce_str", Long.toHexString(System.currentTimeMillis()) + Long.toHexString(System.nanoTime()));
map.put("out_trade_no", "" + orderpayid);
{
final StringBuilder sb = new StringBuilder();
map.forEach((x, y) -> sb.append(x).append('=').append(y).append('&'));
sb.append("key=").append(wxpaykey);
map.put("sign", Utility.binToHexString(MessageDigest.getInstance("MD5").digest(sb.toString().getBytes())).toUpperCase());
}
if (finest) logger.finest("weixinclosepay2: " + orderpayid + " -> closeorder.map =" + map);
Map<String, String> wxresult = formatXMLToMap(Utility.postHttpContent("https://api.mch.weixin.qq.com/pay/closeorder", formatMapToXML(map)));
if (finest) logger.finest("weixinclosepay3: " + orderpayid + " -> closeorder.callback =" + wxresult);
if (!"SUCCESS".equals(wxresult.get("return_code"))) return new RetResult<>(PAY_WX_ERROR);
if (!checkSign(wxresult)) return new RetResult(PAY_FALSIFY_ORDER);
return new RetResult(wxresult);
} catch (Exception e) {
logger.log(Level.WARNING, "closepay error: " + orderpayid, e);
}
return result;
}
public WeiXinPayResult checkPay(long orderid, long orderpayid) {
WeiXinPayResult result = new WeiXinPayResult(PAY_STATUS_ERROR);
try {
Map<String, String> map = new TreeMap<>();
map.put("appid", wxpayappid);
map.put("mch_id", wxpaymchid);
map.put("out_trade_no", "" + orderpayid);
map.put("nonce_str", Long.toHexString(System.currentTimeMillis()) + Long.toHexString(System.nanoTime()));
{
final StringBuilder sb = new StringBuilder();
map.forEach((x, y) -> sb.append(x).append('=').append(y).append('&'));
sb.append("key=").append(wxpaykey);
map.put("sign", Utility.binToHexString(MessageDigest.getInstance("MD5").digest(sb.toString().getBytes())).toUpperCase());
}
Map<String, String> wxresult = formatXMLToMap(Utility.postHttpContent("https://api.mch.weixin.qq.com/pay/orderquery", formatMapToXML(map)));
return callbackPay(wxresult);
} catch (Exception e) {
logger.log(Level.FINER, "check weixinpay[" + orderid + "] except", e);
return result;
}
}
/**
* <xml>
* <appid><![CDATA[wx4ad12c89818dd981]]></appid>
* <attach><![CDATA[10000070334]]></attach>
* <bank_type><![CDATA[ICBC_DEBIT]]></bank_type>
* <cash_fee><![CDATA[10]]></cash_fee>
* <fee_type><![CDATA[CNY]]></fee_type>
* <is_subscribe><![CDATA[Y]]></is_subscribe>
* <mch_id><![CDATA[1241384602]]></mch_id>
* <nonce_str><![CDATA[14d69ac6d6525f27dc9bcbebc]]></nonce_str>
* <openid><![CDATA[ojEVbsyDUzGqlgX3eDgmAMaUDucA]]></openid>
* <out_trade_no><![CDATA[1000072334]]></out_trade_no>
* <result_code><![CDATA[SUCCESS]]></result_code>
* <return_code><![CDATA[SUCCESS]]></return_code>
* <sign><![CDATA[60D95E25EA9C4F54BD1020952303C4E2]]></sign>
* <time_end><![CDATA[20150519085546]]></time_end>
* <total_fee>10</total_fee>
* <trade_type><![CDATA[JSAPI]]></trade_type>
* <transaction_id><![CDATA[1009630061201505190139511926]]></transaction_id>
* </xml>
* <p>
* @param map
* @return
*/
public WeiXinPayResult callbackPay(Map<String, String> map) {
if (!"SUCCESS".equals(map.get("return_code"))) return new WeiXinPayResult(PAY_WX_ERROR);
if (!(map instanceof SortedMap)) map = new TreeMap<>(map);
if (!checkSign(map)) return new WeiXinPayResult(PAY_FALSIFY_ORDER);
String state = map.get("trade_state");
if (state == null && "SUCCESS".equals(map.get("result_code")) && Long.parseLong(map.get("total_fee")) > 0) {
state = "SUCCESS";
}
short paystatus = "SUCCESS".equals(state) ? PAYSTATUS_PAYOK : PAYSTATUS_UNPAY;
return new WeiXinPayResult(Long.parseLong(map.get("out_trade_no")), Long.parseLong(map.get("attach")), paystatus, Long.parseLong(map.get("total_fee")), convert.convertTo(map));
}
protected static String formatMapToXML(final Map<String, String> map) {
final StringBuilder sb = new StringBuilder();
sb.append("<xml>");
map.forEach((x, y) -> sb.append('<').append(x).append('>').append(y.replace("<", "&lt;").replace(">", "&gt;").replace("&", "&amp;")).append("</").append(x).append('>'));
sb.append("</xml>");
return sb.toString();
}
protected boolean checkSign(Map<String, String> map) {
if (!(map instanceof SortedMap)) map = new TreeMap<>(map);
String sign = map.remove("sign");
final StringBuilder sb = new StringBuilder();
map.forEach((x, y) -> sb.append(x).append('=').append(y).append('&'));
sb.append("key=").append(wxpaykey);
try {
return sign.equals(Utility.binToHexString(MessageDigest.getInstance("MD5").digest(sb.toString().getBytes())).toUpperCase());
} catch (Exception e) {
return false;
}
}
public static Map<String, String> formatXMLToMap(final String xml) {
Map<String, String> map = new TreeMap<>();
Matcher m = PAYXML.matcher(xml.substring(xml.indexOf('>') + 1));
while (m.find()) {
String val = m.group(2);
if (val.startsWith("<![CDATA[")) val = val.substring("<![CDATA[".length(), val.length() - 3);
map.put(m.group(1), val);
}
return map;
}
}