微信支付功能做了太多,今天又做了支付、退款、查询、提现等等,顺便把支付和退款代码贴出来,希望对初学者有点帮助。
首先调用微信支付退款 API 地址
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4
案例代码
支付退款工具类
用于支付参数中的 nonce_str 字段赋值
/**
* 生产32位随机数
**/
public static String getUUID(){
UUID uuid = UUID.randomUUID();
return uuid.toString().replace("-","");
}
用于支付参数中的 spbill_create_ip 字段赋值
/**
* 获取服务器ip
**/
public static String getHostIp(){
try{
Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
while (allNetInterfaces.hasMoreElements()){
NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement();
Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
while (addresses.hasMoreElements()){
InetAddress ip = (InetAddress) addresses.nextElement();
if (ip != null
&& ip instanceof Inet4Address
&& !ip.isLoopbackAddress() //loopback地址即本机地址,IPv4的loopback范围是127.0.0.0 ~ 127.255.255.255
&& ip.getHostAddress().indexOf(":")==-1){
return ip.getHostAddress();
}
}
}
}catch(Exception e){
e.printStackTrace();
}
return null;
}
用于生成支付签名 sign字段赋值
private String getSign(SortedMap<Object, Object> map,String appKey,String type){
String signPayTmp = Common.Map2String(map) + "key=" + appKey;
log.info("微信" + type +"接口签名参数:sign:" + signPayTmp);
String signPay = MD5Util.MD5Encode(signPayTmp, "UTF-8").toUpperCase();
return signPay;
}
/**
* Map 转 String
**/
public static String Map2String(Map<Object,Object> map){
StringBuffer sb = new StringBuffer();
Iterator<Map.Entry<Object, Object>> it = map.entrySet().iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
String v = (String)entry.getValue();
if(null != v && !"".equals(v)
&& !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
return sb.toString();
}
用于支付退款金额转换成整数(微信要求)
public static String getMoney(String amount) {
if(amount==null){
return "";
}
// 金额转化为分为单位
// 处理包含, ¥ 或者$的金额
String currency = amount.replaceAll("\\$|\\¥|\\,", "");
int index = currency.indexOf(".");
int length = currency.length();
Long amLong = 0l;
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();
}
创建一个MD5Util 类:
public class MD5Util {
public static String encryptMd5(String string) throws UnsupportedEncodingException {
return encryptMd5(string, "UTF-8");
}
public static String encryptMd5(String string, String charSet) throws UnsupportedEncodingException {
return DigestUtils.md5Hex(string.getBytes(charSet));
}
private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++)
resultSb.append(byteToHexString(b[i]));
return resultSb.toString();
}
private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
n += 256;
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
public static String MD5Encode(String origin, String charsetname) {
String resultString = null;
try {
resultString = new String(origin);
MessageDigest md = MessageDigest.getInstance("MD5");
if (charsetname == null || "".equals(charsetname))
resultString = byteArrayToHexString(md.digest(resultString
.getBytes()));
else
resultString = byteArrayToHexString(md.digest(resultString
.getBytes(charsetname)));
} catch (Exception exception) {
}
return resultString;
}
private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
}
创建一个 SnowflakeIdUtil 类(雪花算法):
public class SnowflakeIdUtil {
// ==============================Fields===========================================
/** 开始时间截 (2015-01-01) */
private static final long twepoch = 1420041600000L;
/** 机器id所占的位数 */
private static final long workerIdBits = 5L;
/** 数据标识id所占的位数 */
private static final long datacenterIdBits = 5L;
/** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
private static final long maxWorkerId = -1L ^ (-1L << workerIdBits);
/** 支持的最大数据标识id,结果是31 */
private static final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
/** 序列在id中占的位数 */
private static final long sequenceBits = 12L;
/** 机器ID向左移12位 */
private static final long workerIdShift = sequenceBits;
/** 数据标识id向左移17位(12+5) */
private static final long datacenterIdShift = sequenceBits + workerIdBits;
/** 时间截向左移22位(5+5+12) */
private static final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
/** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
private static final long sequenceMask = -1L ^ (-1L << sequenceBits);
/** 工作机器ID(0~31) */
private static long workerId;
/** 数据中心ID(0~31) */
private static long datacenterId;
/** 毫秒内序列(0~4095) */
private static long sequence = 0L;
/** 上次生成ID的时间截 */
private static long lastTimestamp = -1L;
private static SnowflakeIdUtil idWorker;
/**
* 以SnowFlake算法,获取唯一有序id
* @return
*/
public static long getSnowflakeId() {
if(idWorker == null) {
synchronized (SnowflakeIdUtil.class) {
if(idWorker == null) {
idWorker = new SnowflakeIdUtil(0, 0);
}
}
}
return idWorker.nextId();
}
// ==============================Methods==========================================
private SnowflakeIdUtil() {
}
//==============================Constructors=====================================
/**
* 构造函数
* @param workerId 工作ID (0~31)
* @param datacenterId 数据中心ID (0~31)
*/
public SnowflakeIdUtil(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
/**
* 阻塞到下一个毫秒,直到获得新的时间戳
* @param lastTimestamp 上次生成ID的时间截
* @return 当前时间戳
*/
public static long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
/**
* 返回以毫秒为单位的当前时间
* @return 当前时间(毫秒)
*/
public static long timeGen() {
return System.currentTimeMillis();
}
/**
* 获得下一个ID (该方法是线程安全的)
* @return SnowflakeId
*/
public static synchronized long nextId() {
long timestamp = timeGen();
//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
if (timestamp < lastTimestamp) {
throw new RuntimeException(
String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
//如果是同一时间生成的,则进行毫秒内序列
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
//毫秒内序列溢出
if (sequence == 0) {
//阻塞到下一个毫秒,获得新的时间戳
timestamp = tilNextMillis(lastTimestamp);
}
}
//时间戳改变,毫秒内序列重置
else {
sequence = 0L;
}
//上次生成ID的时间截
lastTimestamp = timestamp;
//移位并通过或运算拼到一起组成64位的ID
return ((timestamp - twepoch) << timestampLeftShift) //
| (datacenterId << datacenterIdShift) //
| (workerId << workerIdShift) //
| sequence;
}
}
创建一个 XMLUtil 类:
public class XMLUtil {
private static Logger log = LoggerFactory.getLogger(XMLUtil.class);
private static final String PREFIX_XML = "<xml>";
private static final String SUFFIX_XML = "</xml>";
private static final String PREFIX_CDATA = "<![CDATA[";
private static final String SUFFIX_CDATA = "]]>";
/**
* 转化成xml, 单层无嵌套
* @param parm
* @param isAddCDATA
* @return
*/
public static String mapToXml(Map<Object, Object> parm, boolean isAddCDATA) {
StringBuffer strbuff = new StringBuffer(PREFIX_XML);
if (null != parm) {
for (Entry<Object, Object> entry : parm.entrySet()) {
strbuff.append("<").append(entry.getKey()).append(">");
if (isAddCDATA) {
if(!"sign".equals(entry.getKey())) {
strbuff.append(PREFIX_CDATA);
if (null != entry.getValue()) {
strbuff.append(entry.getValue());
}
strbuff.append(SUFFIX_CDATA);
}else {
if (null != entry.getValue()) {
strbuff.append(entry.getValue());
}
}
} else {
if (null != entry.getValue()) {
strbuff.append(entry.getValue());
}
}
strbuff.append("</").append(entry.getKey()).append(">");
}
}
return strbuff.append(SUFFIX_XML).toString();
}
/**
* xml转化成map
* @param xmlStr
* @return
*/
public static Map<String,String> XmlToMap(String xmlStr){
Map<String,String> map = XmlUtil.of(xmlStr).toMap();
return map;
}
/**
* json 字符串转化成xml
* @param params
* @return
*/
public static String JsonToMap(String params){
Map maps = (Map)JSON.parse(params);
Map<Object, Object> sortMap = new TreeMap<Object, Object>();
sortMap.putAll(maps);
return XMLUtil.mapToXml(sortMap,true);
}
/**
* 获取xml节点文本内容
* @param nodeName
* @return
*/
public static String getXmlNodeValue(String xmlStr, String nodeName) {
Map<String,String> map = XmlToMap(xmlStr);
if(map != null && map.size() > 0){
return (String)map.get(nodeName);
}else{
return null;
}
}
public static String parseXml2Str(HttpServletRequest request) {
DataInputStream in;
String wxNotifyXml = "";
try {
InputStream stream = request.getInputStream();
in = new DataInputStream(stream);
String str = IOUtils.toString(stream, "UTF-8");
log.info("******************str="+str + "======contentLength="+request.getContentLength());
byte[] dataOrigin = new byte[request.getContentLength()];
in.readFully(dataOrigin); // 根据长度,将消息实体的内容读入字节数组dataOrigin中
if(null != in) in.close(); // 关闭数据流
wxNotifyXml = new String(dataOrigin); // 从字节数组中得到表示实体的字符串
} catch (IOException e) {
e.printStackTrace();
}
return wxNotifyXml;
}
/**
* 解析微信发来的请求(XML)
*
* @param request
* @return
* @throws Exception
*/
@SuppressWarnings("unchecked")
public static Map<String, String> parseXml4Map(HttpServletRequest request) throws Exception {
// 将解析结果存储在HashMap中
Map<String, String> map = new HashMap<String, String>();
// 从request中取得输入流
InputStream inputStream = request.getInputStream();
// String str = IOUtils.toString(inputStream, "utf-8");
// log.info("******************str=" + str);
// 读取输入流
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
// 得到xml根元素
Element root = document.getRootElement();
// 得到根元素的所有子节点
List<Element> elementList = root.elements();
// 遍历所有子节点
for (Element e : elementList)
map.put(e.getName(), e.getText());
// 释放资源
inputStream.close();
inputStream = null;
return map;
}
}
创建一个 HttpUtil 类:
public class HttpUtil {
private static Logger log = LoggerFactory.getLogger(HttpUtil.class);
/**
* http POST 请求
* @param url:请求地址
* @param body: body实体字符串
* @param sign:签名
* @param sn: 序列号
* @return
*/
public static String httpPost(String url, String body,String sign,String sn) throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
String xmlRes = "{}";
HttpClient client = createSSLClientDefault();
HttpPost httpost = new HttpPost(url);
try {
log.debug("Request string: " + body);
//所有请求的body都需采用UTF-8编码
StringEntity entity = new StringEntity(body,"UTF-8");
entity.setContentType("application/json");
httpost.setEntity(entity);
//支付平台所有的API仅支持JSON格式的请求调用,HTTP请求头Content-Type设为application/json
httpost.addHeader("Content-Type","application/json");
//支付平台所有的API调用都需要签名验证,签名首部: Authorization: sn + " " + sign
httpost.addHeader("Authorization",sn + " " + sign);
HttpResponse response = client.execute(httpost);
//所有响应也采用UTF-8编码
String result = EntityUtils.toString(response.getEntity(), "UTF-8");
xmlRes = result;
log.debug("Response string: " + xmlRes);
} catch (ClientProtocolException e) {
log.error("",e);
}catch (UnknownHostException e){
log.error("",e);
}
catch (IOException e) {
log.error("",e);
}
return xmlRes;
}
public static CloseableHttpClient createSSLClientDefault() {
try {
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
//信任所有
public boolean isTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
return true;
}
}).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
return HttpClients.custom().setSSLSocketFactory(sslsf).build();
} catch (KeyManagementException e) {
log.error("", e);
} catch (NoSuchAlgorithmException e) {
log.error("", e);
} catch (KeyStoreException e) {
log.error("", e);
}
return HttpClients.createDefault();
}
/**
* 创建带证书的实例
* @param certPath
* @return
*/
public static CloseableHttpClient createSSLClientCert(InputStream certPath,String password) {
try {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(certPath,password.toCharArray());
certPath.close();
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
//信任所有
public boolean isTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
return true;
}
}).loadKeyMaterial(keyStore, password.toCharArray()).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,new String[]{"TLSv1","TLSv1.2"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
return HttpClients.custom().setSSLSocketFactory(sslsf).build();
} catch (KeyManagementException e) {
log.error("", e);
} catch (NoSuchAlgorithmException e) {
log.error("", e);
} catch (KeyStoreException e) {
log.error("", e);
}catch (FileNotFoundException e){
log.error("", e);
}catch (Exception e){
log.error("", e);
}
return HttpClients.createDefault();
}
public static String doGet(String url,String parameter)
{
String uriAPI =url+"?"+parameter ;
String result= "";
HttpClient client = createSSLClientDefault();
HttpGet httpRequst = new HttpGet(uriAPI);
try {
HttpResponse httpResponse = client.execute(httpRequst);//其中HttpGet是HttpUriRequst的子类
if(httpResponse.getStatusLine().getStatusCode() == 200)
{
HttpEntity httpEntity = httpResponse.getEntity();
result = EntityUtils.toString(httpEntity);//取出应答字符串
// 一般来说都要删除多余的字符
result.replaceAll("\r", "");//去掉返回结果中的"\r"字符,否则会在结果字符串后面显示一个小方格
}
else
httpRequst.abort();
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
result = e.getMessage().toString();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
result = e.getMessage().toString();
}
return result;
}
public static String doGet(String url)
{
String result= "";
HttpClient client = createSSLClientDefault();
HttpGet httpRequst = new HttpGet(url);
try {
HttpResponse httpResponse = client.execute(httpRequst);//其中HttpGet是HttpUriRequst的子类
if(httpResponse.getStatusLine().getStatusCode() == 200)
{
HttpEntity httpEntity = httpResponse.getEntity();
result = EntityUtils.toString(httpEntity);//取出应答字符串
// 一般来说都要删除多余的字符
result.replaceAll("\r", "");//去掉返回结果中的"\r"字符,否则会在结果字符串后面显示一个小方格
}
else
httpRequst.abort();
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
result = e.getMessage().toString();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
result = e.getMessage().toString();
}
return result;
}
/**
* http POST 请求
* @param url:请求地址
* @param body: body实体字符串
* @return
*/
public static String httpPost(String url, String body){
String xmlRes = "{}";
HttpClient client = createSSLClientDefault();
HttpPost httpost = new HttpPost(url);
try {
//所有请求的body都需采用UTF-8编码
StringEntity entity = new StringEntity(body,"UTF-8");
httpost.setEntity(entity);
//支付平台所有的API仅支持JSON格式的请求调用,HTTP请求头Content-Type设为application/json
httpost.addHeader("Content-Type","text/xml");
HttpResponse response = client.execute(httpost);
//所有响应也采用UTF-8编码
String result = EntityUtils.toString(response.getEntity(), "UTF-8");
xmlRes = result;
} catch (ClientProtocolException e) {
log.error("",e);
}catch (UnknownHostException e){
log.error("",e);
}
catch (IOException e) {
log.error("",e);
}
return xmlRes;
}
/**
* http POST 请求
* @param url:请求地址
* @param body: body实体字符串
* @param certPath: 证书路径
* @param password: 证书密码
* @return
*/
public static String httpPostReflect(String url, String body, InputStream certPath, String password){
String xmlRes = "{}";
HttpClient client = createSSLClientCert(certPath,password);
HttpPost httpost = new HttpPost(url);
try {
//所有请求的body都需采用UTF-8编码
StringEntity entity = new StringEntity(body,"UTF-8");
httpost.setEntity(entity);
//支付平台所有的API仅支持JSON格式的请求调用,HTTP请求头Content-Type设为application/json
httpost.addHeader("Content-Type","text/xml");
HttpResponse response = client.execute(httpost);
//所有响应也采用UTF-8编码
String result = EntityUtils.toString(response.getEntity(), "UTF-8");
xmlRes = result;
} catch (ClientProtocolException e) {
log.error("",e);
}catch (UnknownHostException e){
log.error("",e);
}
catch (IOException e) {
log.error("",e);
}
return xmlRes;
}
}
创建一个 XMLUtil 类:
public class XMLUtil {
private static Logger log = LoggerFactory.getLogger(XMLUtil.class);
private static final String PREFIX_XML = "<xml>";
private static final String SUFFIX_XML = "</xml>";
private static final String PREFIX_CDATA = "<![CDATA[";
private static final String SUFFIX_CDATA = "]]>";
/**
* 转化成xml, 单层无嵌套
* @param parm
* @param isAddCDATA
* @return
*/
public static String mapToXml(Map<Object, Object> parm, boolean isAddCDATA) {
StringBuffer strbuff = new StringBuffer(PREFIX_XML);
if (null != parm) {
for (Entry<Object, Object> entry : parm.entrySet()) {
strbuff.append("<").append(entry.getKey()).append(">");
if (isAddCDATA) {
if(!"sign".equals(entry.getKey())) {
strbuff.append(PREFIX_CDATA);
if (null != entry.getValue()) {
strbuff.append(entry.getValue());
}
strbuff.append(SUFFIX_CDATA);
}else {
if (null != entry.getValue()) {
strbuff.append(entry.getValue());
}
}
} else {
if (null != entry.getValue()) {
strbuff.append(entry.getValue());
}
}
strbuff.append("</").append(entry.getKey()).append(">");
}
}
return strbuff.append(SUFFIX_XML).toString();
}
/**
* xml转化成map
* @param xmlStr
* @return
*/
public static Map<String,String> XmlToMap(String xmlStr){
Map<String,String> map = XmlUtil.of(xmlStr).toMap();
return map;
}
/**
* json 字符串转化成xml
* @param params
* @return
*/
public static String JsonToMap(String params){
Map maps = (Map)JSON.parse(params);
Map<Object, Object> sortMap = new TreeMap<Object, Object>();
sortMap.putAll(maps);
return XMLUtil.mapToXml(sortMap,true);
}
/**
* 获取xml节点文本内容
* @param nodeName
* @return
*/
public static String getXmlNodeValue(String xmlStr, String nodeName) {
Map<String,String> map = XmlToMap(xmlStr);
if(map != null && map.size() > 0){
return (String)map.get(nodeName);
}else{
return null;
}
}
public static String parseXml2Str(HttpServletRequest request) {
DataInputStream in;
String wxNotifyXml = "";
try {
InputStream stream = request.getInputStream();
in = new DataInputStream(stream);
String str = IOUtils.toString(stream, "UTF-8");
log.info("******************str="+str + "======contentLength="+request.getContentLength());
byte[] dataOrigin = new byte[request.getContentLength()];
in.readFully(dataOrigin); // 根据长度,将消息实体的内容读入字节数组dataOrigin中
if(null != in) in.close(); // 关闭数据流
wxNotifyXml = new String(dataOrigin); // 从字节数组中得到表示实体的字符串
} catch (IOException e) {
e.printStackTrace();
}
return wxNotifyXml;
}
/**
* 解析微信发来的请求(XML)
*
* @param request
* @return
* @throws Exception
*/
@SuppressWarnings("unchecked")
public static Map<String, String> parseXml4Map(HttpServletRequest request) throws Exception {
// 将解析结果存储在HashMap中
Map<String, String> map = new HashMap<String, String>();
// 从request中取得输入流
InputStream inputStream = request.getInputStream();
// String str = IOUtils.toString(inputStream, "utf-8");
// log.info("******************str=" + str);
// 读取输入流
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
// 得到xml根元素
Element root = document.getRootElement();
// 得到根元素的所有子节点
List<Element> elementList = root.elements();
// 遍历所有子节点
for (Element e : elementList)
map.put(e.getName(), e.getText());
// 释放资源
inputStream.close();
inputStream = null;
return map;
}
}
微信统一下单接口返回结果对象:
public class WxPayParam {
/**
* appID
*/
@ApiModelProperty(value = "appID")
private String appId;
/**
* 时间戳
*/
@ApiModelProperty(value = "时间戳")
private String timeStamp;
/**
* 随机字符串
*/
@ApiModelProperty(value = "随机字符串")
private String nonceStr;
/**
* 统一下单接口返回的 prepay_id 参数值
*/
@ApiModelProperty(value = "统一下单接口返回的 prepay_id 参数值")
private String prepayID;
/**
* 支付签名
*/
@ApiModelProperty(value = "支付签名")
private String paySign;
/**
* 签名类型,默认为MD5
*/
@ApiModelProperty(value = "签名类型,默认为MD5")
private String signType;
/**
* 请求响应编码
*/
@ApiModelProperty(value = "请求响应编码")
private String resultStr;
/**
* 请求响应编码
*/
@ApiModelProperty(value = "用于标注openId是否为空")
private String message;
/**
* 请求响应编码
*/
@ApiModelProperty(value = "用于存储支付前的流水id")
private String thirdPayId;
/**
* 请求响应编码
*/
@ApiModelProperty(value = "用于存储平台业务流水号(订单流水)")
private String serialNo;
private String packageValue;
}
支付下单代码
public R pay(){
//计算sign签名并用MD5加密生成支付签名
String nonceStr = Common.getUUID();
SortedMap<Object,Object> map = new TreeMap<Object,Object>();
map.put("appid",appId);//公众账号ID
//map.put("attach",bladeTenantId);
map.put("attach",flag);//附加数据
map.put("body",description);//商品描述
map.put("mch_id",mchId);//商户号
map.put("nonce_str",nonceStr);//随机字符串
map.put("notify_url",notifyUrl);//通知地址
map.put("openid",openId);//用户标识
map.put("out_trade_no",32yu34b34n); //商户订单号
map.put("spbill_create_ip",Common.getHostIp());//终端IP
map.put("total_fee",totalFee);//标价金额
map.put("trade_type","JSAPI");//交易类型,比如JSAPI -JSAPI支付,NATIVE -Native支付,APP -APP支付
map.put("sign", getSign(map,appKey,"支付统一下单"));//签名
//调用微信通知下单接口
String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
String xmlStr = XMLUtil.mapToXml(map,true);
String resultStr = HttpUtil.httpPost(url,xmlStr);
log.info("微信支付统一下单接口返回参数:resultStr:" + resultStr);
Map<String,String> mapResult= XmlUtil.of(resultStr).toMap();
log.info("解析微信响应参数成Map格式:{}",mapResult);
if(mapResult !=null){
if("FAIL".equals(mapResult.get("return_code")))
return R.fail(mapResult.get("return_msg"));
if("FAIL".equals(mapResult.get("result_code")))
return R.fail(mapResult.get("err_code_des"));
}
//设置支付参数返回给前端
String timestamp = Common.getTimestamp();
WxPayParam param = new WxPayParam();
param.setAppId(appId);
param.setTimeStamp(timestamp);
param.setNonceStr(nonceStr);
param.setSignType("MD5");
String prepayid = XMLUtil.getXmlNodeValue(resultStr,"prepay_id");//获取prepayId
if(StringUtils.isNotBlank(prepayid)){
String prepayID = "prepay_id=" + prepayid;
param.setPrepayID(prepayID);
//生成前台支付签名,采用MD5算法!
SortedMap<Object, Object> signParams = new TreeMap<Object,Object>();
signParams.put("appId", param.getAppId());
signParams.put("timeStamp", param.getTimeStamp());
signParams.put("nonceStr", param.getNonceStr());
signParams.put("signType", param.getSignType());
signParams.put("package", param.getPrepayID());
param.setPaySign(getSign(signParams,appKey,"支付统一下单支付"));
param.setPackageValue(prepayID);
}
return R.data(param);
}
退款代码
//订单id
String orderId ="xm-" + SnowflakeIdUtil.nextId();
//计算sign签名并用MD5加密生成支付签名
String nonceStr = Common.getUUID();
String refundFee = CommonUtils.getMoney("0.03");
String totalFee = CommonUtils.getMoney("0.05");
//配置请求微信退款的参数
SortedMap<Object, Object> map = new TreeMap<Object, Object>();
map.put("appid", appId);
map.put("mch_id", mchId);
map.put("nonce_str", nonceStr);
map.put("transaction_id", doctMzRecord.getThirdorderNo());
map.put("out_refund_no", orderId);
map.put("out_trade_no", doctMzRecord.getOrderId());
map.put("total_fee", totalFee);//这里必须是支付时候的下单金额,特别是分批次退款的时候注意这里,不然微信提示:订单金额或退款金额与之前请求不一致,请核实后再试
map.put("refund_fee", refundFee);//这里是需要退款的金额
map.put("sign", WxUtil.getSign(map, appKey, "订单退款"));
logger.info("map的参数:{}",map);
String xmlStr = XMLUtil.mapToXml(map, true);
logger.info("封装xml格式参数:{}",xmlStr);
//证书路径
//本地环境路径定义
//InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("cert/apiclient_cert.p12");
//正式环境路径定义
File file = new File("/srv/apiclient_cert.p12");
InputStream input = new FileInputStream(file);
String wxUrl = "https://api.mch.weixin.qq.com/secapi/pay/refund";
String resultStr = HttpUtil.httpPostReflect(wxUrl , xmlStr, input, mchId);
logger.info("退款请求接口返回参数:{}" , resultStr);
Map<String, String> mapResult = XmlUtil.of(resultStr).toMap();
logger.info("解析微信响应参数成Map格式:{}",mapResult);
if (mapResult != null) {
if ("FAIL".equals(mapResult.get("return_code")))
return R.fail(mapResult.get("return_msg"));
if ("FAIL".equals(mapResult.get("result_code")))
return R.fail(mapResult.get("err_code_des"));
}
}
退款注意点:
1、退款需要证书:apiclient_cert.p12,如果做退款功能需要申请证书。
2、total_fee:必须是订单的支付金额,单位:分;
3、refund_fee:少于订单支付金额,单位:分;
4、同一个订单可以发起多次退款,商户退款单号(out_refund_no)可以不同;
4、使用一个商户退款单号(out_refund_no)进行退款,因为某种原因退款失败了,不影响下次使用其他的商户退款单号进行退款 。
更多信息可以参考微信支付开发文档
版权声明:本文为wuhuayangs原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。