Java--微信支付--小程序支付--v3版--完整的代码例子

1年前 (2023) 程序员胖胖胖虎阿
172 0 0

微信官方文档–小程序支付:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml

微信官方文档–支付接入前准备https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_1.shtml

下载平台证书报错处理:https://blog.csdn.net/aiguo94/article/details/124037688

使用Apache HttpClient处理HTTP的Java开发库(封装了签名生成、签名验证、敏感信息加/解密、媒体文件上传等基础功能):https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient

maven导包

<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-apache-httpclient</artifactId>
    <version>0.4.5</version>
</dependency>
<dependency>
    <groupId>com.github.javen205</groupId>
    <artifactId>IJPay-WxPay</artifactId>
</dependency>

实体类

wxpay_v3.properties(微信支付配置,有appId,商户id,API密钥,证书地址,回调地址,该文件在resource根目录)
WxPayV3Bean (微信支付配置对象,获取wxpay_v3.properties中的配置的参数值)
PayOrderDTO (微信下单对象)
WxPayVO (微信下单后返回给前端的对象)
WxQueryVO(微信查单返回的对象)
WxPayStateEnum (微信支付订单状态枚举)

wxpay_v3.properties(其中,apiclient_key.pem,apiclient_cert.pem,apiclient_cert.p12是通过接入前准备,在微信支付商户平台配置后获取,platformCert.pem的获取参考https://blog.csdn.net/aiguo94/article/details/124037688)

v3.appId=wx10xxxxxxxxxxxx
v3.mchId=16xxxxxxxxx
v3.mchSerialNo=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
v3.apiKey3=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
v3.keyPath=/cert/apiclient_key.pem
v3.certPath=/cert/apiclient_cert.pem
v3.certP12Path=/cert/apiclient_cert.p12
v3.platformCertPath=/cert/platformCert.pem
v3.notifyUrl=https:/xxxxxxxxxxxxxxxxx/app/pay/payNotify
import lombok.Data;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * 配置对象
 * @Author smilehan
 */
@Component
@ConfigurationProperties(prefix = "v3")
@Data
@ToString
public class WxPayV3Bean {
    private String appId;
    private String mchId;
    private String mchSerialNo;
    private String apiKey3;
    private String keyPath;
    private String certPath;
    private String certP12Path;
    private String platformCertPath;
    private String notifyUrl;
}
import lombok.Data;
import lombok.experimental.Accessors;

/**
 * 支付下单
 * @Author smilehan
 */
@Data
@Accessors(chain = true)
public class PayOrderDTO {
    private Long userId;
    private Integer totalPrice;
    private String goodsName;
    private String openId;
    private String orderSn;
}
import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
 * 微信下单返回
 * @Author smilehan
 */
@Data
@Accessors(chain = true)
public class WxPayVO implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 预支付交易会话标识小程序下单接口返回的prepay_id参数值
     */
    private String prepayId;
    /**
     * 随机字符串
     */
    private String nonceStr;
    /**
     * 时间戳
     */
    private String timeStamp;
    /**
     * 签名
     */
    private String paySign;
}
import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
 * 微信订单查询
 * @Author smilehan
 */
@Data
@Accessors(chain = true)
public class WxQueryVO implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 订单号
     */
    private String outTradeNo;

    /**
     * 交易状态(0:支付成功 1:转入退款 2:未支付 3:已关闭 4:已撤销(付款码支付)" +
     *             " 5:用户支付中(付款码支付) 6:支付失败(其他原因,如银行返回失败) 7:已接收,等待扣款 8: 订单不存在)
     */
    private Integer tradeState;

    /**
     * 交易状态描述
     */
    private String tradeStateDesc;

    /**
     * 支付单号
     */
    private String transactionId;
}
import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * 微信支付状态值
 * @Author smilehan
 */
@Getter
@AllArgsConstructor
public enum  WxPayStateEnum {
    SUCCESS(0, "SUCCESS", "支付成功"),
    REFUND(1, "REFUND", "转入退款"),
    NOTPAY(2, "NOTPAY", "未支付"),
    CLOSED(3, "CLOSED", "已关闭"),
    REVOKED(4, "REVOKED", "已撤销(付款码支付)"),
    USERPAYING(5, "USERPAYING", "用户支付中(付款码支付)"),
    PAYERROR(6, "PAYERROR", "支付失败(其他原因,如银行返回失败)"),
    ACCEPT(7, "ACCEPT", "已接收,等待扣款"),
    ABSENCE(8, "ABSENCE", "订单不存在"),
    OK(9, "OK", "OK"),
    PROCESS(10, "PROCESSING", "PROCESSING")
    ;
    private Integer code;

