一、前言
微信小程序现在成为了增长模式的宠儿,小程序的便利想必看这篇博客的各位都已经了解到了,本篇呢主要介绍支付退款。
其中里面涉及的关键词:
templateId:小程序运营人员申请模版消息时生成的一个标示,后续推送时会使用。
formId:微信推送时给用户的标示,用一次就失效,这个是在小程序端通过表单获取。
accessToken:通过用户验证登录和授权,获取Access Token,为下一步获取用户的OpenID做准备。
openId:在本小程序内用户的唯一标示。
page:指定召回时点击跳转的页面。
content:这个是根据模版定义出来的任务内容。
mchid:微信中的商户号
partner_key:微信中商户中的商户key
二、需求场景
主要用于商户做活动给用户返现或者退款使用,相当于微信支付的逆向。
三、后台准备
需要微信支付中申请的商户号和小程序或者商户号中的主体进行绑定,并且下载证书apiclient_cert.p12,并且需要申请配置ip白名单。
四、需要的jar pom
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.3</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.4</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.2</version>
</dependency>
五、上代码
a.主体代码
public class WeixinWithdrawClient {
private static final Logger LOGGER = new Logger(WeixinWithdrawClient.class);
/**
* 微信提现(企业付款)
*/
public GenericResult<Void> weixinWithdraw(String openId, String appid, String desc, String secret, String partnerkey, String amount,String mechantNo, String realPath,String ip){
LOGGER.info("微信提现企业付款入参openId=",openId," ,desc=", desc," ,amount=",amount);
if (StringUtils.isNotBlank(desc) && StringUtils.isNotBlank(amount) && StringUtils.isNotBlank(ip) && StringUtils.isNotBlank(openId)) {
String xmlInfo = enterprisePay(openId,amount,ip,desc,appid,secret,partnerkey,mechantNo);
LOGGER.info("构造微信提现入参openId=",openId," ,xmlInfo=", FastJsonUtils.toJSONNoFeatures(xmlInfo));
try {
CloseableHttpResponse response = HttpUtil.Post(WeixinConfigConstans.request_url, xmlInfo, true, realPath);
String transfersXml = EntityUtils.toString(response.getEntity(), "utf-8");
LOGGER.info("微信提现出参openId=",openId," ,transfersXml=",transfersXml);
Map<String, String> transferMap = XmlUtils.readStringXmlOut(transfersXml);
if (transferMap.size()>0) {
if (transferMap.get("result_code").equals("SUCCESS") && transferMap.get("return_code").equals("SUCCESS")) {
//成功需要进行的逻辑操作,push 提现记录
return new GenericResult<>(CommonResponseEnum.SUCCESS,null);
}
}
} catch (Exception e) {
LOGGER.info("调用微信提现接口异常openId=",openId," ,e=",e);
}
}
return new GenericResult<>(CommonResponseEnum.FAILED,null);
}
public static String enterprisePay(String openId, String price, String ip,String desc,String appId,String secret,String partnerkey,String merchantNo){
Long currTime = System.currentTimeMillis();
// 六位随机数
String strRandom = CommonUtils.generateVerificationCode(6);
// 12位序列号,可以自行调整。
String nonce_str = currTime + strRandom;
String casNo = strRandom + currTime;
//签名准备
SortedMap<String, String> packageParams = getStringStringSortedMap(openId, price, ip, desc, appId, merchantNo, nonce_str, casNo);
LOGGER.info("签名参数构造openId=",openId," ,packageParams=", FastJsonUtils.toJSONNoFeatures(packageParams));
//参数以及初始化
RequestHandler reqHandler = new RequestHandler(null, null);
reqHandler.init(appId, secret, partnerkey);
//构造XML参数
return getStringParam(openId, price, ip, desc, appId, merchantNo, nonce_str, casNo, packageParams, reqHandler);
}
/**
* 构造微信入参
* @param openId
* @param price
* @param ip
* @param desc
* @param appId
* @param merchantNo
* @param nonce_str
* @param casNo
* @param packageParams
* @param reqHandler
* @return
*/
private static String getStringParam(String openId, String price, String ip, String desc, String appId, String merchantNo, String nonce_str, String casNo, SortedMap<String, String> packageParams, RequestHandler reqHandler) {
String sign = reqHandler.createSign(packageParams);
String xml2 = "<xml>";
xml2 +="<mch_appid>" +appId+ "</mch_appid>";
xml2 +="<mchid>"+merchantNo+"</mchid>";
xml2 +="<nonce_str>" + nonce_str +"</nonce_str>";
xml2 +="<partner_trade_no>"+ casNo +"</partner_trade_no>";//商户订单号
xml2 +="<openid>"+ openId +"</openid>";
xml2 +="<check_name>NO_CHECK</check_name>";
xml2 +="<amount>"+ price +"</amount>";
xml2 +="<desc>"+desc+"</desc>";
xml2 +="<spbill_create_ip>"+ip+"</spbill_create_ip>";
xml2 +="<sign>"+ sign +"</sign>";
xml2 +="</xml>";
return xml2;
}
/**
* 准备签名
* @param openId
* @param price
* @param ip
* @param desc
* @param appId
* @param merchantNo
* @param nonce_str
* @param casNo
* @return
*/
private static SortedMap<String, String> getStringStringSortedMap(String openId, String price, String ip, String desc, String appId, String merchantNo, String nonce_str, String casNo) {
SortedMap<String, String> packageParams = new TreeMap<String, String>();
packageParams.put("mch_appid", appId);
packageParams.put("mchid", merchantNo);
packageParams.put("nonce_str", nonce_str);
packageParams.put("partner_trade_no", casNo);
packageParams.put("amount", price);
packageParams.put("spbill_create_ip", ip);
packageParams.put("openid", openId);
packageParams.put("desc", desc);
packageParams.put("check_name", "NO_CHECK");
return packageParams;
}
}
b.HttpUtil请求类
public class HttpUtil {
/**
* 发送post请求
*
* @param url
* 请求地址
* @param outputEntity
* 发送内容
* @param isLoadCert
* 是否加载证书
*/
public static CloseableHttpResponse Post(String url, String outputEntity, boolean isLoadCert, String realPath) throws Exception {
Protocol.registerProtocol("https",
new Protocol("https", (ProtocolSocketFactory)new SSLProtocolSocketFactory(),
443));
HttpPost httpPost = new HttpPost(url);
// 得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
httpPost.addHeader("Content-Type", "text/xml");
httpPost.setEntity(new StringEntity(outputEntity, "UTF-8"));
if (isLoadCert) {
// 加载含有证书的http请求
SSLConnectionSocketFactory sslConnectionSocketFactory=CertUtil.initCert(realPath);
CloseableHttpResponse closeableHttpResponse=HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build().execute(httpPost);
return closeableHttpResponse;
} else {
return HttpClients.custom().build().execute(httpPost);
}
}
public static String getLocalIP() {
InetAddress addr = null;
try {
addr = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
byte[] ipAddr = addr.getAddress();
String ipAddrStr = "";
for (int i = 0; i < ipAddr.length; i++) {
if (i > 0) {
ipAddrStr += ".";
}
ipAddrStr += ipAddr[i] & 0xFF;
}
return ipAddrStr;
}
}
c.加载证书
/**
* 加载证书的类
* @author
* @since 2017/08/16
*/
public class CertUtil {
/**
* 加载证书
*/
public static SSLConnectionSocketFactory initCert( String realPath) throws Exception {
FileInputStream instream = null;
KeyStore keyStore = KeyStore.getInstance("PKCS12");
try {
//加载证书
File file=new File(realPath+"/apiclient_cert.p12");
instream = new FileInputStream(file);//证书文件地址
//商户号mch_id
keyStore.load(instream,mch_id.toCharArray());
}catch (Exception e){
System.out.println("文件不存在或者文件不可读或者文件是目录");
}finally {
if (null != instream) {
instream.close();
}
}
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, WeixinConfigConstans.mch_id.toCharArray()).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
return sslsf;
}
}
d.xml解析类
public class XmlUtils {
/**
* 得到一个类的内部类
* @param clz
* @return
*/
public static Class<?>[] getInnerClasses(Class<?> clz) {
Class<?>[] innerClasses = clz.getClasses();
if (innerClasses == null) {
return null;
}
List<Class<?>> result = new ArrayList<>();
result.addAll(Arrays.asList(innerClasses));
for (Class<?> inner : innerClasses) {
Class<?>[] innerClz = getInnerClasses(inner);
if (innerClz == null) {
continue;
}
result.addAll(Arrays.asList(innerClz));
}
return result.toArray(new Class<?>[0]);
}
/**
* @description 将xml字符串转换成map
* @param xml
* @return Map
*/
public static Map<String, String> readStringXmlOut(String xml) {
Map<String, String> map = new HashMap<String, String>();
Document doc = null;
try {
doc = DocumentHelper.parseText(xml); // 将字符串转为XML
Element rootElt = doc.getRootElement(); // 获取根节点
@SuppressWarnings("unchecked")
List<Element> list = rootElt.elements();// 获取根节点下所有节点
for (Element element : list) { // 遍历节点
map.put(element.getName(), element.getText()); // 节点的name为map的key,text为map的value
}
} catch (DocumentException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
}
六、感悟与展望
在人口红利消退的下半场,用户增长是公共难题。硅谷出现了“Growth Hacker”,中国出现了“精细化运营”,两岸不约而同的出现了以数据为基础,以人工智能为抓手的新增长模式。潜客发掘,流失召回,用户留存,状态跃迁,一批新的模型被定义出来,成为先进增长模式的关键词。流量为王的时代还没有过去,与君共勉。
注:有些技术细节不便在本文中展示体现,如有问题可以评论私信,必有回响。