一、JDK版本需要1.8u162+以上版本

微信支付-开发教程_微信支付

<!--微信支付 V3-->
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-java</artifactId>
            <version>0.2.7</version>
        </dependency>
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-apache-httpclient</artifactId>
            <version>0.4.9</version>
        </dependency>

二、前期准备

官网引导:接入前准备-小程序支付 | 微信支付商户平台文档中心

①appid:小程序appid-登录小程序后台获取

②secret:小程序密钥-登录小程序后台获取

登录小程序后台:

微信支付-开发教程_微信小程序支付_02

微信支付-开发教程_java_03

③appV3Secret:商户apiV3密钥-登录商户后台获取

④mchid:商户号-登录商户后台获取(商户号要绑定小程序)

⑤certpath:微信平台证书-登录商户后台获取

⑥privateKeyPath:商户私钥证书-登录商户后台获取

⑦merchantSerialNumber:商户证书序列化-登录商户后台获取

登录商户后台:

三、证书存放地

微信支付-开发教程_java_04

四、配置与加载

# application-dev.yml

wx:
  miniapp:
    configs:
    - appid: wx1fcxxxxxxxx
      secret: 1exxxxxxxxxxxxxxxxxxxxxxxxxxxxx
      token:  #微信小程序消息服务器配置的token
      aesKey:  #微信小程序消息服务器配置的EncodingAESKey
      msgDataFormat: JSON
      appV3Secret: ADxxxAas654xxxxxxxxxxxxxxxxxx # V3密钥
      mchid: 12345678 # 商户号
      certpath: D:/workspace/hxj-miniprogram-java/src/main/resources/wxpay/apiclient_cert.pem # 微信平台证书(发布时换成linux地址)
      privateKeyPath: D:/workspace/hxj-miniprogram-java/src/main/resources/wxpay/apiclient_key.pem # 商户私钥证书(发布时换成linux地址)
      payCallBackUrl: https://xxxxxxxxxxxxx/wechatPay/payCallback # 支付结束回调地址
      timeExpire: 1200000 # 交易过期时间(单位毫秒)
      merchantSerialNumber: 37ABCD098765ADCBF124656099AB # 商户证书序列号
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;

/**
 * @author xuhj
 */
@Slf4j
@Data
@ConfigurationProperties(prefix = "wx.miniapp")
public class WxMaProperties {
    private List<Config> configs;

    @Data
    public static class Config {
        /**
         * 设置微信小程序的appid
         */
        private String appid;

        /**
         * 设置微信小程序的Secret
         */
        private String secret;

        /**
         * 设置微信小程序消息服务器配置的token
         */
        private String token;

        /**
         * 设置微信小程序消息服务器配置的EncodingAESKey
         */
        private String aesKey;

        /**
         * 消息格式,XML或者JSON
         */
        private String msgDataFormat;
        /**
         * appV3Secret V3密钥
         */
        private String appV3Secret;
        /**
         * mchid 商户号
         */
        private String mchid;
        /**
         * 微信支付平台证书地址,注意 windows地址与linux地址不同
         */
        private String certpath;
        /**
         * 商户API证书的路径(一般和上边的certpath在一起下载)
         */
        private String privateKeyPath;
        /**
         * 支付回调地址
         */
        private String payCallBackUrl;
        /**
         * 交易过期时间(单位毫秒)
         */
        private Long timeExpire;
        /**
         * 商户证书序列号
         */
        private String merchantSerialNumber;

    }
}

五、证书自动更新

import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class WxPayConfig {

    @Autowired
    private WxMaProperties properties;

    /**
     * 初始化商户配置(只能加载一次)
     * 确保是单例
     *
     * @return
     */
    @Bean
    public RSAAutoCertificateConfig rsaAutoCertificateConfig() {
        RSAAutoCertificateConfig config = new RSAAutoCertificateConfig.Builder()
                .merchantId(properties.getConfigs().get(0).getMchid())
                .privateKeyFromPath(properties.getConfigs().get(0).getPrivateKeyPath())
                .merchantSerialNumber(properties.getConfigs().get(0).getMerchantSerialNumber())
                .apiV3Key(properties.getConfigs().get(0).getAppV3Secret())
                .build();
        return config;
    }
}

六、预支付和回调

import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.model.Transaction;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
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 java.io.IOException;
import java.util.List;

import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.WECHAT_PAY_SIGNATURE;
import static com.wechat.pay.java.core.http.Constant.WECHAT_PAY_NONCE;
import static com.wechat.pay.java.core.http.Constant.WECHAT_PAY_TIMESTAMP;

/**
 * <p>
 * 微信支付 前端控制器
 * </p>
 *
 * @author sunziwen
 * @since 2023-03-23
 */
@Api(tags = {"微信支付"})
@Slf4j
@RestController
@RequestMapping("/wechatPay")
@Validated
public class PayController extends BaseController {
    /**
     * 配置文件
     */
    @Autowired
    private WxMaProperties properties;
    @Autowired
    private RSAAutoCertificateConfig rsaAutoCertificateConfig;
    @Autowired
    private IOrderMasterService iOrderMasterService;

