侧边栏壁纸
博主头像
xh博主等级

永远是一个学者

  • 累计撰写 15 篇文章
  • 累计创建 6 个标签
  • 累计收到 8 条评论
标签搜索

目 录CONTENT

文章目录

SpringBoot集成微信APP支付

xh
xh
2025-02-05 / 0 评论 / 0 点赞 / 233 阅读 / 1,351 字
温馨提示:
本文最后更新于 2025-02-06,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

前言

微信支付有两个版本:v2和v3版本。
【v2版本】微信支付最初版本,采用xml格式交互
【v3版本】遵循REST设计风格,采用json格式交互,更加简单易用
【v2文档】https://pay.weixin.qq.com/docs/merchant/products/jsapi-payment/introduction.html
【v3文档】https://pay.weixin.qq.com/doc/v3/merchant/4013070158
本文采用v2+v3混合使用。

接入

APP接入文档:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_5_1.shtml
微信开放平台:https://open.weixin.qq.com
微信商户平台:https://pay.weixin.qq.com

配置

配置文件

# 服务号的应用ID
APP_ID = wx631abbc200000001

# 商户号
MCH_ID = 1700000001

# API密钥
API_KEY = TEST952op762K654YHJSK61F500001

# 商户API私钥路径
MCH_API_KEY_PATH = /apiclient_key.pem

# 商户证书序列号
MCH_SERIAL_NUMBER = 34280FAA686E09A8A3sd12sa5daw12d12asA

# 商户APIv3密钥
MCH_API_V3_KEY = TESTjG3uY5dJ9tS3tT7oN5lP8s4d51wd

配置类

package com.jiang.pay;

import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Parameters;
import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
import org.apache.commons.configuration2.ex.ConfigurationException;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.SortedMap;

/**
 * 读取支付相关配置(从配置文件中读取)
 */
public class WxPayConfigUtil {

    /** 微信支付统一下单接口(POST) */
    public final static String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";

    private static PropertiesConfiguration configs;
    /** 应用ID*/
    public static String APP_ID;
    /** 商户号*/
    public static String MCH_ID;
    /** API密钥*/
    public static String API_KEY;
    /**商户API私钥路径*/
    public static String MCH_API_KEY_PATH;
    /**商户证书序列号*/
    public static String MCH_SERIAL_NUMBER;
    /**商户APIv3密钥*/
    public static String MCH_API_V3_KEY;

    // 项目启动时,需调用此静态方法加载配置
    public static synchronized void init(String filePath) {
        if (configs != null) {
            return;
        }
        try {
            FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
                    new FileBasedConfigurationBuilder<PropertiesConfiguration>(PropertiesConfiguration.class)
                            .configure(new Parameters().properties()
                                    .setFileName(filePath)
                                    .setThrowExceptionOnMissing(true)
                                    .setListDelimiterHandler(new DefaultListDelimiterHandler(';'))
                                    .setIncludesAllowed(false));
            configs = builder.getConfiguration();
        } catch (ConfigurationException e) {
            e.printStackTrace();
        }

        if (configs == null) {
            throw new IllegalStateException("can`t find file by path:" + filePath);
        }
        APP_ID = configs.getString("APP_ID");
        MCH_ID = configs.getString("MCH_ID");
        API_KEY = configs.getString("API_KEY");
        MCH_API_KEY_PATH = configs.getString("MCH_API_KEY_PATH");
        MCH_SERIAL_NUMBER = configs.getString("MCH_SERIAL_NUMBER");
        MCH_API_V3_KEY = configs.getString("MCH_API_V3_KEY");
    }
}

下单

