手把手教你如何基于Springboot对接微信支付接口_Native支付(奶爸级别)

微信开发文档地址:https://pay.weixin.qq.com/wiki/doc/api/index.html

申请商户号地址:https://pay.weixin.qq.com/static/help_guide/bind_guide_admin.shtml

demo下载地址:https://download.youkuaiyun.com/download/qq_17555933/18507085

微信对接时序图:

微信对接时序图

准备资料
# 微信支付二维码key
wxpay.qrcodeKey=wxpay_qrcode
# 微信支付二维码过期时间为 < 2小时(微信二维码code_url有效期为2小时)
wxpay.qrcodeExpire=7000

# 注意!!!如果异步通知接口没有返回success,微信异步通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒

# 公众账号ID
wxpay.appId=xxx
# 商户号
wxpay.merchantId=xxx
# 商户秘钥
wxpay.secrectKey=xxxx

# APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP
wxpay.spbillCreateIp=127.0.0.1
# 接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数
wxpay.notifyUrl=http://5ktbak.natappfree.cc/payment/notice/wxpay

# 支付方式,取值如下:JSAPI,NATIVE,APP
wxpay.tradeType=NATIVE

# 微信支付 - 统一下单地址
wxpay.placeOrderUrl=https://api.mch.weixin.qq.com/pay/unifiedorder
代码实现
WXPayController.java
package com.wxpay.controller;

import com.wxpay.pojo.Orders;
import com.wxpay.pojo.PayResult;
import com.wxpay.pojo.PaymentInfoVO;
import com.wxpay.pojo.PreOrderResult;
import com.wxpay.resource.WXPayResource;
import com.wxpay.service.WxOrderService;
import com.wxpay.utils.DateUtil;
import com.wxpay.utils.JsonResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: HuGoldWater
 * @description:
 */
@Slf4j
@RestController
@RequestMapping("payment")
public class WXPayController {
    @Autowired
    private WXPayResource wxPayResource;
    @Autowired
    private WxOrderService wxOrderService;
    /**
     * 支付成功后的微信支付异步通知
     */
    @RequestMapping(value = "/notify/wxpay")
    public void wxpay(HttpServletRequest request, HttpServletResponse response) throws Exception {
        log.info("支付成功后的微信支付异步通知");
        // 获取微信支付结果
        PayResult payResult = wxOrderService.getWxPayResult(request.getInputStream());

        boolean isPaid = payResult.getReturn_code().equals("SUCCESS") ? true : false;
        // 查询该笔订单在微信那边是否成功支付
        // 支付成功,商户处理后同步返回给微信参数
        PrintWriter writer = response.getWriter();
        if (isPaid) {
            String merchantOrderId = payResult.getOut_trade_no();            // 商户订单号
            String wxFlowId = payResult.getTransaction_id();
            Integer paidAmount = payResult.getTotal_fee();

            System.out.println("================================= 支付成功 =================================");

            log.info("************* 支付成功(微信支付异步通知) - 时间: {} *************", DateUtil.getCurrentDateString(DateUtil.DATETIME_PATTERN));
            log.info("* 商户订单号: {}", merchantOrderId);
            log.info("* 微信订单号: {}", wxFlowId);
            log.info("* 实际支付金额: {}", paidAmount);
            log.info("*****************************************************************************");
            // 通知订单服务,该订单已支付
            System.out.println("================================= 通知订单服务,该订单已支付 =================================");

            // 通知微信已经收到消息,不要再给我发消息了,否则微信会10连击调用本接口
            String noticeStr = setXML("SUCCESS", "");
            writer.write(noticeStr);
            writer.flush();
        } else {
            System.out.println("================================= 支付失败 =================================");

            // 支付失败
            String noticeStr = setXML("FAIL", "");
            writer.write(noticeStr);
            writer.flush();
        }
    }

    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>";
    }

    /**
     * 微信扫码支付页面
     */
    @PostMapping(value = "/getWXPayQRCode")
    public JsonResult getWXPayQRCode(String merchantUserId, String merchantOrderId) throws Exception {
        // 根据订单ID和用户ID查询订单详情
//        Orders waitPayOrder = paymentOrderService.queryOrderByStatus(merchantOrderId,merchantUserId,PaymentStatus.WAIT_PAY.type);
        Orders waitPayOrder = new Orders();
        // 商品描述
        String body = "胡金水-付款用户[" + merchantUserId + "]";
        // 商户订单号
        String out_trade_no = merchantOrderId;
        // 使用map代替redis
        Map<String, String> redisCacheMap = new HashMap<>();
        // 从redis中去获得这笔订单的微信支付二维码,如果订单状态没有支付没有就放入,这样的做法防止用户频繁刷新而调用微信接口
        if (waitPayOrder != null) {
            String qrCodeUrl = redisCacheMap.get(wxPayResource.getQrcodeKey() + ":" + merchantOrderId);

            if (StringUtils.isEmpty(qrCodeUrl)) {
                // 订单总金额,单位为分
                String total_fee = "1"; // 测试金额
                // 统一下单
                PreOrderResult preOrderResult = wxOrderService.placeOrder(body, out_trade_no, total_fee);
                qrCodeUrl = preOrderResult.getCode_url();
            }

            PaymentInfoVO paymentInfoVO = new PaymentInfoVO();
            paymentInfoVO.setAmount(1);
            paymentInfoVO.setMerchantOrderId(merchantOrderId);
            paymentInfoVO.setMerchantUserId(merchantUserId);
            paymentInfoVO.setQrCodeUrl(qrCodeUrl);

            redisCacheMap.put(wxPayResource.getQrcodeKey() + ":" + merchantOrderId, qrCodeUrl);

            return JsonResult.ok(paymentInfoVO);
        } else {
            return JsonResult.errorMsg("该订单不存在,或已经支付");
        }
    }
}
WXPayResource.java
package com.wxpay.resource;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "wxpay")
@PropertySource("classpath:wxpay.properties")
public class WXPayResource {

