Java(四)Spring Boot 整合微信支付

背景

  1. 微信商户平台 建有一个普通服务商号、普通商户号。
  2. 微信开放平台 创建一个移动应用并通过审核,此时该移动应用会被分发一个 AppID。开通 微信支付 并关联普通商户号。
  3. 服务商平台 新增商户,将普通商户号签约为特约商户。
  4. 服务商平台 AppID 账号管理中关联创建的移动应用的 AppID。
  5. 详情请参考 微信官方文档

注意:如果服务商有两个 AppID,要区分是什么支付:如果是 JSAPI 支付、小程序支付、刷脸支付、Native 支付使用第一个,如果是 APP 内使用第二个。


配置

application.properties 配置

# 服务商 AppID
wxpay.app_id=wx****************
# 商户号
wxpay.mch_id=1*********
# 子商户号
wxpay.sub_mch_id=1*********
# 商户的 key(私钥)
wxpay.key=******************************
# 证书地址
wxpay.cert_path=/xxx/xxxx/xx/apiclient_cert.p12
# API 支付请求地址
wxpay.pay_url=https://api.mch.weixin.qq.com/pay/unifiedorder
# API 查询请求地址
wxpay.query_url=https://api.mch.weixin.qq.com/pay/orderquery
# package
wxpay.package_value=Sign=WXPay
# 回调地址
wxpay.tenpay_order_callback=https://ip:prot/xxx/xxxxxx
# 交易类型
wxpay.trade_type_app=APP

配置文件对应的 Config

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Data
@Component
public class WxPayConfig {
    // 服务商 AppID
    @Value("${wxpay.app_id}")
    private String appId;
    // 商户号
    @Value("${wxpay.mch_id}")
    private String mchId;
    // 子商户号
    @Value("${wxpay.sub_mch_id}")
    private String subMchId;
    // 商户的 key(API 私钥)
    @Value("${wxpay.key}")
    private String key;
    // 证书地址
    @Value("${wxpay.cert_path}")
    private String certPath;
    // API 支付请求地址
    @Value("${wxpay.pay_url}")
    private String payUrl;
    // API 查询请求地址
    @Value("${wxpay.query_url}")
    private String queryUrl;
    // package
    @Value("${wxpay.package_value}")
    private String packageValue;
    // 回调地址
    @Value("${wxpay.tenpay_order_callback}")
    public String TENPAY_ORDER_CALLBACK;
    // 交易类型
    @Value("${wxpay.trade_type_app}")
    public String TRADE_TYPE_APP;
}

参数配置文件

import com.github.wxpay.sdk.WXPayConfig;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

public class WxConfigUtil implements WXPayConfig {
    private byte[] certData;

    public static String APP_ID; // 服务商的 APPID(公众平台服务号)
    public static String MCH_ID; // 商户号
    public static String KEY; // 服务商密钥

    public WxConfigUtil(String path,String appId,String mchId,String key) throws Exception {
        APP_ID = appId;
        MCH_ID = mchId;
        KEY = key;
        String certPath = path;
        File file = new File(certPath);
        InputStream certStream = new FileInputStream(file);
        this.certData = new byte[(int) file.length()];
        certStream.read(this.certData);
        certStream.close();
    }

    @Override
    public String getAppID() { return APP_ID; }

    @Override
    public String getMchID() { return MCH_ID; }

    @Override
    public String getKey() { return KEY; }

    @Override
    public InputStream getCertStream() {
        ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
        return certBis;
    }

    @Override
    public int getHttpConnectTimeoutMs() {
        return 8000;
    }

    @Override
    public int getHttpReadTimeoutMs() {
        return 10000;
    }
}

在 pom.xml 中添加 xstream 的 jar 依赖、微信支付依赖

<!--xstream-->
<dependency>
    <groupId>com.thoughtworks.xstream</groupId>
    <artifactId>xstream</artifactId>
    <version>1.4.10</version>
</dependency>
<!-- 微信 -->
<dependency>
    <groupId>com.github.wxpay</groupId>
    <artifactId>wxpay-sdk</artifactId>
    <version>0.0.3</version>
</dependency>

请求微信支付

发起支付请求

