Java对接微信扫码支付Native支付-V3版本接口
Hi 👋, I'm shy有人见尘埃,有人见星辰 | ![]() |
系列文章:
1、Java对接微信JS支付-V3版本接口
2、Java对接微信H5支付-V3版本接口
3、Java对接微信Native(扫码)支付-V3版本接口
前言: 对接完H5支付和JS支付后因为无法满足所有场景的支付需求,所以还需要对接微信Native(扫码)支付,特来记录一下整个对接过程和遇到的一些坑
适用场景: Native支付适用于PC网站、实体店单品或订单、媒体广告支付等场景(博主的使用场景:PC端页面)
对接流程图:
-----------------------------------------------------对接开始-----------------------------------------------------
1. 查阅微信对接文档
1、Native支付产品介绍
2、Native下单
3、github代码参考
2. 数据准备
微信官网有详细的文档介绍,你需要准备那些东西
1、Native支付接入前准备
3. Java代码
1. 引入微信官网依赖
<dependency><groupId>com.github.wechatpay-apiv3</groupId><artifactId>wechatpay-java</artifactId><version>0.2.12</version>
</dependency>
2. 编写WeChatConfig,将微信公共配置进行配置化
import javax.annotation.PostConstruct;import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;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.service.payments.h5.H5Service;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;import lombok.Getter;/*** @desc: 微信config* @author: shy* @date: 2024/4/9 10:06*/
@Configuration
@Getter
public class WeChatConfig {/*** 商户号*/@Value("${wechat.pay.merchantId}")public String merchantId;/*** 商户API私钥路径*/@Value("${wechat.pay.privateKeyPath}")public String privateKeyPath;/*** 商户证书序列号*/@Value("${wechat.pay.merchantSerialNumber}")public String merchantSerialNumber;/*** 商户APIV3密钥*/@Value("${wechat.pay.apiV3Key}")public String apiV3Key;/*** AppId*/@Value("${wechat.pay.appId}")public String appId;private Config config;@PostConstructpublic void initConfig() {// 使用自动更新平台证书的RSA配置// 一个商户号只能初始化一个配置,否则会因为重复的下载任务报错config = new RSAAutoCertificateConfig.Builder().merchantId(merchantId).privateKeyFromPath(privateKeyPath).merchantSerialNumber(merchantSerialNumber).apiV3Key(apiV3Key).build();}@Primary@Bean()public H5Service h5Service() {return new H5Service.Builder().config(config).build();}@Primary@Bean()public JsapiService jsapiService() {return new JsapiService.Builder().config(config).build();}@Primary@Bean()public NativePayService nativePayService() {return new NativePayService.Builder().config(config).build();}@Primary@Beanpublic NotificationParser notificationParser() {return new NotificationParser((NotificationConfig) config);}
}
3. 编写预下单接口
controller
@Autowired
WechatService wechatService;@RequestMapping("/wxpay/v3/nativePay")
public Result<String> nativePay(Integer memberFeeType, HttpServletRequest request) {MemberFeeTypeEnum memberFeeTypeEnum = MemberFeeTypeEnum.getMemberFeeTypeEnumByType(memberFeeType);if (Objects.isNull(memberFeeTypeEnum)) {return Result.fail(ResultEnum.PARAM_ERROR);}String nativeUrl = wechatService.nativePay(memberFeeType, request, memberFeeTypeEnum);if (StringUtils.isBlank(nativeUrl)) {return Result.fail("支付失败");}return Result.success(nativeUrl);
}
service
@Resource
private WechatPayOrderMapper wechatPayOrderMapper;
@Resource
private NativePayService nativePayService;public String nativePay(Integer memberFeeType, HttpServletRequest request, MemberFeeTypeEnum memberFeeTypeEnum) {com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest prepayRequest = new com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest();prepayRequest.setAppid(appId);prepayRequest.setMchid(merchantId);prepayRequest.setOutTradeNo(WeChatUtil.generateTradeNumber());prepayRequest.setDescription(memberFeeTypeEnum.getDesc());prepayRequest.setNotifyUrl(notifyUrl);com.wechat.pay.java.service.payments.nativepay.model.Amount amount = new com.wechat.pay.java.service.payments.nativepay.model.Amount();amount.setTotal(memberFeeTypeEnum.getPrice());prepayRequest.setAmount(amount);// 调用下单方法,得到应答PrepayResponse response;try {com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse prepay = nativePayService.prepay(prepayRequest);//预支付成功,创建预支付订单int add = addWechatPayOrder(request, prepayRequest.getOutTradeNo(), memberFeeType, memberFeeTypeEnum.getPrice(),WeChatPayTradeTypeEnum.扫码支付);if (add <= 0) {return null;}return prepay.getCodeUrl();} catch (HttpException e) { // 发送HTTP请求失败log.error("发送HTTP请求失败: {}", e.getHttpRequest());} catch (ServiceException e) { // 服务返回状态小于200或大于等于300,例如500log.error("服务返回状态异常: {}", e.getResponseBody());} catch (MalformedMessageException e) { // 服务返回成功,返回体类型不合法,或者解析返回体失败log.error("返回体类型不合法: {}", e.getMessage());} catch (Exception e) {log.error("预下单异常: {}", e.getMessage());}return null;
}
4. 编写回调接口
controller
@RequestMapping("/v3/payNotify")
public WeChatPayResult payNotify(HttpServletRequest request) {try {return wechatService.payNotify(request);} catch (Exception e) {log.error("微信回调异常: ", e);return new WeChatPayResult("FAIL", "FAIL");}
}
service
@Resource
private NotificationParser notificationParser;
@Resource
private WechatPayOrderMapper wechatPayOrderMapper;@Transactional
public WeChatPayResult payNotify(HttpServletRequest request) throws Exception{log.info("微信回调开始-------------------------");Transaction transaction;try {transaction = notificationParser.parse(WeChatUtil.handleNodifyRequestParam(request), Transaction.class);if (transaction.getTradeState() == Transaction.TradeStateEnum.SUCCESS) {WechatPayOrder wechatPayOrder = wechatPayOrderMapper.getOrderByTradeNo(transaction.getOutTradeNo());if (Objects.isNull(wechatPayOrder)) {log.error("订单不存在, 订单号: {}", transaction.getOutTradeNo());return new WeChatPayResult("SUCCESS", "ORDER_DOES_NOT_EXIST");}//校验订单状态:若订单已支付则直接返回成功if (Objects.equals(wechatPayOrder.getTradeStatus(), WeChatPayTradeStatusEnum.已支付.getType())) {log.error("订单已支付, 订单号: {}", transaction.getOutTradeNo());return new WeChatPayResult("SUCCESS", "ORDER_PAID");}//支付成功-修改订单状态wechatPayOrderMapper.updateTradeStatus(transaction.getOutTradeNo(), transaction.getTransactionId(), wechatPayOrder.getVersion());//支付成功-添加会员MemberFeeTypeEnum memberFeeTypeEnum = MemberFeeTypeEnum.getMemberFeeTypeEnumByType(wechatPayOrder.getMemberFeeType());userAwardService.addAward(wechatPayOrder.getUserId(), memberFeeTypeEnum.getMemberDays());}} catch (ValidationException e) {// 签名验证失败,返回 401 UNAUTHORIZED 状态码log.error("签名验证失败: ", e);return new WeChatPayResult("FAIL", "UNAUTHORIZED");}log.info("微信回调结束, 微信回调报文: {}", transaction);return new WeChatPayResult("SUCCESS", "SUCCESS");
}
5. 编写微信工具类
微信的依赖中有很多好用的工具类,但是在他们的文档中都没有体现,需要耐心地找一下
WeChatUtil
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.util.Random;import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;import com.wechat.pay.java.core.cipher.RSASigner;
import com.wechat.pay.java.core.cipher.SignatureResult;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.core.util.NonceUtil;
import com.wechat.pay.java.core.util.PemUtil;/*** @desc: 微信工具类* @author: shy* @date: 2024/4/8 16:10*/
public class WeChatUtil {private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";private static final Random RANDOM = new SecureRandom();/*** 生成订单号** @param* @return String* @author shy* @date 2024/4/8 16:15*/public static String generateTradeNumber() {// 定义订单号前缀String prefix = "shy";// 当前年月日String currentTimeStr = DateUtil.getCurrent("yyyyMMddHHmmss");// 获取当前时间戳long timestamp = System.currentTimeMillis();// 构造订单号return prefix + currentTimeStr + timestamp;}/*** 获取随机字符串 Nonce Str** @param* @return String* @author shy* @date 2024/4/16 17:07*/public static String generateNonceStr() {return NonceUtil.createNonce(32);}/*** 获取当前时间戳,单位秒* @param* @return long* @author shy* @date 2024/4/16 17:10*/public static long getCurrentTimestamp() {return System.currentTimeMillis() / 1000;}public static String getSign(String signatureStr, String privateKeyPath, String merchantSerialNumber) {PrivateKey privateKey = PemUtil.loadPrivateKeyFromPath(privateKeyPath);RSASigner rsaSigner = new RSASigner(merchantSerialNumber, privateKey);SignatureResult signatureResult = rsaSigner.sign(signatureStr);return signatureResult.getSign();}/*** 构造 RequestParam** @param request* @return RequestParam* @author shy* @date 2024/4/9 11:16*/public static RequestParam handleNodifyRequestParam(HttpServletRequest request) throws IOException {// 请求头Wechatpay-SignatureString signature = request.getHeader("Wechatpay-Signature");// 请求头Wechatpay-nonceString nonce = request.getHeader("Wechatpay-Nonce");// 请求头Wechatpay-TimestampString timestamp = request.getHeader("Wechatpay-Timestamp");// 微信支付证书序列号String serial = request.getHeader("Wechatpay-Serial");// 签名方式String signType = request.getHeader("Wechatpay-Signature-Type");// 构造 RequestParamreturn new RequestParam.Builder().serialNumber(serial).nonce(nonce).signature(signature).timestamp(timestamp).signType(signType).body(getRequestBody(request)).build();}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, StandardCharsets.UTF_8));String line;while ((line = reader.readLine()) != null) {sb.append(line);}} catch (IOException e) {throw new IOException("读取返回支付接口数据流出现异常!");} finally {if (reader != null) {reader.close();}}return sb.toString();}
}
6. 测试接口
测试需要使用互联网可以连通的域名,所以我是在生产环境新创建了一个二级域名做的测试,并且回调地址也必须是你申请审核通过的支付域名才可以。
7. 总结
可能是我阅读文档的方式不太对,微信支付的官方开发文档写的真是一言难尽,看了好长时间才大致看明白。所以写下这篇文章,愿后面开发的兄弟们对接起来都可以开开心心,顺顺利利!一次成功!