/** 发起微信支付 */
public Map wechatPay(String openid, HttpServletRequest request, String outTradeNo, Integer totalFee) {
//生成的随机32位字符串
String nonce_str = RandomString.generateRandomString(32, RandomString.POSSIBLE_CHARS);
//商品金额 单位分 将原来的金额*100 元->分
Integer total_fee = (totalFee);
//商品名称
String packageName = "杭家订单:" + outTradeNo;
//获取客户端的ip地址
String spbill_create_ip = getIpAddress(request);
//生成商户订单号
String out_trade_no = outTradeNo;
//组装参数,用户生成统一下单接口的签名
Map packageParams = new HashMap();
packageParams.put("appid", weChatConfig.appId);
packageParams.put("mch_id", weChatConfig.mchId);
packageParams.put("nonce_str", nonce_str);
packageParams.put("body", packageName);
packageParams.put("out_trade_no", out_trade_no);//商户订单号
packageParams.put("total_fee", String.valueOf(total_fee));//支付金额,这边需要转成字符串类型,否则后面的签名会失败
packageParams.put("spbill_create_ip", spbill_create_ip);
packageParams.put("notify_url", weChatConfig.notifyUrl);//支付成功后的回调地址
packageParams.put("trade_type", weChatConfig.tradetype);//支付方式
packageParams.put("openid", openid);
String prestr = PayUtil.createLinkString(packageParams); // 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
//MD5运算生成签名,这里是第一次签名,用于调用统一下单接口
String mysign = PayUtil.sign(prestr, weChatConfig.key, "utf-8").toUpperCase();
//拼接统一下单接口使用的xml数据,要将上一步生成的签名一起拼接进去
String xml = "" + "" + weChatConfig.appId + " "
+ ""
+ "" + weChatConfig.mchId + " "
+ "" + nonce_str + " "
+ "" + weChatConfig.notifyUrl + " "
+ "" + openid + " "
+ "" + out_trade_no + " "
+ "" + spbill_create_ip + " "
+ "" + total_fee + " "
+ "" + weChatConfig.tradetype + " "
+ "" + mysign + " "
+ " ";
//调用统一下单接口,并接受返回的结果
String result = PayUtil.httpRequest(weChatConfig.payUrl, "POST", xml);
System.out.println("调试模式 返回XML数据:" + result);
// 将解析结果存储在HashMap中
Map map = PayUtil.doXMLParse(result);
String return_code = (String) map.get("return_code");//返回状态码
String return_msg = (String) map.get("return_msg");//返回状态信息
//返回给移动端需要的参数
Map response = new HashMap<>();
if ("SUCCESS".equals(return_code)) {
// 业务结果
String prepay_id = (String) map.get("prepay_id");//返回的预付单信息
String sign = (String) map.get("sign");//微信返回的签名值
response.put("sign", sign);
response.put("signType", weChatConfig.signtype);
response.put("nonceStr", nonce_str);
response.put("package", "prepay_id=" + prepay_id);
Long timeStamp = System.currentTimeMillis() / 1000;
response.put("timeStamp", timeStamp + "");//这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误
String stringSignTemp = "appId=" + weChatConfig.appId + "&nonceStr=" + nonce_str + "&package=prepay_id=" + prepay_id + "&signType=" + weChatConfig.signtype + "&timeStamp=" + timeStamp;
//再次签名,这个签名用于小程序端调用wx.requesetPayment方法
String paySign = PayUtil.sign(stringSignTemp, weChatConfig.key, "utf-8").toUpperCase();
log.info("=======================第二次签名:" + paySign + "=====================");
response.put("paySign", paySign);
response.put("totalFee",total_fee);
/**业务代码*/
} else {
throw new BaseException(ResultEnum.WECHAT_PAY_ERROR, return_msg);
}
response.put("appid", weChatConfig.appId);
return response;
}
/** 支付回调 */
@Override
@Transactional
public String wechatPayCallBack(HttpServletRequest request, HttpServletResponse response) {
try {
String line = null;
BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream) request.getInputStream()));
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null) {
sb.append(line);
}
br.close();
//sb为微信返回的xml
String notityXml = sb.toString();
String resXml = "";
log.info("接收到的报文:" + notityXml);
Map map = PayUtil.doXMLParse(notityXml);
String returnCode = (String) map.get("return_code");
if ("SUCCESS".equals(returnCode)) {
//验证签名是否正确
Map validParams = PayUtil.paraFilter(map); //回调验签时需要去除sign和空值参数
String validStr = PayUtil.createLinkString(validParams);//把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
String sign = PayUtil.sign(validStr, weChatConfig.key, "utf-8").toUpperCase();//拼装生成服务器端验证的签名
//根据微信官网的介绍,此处不仅对回调的参数进行验签,还需要对返回的金额与系统订单的金额进行比对等
if (sign.equals(map.get("sign"))) {
log.info("sign == sigin 签名通过");
/**此处添加自己的业务逻辑代码start*/
String outTradeNo = (String) map.get("out_trade_no");
String transactionId = (String) map.get("transaction_id");
String ip = getIpAddress(request);
// 支付回调处理
Boolean check = orderService.wechatPayCallBack(outTradeNo,transactionId,ip);
if(check){
// 通知微信服务器已经支付成功
resXml = "" + " "
+ " " + " ";
} else {
// 通知微信服务器已经支付失败
resXml = "" + " "
+ " " + " ";
}
/**此处添加自己的业务逻辑代码end**/
}
} else {
resXml = "" + " "
+ " " + " ";
}
log.info(resXml);
log.info("微信支付回调数据结束");
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
return resXml;
} catch (Exception e) {
return null;
}
}
/** 微信退款 */
@Override
public Map wechatRefund(int refundMoney, int totalMoney, String out_trade_no) {
//生成的随机32位字符串
String nonce_str = RandomString.generateRandomString(32, RandomString.POSSIBLE_CHARS);
String out_refund_no = SnowflakeIdWorker.getId();//商户退款单号
//签名算法
SortedMap
微信退款是需要证书的,怎么获取证书,微信支付文档比任何人说得都详细
/** 微信退款--向微信端发送post请求 */
public static String WeixinSendPostToRefund(String url,String xmlObj,String mch_id) throws Exception{
ClassPathResource classPathResource = new ClassPathResource("apiclient_cert.p12");//证书路径(此处我用的相对路径,你也可以考虑安全性,放在项目外)
InputStream instream = classPathResource.getInputStream();
KeyStore keyStore = KeyStore.getInstance("PKCS12");
try {
keyStore.load(instream, mch_id.toCharArray());
} catch (Exception e) {
e.printStackTrace();
} finally {
instream.close();
}
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mch_id.toCharArray()).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
String result = "";
try {
HttpPost httpPost = new HttpPost(url);
HttpEntity xmlData = new StringEntity((String) xmlObj, "text/xml", "iso-8859-1");
httpPost.setEntity(xmlData);
CloseableHttpResponse response = httpclient.execute(httpPost);
try {
HttpEntity entity = response.getEntity();
result = EntityUtils.toString(entity, "UTF-8");
System.out.println(response.getStatusLine());
EntityUtils.consume(entity);
} finally {
response.close();
}
} finally {
httpclient.close();
}
//去除空格
return result.replaceAll(" ", "");
}
生成随机字符串:你可以用微信提供的javaSDK,其中有这个,而且map转xml和xml转map等都有
商户订单号:我用的SnowflakeIdWorker,这个随你,只要唯一就行了
sign签名:
/**
* 微信退款--sign签名
*/
public static String createSign(String secretKey, SortedMap parameters) {
StringBuffer sb = new StringBuffer();
Set> es = parameters.entrySet();
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=" + secretKey);
String sign = MD5Utils.MD5(sb.toString()).toUpperCase();
parameters.put("sign", sign);
return sign;
}
/** 微信退款回调 */
@Override
@Transactional(rollbackFor = Exception.class)
public String wechatRefundCallback(HttpServletRequest request, HttpServletResponse response) {
log.info("微信回调开始啦!!!!");
try {
BufferedReader br = new BufferedReader(new InputStreamReader((ServletInputStream) request.getInputStream()));
String line = null;
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null) {
sb.append(line);
}
br.close();
//sb为微信返回的xml
Map resultMap = PayUtil.doXMLParse(sb.toString());
if ("SUCCESS".equals(resultMap.get("return_code"))){//通信成功
String req_info = String.valueOf(resultMap.get("req_info"));
String resultXml = AESUtil.decryptData(req_info,weChatConfig.key);
Map reqInfoMap = PayUtil.doXMLParse(resultXml);
//code....
String resultStr = " ";
return resultStr;
}
}catch (Exception e){
e.printStackTrace();
}
throw new RuntimeException("微信回调失败");
}
AESUtil
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class AESUtil {
/** 密钥算法 */
private static final String ALGORITHM = "AES";
/** 加解密算法/工作模式/填充方式 */
private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS5Padding";
/** AES加密 */
public static String encryptData(String data,String password) throws Exception {
// 创建密码器
Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING);
SecretKeySpec key = new SecretKeySpec(MD5Utils.MD5(password).toLowerCase().getBytes(), ALGORITHM);
// 初始化
cipher.init(Cipher.ENCRYPT_MODE, key);
return Base64Util.encode(cipher.doFinal(data.getBytes()));
}
/** AES解密 */
public static String decryptData(String base64Data,String password) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING);
SecretKeySpec key = new SecretKeySpec(MD5Utils.MD5(password).toLowerCase().getBytes(), ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decode = Base64Util.decode(base64Data);
byte[] doFinal = cipher.doFinal(decode);
return new String(doFinal,"utf-8");
}
}
Base64Util
import java.util.Base64;
public class Base64Util {
/** 解码 */
public static byte[] decode(String encodedText){
final Base64.Decoder decoder = Base64.getDecoder();
return decoder.decode(encodedText);
}
/** 编码 */
public static String encode(byte[] data){
final Base64.Encoder encoder = Base64.getEncoder();
return encoder.encodeToString(data);
}
}
MD5Utils
/** 普通MD5 */
public static String MD5(String input) {
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
return "check jdk";
} catch (Exception e) {
e.printStackTrace();
return "";
}
char[] charArray = input.toCharArray();
byte[] byteArray = new byte[charArray.length];
for (int i = 0; i < charArray.length; i++) {
byteArray[i] = (byte) charArray[i];
}
byte[] md5Bytes = md5.digest(byteArray);
StringBuffer hexValue = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++) {
int val = ((int) md5Bytes[i]) & 0xff;
if (val < 16) {
hexValue.append("0");
}
hexValue.append(Integer.toHexString(val));
}
return hexValue.toString();
}
ps:很多教程说要替换jre的两个jar包,我这里没照这一步,在AES解密的时候
SecretKeySpec key = new SecretKeySpec(MD5Utils.MD5(password).toLowerCase().getBytes(), ALGORITHM);
这步将商户key加密串转成小写了,这样就解决了"JAVA运行环境默认不允许256位密钥的AES加解密""的问题
有啥不懂得,或者我贴的代码有啥缺漏的,可以留言
参考:https://blog.csdn.net/zhangxing52077/article/details/80269999