    private String qrcodeKey;
    private long qrcodeExpire;

    private String appId;
    private String merchantId;
    private String secrectKey;

    private String spbillCreateIp;
    private String notifyUrl;

    private String tradeType;
    private String placeOrderUrl;
}
WxOrderServiceImpl.java
package com.wxpay.service.impl;

import com.wxpay.pojo.PayResult;
import com.wxpay.pojo.PreOrder;
import com.wxpay.pojo.PreOrderResult;
import com.wxpay.resource.WXPayResource;
import com.wxpay.service.WxOrderService;
import com.wxpay.utils.HttpUtil;
import com.wxpay.utils.Sign;
import com.wxpay.utils.XmlUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.UUID;

@Service
public class WxOrderServiceImpl implements WxOrderService {

    @Autowired
    private WXPayResource wxPayResource;

    /**
     * ==========================================
     * 微信预付单:指的是在自己的平台需要和微信进行支付交易生成的一个微信订单,称之为“预付单”
     * 订单:指的是自己的网站平台与用户之间交易生成的订单
     * <p>
     * 1. 用户购买产品 --> 生成网站订单
     * 2. 用户支付 --> 网站在微信平台生成预付单
     * 3. 最终实际根据预付单的信息进行支付
     * ==========================================
     */

    @Override
    public PreOrderResult placeOrder(String body, String out_trade_no, String total_fee) throws Exception {
        // 生成预付订单对象
        PreOrder o = new PreOrder();
        // 生成随机字符串
        String nonce_str = UUID.randomUUID().toString().trim().replaceAll("-", "");
        o.setAppid(wxPayResource.getAppId());
        o.setBody(body);
        o.setMch_id(wxPayResource.getMerchantId());
        o.setNotify_url(wxPayResource.getNotifyUrl());
        o.setOut_trade_no(out_trade_no);
        if (total_fee != null && !total_fee.equals("")) {
            o.setTotal_fee(Integer.parseInt(total_fee));
        } else {
            o.setTotal_fee(1);
        }
        o.setNonce_str(nonce_str);
        o.setTrade_type(wxPayResource.getTradeType());
        o.setSpbill_create_ip(wxPayResource.getSpbillCreateIp());
        SortedMap<Object, Object> p = new TreeMap<Object, Object>();
        p.put("appid", wxPayResource.getAppId());
        p.put("mch_id", wxPayResource.getMerchantId());
        p.put("body", body);
        p.put("nonce_str", nonce_str);
        p.put("out_trade_no", out_trade_no);
        p.put("total_fee", total_fee);
        p.put("spbill_create_ip", wxPayResource.getSpbillCreateIp());
        p.put("notify_url", wxPayResource.getNotifyUrl());
        p.put("trade_type", wxPayResource.getTradeType());
        // 获得签名
        String sign = Sign.createSign("utf-8", p, wxPayResource.getSecrectKey());
        o.setSign(sign);
        // Object转换为XML
        String xml = XmlUtil.object2Xml(o, PreOrder.class);
        // 统一下单地址
        String url = wxPayResource.getPlaceOrderUrl();
        // 调用微信统一下单地址
        String returnXml = HttpUtil.sendPost(url, xml);

        // XML转换为Object
        PreOrderResult preOrderResult = (PreOrderResult) XmlUtil.xml2Object(returnXml, PreOrderResult.class);

        return preOrderResult;
    }

