小程序微信支付实现---微服务之支付服务

目录

 

开发前准备

小程序支付开发帮助文档

JSAPI支付

小程序支付业务流程图

统一下单API接口帮助文档

微信支付配置文件:Configure

maven配置SAXBuilder和Gson 

微信支付工具类:PayUtils  

获取客户端IP地址工具类:IPAddr

请求支付订单基础参数

调用微信支付接口

后端统一下单和支付回调接口

返回参数到前端起调微信支付


开发前准备

1、登录微信公众后台:小程序开通微信支付并绑定商户号(在小程序后台微信支付模块按提示操作即可)

2、登录商户平台:商户号对小程序绑定授权(产品中心-APPID授权管理,另外,如果是公众号还需在开发配置中配置支付授权目录)

注意:如果以上2条没有配置好则后面接口返回会出现 appid 和 mch_id 不匹配 (Lz第一次入坑)

3、准备好小程序的appid,微信支付的商户号,支付秘钥

4、小程序内调用登录接口,获取到用户的openid(小程序登录开发指南) 

小程序支付开发帮助文档

JSAPI支付

小程序支付业务流程图

统一下单API接口帮助文档

接下来给大家分享实现代码

微信支付配置文件:Configure

注意 : Configure 中 key不是商户秘钥,而是手动生成的key,LZ第二次入坑,key写的是商户密钥,出现签名错误,为此专门记录一篇博客,希望大家别再掉坑里了,看key如何生成戳这里 微信小程序支付统一下单接口返回签名错误已解决

/**
 * @ClassName: Configure
 * @Author: liuyaolong
 * @Description: 微信端支付配置信息
 * @Version: 1.0
 */
public class Configure {

    //微信小程序appid
    public static String appId = "";

    //微信小程序appsecret
    public static String appSecret = "";

    //微信商户号
    public static String mch_id="";

    //手动生成的key
    public static final String key = "";

    //获取微信Openid的请求地址
    public static String WxGetOpenIdUrl = "";

    //支付成功后的服务器回调url
    public static final String notify_url="";

    //签名方式
    public static final String SIGNTYPE = "MD5";

    //交易类型
    public static final String TRADETYPE = "JSAPI";

    //微信统一下单接口地址
    public static final String pay_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";



    public static String getAppId() {
        return appId;
    }

    public static void setAppId(String appId) {
        Configure.appId = appId;
    }

    public static String getAppSecret() {
        return appSecret;
    }

    public static void setAppSecret(String appSecret) {
        Configure.appSecret = appSecret;
    }

    public static String getMch_id() {
        return mch_id;
    }

    public static void setMch_id(String mch_id) {
        Configure.mch_id = mch_id;
    }

    public static String getKey() {
        return key;
    }

    public static String getWxGetOpenIdUrl() {
        return WxGetOpenIdUrl;
    }

    public static void setWxGetOpenIdUrl(String wxGetOpenIdUrl) {
        WxGetOpenIdUrl = wxGetOpenIdUrl;
    }

    public static String getNotify_url() {
        return notify_url;
    }

    public static String getSIGNTYPE() {
        return SIGNTYPE;
    }

    public static String getTRADETYPE() {
        return TRADETYPE;
    }

    public static String getPay_url() {
        return pay_url;
    }
}

PayUtils在使用时需要在Maven中配置SAXBuilder和Gson

SAXBuilder解读分析: Java解析XML之SAXBuilder用法

maven配置SAXBuilder和Gson

<properties>
    <gson.version>2.2.4</gson.version>
    <saxbuilder.version>2.0.5</saxbuilder.version>
</properties>
 
<!-- SAXBuilder -->
<dependency>
    <groupId>org.jdom</groupId>
    <artifactId>jdom2</artifactId>
    <version>${saxbuilder.version}</version>
 </dependency>
<!-- gson -->
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>${gson.version}</version>
</dependency>

微信支付工具类:PayUtils  

/**
 * @ClassName: PayUtils
 * @Author: liuyaolong
 * @Description: 微信支付工具类
 * @Version: 1.0
 */
public class PayUtils {


    /**
     * Map值过滤,除去 Map中值为空值和签名参数
     * @param params 签名参数
     * @return
     */
    public static Map<String,String> paramsFilter(Map<String,String> params){
        Map<String,String> rsMap = new HashMap<String,String>();
        if(params == null || params.size() <= 0){
            return rsMap;
        }
        for (String key : params.keySet()){
            String value = params.get(key);
            if(value == null || value.equals("") || key.equalsIgnoreCase("sign") || key.equalsIgnoreCase("sign_type")){
                continue;
            }
            rsMap.put(key,value);
        }
        return rsMap;
    }