    private String name;

    private String description;

    public static WxPayStateEnum getByName(String name) {
        for (WxPayStateEnum wxPayStateEnum : WxPayStateEnum.values()) {
            if (wxPayStateEnum.getName().equals(name)) {
                return wxPayStateEnum;
            }
        }
        return null;
    }

}

工具类

WxPayUtil(支付util,初始化httpClient,生成签名,支付时间转换)

import cn.hutool.core.date.DateTime;
import cn.hutool.core.io.FileUtil;
import com.ijpay.core.kit.PayKit;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import com.entity.pay.WxPayV3Bean;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;

/**
 * 支付相关
 * @Author smilehan
 */
@Component
@Slf4j
@Data
public class WxPayUtil {
    @Resource
    private WxPayV3Bean wxPayV3Bean;

    private CloseableHttpClient httpClient;
    private Verifier verifier;

    @PostConstruct
    public void initHttpClient(){
        log.info("微信支付httpClient初始化");
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(FileUtil.getInputStream(wxPayV3Bean.getKeyPath()));
        X509Certificate wechatPayCertificate = PemUtil.loadCertificate(FileUtil.getInputStream(wxPayV3Bean.getPlatformCertPath()));

        ArrayList<X509Certificate> listCertificates = new ArrayList<>();
        listCertificates.add(wechatPayCertificate);

        httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(wxPayV3Bean.getMchId(), wxPayV3Bean.getMchSerialNo(), merchantPrivateKey)
                .withWechatPay(listCertificates)
                .build();
    }


    @PostConstruct
    public void initVerifier() throws Exception{
        log.info("微信支付Verifier初始化");
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(FileUtil.getInputStream(wxPayV3Bean.getKeyPath()));
        // 获取证书管理器实例
        CertificatesManager certificatesManager = CertificatesManager.getInstance();
        // 向证书管理器增加需要自动更新平台证书的商户信息
        certificatesManager.putMerchant(wxPayV3Bean.getMchId(), new WechatPay2Credentials(wxPayV3Bean.getMchId(),
                new PrivateKeySigner(wxPayV3Bean.getMchSerialNo(), merchantPrivateKey)), wxPayV3Bean.getApiKey3().getBytes(StandardCharsets.UTF_8));
        // 从证书管理器中获取verifier
        verifier = certificatesManager.getVerifier(wxPayV3Bean.getMchId());
    }


    /**
     * 生成签名
     * @param appId
     * @param timestamp
     * @param nonceStr
     * @param prepayId
     * @return
     * @throws Exception
     */
    public String getSign(String appId, String timestamp, String nonceStr, String prepayId) throws Exception{
        String message=appId + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + prepayId + "\n";

        String sign = PayKit.createSign(message, wxPayV3Bean.getKeyPath());

        return sign;
    }


    /**
     * 回调的支付时间转成date
     * @param dateTimeString
     * @return
     */
    public Date dateTimeToDate(String dateTimeString){
        DateTime dateTime = new DateTime(dateTimeString);
        long timeInMillis = dateTime.toCalendar(Locale.getDefault()).getTimeInMillis();
        return new Date(timeInMillis);
    }

}

service

WxPayService (生成本系统自有订单后调用,微信下单,生成预支付交易会话等信息WxPayVO返回给小程序)

import com.web.controller.dto.pay.PayOrderDTO;
import com.web.controller.vo.pay.WxPayVO;
import com.web.controller.vo.pay.WxQueryVO;

public interface WxPayService {
    /**
     * 微信下单
     * @param dto
     * @return
     */
    WxPayVO createOrder(PayOrderDTO dto) throws Exception;