    @Override
    public PayResult getWxPayResult(InputStream inStream) throws Exception {
        BufferedReader in = null;
        String result = "";
        in = new BufferedReader(new InputStreamReader(inStream));
        String line;
        while ((line = in.readLine()) != null) {
            result += line;
        }
        PayResult pr = (PayResult) XmlUtil.xml2Object(result, PayResult.class);
        return pr;
    }
}
MerchantOrdersBO.java
package com.wxpay.pojo;

import lombok.Data;

@Data
public class MerchantOrdersBO {
    private String merchantOrderId;         // 商户订单号
    private String merchantUserId;          // 商户方的发起用户的用户主键id
    private Integer amount;                 // 实际支付总金额(包含商户所支付的订单费邮费总额)
    private Integer payMethod;              // 支付方式 1:微信   2:支付宝
    private String returnUrl;               // 支付成功后的回调地址(学生自定义)

}
Orders.java
package com.wxpay.pojo;


import lombok.Data;

import java.util.Date;

@Data
public class Orders {
    /**
     * 订单主键
     */
    private String id;

    /**
     * 商户订单号
     */
    private String merchantOrderId;

    /**
     * 商户方的发起用户的用户主键id
     */
    private String merchantUserId;

    /**
     * 实际支付总金额(包含商户所支付的订单费邮费总额)
     */
    private Integer amount;

    /**
     * 支付方式
     */
    private Integer payMethod;

    /**
     * 支付状态 10:未支付 20:已支付 30:支付失败 40:已退款
     */
    private Integer payStatus;

    /**
     * 从哪一端来的,比如从XXX过来的
     */
    private String comeFrom;

    /**
     * 支付成功后的通知地址,这个是开发者那一段的,不是第三方支付通知的地址
     */
    private String returnUrl;

    /**
     * 逻辑删除状态;1: 删除 0:未删除
     */
    private Integer isDelete;

    /**
     * 创建时间(成交时间)
     */
    private Date createdTime;


}
PaymentInfoVO.java
package com.wxpay.pojo;

import lombok.Data;

@Data
public class PaymentInfoVO {

    private String merchantOrderId;         // 商户订单号
    private String merchantUserId;          // 商户方的发起用户的用户主键id
    private Integer amount;                 // 实际支付总金额(包含商户所支付的订单费邮费总额)
    private String qrCodeUrl;               // 二维码扫码地址

}
PayResult.java
package com.wxpay.pojo;

import lombok.Data;

/**
 * 支付结果封装类
 */
