Java接入微信扫码支付

提前说明:此博客使用的是V3版本,只是做了微信支付的接入,没有业务逻辑处理说明。

支付准备

1.获取商户号

微信商户平台:微信支付 - 中国领先的第三方支付平台 | 微信支付提供安全快捷的支付方式 步骤:申请成为商户 => 提交资料 => 签署协议 => 获取商户号 

2.获取appid

微信公众平台:微信公众平台 步骤:注册服务号 => 服务号认证 => 获取APPID => 绑定商户号

3.获取商户证书

 登录商户平台 => 选择 账户中心 => 安全中心 => API安全 => 申请API证书 包括商户证书和商户私钥

4.获取V3密钥

获取APIv3秘钥(在微信支付回调通知和商户获取平台证书使用APIv3密钥)

pom引入

<!--微信支付-->
<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-apache-httpclient</artifactId>
    <version>0.4.9</version>
</dependency>
<!--实体对象工具类-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<!--工具类-->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.16</version>
</dependency>
<!--web应用-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

 配置文件:

wxpay:
  # APIv3密钥
  api-v3-key: 
  # APPID
  appid: 
  # 商户ID
  mch-id: 
  # 商户API证书序列号
  mch-serial-no: 
  # 接收支付结果通知地址
  notify-domain: https://127.0.0.1/pay/wxNativeNotify
  # 退款回调通知
  refund-notify-domain: https://127.0.0.1/pay/wxRefundNotify
  # 商户私钥文件路径
  private-key-path: D:\key\apiclient_key.pem

 回调路径需要配置访问权限,需要自己做本地路径映射

私钥文件路径放到自己本地可以访问的地方就行

对应的配置类:

@Component
@Data
@ConfigurationProperties(prefix = "wxpay")
public class WechatpayConfig {
    // 商户ID
    private String mchId;
    // 商户API证书序列号
    private String mchSerialNo;
    // 商户私钥文件
    private String privateKeyPath;
    // APIv3密钥
    private String apiV3Key;
    // APPID
    private String appid;
    // 接收结果通知地址
    private String notifyDomain;
    // 退款通知
    private String refundNotifyDomain;
}

支付

官方支付文档

Native下单 - Native支付 | 微信支付商户文档中心

微信有5钟支付场景,我使用的是扫码支付

import lombok.Data;

/**
 * @Author: zm
 * @Description: 微信订单实体类
 * @Date: 2024/10/8 15:05
 */
@Data
public class WechatPayModel {

    //商品描述
    private String description;
    //商户订单号 (唯一)
    private String outTradeNo;
    //附加数据,会在支付通知中返回
    private String attach;
    //金额实体
    private Amount amount;
    //微信支付订单号 返回
    private String transactionId;
    /**
     * 交易状态,枚举值:
     * SUCCESS:支付成功
     * REFUND:转入退款
     * NOTPAY:未支付
     * CLOSED:已关闭
     * REVOKED:已撤销(仅付款码支付会返回)
     * USERPAYING:用户支付中(仅付款码支付会返回)
     * PAYERROR:支付失败(仅付款码支付会返回)
     */
    private String tradeState;

    /**
     * 退款状态
     * SUCCESS:退款成功
     * CLOSED:退款关闭
     * PROCESSING:退款处理中
     * ABNORMAL:退款异常
     */
    private String refundState;

    /**
     * 订单金额实体类
     */
    @Data
    public class Amount{
        //订单金额 单位为分
        private Integer total;
        //CNY:人民币,境内商户号仅支持人民币
        private String currency;
    }

}
import com.ruoyi.zm.propertie.WechatpayConfig;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import java.util.Properties;

/**
 * @Author: zm
 * @Description: 支付配置公共类
 * @Date: 2024/9/29 15:23
 */
@Configuration
public class CommonUtils {
    @Resource
    private WechatpayConfig wxPayConfig;