    @ApiOperation(value = "预支付-委托")
    @PostMapping("preparePayment")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "openid", value = "openid"),
            @ApiImplicitParam(name = "description", value = "商品描述"),
            @ApiImplicitParam(name = "outTradeNo", value = "商户订单号"),
            @ApiImplicitParam(name = "total", value = "订单总金额,单位为分")
    })
    public ServiceResult<?> preparePayment(String openid, String description, String outTradeNo, Integer total) {
        // 初始化服务
        JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(rsaAutoCertificateConfig).build();
        PrepayRequest request = new PrepayRequest();

        // 应用ID
        request.setAppid(properties.getConfigs().get(0).getAppid());
        // 直连商户号
        request.setMchid(properties.getConfigs().get(0).getMchid());
        // 商品描述
        request.setDescription(description);
        // 商户订单号
        request.setOutTradeNo(outTradeNo);
        // 交易结束时间:2018-06-08T10:34:56+08:00
        request.setTimeExpire(timeStampToRfc3339(System.currentTimeMillis() + properties.getConfigs().get(0).getTimeExpire()));
        // 附加数据:在查询API和支付通知中原样返回,可作为自定义参数使用
        request.setAttach("");
        // 回调地址:异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 公网域名必须为https
        request.setNotifyUrl(properties.getConfigs().get(0).getPayCallBackUrl());
        // 商品标记,代金券或立减优惠功能的参数
        request.setGoodsTag("");
        // 限制支付类型
//        request.setLimitPay(LimitPayEnum.);
        // 电子发票入口开放标识:传入true时,支付成功消息和支付详情页将出现开票入口。需要在微信支付商户平台或微信公众平台开通电子发票功能,传此字段才可生效
        request.setSupportFapiao(true);
        Amount amount = new Amount();
        amount.setTotal(total);
        amount.setCurrency("CNY");
        // 订单金额信息
        request.setAmount(amount);
        Payer payer = new Payer();
        payer.setOpenid(openid);
        // 支付者信息
        request.setPayer(payer);
        // 优惠功能
        request.setDetail(null);
        // 支付场景描述
        request.setSceneInfo(null);
        // 结算信息
        request.setSettleInfo(null);

        PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(request);
        //重复支付判断TODO

        // 存储预付订单信息 TODO
        log.info("发起预支付成功-" + response.toString());

        return success(response);
    }

    @ApiOperation(value = "预支付-回调(或退款)")
    @RequestMapping("payCallback")
    public synchronized String payCallback(HttpServletRequest request) throws IOException {
        log.info("收到支付回调-------------");

        // 请求头Wechatpay-nonce
        String nonce = request.getHeader(WECHAT_PAY_NONCE);
        // 请求头Wechatpay-Timestamp
        String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
        // 请求头Wechatpay-Signature
        String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
        // 微信支付证书序列号 TODO 这里千万不能搞错,不能是商家证书序列号!!!
        String serial = request.getHeader("Wechatpay-Serial");
        // 签名方式
        String signType = request.getHeader("Wechatpay-Signature-Type");

        // 构造 RequestParam
        RequestParam requestParam = new RequestParam.Builder()
                .serialNumber(serial) // TODO 不能搞错!!!
                .nonce(nonce)
                .signature(signature)
                .timestamp(timestamp)
                .signType(signType)
                .body(getRequestBody())
                .build();

        // 初始化 NotificationParser
        NotificationParser parser = new NotificationParser(rsaAutoCertificateConfig);

        // 以支付通知回调为例,验签、解密并转换成 Transaction
        log.info("验签参数:" + requestParam);
        Transaction transaction = parser.parse(requestParam, Transaction.class);
        log.info("验签成功!-支付回调结果:" + transaction.toString());
//        RefundNotification refundNotification = parser.parse(requestParam, RefundNotification.class);
        // 修改订单之前,主动去微信支付主动查询订单是否已经支付成功,这一步不能少,防止有人假冒主动post接口。

        /**
         * 收到支付成功的回调:这里进行处理业务
         */
//        List<OrderMaster> orderMasters = iOrderMasterService.lambdaQuery().eq(OrderMaster::getOrderNo, transaction.getOutTradeNo()).list();
//        Assert.isTrue(orderMasters.size() == 1, "根据订单编号未查到订单OR查到了多个订单!!!");
//        if (Transaction.TradeStateEnum.SUCCESS.equals(transaction.getTradeState())) {
//            //支付成功 0待付款1待发货2待收货3已完成4已取消
//            orderMasters.get(0).setBuyStatus(1);
//            // 1待送仓2待打款3已完成4已取消
//            orderMasters.get(0).setSaleStatus(1);
//            // 1现金,2余额,3网银,4支付宝,5微信
//            orderMasters.get(0).setPaymentMethod(5);
//            return "{\"code\": \"FAIL\",\"message\": \"失败\"}";
//        } else {
//            log.info("支付未成功:" + transaction.getTradeState());
//        }
//        // TODO 存储支付流水

        return "{\"code\": \"FAIL\",\"message\": \"失败\"}";
    }

}