    /**
     * 查询订单。queryType。1,根据支付订单查询。2,根据系统自有订单号查询
     * @return
     */
    WxQueryVO queryOrder(String payNumber,Integer queryType);
}
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.ijpay.core.kit.WxPayKit;
import com.base.execption.RRException;
import com.common.enums.pay.WxPayStateEnum;
import com.common.utils.WxPayUtil;
import com.entity.pay.WxPayV3Bean;
import com.service.pay.WxPayService;
import com.web.controller.dto.pay.PayOrderDTO;
import com.web.controller.vo.pay.WxPayVO;
import com.web.controller.vo.pay.WxQueryVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import static org.apache.http.HttpHeaders.ACCEPT;
import static org.apache.http.HttpHeaders.CONTENT_TYPE;
import static org.apache.http.entity.ContentType.APPLICATION_JSON;

/**
 * 微信下单/查单
 * @Author smilehan
 */
@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService {
    @Autowired
    private WxPayV3Bean wxPayV3Bean;

    @Autowired
    private WxPayUtil wxPayUtil;

    @Override
    public WxPayVO createOrder(PayOrderDTO orderDetailEntity) throws Exception {
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
        httpPost.addHeader(ACCEPT, APPLICATION_JSON.toString());
        httpPost.addHeader(CONTENT_TYPE, APPLICATION_JSON.toString());

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();

        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("mchid", wxPayV3Bean.getMchId())
                .put("appid", wxPayV3Bean.getAppId())
                .put("description", orderDetailEntity.getGoodsName())
                .put("notify_url", wxPayV3Bean.getNotifyUrl())
                .put("out_trade_no", orderDetailEntity.getOrderSn())
                .put("attach", orderDetailEntity.getUserId().toString());
        rootNode.putObject("amount")
                .put("total", orderDetailEntity.getTotalPrice());
        rootNode.putObject("payer")
                .put("openid", orderDetailEntity.getOpenId());

        objectMapper.writeValue(bos, rootNode);
        httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));

        CloseableHttpClient httpClient = wxPayUtil.getHttpClient();
        CloseableHttpResponse response = httpClient.execute(httpPost);

        WxPayVO wxPayVO = new WxPayVO();
        try {
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                wxPayVO.setNonceStr(WxPayKit.generateStr());
                wxPayVO.setTimeStamp(System.currentTimeMillis() / 1000 + "");
                JSONObject jsonObject = JSONObject.parseObject(EntityUtils.toString(response.getEntity()));
                String prepay_id = jsonObject.getString("prepay_id");
                wxPayVO.setPrepayId("prepay_id=" + prepay_id);
                wxPayVO.setPaySign(wxPayUtil.getSign(wxPayV3Bean.getAppId(), wxPayVO.getTimeStamp(), wxPayVO.getNonceStr(), wxPayVO.getPrepayId()));
                log.info("200success,return body:{} ", EntityUtils.toString(response.getEntity()));
            } else if (statusCode == 204) {
                log.info("204success");
            } else {
                log.info("failed,resp code:{} ,return body:{} ", statusCode, EntityUtils.toString(response.getEntity()));
                throw new IOException("request failed");
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception("系统错误");
        }
        return wxPayVO;
    }

    /**
     * 订单查询
     *
     * @return
     */
    @Override
    public WxQueryVO queryOrder(String payNumber, Integer queryType) {
        WxQueryVO vo = new WxQueryVO();
        try {
            String url = "";
            if (queryType.equals(1)) {
                //根据支付订单查询
                url = "https://api.mch.weixin.qq.com/v3/pay/transactions/id/" + payNumber;
            } else {
                //根据系统自有订单号查询
                url = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + payNumber;
            }
            URIBuilder uriBuilder = new URIBuilder(url);
            uriBuilder.setParameter("mchid", wxPayV3Bean.getMchId());

            HttpGet httpGet = new HttpGet(uriBuilder.build());
            httpGet.addHeader(ACCEPT, APPLICATION_JSON.toString());

            CloseableHttpClient httpClient = wxPayUtil.getHttpClient();
            CloseableHttpResponse response = httpClient.execute(httpGet);

            String bodyAsString = EntityUtils.toString(response.getEntity());
            JSONObject data = JSON.parseObject(bodyAsString);
            log.info("微信订单查询结果:{}", data);
            if (StringUtils.hasText(data.getString("out_trade_no"))
                    && StringUtils.hasText(data.getString("trade_state"))) {
                vo.setOutTradeNo(data.getString("out_trade_no"));
                vo.setTradeState(WxPayStateEnum.getByName(data.getString("trade_state")).getCode());
                vo.setTradeStateDesc(data.getString("trade_state_desc"));
                vo.setTransactionId(data.getString("transaction_id"));
            } else {
                vo.setTradeState(WxPayStateEnum.ABSENCE.getCode());
                vo.setTradeStateDesc(data.getString("message"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return vo;
    }
}

controller

WxPayV3Controller (用于支付成功后的回调处理)

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import com.common.enums.pay.WxPayStateEnum;
import com.common.utils.WxPayUtil;
import com.entity.pay.WxPayV3Bean;
import com.web.controller.dto.pay.PayNotifyDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.wildfly.common.Assert;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*;
import static org.apache.http.HttpHeaders.CONTENT_TYPE;
import static org.apache.http.entity.ContentType.APPLICATION_JSON;


/**
 * 微信支付回调
 * @Author smilehan
 */
@RestController
@RequestMapping("/app/pay")
@Slf4j
public class WxPayV3Controller {

    @Autowired
    private WxPayV3Bean wxPayV3Bean;

    @Autowired
    private WxPayUtil wxPayUtil;

    /**
     * 微信支付回调通知
     */
    @PostMapping(value = "/payNotify")
    public void payNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        log.info("微信回调开始");
        //读取请求体的信息
        ServletInputStream inputStream = request.getInputStream();
        StringBuffer stringBuffer = new StringBuffer();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        String s;
        //读取回调请求体
        while ((s = bufferedReader.readLine()) != null) {
            stringBuffer.append(s);
        }
        String s1 = stringBuffer.toString();

        String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
        String nonce = request.getHeader(WECHAT_PAY_NONCE);
        String serialNo = request.getHeader(WECHAT_PAY_SERIAL);
        String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
        // 构建request,传入必要参数
        NotificationRequest notificationRequest = new NotificationRequest.Builder().withSerialNumber(serialNo)
                .withNonce(nonce)
                .withTimestamp(timestamp)
                .withSignature(signature)
                .withBody(s1)
                .build();
        NotificationHandler handler = new NotificationHandler(wxPayUtil.getVerifier(), wxPayV3Bean.getApiKey3().getBytes(StandardCharsets.UTF_8));
        // 验签和解析请求体
        Notification notification = handler.parse(notificationRequest);
        Assert.assertNotNull(notification);
        log.info("回调结果:{}",notification);

        JSONObject jsonObject = JSONObject.parseObject(notification.getDecryptData());
        String tradeState = jsonObject.getString("trade_state");
        String paySn = jsonObject.getString("transaction_id");
        String successTime = jsonObject.getString("success_time");
        String attach = jsonObject.getString("attach");
        JSONObject amount = JSONObject.parseObject(jsonObject.getString("amount"));
        String payerTotal = amount.getString("payer_total");
        String orderSn = jsonObject.getString("out_trade_no");

        try {
            PayNotifyDTO payNotifyDTO = new PayNotifyDTO().setPaySn(paySn)
                    .setPayPrice(new BigDecimal(payerTotal).divide(new BigDecimal("100")))
                    .setPayTime(wxPayUtil.dateTimeToDate(successTime))
                    .setAttach(attach)
                    .setOrderSn(orderSn);
            if (StringUtils.hasText(tradeState) && WxPayStateEnum.SUCCESS.getName().equals(tradeState)) {
                //用payNotifyDTO处理相关逻辑(比如:更改订单状态为已支付,将微信支付单号-支付金额-支付时间存到订单表等等)
            }
            response.setStatus(200);
        }catch (Exception e){
            Map<String, String> map = new HashMap<>(12);
            response.setStatus(500);
            map.put("code", "FAIL");
            map.put("message", "失败");
            response.addHeader(CONTENT_TYPE, APPLICATION_JSON.toString());
            response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
        }finally {
            response.flushBuffer();
        }
    }
}

小程序,确定订单信息后——>确认支付——>调用后端的生成订单接口——>后端生成系统自有订单——>后端通过自有订单号、金额、openid等去请求微信下单接口,微信返回预支付交易会话标识prepay_id——>后端给appid、timestamp、nonceStr、prepayId签名,并将签名、timestamp、nonceStr、prepay_id返回给小程序——>小程序调用wx.requestPayment拉起微信支付——>用户付完钱后,微信根据下单时候填的回调地址,请求自有系统的回调接口,自有系统处理相应的逻辑

相关文章

暂无评论

暂无评论...