@Data
public class PayResult {
    private String return_code;                // 返回状态码
    private String appid;                    // 公众账号ID
    private String mch_id;                    // 商户号
    private String nonce_str;                // 随机字符串
    private String sign;                    // 签名
    private String result_code;                // 业务结果
    private String openid;                    // 用户标识
    private String trade_type;                // 交易类型
    private String bank_type;                // 付款银行
    private int total_fee;                    // 总金额
    private int cash_fee;                    // 现金支付金额
    private String transaction_id;            // 微信支付订单号
    private String out_trade_no;            // 商户订单号
    private String time_end;                // 支付完成时间
    private String return_msg;                // 返回信息
    private String device_info;                // 设备号
    private String err_code;                // 错误代码
    private String err_code_des;            // 错误代码描述
    private String is_subscribe;            // 是否关注公众账号
    private String fee_type;                // 货币种类
    private String cash_fee_type;            // 现金支付货币类型
    private String coupon_fee;                // 代金券或立减优惠金额
    private String coupon_count;            // 代金券或立减优惠使用数量
    private String coupon_id_$n;            // 代金券或立减优惠ID
    private String coupon_fee_$n;            // 单个代金券或立减优惠支付金额
    private String attach;                    // 商家数据包

}
PreOrder.java
package com.wxpay.pojo;

import lombok.Data;

/**
 * 统一下单
 */
@Data
public class PreOrder {
    private String appid;// 公众账号ID
    private String mch_id;// 商户号
    private String nonce_str;// 随机字符串
    private String sign;// 签名
    private String body;// 商品描述
    private String out_trade_no;// 商户订单号
    private int total_fee;// 订单总金额,单位为分
    private String spbill_create_ip;// APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。
    private String notify_url;// 接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。
    private String trade_type;// 取值如下:JSAPI,NATIVE,APP

}
PreOrderResult.java
package com.wxpay.pojo;

import lombok.Data;

/**
 * 微信支付 - 统一下单返回结果的封装entity
 */
@Data
public class PreOrderResult {

    private String return_code;                // 返回状态码
    private String return_msg;                // 返回信息
    private String appid;                    // 公众账号ID
    private String mch_id;                    // 商户号
    private String device_info;                // 设备号
    private String nonce_str;                // 随机字符串
    private String sign;                    // 签名
    private String result_code;                // 业务结果
    private String err_code;                // 错误代码
    private String err_code_des;            // 错误代码描述
    private String trade_type;                // 交易类型
    private String prepay_id;                // 预支付交易会话标识
    private String code_url;                // 二维码链接
}
HttpUtil.java
package com.wxpay.utils;

import java.io.*;
import java.net.URL;
import java.net.URLConnection;

public class HttpUtil {