    /**
     * 初始化商户配置
     * @return
     */
    @Bean
    public RSAAutoCertificateConfig getConfig(){
        return new RSAAutoCertificateConfig.Builder()
                .merchantId(wxPayConfig.getMchId())
                .privateKeyFromPath(wxPayConfig.getPrivateKeyPath())
                .merchantSerialNumber(wxPayConfig.getMchSerialNo())
                .apiV3Key(wxPayConfig.getApiV3Key())
                .build();
    }

}
# 支付引入相关的包
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.core.constant.HttpStatus;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.zm.dto.WechatPayModel;
import com.ruoyi.zm.propertie.WechatpayConfig;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.HttpException;
import com.wechat.pay.java.core.exception.MalformedMessageException;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.exception.ValidationException;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
import com.wechat.pay.java.service.payments.nativepay.model.Amount;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse;
import com.wechat.pay.java.service.payments.nativepay.model.QueryOrderByOutTradeNoRequest;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

扫码预下单

使用 Java SDK 快速开始 - SDK&开发工具 | 微信支付商户文档中心icon-default.png?t=O83Ahttps://pay.weixin.qq.com/docs/merchant/sdk-tools/quickstart-java.html#%E6%8E%A5%E4%B8%8B%E6%9D%A5%E9%98%85%E8%AF%BB

AjaxResult是我自己封装的返回处理,你们替换成自己的就可以

/**
     * 微信支付 扫码预下单
     * @param wechatPayModel 参数实体
     * @author: zm
     * @date: 2024/10/8
     * @rerturn: java.lang.String
     */
    public AjaxResult nativePay(WechatPayModel wechatPayModel){
        if(StringUtils.isAnyEmpty(wechatPayModel.getOutTradeNo(),wechatPayModel.getDescription())){
            return AjaxResult.error("订单号或描述为空");
        }
        if(StringUtils.isAnyEmpty(wechatPayModel.getAmount().getTotal().toString())){
            return AjaxResult.error("金额为空,请检查参数");
        }
        PrepayResponse response;
        try{
            // 构建service
            NativePayService service = new NativePayService.Builder().config(config).build();
            // request.setXxx(val)设置所需参数,具体参数可见Request定义
            PrepayRequest request = new PrepayRequest();
            Amount amount = new Amount();
            amount.setTotal(wechatPayModel.getAmount().getTotal());
            request.setAmount(amount);
            request.setAppid(payConfig.getAppid());
            request.setMchid(payConfig.getMchId());
            request.setDescription(wechatPayModel.getDescription());
            request.setNotifyUrl(payConfig.getNotifyDomain());
            request.setOutTradeNo(wechatPayModel.getOutTradeNo());
            request.setAttach(wechatPayModel.getAttach());
            // 调用下单方法,得到应答
            response = service.prepay(request);
        }catch (HttpException e) { // 发送HTTP请求失败
            log.error("微信下单发送HTTP请求失败,错误信息:{}", e.getHttpRequest());
            return AjaxResult.error("下单失败");
        } catch (ServiceException e) { // 服务返回状态小于200或大于等于300,例如500
            log.error("微信下单服务状态错误,错误信息:{}", e.getErrorMessage());
            return AjaxResult.error("下单失败");
        } catch (MalformedMessageException e) { // 服务返回成功,返回体类型不合法,或者解析返回体失败
            log.error("服务返回成功,返回体类型不合法,或者解析返回体失败,错误信息:{}", e.getMessage());
            return AjaxResult.error("下单失败");
        }
        return AjaxResult.success(response);
    }

支付回调

回调请求工具类

