# 小程序红包前期准备
对于小程序红包坑是真的多。看官网的文档,对商户号的要求就一堆了,本身的接口也是坑巨多。
## 小程序发红包的方式比较顺利的有两种方式
1.使用企业付款到零钱(常用) 2.使用公众号付款
千万不要使用小程序红包功能,对应的支付接口是现金红包接口,这个接口中,有个字段是红包类型字段,
我在商户设置这个红包类型字段根本设置不成功,它要设置一个支付调用的IP地址,然后你会发现这个IP根本设置不成功。
## 小程序发红包-企业付款到零钱
申请一个小程序,然后申请一个可用的商户号,且该商户号如果要使用支付功能还要满足以下要求:
然后在商户号的产品中心-企业付款到零钱-开通 ,设置好秘钥证书和appKey
# 小程序红包后台开发
开发就直接按照文档的来写就可以了,<https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2>
因为微信的接口要求使用HTTPS协议,所以需要引用okHttp3,而okHttp3要用到okoi,项目引入以下两个jar包,使用lib形式导入
代码如下:
```
import com.tecsun.access.modules.common.config.WechatConfig;
import com.tecsun.access.modules.common.enums.SignTypeEnum;
import com.tecsun.access.modules.dto.redpack.my.MyRedPack;
import com.tecsun.access.modules.dto.redpack.my.RedPackStatusDTO;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import okhttp3.internal.platform.Platform;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.ssl.SSLContexts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.*;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URLEncoder;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.*;
/**
* @Author echo
* @Date 2020/7/30 14:51
* @Description 红包工具类
*/
@Component
@Slf4j
public class MyRedPackUtil {
@Autowired
private WechatConfig wechatConfig;
/**
* @Author echo
* @Description //红包发送
* @Date 10:21 2020/7/31
**/
public String sendRedPack(MyRedPack redPack)throws Exception{
//配置公共参数
redPack.setNonce_str(getRandomString(32));
redPack.setMch_appid(wechatConfig.appid());
redPack.setMchid(wechatConfig.mchId());
redPack.setCheck_name("NO_CHECK");
//微信要求按照参数名ASCII字典序排序,这里巧用treeMap进行字典排序
TreeMap treeMap = new TreeMap(toMap(redPack));
String urlParamsByMap = getUrlParamsByMap(treeMap);//将实体类转换为url形式
urlParamsByMap += "&key="+wechatConfig.apiKey();//拼接API密钥
//进行签名,需要说明的是,如果内容包含中文的话,要使用utf-8进行md5签名,不然会签名错误
String sign = parseStrToMd5L32(urlParamsByMap).toUpperCase();
redPack.setSign(sign);
treeMap.put("sign",redPack.getSign());
//转换成xml格式
String soapRequestData = getSoapRequestData(treeMap);
log.info("微信发放红包发送报文:"+soapRequestData);
//发起请求前准备
RequestBody body = RequestBody.create(MediaType.parse("text/xml;charset=UTF-8"), soapRequestData);
Request request = new Request.Builder()
.url("https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers")
.post(body)
.build();
//为http请求设置证书
X509TrustManager x509TrustManager = getX509TrustManager();
SSLSocketFactory socketFactory = getSSL2(x509TrustManager).getSocketFactory();
OkHttpClient okHttpClient = new OkHttpClient.Builder().sslSocketFactory(socketFactory, x509TrustManager).build();
//得到输出内容
Response response = okHttpClient.newCall(request).execute();
String content = response.body().string();
log.info("微信发放红包返回报文:"+content);
return content;
}
/**
* @Author echo
* @Description //红包状态查询
* @Date 15:44 2020/8/3
**/
public String redPackStatus(String billNo)throws Exception{
RedPackStatusDTO dto = new RedPackStatusDTO();
//配置公共参数
dto.setNonce_str(getRandomString(32));
dto.setAppid(wechatConfig.appid());
dto.setMch_id(wechatConfig.mchId());
dto.setPartner_trade_no(billNo);
//微信要求按照参数名ASCII字典序排序,这里巧用treeMap进行字典排序
TreeMap treeMap = new TreeMap(toMap(dto));
String urlParamsByMap = getUrlParamsByMap(treeMap);//将实体类转换为url形式
urlParamsByMap += "&key="+wechatConfig.apiKey();//拼接API密钥
//进行签名,需要说明的是,如果内容包含中文的话,要使用utf-8进行md5签名,不然会签名错误
String sign = parseStrToMd5L32(urlParamsByMap).toUpperCase();
dto.setSign(sign);
treeMap.put("sign",dto.getSign());
//转换成xml格式
String soapRequestData = getSoapRequestData(treeMap);
log.info("----------微信查询红包状态发送报文:"+soapRequestData);
//发起请求前准备
RequestBody body = RequestBody.create(MediaType.parse("text/xml;charset=UTF-8"), soapRequestData);
Request request = new Request.Builder()
.url("https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo")
.post(body)
.build();
//为http请求设置证书
X509TrustManager x509TrustManager = getX509TrustManager();
SSLSocketFactory socketFactory = getSSL2(x509TrustManager).getSocketFactory();
OkHttpClient okHttpClient = new OkHttpClient.Builder().sslSocketFactory(socketFactory, x509TrustManager).build();
//得到输出内容
Response response = okHttpClient.newCall(request).execute();
String content = response.body().string();
log.info("----------微信查询红包状态返回报文:"+content);
return content;
}
//随机生成字符串 length用户要求产生字符串的长度
public String getRandomString(int length){
String str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random=new Random();
StringBuffer sb=new StringBuffer();
for(int i=0;i<length;i++){
int number=random.nextInt(62);
sb.append(str.charAt(number));
}
return sb.toString();
}
/**
* 将map转换成url
* @param map
* @return
*/
private String getUrlParamsByMap(Map<String, String> map) {
if (map == null) {
return "";
}
StringBuffer sb = new StringBuffer();
map = new TreeMap<String, String>(map);
for (Map.Entry<String, String> entry : map.entrySet()) {
if(entry.getValue() == null){
continue;
}
sb.append(entry.getKey() + "=" + entry.getValue());
sb.append("&");
}
String s = sb.toString();
if (s.endsWith("&")) {
s = StringUtils.substringBeforeLast(s, "&");
}
return s;
}
private Map toMap(Object bean) {
Class<? extends Object> clazz = bean.getClass();
Map<Object, Object> returnMap = new HashMap<Object, Object>();
BeanInfo beanInfo = null;
try {
beanInfo = Introspector.getBeanInfo(clazz);
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (int i = 0; i < propertyDescriptors.length; i++) {
PropertyDescriptor descriptor = propertyDescriptors[i];
String propertyName = descriptor.getName();
if (!propertyName.equals("class")) {
Method readMethod = descriptor.getReadMethod();
Object result = null;
result = readMethod.invoke(bean, new Object[0]);
if (null != propertyName) {
propertyName = propertyName.toString();
}
if (null != result) {
result = result.toString();
returnMap.put(propertyName, result);
}
}
} //这个发生的异常被吃掉了,如果有要进行处理的应该往上一层抛出去进行处理,我在上一层懒得处理了就在这里吃掉了
} catch (IntrospectionException e) {
log.error("分析类属性失败"+e.getMessage(),e);
} catch (IllegalAccessException e) {
log.error("实例化 JavaBean 失败"+e.getMessage(),e);
} catch (IllegalArgumentException e) {
log.error("映射错误"+e.getMessage(),e);
} catch (InvocationTargetException e) {
log.error("调用属性的 setter 方法失败"+e.getMessage(),e);
}
return returnMap;
}
/**
* 获取md5
* @param str
* @return
*/
private String parseStrToMd5L32(String str) {
return parseStrToMd5L32(str,"utf-8");
}
private String parseStrToMd5L32(String str,String charset) {
String reStr = null;
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] bytes = md5.digest(str.getBytes(charset));
StringBuffer stringBuffer = new StringBuffer();
for (byte b : bytes) {
int bt = b & 0xff;
if (bt < 16) {
stringBuffer.append(0);
}
stringBuffer.append(Integer.toHexString(bt));
}
reStr = stringBuffer.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return reStr;
}
/**
* 凑成xml格式字符串
*
* @return
*/
private String getSoapRequestData(Map<String, String> map) throws Exception {
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
for (Map.Entry<String, String> entry : map.entrySet()) {
sb.append("<" + entry.getKey() + ">" + entry.getValue() + "</" + entry.getKey() + ">");
}
sb.append("</xml>");
return sb.toString();
}
private X509TrustManager getX509TrustManager() throws NoSuchAlgorithmException,KeyStoreException{
TrustManagerFactory trustManagerFactory = null;
trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
return trustManager;
}
private SSLContext getSSL2(X509TrustManager trustManager)throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException{
SSLContext wx_ssl_context = SSLContext.getInstance("TLS");
KeyStore keyStore = KeyStore.getInstance("PKCS12");
//证书位置自己定义
FileInputStream instream = new FileInputStream(new File(wechatConfig.keyPath()));
try {
keyStore.load(instream, wechatConfig.keyMima().toCharArray());
} finally {
instream.close();
}
KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmfactory.init(keyStore, wechatConfig.keyMima().toCharArray());
wx_ssl_context.init(kmfactory.getKeyManagers(), new TrustManager[]{trustManager}, new SecureRandom());
return wx_ssl_context;
}
private String md5(String password) {
try {
MessageDigest md = MessageDigest.getInstance("md5");
// 不可逆
byte[] bytes = md.digest(password.getBytes());
String str = Base64.getEncoder().encodeToString(bytes);
return str;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
}```
上面的代码只是对微信对接那部分的代码,但其实整个红包的流程应该如下的:
因为公司部署服务里,是通过前置机转发到服务的,而服务未开通对SSL协议的443接口,所以在前置机上部署对微信对接的网关,关于服务以及rabbitMQ部分代码就不展示了,具体业务具体分析。