前言:最近写小程序中微信支付!
其实支付很简单,不就是些请求接口然后返回结果最后回调处理业务逻辑!
官网:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=5
话不多说:看代码
/**
* 开始
* 进行支付
* 参数自己配置上
*/
@ApiOperation(value = "付款")
@RequestMapping("/paymentOrder")
public Object paymentOrder() {
String nonceStr = CurrencyUtils.getRandomString(32);
Map<Object, Object> resultObj = new TreeMap();
try {
Map<Object, Object> parame = new TreeMap<Object, Object>();
//小程序id
parame.put("appid", ResourceUtil.getConfigByName("wx.appId"));
// 商家账号。
parame.put("mch_id", ResourceUtil.getConfigByName("wx.mchId"));
String randomStr = CurrencyUtils.getRandomNum(18).toUpperCase();
// 随机字符串
parame.put("nonce_str", randomStr);
//商户订单编号:建议商品订单号设置 随机号+订单id,然后存在数据库,回调的时候根据随机号+订单id查询在做业务逻辑处理
parame.put("out_trade_no", "123456");
// 商品描述
parame.put("body", "服务类型-支付");
//支付金额 单位是分
BigDecimal money = new BigDecimal(1);//一块钱
parame.put("total_fee", money.multiply(new BigDecimal(100)).intValue());
// 回调地址
parame.put("notify_url", ResourceUtil.getConfigByName("wx.notifyUrl"));
// 交易类型APP
parame.put("trade_type", ResourceUtil.getConfigByName("wx.tradeType"));
//客户ip
parame.put("spbill_create_ip", getClientIp());
//微信客户的openid
parame.put("openid", "xxxxxxxxxxx");
//设置签名
String sign = CurrencyUtils.arraySign(parame, ResourceUtil.getConfigByName("wx.paySignKey"));
// 数字签证
parame.put("sign", sign);
//转成xml格式
String xml = CurrencyUtils.convertMap2Xml(parame);
logger.info("xml:" + xml);
/*请求url :https://api.mch.weixin.qq.com/pay/unifiedorder */
Map<String, Object> resultUn = CurrencyUtils.xmlStrToMap(CurrencyUtils.requestOnce(ResourceUtil.getConfigByName("wx.uniformorder"), xml));
// 响应报文
String return_code = CurrencyUtils.getString("return_code", resultUn);
String return_msg = CurrencyUtils.getString("return_msg", resultUn);
if (return_code.equalsIgnoreCase("FAIL")) {
return toResponsFail("支付失败," + return_msg);
} else if (return_code.equalsIgnoreCase("SUCCESS")) {
// 返回数据
String result_code = CurrencyUtils.getString("result_code", resultUn);
String err_code_des = CurrencyUtils.getString("err_code_des", resultUn);
if (result_code.equalsIgnoreCase("FAIL")) {
return toResponsFail("支付失败," + err_code_des);
} else if (result_code.equalsIgnoreCase("SUCCESS")) {
String prepay_id = MapUtils.getString("prepay_id", resultUn);
// 先生成paySign 参考https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=5
resultObj.put("appId", ResourceUtil.getConfigByName("wx.appId"));
resultObj.put("timeStamp", CurrencyUtils.timeToStr(System.currentTimeMillis() / 1000, CurrencyUtils.DATE_TIME_PATTERN));
resultObj.put("nonceStr", nonceStr);
resultObj.put("package", "prepay_id=" + prepay_id);
resultObj.put("signType", "MD5");
String paySign = CurrencyUtils.arraySign(resultObj, ResourceUtil.getConfigByName("wx.paySignKey"));
resultObj.put("paySign", paySign);
//业务处理
/* order.setPayId(prepay_id);
order.setPayStatus(1);
aboutService.update(order);*/
return toResponsObject(0, "微信统一订单下单成功", resultObj);
}
}
} catch (Exception e) {
e.printStackTrace();
return toResponsFail("下单失败,error=" + e.getMessage());
}
return toResponsFail("下单失败");
}
工具类有点多 暂时没有整理pom.xml文件 缺少包自己找
package com.platform.utils.excel;
import com.alibaba.druid.support.logging.Log;
import com.alibaba.druid.support.logging.LogFactory;
import com.platform.utils.ResourceUtil;
import com.platform.utils.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* @Author: HUN
* @Date: 2019/11/6 16:06
* @Description:
*/
public class CurrencyUtils {
private static Log logger = LogFactory.getLog(CurrencyUtils.class);
/**
* 获取随机字符串
*
* @param num
* @return
*/
public static String getRandomString(Integer num) {
String base = "abcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < num; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
/**
* 获取随机字符串
*
* @param num
* @return
*/
public static String getRandomNum(Integer num) {
String base = "0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < num; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
/* * 生成 MD5
*
* @param data 待处理数据
* @return MD5结果
*/
public static String getMessageDigest(String data) {
StringBuilder sb = new StringBuilder();
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest(data.getBytes("UTF-8"));
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
} catch (Exception e) {
return null;
}
return sb.toString().toUpperCase();
}
/**
* 方法描述:根据签名加密请求参数
* 创建时间:2017年6月8日 上午11:28:52
* 作者: xubo
*
* @param
* @return
*/
public static String arraySign(Map<Object, Object> params, String paySignKey) {
boolean encode = false;
Set<Object> keysSet = params.keySet();
Object[] keys = keysSet.toArray();
Arrays.sort(keys);
StringBuffer temp = new StringBuffer();
boolean first = true;
for (Object key : keys) {
if (first) {
first = false;
} else {
temp.append("&");
}
temp.append(key).append("=");
Object value = params.get(key);
String valueString = "";
if (null != value) {
valueString = value.toString();
}
if (encode) {
try {
temp.append(URLEncoder.encode(valueString, "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else {
temp.append(valueString);
}
}
temp.append("&key=");
temp.append(paySignKey);
System.out.println(temp.toString());
String packageSign = getMessageDigest(temp.toString());
return packageSign;
}
//转成xml格式
public static String convertMap2Xml(Map<Object, Object> paraMap) {
StringBuffer xmlStr = new StringBuffer();
if (paraMap != null) {
xmlStr.append("<xml>");
Set<Object> keySet = paraMap.keySet();
Iterator<Object> keyIte = keySet.iterator();
while (keyIte.hasNext()) {
String key = (String) keyIte.next();
String val = String.valueOf(paraMap.get(key));
xmlStr.append("<");
xmlStr.append(key);
xmlStr.append(">");
xmlStr.append(val);
xmlStr.append("</");
xmlStr.append(key);
xmlStr.append(">");
}
xmlStr.append("</xml>");
}
return xmlStr.toString();
}
/**
* 请求,只请求一次,不做重试
*
* @param url
* @param data
* @return
* @throws Exception
*/
public static String requestOnce(final String url, String data) throws Exception {
BasicHttpClientConnectionManager connManager;
connManager = new BasicHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", SSLConnectionSocketFactory.getSocketFactory())
.build(),
null,
null,
null
);
HttpClient httpClient = HttpClientBuilder.create()
.setConnectionManager(connManager)
.build();
HttpPost httpPost = new HttpPost(url);
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(5000)
.setConnectTimeout(5000)
.setConnectionRequestTimeout(10000).build();
httpPost.setConfig(requestConfig);
StringEntity postEntity = new StringEntity(data, "UTF-8");
httpPost.addHeader("Content-Type", "text/xml");
httpPost.addHeader("User-Agent", "wxpay sdk java v1.0 " + ResourceUtil.getConfigByName("wx.mchId"));
httpPost.setEntity(postEntity);
HttpResponse httpResponse = httpClient.execute(httpPost);
HttpEntity httpEntity = httpResponse.getEntity();
String reusltObj = EntityUtils.toString(httpEntity, "UTF-8");
logger.info("请求结果:" + reusltObj);
return reusltObj;
}
/**
* 将xml格式的字符串转换成Map对象
*
* @param xmlStr xml格式的字符串
* @return Map对象
* @throws Exception 异常
*/
public static Map<String, Object> xmlStrToMap(String xmlStr) throws Exception {
if (StringUtils.isNullOrEmpty(xmlStr)) {
return null;
}
Map<String, Object> map = new HashMap<String, Object>();
// 将xml格式的字符串转换成Document对象
Document doc = DocumentHelper.parseText(xmlStr);
// 获取根节点
Element root = doc.getRootElement();
// 获取根节点下的所有元素
List children = root.elements();
// 循环所有子元素
if (children != null && children.size() > 0) {
for (int i = 0; i < children.size(); i++) {
Element child = (Element) children.get(i);
map.put(child.getName(), child.getTextTrim());
}
}
return map;
}
/**
*
* @param key
* @param map 从map中获取key的值
* @return
*/
public static String getString(String key, Map<String, Object> map) {
if (map == null || key == null)
throw new IllegalArgumentException();
if (!map.containsKey(key))
return null;
Object value = map.get(key);
if (value == null)
return null;
return value.toString();
}
/**
* 时间格式(yyyy-MM-dd HH:mm:ss)
*/
public final static String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
/**
* 获取时间戳
* @param time
* @param pattern
* @return
*/
public static String timeToStr(Long time, String pattern) {
SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);
if (time.toString().length() < 13) {
time = time * 1000L;
}
Date date = new Date(time);
String value = dateFormat.format(date);
return value;
}
/**
* @param requestCode
* @param msg
* @param data
* @return Map<String,Object>
* @throws
* @Description:构建统一格式返回对象
* @date 2016年9月2日
* @author zhuliyun
*/
public Map<String, Object> toResponsObject(int requestCode, String msg, Object data) {
Map<String, Object> obj = new HashMap<String, Object>();
obj.put("errno", requestCode);
obj.put("errmsg", msg);
if (data != null)
obj.put("data", data);
return obj;
}
/**
* 错误的json格式
* @param msg
* @return
*/
public Map<String, Object> toResponsFail(String msg) {
return toResponsObject(1, msg, null);
}
/**
* 将Map对象通过反射机制转换成Bean对象
*
* @param map 存放数据的map对象
* @param clazz 待转换的class
* @return 转换后的Bean对象
* @throws Exception 异常
*/
public static Object mapToBean(Map<String, Object> map, Class clazz) throws Exception {
Object obj = clazz.newInstance();
if (map != null && map.size() > 0) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
String propertyName = entry.getKey();
Object value = entry.getValue();
String setMethodName = "set" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
Field field = getClassField(clazz, propertyName);
if (field != null) {
Class fieldTypeClass = field.getType();
value = convertValType(value, fieldTypeClass);
clazz.getMethod(setMethodName, field.getType()).invoke(obj, value);
}
}
}
return obj;
}
/**
* 获取指定字段名称查找在class中的对应的Field对象(包括查找父类)
*
* @param clazz 指定的class
* @param fieldName 字段名称
* @return Field对象
*/
private static Field getClassField(Class clazz, String fieldName) {
if (Object.class.getName().equals(clazz.getName())) {
return null;
}
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
if (field.getName().equals(fieldName)) {
return field;
}
}
Class superClass = clazz.getSuperclass();
if (superClass != null) {// 简单的递归一下
return getClassField(superClass, fieldName);
}
return null;
}
/**
* 将Object类型的值,转换成bean对象属性里对应的类型值
*
* @param value Object对象值
* @param fieldTypeClass 属性的类型
* @return 转换后的值
*/
private static Object convertValType(Object value, Class fieldTypeClass) {
Object retVal = null;
if (Long.class.getName().equals(fieldTypeClass.getName())
|| long.class.getName().equals(fieldTypeClass.getName())) {
retVal = Long.parseLong(value.toString());
} else if (Integer.class.getName().equals(fieldTypeClass.getName())
|| int.class.getName().equals(fieldTypeClass.getName())) {
retVal = Integer.parseInt(value.toString());
} else if (Float.class.getName().equals(fieldTypeClass.getName())
|| float.class.getName().equals(fieldTypeClass.getName())) {
retVal = Float.parseFloat(value.toString());
} else if (Double.class.getName().equals(fieldTypeClass.getName())
|| double.class.getName().equals(fieldTypeClass.getName())) {
retVal = Double.parseDouble(value.toString());
} else {
retVal = value;
}
return retVal;
}
}
回调接口
注意你请求时记回调设置要用域名或者网穿透。否则微信不会回调。
/**
* 微信订单回调接口
*
* @return
*/
@ApiOperation(value = "直接微信订单回调接口")
@RequestMapping(value = "/notify", method = RequestMethod.POST, produces = "text/html;charset=UTF-8")
@ResponseBody
public void notify(HttpServletRequest request, HttpServletResponse response) {
try {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
response.setHeader("Access-Control-Allow-Origin", "*");
InputStream in = request.getInputStream();
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
out.close();
in.close();
//xml数据
String reponseXml = new String(out.toByteArray(), "utf-8");
WechatRefundApiResult result = (WechatRefundApiResult) CurrencyUtils.xmlStrToBean(reponseXml, WechatRefundApiResult.class);
String result_code = result.getResult_code();
if (result_code.equalsIgnoreCase("FAIL")) {
//订单编号
String out_trade_no = result.getOut_trade_no();
logger.error("订单" + out_trade_no + "支付失败");
response.getWriter().write(setXml("SUCCESS", "OK"));
} else if (result_code.equalsIgnoreCase("SUCCESS")) {
//订单编号
String out_trade_no = result.getOut_trade_no();
logger.error("订单" + out_trade_no + "支付成功");
// 业务处理
//....
response.getWriter().write(setXml("SUCCESS", "OK"));
}
} catch (Exception e) {
e.printStackTrace();
return;
}
}
//返回微信服务
public static String setXml(String return_code, String return_msg) {
return "<xml><return_code><![CDATA[" + return_code + "]]></return_code><return_msg><![CDATA[" + return_msg + "]]></return_msg></xml>";
}
回调xml转实体类
public class WechatRefundApiResult {
private String return_code;
private String return_msg;
private String result_code;
private String err_code;
private String err_code_des;
private String appid;
private String mch_id;
private String device_info;
private String nonce_str;
private String sign;
private String transaction_id;
private String out_trade_no;
private String out_refund_no;
private String refund_id;
private String refund_channel;
private String refund_fee;
private String settlement_refund_fee;
private String total_fee;
private String settlement_total_fee;
private String fee_type;
private String cash_fee;
private String cash_refund_fee;
private String refund_status;
}
小程序代码
主要是这个方法:requestPayment
前台请求支付接口返回值放入 requestPayment方法中.然后小程序就会自动调出支付!
这里建议看官网!前台不是我写的。
到这里差不多完了代码就这么多!还有一种是扫码支付。但是思想是一样的,微信请求url不同,参数差不多,多了生产个二维码而已!具体就不介绍了。
讲讲有坑的地方,
1.订单号这个坑,当时我是碰到了,主要是生成订单后不支付,就占用了订单号,这时候客户去修改订单,有可能会变动金额,就支付不了,还有正式上线时,出现该订单已支付这种问题,其实就是测试订单时把改订单号已经使用了。所以建议随机号加id,存在订单中。
2.金额是分作为单位,
3.最难解决的是签名问题。建议看仔细官网签名生成规则!
4.最后回调调试。