微信商家紅包發(fā)放接入
微信紅包
準備工作
微信商家開通支付功能、微信公眾號、開通紅包功能
相關網站
- 微信支付: https://pay.weixin.qq.com/
- 簽名生成算法:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=4_3
- 發(fā)放紅包接口:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_4&index=3
需要資料
- API證書(apiclient_cert.p12):微信商戶平臺(pay.weixin.qq.com)-->賬戶中心-->賬戶設置-->API安全
- API支付秘鑰:商戶平臺設置的密鑰key 32 位
- openId:用戶微信公眾授權獲取到得 openId
注意事項
- 現(xiàn)金紅包接口目前微信還沒升級,使用的v2版本,所以提交數(shù)據(jù)和返回數(shù)據(jù)采用的是xml格式
- 付款金額,單位分
- 紅包發(fā)放成功,會發(fā)放到公眾號中,需要用戶主動點擊領取
- 測試過程中保證商戶運營賬號有余額
- 微信紅包-產品設置-添加測試地址ip白名單,不知道自己外網ip地址 百度搜索 ip 即可
代碼示例
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.HttpClientBuilder;
import org.apache.http.ssl.SSLContexts;
import org.jeecg.common.util.Md5Util;
import org.jeecg.modules.video.utitls.Constants;
import org.springframework.core.io.ClassPathResource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.net.ssl.SSLContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.security.*;
import java.util.*;
/**
* @description:
* @author: Mr.Fang
* @create: 2023-05-26
**/
@Slf4j
public class WxTest {
public static void main(String[] args) throws Exception {
// 1、參數(shù)封裝
TreeMap<String, Object> map = new TreeMap<>();
map.put("nonce_str", UUID.randomUUID().toString().replaceAll("-", "")); // 隨機字符串
map.put("mch_billno", "202302260000001");// "商家訂單編號"
map.put("mch_id", Constants.WX_MERCHANT_ID); // 商戶號
map.put("wxappid", Constants.WX_APP_ID); // 公眾號 APPID
map.put("send_name", "我發(fā)的");
map.put("re_openid", "123456"); // openid
map.put("total_amount", "100"); // 金額
map.put("total_num", 1); // 數(shù)量
map.put("wishing", "恭喜發(fā)財");
map.put("client_ip", "127.0.0.1");
map.put("act_name", "任務分傭");
map.put("remark", "無");
// 2、參數(shù)拼接 k=v 格式
List<String> list = new ArrayList<>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
list.add(String.format("%s=%s", key, value.toString()));
}
String join = String.join("&", list);
log.debug("拼接參數(shù)結果:{}", join);
// 3、參數(shù)+&key=API支付秘鑰 md5 簽名
String sign = Md5Util.md5Encode(join + "&key=" + Constants.WX_MERCHANT_API_SECRET, "UTF-8");
log.debug("拼接參數(shù)md5:{}", sign.toUpperCase(Locale.ROOT));
map.put("sign", sign.toUpperCase(Locale.ROOT));
log.debug("請求參數(shù)Map:{}", map);
// 4、獲取 API 證書,當前證書是放在 resource 下的
ClassPathResource resource = new ClassPathResource("apiclient_cert.p12");
InputStream inputStream = null;
try {
inputStream = resource.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
// FIXME: 2023/5/26 也可以這樣
// inputStream = new FileInputStream("證書路徑");
// 5、加載 SSL 證書
String pass =Constants.WX_MERCHANT_ID; // 密碼商戶號
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(inputStream, pass.toCharArray()); // 證書默認密碼是商戶號碼
SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial((chain, authType) -> true).loadKeyMaterial(keyStore, pass.toCharArray()).build();
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
// 6、創(chuàng)建 httpClient 客戶端 并設置 ssl 對象
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
httpClientBuilder.setSSLSocketFactory(sslConnectionSocketFactory);
CloseableHttpClient build = httpClientBuilder.build();
// 7、創(chuàng)建 post 請求
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack");
String xml = parseMapToXml(map);
log.debug("請求體xml:{}", xml);
StringEntity stringEntity = new StringEntity(xml, "UTF-8");
stringEntity.setContentEncoding("UTF-8");
httpPost.setEntity(stringEntity);
// 8、發(fā)起請求
CloseableHttpResponse response = build.execute(httpPost);
Map<String, Object> toMap = parseXMLToMap(response.getEntity().getContent());
log.info("mchRedEnvelope:{}", toMap);
}
/**
* 解析 xml
*
* @param map
* @return
*/
public static String parseMapToXml(Map<String, ? extends Object> map) {
StringBuilder sb = new StringBuilder("<xml>");
if (null != map && map.size() > 0) {
Set<? extends Map.Entry<String, ?>> entries = map.entrySet();
String key;
for (Iterator iterator = entries.iterator(); iterator.hasNext(); sb.append("</" + key + ">")) {
Map.Entry next = (Map.Entry) iterator.next();
key = (String) next.getKey();
Object value = next.getValue();
sb.append("<" + key + ">");
if (Objects.nonNull(value)) {
sb.append("<![CDATA[" + value + "]]>");
}
}
}
sb.append("</xml>");
return sb.toString();
}
/**
* xml 轉 map
* @param inputStream
* @return
*/
public static Map<String, Object> parseXMLToMap(InputStream inputStream) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(inputStream);
Element documentElement = document.getDocumentElement();
Map<String, Object> map = recursiveFindXmlElement(documentElement);
return map;
} catch (ParserConfigurationException | SAXException | IOException e) {
e.printStackTrace();
}
return null;
}
private static Map<String, Object> recursiveFindXmlElement(Element element) {
if (Objects.nonNull(element)) {
HashMap<String, Object> map = new HashMap();
NodeList childNodes = element.getChildNodes();
for(int i = 0; i < childNodes.getLength(); ++i) {
Node item = childNodes.item(i);
short nodeType = item.getNodeType();
if (Objects.equals(nodeType, (short) 1)) {
String nodeName = item.getNodeName();
Element childElement = (Element)item;
Map<String, Object> childMap = recursiveFindXmlElement(childElement);
if (null != childMap && childMap.size() > 0) {
map.put(nodeName, childMap);
} else {
String nodeValue = StringUtils.isBlank(item.getTextContent()) ? null : item.getTextContent().trim();
map.put(nodeName, nodeValue);
}
}
}
if (map.size() > 0) {
return map;
}
}
return null;
}
}
響應結果
展示參數(shù)進行處理,不代表真實發(fā)送數(shù)據(jù)內容
拼接參數(shù)結果:
act_name=任務分傭&client_ip=127.0.0.1&mch_billno=202302260000001&mch_id=1299535101&nonce_str=c3dad56d4bc74f9d910a6210edaddd0a&re_openid=1111111&remark=無&send_name=我發(fā)的&total_amount=100&total_num=1&wishing=恭喜發(fā)財&wxappid=1111111
拼接參數(shù)md5:
426a84083ea5adc67398a7918490adeb
請求參數(shù)Map:
{act_name=任務分傭, client_ip=127.0.0.1, mch_billno=202302260000001, mch_id=1111111, nonce_str=c3dad56d4bc74f9d910a6210edaddd0a, re_openid=1111111, remark=無, send_name=恭喜發(fā)財, sign=426a84083eadadc67398a7918490adeb, total_amount=100, total_num=1, wishing=恭喜發(fā)財, wxappid=1111111}
請求體xml
<xml>
<act_name><![CDATA[任務分傭]]></act_name>
<client_ip><![CDATA[127.0.0.1]]></client_ip>
<mch_billno><![CDATA[202302260000001]]></mch_billno>
<mch_id><![CDATA[1111111]]></mch_id>
<nonce_str><![CDATA[c3dad56d4bc74f9d910a6210edaddd0a]]></nonce_str>
<re_openid><![CDATA[1111111]]></re_openid>
<remark><![CDATA[無]]></remark>
<send_name><![CDATA[33333]]></send_name>
<sign><![CDATA[426a84083eadadc67398a7918490adeb]]></sign>
<total_amount><![CDATA[100]]></total_amount>
<total_num><![CDATA[1]]></total_num>
<wishing><![CDATA[恭喜發(fā)財]]></wishing>
<wxappid><![CDATA[1111111]]></wxappid>
</xml>
哇!又賺了一天人民幣

浙公網安備 33010602011771號