    /**
     * 把数组所有元素排序,并按照  “参数=参数值” 的模式用  “&”  字符拼接成字符串
     * @param params 需要排序并参与字符拼接的参数组
     * @return 拼接后字符串
     */
    public static String createLinkString(Map<String, String> params) {
        List<String> keys = new ArrayList<>(params.keySet());
        Collections.sort(keys);
        String prestr = "";
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);
            if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符
                prestr = prestr + key + "=" + value;
            } else {
                prestr = prestr + key + "=" + value + "&";
            }
        }
        return prestr;
    }

    /**
     * 签名字符串
     * @param text 需要签名的字符串
     * @param key 密钥
     * @param input_charset 编码格式
     * @return 签名结果
     */
    public static String sign(String text, String key, String input_charset) {
        text = text + "&key=" + key;
        return DigestUtils.md5Hex(getContentBytes(text, input_charset));
    }

    /**
     * 签名字符串
     * @param text 需要签名的字符串
     * @param sign 签名结果
     * @param key 密钥
     * @param input_charset 编码格式
     * @return 签名结果
     */
    public static boolean verify(String text, String sign, String key, String input_charset) {
        text = text +"&key="+ key;
        String mysign = DigestUtils.md5Hex(getContentBytes(text, input_charset));
        if (mysign.equals(sign)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * @param content
     * @param charset
     * @return
     * @throws java.security.SignatureException
     * @throws UnsupportedEncodingException
     */
    public static byte[] getContentBytes(String content, String charset) {
        if (charset == null || "".equals(charset)) {
            return content.getBytes();
        }
        try {
            return content.getBytes(charset);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);
        }
    }

    /** HttpRequest 请求
     * @param requestUrl    请求地址
     * @param requestMethod 请求方法
     * @param outputStr     参数
     */
    public static String httpRequest(String requestUrl, String requestMethod, String outputStr) {
        // 创建SSLContext
        StringBuffer buffer = null;
        try {
            URL url = new URL(requestUrl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod(requestMethod);
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.connect();
            //往服务器端写内容
            if (null != outputStr) {
                OutputStream os = conn.getOutputStream();
                os.write(outputStr.getBytes("utf-8"));
                os.close();
            }
            // 读取服务器端返回的内容
            InputStream is = conn.getInputStream();
            InputStreamReader isr = new InputStreamReader(is, "utf-8");
            BufferedReader br = new BufferedReader(isr);
            buffer = new StringBuffer();
            String line = null;
            while ((line = br.readLine()) != null) {
                buffer.append(line);
            }
            br.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return buffer.toString();
    }

    public static String urlEncodeUTF8(String source) {
        String result = source;
        try {
            result = java.net.URLEncoder.encode(source, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
     * @param strxml
     * @return
     * @throws org.jdom2.JDOMException
     * @throws IOException
     */
    public static Map doXMLParse(String strxml) throws Exception {
        if (null == strxml || "".equals(strxml)) {
            return null;
        }

        Map m = new HashMap();
        InputStream in = StringInputstream(strxml);

        SAXBuilder builder = new SAXBuilder();
        Document doc = builder.build(in);
        Element root = doc.getRootElement();
        List list = root.getChildren();
        Iterator it = list.iterator();
        while (it.hasNext()) {
            Element e = (Element) it.next();
            String k = e.getName();
            String v = "";
            List children = e.getChildren();
            if (children.isEmpty()) {
                v = e.getTextNormalize();
            } else {
                v = getChildrenText(children);
            }

            m.put(k, v);
        }

        //关闭流
        in.close();

        return m;
    }

    /**
     * 获取子结点的xml
     *
     * @param children
     * @return String
     */
    public static String getChildrenText(List children) {
        StringBuffer sb = new StringBuffer();
        if (!children.isEmpty()) {
            Iterator it = children.iterator();
            while (it.hasNext()) {
                Element e = (Element) it.next();
                String name = e.getName();
                String value = e.getTextNormalize();
                List list = e.getChildren();
                sb.append("<" + name + ">");
                if (!list.isEmpty()) {
                    sb.append(getChildrenText(list));
                }
                sb.append(value);
                sb.append("</" + name + ">");
            }
        }
        return sb.toString();
    }

    public static InputStream StringInputstream(String str) {
        return new ByteArrayInputStream(str.getBytes());
    }

    /**
     * java对象转字符串
     * @param <T> 泛型占位符
     * @param obj T
     * @return String
     */
    public static <T> String toJson(T obj) {
        Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").enableComplexMapKeySerialization().create();
        return gson.toJson(obj);
    }

获取客户端IP地址工具类:IPAddr

/**
 * @ClassName: IPAddr
 * @Author: liuyaolong
 * @Description: IP地址工具类
 * @Version: 1.0
 */
public class IPAddr {

    /**
     * 获取Ip地址,多级反向代理
     * @param request
     * @return
     */
    public static String getIpaddr(HttpServletRequest request){
        String ipAddress = request.getHeader("x-forwarded-for");
        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("Proxy-Client-IP");
        }
        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("WL-Proxy-Client-IP");
        }
        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getRemoteAddr();
            if(ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")){
                //根据网卡取本机配置的IP
                InetAddress inet=null;
                try {
                    inet = InetAddress.getLocalHost();
                } catch (UnknownHostException e) {
                    e.printStackTrace();
                }
                ipAddress= inet.getHostAddress();
            }
        }
        //对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
        if(ipAddress!=null && ipAddress.length()>15){ //"***.***.***.***".length() = 15
            if(ipAddress.indexOf(",")>0){
                ipAddress = ipAddress.substring(0,ipAddress.indexOf(","));
            }
        }
        return ipAddress;
    }
}

请求支付订单基础参数

/**
 * @ClassName: orderInfo
 * @Author: liuyaolong
 * @Description: 订单基础信息
 * @Version: 1.0
 */
@Data
public class OrderInfo implements Serializable {

    private static final long serialVersionUID = 1L;

    @NotBlank(message = "orderId can not be null !")
    private String orderId;//订单编号

    @NotBlank(message = "title can not be null !")
    private String title;//商品标题

    @NotNull(message = "money can not be null !")
    private Integer money;//订单金额,单位:分

    @NotBlank(message = "openId can not be null !")
    private String openId;//小程序用户唯一标识

    private String intro;//商品介绍

}

调用微信支付接口Service

/**
 * @ClassName: WeChatPayService
 * @Author: liuyaolong
 * @Description: 微信支付服务类
 * @Version: 1.0
 */
@Service
public class WeChatPayService {

    private static Logger logger = LoggerFactory.getLogger(WeChatPayService.class);

    /**
     * 微信支付
     * @param ipAddr 用户IP地址
     * @param orderInfo 订单信息
     * @return
     */
    public Map WeChatPay(String ipAddr, OrderInfo orderInfo){

        Map<String,Object> map = new HashMap<String, Object>();

        //生成的随机字符串32
        String nonce_str  = RandomStringGenerator.getRandomStringByLength(32);

        try{
            String body = new String(orderInfo.getTitle().getBytes("ISO-8859-1"),"UTF-8");

            //参数组装,生成统一下单接口签名
            Map<String,String> paramsMap = new HashMap<String,String>();
            paramsMap.put("appid",Configure.getAppId());
            paramsMap.put("mch_id",Configure.getMch_id());
            paramsMap.put("nonce_str",nonce_str);
            paramsMap.put("body",body);
            paramsMap.put("out_trade_no",orderInfo.getOrderId());
            paramsMap.put("total_fee",String.valueOf(orderInfo.getMoney()));
            paramsMap.put("spbill_create_ip",ipAddr);
            paramsMap.put("notify_url",Configure.getNotify_url());
            paramsMap.put("trade_type",Configure.getTRADETYPE());
            paramsMap.put("openid",orderInfo.getOpenId());

            //参数过滤
            paramsMap = PayUtils.paramsFilter(paramsMap);
            //把所有数据拼接成字符串
            String linkParams = PayUtils.createLinkString(paramsMap);

            //MD5生成签名,用于调用统一下单接口
            String sign = PayUtils.sign(linkParams,Configure.getKey(),"utf-8").toUpperCase();

            //拼接统一下单接口所需xml数据
            String xml = "<xml>" + "<appid>" + Configure.getAppId() + "</appid>"
                    + "<body><![CDATA[" + body + "]]></body>"
                    + "<mch_id>" + Configure.mch_id + "</mch_id>"
                    + "<nonce_str>" + nonce_str + "</nonce_str>"
                    + "<notify_url>" + Configure.notify_url + "</notify_url>"
                    + "<openid>" + orderInfo.getOpenId() + "</openid>"
                    + "<out_trade_no>" + orderInfo.getOrderId() + "</out_trade_no>"
                    + "<spbill_create_ip>" + ipAddr + "</spbill_create_ip>"
                    + "<total_fee>" + String.valueOf(orderInfo.getMoney()) + "</total_fee>"//支付的金额,单位:分
                    + "<trade_type>" + Configure.TRADETYPE + "</trade_type>"
                    + "<sign>" + sign + "</sign>"
                    + "</xml>";

            //调用统一下单接口,返回响应数据
            String result = PayUtils.httpRequest(Configure.pay_url,"POST",xml);

            //将解析结果存入Map中
            Map rsMap = PayUtils.doXMLParse(result);

            //返回状态码
            String return_code = (String) rsMap.get("return_code");
            String result_msg = (String) rsMap.get("result_msg");
            String err_code = (String) rsMap.get("err_code");
            String err_code_des = (String) rsMap.get("err_code_des");

            if (return_code.equals("SUCCESS") || return_code.equals(return_code) ) {
                //返回的预付单信息
                String prepay_id = (String) rsMap .get("prepay_id");
                map.put("nonceStr", nonce_str);
                map.put("package", "prepay_id=" + prepay_id);
                Long timeStamp = System.currentTimeMillis() / 1000;
                //这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误
                map.put("timeStamp", timeStamp + "");
                //拼接签名需要的参数
                String stringSignTemp = "appId=" + Configure.getAppId() + "&nonceStr=" + nonce_str + "&package=prepay_id=" + prepay_id + "&signType=MD5&timeStamp=" + timeStamp;
                //再次签名,这个签名用于小程序端调用wx.requesetPayment方法
                String paySign = PayUtils.sign(stringSignTemp, Configure.key, "utf-8").toUpperCase();
                map.put("paySign", paySign);
            } else {
                logger.info("统一下单失败");
                return map;
            }
            map.put("appid", Configure.getAppId());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }

}

后端统一下单和支付回调接口



/**
 * @ClassName: WechatPayController
 * @Author: liuyaolong
 * @Description: 微信支付api控制层
 * @Version: 1.0
 */
@RestController
@RequestMapping("/wechat")
public class WechatPayController {

    @Resource
    private WeChatPayService weChatPayService;
   
    private static Logger logger = LoggerFactory.getLogger(WechatPayController.class);

    /**
     * 微信支付
     * @param in
     * @param request
     * @param result
     * @return
     */
    @PostMapping("/pay")
    public ResponseMsg<String> pay(@Valid  @RequestBody RequestMsg<OrderInfo> in, HttpServletRequest request, BindingResult result) {
        ResponseMsg<String> response = new ResponseMsg<>();

        Object object;
        try{
            //获取客户端ip地址
            String ip = IPAddr.getIpaddr(request);
            object = weChatPayService.WeChatPay(ip,in.getData());
            response.setData(PayUtils.toJson(object));
        } catch (Exception e) {
            e.printStackTrace();
        }
        logger.info("调用微信支付接口,入参信息为:{}",in.getData());
        return response;
    }

    /**
     * 支付回调接口
     * @param request
     * @param response
     * @throws Exception
     */
    @PostMapping("/notify")
    public void notify(HttpServletRequest request, HttpServletResponse response) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));
        StringBuilder sb = new StringBuilder();
        BufferedOutputStream out = null;
        String line = null;
        try{
            while((line = br.readLine()) != null){
                sb.append(line);
            }

            //响应xml
            String notifyXml = sb.toString();
            String resXml = "";

            Map map = PayUtils.doXMLParse(notifyXml);
            String returnCode = (String) map.get("return_code");
            if ("SUCCESS".equals(returnCode)) {
                //验证签名是否正确,回调验签时需要去除sign和空值参数
                Map<String, String> validParams = PayUtils.paramsFilter(map);
                //把所有数据拼接成字符串
                String prestr = PayUtils.createLinkString(validParams);
                //根据微信官网的介绍,此处不仅对回调的参数进行验签,还需要对返回的金额与系统订单的金额进行比对等
                if (PayUtils.verify(prestr, (String) map.get("sign"), Configure.getKey(), "utf-8")) {
                    /**此处添加自己的业务逻辑代码start**/

                    //注意要判断微信支付重复回调,支付成功后微信会重复的进行回调

                    /**此处添加自己的业务逻辑代码end**/
                    //通知微信服务器已经支付成功
                    resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
                }
            } else {
                resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
            }

            out = new BufferedOutputStream(response.getOutputStream());
            out.write(resXml.getBytes());
            out.flush();
            logger.info("微信支付接口回调 ! ");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            br.close();
            out.close();
        }
    }



}

 返回参数到前端起调微信支付

以上就是本文的全部内容,希望对大家的学习有所帮助,欢迎评论交流。能get到知识点不要忘了关注点赞~~~

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

破茧重生ys

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值