1、准备工作:
开通商户平台,并且在商户平台-产品中心-现金红包 点击申请开通现金红包。
2、商户平台-账户中心-API安全-下载证书,并把证书放到服务器的E盘(这个放哪里自己喜欢了),如图:
3、引入jar包:
4、直接上代码:
package com.wp.index.user;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.KeyStore;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import javax.net.ssl.SSLContext;
import org.apache.commons.lang3.StringUtils;
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.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 com.jfinal.core.Controller;
import com.jfinal.kit.PropKit;
import com.jfinal.kit.StrKit;
import com.jfinal.weixin.sdk.kit.IpKit;
import com.jfinal.weixin.sdk.kit.PaymentKit;
import com.jfinal.weixin.sdk.utils.JsonUtils;
import com.wp.common.model.Member;
public class WeixinRedPacketController extends Controller {
// 商户相关资料
private static String appid = PropKit.get("appid");
private static String partner = PropKit.get("partner");
private static String paternerKey = PropKit.get("paternerKey");
private static String redPack = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";//现金普通红包接口
/**
* 公众号支付js-sdk(公众号的微信支付)
*/
public void ajaxRedPacket() {
String id = getCookie(Member.USER_LOGIN_ID_COOKIE);
if (StringUtils.isEmpty(id)) {
renderText("参数错误");
return;
}
Member m = Member.dao.findById(Long.parseLong(id));
if (m == null) {
renderText("参数错误");
return;
}
// openId,采用 网页授权获取 access_token API:SnsAccessTokenApi获取
String openId = m.getOpenId();
//生成订单号
String mchBillNo = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
//用户电脑ip
String ip = IpKit.getRealIp(getRequest());
if (StrKit.isBlank(ip)) {
ip = "127.0.0.1";
}
// 发普通红包接口文档:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_4&index=3
Map<String, String> params = new HashMap<String, String>();
params.put("nonce_str", UUID.randomUUID().toString().replaceAll("-", ""));//随机字符串
params.put("mch_billno", mchBillNo);// 商户订单号
params.put("mch_id", partner);//商户号
params.put("wxappid", appid);//公众号id
params.put("send_name", "王者猜猜猜"); //商户名称
params.put("re_openid", openId);//用户公众号唯一识别
params.put("total_amount", "100");// 因为微信的total_amount的单位是分,所有需要乘以100
params.put("total_num", "1");// 红包发放总人数
params.put("wishing", "祝您开心!");// 祝福语
params.put("client_ip", ip);// Ip地址
params.put("act_name", "财币兑换");// 活动名称
params.put("remark", "王者猜猜猜兑大奖");//备注
params.put("scene_id", "PRODUCT_5");// 场景id,需要在商户平台-产品中心-现金红包-产品设置里面设置,发放红包使用场景,红包金额大于200时必传。PRODUCT_1:商品促销;PRODUCT_2:抽奖;PRODUCT_3:虚拟物品兑奖 ;PRODUCT_4:企业内部福利;PRODUCT_5:渠道分润;PRODUCT_6:保险回馈;PRODUCT_7:彩票派奖;PRODUCT_8:税务刮奖
String sign = PaymentKit.createSign(params, paternerKey);
params.put("sign", sign);
String xml = PaymentKit.toXml(params);//得到签名之后的xml
String xmlResult = ssl(redPack,xml);//HttpUtils.post(redPack, xml);
System.out.println(xmlResult);
Map<String, String> result = PaymentKit.xmlToMap(xmlResult);
String return_code = result.get("return_code");
String return_msg = result.get("return_msg");
if (StrKit.isBlank(return_code) || !"SUCCESS".equals(return_code)) {
renderText(return_msg);
return;
}
String result_code = result.get("result_code");
if (StrKit.isBlank(result_code) || !"SUCCESS".equals(result_code)) {
renderText(return_msg);
return;
}
String jsonStr = JsonUtils.toJson(result);
renderText(jsonStr);
}
//这方法是用于带上刚刚下载的证书调动发红包接口
private String ssl(String url,String data){
StringBuffer message = new StringBuffer();
try {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
FileInputStream instream = new FileInputStream(new File("E:/cert/apiclient_cert.p12"));
keyStore.load(instream, partner.toCharArray());
// Trust own CA and all self-signed certs
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, partner.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();
}
}
//---------------------------------------------------------------------------------------------------------------------------------------------------------------------
package com.jfinal.weixin.sdk.kit;
import com.jfinal.kit.HashKit;
import com.jfinal.kit.StrKit;
import com.jfinal.weixin.sdk.utils.Charsets;
import com.jfinal.weixin.sdk.utils.XmlHelper;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
/**
* 微信支付的统一下单工具类
* @author L.cm
*/
public class PaymentKit {
/**
* 组装签名的字段
* @param params 参数
* @param urlEncoder 是否urlEncoder
* @return String
*/
public static String packageSign(Map<String, String> params, boolean urlEncoder) {
// 先将参数以其参数名的字典序升序进行排序
TreeMap<String, String> sortedParams = new TreeMap<String, String>(params);
// 遍历排序后的字典,将所有参数按"key=value"格式拼接在一起
StringBuilder sb = new StringBuilder();
boolean first = true;
for (Entry<String, String> param : sortedParams.entrySet()) {
String value = param.getValue();
if (StrKit.isBlank(value)) {
continue;
}
if (first) {
first = false;
} else {
sb.append("&");
}
sb.append(param.getKey()).append("=");
if (urlEncoder) {
try { value = urlEncode(value); } catch (UnsupportedEncodingException e) {}
}
sb.append(value);
}
return sb.toString();
}
/**
* urlEncode
* @param src 微信参数
* @return String
* @throws UnsupportedEncodingException 编码错误
*/
public static String urlEncode(String src) throws UnsupportedEncodingException {
return URLEncoder.encode(src, Charsets.UTF_8.name()).replace("+", "%20");
}
/**
* 生成签名
* @param params 参数
* @param paternerKey 支付密钥
* @return sign
*/
public static String createSign(Map<String, String> params, String paternerKey) {
// 生成签名前先去除sign
params.remove("sign");
String stringA = packageSign(params, false);
String stringSignTemp = stringA + "&key=" + paternerKey;
return HashKit.md5(stringSignTemp).toUpperCase();
}
/**
* 支付异步通知时校验sign
* @param params 参数
* @param paternerKey 支付密钥
* @return {boolean}
*/
public static boolean verifyNotify(Map<String, String> params, String paternerKey){
String sign = params.get("sign");
String localSign = PaymentKit.createSign(params, paternerKey);
return sign.equals(localSign);
}
/**
* 微信下单,map to xml
* @param params 参数
* @return String
*/
public static String toXml(Map<String, String> params) {
StringBuilder xml = new StringBuilder();
xml.append("<xml>");
for (Entry<String, String> entry : params.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
// 略过空值
if (StrKit.isBlank(value)) continue;
xml.append("<").append(key).append(">");
xml.append(entry.getValue());
xml.append("</").append(key).append(">");
}
xml.append("</xml>");
return xml.toString();
}
/**
* 针对支付的xml,没有嵌套节点的简单处理
* @param xmlStr xml字符串
* @return map集合
*/
public static Map<String, String> xmlToMap(String xmlStr) {
XmlHelper xmlHelper = XmlHelper.of(xmlStr);
return xmlHelper.toMap();
}
}
温馨提示:
微信现金红包有一点特别坑:设置金额的时候,因为其金额的单位是分,不能设置成100.00分,而非要设置成100分,这个有时候确实很难发现,
其报错信息为:<return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[签名错误]]></return_msg><result_code><![CDATA[FAIL]]></result_code>