系列文章:
1、Java对接微信JS支付-V3版本接口
2、Java对接微信H5支付-V3版本接口
3、Java对接微信Native(扫码)支付-V3版本接口
前言: 对接完H5支付和JS支付后因为无法满足所有场景的支付需求,所以还需要对接微信Native(扫码)支付,特来记录一下整个对接过程和遇到的一些坑
适用场景: Native支付适用于PC网站、实体店单品或订单、媒体广告支付等场景(博主的使用场景:PC端页面)
对接流程图:
1、Native支付产品介绍
2、Native下单
3、github代码参考
微信官网有详细的文档介绍,你需要准备那些东西
1、Native支付接入前准备
<dependency> <groupId>com.github.wechatpay-apiv3</groupId> <artifactId>wechatpay-java</artifactId> <version>0.2.12</version> </dependency> 12345 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; @PostConstruct public 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 @Bean public NotificationParser notificationParser() { return new NotificationParser((NotificationConfig) config); } } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596 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); } 123456789101112131415 service
@Resource private WechatPayOrderMapper wechatPayOrderMapper; @Resource private NativePayService nativePayService; public String h5Pay(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,例如500 log.error("服务返回状态异常: {}", e.getResponseBody()); } catch (MalformedMessageException e) { // 服务返回成功,返回体类型不合法,或者解析返回体失败 log.error("返回体类型不合法: {}", e.getMessage()); } catch (Exception e) { log.error("预下单异常: {}", e.getMessage()); } return null; } 123456789101112131415161718192021222324252627282930313233343536 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"); } } 123456789 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"); } 123456789101112131415161718192021222324252627282930313233343536 5. 编写微信工具类
微信的依赖中有很多好用的工具类,但是在他们的文档中都没有体现,需要耐心地找一下
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-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 return 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)); 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(); } } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123 6. 测试接口
测试需要使用互联网可以连通的域名,所以我是在生产环境新创建了一个二级域名做的测试,并且回调地址也必须是你申请审核通过的支付域名才可以。
7. 总结可能是我阅读文档的方式不太对,微信支付的官方开发文档写的真是一言难尽,看了好长时间才大致看明白。所以写下这篇文章,愿后面开发的兄弟们对接起来都可以开开心心,顺顺利利!一次成功!
相关知识
对接支付宝、微信、第三方支付,超详细讲解+demo演示
花瓣支付可以扫微信吗
聚合支付系统平台搭建
扫脸支付系统开发模式介绍
华为旗下支付公司获批更名为“花瓣支付”,不直接对标微信支付和支付宝丨大厂金融事
一文看“透”你所不知道在线支付
web支付基础教程
Java通用型支付+电商平台双系统实战
医院移动支付结算 你需要了解的几件事
华为“花瓣支付”来了,与微信、支付宝有什么区别
网址: Java对接微信扫码支付Native支付 https://m.huajiangbk.com/newsview948959.html
上一篇: java中支付模块如何实现 |
下一篇: 应用内支付服务(HMS) |