赞
踩
NATIVE
支付模式 (V2版本)】开发环境
1. Spring Boot Version: 2.1.17.RELEASE
2. jdk 8
准备工作
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-pay-spring-boot-starter</artifactId>
<version>${wx.pay.version}</version>
</dependency>
1.appId,mchId,mchKey [这些都需要去微信平台获取]
2.配置一个可以直接外网访问的url 用作微信支付后微信平台回调我们的接口
支付流程图
流程
1.商户后台系统根据用户选购的商品生成订单。 2.用户确认支付后调用微信支付【统一下单API】生成预支付交易; 3.微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。 4.商户后台系统根据返回的code_url生成二维码。 5.用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。 6.微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。 7.用户在微信客户端输入密码,确认支付后,微信客户端提交授权。 8.微信支付系统根据用户授权完成支付交易。 9.微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。 10.微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。 11.未收到支付通知的情况,商户后台系统调用【查询订单API】。 12.商户确认订单已支付后给用户发货。
替换自己的appId,mchId,mchKey,回调地址一定要可以直接外网访问通,不能有查询参数限制,不然微信无法通知自己的业务系统
)wx:
pay:
appId: wx538891b3add78wew
mchId: 1644361212
mchKey: 5b2b6687cfdde9cb5e2d0b36d95f6548
subAppId:
subMchId:
keyPath:
notifyUrl: https://payapi.wx.com/wx/order/notify
/** * @author:kyrie * @date:2023/5/16 13:21 * @Description: 微信支付配置属性 **/ @Data @Component @ConfigurationProperties(prefix = "wx.pay") public class WxPayProperties { /** * 设置微信公众号或者小程序等的appid. */ private String appId; /** * 微信支付商户号. */ private String mchId; /** * 微信支付商户密钥. */ private String mchKey; /** * 服务商模式下的子商户公众账号ID,普通模式请不要配置,请在配置文件中将对应项删除. */ private String subAppId; /** * 服务商模式下的子商户号,普通模式请不要配置,最好是请在配置文件中将对应项删除. */ private String subMchId; /** * apiclient_cert.p12文件的绝对路径,或者如果放在项目中,请以classpath:开头指定. */ private String keyPath; private String notifyUrl; }
/** * @author: kyrie * @date:2023/5/11 13:02 * @Description: 微信支付配置类 **/ @Configuration public class WxPayConfiguration { @Autowired private WxPayProperties payProperties; @Bean public WxPayService wxPayService() { WxPayServiceImpl wxPayService = new WxPayServiceImpl(); WxPayConfig payConfig = new WxPayConfig(); payConfig.setAppId(payProperties.getAppId()); payConfig.setMchId(payProperties.getMchId()); payConfig.setMchKey(payProperties.getMchKey()); payConfig.setKeyPath(payProperties.getKeyPath()); payConfig.setNotifyUrl(payProperties.getNotifyUrl()); wxPayService.setConfig(payConfig); return wxPayService; } }
--------------------controller-------------------- /** * 创建订单【微信】 * * @param orderRequest 请求对象 * @return result */ @ApiOperation("创建订单【微信】") @Log(title = "创建订单【微信】", businessType = BusinessTypeEnum.INSERT) @PostMapping("/native") @ApiTimeStatistics public AjaxResult createNativeOrder(@RequestBody @Validated OrderNativeCreatRequest orderRequest) { OrderNativeCreatedResponse response = orderService.handlerNativeCreateOrder(orderRequest); return AjaxResult.success(response); } --------------------dto-------------------- /** * @author:kyrie * @date:2023/5/17 16:25 * @Description: 请求对象 **/ @Data public class OrderNativeCreatRequest implements Serializable { private static final long serialVersionUID = -7961794957591750053L; @NotBlank(message = "订单号不能为空") private String outTradeNo; } --------------------serviceImpl-------------------- /** * 微信创建订单 * * @param orderRequest 请求对象 * @return 返回对象 */ @Transactional(rollbackFor = Exception.class) @Override public OrderNativeCreatedResponse handlerNativeCreateOrder(OrderNativeCreatRequest orderRequest) { // 我这的outTradeNo是我前置的服务创建的,就是先创建了自己业务的一个订单对象,这个no要确保唯一,我之前使用的是MongoDB的Id生成策略生成的,这里可以自己自定义 OrderNativeCreatedResponse result; String outTradeNo = orderRequest.getOutTradeNo(); Order order = null; // 单服务的话这里可以直接使用RT自旋锁解决并发问题(ReentrantLock) // 没必要采用这种Redisson的分布式lock locker.lock(outTradeNo, Constants.REDIS_TIMEOUT); try { // 前置校验查询订单是否已经存在 就是一个getById() order = selectOrderByOrderNo(outTradeNo); Assert.notNull(order, "创建订单异常:订单不存在"); // 构建下单请求对象 这里我使用的是wxjava工具包 直接封装好了这个对象 sign和时间戳无需自己生成 WxPayUnifiedOrderRequest wxPayUnifiedOrderRequest = new WxPayUnifiedOrderRequest(); wxPayUnifiedOrderRequest.setBody(order.getProductDescription() + "_" + order.getEnterpriseName()); wxPayUnifiedOrderRequest.setOutTradeNo(outTradeNo); // 金额需要转成分 微信是认分的所以不能有小数点 直接简单点*100 wxPayUnifiedOrderRequest.setTotalFee(Integer.valueOf(MoneyUtil.getMoney(order.getOrderMoney().toString()))); // 获取真实的ip 直接找个工具类就行 wxPayUnifiedOrderRequest.setSpbillCreateIp(IpUtils.getRealIP(ServletUtils.getRequest())); wxPayUnifiedOrderRequest.setTradeType(com.github.binarywang.wxpay.constant.WxPayConstants.TradeType.NATIVE); // 自己商品的id 这里我是瞎写的 可以自己根据自己业务修改 wxPayUnifiedOrderRequest.setProductId("12235413214070356458058"); // 这个我一开始没写 按照wxjava的配置 没有设置好像会去加载config的默认配置【我觉得没必要写】 wxPayUnifiedOrderRequest.setNotifyUrl(notifyUrl); // 调用封装的wxPayService的方法即可【v2版本的都是xml交互,v3是json交互 ,所以不一样,这里wxjava已经写好了现成的bean转xml,直接用即可】 WxPayUnifiedOrderResult wxOrderResponse = wxPayService.unifiedOrder(wxPayUnifiedOrderRequest); // 可能是啥情况error了直接订单失败 抛异常 if (ObjectUtil.isNull(wxOrderResponse)) { order.setOrderStatus(WxPayStatusEnum.PAY_FAIL.getPayStatusCode()); updateById(order); throw WxPayException.newBuilder().returnMsg("创建订单异常:无返回数据").build(); } else { // 预支付id【可以不存,因为好多微信支付接口都可以用你自己的那个no去调用,就相当于你的orderNo和微信的内部的一个id是对应的,这样双方就可以通了】 order.setPrePayId(wxOrderResponse.getPrepayId()); order.setOrderStatus(WxPayStatusEnum.PAY_ING.getPayStatusCode()); updateById(order); } // 返回前端展示的数据而已 【没啥】 result = new OrderNativeCreatedResponse(); result.setPrepayId(wxOrderResponse.getPrepayId()); result.setCodeUrl(wxOrderResponse.getCodeURL()); // hutool 工具类生成base64的字符串 String qrCode = QrCodeUtil.generateAsBase64(wxOrderResponse.getCodeURL(), QrConfig.create(), "jpg"); result.setQrCode(qrCode); result.setReturnUrl(order.getReturnUrl()); result.setBasiUrl(order.getBasiUrl()); result.setOrderId(order.getOrderId()); result.setOrderNo(order.getOrderNo()); result.setProductDescription(order.getProductDescription()); result.setEnterpriseName(order.getEnterpriseName()); } catch (WxPayException e) { // 异常log log.error("OrderServiceImpl--handlerNativeCreateOrder===error---->{}", e.getMessage()); if (ObjectUtil.isNotNull(order)) { order.setOrderStatus(WxPayStatusEnum.PAY_FAIL.getPayStatusCode()); updateById(order); } throw new CustomException("创建订单异常:" + e.getReturnMsg()); } finally { // 释放锁 locker.unlock(outTradeNo); } // return return result; }
--------------------controller-------------------- /** * 根据订单no查询订单号 * * @param orderQueryRequest 请求对象 * @return result */ @ApiOperation("查询订单") @Log(title = "查询订单", businessType = BusinessTypeEnum.UPDATE) @PostMapping("/query") @ApiTimeStatistics public AjaxResult queryOrder(@RequestBody @Validated OrderQueryRequest orderQueryRequest) { OrderQueryResponse response = orderService.queryOrder(orderQueryRequest); return AjaxResult.success(response); } --------------------dto-------------------- /** * @author:kyrie * @date:2023/5/17 16:25 * @Description: 请求对象 **/ @Data public class OrderQueryRequest implements Serializable { private static final long serialVersionUID = 2516981824084563768L; @NotBlank(message = "订单号不能为空") private String outTradeNo; } --------------------serviceImpl-------------------- /** * 查询订单 * * @param orderQueryRequest 订单号 * @return 返回对象 */ @Override public OrderQueryResponse queryOrder(OrderQueryRequest orderQueryRequest) { OrderQueryResponse response = new OrderQueryResponse(); String outTradeNo = orderQueryRequest.getOutTradeNo(); locker.lock(outTradeNo, Constants.REDIS_TIMEOUT); try { // 再次去查一下order的状态 Order order = selectOrderByOrderNo(outTradeNo); response.setOrderId(order.getOrderId()); response.setOrderNo(order.getOrderNo()); response.setReturnUrl(order.getReturnUrl()); response.setBasiUrl(order.getBasiUrl()); response.setValidityDateNow(order.getValidityDateNow()); response.setValidityDateAfter(order.getValidityDateAfter()); response.setProductDescription(order.getProductDescription()); response.setMoney(order.getOrderMoney()); // 自己业务系统中已经成功或者事变 直接返回就行 没必要调微信接口 if (WxPayStatusEnum.PAY_SUCCESS.getPayStatusCode().equals(order.getOrderStatus()) || WxPayStatusEnum.PAY_FAIL.getPayStatusCode().equals(order.getOrderStatus()) || WxPayStatusEnum.CLOSED.getPayStatusCode().equals(order.getOrderStatus())) { response.setOrderStatus(order.getOrderStatus()); } else { // 这就是拿那个订单号去调微信的查询接口 WxPayOrderQueryResult result = wxPayService.queryOrder(null, outTradeNo); if (ObjectUtil.isNotNull(result)) { // 返回值 String tradeState = result.getTradeState(); String status = null; // 判断 if (StrUtil.isNotBlank(tradeState)) { if (WxPayConstants.NOT_PAY.equals(tradeState)) { // status = WxPayStatusEnum.TO_BE_PAID.getPayStatusCode(); } else if (WxPayConstants.PAY_SUCCESS.equals(tradeState)) { status = WxPayStatusEnum.PAY_SUCCESS.getPayStatusCode(); } else if (WxPayConstants.PAY_CLOSED.equals(tradeState)) { status = WxPayStatusEnum.CLOSED.getPayStatusCode(); } else if (WxPayConstants.PAY_REFUND.equals(tradeState)) { // status = WxPayStatusEnum.PAY_REFUND.getPayStatusCode(); } else if (WxPayConstants.PAY_REVOKED.equals(tradeState)) { // status = WxPayStatusEnum.CANCEL.getPayStatusCode(); } else if (WxPayConstants.PAY_ING.equals(tradeState)) { // status = WxPayStatusEnum.PAY_ING.getPayStatusCode(); } else if (WxPayConstants.PAY_ERROR.equals(tradeState)) { // status = WxPayStatusEnum.PAY_FAIL.getPayStatusCode(); } order.setOrderStatus(status); response.setOrderStatusDesc(result.getTradeStateDesc()); } if (StrUtil.isNotBlank(status)) { updateById(order); response.setOrderStatus(order.getOrderStatus()); // 发送通知给别的系统 sendNotifyToOtherSystem(order); } } else { removeById(order.getOrderId()); } } } catch (WxPayException e) { log.error("OrderServiceImpl--[queryOrder]===error---->{}", e.getMessage()); throw new CustomException("查询订单异常:" + e.getErrCodeDes()); } finally { locker.unlock(outTradeNo); } return response; }
--------------------controller-------------------- /** * 根据订单no关闭订单 * * @param closeRequest 请求对象 * @return result */ @ApiOperation("关闭订单") @Log(title = "关闭订单", businessType = BusinessTypeEnum.UPDATE) @PostMapping("/close") @ApiTimeStatistics public AjaxResult closeOrder(@RequestBody @Validated OrderCloseRequest closeRequest) { WxPayOrderCloseResult result = orderService.closeOrder(closeRequest); return AjaxResult.success(result); } --------------------dto-------------------- /** * @author:kyrie * @date:2023/5/17 16:25 * @Description: 请求对象 **/ @Data public class OrderCloseRequest implements Serializable { private static final long serialVersionUID = 2516981824084563768L; @NotBlank(message = "订单号不能为空") private String outTradeNo; } --------------------serviceImpl-------------------- /** * 关闭订单 * * @param closeRequest 关闭对象 * @return closeRequest */ @Override public WxPayOrderCloseResult closeOrder(OrderCloseRequest closeRequest) { WxPayOrderCloseResult result; String outTradeNo = closeRequest.getOutTradeNo(); locker.lock(outTradeNo, Constants.REDIS_TIMEOUT); try { // 根据订单号查询订单 Order order = selectOrderByOrderNo(outTradeNo); if (WxPayStatusEnum.PAY_SUCCESS.getPayStatusCode().equals(order.getOrderStatus())) { throw WxPayException.newBuilder().returnMsg(outTradeNo + "订单已支付成功,不允许关闭!").build(); } result = wxPayService.closeOrder(outTradeNo); if (ObjectUtil.isNotNull(result)) { order.setOrderStatus(WxPayStatusEnum.CLOSED.getPayStatusCode()); updateById(order); } } catch (WxPayException e) { log.error("OrderServiceImpl--[{}]===error---->{}", "closeOrder", e.getMessage()); throw new CustomException("关闭订单异常:" + e.getReturnMsg()); } finally { locker.unlock(outTradeNo); } return result; }
支付后微信回通过回调地址调用我们自己的业务,需要给微信返回一个成功code和message 不然它会一直尝试调用 【好像会重试24h】
)/** * 微信支付回调处理 * * @param paramsMap 参数 * @return 返回结果 */ @PostMapping(value = "/notify", produces = "application/xml") @ApiTimeStatistics public Map<String, String> wxNotify(@RequestBody Map<String, String> paramsMap) { orderService.wxNotify(paramsMap); // 返回信息给微信 Map<String, String> resultMap = new HashMap<>(2); resultMap.put("return_code", "SUCCESS"); resultMap.put("return_msg", "OK"); return resultMap; } --------------------serviceImpl-------------------- /** * 微信支付后回调 * * @param paramsMap 参数 */ @Override public void wxNotify(Map<String, String> paramsMap) { log.info("【微信支付回调】参数--->{}", paramsMap); // 获取订单号和支付金额 String orderNo = paramsMap.get("out_trade_no"); long totalFee = Long.parseLong(paramsMap.get("total_fee")); locker.lock(orderNo, Constants.REDIS_TIMEOUT); try { // 根据订单号查询订单 Order order = selectOrderByOrderNo(orderNo); // 判断支付金额是否一致 if (Long.parseLong(MoneyUtil.getMoney(order.getOrderMoney().toString())) != totalFee) { log.error("【微信支付回调】支付金额不一致!"); throw new CustomException("支付金额不一致"); } // 判断订单状态如果已经支付过了,无需支付了 if (WxPayStatusEnum.PAY_SUCCESS.getPayStatusCode().equals(order.getOrderStatus())) { log.error("【微信支付回调】订单已经支付,无需重复支付!"); throw new CustomException("订单已经支付,无需重复支付"); } // 判断订单状态 if (WxPayStatusEnum.PAY_FAIL.getPayStatusCode().equals(order.getOrderStatus())) { log.error("【微信支付回调】订单已关闭,无发支付!"); throw new CustomException("订单已关闭,无发支付"); } order.setOrderStatus(WxPayStatusEnum.PAY_SUCCESS.getPayStatusCode()); order.setPayTime(DateUtil.date()); updateById(order); log.info("【微信支付回调】订单状态更新成功"); sendNotifyToOtherSystem(order); } catch (Exception e) { log.error("【微信支付回调】订单状态更新失败-->{}", e.getMessage()); } finally { locker.unlock(orderNo); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。