@Override
public Map<String, String> payRequest(double totalAmount, String orderId) throws Exception {
    try {
        // 微信的支付单位是分所以要转换一些单位
        String totalproce = String.valueOf(Float.parseFloat(totalAmount+"") * 100);
        WxConfigUtil configUtil = new WxConfigUtil(wxPayConfig.getCertPath(),wxPayConfig.getAppId(),wxPayConfig.getMchId(),wxPayConfig.getKey());
        WXPay wxPay = new WXPay(configUtil);
        Map<String, String> data = new HashMap<>();

        // 查询获得订单id
        data.put("appid", configUtil.getAppID()); // 服务商的APPID
        data.put("mch_id", configUtil.getMchID()); // 商户号
        // 这里根据具体业务,查询出不同的子商户号,从而实现不同商户收款。这里只有一个,写在配置文件中
        data.put("sub_mch_id", wxPayConfig.getSubMchId()); // 子商户号
        data.put("nonce_str", WXPayUtil.generateNonceStr());// 随机字符串
        data.put("out_trade_no", orderId); // 商品订单号
        data.put("total_fee", totalproce);  // 总金额
        InetAddress inetAddress = InetAddress.getLocalHost();// 用户端ip
        String spbillCreateIp= inetAddress== null? "": inetAddress.getHostAddress();
        data.put("spbill_create_ip", spbillCreateIp);// 终端IP
        data.put("notify_url", wxPayConfig.getTENPAY_ORDER_CALLBACK());// 回调地址
        data.put("trade_type", wxPayConfig.getTRADE_TYPE_APP());// 交易类型
        String sign= WXPayUtil.generateSignature(data, configUtil.getKey(), WXPayConstants.SignType.MD5);	// 生成第一次签名
        data.put("sign", sign);
        // 使用官方API请求预付订单
        Map<String, String> response = wxPay.unifiedOrder(data);
        String returnCode = response.get("return_code");    // 获取返回码
        Map<String, String> param = new LinkedHashMap<>();
        // 判断返回状态码是否成功
        if (returnCode.equals("SUCCESS")) {
            // 成功后接受微信返回的参数
            String resultCode = response.get("result_code");
            param.put("appid", response.get("sub_appid"));// 子商户应用id
            param.put("partnerid", response.get("sub_mch_id"));// 子商户号
            param.put("package", wxPayConfig.getPackageValue());
            param.put("noncestr", WXPayUtil.generateNonceStr());// 随机字符串
            param.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));     // 时间戳
            if (resultCode.equals("SUCCESS")) {
                param.put("prepayid", response.get("prepay_id"));// 预支付交易会话 ID
                String retutnSign = WXPayUtil.generateSignature(param, configUtil.getKey(), WXPayConstants.SignType.MD5);	// 第二次签名
                param.put("sign", retutnSign);
                String str1 = WXPayUtil.mapToXml(param);
                param.put("packages", wxPayConfig.getPackageValue());
                return param;
            } else {
                throw new Exception("");
            }
        } else {
            throw new Exception("");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    throw new Exception("");
}

微信支付订单回调

