微信小程序退款提现一定要申请证书(证书条件)
1.支付、提现、退款的依赖
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>WXPay-SDK-Java</artifactId>
<version>0.0.4</version>
</dependency>
2.参数配置
package com.wsh.wz.utils.wxpay;
/**
* @User
* @PackageName com.wsh.wz.utils.wxpay
* @CreateTime: 2021-08-15 14:42
* @Description: TODO
*/
public class WechatConfig {
//小程序appid
public static final String appid = "wx365a28a668734937";
//微信支付的商户id
public static final String mch_id = "商户id";
//微信支付的商户密钥
public static final String key = "支付秘钥";
//支付成功后的服务器回调url,这里填PayController里的回调函数地址
public static final String notify_url ="https://www.cankuaixiu.cn/wz/api/pay/wxNotify";
//签名方式,固定值
public static final String SIGNTYPE = "MD5";
//交易类型,小程序支付的固定值为JSAPI
public static final String TRADETYPE = "JSAPI";
//微信统一下单接口地址
public static final String pay_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
//证书路径 退款、提现必备的证书
public static final String CERT_PATH1 = "证书的位置";
}
3.提现实现代码(只给了提现代码 全部放在controller中实现了)
package com.wsh.wz.controller;
import com.wsh.wz.utils.wxpay.CertHttpUtil;
import com.wsh.wz.utils.wxpay.PayUtil;
import com.wsh.wz.utils.wxpay.WechatConfig;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
@RestController
@RequestMapping("/api/pay")
public class WxWithdrawController {
private final String URL ="https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
@PostMapping("/withdraw")
public String withdraw(String openId){
// 1. 拼凑企业支付需要的参数
String appid = WechatConfig.appid; // APP对应的微信的appid
String mch_id =WechatConfig.mch_id; // 商户号
String nonce_str = getRandomStringByLength(32);// 生成随机数
String partner_trade_no = getRandomStringByLength(32); // 生成商户订单号
String check_name = "NO_CHECK"; // 是否验证真实姓名呢
String amount = String.valueOf(1500); // 企业付款金额,最少为100,单位为分
String desc = "恭喜你,完成了一个订单!"; // 企业付款操作说明信息。必填。
// 2.生成map集合
Map<String, String> hashMap = new HashMap<String, String>();
hashMap.put("mch_appid", appid); // 微信公众号的appid
hashMap.put("mchid", mch_id); // 商务号
hashMap.put("nonce_str", nonce_str); // 随机生成后数字,保证安全性
hashMap.put("partner_trade_no", partner_trade_no); // 生成商户订单号
hashMap.put("openid", openId); // 支付给用户openid
hashMap.put("check_name", check_name); // 是否验证真实姓名呢
hashMap.put("amount", amount); // 企业付款金额,单位为分
hashMap.put("desc", desc); // 企业付款操作说明信息。必填。
String prestr = PayUtil.createLinkString(hashMap); // 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
// 3. 利用上面的参数,先去生成自己的签名
String sign = PayUtil.sign(prestr, WechatConfig.key, "utf-8").toUpperCase();
//4.放回map集合中
hashMap.put("sign",sign);
try {
//5.转换成xml
final String wx = PayUtil.mapToXml(hashMap);
//6.发送请求
String returnXml = CertHttpUtil.postData(URL, wx, WechatConfig.mch_id, WechatConfig.CERT_PATH1);
System.out.println("返回的returnXml为:" + returnXml);
// 7.将微信返回的xml结果转成map格式
Map<String, String> returnMap = PayUtil.doXMLParse(returnXml);
if(returnMap.get("result_code").equals("SUCCESS")){
System.out.println("提现成功");
}else {
System.out.println("用户提现失败");
System.out.println(returnMap.get("result_code"));
}
} catch (Exception e) {
e.printStackTrace();
}
return "提现成功";
}
//获取随机字符串
private String getRandomStringByLength(int length) {
String base = "abcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
}
4.里面用到的工具类
package com.wsh.wz.utils.wxpay;
import org.apache.commons.codec.digest.DigestUtils;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
/**
* @User
* @PackageName com.wsh.wz.utils.wxpay
* @CreateTime: 2021-08-15 14:37
* @Description: TODO
*/
public class PayUtil {
/**
* 签名字符串
*
* @param text 需要签名的字符串
* @param key 密钥
* @param input_charset 编码格式
* @return 签名结果
*/
public static String sign(String text, String key, String input_charset) {
text = text + "&key=" + key;
return DigestUtils.md5Hex(getContentBytes(text, input_charset));
}
/**
* 签名字符串
*
* @param text 需要签名的字符串
* @param sign 签名结果
* @param key 密钥
* @param input_charset 编码格式
* @return 签名结果
*/
public static boolean verify(String text, String sign, String key, String input_charset) {
text = text +"&key=" +key;
String mysign = DigestUtils.md5Hex(getContentBytes(text, input_charset)).toUpperCase();
if (mysign.equals(sign)) {
return true;
} else {
return false;
}
}
/**
* @param content
* @param charset
* @return
* @throws java.security.SignatureException
* @throws UnsupportedEncodingException
*/
public static byte[] getContentBytes(String content, String charset) {
if (charset == null || "".equals(charset)) {
return content.getBytes();
}
try {
return content.getBytes(charset);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);
}
}
private static boolean isValidChar(char ch) {
if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))
return true;
if ((ch >= 0x4e00 && ch <= 0x7fff) || (ch >= 0x8000 && ch <= 0x952f))
return true;// 简体中文汉字编码
return false;
}
/**
* 除去数组中的空值和签名参数
*
* @param sArray 签名参数组
* @return 去掉空值与签名参数后的新签名参数组
*/
public static Map<String, String> paraFilter(Map<String, String> sArray) {
Map<String, String> result = new HashMap<String, String>();
if (sArray == null || sArray.size() <= 0) {
return result;
}
for (String key : sArray.keySet()) {
String value = sArray.get(key);
if (value == null || value.equals("") || key.equalsIgnoreCase("sign")
|| key.equalsIgnoreCase("sign_type")) {
continue;
}
result.put(key, value);
}
return result;
}
/**
* 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
*
* @param params 需要排序并参与字符拼接的参数组
* @return 拼接后字符串
*/
public static String createLinkString(Map<String, String> params) {
List<String> keys = new ArrayList<>(params.keySet());
Collections.sort(keys);
String prestr = "";
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = params.get(key);
if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符
prestr = prestr + key + "=" + value;
} else {
prestr = prestr + key + "=" + value + "&";
}
}
return prestr;
}
/**
* @param requestUrl 请求地址
* @param requestMethod 请求方法
* @param outputStr 参数
*/
public static String httpRequest(String requestUrl, String requestMethod, String outputStr) {
// 创建SSLContext
StringBuffer buffer = null;
try {
URL url = new URL(requestUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(requestMethod);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.connect();
//往服务器端写内容
if (null != outputStr) {
OutputStream os = conn.getOutputStream();
os.write(outputStr.getBytes("utf-8"));
os.close();
}
// 读取服务器端返回的内容
InputStream is = conn.getInputStream();
InputStreamReader isr = new InputStreamReader(is, "utf-8");
BufferedReader br = new BufferedReader(isr);
buffer = new StringBuffer();
String line = null;
while ((line = br.readLine()) != null) {
buffer.append(line);
}
br.close();
} catch (Exception e) {
e.printStackTrace();
}
return buffer.toString();
}
public static String urlEncodeUTF8(String source) {
String result = source;
try {
result = java.net.URLEncoder.encode(source, "UTF-8");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return result;
}
/**
* 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
*
*/
public static Map doXMLParse(String strXML) throws Exception {
try {
Map<String, String> data = new HashMap<String, String>();
DocumentBuilder documentBuilder = WXPayXmlUtil.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;
}
}
/**
* 将Map转换为XML格式的字符串
*
* @param data Map类型数据
* @return XML格式的字符串
* @throws Exception
*/
public static String mapToXml(Map<String, String> data) throws Exception {
org.w3c.dom.Document document = WXPayXmlUtil.newDocument();
org.w3c.dom.Element root = document.createElement("xml");
document.appendChild(root);
for (String key: data.keySet()) {
String value = data.get(key);
if (value == null) {
value = "";
}
value = value.trim();
org.w3c.dom.Element filed = document.createElement(key);
filed.appendChild(document.createTextNode(value));
root.appendChild(filed);
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMSource source = new DOMSource(document);
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
try {
writer.close();
}
catch (Exception ex) {
}
return output;
}
public static InputStream String2Inputstream(String str) {
return new ByteArrayInputStream(str.getBytes());
}
}
package com.wsh.wz.utils.wxpay;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import javax.net.ssl.SSLContext;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;
/**
* 获取微信apiclient_cert.p12证书
*/
@Component
public class CertHttpUtil {
private static int socketTimeout = 10000;// 连接超时时间,默认10秒
private static int connectTimeout = 30000;// 传输超时时间,默认30秒
private static RequestConfig requestConfig;// 请求器的配置
private static CloseableHttpClient httpClient;// HTTP请求器
/**
* 通过Https往API post xml数据
*
* @param url API地址
* @param xmlObj 要提交的XML数据对象
* @param mchId 商户ID
* @param certPath 证书位置
* @return
*/
public static String postData(String url, String xmlObj, String mchId, String certPath) {
// 加载证书
try {
initCert(mchId, certPath);
} catch (Exception e) {
e.printStackTrace();
}
String result = null;
HttpPost httpPost = new HttpPost(url);
// 得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
StringEntity postEntity = new StringEntity(xmlObj, "UTF-8");
httpPost.addHeader("Content-Type", "text/xml");
httpPost.setEntity(postEntity);
// 根据默认超时限制初始化requestConfig
requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
// 设置请求器的配置
httpPost.setConfig(requestConfig);
try {
HttpResponse response = null;
try {
response = httpClient.execute(httpPost);
} catch (IOException e) {
e.printStackTrace();
}
HttpEntity entity = response.getEntity();
try {
result = EntityUtils.toString(entity, "UTF-8");
} catch (IOException e) {
e.printStackTrace();
}
} finally {
httpPost.abort();
}
return result;
}
/**
* 加载证书
*
* @param mchId 商户ID
* @param certPath 证书位置
* @throws Exception
*/
private static void initCert(String mchId, String certPath) throws Exception {
// 证书密码,默认为商户ID
String key = mchId;
// 证书的路径
String path = certPath;
// 指定读取证书格式为PKCS12
KeyStore keyStore = KeyStore.getInstance("PKCS12");
// 读取本机存放的PKCS12证书文件
File file = new File(path);
InputStream in = new FileInputStream(file);
try {
// 指定PKCS12的密码(商户ID)
keyStore.load(in, key.toCharArray());
} finally {
in.close();
}
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, key.toCharArray()).build();
SSLConnectionSocketFactory sslsf =
new SSLConnectionSocketFactory(sslcontext, new String[] {"TLSv1"}, null,
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
}
}