1.小程序支付的官方文档(点击即可) 参考官方文档为主
1.小程序默认是JSAPI支付方式 只需要HTTPS服务器 不需要设置安全域名目录
2.如果要设置安全域名目录也是在微信商户设定
2.安装HTTPS服务器(点击即可) 上一篇文档
3.微信商户的信息 微信商户登录
1.微信商户号MCH_ID
2.微信商户平台的秘钥MCH_KEY
4.小程序的信息小程序登录
1.先去提交小程序支付申请
2.小程序的appID
5.小程序支付用到的工具类 这些工具类是我一次一次整理出来的 请自行复制
1.获取服务器ip的工具类(点击即可)
2.生成签名的工具类(点击即可)
3.解析xml的工具类(点击即可)
4.HTPP请求的类(点击即可)
5.请求支付的类
import com.aui.stock.controller.mini.BaseController;
import com.aui.stock.util.http.HttpUtil;
import com.aui.stock.util.wx.xml.XmlUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
/**
* @date: 2018/11/28 17:06
* @author: YINLELE
* @description: 小程序支付的工具类
*/
public class WxPayHttp {
public static final Logger logger= LoggerFactory.getLogger(BaseController.class);
/**
* 请求微信下单的接口
*
* @param urlRequest
* @param xmlRequest
* void
*/
public static String doPostPayUnifiedOrder(String urlRequest, String xmlRequest) {
String xmlResponse = HttpUtil.doSSLPost(urlRequest, xmlRequest);
return xmlResponse;
}
/**
* 返回给微信异步通知的信息
* SUCCESS/FAIL
* SUCCESS表示商户接收通知成功并校验成功
*
* 返回信息,如非空,为错误原因:
* 签名失败
* 参数格式校验错误
* */
public static void responseXmlSuccess(HttpServletResponse response) throws Exception {
Map<String,String> map =new HashMap<String,String>();
map.put("return_code","SUCCESS");
map.put("return_msg","OK");
String xml = XmlUtil.createRequestXml(map);
logger.info("微信异步回调结束====> "+xml);
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
response.setHeader("Access-Control-Allow-Origin", "*");
PrintWriter writer = response.getWriter();
writer.write(xml);
writer.flush();
}
}
6.时间工具类(点击即可)
7.MD5工具类(点击即可)
6.操作步骤
1.生成必要的xml换取预支付ID
2.提供微信异步回调的接口,进行响应给微信接口
3.解析出来预支付ID,在进行计算小程序需要的必须要参数
4.小程序调起支付请求,支付成功回调自己的接口(默认提供get 和 post)两种类型的接口
公共使用参数
/**
* @date: 2018/10/26 16:42
* @author: YINLELE
* @description: 关于微信小程序的配置参数
*/
public class WxSPConfig {
/*微信公众号的ID*/
public static final String APP_ID_PUBLIC = "";
/*微信公众号的SECRT*/
public static final String SECRET_PUBLIC = "";
/*商户号*/
public static final String MCH_ID = "";
/*商户平台的秘钥*/
public static final String MCH_KEY="";
/*小程序的ID*/
public static final String APP_ID_SMALL_PROGRAM = "";
/*小程序的appsecret*/
public static final String SECRET_SMALL_PROGRAM = "";
/*获取微信小程序的access_token接口*/
public static final String ACCESS_TOKEN_SMALL_PROGRAM = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
/*获取微信网页的access_token*/
public static final String ACCEAA_Token_WEB = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
/*获取微信用户的信息*/
public static final String USER_INFO_URL = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
/*登录凭证校验。通过 wx.login() 接口获得临时登录凭证 code 后传到开发者服务器调用此接口完成登录流程*/
public static final String CODE2_SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code";
/*统一下单 URL*/
public static final String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
}
封装微信参数
import com.aui.stock.entity.OrderEntity;
import com.aui.stock.util.UUIDUtil;
import com.aui.stock.util.wx.ip.IpAddress;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
/**
* @date: 2018/11/28 16:45
* @author: YINLELE
* @description: 微信请求参数
*/
public class WxParam {
/*微信分配的小程序ID*/
private String appid;
/*微信支付分配的商户号*/
private String mch_id;
/*随机字符串,长度要求在32位以内*/
private String nonce_str;
/*签名*/
private String sign;
/*商品描述*/
private String body;
/*商户订单号*/
private String out_trade_no;
/*标价金额 默认分*/
private Integer total_fee;
/*终端IP*/
private String spbill_create_ip;
/*通知地址*/
private String notify_url;
/*交易类型*/
private String trade_type;
/*用户的openid*/
private String openid;
public String getAppid() {
return appid;
}
public void setAppid(String appid) {
this.appid = appid;
}
public String getMch_id() {
return mch_id;
}
public void setMch_id(String mch_id) {
this.mch_id = mch_id;
}
public String getNonce_str() {
return nonce_str;
}
public void setNonce_str(String nonce_str) {
this.nonce_str = nonce_str;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getOut_trade_no() {
return out_trade_no;
}
public void setOut_trade_no(String out_trade_no) {
this.out_trade_no = out_trade_no;
}
public Integer getTotal_fee() {
return total_fee;
}
public void setTotal_fee(Integer total_fee) {
this.total_fee = total_fee;
}
public String getSpbill_create_ip() {
return spbill_create_ip;
}
public void setSpbill_create_ip(String spbill_create_ip) {
this.spbill_create_ip = spbill_create_ip;
}
public String getNotify_url() {
return notify_url;
}
public void setNotify_url(String notify_url) {
this.notify_url = notify_url;
}
public String getTrade_type() {
return trade_type;
}
public void setTrade_type(String trade_type) {
this.trade_type = trade_type;
}
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
/*组装微信支付请求的参数*/
public void setWxParam(WxParam wxParam, OrderEntity orderEntity,
HttpServletRequest request,
String notify_url,String openid)
throws UnsupportedEncodingException {
wxParam.setAppid(WxSPConfig.APP_ID_SMALL_PROGRAM);
wxParam.setMch_id(WxSPConfig.MCH_ID);
wxParam.setSpbill_create_ip(IpAddress.getIpAddress(request));
wxParam.setTrade_type("JSAPI");
wxParam.setNonce_str(UUIDUtil.GenerateUUID8());
wxParam.setOut_trade_no(orderEntity.getSn());
wxParam.setBody(orderEntity.getDetail());
wxParam.setOpenid(openid);
//wxParam.setTotal_fee(Integer.valueOf(String.valueOf(orderEntity.getTotal()*100)));
wxParam.setTotal_fee(1);
wxParam.setNotify_url(notify_url);
wxParam.setOpenid(openid);
}
}
BeanUtil 属性拷贝 javaBean传Map Map转Javabean
import com.aui.stock.controller.mini.BaseController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class BeanUtil {
public static final Logger logger = LoggerFactory.getLogger(BaseController.class);
/**
* 根据clazz的属性进行拷贝
*/
public static <T> T copyProperties(Object source, Class<T> clazz) {
if (source == null) {
return null;
}
try {
T target = BeanUtils.instantiateClass(clazz);
BeanUtils.copyProperties(source, target);
return target;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 根据clazz的属性进行拷贝+忽略属性
*/
public static <T> T copyPropertiesIgnore(Object source, Class<T> clazz,
String... ignoreProperties) {
if (source == null) {
return null;
}
try {
T target = BeanUtils.instantiateClass(clazz);
BeanUtils.copyProperties(source, target, ignoreProperties);
return target;
} catch (Exception e) {
}
return null;
}
/**
* 拷贝指定属性
*/
public static <T> T copyPropertiesSpecific(Object source, Class<T> clazz,
String... specificProperties) {
if (source == null) {
return null;
}
try {
T target = BeanUtils.instantiate(clazz);
if (specificProperties == null) {
return target;
}
List<String> specificList = Arrays.asList(specificProperties);
copySpecificProperties(source, target, specificList);
return target;
} catch (Exception e) {
}
return null;
}
/**
* 拷贝指定属性
*/
public static <T> T copyPropertiesSpecific(Object source, T target,
String... specificProperties) {
if (source == null) {
return target;
}
try {
if (specificProperties != null) {
List<String> specificList = Arrays.asList(specificProperties);
copySpecificProperties(source, target, specificList);
}
} catch (Exception e) {
}
return target;
}
private static void copySpecificProperties(final Object source,
final Object target, final Iterable<String> properties) {
final BeanWrapper src = new BeanWrapperImpl(source);
final BeanWrapper trg = new BeanWrapperImpl(target);
for (final String propertyName : properties) {
trg.setPropertyValue(propertyName,
src.getPropertyValue(propertyName));
}
}
/**
* * 实体类转Map<String,String>
* * @param obj
* * @return
*
*/
public static Map<String, String> convertBeanToMap(Object obj) {
if (obj == null) {
return null;
}
Map<String, String> map = new HashMap<String, String>();
try {
BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor property : propertyDescriptors) {
String key = property.getName();
// 过滤class属性
if (!key.equals("class")) {
// 得到property对应的getter方法
Method getter = property.getReadMethod();
Object value = getter.invoke(obj);
if (null == value) {
map.put(key, "");
} else {
map.put(key, String.valueOf(value));
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
/**
* * map 转实体类
* * @param clazz
* * @param map
* * @param <T>
* * @return
*
*/
public static <T> T convertMapToBean(Class<T> clazz, Map<String, Object> map) {
T obj = null;
try {
BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
obj = clazz.newInstance(); // 创建 JavaBean 对象
// 给 JavaBean 对象的属性赋值
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (int i = 0; i < propertyDescriptors.length; i++) {
PropertyDescriptor descriptor = propertyDescriptors[i];
String propertyName = descriptor.getName();
if (map.containsKey(propertyName)) {
// 下面一句可以 try 起来,这样当一个属性赋值失败的时候就不会影响其他属性赋值。
Object value = map.get(propertyName);
if ("".equals(value)) {
value = null;
}
Object[] args = new Object[1];
args[0] = value;
descriptor.getWriteMethod().invoke(obj, args);
}
}
} catch (IllegalAccessException e) {
logger.error("convertMapToBean 实例化JavaBean失败 Error{}", e);
} catch (IntrospectionException e) {
logger.error("convertMapToBean 分析类属性失败 Error{}", e);
} catch (IllegalArgumentException e) {
logger.error("convertMapToBean 映射错误 Error{}", e);
} catch (InstantiationException e) {
logger.error("convertMapToBean 实例化 JavaBean 失败 Error{}", e);
} catch (InvocationTargetException e) {
logger.error("convertMapToBean字段映射失败 Error{}", e);
} catch (Exception e) {
logger.error("convertMapToBean Error{}", e);
}
return (T) obj;
}
}
实现操作
import com.aui.stock.controller.mini.config.WxParam;
import com.aui.stock.controller.mini.config.WxSPConfig;
import com.aui.stock.entity.OrderEntity;
import com.aui.stock.entity.type.OrderStatusType;
import com.aui.stock.framework.response.ResponseBean;
import com.aui.stock.framework.response.ResponseUtil;
import com.aui.stock.service.OrderService;
import com.aui.stock.util.BeanUtil;
import com.aui.stock.util.StringUtil;
import com.aui.stock.util.UUIDUtil;
import com.aui.stock.util.date.DateUtil;
import com.aui.stock.util.json.FastJsonUtil;
import com.aui.stock.util.wx.pay.WxPayHttp;
import com.aui.stock.util.wx.sign.Signature;
import com.aui.stock.util.wx.xml.XmlUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @date: 2018/11/28 10:50
* @author: YINLELE
* @description:小程序的支付
*/
@Api(value = "Mini.wxSpPay", description = "小程序的支付")
@RestController
@RequestMapping("mini/wx/sp/")
public class MiniWxSPPayController extends BaseController {
@Autowired
private OrderService orderService;
/*微信通知异步回调*/
public static final String notify_url="https://xx.xxx.com/mini/wx/sp/notifyResult";
@ApiOperation(value = "订单支付", notes = "订单支付==>微信小程序支付")
@GetMapping("/pay/{sn}/{openid}")
public ResponseBean spPay(@PathVariable String sn,
@PathVariable String openid,
HttpServletRequest request) throws Exception {
logger.info("产品订单的sn====> " + sn+" 用户openId====> "+openid+" 小程序支付开始");
if(StringUtil.isNullOrEmpty(sn)||StringUtil.isNullOrEmpty(openid)){
return ResponseUtil.error(80500,"请求微信支付失败");
}
OrderEntity orderEntity = orderService.findBySn(sn);
if (orderEntity.getStatus().getValue() == OrderStatusType.ORDER_PAYED.getValue() || orderEntity.getStatus().getValue() == OrderStatusType.ORDER_SUCCESS.getValue()) {
return ResponseUtil.success("订单已支付或者已完成");
}
if (orderEntity.getStatus().getValue() == OrderStatusType.ORDER_PAYING.getValue()) {
WxParam wxParam = new WxParam();
wxParam.setWxParam(wxParam,orderEntity,request,notify_url,openid);
Map<String, String> wxParamTOMap = BeanUtil.convertBeanToMap(wxParam);
String sign = Signature.getSign(wxParamTOMap, WxSPConfig.MCH_KEY);
wxParamTOMap.put("sign",sign);
logger.info("微信小程序支付请求的Map===>"+wxParamTOMap);
String xmlResponse = WxPayHttp.doPostPayUnifiedOrder(WxSPConfig.UNIFIED_ORDER_URL, XmlUtil.createRequestXml(wxParamTOMap));
Map<String,String> wxParamVO = parseXmlResponse(xmlResponse);
return ResponseUtil.success(wxParamVO);
}
return ResponseUtil.error(80500,"请求微信支付失败");
}
@ApiOperation(value = "微信支付成功回调",notes = "微信支付成功异步回调,修改订单信息")
@GetMapping("/notifyResult")
public void notifyResultGet(HttpServletRequest request, HttpServletResponse response) throws Exception {
logger.info("微信回调requestGet===>"+request);
notifyResult(request,response);
}
@ApiOperation(value = "微信支付成功回调",notes = "微信支付成功异步回调,修改订单信息")
@PostMapping("/notifyResult")
public void notifyResultPost(HttpServletRequest request, HttpServletResponse response) throws Exception {
logger.info("微信回调requestPOSt===>"+request);
notifyResult(request,response);
}
private void notifyResult (HttpServletRequest request, HttpServletResponse response) throws Exception {
logger.info("微信回调request===>"+request);
//获取微信异步通知的数据
Map<String, String> paramsToMap = XmlUtil.reqParamsToMap(request);
logger.info("微信回调参数map===>"+paramsToMap);
//校验微信的sign值
boolean flag = Signature.validateSign(paramsToMap, WxSPConfig.MCH_KEY);
if(flag){
OrderEntity orderEntity = orderService.findBySn(paramsToMap.get("out_trade_no"));
logger.info("微信回调订单实体===>"+orderEntity);
//判断是否是未支付状态
if(orderEntity.getStatus().getValue()==OrderStatusType.ORDER_PAYING.getValue()){
//更新订单状态
orderEntity.setStatus(OrderStatusType.ORDER_PAYED);
orderEntity.setPayment("微信支付");
orderEntity.setTransaction_id(paramsToMap.get("transaction_id"));
orderEntity.setTime_end(DateUtil.timeToDate(Long.valueOf(paramsToMap.get("time_end")),DateUtil.DATE_TIME_PATTERN));
orderEntity.setNotify_result(FastJsonUtil.toJson(paramsToMap));
orderService.save(orderEntity);
//如果订单修改成功,通知微信接口不要在回调这个接口
WxPayHttp.responseXmlSuccess(response);
}
}
}
/*解析微信请求响应的数据并返回小程序需要的数据*/
private Map<String,String> parseXmlResponse(String xmlResponse){
Map<String,String> resultMap=new HashMap<>();
//判断sign值是否正确
Boolean flag = Signature.validateResponseXmlToMap(xmlResponse);
if(flag){
Map<String, String> map = XmlUtil.xmlToMap(xmlResponse);
logger.info("微信支付请求响应的数据的预支付ID====>"+map.get("prepay_id"));
resultMap.put("appId",WxSPConfig.APP_ID_SMALL_PROGRAM);
resultMap.put("timeStamp",String.valueOf(DateUtil.strToTime(DateUtil.format(new Date()))));
resultMap.put("nonceStr",UUIDUtil.GenerateUUID8());
resultMap.put("package","prepay_id="+map.get("prepay_id"));
resultMap.put("signType","MD5");
String sign = Signature.getSign(resultMap, WxSPConfig.MCH_KEY);
resultMap.put("paySign",sign);
}
return resultMap;
}
}
7.小程序支付的代码(请不要在wx.requestPayment()函数内获取参数的时候加入'' 否则会报错
小程序调用微信支付返回错误 “调用支付JSAPI缺少参数:total_fee")
requestPayParam() {
let that = this;
let openId = app.globalData.userInfo.openId;
if (util.isBlank(openId)) {
wx.showToast({
title: '请先去登录',
icon: 'loading',
duration: 2000
})
return false;
}
let sn = that.data.orderId;
if (util.isBlank(sn)) {
wx.showToast({
title: '订单号不能为空',
icon: 'loading',
duration: 2000
})
return false;
}
httpUtil.http_get(api.MiniPay + sn + "/" + openId, {}).then((res) => {
console.log("预支付信息====>>>" + JSON.stringify(res));
if (res.status == 80200) {
let payParam = res.data;
wx.requestPayment({
timeStamp: payParam.timeStamp,
nonceStr: payParam.nonceStr,
package: payParam.package,
signType: payParam.signType,
paySign: payParam.paySign,
'success': function (res) {
console.log(JSON.stringify(res));
wx.redirectTo({
url: '../../../pages/pay/payresult/payresult?status=true&sn=' + sn,
})
},
'fail': function (res) {
console.log(JSON.stringify(res));
wx.redirectTo({
url: '../../../pages/pay/payresult/payresult?status=false&sn=' + sn,
})
}
})
} else {
wx.showToast({
title: that.data.fail,
icon: 'loading',
duration: 2000
})
}
});
},
记录一下 下班