@PostMapping(value = "/xxxxx")
public String wxPayNotify(HttpServletRequest request) {
    String resXml = "";
    try {
        InputStream inputStream = request.getInputStream();
        //将InputStream转换成xmlString
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        StringBuilder sb = new StringBuilder();
        String line = null;
        try {
            while ((line = reader.readLine()) != null) {
                sb.append(line + "\n");
            }
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        resXml = sb.toString();
        String result = payBiz.payBack(resXml);
        return result;
    } catch (Exception e) {
        logger.info("微信手机支付失败:" + e.getMessage());
        String result = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
        return result;
    }
}

异步通知后的 XML 数据

@Override
public String payBack(String resXml) {
    WxConfigUtil config = null;
    try {
        config = new WxConfigUtil(wxPayConfig.getCertPath(),wxPayConfig.getAppId(),wxPayConfig.getMchId(),wxPayConfig.getKey());
    } catch (Exception e) {
        e.printStackTrace();
    }
    WXPay wxpay = new WXPay(config);
    String xmlBack = "";
    Map<String, String> notifyMap = null;
    try {
        notifyMap = WXPayUtil.xmlToMap(resXml);// 调用官方 SDK 转换成 map 类型数据
        if (wxpay.isPayResultNotifySignatureValid(notifyMap)) {// 验证签名是否有效,有效则进一步处理
            String return_code = notifyMap.get("return_code");// 状态
            if (return_code.equals("SUCCESS")) {
                String out_trade_no = notifyMap.get("out_trade_no");// 商户订单号
                String type = notifyMap.get("attach");
                if (out_trade_no != null) {
                    // 根据交易编号加锁,处理高并发
                    synchronized (out_trade_no) {
                        // 该代码块为业务逻辑,可改成自己的
                        Integer status = payLogMapper.getOneOrderStatusByPayNo(out_trade_no);
                        if (status == null || status != 2) {
                            // 修改订单状态
                            orderBiz.order(type,out_trade_no);
                        } else {
                            logger.info("该订单已支付处理,交易编号为: " + out_trade_no);
                        }
                    }
                    xmlBack = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
                } else {
                    logger.info("微信手机支付回调失败订单号为空");
                    xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
                }
            }
            return xmlBack;
        } else {
            // 签名错误,如果数据里没有sign字段,也认为是签名错误
            logger.info("手机支付回调通知签名错误");
            xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
            return xmlBack;
        }
    } catch (Exception e) {
        logger.info("手机支付回调通知失败:"+ e);
        xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
    }
    return xmlBack;
}

微信定时回调方法

一般微信发起的异步回调都是无序不定时的,所以一般保险起见都会写一个自己的定时任务主动查询微信支付回调。

/**
 * 定时任务:每十五分钟触发一次主动调用订单支付回调
 */
@Scheduled(cron = "0 */15 * * * ?")
public void initiativeOrderPayCallBack(){
    //主动调用订单支付回调
    try {
        payService.initiativeOrderPayCallBack();
    } catch (Exception e) {
        logger.error("timer initiativeOrderPayCallBack Error.", e);
        e.printStackTrace();
    }
}
/**
 * 主动调用订单支付回调
 */
@Override
public void initiativeOrderPayCallBack() throws Exception {
    // 查询订单表里订单状态为待付款的支付编号
    List<Map<String, String>> payNoList = payLogMapper.getOnePayNoByStatus(1);
    for (Map<String, String> map : payNoList) {
        try {
            // 微信
            TenPayVO tenPayVO = this.tenPayQueryCallBack(map.get("payNo"));
            // 订单回调处理
            this.tenPayOrderCallBack(tenPayVO);
        } catch (Exception e) {
            logger.error(e.getMessage());
            e.printStackTrace();
        }
    }
}
/**
 * @Description: 微信支付主动查询回调
 */
@Override
public TenPayVO tenPayQueryCallBack(String orderId) throws Exception {
    SortedMap<String, Object> paramsMap = new TreeMap<String, Object>();
    // 应用 APPID
    paramsMap.put("appid", configUtil.getAppID());
    // 商户号
    paramsMap.put("mch_id", configUtil.getMchID());
    // 商户订单号
    paramsMap.put("out_trade_no", orderId);
    // 随机字符串
    paramsMap.put("nonce_str", WXPayUtil.generateNonceStr());
    TenPayUtils tenPayUtils = new TenPayUtils();
    // 签名
    String sign = tenPayUtils.createSign(paramsMap, "UTF-8");
    paramsMap.put("sign", sign);
    // 请求报文
    String requestXml = TenPayUtils.tenPayXmlInfo(paramsMap);
    // 发送微信查询post请求
    String tenQueryPost = TenPayUtils.httpsRequest(tenpayConfig.getQueryUrl(), "POST", requestXml);

    TenPayVO tenPayVO = this.tenPayCallBackInfo(tenQueryPost, "xml", "");
    return tenPayVO;
}

更新时间:2021-04-29 14:45:30

本文由 caroly 创作,如果您觉得本文不错,请随意赞赏
采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载 / 出处外,均为本站原创或翻译,转载前请务必署名
原文链接:https://caroly.fun/archives/springboot整合微信支付
最后更新:2021-04-29 14:45:30

评论

Your browser is out of date!

Update your browser to view this website correctly. Update my browser now

×