new-dev
袁野 1 year ago
parent fd201b86d7
commit 198e338640
  1. 2
      hai-bweb/src/main/java/com/bweb/controller/HighApproveController.java
  2. 2
      hai-bweb/src/main/java/com/bweb/controller/HighTestController.java
  3. 11
      hai-cweb/src/main/java/com/cweb/controller/HighCouponController.java
  4. 75
      hai-order/src/main/java/com/web/controller/OrderPayController.java
  5. 21
      hai-order/src/main/resources/dev/logback.xml
  6. 20
      hai-order/src/main/resources/prod/logback.xml
  7. 2
      hai-service/src/main/java/com/hai/config/CmsContentConfig.java
  8. 2
      hai-service/src/main/java/com/hai/config/CommonSysConfig.java
  9. 15
      hai-service/src/main/java/com/hai/config/WxOrderConfig.java
  10. 2
      hai-service/src/main/java/com/hai/entity/ApiMchProduct.java
  11. 1
      hai-service/src/main/java/com/hai/enum_type/CmsContentDataEnum.java
  12. 11
      hai-service/src/main/java/com/hai/model/UserModel.java
  13. 68
      hai-service/src/main/java/com/hai/openApi/config/BlxConfig.java
  14. 2
      hai-service/src/main/java/com/hai/openApi/service/ApiOrderCreateHandleService.java
  15. 14
      hai-service/src/main/java/com/hai/openApi/service/impl/ApiOrderCreateHandleServiceImpl.java
  16. 21
      hai-service/src/main/java/com/hai/openApi/service/impl/ApiOrderServiceImpl.java
  17. 2
      hai-service/src/main/java/com/hai/order/service/impl/OrderCreateHandleServiceImpl.java
  18. 9
      hai-service/src/main/java/com/hai/order/service/impl/OrderPaySuccessServiceImpl.java
  19. 1
      hai-service/src/main/java/com/hai/order/type/OrderProductType.java
  20. 46
      hai-service/src/main/java/com/hai/order/type/PlatformType.java
  21. 2
      hai-service/src/main/java/com/hai/service/HighUserCardService.java
  22. 51
      hai-service/src/main/java/com/hai/service/impl/HighCouponCodeServiceImpl.java
  23. 27
      hai-service/src/main/java/com/hai/service/impl/HighUserCardServiceImpl.java
  24. 4
      hai-service/src/main/java/com/hai/service/impl/HighUserServiceImpl.java
  25. 1
      hai-service/src/main/resources/dev/commonConfig.properties
  26. 3
      hai-service/src/main/resources/prod-9401/commonConfig.properties
  27. 3
      hai-service/src/main/resources/prod/commonConfig.properties
  28. 20
      hai-user/src/main/resources/dev/logback.xml
  29. 20
      hai-user/src/main/resources/prod/logback.xml
  30. 117
      v1/src/main/java/com/v1/controller/business/BlxCouponController.java
  31. 11
      v1/src/main/java/com/v1/controller/orderController.java

