证实过可以用的公众号红包2020.05.16
1.__开发前先从公众号查出APPID和APPSECRE
2.注册url的域名: 公众号-设置-公众号设置-功能设置
3.注册的url是项目根路径,
4.一般将MP_*****.txt放在web或webapp中,微信会从注册的url中找到这个文件
5.登录微信支付商户平台: 产品中心-开发配置-支付配置
6.查出微信商户号密钥: 账户中心-API安全
重要: 将API证书 apiclient_cert.p12 放到resource目录下
下面是公众号发送红包给用户的代码
import org.weixin4j.WeixinSupport;
@RestController
@RequestMapping("/sendControl")
public class SendRedPackController extends WeixinSupport {
// 商户wxappid
private static final String wxappid = "wx***************";
// 微信支付的商户id
private static final String mch_id = "15*******";
// 微信支付的商户api密钥
private static final String Key = "ae***************";
// 签名方式,固定值
private static final String SIGNTYPE = "MD5";
// 交易类型,支付的固定值为JSAPI
private static final String TRADETYPE = "JSAPI";
//发送红包的商家
private static final String send_name = "大超市";
//红包祝福语
private static final String wishing = "感谢您的参与,祝您生活愉快";
//活动名称
private static final String act_name = "大特价";
//备注
private static final String remark = "买越多得越多,快去分享吧";
/**
* 现金红包发放
* re_openid: 收红包人的openid( openid 的获取需要用户授权登录公众号,也可以查出自己的试试)
* total_amount: 金额(普通情况最小单位: 100 ; 即一元)
* total_num: 人数(单位: 1)
*/
@RequestMapping("/sendpack")
public Map<String, String> sendPack(String redPacket,String re_openid, Integer total_amount,
Integer total_num, HttpServletRequest request) throws Exception {
// 获取本机的ip地址
String client_ip = IpUtils.getIpAddr(request);
//商品订单号
String mch_billno = StringUtils.generateOrderSN();
//随机字符串
String nonce_str = StringUtils.getRandomStringByLength(32);
Map<String, String> map = new HashMap<String, String>();
map.put("act_name", act_name);
map.put("client_ip", client_ip);
map.put("mch_billno", mch_billno);
map.put("mch_id", mch_id);
map.put("nonce_str", nonce_str);
map.put("re_openid", re_openid);
map.put("remark", remark);
map.put("wxappid", wxappid);
map.put("send_name", send_name);
map.put("total_amount", total_amount + "");
map.put("total_num", total_num + "");
map.put("wishing", wishing);
// MD5运算生成签名
//签名: 就是将所有参数放进来用微信工具生成一串字符串,这里比较容易出错....
String mdSign = WXPayUtil.generateSignature(map, Key);
System.out.println("mdSign:==== " + mdSign);
SendRedPack redPack = new SendRedPack();
redPack.setAct_name(act_name);
redPack.setClient_ip(client_ip);
redPack.setMch_billno(mch_billno);
redPack.setMch_id(mch_id);
redPack.setNonce_str(nonce_str);
redPack.setRe_openid(re_openid);
redPack.setRemark(remark);
redPack.setWxappid(wxappid);
redPack.setSend_name(send_name);
redPack.setTotal_amount(total_amount);
redPack.setTotal_num(total_num);
redPack.setWishing(wishing);
redPack.setSign(mdSign);
String xml = XMLUtil.convertToXml(redPack);
//发送请求,获取响应数据
String response = SSLUtil.ssl("https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack", xml);
System.out.println("response:==== " + response);
Map<String, String> responseMap = WXPayUtil.xmlToMap(response);
return responseMap;
}
}
实体类
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
@XmlType
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement
public class SendRedPack {
private String nonce_str;// 随机字符串
private String sign;// 签名
private String mch_billno;// 商户订单号
private String mch_id;// 商户号1517541601
private String wxappid;// 公众账号
private String send_name;// 商户名称
private String re_openid;// 用户
private Integer total_amount;// 付款金额
private Integer total_num;// 红包发放总人数
private String wishing;// 红包祝福语
private String client_ip;// 调用接口的服务器Ip地址
private String act_name;// 活动名称
private String remark;// 备注
//set.get略..
}
工具类
public class IpUtils {
/**
* 获取真实ip地址
*
* @param request
* @return
*/
public static String getIpAddr(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
int index = ip.indexOf(",");
if (index != -1) {
return ip.substring(0, index);
} else {
return ip;
}
}
ip = request.getHeader("X-Real-IP");
if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
return ip;
}
return request.getRemoteAddr();
}
}
public class StringUtils extends org.apache.commons.lang.StringUtils {
/**
* StringUtils工具类方法 获取一定长度的随机字符串,范围0-9,a-z
*
* @param length:指定字符串长度
* @return 一定长度的随机字符串
*/
public static 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();
}
/**
* 针对微信支付生成商户订单号,为了避免微信商户订单号重复(下单单位支付),
* @return
*/
public static String generateOrderSN() {
String format = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
return getRandomStringByLength(6)+format;
}
}
/**
* xml与pojo转换
*/
public class XMLUtil {
public static final String DEFAULT_ENCODING = "UTF-8";
/**
* pojo转换成xml 默认编码UTF-8
*
* @param obj 待转化的对象
* @return xml格式字符串
* @throws Exception JAXBException
*/
public static String convertToXml(Object obj) throws Exception {
return convertToXml(obj, DEFAULT_ENCODING);
}
/**
* pojo转换成xml
*
* @param obj 待转化的对象
* @param encoding 编码
* @return xml格式字符串
* @throws Exception JAXBException
*/
public static String convertToXml(Object obj, String encoding) throws Exception {
String result = null;
JAXBContext context = JAXBContext.newInstance(obj.getClass());
Marshaller marshaller = context.createMarshaller();
// 指定是否使用换行和缩排对已编组 XML 数据进行格式化的属性名称。
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(Marshaller.JAXB_ENCODING, encoding);
StringWriter writer = new StringWriter();
marshaller.marshal(obj, writer);
result = writer.toString();
return result;
}
/**
* xml转换成JavaBean
*
* @param xml xml格式字符串
* @param t 待转化的对象
* @return 转化后的对象
* @throws Exception JAXBException
*/
@SuppressWarnings("unchecked")
public static <T> T convertToJavaBean(String xml, Class<T> t) throws Exception {
T obj = null;
JAXBContext context = JAXBContext.newInstance(t);
Unmarshaller unmarshaller = context.createUnmarshaller();
obj = (T) unmarshaller.unmarshal(new StringReader(xml));
return obj;
}
}
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.springframework.core.io.ClassPathResource;
import javax.net.ssl.SSLContext;
import java.io.*;
import java.security.KeyStore;
public class SSLUtil {
// 微信支付的商户id
private static final String mch_id = "15*******";
public static String ssl(String url,String data){
StringBuffer message = new StringBuffer();
try {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
//FileInputStream instream = new FileInputStream(new File("treasurebox/apiclient_cert.p12"));
/**
*
*重要:将API证书 apiclient_cert.p12 放到resource目录下
*
*/
ClassPathResource cl = new ClassPathResource("apiclient_cert.p12");
logger.info(cl.toString());
//证书,商户号
keyStore.load(cl.getInputStream(),mch_id.toCharArray());
// Trust own CA and all self-signed certs
SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStore, mch_id.toCharArray())
.build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext, new String[] { "TLSv1" }, null,
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build();
HttpPost httpost = new HttpPost(url);
httpost.addHeader("Connection", "keep-alive");
httpost.addHeader("Accept", "*/*");
httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
httpost.addHeader("Host", "api.mch.weixin.qq.com");
httpost.addHeader("X-Requested-With", "XMLHttpRequest");
httpost.addHeader("Cache-Control", "max-age=0");
httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
httpost.setEntity(new StringEntity(data, "UTF-8"));
System.out.println("executing request" + httpost.getRequestLine());
CloseableHttpResponse response = httpclient.execute(httpost);
try {
HttpEntity entity = response.getEntity();
System.out.println("----------------------------------------");
System.out.println(response.getStatusLine());
if (entity != null) {
System.out.println("Response content length: " + entity.getContentLength());
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent(),"UTF-8"));
String text;
while ((text = bufferedReader.readLine()) != null) {
message.append(text);
}
}
EntityUtils.consume(entity);
} catch (IOException e) {
e.printStackTrace();
} finally {
response.close();
}
} catch (Exception e1) {
e1.printStackTrace();
}
return message.toString();
}
}
public class WXPayUtil {
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random RANDOM = new SecureRandom();
/**
* 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) {
WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}",
ex.getMessage(), strXML);
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 = 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 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();
}
public static Document newDocument() throws ParserConfigurationException {
return newDocumentBuilder().newDocument();
}
/**
* 生成 XML 格式字符串
*
* @param data Map类型数据
* @param key API密钥
* @return 含有sign字段的XML
*/
public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
return generateSignedXml(data, key, SignType.MD5);
}
/**
* 生成带有 sign 的 XML 格式字符串
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名类型
* @return 含有sign字段的XML
*/
public static String generateSignedXml(final Map<String, String> data, String key, SignType signType)
throws Exception {
String sign = generateSignature(data, key, signType);
data.put(WXPayConstants.FIELD_SIGN, sign);
return mapToXml(data);
}
/**
* 判断签名是否正确
*
* @param xmlStr XML格式数据
* @param key API密钥
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
Map<String, String> data = xmlToMap(xmlStr);
if (!data.containsKey(WXPayConstants.FIELD_SIGN)) {
return false;
}
String sign = data.get(WXPayConstants.FIELD_SIGN);
return generateSignature(data, key).equals(sign);
}
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
*
* @param data Map类型数据
* @param key API密钥
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
return isSignatureValid(data, key, SignType.MD5);
}
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名方式
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key, SignType signType) throws Exception {
if (!data.containsKey(WXPayConstants.FIELD_SIGN)) {
return false;
}
String sign = data.get(WXPayConstants.FIELD_SIGN);
return generateSignature(data, key, signType).equals(sign);
}
/**
* 生成签名
*
* @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(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(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));
}
}
/**
* 获取随机字符串 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);
}
/**
* 生成 MD5
*
* @param data 待处理数据
* @return MD5结果
*/
public static String MD5(String data) throws Exception {
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();
}
/**
* 日志
*
* @return
*/
public static Logger getLogger() {
Logger logger = LoggerFactory.getLogger("wxpay java sdk");
return logger;
}
/**
* 获取当前时间戳,单位秒
*
* @return
*/
public static long getCurrentTimestamp() {
return System.currentTimeMillis() / 1000;
}
/**
* 获取当前时间戳,单位毫秒
*
* @return
*/
public static long getCurrentTimestampMs() {
return System.currentTimeMillis();
}
}
感谢社区的作者:
如有错误,请不吝指导