不得不吐槽下微信的开发文档,实在是太烂。还是支付宝的开发文档靠谱
首先说下我踩过的坑:
1.签名错误,首先看自己的签名是否正确,可以用微信的签名校验工具检测https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=20_1
2.微信的证书放到项目里边导致部署到服务器无法获取,放到项目外解决
3.返回app端的"package","Sign=WXPay");不能去掉,不然app无法拉起微信支付
首先是pom文件
com.github.wxpay
wxpay-sdk
0.0.3
微信app支付的流程官网已经说的比较明确了,后台常用的就是两个接口:生成预支付订单和支付回调
contorller生成预支付订单
@RequestMapping(value = "/pay", method = {RequestMethod.GET, RequestMethod.POST})
public String orderPay(/*@RequestParam(required = true,value = "user_id")String user_id,
@RequestParam(required = true,value = "coupon_id")String coupon_id,
@RequestParam(required = true,value = "out_trade_no")String out_trade_no,
@RequestParam(required = true,value = "total_fee")String total_fee,*/
HttpServletRequest req, HttpServletResponse response) throws Exception {
System.err.println("进入微信支付申请");
Date now = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");//可以方便地修改日期格式
String hehe = dateFormat.format(now);
String out_trade_no=hehe+"wxpay"; //777777 需要前端给的参数
String total_fee="1"; //7777777 微信支付钱的单位为分
String user_id="1"; //77777
String coupon_id="7"; //777777
String attach=user_id+","+coupon_id;
WXMyConfigUtil config = new WXMyConfigUtil();
// String spbill_create_ip = GetIPAddrUtil.getIpAddr(req);
String spbill_create_ip="10.4.21.78";
System.err.println(spbill_create_ip);
Map result = wxPayService.dounifiedOrder(attach,out_trade_no,total_fee,spbill_create_ip,1);
String nonce_str = (String)result.get("nonce_str");
String prepay_id = (String)result.get("prepay_id");
Long time =System.currentTimeMillis()/1000;
String timestamp=time.toString();
//签名生成算法
MD5Util md5Util = new MD5Util();
Map map = new HashMap<>();
map.put("appid",config.getAppID());
map.put("partnerid",config.getMchID());
map.put("package","Sign=WXPay");
map.put("noncestr",nonce_str);
map.put("timestamp",timestamp);
map.put("prepayid",prepay_id);
String sign = md5Util.getSign(map);
String resultString="{\"appid\":\""+config.getAppID()+"\",\"partnerid\":\""+config.getMchID()+"\",\"package\":\"Sign=WXPay\"," +
"\"noncestr\":\""+nonce_str+"\",\"timestamp\":"+timestamp+"," +
"\"prepayid\":\""+prepay_id+"\",\"sign\":\""+sign+"\"}";
System.err.println(resultString);
return resultString; //给前端app返回此字符串,再调用前端的微信sdk引起微信支付
}
service生成预支付订单
@Override
public Map dounifiedOrder(String attach, String out_trade_no, String total_fee,String spbill_create_ip,int type) throws Exception {
Map fail = new HashMap<>();
WXMyConfigUtil config = new WXMyConfigUtil();
WXPay wxpay = new WXPay(config);
Map data = new HashMap();
// data.put("appid", config.getAppID());
// data.put("mch_id", config.getMchID());
// data.put("nonce_str",WXPayUtil.generateNonceStr());
// data.put("nonce_str","6128be982a7f40daa930025dedd1a90d");
String body="订单支付";
data.put("body", body);
data.put("out_trade_no", out_trade_no);
data.put("total_fee", total_fee);
data.put("spbill_create_ip",spbill_create_ip);
//异步通知地址(请注意必须是外网)
data.put("notify_url", "http://1y8723.51mypc.cn:21813/api/wxpay/notify");
data.put("trade_type", "APP");
data.put("attach", attach);
// data.put("sign", md5Util.getSign(data));
StringBuffer url= new StringBuffer();
try {
Map resp = wxpay.unifiedOrder(data);
System.out.println(resp);
String returnCode = resp.get("return_code"); //获取返回码
String returnMsg = resp.get("return_msg");
if("SUCCESS".equals(returnCode)){ //若返回码为SUCCESS,则会返回一个result_code,再对该result_code进行判断
String resultCode = (String)resp.get("result_code");
String errCodeDes = (String)resp.get("err_code_des");
System.out.print(errCodeDes);
if("SUCCESS".equals(resultCode)){
//获取预支付交易回话标志
Map map = new HashMap<>();
String prepay_id = resp.get("prepay_id");
String signType = "MD5";
map.put("prepay_id",prepay_id);
map.put("signType",signType);
String sign = md5Util.getSign(map);
resp.put("realsign",sign);
url.append("prepay_id="+prepay_id+"&signType="+signType+ "&sign="+sign);
return resp;
}else {
logger.info("订单号:{},错误信息:{}",out_trade_no,errCodeDes);
url.append(errCodeDes);
}
}else {
logger.info("订单号:{},错误信息:{}",out_trade_no,returnMsg);
url.append(returnMsg);
}
} catch (Exception e) {
System.out.println("aaaaaaaaaaaaa");
System.out.println(e);
logger.info(e.getMessage());
}
return fail;
}
contorller支付回调
/**
* 订单支付异步通知
*/
@ApiOperation(value = "手机订单支付完成后回调")
@RequestMapping(value = "/notify",method = {RequestMethod.GET, RequestMethod.POST})
public String WXPayBack(HttpServletRequest request,HttpServletResponse response){
String resXml="";
System.err.println("进入异步通知");
try{
//
InputStream is = request.getInputStream();
//将InputStream转换成String
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = null;
try {
while ((line = reader.readLine()) != null) {
sb.append(line + "\n");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
resXml=sb.toString();
System.err.println(resXml);
String result = wxPayService.payBack(resXml);
// return " ";
return result;
}catch (Exception e){
logger.error("手机支付回调通知失败",e);
String result = "" + " " + " " + " ";
return result;
}
}
service支付回调
/**
* 支付结果通知
* @param notifyData 异步通知后的XML数据
* @return
*/
@Override
public String payBack(String notifyData) {
WXMyConfigUtil config = null;
try {
config = new WXMyConfigUtil();
} catch (Exception e) {
e.printStackTrace();
}
WXPay wxpay = new WXPay(config);
String xmlBack="";
Map notifyMap = null;
try {
notifyMap = WXPayUtil.xmlToMap(notifyData); // 转换成map
if (wxpay.isPayResultNotifySignatureValid(notifyMap)) {
// 签名正确
// 进行处理。
// 注意特殊情况:订单已经退款,但收到了支付结果成功的通知,不应把商户侧订单状态从退款改成支付成功
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(">>>>>支付成功");
logger.info("微信手机支付回调成功订单号:{}",out_trade_no);
xmlBack = "" + " " + " " + " ";
}else {
logger.info("微信手机支付回调失败订单号:{}",out_trade_no);
xmlBack = "" + " " + " " + " ";
}
}
return xmlBack;
}
else {
// 签名错误,如果数据里没有sign字段,也认为是签名错误
logger.error("手机支付回调通知签名错误");
xmlBack = "" + " " + " " + " ";
return xmlBack;
}
} catch (Exception e) {
logger.error("手机支付回调通知失败",e);
xmlBack = "" + " " + " " + " ";
}
return xmlBack;
}
签名算法工具类
package com.hrhs.wxpay;
import com.github.wxpay.sdk.WXPayConstants;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
public class MD5Util {
public static String getSign(Map data) throws Exception {
WXMyConfigUtil config = new WXMyConfigUtil();
Set keySet = data.keySet();
String[] keyArray = keySet.toArray(new String[keySet.size()]);
Arrays.sort(keyArray);
StringBuilder sb = new StringBuilder();
for (String k : keyArray) {
if (k.equals(WXPayConstants.FIELD_SIGN)) {
continue;
}
if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
sb.append(k).append("=").append(data.get(k).trim()).append("&");
}
sb.append("key=").append(config.getKey());
/*MessageDigest md = null;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
byte[] array = new byte[0];
try {
array = md.digest(sb.toString().getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
StringBuilder sb2 = new StringBuilder();
for (byte item : array) {
sb2.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb2.toString().toUpperCase();*/
String sign = MD5Util.MD5Encode(sb.toString(), "UTF-8").toUpperCase();
return sign;
}
public static String createSign(String characterEncoding, SortedMap
微信的各种配置,一定不要写错
package com.kyd.callcenter.util.weixin;
import com.github.wxpay.sdk.WXPayConfig;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class WXMyConfigUtil implements WXPayConfig {
private byte[] certData;
public WXMyConfigUtil() throws Exception {
String certPath = "证书地址";//从微信商户平台下载的安全证书存放的目录
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 String getAppID() {
return "填写你的appid";
}
//parnerid
@Override
public String getMchID() {
return "填写商户id";
}
@Override
public String getKey() {
return "填写api密钥";
}
@Override
public InputStream getCertStream() {
ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
return certBis;
}
@Override
public int getHttpConnectTimeoutMs() {
return 8000;
}
@Override
public int getHttpReadTimeoutMs() {
return 10000;
}
}