    /**
     * POST请求
     *
     * @param url
     * @param outStr
     * @return
     */
    public static String doPostStr(String url, String outStr) {
        PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        try {
            URL realUrl = new URL(url);
            // 打开和URL之间的连接
            URLConnection conn = realUrl.openConnection();
            // 设置通用的请求属性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 发送POST请求必须设置如下两行
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // 获取URLConnection对象对应的输出流
            out = new PrintWriter(conn.getOutputStream());
            // 发送请求参数
            out.print(outStr);
            // flush输出流的缓冲
            out.flush();
            // 定义BufferedReader输入流来读取URL的响应
            in = new BufferedReader(
                    new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
            System.out.println(result);
        } catch (Exception e) {
            System.out.println("发送 POST 请求出现异常!" + e);
            e.printStackTrace();
        }
        // 使用finally块来关闭输出流、输入流
        finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 向指定 URL 发送POST方法的请求
     *
     * @param url     发送请求的 URL
     * @param param    请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @return 所代表远程资源的响应结果
     */
    public static String sendPost(String url, String param) {
        Writer out = null;
        BufferedReader in = null;
        String result = "";
        try {
            URL realUrl = new URL(url);
            // 打开和URL之间的连接
            URLConnection conn = realUrl.openConnection();
            // 设置通用的请求属性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 发送POST请求必须设置如下两行
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // 获取URLConnection对象对应的输出流
            out = new BufferedWriter(new OutputStreamWriter(
                    conn.getOutputStream(),"UTF-8"));
            // 发送请求参数
            out.write(param);
            // flush输出流的缓冲
            out.flush();
            // 定义BufferedReader输入流来读取URL的响应
            in = new BufferedReader(
                    new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println("发送 POST 请求出现异常!" + e);
            e.printStackTrace();
        }
        // 使用finally块来关闭输出流、输入流
        finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return result;
    }

}
MD5Util.java
package com.wxpay.utils;

import java.security.MessageDigest;

/**
 * MD5加密 工具类
 */
public class MD5Util {
    private static String byteArrayToHexString(byte b[]) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++)
            resultSb.append(byteToHexString(b[i]));

        return resultSb.toString();
    }

    private static String byteToHexString(byte b) {
        int n = b;
        if (n < 0)
            n += 256;
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }

    public static String MD5Encode(String origin, String charsetname) {
        String resultString = null;
        try {
            resultString = new String(origin);
            MessageDigest md = MessageDigest.getInstance("MD5");
            if (charsetname == null || "".equals(charsetname))
                resultString = byteArrayToHexString(md.digest(resultString
                        .getBytes()));
            else
                resultString = byteArrayToHexString(md.digest(resultString
                        .getBytes(charsetname)));
        } catch (Exception exception) {
        }
        return resultString;
    }

    private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
}
Sign.java
package com.wxpay.utils;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;

public class Sign {
    /**
     * 微信支付签名算法sign
     *
     * @param characterEncoding
     * @param parameters
     * @return
     */
    @SuppressWarnings("unchecked")
    public static String createSign(String characterEncoding,
                                    SortedMap<Object, Object> parameters, String key) {
        StringBuffer sb = new StringBuffer();
        Set es = parameters.entrySet();// 所有参与传参的参数按照accsii排序(升序)
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            Object v = entry.getValue();
            if (null != v && !"".equals(v) && !"sign".equals(k)
                    && !"key".equals(k)) {
                sb.append(k + "=" + v + "&");
            }
        }
        sb.append("key=" + key);
        String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding)
                .toUpperCase();
        return sign;
    }
}
XmlUtil.java
package com.wxpay.utils;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;

import java.io.InputStream;

public class XmlUtil {

    /**
     *
     * @Description: xml字符串转换为对象
     * @param inputXml
     * @param type
     * @return
     * @throws Exception
     *
     */
    public static Object xml2Object(String inputXml, Class<?> type) throws Exception {
        if (null == inputXml || "".equals(inputXml)) {
            return null;
        }
        XStream xstream = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_")));
        xstream.alias("xml", type);
        return xstream.fromXML(inputXml);
    }

    /**
     *
     * @Description: 从inputStream中读取对象
     * @param inputStream
     * @param type
     * @return
     * @throws Exception
     *
     */
    public static Object xml2Object(InputStream inputStream, Class<?> type) throws Exception {
        if (null == inputStream) {
            return null;
        }
        XStream xstream = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_")));
        xstream.alias("xml", type);
        return xstream.fromXML(inputStream, type);
    }

    /**
     *
     * @Description: 对象转换为xml字符串
     * @param ro
     * @param types
     * @return
     * @throws Exception
     *
     */
    public static String object2Xml(Object ro, Class<?> types) throws Exception {
        if (null == ro) {
            return null;
        }
        XStream xstream = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_")));
        xstream.alias("xml", types);
        return xstream.toXML(ro);
    }

}
Application.java
package com.wxpay;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
   public static void main(String[] args) {
      SpringApplication.run(Application.class, args);
   }
}

运行Application,使用postman调用接口http://localhost:8080/payment/getWXPayQRCode?merchantUserId=123&merchantOrderId=4566

结果如下:

{
    "status": 200,
    "msg": "OK",
    "data": {
        "merchantOrderId": "4566",
        "merchantUserId": "123",
        "amount": 1,
        "qrCodeUrl": "weixin://wxpay/bizpayurl?pr=z3rDPy8zz"
    }
}

将qrCodeUrl的值,生成二维码,使用手机微信扫一扫,就可以支付了。

付款二维码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

雨润泽林

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

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

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

打赏作者

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

抵扣说明:

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

余额充值