下单

    /**
     * 下单
     * @param orderNo 订单编号
     * @param amount 订单金额
     */
    public WxApiUnifiedOrderResultVO pay(String orderNo, BigDecimal amount) {
        // [请求参数]
        SortedMap<String, String> params = new TreeMap<>();

        // 1.构建请求参数
        this.buildAppPayParams(params, orderNo, amount);

        // 2.参数转xml字符串(微信v2版本api规范,需传xml)
        String requestXml = WxPayCommonUtil.getRequestXml(params);
        System.out.println("请求微信xml参数:");
        System.out.println(requestXml);
        try {
            // 3.调用微信统一下单api
            String resXml = HttpUtil.postData(WxPayConfigUtil.UNIFIED_ORDER_URL, requestXml);

            // 4.解析响应结果(微信api响应的结果是xml格式,需转成自己需要的格式)
            Map<String, String> payData = XmlUtil.doXMLParse(resXml);

            // 5.保存微信响应内容(响应内容是前端拉起微信支付必备的参数)
            return this.revertWxResult(payData, params.get("sign"));
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

回调通知

    @ApiOperation("微信支付后回调")
    @PostMapping("/order/wx/notify")
    public void wxNotify(HttpServletRequest request, HttpServletResponse response) {
        log.info("微信支付回调通知");

        SortedMap<String, String> packageParams = parseWxParam(request);
        log.info("收到的参数:" + JSON.toJSONString(packageParams));

        // 账号信息 key
        String key = WxPayConfigUtil.API_KEY;
        // 判断签名是否正确
        if (WxPayCommonUtil.isTenpaySign("UTF-8", packageParams, key)) {
            log.info("微信支付成功回调");
            if ("SUCCESS".equals(packageParams.get("result_code"))) {
                // 支付成功
                String orderNo = packageParams.get("out_trade_no");
                log.info("微信订单号{}付款成功", orderNo);
                // 这里根据实际业务场景 做相应的操作
                orderService.updateResponse(PayTypeEnum.TYPE_2.getCode(), packageParams);
                outPutStreamNotify(response, "SUCCESS", "OK");
            } else {
                log.error("支付失败,错误信息:{}", packageParams.get("err_code"));
                outPutStreamNotify(response, "FAIL", packageParams.get("err_code"));
            }
        } else {
            log.error("通知签名验证失败");
        }
    }

退款

退款

    /**
     * 退款申请
     * @param orderNo 订单号
     * @param mchOrderNo 微信订单号
     * @param refundOrderNo 退款订单号
     * @param orderTotalAmount 订单总金额
     * @param refundAmount 本次退款金额
     */
    public void refundApply(String orderNo, String mchOrderNo, String refundOrderNo, BigDecimal orderTotalAmount, BigDecimal refundAmount) {
        Config config = new RSAAutoCertificateConfig.Builder()
                        .merchantId(WxPayConfigUtil.MCH_ID)
                        .privateKeyFromPath(WxPayConfigUtil.MCH_API_KEY_PATH)
                        .merchantSerialNumber(WxPayConfigUtil.MCH_SERIAL_NUMBER)
                        .apiV3Key(WxPayConfigUtil.MCH_API_V3_KEY)
                        .build();

        // 构建service
        RefundService service = new RefundService.Builder().config(config).build();

        // 请求参数
        CreateRequest request = new CreateRequest();
        request.setTransactionId(mchOrderNo);
        request.setOutTradeNo(orderNo);
        request.setOutRefundNo(refundOrderNo);
        request.setReason("退款原因");
        request.setNotifyUrl("回调通知地址");
        AmountReq amountReq = new AmountReq();
        amountReq.setTotal(Long.valueOf(MoneyUtil.YuanToFen(orderTotalAmount.toString()))); // 元转分
        amountReq.setRefund(Long.valueOf(MoneyUtil.YuanToFen(refundAmount.toString())));    // 元转分
        amountReq.setCurrency("CNY");
        request.setAmount(amountReq);

        try {
            Refund refund = service.create(request);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

回调通知

    @ApiOperation("微信退款回调")
    @PostMapping("/order/wx/refund/notify")
    public void wxRefund(HttpServletRequest request, HttpServletResponse response) {
        log.info("微信退款回调通知");
        String requestStr = readRequestStr(request);
        log.info("收到请求参数:" + requestStr);
        Map<String, String> map = JSON.parseObject(requestStr, new TypeReference<Map<String, String>>(){});
        log.info("[map][{}]", JSON.toJSONString(map));
        AesUtil aesUtil = new AesUtil(WxPayConfigUtil.MCH_API_V3_KEY.getBytes());
        try {
            Map<String, String> resource = JSON.parseObject(map.get("resource"), new TypeReference<Map<String, String>>(){});
            String decrypt = aesUtil.decryptToString(resource.get("associated_data").getBytes(), resource.get("nonce").getBytes(), resource.get("ciphertext"));
            log.info("[解密结果][{}]", decrypt);
            Map<String, String> dataMap = JSON.parseObject(decrypt, new TypeReference<Map<String, String>>(){});
            String transactionId = dataMap.get("transaction_id");   // 微信支付订单号
            String out_trade_no = dataMap.get("out_trade_no");      // 订单号
            String out_refund_no = dataMap.get("out_refund_no");    // 退款单号
            // 业务处理
            orderRefundService.updateIsRefundOk(out_refund_no);
        } catch (Exception e) {
            e.printStackTrace();
            log.info("[解密异常][{}]", e.getMessage());
            throw new CustomException(e.getMessage());
        }
    }
0

评论区