/**
     * 获取请求体
     */
    public static String getRequestBody(HttpServletRequest request) throws IOException {
        ServletInputStream stream;
        BufferedReader reader = null;
        StringBuilder sb = new StringBuilder();
        try {
            stream = request.getInputStream();
            // 获取响应
            reader = new BufferedReader(new InputStreamReader(stream));
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            throw new IOException("读取返回支付接口数据流出现异常!");
        } finally {
            assert reader != null;
            reader.close();
        }
        return sb.toString();
    }
 /**
     * 支付回调方法
     * 业务信息不在这里写,回调地址只返回了业务订单号
     * @param request 请求参数
     * @author: zm
     * @date: 2024/10/8
     * @rerturn: java.lang.String
     */
    public synchronized Map<String,String> payNativeNotify(HttpServletRequest request, HttpServletResponse response) throws IOException {
        log.info("------收到支付通知------");
        Map<String, String> map = new HashMap<>();//应答对象
        // 请求头Wechatpay-Signature
        String signature = request.getHeader("Wechatpay-Signature");
        // 请求头Wechatpay-nonce
        String nonce = request.getHeader("Wechatpay-Nonce");
        // 请求头Wechatpay-Timestamp
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        // 微信支付证书序列号
        String serial = request.getHeader("Wechatpay-Serial");
        // 签名方式
        String signType = request.getHeader("Wechatpay-Signature-Type");
        // 构造 RequestParam
        RequestParam requestParam = new RequestParam.Builder()
                .serialNumber(serial)
                .nonce(nonce)
                .signature(signature)
                .timestamp(timestamp)
                .signType(signType)
                .body(getRequestBody(request))
                .build();
        // 初始化 NotificationParser
        NotificationParser parser = new NotificationParser(config);
        // 以支付通知回调为例,验签、解密并转换成 Transaction
        log.info("验签");
        Transaction transaction;
        try {
            // 以支付通知回调为例,验签、解密并转换成 Transaction
            transaction = parser.parse(requestParam, Transaction.class);
        } catch (ValidationException e) {
            // 签名验证失败,返回 401 UNAUTHORIZED 状态码
            response.setStatus(500);
            log.error("sign verification failed", e);
            map.put("code", "FAIL");
            map.put("message","失败");
            return map;
        }
        log.info("验签成功!支付回调结果:{}", transaction.toString());
        //处理业务逻辑
        WechatPayModel payModel=new WechatPayModel();
        payModel.setOutTradeNo(transaction.getOutTradeNo());
        payModel.setTransactionId(transaction.getTransactionId());
        payModel.setTradeState(transaction.getTradeState().name());
        // 处理成功,返回 200 OK 状态码
        map.put("code", "SUCCESS");
        response.setStatus(200);
        return map;
    }

支付查询

    /**
     * 根据商户订单号查询订单
     * @param model 请求参数
     * @author: zm
     * @date: 2024/10/9
     * @rerturn: java.lang.String
     */
    public AjaxResult queryOrderByOrderNo(WechatPayModel model){
        log.info("微信订单查询开始:商户订单号:{}",model.getOutTradeNo());
        QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();
        queryRequest.setMchid(payConfig.getMchId());
        queryRequest.setOutTradeNo(model.getOutTradeNo());
        try {
            NativePayService service = new NativePayService.Builder().config(config).build();
            Transaction result = service.queryOrderByOutTradeNo(queryRequest);
            log.info("订单查询成功,查询返回数据:{}",result.toString());
            //支付成功
            WechatPayModel payModel=new WechatPayModel();
            payModel.setOutTradeNo(result.getOutTradeNo());
            payModel.setTransactionId(result.getTransactionId());
            payModel.setTradeState(result.getTradeState().name());
            return AjaxResult.success(payModel);
        } catch (ServiceException e) {
            log.error("订单查询失败,返回码:{},返回信息:{}", e.getErrorCode(), e.getErrorMessage());
            return AjaxResult.error("订单查询失败!");
        }
    }

退款申请

退款申请挺复杂,可以支持部分退款,我这里没有做部分退款,是全部退款的例子。

