大家好我是user_fangniu
不说概念了直接上代码,顺便说一下期间遇到的坑,由于maven依赖是直接引用远端库,HttpClient版本冲突就使用了spring内置的RestTemplate的POST方式来调起微信支付接口。将APPID、MCHID、SERCET改成自己公众号和微信支付的参数就正常可以使用了。有什么不懂得随时联系;QQ:97000937,备注来意
package com.zk.pos.pay.weixin;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.zk.pos.entity.PosTransactionCardNet;
public class PosWxPayUtils {
private static Log log = LogFactory.getLog(PosWxPayUtils.class);
public enum SignType {
MD5, HMACSHA256
}
public static final String FIELD_SIGN = "sign";
public static final String FIELD_SIGN_TYPE = "sign_type";
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final String PAYUrl = "https://api.mch.weixin.qq.com/pay/micropay";
private static final String PAYSNUrl = "https://api.mch.weixin.qq.com/pay/orderquery";
private static final String APPID = "";
private static final String MCHID = "";
private static final String SERCET = "";
private static final Random RANDOM = new SecureRandom();
/**
* 扫码支付
* @throws Exception
*/
public static boolean barCode(String code, String out_trade_no, String deviceSn, String price, PosTransactionCardNet posTransactionCardNet) throws Exception {
// TODO 微信支付sdk带的xml转换器jdk1.8使用不兼容,硬拼接xml吧
try {
String nonceStr = generateNonceStr();
String ipV4 = getLocalIpv4Address();
price = changeBranch(price);
StringBuffer strb1 = new StringBuffer();
strb1.append("<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?><xml>");
SortedMap<String, String> packageParams = new TreeMap<String, String>();
packageParams.put("appid", APPID);
strb1.append("<appid>" + APPID + "</appid>");
packageParams.put("mch_id", MCHID);
strb1.append("<mch_id>" + MCHID + "</mch_id>");
packageParams.put("attach", "卡充值");
strb1.append("<attach>" + "卡充值" + "</attach>");
packageParams.put("auth_code", code);
strb1.append("<auth_code>" + code + "</auth_code>");
packageParams.put("body", "付款码卡充值");
strb1.append("<body>" + "付款码卡充值" + "</body>");
packageParams.put("device_info", deviceSn);
strb1.append("<device_info>" + deviceSn + "</device_info>");
packageParams.put("nonce_str", nonceStr);
strb1.append("<nonce_str>" + nonceStr + "</nonce_str>");
packageParams.put("out_trade_no", out_trade_no);
strb1.append("<out_trade_no>" + out_trade_no + "</out_trade_no>");
packageParams.put("spbill_create_ip", ipV4);
strb1.append("<spbill_create_ip>" + ipV4 + "</spbill_create_ip>");
packageParams.put("total_fee", price);
strb1.append("<total_fee>" + price + "</total_fee>");
// 根据package数据生成预支付订单号的签名sign
String sign = generateSignature(packageParams, SERCET);
// 生成需要提交给统一支付接口https://api.mch.weixin.qq.com/pay/unifiedorder 的xml数据
packageParams.put("sign", sign);
strb1.append("<sign>" + sign + "</sign>");
strb1.append("</xml>");
String result = restTemplatePost(strb1.toString(), PAYUrl);
Map resultMap = xmlToMap(result);
/*System.out.println("map"+resultMap);*/
//判断支付是否成功
String return_code = resultMap.get("return_code").toString();
String result_code = resultMap.get("result_code").toString();
String err_code = resultMap.get("err_code").toString();
String err_code_des = resultMap.get("err_code_des").toString();
if("SUCCESS".equals(return_code) && "SUCCESS".equals(result_code)){
posTransactionCardNet.setTransactionId(resultMap.get("transaction_id").toString());
log.info("微信免密支付成功!");
/*System.out.println("微信免密支付成功!");*/
return true;
} else if ("USERPAYING".equals(err_code)){
//采用轮询的方式去调取,
for(int i = 0; i < 4; i++){
Thread.sleep(3000);
//手动拼装需要查询订单的接口的参数xml
String nonceStr2 = generateNonceStr();
StringBuffer strb2 = new StringBuffer();
strb2.append("<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?><xml>");
strb2.append("<appid>" + APPID + "</appid>");
strb2.append("<mch_id>" + MCHID + "</mch_id>");
strb2.append("<nonce_str>" + nonceStr2 + "</nonce_str>");
//微信订单号和商户订单号2选1传,这里选择传入商户订单号
strb2.append("<out_trade_no>" + out_trade_no + "</out_trade_no>");
SortedMap<String, String> packageParams2 = new TreeMap<String, String>();
packageParams2.put("appid", APPID);
packageParams2.put("mch_id", MCHID);
packageParams2.put("nonce_str", nonceStr2);
packageParams2.put("out_trade_no", out_trade_no);
//验签
String sign2 = generateSignature(packageParams2, SERCET);
packageParams2.put("sign", sign2);
strb2.append("<sign>" + sign2 + "</sign>");
strb2.append("</xml>");
String result2 = restTemplatePost(strb2.toString(), PAYSNUrl);
System.out.println("result2"+result2);
Map resultMap2 = xmlToMap(result2);
String trade_state = resultMap2.get("trade_state").toString();
if("SUCCESS".equals(trade_state)){
/*System.out.println("微信免密支付成功!");*/
log.info("微信免密支付成功!");
return true;
}
log.info("正在支付" + out_trade_no);
}
}
/*System.out.println("微信支付失败,"+err_code_des);*/
log.error("微信支付失败,"+err_code_des);
return false;
}catch(Exception e) {
e.printStackTrace();
return false;
}
}
public static String getLocalIpv4Address() throws SocketException {
Enumeration allNetInterfaces = null;
try {
allNetInterfaces = NetworkInterface.getNetworkInterfaces();
} catch (java.net.SocketException e) {
e.printStackTrace();
}
InetAddress ip = null;
while (allNetInterfaces.hasMoreElements())
{
NetworkInterface netInterface = (NetworkInterface) allNetInterfaces
.nextElement();
Enumeration addresses = netInterface.getInetAddresses();
while (addresses.hasMoreElements())
{
ip = (InetAddress) addresses.nextElement();
if (ip != null && ip instanceof Inet4Address)
{
if(ip.getHostAddress().equals("127.0.0.1")){
continue;
}
/*System.out.println("/u672c/u673a/u7684IP = " + ip.getHostAddress());*/
return ip.getHostAddress();
}
}
}
System.out.println("网络无连接!");
return null;
}
/**
* XML格式字符串转换为Map
*
* @param strXML XML字符串
* @return XML数据转换后的Map
* @throws Exception
*/
public static Map<String, String> xmlToMap(String strXML) throws Exception {
try {
Map<String, String> data = new HashMap<String, String>();
DocumentBuilder documentBuilder = newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
// do nothing
}
return data;
} catch (Exception ex) {
throw ex;
}
}
public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
documentBuilderFactory.setXIncludeAware(false);
documentBuilderFactory.setExpandEntityReferences(false);
return documentBuilderFactory.newDocumentBuilder();
}
/**
* 生成签名
*
* @param data 待签名数据
* @param key API密钥
* @return 签名
*/
public static String generateSignature(final Map<String, String> data, String key) throws Exception {
return generateSignature(data, key, SignType.MD5);
}
/**
* 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
*
* @param data 待签名数据
* @param key API密钥
* @param signType 签名方式
* @return 签名
*/
public static String generateSignature(final Map<String, String> data, String key, SignType signType) throws Exception {
Set<String> 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(FIELD_SIGN)) {
continue;
}
if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
sb.append(k).append("=").append(data.get(k).trim()).append("&");
}
sb.append("key=").append(key);
if (SignType.MD5.equals(signType)) {
return MD5(sb.toString()).toUpperCase();
}
else if (SignType.HMACSHA256.equals(signType)) {
return HMACSHA256(sb.toString(), key);
}
else {
throw new Exception(String.format("Invalid sign_type: %s", signType));
}
}
/**
* 生成 MD5
*
* @param data 待处理数据
* @return MD5结果
*/
public static String MD5(String data) throws Exception {
java.security.MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* 生成 HMACSHA256
* @param data 待处理数据
* @param key 密钥
* @return 加密结果
* @throws Exception
*/
public static String HMACSHA256(String data, String key) throws Exception {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* 获取随机字符串 Nonce Str
*
* @return String 随机字符串
*/
public static String generateNonceStr() {
char[] nonceChars = new char[32];
for (int index = 0; index < nonceChars.length; ++index) {
nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
}
return new String(nonceChars);
}
/**
* spring内置的RestTemplate去请求微信客户端
* @return
*/
public static String restTemplatePost(String str, String url) {
//这里更改成spring内置的RestTemplate去请求微信客户端
RestTemplate restTemplate = new RestTemplate();
//指定字符编码为UTF-8,原编码为ISO-8859-1,这个必须加
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
HttpHeaders headers = new HttpHeaders();
MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
headers.setContentType(type);
headers.add("Accept", MediaType.APPLICATION_JSON.toString());
HttpEntity<String> formEntity = new HttpEntity<String>(str, headers);
String result = restTemplate.postForObject(url, formEntity, String.class);
return result;
}
/**
* 将元为单位的转换为分 替换小数点,支持以逗号区分的金额
*
* @param amount
* @return
*/
public static String changeBranch(String amount) {
String currency = amount.replaceAll("\\$|\\¥|\\,", ""); // 处理包含, ¥
// 或者$的金额
int index = currency.indexOf(".");
int length = currency.length();
Long amLong;
if (index == -1) {
amLong = Long.valueOf(currency + "00");
} else if (length - index >= 3) {
amLong = Long.valueOf((currency.substring(0, index + 3)).replace(".", ""));
} else if (length - index == 2) {
amLong = Long.valueOf((currency.substring(0, index + 2)).replace(".", "") + 0);
} else {
amLong = Long.valueOf((currency.substring(0, index + 1)).replace(".", "") + "00");
}
return amLong.toString();
}
}