@ -172,7 +172,7 @@ public class HighApproveController {
Map<String, Object> map = new HashMap<>();
map.put("approveSerialNo", approveSerialNo);
map.put("objectType", objectType);
if (objectType == 4) {
if (objectType != null && objectType == 4) {
map.put("companyId", userInfoModel.getBsCompany().getId());
}
map.put("objectId", objectId);

File diff suppressed because one or more lines are too long

@ -77,7 +77,7 @@ public class HighCouponController {
@RequestMapping(value = "/getCouponList", method = RequestMethod.GET)
@ResponseBody
@ApiOperation(value = "卡卷列表")
public ResponseData getCouponList(@RequestParam(name = "regionId", required = true) String regionId,
public ResponseData getCouponList(@RequestParam(name = "companyId", required = true) String companyId,
@RequestParam(name = "merchantId", required = false) Long merchantId,
@RequestParam(name = "couponName", required = false) String couponName,
@RequestParam(name = "couponType", required = false) Integer couponType,
@ -88,12 +88,8 @@ public class HighCouponController {
@RequestParam(name = "pageSize", required = true) Integer pageSize) {
try {
SecRegion region = commonService.getParentByRegion(Long.parseLong(regionId));
if (region != null) {
BsCompany bsCompany = bsCompanyService.selectCompanyByRegion(region.getRegionId().toString());
if (bsCompany != null) {
Map<String, Object> map = new HashMap<>();
map.put("companyId", bsCompany.getId());
map.put("companyId", companyId);
map.put("merchantId", merchantId);
map.put("couponName", couponName);
map.put("couponType", couponType);
@ -104,9 +100,6 @@ public class HighCouponController {
map.put("status", 2);
PageHelper.startPage(pageNum, pageSize);
return ResponseMsgUtil.success(new PageInfo<>(highCouponService.getCouponList(map)));
}
}
return ResponseMsgUtil.success(new PageInfo<>());
} catch (Exception e) {
log.error("HighCouponController -> getCouponList() error!", e);

@ -26,6 +26,7 @@ import com.hai.order.service.OrderService;
import com.hai.order.service.OrderPayService;
import com.hai.order.type.OrderProductType;
import com.hai.order.type.OrderStatus;
import com.hai.order.type.PlatformType;
import com.hai.service.*;
import com.web.config.SysConst;
import io.swagger.annotations.Api;
@ -50,6 +51,7 @@ import java.util.concurrent.ThreadLocalRandom;
/**
* 订单支付
*
* @className: OrderPayController
* @author: HuRui
* @date: 2022/8/30
@ -103,7 +105,7 @@ public class OrderPayController {
@Resource
private SecConfigService secConfigService;
@RequestMapping(value="/wechatPay",method = RequestMethod.POST)
@RequestMapping(value = "/wechatPay", method = RequestMethod.POST)
@ResponseBody
@ApiOperation(value = "微信支付")
public ResponseData wechatPay(@RequestBody JSONObject body) {
@ -122,7 +124,7 @@ public class OrderPayController {
// 查询订单
HighOrder order = orderService.getOrderDetailByNo(body.getString("orderNo"));
if(!order.getOrderStatus().equals(OrderStatus.STATUS1.getNumber())) {
if (!order.getOrderStatus().equals(OrderStatus.STATUS1.getNumber())) {
log.error("wxPay ERROR", "订单不处于待支付状态");
throw ErrorHelp.genException(SysCode.System, ErrorCode.ORDER_NO_STAY_PAY, "");
}
@ -138,11 +140,19 @@ public class OrderPayController {
WeChatPayReqInfo weChatPayReqInfo = new WeChatPayReqInfo();
weChatPayReqInfo.setAppid(SysConst.getSysConfig().getWxPayMchAppId()); // 公众号id
weChatPayReqInfo.setMch_id(SysConst.getSysConfig().getWxPayMchId()); // 商户号
if (PlatformType.TYPE2.getNumber().equals(body.getInteger("platformType"))) {
if (openIdType != null && openIdType.equals(2)) {
weChatPayReqInfo.setSub_appid(WxOrderConfig.APPID_QYX_MP); // 商户号公众号
} else {
weChatPayReqInfo.setSub_appid(WxOrderConfig.APPID_QYX_MINI); // 嗨森逛小程序
}
} else {
if (openIdType != null && openIdType.equals(2)) {
weChatPayReqInfo.setSub_appid(WxOrderConfig.APPID_HSG_MP); // 商户号公众号
} else {
weChatPayReqInfo.setSub_appid(WxOrderConfig.APPID_HSG_MINI); // 嗨森逛小程序
}
}
// 订单产品类型
OrderProductType productType = OrderProductType.getDataByType(order.getProductType());
@ -150,6 +160,10 @@ public class OrderPayController {
orderTitle = productType.getName();
}
if (PlatformType.TYPE2.getNumber().equals(body.getInteger("platformType"))) {
weChatPayReqInfo.setSub_mch_id(WxOrderConfig.MCH_ID_1648729281);
} else {
if (order.getProductType().equals(OrderProductType.PRODUCT_TYPE1.getNumber())) {
weChatPayReqInfo.setSub_mch_id(WxOrderConfig.MCH_ID_1614670195);
@ -168,7 +182,7 @@ public class OrderPayController {
// GZ_SINOPEC_QUOTA 单日限购数量
SecConfig gzSinopecQuota = secConfigService.findByCodeType("GZ_SINOPEC_QUOTA");
if (gzSinopecQuota != null
&& couponService.userBuySourceLimitNumber(order.getMemId(),4) >= Integer.parseInt(gzSinopecQuota.getCodeValue())) {
&& couponService.userBuySourceLimitNumber(order.getMemId(), 4) >= Integer.parseInt(gzSinopecQuota.getCodeValue())) {
throw ErrorHelp.genException(SysCode.System, ErrorCode.COMMON_ERROR, "暂时无法支付,已达单日限购数量");
}
if (order.getMemDiscountId() != null) {
@ -179,6 +193,17 @@ public class OrderPayController {
} else {
weChatPayReqInfo.setSub_mch_id(WxOrderConfig.MCH_ID_1614670195);
}
} else if (order.getProductType().equals(OrderProductType.PRODUCT_TYPE5.getNumber())) {
HighCoupon coupon = couponService.getCouponById(order.getHighChildOrderList().get(0).getGoodsId());
if (coupon.getCouponSource() != null && coupon.getCouponSource().equals(4)) {
if (order.getMemDiscountId() != null) {
weChatPayReqInfo.setSub_mch_id(WxOrderConfig.MCH_ID_1614670195);
} else {
weChatPayReqInfo.setSub_mch_id(WxOrderConfig.MCH_ID_1634835264);
}
} else {
weChatPayReqInfo.setSub_mch_id(WxOrderConfig.MCH_ID_1614670195);
}
} else if (order.getProductType().equals(OrderProductType.PRODUCT_TYPE6.getNumber())) {
// 查询油站
@ -194,12 +219,12 @@ public class OrderPayController {
if (store.getSourceType().equals(MerchantStoreSourceType.type1.getNumber())) {
// 查询支付配置
HighGasChannelConfig gasChannelConfig = gasChannelConfigService.getConfig(GasChannel.type1, GasChannelPayPlatformType.type1 );
HighGasChannelConfig gasChannelConfig = gasChannelConfigService.getConfig(GasChannel.type1, GasChannelPayPlatformType.type1);
if (gasChannelConfig == null) {
throw ErrorHelp.genException(SysCode.System, ErrorCode.COMMON_ERROR, "未配置支付参数,请稍后重试!");
}
weChatPayReqInfo.setSub_mch_id(gasChannelConfig.getPayPlatformMerNo());
profitSharing = gasChannelConfig.getProfitSharingStatus().equals(true)?"Y":"N";
profitSharing = gasChannelConfig.getProfitSharingStatus().equals(true) ? "Y" : "N";
if (store.getSourceType().equals(1)) {
// 预存类型 0:非预存 1:预存门店
@ -209,7 +234,7 @@ public class OrderPayController {
if (merTripartitePlatform != null) {
weChatPayReqInfo.setSub_mch_id(merTripartitePlatform.getPlatformMerNumber());
// 是否分账
profitSharing = merTripartitePlatform.getProfitSharingStatus().equals(true)?"Y":"N";
profitSharing = merTripartitePlatform.getProfitSharingStatus().equals(true) ? "Y" : "N";
}
}
}
@ -217,40 +242,40 @@ public class OrderPayController {
} else if (store.getSourceType().equals(MerchantStoreSourceType.type2.getNumber())) {
// 查询支付配置
HighGasChannelConfig gasChannelConfig = gasChannelConfigService.getConfig(GasChannel.type2,GasChannelPayPlatformType.type1 );
HighGasChannelConfig gasChannelConfig = gasChannelConfigService.getConfig(GasChannel.type2, GasChannelPayPlatformType.type1);
if (gasChannelConfig == null) {
throw ErrorHelp.genException(SysCode.System, ErrorCode.COMMON_ERROR, "未配置支付参数,请稍后重试!");
}
weChatPayReqInfo.setSub_mch_id(gasChannelConfig.getPayPlatformMerNo());
profitSharing = gasChannelConfig.getProfitSharingStatus().equals(true)?"Y":"N";
profitSharing = gasChannelConfig.getProfitSharingStatus().equals(true) ? "Y" : "N";
} else if (store.getSourceType().equals(MerchantStoreSourceType.type3.getNumber())) {
// 查询支付配置
HighGasChannelConfig gasChannelConfig = gasChannelConfigService.getConfig(GasChannel.type3,GasChannelPayPlatformType.type1 );
HighGasChannelConfig gasChannelConfig = gasChannelConfigService.getConfig(GasChannel.type3, GasChannelPayPlatformType.type1);
if (gasChannelConfig == null) {
throw ErrorHelp.genException(SysCode.System, ErrorCode.COMMON_ERROR, "未配置支付参数,请稍后重试!");
}
weChatPayReqInfo.setSub_mch_id(gasChannelConfig.getPayPlatformMerNo());
profitSharing = gasChannelConfig.getProfitSharingStatus().equals(true)?"Y":"N";
profitSharing = gasChannelConfig.getProfitSharingStatus().equals(true) ? "Y" : "N";
} else if (store.getSourceType().equals(MerchantStoreSourceType.type4.getNumber())) {
// 查询支付配置
HighGasChannelConfig gasChannelConfig = gasChannelConfigService.getConfig(GasChannel.type4,GasChannelPayPlatformType.type1 );
HighGasChannelConfig gasChannelConfig = gasChannelConfigService.getConfig(GasChannel.type4, GasChannelPayPlatformType.type1);
if (gasChannelConfig == null) {
throw ErrorHelp.genException(SysCode.System, ErrorCode.COMMON_ERROR, "未配置支付参数,请稍后重试!");
}
weChatPayReqInfo.setSub_mch_id(gasChannelConfig.getPayPlatformMerNo());
profitSharing = gasChannelConfig.getProfitSharingStatus().equals(true)?"Y":"N";
profitSharing = gasChannelConfig.getProfitSharingStatus().equals(true) ? "Y" : "N";
} else if (store.getSourceType().equals(MerchantStoreSourceType.type5.getNumber())) {
// 查询支付配置
HighGasChannelConfig gasChannelConfig = gasChannelConfigService.getConfig(GasChannel.type5,GasChannelPayPlatformType.type1 );
HighGasChannelConfig gasChannelConfig = gasChannelConfigService.getConfig(GasChannel.type5, GasChannelPayPlatformType.type1);
if (gasChannelConfig == null) {
throw ErrorHelp.genException(SysCode.System, ErrorCode.COMMON_ERROR, "未配置支付参数,请稍后重试!");
}
weChatPayReqInfo.setSub_mch_id(gasChannelConfig.getPayPlatformMerNo());
profitSharing = gasChannelConfig.getProfitSharingStatus().equals(true)?"Y":"N";
profitSharing = gasChannelConfig.getProfitSharingStatus().equals(true) ? "Y" : "N";
} else {
throw ErrorHelp.genException(SysCode.System, ErrorCode.COMMON_ERROR, "未配置支付参数,请稍后重试!");
@ -267,9 +292,11 @@ public class OrderPayController {
} else if (order.getProductType().equals(OrderProductType.PRODUCT_TYPE12.getNumber())) {
weChatPayReqInfo.setSub_mch_id(WxOrderConfig.MCH_ID_1614670195);
}else {
} else {
weChatPayReqInfo.setSub_mch_id(WxOrderConfig.MCH_ID_1609882817);
}
}
// weChatPayReqInfo.setOpenid(openId);
weChatPayReqInfo.setSub_openid(body.getString("openId"));
@ -286,11 +313,11 @@ public class OrderPayController {
weChatPayReqInfo.setTime_expire(DateUtil.date2String(DateUtil.addMinute(order.getCreateTime(), 10), "yyyyMMddHHmmss"));
//附加数据,区分订单类型
Map<String,String> payMap = new HashMap<>();
Map<String, String> payMap = new HashMap<>();
payMap.put("app_id", weChatPayReqInfo.getSub_appid());
payMap.put("api_key", SysConst.getSysConfig().getWxPayApiKey());
payMap.put("unified_order_url", SysConst.getSysConfig().getWxPayUnifiedOrderUrl());
SortedMap<Object, Object> sortedMap = wechatPayUtil.goWechatPay(weChatPayReqInfo,payMap);
SortedMap<Object, Object> sortedMap = wechatPayUtil.goWechatPay(weChatPayReqInfo, payMap);
order.setAccountMerchantNum(weChatPayReqInfo.getSub_mch_id());
order.setProfitSharingStatus(profitSharing.equals("Y"));
@ -299,12 +326,12 @@ public class OrderPayController {
return ResponseMsgUtil.success(sortedMap);
} catch (Exception e) {
log.error("OrderController -> create() error!",e);
log.error("OrderController -> create() error!", e);
return ResponseMsgUtil.exception(e);
}
}
@RequestMapping(value="/cardPay",method = RequestMethod.POST)
@RequestMapping(value = "/cardPay", method = RequestMethod.POST)
@ResponseBody
@ApiOperation(value = "卡支付")
public ResponseData cardPay(@RequestBody JSONObject body) {
@ -347,7 +374,7 @@ public class OrderPayController {
}
// 查询订单
HighOrder order = orderService.getOrderDetailByNo(body.getString("orderNo"));
if(!order.getOrderStatus().equals(OrderStatus.STATUS1.getNumber())) {
if (!order.getOrderStatus().equals(OrderStatus.STATUS1.getNumber())) {
log.error("OrderController --> cardPay() ERROR", "订单不处于待支付状态");
throw ErrorHelp.genException(SysCode.System, ErrorCode.ORDER_NO_STAY_PAY, "");
}
@ -369,7 +396,7 @@ public class OrderPayController {
}
} catch (Exception e) {
log.error("OrderPayController -> cardPay() error!",e);
log.error("OrderPayController -> cardPay() error!", e);
return ResponseMsgUtil.exception(e);
}
}
@ -487,7 +514,7 @@ public class OrderPayController {
}
}*/
@RequestMapping(value="/unionPay",method = RequestMethod.POST)
@RequestMapping(value = "/unionPay", method = RequestMethod.POST)
@ResponseBody
@ApiOperation(value = "银联支付")
public ResponseData unionPay(@RequestBody JSONObject body, HttpServletRequest request) {
@ -498,11 +525,11 @@ public class OrderPayController {
}
HighOrder order = orderService.getOrderDetailByNo(body.getString("orderNo"));
if(order == null) {
if (order == null) {
log.error("OrderController --> orderToPay() ERROR", "未找到订单信息");
throw ErrorHelp.genException(SysCode.System, ErrorCode.NOT_FOUND_ORDER, "");
}
if(!order.getOrderStatus().equals(OrderStatus.STATUS1.getNumber())) {
if (!order.getOrderStatus().equals(OrderStatus.STATUS1.getNumber())) {
log.error("OrderController --> orderToPay() ERROR", "订单不处于待支付状态");
throw ErrorHelp.genException(SysCode.System, ErrorCode.ORDER_NO_STAY_PAY, "");
}

@ -175,4 +175,25 @@
<appender-ref ref="wechatPayLog" />
</logger>
<appender name="blxLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>log/blxLog.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log/blxLog/blxLog.log.%d.%i</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- or whenever the file size reaches 64 MB -->
<maxFileSize>64 MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>
%d %p (%file:%line\)- %m%n
</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
</appender>
<logger name="com.hai.openApi.config.BlxConfig" level="INFO">
<appender-ref ref="blxLog" />
</logger>
</configuration>

@ -195,4 +195,24 @@
<appender-ref ref="chongQingCNPC" />
</logger>
<appender name="blxLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>log/blxLog.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log/blxLog/blxLog.log.%d.%i</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- or whenever the file size reaches 64 MB -->
<maxFileSize>64 MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>
%d %p (%file:%line\)- %m%n
</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
</appender>
<logger name="com.hai.openApi.config.BlxConfig" level="INFO">
<appender-ref ref="blxLog" />
</logger>
</configuration>

@ -41,7 +41,7 @@ public class CmsContentConfig {
map.put("status" , 1);
PageHelper.startPage(1, 3);
return new PageInfo<>( highDiscountPackageService.getDiscountPackageList(map)).getList();
} else if (CmsContentDataEnum.REFUEL.getName().equals(title) || CmsContentDataEnum.INTEGRAL.getName().equals(title) || CmsContentDataEnum.UNION_CARD.getName().equals(title)){
} else if (CmsContentDataEnum.REFUEL.getName().equals(title) || CmsContentDataEnum.INTEGRAL.getName().equals(title) || CmsContentDataEnum.UNION_CARD.getName().equals(title) || CmsContentDataEnum.BLX_COUPON.getName().equals(title)){
map.put("companyId", companyId);
map.put("displayArea", CmsContentDataEnum.getTypeByName(title));
map.put("notCouponSource", 2);

@ -136,4 +136,6 @@ public class CommonSysConfig {
private String payPostUrl;
private String filesystem;
private String blxPostUrl;
}

@ -74,6 +74,12 @@ public class WxOrderConfig {
*/
public static final String MCH_ID_1624126902 = "1624126902";
/**
* 微信支付商户号
* 贵州紫凌电子商务有限公司
*/
public static final String MCH_ID_1648729281 = "1648729281";
/**
* 嗨森逛公众号APPID
*/
@ -83,6 +89,15 @@ public class WxOrderConfig {
* 嗨森逛小程序APPID
*/
public static final String APPID_HSG_MINI = "wx8d49e2f83025229d";
/**
* 黔印象公众号APPID
*/
public static final String APPID_QYX_MP = "wxf846cf24e87420a2";
/**
* 黔印象小程序APPID
*/
public static final String APPID_QYX_MINI = "wx5e0cac96ea91c01f";
public static OrderRefundModel orderToRefund(String paySerialNo,BigDecimal totalFee,BigDecimal refundFee) throws Exception {

@ -20,7 +20,7 @@ public class ApiMchProduct implements Serializable {
private Long id;
/**
* 产品类型 1星巴克 2肯德基 3会员充值 4积分充值 5购买卡券 6加油服务 7优惠券包 8汇联通充值 9话费充值
* 产品类型 1星巴克 2肯德基 3会员充值 4积分充值 5购买卡券 6加油服务 7优惠券包 8汇联通充值 9话费充值 12:实物产品
*/
private Integer productType;

@ -17,6 +17,7 @@ public enum CmsContentDataEnum {
PREFERABLY(4 , "优选商品"),
UNION_CARD(5 , "工会卡专区"),
DISCOUNT(6 , "天天好券"),
BLX_COUPON(7 , "停车券专区"),
MSG(90 , "消息通知"),
;

@ -17,6 +17,9 @@ public class UserModel {
// 未使用优惠券数量
private Integer unusedDiscount;
// 停车券余额
private Integer blxCouponBalance;
// 待支付数量
private Long unpaid;
@ -157,4 +160,12 @@ public class UserModel {
public void setOrderPayNum(Long orderPayNum) {
this.orderPayNum = orderPayNum;
}
public Integer getBlxCouponBalance() {
return blxCouponBalance;
}
public void setBlxCouponBalance(Integer blxCouponBalance) {
this.blxCouponBalance = blxCouponBalance;
}
}

@ -1,8 +1,13 @@
package com.hai.openApi.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.hai.common.utils.HttpsUtils;
import com.hai.common.utils.RedisUtil;
import com.hai.config.CommonSysConst;
import com.hai.config.HuiLianTongUnionCardConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@ -15,6 +20,8 @@ public class BlxConfig {
@Resource
private RedisUtil redisUtil;
private static Logger log = LoggerFactory.getLogger(BlxConfig.class);
/**
* @Author Sum1Dream
* @Name generateJw
@ -24,18 +31,20 @@ public class BlxConfig {
* @Return java.lang.String
*/
public String generateJw() {
Object token = redisUtil.get("BlxToken");
if (token == null) {
Map<String , Object > map = new HashMap<>();
map.put("organizeCode" , "dhy");
log.info("============ 比邻星停车券业务-获取TOKEN-START =============");
JSONObject object = HttpsUtils.doGet("https://vertical-formats-gateway.blx.bodata.cn:18443/admin/api/generateJwt" , map);
log.info("请求参数: " + map);
log.info("响应参数: " + object);
log.info("============ 比邻星停车券业务-获取TOKEN-END ==============");
return object.getString("data");
}
return token.toString();
}
@ -48,7 +57,7 @@ public class BlxConfig {
* @Return com.alibaba.fastjson.JSONObject
*/
public JSONObject queryCouponPage() {
log.info("============ 比邻星停车券业务-获取比邻星停车券列表-START =============");
Map<String , Object > map = new HashMap<>();
map.put("rows" , "20");
map.put("currPage" , "1");
@ -56,10 +65,14 @@ public class BlxConfig {
Map<String , Object> header = new HashMap<>();
header.put("token" , generateJw());
return HttpsUtils.doPost("https://vertical-formats-gateway.blx.bodata.cn:18443/admin/api/qureyCouponPage" , map , header);
JSONObject object = HttpsUtils.doPost(CommonSysConst.getSysConfig().getBlxPostUrl() + "/admin/api/qureyCouponPage" , map , header);
log.info("头部参数: " + header);
log.info("请求参数: " + map);
log.info("响应参数: " + object);
log.info("============ 比邻星停车券业务-获取比邻星停车券列表-END ==============");
return object;
}
/**
* @Author Sum1Dream
* @Name sendCoupon
@ -69,6 +82,7 @@ public class BlxConfig {
* @Return com.alibaba.fastjson.JSONObject
*/
public JSONObject sendCoupon(JSONObject object) {
Map<String , Object > map = new HashMap<>();
map.put("couponCode" , object.getString("couponCode"));
map.put("receiverPhone" , object.getString("receiverPhone"));
@ -76,8 +90,46 @@ public class BlxConfig {
Map<String , Object> header = new HashMap<>();
header.put("token" , generateJw());
log.info("============ 比邻星停车券业务-发放停车券-START =============");
JSONObject data = HttpsUtils.doPost(CommonSysConst.getSysConfig().getBlxPostUrl() + "/admin/api/sendCupon" , map , header);
log.info("头部参数: " + header);
log.info("请求参数: " + map);
log.info("响应参数: " + data);
log.info("============ 比邻星停车券业务-发放停车券-END =============");
return data;
}
/**
* @Author Sum1Dream
* @Name queryCouponPage
* @Description // 获取比邻星停车券列表
* @Date 10:57 2023/7/11
* @Param []
* @Return com.alibaba.fastjson.JSONObject
*/
public JSONObject getUserCouponPage(JSONObject object) {
log.info("============ 比邻星停车券业务-获取用户未使用卡券列表-START =============");
Map<String , Object > map = new HashMap<>();
map.put("rows" , "2000");
map.put("currPage" , "1");
JSONObject jsonObject = new JSONObject();
jsonObject.put("userMobile" , object.getString("userMobile"));
jsonObject.put("couponStatus" , 1);
map.put("data" , jsonObject);
Map<String , Object> header = new HashMap<>();
header.put("token" , generateJw());
JSONObject data = HttpsUtils.doPost(CommonSysConst.getSysConfig().getBlxPostUrl() + "/admin/api/getUserCouponPage" , map , header);
log.info("头部参数: " + header);
log.info("请求参数: " + map);
log.info("响应参数: " + data);
log.info("============ 比邻星停车券业务-获取比邻星停车券列表-END ==============");
return data;
return HttpsUtils.doPost("https://vertical-formats-gateway.blx.bodata.cn:18443/admin/api/qureyCouponPage" , map , header);
}
}

@ -68,4 +68,6 @@ public interface ApiOrderCreateHandleService {
*/
ApiOrder starbucksHandle(JSONObject object) throws Exception;
ApiOrder blxCouponHandle(JSONObject object) throws Exception;
}

@ -337,4 +337,18 @@ public class ApiOrderCreateHandleServiceImpl implements ApiOrderCreateHandleServ
}
@Override
public ApiOrder blxCouponHandle(JSONObject object) throws Exception {
// 获取下单内容
JSONObject dataObject = JSONObject.parseObject(object.getString("content"));
JSONArray dataArray = dataObject.getJSONArray("data");
// 查询api商户
ApiMerchants apiMerchants = apiMerchantsService.findByMchId(object.getString("merchId"));
return null;
}
}

@ -203,6 +203,27 @@ public class ApiOrderServiceImpl implements ApiOrderService {
orderService.createOrder(createOrderModel);
}
// 处理话费充值订单
if (OrderProductType.PRODUCT_TYPE13.getNumber().equals(object.getInteger("productType"))) {
apiOrder = orderCreateHandleService.mobileHandle(object);
// 组装子订单内容
CreateOrderChildModel createOrderChildModel = new CreateOrderChildModel();
createOrderChildModel.setMemName(apiMerchants.getMerchantName());
createOrderChildModel.setMemPhone(apiMerchants.getPhone());
createOrderChildModel.setCompanyId(2L);
createOrderChildModel.setGoodsType(OrderChildGoodsType.TYPE6.getNumber());
createOrderChildModel.setGoodsId(dataObject.getLong("productId"));
createOrderChildModel.setRechargeContent(dataObject.getString("mobile"));
createOrderChildModel.setSaleCount(1);
createOrderChildModel.setIsTyAgent(false);
createOrderChildModel.setGoodsPrice(apiOrder.getOrderPrice());
// 组装订单
createOrderModel.getChildOrderList().add(createOrderChildModel);
orderService.createOrder(createOrderModel);
}
apiOrder.setOrderNo(createOrderModel.getOrderNo());
apiOrder.setStatus(1);

@ -173,7 +173,7 @@ public class OrderCreateHandleServiceImpl implements OrderCreateHandleService {
String childOrderNo = OrderUtil.generateChildOrderNo();
// 如果不是来源不是贵州中石化,需要校验库存
if (!coupon.getCouponSource().equals(4) && !coupon.getCouponSource().equals(5)) {
if (!coupon.getCouponSource().equals(4) && !coupon.getCouponSource().equals(5) && !coupon.getCouponSource().equals(6)) {
if (couponService.assignOrderStock(coupon.getId(), childOrderNo) == null) {
throw ErrorHelp.genException(SysCode.System, ErrorCode.COMMON_ERROR, "库存不足");
}

@ -459,15 +459,6 @@ public class OrderPaySuccessServiceImpl implements OrderPaySuccessService {
private void starbucksOrderHandle(String channelOrderNo) throws Exception {
// 订单支付
QianZhuConfig.starbucksOrdersPay(channelOrderNo);
// 推送订单记录
// HighGasOrderPush highGasOrderPush = new HighGasOrderPush();
// highGasOrderPush.setType(OrderPushType.type8.getType());
// highGasOrderPush.setOrderNo(order.getOrderNo());
// highGasOrderPush.setCreateTime(new Date());
// highGasOrderPush.setRequestContent(order.getOrderNo());
// highGasOrderPush.setReturnContent(requestContent.toJSONString());
// highGasOrderPushMapper.insert(highGasOrderPush);
}
/**

@ -21,6 +21,7 @@ public enum OrderProductType {
PRODUCT_TYPE10(10, "电影票" ,"" , ""),
PRODUCT_TYPE11(11, "贵州中石化" , "", "GZ_SINOPEC_CONFIG"),
PRODUCT_TYPE12(12, "实物商品" , "orderLogo/goods.png", "GOODS"),
PRODUCT_TYPE13(13, "比邻星停车券" , "orderLogo/coupon.png", "BLX_COUPON"),
PRODUCT_TYPE101(101, "惠支付" , "orderLogo/pay.png", "PAY"),
PRODUCT_TYPE102(102, "门店活动" , "orderLogo/store_activity.png", "STORE_ACTIVITY"),
;

@ -0,0 +1,46 @@
package com.hai.order.type;
import java.util.Objects;
public enum PlatformType {
TYPE1(1, "嗨森逛"),
TYPE2(2, "黔印象"),
;
private Integer number;
private String name;
PlatformType(int number, String name) {
this.number = number;
this.name = name;
}
/**
* 根据类型查询数据
* @param type
* @return
*/
public static PlatformType getDataByType(Integer type) {
for (PlatformType ele : values()) {
if(Objects.equals(type,ele.getNumber())) return ele;
}
return null;
}
public Integer getNumber() {
return number;
}
public void setNumber(Integer number) {
this.number = number;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

@ -73,4 +73,6 @@ public interface HighUserCardService {
**/
HighUserCard getUserHtlCardNo(Long userId);
Integer getBlxCouponBalance(String phone);
}

@ -18,6 +18,10 @@ import com.hai.entity.*;
import com.hai.enum_type.OrderPushType;
import com.hai.model.GzSinopecModel;
import com.hai.model.UserInfoModel;
import com.hai.openApi.config.BlxConfig;
import com.hai.openApi.service.ApiBlxCouponService;
import com.hai.order.service.OrderService;
import com.hai.order.type.OrderStatus;
import com.hai.service.*;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
@ -26,6 +30,7 @@ import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
/**
@ -57,9 +62,6 @@ public class HighCouponCodeServiceImpl implements HighCouponCodeService {
@Resource
private HighCouponAgentService highCouponAgentService;
@Resource
private HighChildOrderMapper highChildOrderMapper;
@Resource
private HighActivityInfoService activityInfoService;
@ -71,6 +73,15 @@ public class HighCouponCodeServiceImpl implements HighCouponCodeService {
highCouponCodeMapper.insert(highCouponCode);
}
@Resource
private BlxConfig blxConfig;
@Resource
private ApiBlxCouponService apiBlxCouponService;
@Resource
private OrderService orderService;
@Override
public void insertList(List<HighCouponCode> list) throws Exception {
for (HighCouponCode highCouponCode: list) {
@ -396,6 +407,40 @@ public class HighCouponCodeServiceImpl implements HighCouponCodeService {
highUserCouponService.insertUserCoupon(highUserCoupon);
}
}
} else if (coupon.getCouponSource().equals(6)) {
// 派发停车券
ApiBlxCoupon apiBlxCoupon = apiBlxCouponService.findBlxCoupon(coupon.getCouponKey());
if (apiBlxCoupon == null || apiBlxCoupon.getCouponStatus() != 1) {
throw ErrorHelp.genException(SysCode.System , ErrorCode.COMMON_ERROR , "当前卡券不可购买");
}
// 计算上送卡券数量
int num = coupon.getSalesPrice().divide(new BigDecimal(apiBlxCoupon.getDenomination())).intValue();
if (num > 0) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("couponCode" , coupon.getCouponKey());
jsonObject.put("receiverPhone" , order.getMemPhone());
jsonObject.put("receiverCount" , num);
JSONObject object = blxConfig.sendCoupon(jsonObject);
if (object.getInteger("code") == 1) {
order.setOrderStatus(OrderStatus.STATUS3.getNumber());
order.setFinishTime(new Date());
orderService.updateOrderData(order);
// 卡卷关联用户
HighUserCoupon highUserCoupon = new HighUserCoupon();
highUserCoupon.setMerchantId(coupon.getMerchantId());
highUserCoupon.setCouponId(coupon.getId());
highUserCoupon.setOrderId(order.getId());
highUserCoupon.setChildOrderId(childOrder.getId());
highUserCoupon.setUserId(order.getMemId());
highUserCoupon.setCreateTime(new Date());
highUserCoupon.setUseEndTime(apiBlxCoupon.getUseEndTime());
highUserCoupon.setStatus(1); // 状态 0:已过期 1:未使用 2:已使用
highUserCouponService.insertUserCoupon(highUserCoupon);
}
}
} else {
// 查询子订单的兑换码
HighCouponCode code = getCodeByChildOrderNo(childOrder.getChildOrderNo());

@ -1,5 +1,6 @@
package com.hai.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.hai.common.exception.ErrorCode;
import com.hai.common.exception.ErrorHelp;
import com.hai.common.exception.SysCode;
@ -12,6 +13,7 @@ import com.hai.entity.HighUserCardExample;
import com.hai.enum_type.OilCardStatusEnum;
import com.hai.enum_type.UserCardType;
import com.hai.model.HighUserModel;
import com.hai.openApi.config.BlxConfig;
import com.hai.service.HighFleetOilCardService;
import com.hai.service.HighOilCardService;
import com.hai.service.HighUserCardService;
@ -32,6 +34,10 @@ public class HighUserCardServiceImpl implements HighUserCardService {
@Resource
private UserCenter userCenter;
@Resource
private BlxConfig blxConfig;
@Resource
private HighOilCardService oilCardService;
@ -164,4 +170,25 @@ public class HighUserCardServiceImpl implements HighUserCardService {
}
return null;
}
@Override
public Integer getBlxCouponBalance(String phone) {
if (phone == null) {
return 0;
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("userMobile" , phone);
JSONObject object = blxConfig.getUserCouponPage(jsonObject);
int num = 0;
if (object.getInteger("code") == 1) {
num = object.getJSONObject("data").getInteger("total");
}
return num;
}
}

@ -18,6 +18,7 @@ import com.hai.enum_type.LoginPlatform;
import com.hai.enum_type.OilCardBindStatusEnum;
import com.hai.enum_type.UserCardType;
import com.hai.model.HighUserModel;
import com.hai.openApi.config.BlxConfig;
import com.hai.service.*;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
@ -51,6 +52,7 @@ public class HighUserServiceImpl implements HighUserService {
@Resource
private HighUserCardService highUserCardService;
@Resource
private HighOrderService highOrderService;
@ -125,6 +127,7 @@ public class HighUserServiceImpl implements HighUserService {
}
}
user.setOpenIdH5(MapUtils.getString(other, "openIdH5"));
user.setOpenId(MapUtils.getString(other, "openId"));
loginLog.setUserId(user.getId());
userLoginLogMapper.insert(loginLog);
@ -208,6 +211,7 @@ public class HighUserServiceImpl implements HighUserService {
user.setIsSetPayPwd(highUserPayPasswordService.isSetPayPwd(userId));
user.setIsSetHltCard(highUserCardService.isBindHtlCard(userId));
user.setHltCardNo(highUserCardService.getUserHtlCardNo(userId));
user.setBlxCouponBalance(highUserCardService.getBlxCouponBalance(user.getPhone()));
// 查询油卡
List<HighUserCard> oilCardList = highUserCardService.getListByUser(userId, UserCardType.type2.getType());

@ -132,3 +132,4 @@ filesystem=https://hsgcs.dctpay.com/filesystem/
#payPostUrl=http://localhost:9601/crest/
#
blxPostUrl=https://vertical-formats-gateway.blx.bodata.cn:18443

@ -119,3 +119,6 @@ wxH5AppSecret=0e606fc1378d35e359fcf3f15570b2c5
#PAY
payPostUrl=https://pay.dctpay.com/crest/
filesystem=https://hsg.dctpay.com/filesystem/
blxPostUrl=https://gateway.vf.bilinxing.com

@ -125,3 +125,6 @@ wxH5AppSecret=0e606fc1378d35e359fcf3f15570b2c5
#PAY
payPostUrl=https://pay.dctpay.com/crest/
filesystem=https://hsg.dctpay.com/filesystem/
blxPostUrl=https://gateway.vf.bilinxing.com

@ -90,4 +90,24 @@
<logger name="com.hai.config.ChongQingCNPCService" level="INFO">
<appender-ref ref="ChongQingCNPC" />
</logger>
<appender name="blxLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>log/blxLog.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log/blxLog/blxLog.log.%d.%i</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- or whenever the file size reaches 64 MB -->
<maxFileSize>64 MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>
%d %p (%file:%line\)- %m%n
</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
</appender>
<logger name="com.hai.openApi.config.BlxConfig" level="INFO">
<appender-ref ref="blxLog" />
</logger>
</configuration>

@ -90,4 +90,24 @@
<logger name="com.hai.config.ChongQingCNPCService" level="INFO">
<appender-ref ref="ChongQingCNPC" />
</logger>
<appender name="blxLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>log/blxLog.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log/blxLog/blxLog.log.%d.%i</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- or whenever the file size reaches 64 MB -->
<maxFileSize>64 MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>
%d %p (%file:%line\)- %m%n
</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
</appender>
<logger name="com.hai.openApi.config.BlxConfig" level="INFO">
<appender-ref ref="blxLog" />
</logger>
</configuration>

@ -1,6 +1,13 @@
package com.v1.controller.business;
import com.alibaba.fastjson.JSONObject;
import com.hai.common.exception.ErrorCode;
import com.hai.common.exception.ErrorHelp;
import com.hai.common.exception.SysCode;
import com.hai.common.utils.ResponseMsgUtil;
import com.hai.entity.ApiMerchants;
import com.hai.model.ResponseData;
import com.hai.openApi.service.ApiBlxCouponService;
import com.hai.openApi.service.ApiMobileRechargeProductService;
import com.hai.order.type.OrderProductType;
@ -40,60 +47,60 @@ public class BlxCouponController {
private ApiIpAddressService apiIpAddressService;
// @RequestMapping(value = "/getCouponList", method = RequestMethod.POST)
// @ResponseBody
// @ApiOperation(value = "获取话费充值产品")
// public ResponseData getCouponList(@RequestBody JSONObject object , HttpServletRequest request ) {
// try {
//
// String ip = "";
// // 有的user可能使用代理,为处理用户使用代理的情况,使用x-forwarded-for
// if (request.getHeader("x-forwarded-for") == null) {
// ip = request.getRemoteAddr();
// } else {
// ip = request.getHeader("x-forwarded-for");
// }
//
// if (!apiIpAddressService.validationIpAddressLegal(object.getString("merchId") , ip)) {
// log.error("getRechargeProduct error!", "非法ip地址,请联系管理人员!");
// throw ErrorHelp.genException(SysCode.System, ErrorCode.IP_ERROR, "非法ip地址,请联系管理人员!");
// }
//
// if (
// StringUtils.isBlank(object.getString("merchId")) ||
// StringUtils.isBlank(object.getString("sign"))
// ) {
// log.error("getRechargeProduct error!", "请求参数校验失败!");
// throw ErrorHelp.genException(SysCode.System, ErrorCode.REQ_PARAMS_ERROR);
// }
//
// ApiMerchants apiMerchants = apiMerchantsService.findByMchId(object.getString("merchId"));
//
// if (apiMerchants == null) {
// log.error("getRechargeProduct error!", "帐户号错误!");
// throw ErrorHelp.genException(SysCode.System, ErrorCode.ACCOUNT_NOT_EXIST);
// }
//
// if (!secConfigService.isConfig(OrderProductType.PRODUCT_TYPE9.getApiCode(), object.getString("merchId"))) {
// log.error("getAllCities error!", "当前帐户无权限查询!");
// throw ErrorHelp.genException(SysCode.System, ErrorCode.MERCHANT_NOT_AUTHORIZED, "");
// }
//
// Map<String , Object> map = new HashMap<>();
// map.put("apiKey" , apiMerchants.getApiKey());
// map.put("merchId" , object.getString("merchId"));
//
// if (!secConfigService.isSignVerify(map , object.getString("sign"))) {
// log.error("getRechargeProduct error!", "签名校验失败!");
// throw ErrorHelp.genException(SysCode.System, ErrorCode.SIGN_VERIFY);
// }
//
// return ResponseMsgUtil.success(apiBlxCouponService.getBlxCouponList(object.getString("merchId")));
//
// } catch (Exception e) {
// log.error("HighOpenApiController --> getRechargeProduct() error!", e);
// return ResponseMsgUtil.exception(e);
// }
// }
@RequestMapping(value = "/getCouponList", method = RequestMethod.POST)
@ResponseBody
@ApiOperation(value = "获取停车券列表")
public ResponseData getCouponList(@RequestBody JSONObject object , HttpServletRequest request ) {
try {
String ip = "";
// 有的user可能使用代理,为处理用户使用代理的情况,使用x-forwarded-for
if (request.getHeader("x-forwarded-for") == null) {
ip = request.getRemoteAddr();
} else {
ip = request.getHeader("x-forwarded-for");
}
if (!apiIpAddressService.validationIpAddressLegal(object.getString("merchId") , ip)) {
log.error("getRechargeProduct error!", "非法ip地址,请联系管理人员!");
throw ErrorHelp.genException(SysCode.System, ErrorCode.IP_ERROR, "非法ip地址,请联系管理人员!");
}
if (
StringUtils.isBlank(object.getString("merchId")) ||
StringUtils.isBlank(object.getString("sign"))
) {
log.error("getRechargeProduct error!", "请求参数校验失败!");
throw ErrorHelp.genException(SysCode.System, ErrorCode.REQ_PARAMS_ERROR);
}
ApiMerchants apiMerchants = apiMerchantsService.findByMchId(object.getString("merchId"));
if (apiMerchants == null) {
log.error("getRechargeProduct error!", "帐户号错误!");
throw ErrorHelp.genException(SysCode.System, ErrorCode.ACCOUNT_NOT_EXIST);
}
if (!secConfigService.isConfig(OrderProductType.PRODUCT_TYPE13.getApiCode(), object.getString("merchId"))) {
log.error("getAllCities error!", "当前帐户无权限查询!");
throw ErrorHelp.genException(SysCode.System, ErrorCode.MERCHANT_NOT_AUTHORIZED, "");
}
Map<String , Object> map = new HashMap<>();
map.put("apiKey" , apiMerchants.getApiKey());
map.put("merchId" , object.getString("merchId"));
if (!secConfigService.isSignVerify(map , object.getString("sign"))) {
log.error("getRechargeProduct error!", "签名校验失败!");
throw ErrorHelp.genException(SysCode.System, ErrorCode.SIGN_VERIFY);
}
return ResponseMsgUtil.success(apiBlxCouponService.getBlxCouponList(new HashMap<>()));
} catch (Exception e) {
log.error("HighOpenApiController --> getRechargeProduct() error!", e);
return ResponseMsgUtil.exception(e);
}
}
}

@ -168,6 +168,17 @@ public class orderController {
throw ErrorHelp.genException(SysCode.System, ErrorCode.COMMON_ERROR, "未填写下单手机号,就餐方式,下单信息,门店编码");
}
}
if (object.getInteger("productType").equals(OrderProductType.PRODUCT_TYPE13.getNumber())) {
if (dataObject.getJSONArray("data") == null
|| StringUtils.isBlank(dataObject.getString("couponCode"))
|| StringUtils.isBlank(dataObject.getString("receiverPhone"))
|| dataObject.getInteger("receiverCount") == null
) {
log.error("OrderController -> create() error!","");
throw ErrorHelp.genException(SysCode.System, ErrorCode.COMMON_ERROR, "未填写卡券编码,手机号,购买数量");
}
}
return ResponseMsgUtil.success(apiOrderService.createOrder(object));
} catch (Exception e) {

Loading…
Cancel
Save