/**
     * 微信退款申请
     * @param model 请求参数
     * @author: zm
     * @date: 2024/10/9
     * @rerturn: java.lang.String
     */
    public AjaxResult wxCreateRefund(WechatPayModel model){
        //现在只有全额退款,所以没有重新生成退款流水号,如果部分退款,那么退款流水号需要改成调用端传输退款流水号
        String refundNo="REFUND_"+model.getOutTradeNo();
        if(ObjectUtil.isNull(model.getAmount().getTotal())){
            return AjaxResult.error("退款金额不能为空!");
        }
        log.info("微信发起退款申请:商户订单号[{}],退款单号[{}]",model.getOutTradeNo(),refundNo);
        try {
            // 构建退款service
            RefundService service = new RefundService.Builder()
                    .config(config)
                    .build();
            CreateRequest request = new CreateRequest();
            // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
            request.setOutTradeNo(model.getOutTradeNo());
            request.setOutRefundNo(refundNo);

            AmountReq amount = new AmountReq();
            amount.setTotal(Long.valueOf(model.getAmount().getTotal()));
            amount.setRefund(Long.valueOf(model.getAmount().getTotal()));
            amount.setCurrency("CNY");

            request.setAmount(amount);
            //退款通知回调
            request.setNotifyUrl(payConfig.getRefundNotifyDomain());
            //接收退款返回参数
            Refund refund = service.create(request);
            log.info("退款返回信息:{}", refund);
            WechatPayModel payModel=new WechatPayModel();
            payModel.setOutTradeNo(refund.getOutTradeNo());
            payModel.setTransactionId(refund.getTransactionId());
            payModel.setRefundState(refund.getStatus().name());
            if (refund.getStatus().equals(Status.SUCCESS) || refund.getStatus().equals(Status.PROCESSING)) {
                return AjaxResult.success(payModel);
            }else if (refund.getStatus().equals(Status.ABNORMAL)) {
                return AjaxResult.error(HttpStatus.ERROR,"退款失败,账户异常!");
            } else if (refund.getStatus().equals(Status.CLOSED)) {
                return AjaxResult.error(HttpStatus.ERROR,"用户余额不足或者订单超过退款期限!");
            }else{
                log.info("退款失败!,退款返回信息:{}", refund);
                return AjaxResult.error(HttpStatus.ERROR,"退款失败,未知异常!");
            }
        } catch (ServiceException e) {
            log.error("退款失败!,错误信息:{}", e.getMessage());
            return AjaxResult.error("退款失败!");
        } catch (Exception e) {
            log.error("服务返回成功,返回体类型不合法,或者解析返回体失败,错误信息:{}", e.getMessage());
            return AjaxResult.error("退款失败!");
        }
    }

退款通知

/**
     * 退款通知
     * @param request 请求参数
     * @author: zm
     * @date: 2024/10/9
     * @rerturn: java.lang.String
     */
    public void refundNotify(HttpServletRequest request) {
        try{
            log.info("------收到退款通知------");
            // 请求头Wechatpay-Signature
            String signature = request.getHeader("Wechatpay-Signature");
            // 请求头Wechatpay-nonce
            String nonce = request.getHeader("Wechatpay-Nonce");
            // 请求头Wechatpay-Timestamp
            String timestamp = request.getHeader("Wechatpay-Timestamp");
            // 微信支付证书序列号
            String serial = request.getHeader("Wechatpay-Serial");
            // 签名方式
            String signType = request.getHeader("Wechatpay-Signature-Type");
            // 构造 RequestParam
            RequestParam requestParam = new RequestParam.Builder()
                    .serialNumber(serial)
                    .nonce(nonce)
                    .signature(signature)
                    .timestamp(timestamp)
                    .signType(signType)
                    .body(getRequestBody(request))
                    .build();
            // 初始化 NotificationParser
            NotificationParser parser = new NotificationParser(config);
            // 以支付通知回调为例,验签、解密并转换成 Transaction
            log.info("退款验签");
            RefundNotification refund=null;
            try {
                // 以支付通知回调为例,验签、解密并转换成 Transaction
                refund = parser.parse(requestParam, RefundNotification.class);
            } catch (ValidationException e) {
                // 签名验证失败,返回 401 UNAUTHORIZED 状态码
                e.printStackTrace();
                log.error("sign verification failed", e);
            }
            if(refund!=null){
                log.info("验签成功!-退款回调结果:{}", refund.toString());
                //parse.getRefundStatus().equals("SUCCESS");说明退款成功
                WechatPayModel payModel=new WechatPayModel();
                payModel.setOutTradeNo(refund.getOutTradeNo());
                payModel.setTransactionId(refund.getTransactionId());
                payModel.setRefundState(refund.getRefundStatus().name());
                //业务逻辑
            }else{
                log.error("验签解析失败!");
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("退款回调失败!错误信息:{}", e.getMessage());
        }
    }

业务调试

postman调用,下单返回的二维码路径我没有写前端,我是用的在线工具生成的,偷懒的可以用在线工具生成,勤快的小伙伴可以自己写写前端生成二维码。

好了,微信支付到此就完事了,有疑问可以留言。

参考博文:Java接入微信支付详细教程——提供资料_java微信支付-CSDN博客

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值