springboot 使用微信支付
参考:Java实现微信支付_被编程征服的秃发女子的博客-CSDN博客
一.微信支付流程
支付流程:
用户点击支付按钮调用接口 [/deposit
] =>
返回给小程序payInfo和订单编号orderNum =>
小程序端拿着payInfo和orderNum弹出支付 =>
等待用户支付完成调用支付完成接口 [/deposit/success
],支付完成接口中验证是否存在订单编号,存在则订单充值成功,插入流水和增加余额。
提现流程:
用户点击提现调用接口 [/withdraw
] 等待后台管理审核 =>
后台管理调用接口 [/agree
] 同意提现、 [/reject
] 拒绝提现,同时操作流水。
需要注意的是,在这个项目中,用户提现使用的是微信的 转账到账户 ,而不是网上大部分教程中的 企业付款到个人。
参考:Java整合微信商家转账到个人&开通流程 -CSDN博客_微信商户转账到个人
二.项目导入微信支付
1.引入依赖 并 更新 Maven
<!-- 微信支付 --> <dependency> <groupId>com.github.binarywang</groupId> <artifactId>wx-java-pay-spring-boot-starter</artifactId> <version>4.4.0</version> </dependency> <dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-miniapp</artifactId> <version>4.2.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
2.Yaml中添加以下
project: weixin: #appid app-id: 'xxxx' #密钥 app-secret: 'xxxx' # 商户号 mch-id: 111111 # 商户密钥 mch-key: xxxxx # 用户付款回调地址 保证外网能访问 pay-notify-url: https://test.cn/recycle/wx/pay/notify # 用户退款回调地址 refund-notify-url: https://test.cn/recycle/wx/pay/refund/notify # p12证书的位置,可以绝对路径,可以指定类路径 以classpath:开头(classpath为项目resource文件夹) cert-path: classpath:apiclient_cert.pem key-path: classpath:apiclient_key.pem
3. 新建一个属性类 WeChatProperties
@Configuration @ConfigurationProperties(prefix = "project.weixin") @Data public class WeChatProperties { private String appId; private String appSecret; private String mchId; private String mchKey; private String payNotifyUrl; private String refundNotifyUrl; private String keyPath; private String certPath; }
4.新建配置类 WeChatConfig
@Configuration public class WeChatConfig { @Autowired private WeChatProperties properties; @Bean public WxPayConfig wxPayConfig() { WxPayConfig payConfig = new WxPayConfig(); payConfig.setAppId(properties.getAppId()); payConfig.setMchId(properties.getMchId()); payConfig.setMchKey(properties.getMchKey()); payConfig.setNotifyUrl(properties.getPayNotifyUrl()); payConfig.setApiV3Key(properties.getMchKey()); payConfig.setPrivateKeyPath(properties.getKeyPath()); payConfig.setPrivateCertPath(properties.getCertPath()); payConfig.setTradeType("JSAPI"); payConfig.setSignType("MD5"); return payConfig; } @Bean public WxPayService wxPayService(WxPayConfig payConfig) { WxPayService wxPayService = new WxPayServiceImpl(); wxPayService.setConfig(payConfig); return wxPayService; } @Bean public WxMaService wxMaService(){ WxMaServiceImpl wxMaService = new WxMaServiceImpl(); wxMaService.setWxMaConfig(wxMaConfigMemory()); return wxMaService; } @Bean public WxMaConfig wxMaConfigMemory(){ WxMaDefaultConfigImpl wxMaConfig = new WxMaDefaultConfigImpl(); wxMaConfig.setAppid(properties.getAppId()); wxMaConfig.setSecret(properties.getAppSecret()); return wxMaConfig; } }
至此,微信支付相关的配置完了。
5.添加 WeChatPayService和实现类
service:
public interface WeChatPayService { //用户支付 WxPayReturnInfoVO weChatPay(String openId, String orderNum, Integer totalFee, String desc); //用户退款(退款支付的订单) Boolean refund(String orderNum, String refundNum, Integer totalFee, String desc); //转账到用户 void entPay(String openId, String tradeNo, Integer amount, String ip); }
serviceImpl:
@Slf4j @Service public class WeChatPayServiceImpl implements WeChatPayService { @Autowired WxPayService wxPayService; @Autowired WeChatProperties weChatProperties; private static final String REFUND_SUCCESS = "SUCCESS"; @Override public WxPayReturnInfoVO weChatPay(String openId, String orderNum, Integer totalFee, String desc) { /** * 系统内部业务逻辑 */ // 构建支付参数 final WxPayUnifiedOrderRequest wxPayUnifiedOrderRequest = WxPayUnifiedOrderRequest.newBuilder() //调起支付的人的 openId .openid(openId) //用户生成的唯一订单编号 .outTradeNo(orderNum) //订单金额 单位分 .totalFee(totalFee) //商品描述 .body(desc) //获取本地IP .spbillCreateIp(InetAddress.getLoopbackAddress().getHostAddress()) //回调的 URL 地址 .notifyUrl(weChatProperties.getPayNotifyUrl()) .build(); WxPayUnifiedOrderResult wxPayUnifiedOrderResult = null; try { wxPayUnifiedOrderResult = wxPayService.unifiedOrder(wxPayUnifiedOrderRequest); } catch (WxPayException e) { e.printStackTrace(); log.error("微信支付调起失败!"); } //组合参数构建支付 Map<String, String> paySignInfo = new HashMap<>(5); String timeStamp = createTimestamp(); String nonceStr = String.valueOf(System.currentTimeMillis()); paySignInfo.put("appId", weChatProperties.getAppId()); paySignInfo.put("nonceStr", nonceStr); paySignInfo.put("timeStamp", timeStamp); paySignInfo.put("signType", "MD5"); paySignInfo.put("package", "prepay_id=" + wxPayUnifiedOrderResult.getPrepayId()); String[] signInfo = new String[0]; String paySign = SignUtils.createSign(paySignInfo, "MD5", weChatProperties.getMchKey(), signInfo); //组合支付参数 WxPayReturnInfoVO returnPayInfoVO = new WxPayReturnInfoVO(); returnPayInfoVO.setAppId(weChatProperties.getAppId()); returnPayInfoVO.setNonceStr(nonceStr); returnPayInfoVO.setPaySign(paySign); returnPayInfoVO.setSignType("MD5"); returnPayInfoVO.setPrepayId("prepay_id=" + wxPayUnifiedOrderResult.getPrepayId()); returnPayInfoVO.setTimeStamp(timeStamp); return returnPayInfoVO; } /** * 商城订单退款 * @param orderNum * @param refundNum * @param totalFee * @param desc * @return */ @Override public Boolean refund(String orderNum,String refundNum,Integer totalFee,String desc) { //申请退款 WxPayRefundRequest refundInfo = WxPayRefundRequest.newBuilder() // 支付订单号 .outTradeNo(orderNum) // 退款订单号 .outRefundNo(refundNum) // 支付金额 单位分 .totalFee(totalFee) // 退款金额 单位分 .refundFee(totalFee) // 微信退款回调地址 .notifyUrl(weChatProperties.getRefundNotifyUrl()) .refundAccount("REFUND_SOURCE_RECHARGE_FUNDS") .build(); WxPayRefundResult wxPayRefundResult; try { wxPayRefundResult = wxPayService.refund(refundInfo); //判断退款信息是否正确 if (REFUND_SUCCESS.equals(wxPayRefundResult.getReturnCode()) && REFUND_SUCCESS.equals(wxPayRefundResult.getResultCode())) { /** * 系统内部业务逻辑 */ return true; } } catch (WxPayException e) { log.error("微信退款接口错误信息= {}", e); System.err.println(e.getMessage()); return false; } return false; } /** * 转账到个人(提现用) * @param openId * @param tradeNo * @param amount * @param ip */ @Override public void entPay(String openId, String tradeNo, Integer amount, String ip) { TransferService transferService = wxPayService.getTransferService(); //获取appId String appId = wxPayService.getConfig().getAppId(); //创建批次对象 TransferBatchesRequest transferBatchesRequest=new TransferBatchesRequest(); transferBatchesRequest.setAppid(appId); //设置批次名称 可不写 transferBatchesRequest.setBatchName("测试批次"); //设置批次备注 可不写 transferBatchesRequest.setBatchRemark("测试"); //设置该批次编号 transferBatchesRequest.setOutBatchNo(DateUtil.format(new Date(), "yyyyMMddHHmmss") + RandomUtil.randomNumbers(3)); //设置该批次总个数 transferBatchesRequest.setTotalNum(1); //设置该批次总金额 transferBatchesRequest.setTotalAmount(amount); //创建收款人请求对象 ArrayList<TransferBatchesRequest.TransferDetail> transferDetails = new ArrayList<>(); TransferBatchesRequest.TransferDetail transferDetail=new TransferBatchesRequest.TransferDetail(); //转账的编号 transferDetail.setOutDetailNo(tradeNo); //转账的金额 transferDetail.setTransferAmount(amount); //转账的注释 transferDetail.setTransferRemark("用户提现"); //以实际微信公众号那边的openid为准 transferDetail.setOpenid(openId); //把收款人对象放到批次里面 transferDetails.add(transferDetail); transferBatchesRequest.setTransferDetailList(transferDetails); TransferBatchesResult transferBatchesResult=null; try { transferBatchesResult = transferService.transferBatches(transferBatchesRequest); log.info("企业支付完成:[msg:{}]",transferBatchesResult); } catch (WxPayException e) { e.printStackTrace(); throw new CaptchaException("企业支付失败:"+e.getMessage()); } } /** * 时间 * @return 时间戳 */ private String createTimestamp() { return Long.toString(System.currentTimeMillis() / 1000); } }
6.回调接口(第二点yaml文件要配置的那两个接口)
在这个项目里实际上只用到了支付回调接口,用于判断用户是否支付成功
@ApiOperation(value = "微信的付款回调:前端不需要使用", hidden = true) @PostMapping("/pay/notify") public String parseOrderNotifyResult(@RequestBody String xmlData) { try { final WxPayOrderNotifyResult notifyResult = this.wxPayService.parseOrderNotifyResult(xmlData); if (SUCCESS.equals(notifyResult.getResultCode())) { PayCallback payCallback = new PayCallback(); payCallback.setTotalFee(notifyResult.getTotalFee()); payCallback.setTradeNo(notifyResult.getOutTradeNo()); payCallback.setOpenId(notifyResult.getOpenid()); payCallback.setTime(LocalDateTime.now()); payCallback.setStatus(0); payCallbackService.save(payCallback); log.error("订单:" + notifyResult.getOutTradeNo() + "回调成功"); } else { log.error("订单:" + notifyResult.getOutTradeNo() + "回调成功但支付失败"); } return WxPayNotifyResponse.success("回调成功!"); } catch (WxPayException e) { e.printStackTrace(); return WxPayNotifyResponse.fail("回调有误!"); } } /** * 仅支持一次性退款,多次退款需要修改逻辑 */ @ApiOperation(value = "微信的退款回调:前端不需要使用", hidden = true) @PostMapping("/refund/notify") public String refundNotify(@RequestBody String xmlData) { WxPayRefundNotifyResult wxPayRefundNotifyResult; try { wxPayRefundNotifyResult = wxPayService.parseRefundNotifyResult(xmlData); } catch (WxPayException e) { log.error("退款失败,失败信息:{}", e); return WxPayNotifyResponse.fail("退款失败"); } //判断你返回状态信息是否正确 if (SUCCESS.equals(wxPayRefundNotifyResult.getReturnCode())) { WxPayRefundNotifyResult.ReqInfo reqInfo = wxPayRefundNotifyResult.getReqInfo(); //判断退款状态 if (SUCCESS.equals(reqInfo.getRefundStatus())) { try { /** * 一、可能会重复回调,需要做防重判断 * 二、处理我们系统内部业务,做修改订单状态,释放资源等! */ reqInfo.getOutTradeNo(); return WxPayNotifyResponse.success("退款成功!"); } catch (Exception e) { e.printStackTrace(); System.out.println(e.getMessage()); } } } return WxPayNotifyResponse.fail("退款失败!"); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示