SpringBoot后台结合小程序支付一般流程

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
        })
      }
    });
  },

记录一下 下班

猜你喜欢

转载自blog.csdn.net/yinlell/article/details/84666428