From 0c51a577db337520452022d9d6a22b720ef858d4 Mon Sep 17 00:00:00 2001 From: 无关风月 <443237572@qq.com> Date: 星期二, 31 十二月 2024 14:13:01 +0800 Subject: [PATCH] 全部代码、数据库提交 --- xinquan-modules/xinquan-order/src/main/java/com/xinquan/order/service/impl/OrderServiceImpl.java | 306 +++++++++++++++++++++++++++++++++++++++----------- 1 files changed, 239 insertions(+), 67 deletions(-) diff --git a/xinquan-modules/xinquan-order/src/main/java/com/xinquan/order/service/impl/OrderServiceImpl.java b/xinquan-modules/xinquan-order/src/main/java/com/xinquan/order/service/impl/OrderServiceImpl.java index 43da7b5..529ed9d 100644 --- a/xinquan-modules/xinquan-order/src/main/java/com/xinquan/order/service/impl/OrderServiceImpl.java +++ b/xinquan-modules/xinquan-order/src/main/java/com/xinquan/order/service/impl/OrderServiceImpl.java @@ -1,11 +1,8 @@ package com.xinquan.order.service.impl; import com.alibaba.fastjson2.JSONObject; -import com.alibaba.nacos.shaded.org.checkerframework.checker.units.qual.C; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.xinquan.common.core.constant.Constants; import com.xinquan.common.core.constant.SecurityConstants; -import com.xinquan.common.core.domain.R; import com.xinquan.common.core.enums.ChargeTypeEnum; import com.xinquan.common.core.enums.DisabledEnum; import com.xinquan.common.core.enums.PaymentStatusEnum; @@ -13,7 +10,6 @@ import com.xinquan.common.core.exception.ServiceException; import com.xinquan.common.core.utils.ip.IpUtils; import com.xinquan.common.security.service.TokenService; -import com.xinquan.common.security.utils.SecurityUtils; import com.xinquan.course.api.domain.Course; import com.xinquan.course.api.feign.RemoteCourseService; import com.xinquan.meditation.api.domain.Meditation; @@ -24,15 +20,19 @@ import com.xinquan.order.mapper.OrderMapper; import com.xinquan.order.service.OrderPaymentRecordService; import com.xinquan.order.service.OrderService; -import com.xinquan.order.utils.JuHeFuUtil; +import com.xinquan.common.core.utils.JuHeFuUtil; import com.xinquan.order.utils.OrderUtil; import com.xinquan.system.api.domain.AppUser; +import com.xinquan.system.api.domain.AppUserWalletRecord; import com.xinquan.system.api.model.LoginUser; import com.xinquan.user.api.domain.dto.AppUserDTO; import com.xinquan.user.api.feign.RemoteAppUserService; import java.math.BigDecimal; import java.math.RoundingMode; +import java.time.LocalDateTime; import java.util.List; + +import lombok.Data; import lombok.RequiredArgsConstructor; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; @@ -77,7 +77,7 @@ @Override @Transactional(rollbackFor = Exception.class) public ClientPlaceOrderVO placeOrder(Long targetId, Integer orderFrom, Long receiverId, Long orderId, - Integer balanceFlag, Integer payType,BigDecimal amount,Integer vipType) throws Exception { + Integer balanceFlag, Integer payType,BigDecimal amount,Integer vipType,Integer type) throws Exception { // 获取当前登录用户id LoginUser loginUser = tokenService.getLoginUser(); if (loginUser==null){ @@ -91,12 +91,15 @@ AppUser appUser = getAppUserById(userId); ClientPlaceOrderVO clientPlaceOrderVO = new ClientPlaceOrderVO(); Order order = new Order(); + order.setPayType(payType); + AppUser data = remoteAppUserService.getAppUserById(userId + "").getData(); + // 购买冥想音频 if (orderFrom == 1) { Meditation meditation = remoteMeditationService.getMeditationById(targetId, SecurityConstants.INNER).getData(); String meditationTitle = meditation.getMeditationTitle(); - String detailDescription = meditation.getDetailDescription(); + String detailDescription = "购买疗愈音频"; String wxOpenId = appUser.getWxOpenId(); // 创建订单 String orderNo = OrderUtil.getOrderNoForPrefix("MX"); @@ -106,41 +109,35 @@ order.setBizOrderNo(orderNo); order.setBusinessId(meditation.getId()); order.setAppUserId(userId); - order.setRealPayAmount(meditation.getGeneralPrice()); + order.setRealPayAmount(type==null?meditation.getGeneralPrice():meditation.getIosPrice()); + order.setTotalAmount(type==null?meditation.getGeneralPrice():meditation.getIosPrice()); order.setPayType(payType); order.setBuyContent("购买疗愈【"+meditationTitle+"】"); - order.setTotalAmount(meditation.getGeneralPrice()); order.setGiveUserId(receiverId); this.save(order); }else{ order = this.getById(orderId); - order.setBuyContent("购买疗愈【"+meditationTitle+"】"); + order.setBizOrderNo(orderNo); + order.setBuyContent("购买疗愈【"+meditationTitle+"】"); + this.updateById(order); } Long orderId9 = order.getId(); clientPlaceOrderVO.setOrderNo(orderNo); clientPlaceOrderVO.setId(orderId9); + // 如果冥想音频价格设定为单独收费,且需要使用余额抵扣 - if (balanceFlag.equals(1) && meditation.getChargeType().equals(ChargeTypeEnum.SEPARATE_CHARGE.getCode())) { + if (data.getBalance().compareTo(new BigDecimal("0")) > 0 && balanceFlag.equals(1) && meditation.getChargeType().equals(ChargeTypeEnum.SEPARATE_CHARGE.getCode())) { BigDecimal needPayAmount = handleBalancePayment(appUser, - meditation.getGeneralPrice(), + type==null?meditation.getGeneralPrice():meditation.getIosPrice(), orderId9); if (needPayAmount.compareTo(BigDecimal.ZERO) <= 0) { clientPlaceOrderVO.setZeroFlag(DisabledEnum.YES.getCode()); // 抵扣后金额为0 为余额支付 order.setPayType(4); - order.setRealPayAmount(meditation.getGeneralPrice()); order.setBuyContent("购买疗愈【"+meditationTitle+"】"); this.updateById(order); return clientPlaceOrderVO; - }else{ - // 余额抵扣不完 - OrderPaymentRecord one = orderPaymentRecordService.lambdaQuery().eq(OrderPaymentRecord::getPaymentType, 4) - .eq(OrderPaymentRecord::getOrderId, orderId9).one(); - if (one!=null){ - one.setPayAmount(meditation.getGeneralPrice().subtract(needPayAmount)); - orderPaymentRecordService.updateById(one); - } } if (payType==1){ // 微信+余额 @@ -152,12 +149,12 @@ order.setBuyContent("购买疗愈【"+meditationTitle+"】"); this.updateById(order); // 创建支付订单 - createPayment(payType, orderNo, needPayAmount, meditationTitle, detailDescription, + createPayment(payType, orderNo, needPayAmount, meditationTitle, "购买疗愈", wxOpenId, orderId9, clientPlaceOrderVO); } else { // 创建支付订单 - createPayment(payType, orderNo, meditation.getGeneralPrice(), meditationTitle, - detailDescription, wxOpenId, orderId9, clientPlaceOrderVO); + createPayment(payType, orderNo, type==null?order.getRealPayAmount():order.getRealPayAmount(), meditationTitle, + "购买疗愈", wxOpenId, orderId9, clientPlaceOrderVO); } } else if (orderFrom == 2){ @@ -165,7 +162,7 @@ Course course = remoteCourseService.getCourseById(targetId).getData(); String courseTitle = course.getCourseTitle(); String wxOpenId = appUser.getWxOpenId(); - String description = course.getDescription(); + String description = "购买课程"; // 创建订单 String orderNo = OrderUtil.getOrderNoForPrefix("KC"); @@ -174,42 +171,36 @@ order.setBizOrderNo(orderNo); order.setGiveUserId(receiverId); order.setBusinessId(course.getId()); + order.setAppUserId(userId); order.setBuyContent("购买课程【"+courseTitle+"】"); - order.setRealPayAmount(course.getGeneralPrice()); - order.setTotalAmount(course.getGeneralPrice()); + order.setRealPayAmount(type==null?course.getGeneralPrice():course.getIosPrice()); + order.setTotalAmount(type==null?course.getGeneralPrice():course.getIosPrice()); this.save(order); }else{ order = this.getById(orderId); order.setBuyContent("购买课程【"+courseTitle+"】"); + order.setBizOrderNo(orderNo); + this.updateById(order); } Long orderId8 = order.getId(); clientPlaceOrderVO.setOrderNo(orderNo); clientPlaceOrderVO.setId(orderId8); - if (balanceFlag.equals(1) && course.getChargeType() + if (data.getBalance().compareTo(new BigDecimal("0")) > 0 && balanceFlag.equals(1) && course.getChargeType() .equals(ChargeTypeEnum.SEPARATE_CHARGE.getCode())) { - BigDecimal needPayAmount = handleBalancePayment(appUser, course.getGeneralPrice(), + BigDecimal needPayAmount = handleBalancePayment(appUser, type==null?course.getGeneralPrice():course.getIosPrice(), orderId8); if (needPayAmount.compareTo(BigDecimal.ZERO) <= 0) { clientPlaceOrderVO.setZeroFlag(DisabledEnum.YES.getCode()); // 抵扣后金额为0 为余额支付 order.setPayType(4); - order.setRealPayAmount(course.getGeneralPrice()); order.setBuyContent("购买课程【"+courseTitle+"】"); this.updateById(order); return clientPlaceOrderVO; - }else{ - // 余额抵扣不完 - OrderPaymentRecord one = orderPaymentRecordService.lambdaQuery().eq(OrderPaymentRecord::getPaymentType, 4) - .eq(OrderPaymentRecord::getOrderId, orderId8).one(); - if (one!=null){ - one.setPayAmount(course.getGeneralPrice().subtract(needPayAmount)); - orderPaymentRecordService.updateById(one); - } } if (payType==1){ // 微信+余额 @@ -222,12 +213,12 @@ this.updateById(order); // 创建支付订单 - createPayment(payType, orderNo, needPayAmount, courseTitle, description, + createPayment(payType, orderNo, needPayAmount, courseTitle, "购买课程", wxOpenId, orderId8, clientPlaceOrderVO); } else { // 创建支付订单 - createPayment(payType, orderNo, course.getGeneralPrice(), courseTitle, - description, wxOpenId, orderId8, clientPlaceOrderVO); + createPayment(payType, orderNo, order.getRealPayAmount(), courseTitle, + "购买课程", wxOpenId, orderId8, clientPlaceOrderVO); } } else if (orderFrom == 3){ @@ -298,21 +289,49 @@ if (appUser.getBalance().compareTo(BigDecimal.ZERO) < 0) { throw new ServiceException("用户可用余额不足,请重新选择支付方案"); } - // 更新用户余额 - remoteAppUserService.updateAppUser( - AppUserDTO.builder().balance( - appUser.getBalance().subtract(generalPrice)) - .build(), SecurityConstants.INNER); + Order one = this.lambdaQuery().eq(Order::getId, orderId).one(); - OrderPaymentRecord balancePaymentRecord = new OrderPaymentRecord(); - balancePaymentRecord.setOrderId(orderId); - balancePaymentRecord.setPaymentType(PaymentTypeEnum.BALANCE_PAY.getCode()); - balancePaymentRecord.setPaymentStatus(PaymentStatusEnum.COMPLETED.getCode()); - orderPaymentRecordService.save(balancePaymentRecord); + // 如果余额大于通用价格 则实际支付价格为0 + if (appUser.getBalance().compareTo(one.getRealPayAmount())>=0){ + // 更新用户余额 +// remoteAppUserService.updateAppUser( +// AppUserDTO.builder().balance( +// appUser.getBalance().subtract(one.getRealPayAmount())) +// .build(), SecurityConstants.INNER); + OrderPaymentRecord balancePaymentRecord = new OrderPaymentRecord(); + balancePaymentRecord.setOrderId(orderId); + balancePaymentRecord.setPayAmount(one.getRealPayAmount()); + balancePaymentRecord.setPaymentType(PaymentTypeEnum.BALANCE_PAY.getCode()); + balancePaymentRecord.setPaymentStatus(PaymentStatusEnum.COMPLETED.getCode()); + List<OrderPaymentRecord> list = orderPaymentRecordService.lambdaQuery().eq(OrderPaymentRecord::getOrderId, orderId) + .eq(OrderPaymentRecord::getPaymentType, 4).list(); + if (list.isEmpty()){ + orderPaymentRecordService.save(balancePaymentRecord); + } - // 计算除去余额还需支付的金额 - return generalPrice - .subtract(appUser.getBalance()); + return new BigDecimal("0"); + }else { + // 更新用户余额 +// remoteAppUserService.updateAppUser( +// AppUserDTO.builder().balance( +// one.getRealPayAmount().subtract(appUser.getBalance())) +// .build(), SecurityConstants.INNER); + + OrderPaymentRecord balancePaymentRecord = new OrderPaymentRecord(); + balancePaymentRecord.setOrderId(orderId); + balancePaymentRecord.setPaymentType(PaymentTypeEnum.BALANCE_PAY.getCode()); + balancePaymentRecord.setPaymentStatus(PaymentStatusEnum.COMPLETED.getCode()); + balancePaymentRecord.setPayAmount(appUser.getBalance()); + List<OrderPaymentRecord> list = orderPaymentRecordService.lambdaQuery().eq(OrderPaymentRecord::getOrderId, orderId) + .eq(OrderPaymentRecord::getPaymentType, 4).list(); + if (list.isEmpty()){ + orderPaymentRecordService.save(balancePaymentRecord); + } + // 计算除去余额还需支付的金额 + return one.getRealPayAmount() + .subtract(appUser.getBalance()); + } + } /** @@ -348,8 +367,9 @@ JSONObject payInfo = JuHeFuUtil.createPayment1(orderNo, payType, needPayAmount.toString(), goodsTitle, goodsDesc, IpUtils.getIpAddr(), wxOpenId, - // "https://v7ro848ar5jx.ngrok.xiaomiqiu123.top/order/client/order/order/base/callback") - "https://xq.xqzhihui.com/api/order/client/order/order/base/callback"); + "https://xq.xqzhihui.com/api/order/client/order/order/base/callback" +// "https://xq.xqzhihui.com/api/order/client/order/order/base/callback" + ); // 第三方支付记录 OrderPaymentRecord paymentRecord = new OrderPaymentRecord(); paymentRecord.setOrderId(orderId); @@ -358,13 +378,21 @@ clientPlaceOrderVO.setPayAmt(payInfo.getString("pay_amt")); clientPlaceOrderVO.setPath(payInfo.getJSONObject("expend").getString("path")); clientPlaceOrderVO.setPayInfo(payInfo.getJSONObject("expend").getString("pay_info")); - clientPlaceOrderVO.setOrgId(payInfo.getJSONObject("expend").getString("org_id")); - clientPlaceOrderVO.setAppId(payInfo.getJSONObject("expend").getString("app_id")); + clientPlaceOrderVO.setOrgId(payInfo.getJSONObject("expend").getString("orgId")); + clientPlaceOrderVO.setAppId(payInfo.getJSONObject("expend").getString("appId")); paymentRecord.setPaymentType(PaymentTypeEnum.WECHAT_PAY.getCode()); paymentRecord.setPayOrderNo(payInfo.getString("id")); + clientPlaceOrderVO.setOrderId(payInfo.getString("id")); paymentRecord.setPaymentStatus(PaymentStatusEnum.TO_BE_PAID.getCode()); paymentRecord.setPayAmount(needPayAmount); - orderPaymentRecordService.save(paymentRecord); + OrderPaymentRecord list = orderPaymentRecordService.lambdaQuery().eq(OrderPaymentRecord::getOrderId, orderId) + .ne(OrderPaymentRecord::getPaymentType, 4).one(); + if (list==null){ + orderPaymentRecordService.save(paymentRecord); + }else{ + list.setPayOrderNo(payInfo.getString("id")); + orderPaymentRecordService.updateById(list); + } // 设置订单是否需要支付标识 clientPlaceOrderVO.setZeroFlag(DisabledEnum.NO.getCode()); }else{ @@ -372,19 +400,32 @@ JSONObject payInfo = JuHeFuUtil.createPayment(orderNo, payType, needPayAmount.toString(), goodsTitle, goodsDesc, IpUtils.getIpAddr(), wxOpenId, - // "https://v7ro848ar5jx.ngrok.xiaomiqiu123.top/order/client/order/order/base/callback") - "https://xq.xqzhihui.com/api/order/client/order/order/base/callback"); + "https://xq.xqzhihui.com/api/order/client/order/order/base/callback" +// "https://xq.xqzhihui.com/api/order/client/order/order/base/callback" + + ); // 第三方支付记录 OrderPaymentRecord paymentRecord = new OrderPaymentRecord(); paymentRecord.setOrderId(orderId); paymentRecord.setPayAmount(needPayAmount); Order byId = this.getById(orderId); - byId.setPayOrderNo(payInfo.getString("id")); this.updateById(byId); - clientPlaceOrderVO.setQrcodeUrl(payInfo.getString("qrcode_url")); + JSONObject expend = payInfo.getJSONObject("expend"); + String string = expend.getString("qrcode_url"); + clientPlaceOrderVO.setQrcodeUrl(string); paymentRecord.setPaymentType(PaymentTypeEnum.ALI_PAY.getCode()); paymentRecord.setPaymentStatus(PaymentStatusEnum.TO_BE_PAID.getCode()); - orderPaymentRecordService.save(paymentRecord); + paymentRecord.setPayOrderNo(payInfo.getString("id")); + OrderPaymentRecord list = orderPaymentRecordService.lambdaQuery().eq(OrderPaymentRecord::getOrderId, orderId) + .ne(OrderPaymentRecord::getPaymentType, 4).one(); + if (list==null){ + orderPaymentRecordService.save(paymentRecord); + }else{ + list.setPayOrderNo(payInfo.getString("id")); + orderPaymentRecordService.updateById(list); + } + clientPlaceOrderVO.setPayAmt(payInfo.getString("pay_amt")); + clientPlaceOrderVO.setOrderId(payInfo.getString("id")); // 设置订单是否需要支付标识 clientPlaceOrderVO.setZeroFlag(DisabledEnum.NO.getCode()); } @@ -529,6 +570,7 @@ order.setBizOrderNo(orderNo); order.setAppUserId(userId); order.setTotalAmount(amount); + order.setRealPayAmount(amount); String vipType1 =""; switch (vipType){ case 1: @@ -564,6 +606,8 @@ order.setBizOrderNo(orderNo); order.setAppUserId(userId); order.setTotalAmount(amount); + order.setRealPayAmount(amount); + order.setBuyContent("充值"); this.save(order); Long orderId = order.getId(); @@ -581,15 +625,143 @@ return order.getId()+""; } + + @Override public String refund(Long uid) throws Exception { Order byId = this.getById(uid); + OrderPaymentRecord two = orderPaymentRecordService.lambdaQuery() + .eq(OrderPaymentRecord::getPaymentType,4) + .eq(OrderPaymentRecord::getOrderId, uid) + .one(); + if (byId.getPayType() == 4){ + // 纯余额支付 直接退还余额 + // 回退余额记录 + AppUserWalletRecord appUserWalletRecord = new AppUserWalletRecord(); + appUserWalletRecord.setAppUserId(byId.getAppUserId()); + appUserWalletRecord.setChangeType(1); + appUserWalletRecord.setReason("后台退款"); + appUserWalletRecord.setOrderId(byId.getId()); + appUserWalletRecord.setAmount(two.getPayAmount()); + remoteAppUserService.addBalanceRecord(appUserWalletRecord); + remoteAppUserService.addBalance(byId.getAppUserId(),two.getPayAmount()); + byId.setRefundStatus(3); + byId.setRefundRemark("后台退款"); + byId.setRefundTime(LocalDateTime.now()); + byId.setPaymentStatus(3); + byId.setCancelTime(LocalDateTime.now()); + two.setPaymentStatus(3); + + this.updateById(byId); + orderPaymentRecordService.updateById(two); + switch (byId.getOrderFrom()){ + case 1: + // 冥想订单 删除 + break; + case 2: + // 删除用户与课程的关系表 + remoteAppUserService.deleteAppUserCourse(byId.getBusinessId(),byId.getAppUserId()); + break; + case 3: + // 会员订单 将用户会员到期时间回退 + if (byId.getBuyContent().contains("月")){ + remoteAppUserService.subVipExpireTime(byId.getAppUserId(),1); + }else if (byId.getBuyContent().contains("季")){ + remoteAppUserService.subVipExpireTime(byId.getAppUserId(),2); + }else if (byId.getBuyContent().contains("年")){ + remoteAppUserService.subVipExpireTime(byId.getAppUserId(),3); + } + break; + } + return "success"; + }else if (byId.getPayType() == 5 || byId.getPayType() == 6 || byId.getPayType() == 7){ + OrderPaymentRecord one = orderPaymentRecordService.lambdaQuery() + .eq(OrderPaymentRecord::getOrderId, uid) + .ne(OrderPaymentRecord::getPaymentType, 4) + .ne(OrderPaymentRecord::getPaymentType, 3) + .one(); + JSONObject refund = JuHeFuUtil.refund(one.getPayOrderNo(), byId.getBizOrderNo()); + if (refund.getString("error_msg")!=null){ + return refund.getString("error_msg"); + } + // 回退余额记录 + AppUserWalletRecord appUserWalletRecord = new AppUserWalletRecord(); + appUserWalletRecord.setAppUserId(byId.getAppUserId()); + appUserWalletRecord.setChangeType(1); + appUserWalletRecord.setReason("后台退款"); + appUserWalletRecord.setOrderId(byId.getId()); + appUserWalletRecord.setAmount(two.getPayAmount()); + remoteAppUserService.addBalanceRecord(appUserWalletRecord); + remoteAppUserService.addBalance(byId.getAppUserId(),two.getPayAmount()); + byId.setRefundStatus(3); + byId.setRefundRemark("后台退款"); + byId.setRefundTime(LocalDateTime.now()); + byId.setPaymentStatus(3); + byId.setCancelTime(LocalDateTime.now()); + two.setPaymentStatus(3); + this.updateById(byId); + orderPaymentRecordService.updateById(two); + + switch (byId.getOrderFrom()){ + case 1: + // 冥想订单 删除 + break; + case 2: + // 删除用户与课程的关系表 + remoteAppUserService.deleteAppUserCourse(byId.getBusinessId(),byId.getAppUserId()); + break; + case 3: + // 会员订单 将用户会员到期时间回退 + if (byId.getBuyContent().contains("月")){ + remoteAppUserService.subVipExpireTime(byId.getAppUserId(),1); + }else if (byId.getBuyContent().contains("季")){ + remoteAppUserService.subVipExpireTime(byId.getAppUserId(),2); + }else if (byId.getBuyContent().contains("年")){ + remoteAppUserService.subVipExpireTime(byId.getAppUserId(),3); + } + break; + } + return "success"; + } OrderPaymentRecord one = orderPaymentRecordService.lambdaQuery() .eq(OrderPaymentRecord::getOrderId, uid) + .ne(OrderPaymentRecord::getPaymentType, 4) .ne(OrderPaymentRecord::getPaymentType, 3) - .ne(OrderPaymentRecord::getPaymentType, 4).one(); + .one(); + JSONObject refund = JuHeFuUtil.refund(one.getPayOrderNo(), byId.getBizOrderNo()); + if (refund.getString("error_msg")!=null){ + return refund.getString("error_msg"); + } + one.setPaymentStatus(3); - JuHeFuUtil.refund(one.getPayOrderNo(),byId.getBizOrderNo()); - return null; + orderPaymentRecordService.updateById(one); + String string = refund.getString("payment_id"); + if (string!=null){ + byId.setRefundCode(string); + } + byId.setRefundTime(LocalDateTime.now()); + byId.setRefundStatus(3); + byId.setRefundRemark("后台退款"); + this.updateById(byId); + switch (byId.getOrderFrom()){ + case 1: + // 冥想订单 删除 + break; + case 2: + // 删除用户与课程的关系表 + remoteAppUserService.deleteAppUserCourse(byId.getBusinessId(),byId.getAppUserId()); + break; + case 3: + // 会员订单 将用户会员到期时间回退 + if (byId.getBuyContent().contains("月")){ + remoteAppUserService.subVipExpireTime(byId.getAppUserId(),1); + }else if (byId.getBuyContent().contains("季")){ + remoteAppUserService.subVipExpireTime(byId.getAppUserId(),2); + }else if (byId.getBuyContent().contains("年")){ + remoteAppUserService.subVipExpireTime(byId.getAppUserId(),3); + } + break; + } + return "success"; } } -- Gitblit v1.7.1