我这边是针对微信商户支付功能开发。其他的未涉及到。
当你所有的准备工作准备好后:微信支付申请成功,api_key 配置好,等等一系列。
那么让我们进入java开发吧。
微信支付demo下载:
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1
下载JAVA版。这里面全是微信支付的工具类。
maven配置:
<!-- https://mvnrepository.com/artifact/com.github.wxpay/wxpay-sdk -->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
别去maven工厂搜索了。就3个版本0.01,0.02,0.03
微信支付和支付宝的支付区别在于。支付宝已经全部封装好了。而微信支付这B坑的一比吊扫。只有工具类,还要自己去写继承方法。
上面的java demo 下载之后。当如到自己当前的项目中。新建包名存放。
需要修改的地方:
新建WXPayConfig继承实现类:
public class WxPayUtilConfig extends WXPayConfig
如果该WxPayUtilConfig 和微信工具类的WXPayConfig 不在同一个包下,则WXPayConfig里面的方法需要
public
/**
* 微信支付配置文件
* 继承微信工具配置文件抽象接口
* @author think
*/
public class WxPayUtilConfig extends WXPayConfig{
private byte[] certData;//将证书地址解析成byte
//证书需要自己到微信商户端自己下来。一些了操作很简答,需要找找而已。如果支付放到服务器,则cert这个需要放到服务器,并且路径改成服务器路径,否则,app唤醒支付报500异常
public WxPayUtilConfig() throws Exception {
//从微信商户平台下载的安全证书存放的路径
String certPath = "C:\\services\\wxpay\\cert\\apiclient_cert.p12";
File file = new File(certPath);
InputStream certStream = new FileInputStream(file);
this.certData = new byte[(int) file.length()];
certStream.read(this.certData);
certStream.close();
} @Override
public IWXPayDomain getWXPayDomain() {
// TODO Auto-generated method stub
return WXPayDomainSimpleImpl.instance();
}
@Override
public String getAppID() {
// TODO Auto-generated method stub
return WXPayCommonPath.APP_ID;
} @Override
public String getMchID() {
// TODO Auto-generated method stub
return WXPayCommonPath.MCH_ID;
} @Override
public String getKey() {
// TODO Auto-generated method stub
return WXPayCommonPath.API_KEY;
} @Override
public InputStream getCertStream() {
// TODO Auto-generated method stub
ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
return certBis;
} @Override
public int getHttpConnectTimeoutMs() {
// TODO Auto-generated method stub
return 8000;
} @Override
public int getHttpReadTimeoutMs() {
// TODO Auto-generated method stub
return 10000;
}
}2:实现IWXPayDomain域名主备方法,该方法直接复制即可。无需修改。该方法与IWXPayDomain微信工具类一个包名下。
/**
* 域名管理,实现主备域名自动切换
* 该实现方法需要自己写一个。
* @author think
*/
public class WXPayDomainSimpleImpl implements IWXPayDomain {
private final int MIN_SWITCH_PRIMARY_MSEC = 3 * 60 * 1000; //3 minutes
private long switchToAlternateDomainTime = 0;
private Map<String, DomainStatics> domainData = new HashMap<String, DomainStatics>();
private WXPayDomainSimpleImpl(){}
private static class WxpayDomainHolder{
private static IWXPayDomain holder = new WXPayDomainSimpleImpl();
}
public static IWXPayDomain instance(){
return WxpayDomainHolder.holder;
} @Override
public void report(String domain, long elapsedTimeMillis, Exception ex) {
// TODO Auto-generated method stub
DomainStatics info = domainData.get(domain);
if(info == null){
info = new DomainStatics(domain);
domainData.put(domain, info);
} if(ex == null){ //success
if(info.succCount >= 2){ //continue succ, clear error count
info.connectTimeoutCount = info.dnsErrorCount = info.otherErrorCount = 0;
}else{
++info.succCount;
}
}else if(ex instanceof ConnectTimeoutException){
info.succCount = info.dnsErrorCount = 0;
++info.connectTimeoutCount;
}else if(ex instanceof UnknownHostException){
info.succCount = 0;
++info.dnsErrorCount;
}else{
info.succCount = 0;
++info.otherErrorCount;
}
} @Override
public DomainInfo getDomain(WXPayConfig config) {
// TODO Auto-generated method stub
DomainStatics primaryDomain = domainData.get(WXPayConstants.DOMAIN_API);
if(primaryDomain == null ||
primaryDomain.isGood()) {
return new DomainInfo(WXPayConstants.DOMAIN_API, true);
} long now = System.currentTimeMillis();
if(switchToAlternateDomainTime == 0){ //first switch
switchToAlternateDomainTime = now;
return new DomainInfo(WXPayConstants.DOMAIN_API2, false);
}else if(now - switchToAlternateDomainTime < MIN_SWITCH_PRIMARY_MSEC){
DomainStatics alternateDomain = domainData.get(WXPayConstants.DOMAIN_API2);
if(alternateDomain == null ||
alternateDomain.isGood() ||
alternateDomain.badCount() < primaryDomain.badCount()){
return new DomainInfo(WXPayConstants.DOMAIN_API2, false);
}else{
return new DomainInfo(WXPayConstants.DOMAIN_API, true);
}
}else{ //force switch back
switchToAlternateDomainTime = 0;
primaryDomain.resetCount();
DomainStatics alternateDomain = domainData.get(WXPayConstants.DOMAIN_API2);
if(alternateDomain != null)
alternateDomain.resetCount();
return new DomainInfo(WXPayConstants.DOMAIN_API, true);
}
} static class DomainStatics {
final String domain;
int succCount = 0;
int connectTimeoutCount = 0;
int dnsErrorCount =0;
int otherErrorCount = 0; DomainStatics(String domain) {
this.domain = domain;
}
void resetCount(){
succCount = connectTimeoutCount = dnsErrorCount = otherErrorCount = 0;
}
boolean isGood(){ return connectTimeoutCount <= 2 && dnsErrorCount <= 2; }
int badCount(){
return connectTimeoutCount + dnsErrorCount * 5 + otherErrorCount / 4;
}
}
}3:新建WXPayCommonPath微信支付配置的常量类。自己创建
/**
* 微信支付常量
* @author think
*/
public class WXPayCommonPath { public static final String WXUNIFIEDORDER_HTTPURL = "https://api.mch.weixin.qq.com/pay/unifiedorder";// 微信统一下单接口
public static final String APP_ID = "";// 微信appid
public static final String MCH_ID = "";// 商户号
public static final String API_KEY = "";// apikey应用私钥 api密钥 这个需要自己到商户平台去配置32位长度。
public static final String NOTIFYURL = "";//回调方法
public static final String TRADE_TYPE = "APP";//app支付
public static final String SPBILL_CREATE_IP = "";//服务器ip地址
}下面写微信支付调用方法:
1:新建WxPayController
:OperLog 该方法是aop日志,可以去掉。
/**
* (微信支付) app请求预付支付订单
* @param req
* @param res
* @return
* @throws Exception
*/
@OperLog(logDescription = "app请求预付支付订单(微信支付)")
@RequestMapping(value = "getOrderInfo", method = { RequestMethod.POST, RequestMethod.GET })
@ResponseBody
public Map<String, Object> getOrderInfo(HttpServletRequest req, HttpServletResponse res) throws Exception {
res.setCharacterEncoding("UTF-8"); //该字段属于自定义字段,比如传入id用来service逻辑处理等,不要放入中文,中文会乱码.
String attach = "";
//创建订单编号 commonUtils是我自己的工具类,你可以自己写一个生成随机的订单编号
String out_trade_no = CommonUtils.createNum(); //创建预支付信息
Map<String, String> resultMap = wxPayService.dounifiedOrder(attach,out_trade_no);
Map<String, String> map = new HashMap<String, String>();
map.put("appid", resultMap.get("appid"));
map.put("partnerid", resultMap.get("mch_id"));
map.put("prepayid", resultMap.get("prepay_id"));
map.put("packagevalue", "Sign=WXPay");
map.put("noncestr", resultMap.get("nonce_str"));
map.put("timestamp", String.valueOf(WXPayUtil.getCurrentTimestamp()));// 单位为秒
//这里使用生成带有sign的xml方法,如果使用签名,则前端app无法唤起微信支付界面
String sign = WXPayUtil.generateSignedXml(map, WXPayCommonPath.API_KEY);
map.put("sign",sign);
map.put("extdata", "预留字段,可传入自定字段。");
map.put("tradetype", resultMap.get("trade_type")); return JsonMethod.setJsonMethod(PathKeyEnum.SUCCESS.getKey(), PathKeyEnum.SUCCESS.getValue(), map);
}
/**
* 微信支付回调接口
*
* @param request
* @param response
* @return
*/
@OperLog(logDescription = "微信支付回调接口(微信支付)")
@RequestMapping(value = "returnNotify", method = { RequestMethod.POST, RequestMethod.GET })
@ResponseBody
public String returnNotify(HttpServletRequest request, HttpServletResponse response) {
response.setCharacterEncoding("UTF-8");
String resXml = "";
try {
InputStream inputStream = request.getInputStream();
//将InputStream转换成xmlString
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sb = new StringBuilder();
String line = null;
try {
while ((line = reader.readLine()) != null) {
sb.append(line + "\n");
}
} catch (IOException e) {
System.out.println(e.getMessage());
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
resXml = sb.toString();
String result = wxPayService.returnXml(resXml);
return result;
} catch (Exception e) {
System.out.println("微信手机支付失败:" + e.getMessage());
String result = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
return result;
}
}
2:创建 service serviceImpl 方法 (spring mvc 框架)
WxPayService:
/**
* 微信支付逻辑处理
* @author think
*
*/
public interface WxPayService { /**
* 微信支付请求预付单
* @return
* @throws Exception
*/
public Map<String, String> dounifiedOrder(String attach,String out_trade_no)throws Exception;
/**
* 接收微信回调的方法并解析xml数据
* @param notifyData
* @return
*/
public String returnXml(String notifyData)throws Exception;
}
创建impl方法
/**
* 微信支付逻辑实现
* @author think
*/
@Service("WxPayService")
public class WxPayServiceImpl implements WxPayService { /**
* 微信支付预付单生成
*/
@Override
public Map<String, String> dounifiedOrder(String attach,String out_trade_no) throws Exception {
// TODO Auto-generated method stub
Map<String, String> returnMap = new HashMap<String, String>();
Map<String, String> param = new HashMap<String, String>();
WxPayUtilConfig config = new WxPayUtilConfig();
WXPay wxPay = new WXPay(config);
param.put("appid", config.getAppID());
param.put("attach", attach);
//内容描述可通过前端app传递过来.
param.put("body", "订单支付");
param.put("mch_id", config.getMchID());
param.put("nonce_str", WXPayUtil.generateNonceStr());
param.put("notify_url", WXPayCommonPath.NOTIFYURL);
param.put("out_trade_no", out_trade_no);
//自己的服务器IP地址
param.put("spbill_create_ip", WXPayCommonPath.SPBILL_CREATE_IP);
param.put("total_fee", "1");//金额 以分为单位.我这边是测试数据.
//交易类型
param.put("trade_type", WXPayCommonPath.TRADE_TYPE);
//该类型定义和不定义都无法更改微信回调的签名方式.所以我注释掉.写只是为了测试是否有用
//param.put("sign_type","MD5");
String sign = WXPayUtil.generateSignature(param,WXPayCommonPath.API_KEY);
param.put("sign", sign);
Map<String, String> response = wxPay.unifiedOrder(param);
String returnCode = response.get("return_code");//获取返回码
//若返回码为SUCCESS,则会返回一个result_code,再对该result_code进行判断
if (returnCode.equals("SUCCESS")) {//主要返回以下5个参数
String resultCode = response.get("result_code");
returnMap.put("appid", response.get("appid"));
returnMap.put("mch_id", response.get("mch_id"));
returnMap.put("nonce_str", response.get("nonce_str"));
returnMap.put("sign", response.get("sign"));
if ("SUCCESS".equals(resultCode)) {
//resultCode 为SUCCESS,才会返回prepay_id和trade_type
//获取预支付交易回话标志
returnMap.put("trade_type", response.get("trade_type"));
returnMap.put("prepay_id", response.get("prepay_id"));
return returnMap;
} else {
//此时返回没有预付订单的数据
return returnMap;
}
}
return returnMap;
} /**
* 解析回调后的xml数据
*/
@Override
public String returnXml(String notifyData) throws Exception{
// TODO Auto-generated method stub
String xmlBack = "";
Map<String, String> notifyMap = null;
try {
notifyMap = WXPayUtil.xmlToMap(notifyData);//调用官方SDK转换成map类型数据
/**
* 微信回调返回的xml签名使用的是HMACSHA256
* if判断的方法里需要加入HMACSHA256签名
* 并且HMACSHA256该签名方法要导入sdk的包,别用工具类的包
* 因为它底层判断的是用sdk的包里的枚举来判断的.
*/
if (WXPayUtil.isSignatureValid(notifyMap, WXPayCommonPath.API_KEY,SignType.HMACSHA256)) {//验证签名是否有效,有效则进一步处理
String return_code = notifyMap.get("return_code");//状态
String out_trade_no = notifyMap.get("out_trade_no");//商户订单号
if (return_code.equals("SUCCESS")) {
if (out_trade_no != null) {
//这里就需要去做你的逻辑判断,比如说订单的状态 System.err.println("支付成功");
xmlBack = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
} else {
LogComm.setLog("微信手机支付回调失败订单号:{}" + out_trade_no);
xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
}
}
return xmlBack;
} else {
// 签名错误,如果数据里没有sign字段,也认为是签名错误
//失败的数据要不要存储?
LogComm.setLog("手机支付回调通知签名错误:");
xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
return xmlBack;
}
} catch (Exception e) {
LogComm.setLog("手机支付回调通知失败:" + e.getMessage());
xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
}
return xmlBack;
}
}
以上所有功能就是java微信支付服务端开发的功能
备注:
1:微信预付单需要签名,而返回给app的json里面的sign 调用工具类里面的 xml方法即可,不需要继续签名,否则app端无法唤起支付,上面有注释到。
2:微信回调已经给你处理过了签名使用的是HMACSHA256 签名方式。
在回调方法里如果用MD5签名去和微信回调的xml签名对比,永远都不会对,
所以回调之后需要用HMACSHA256 签名 并且与微信回调xml签名对比即可。
HMACSHA256:导入的是sdk包,别到工具类包,看工具类源码就可以读到。它底层判断sign就是用sdk的包来判断的。
好了。最后说一句。微信支付是真坑。开发花半天,回调 调试就浪费了半天,就因为签名对比不成功。坑。
注各位脱坑愉快。