From c4f5a1e6d46f244f0f58296ff9bd9d341b9ba739 Mon Sep 17 00:00:00 2001 From: xiaohanzi Date: Tue, 13 Apr 2021 18:17:48 +0800 Subject: [PATCH] add cash out --- .../migration/V202104130001__for_cash_out.sql | 34 +++ .../controller/WxCashOutController.java | 62 +++++ .../java/com/iformall/mq/MqBaseConsumer.java | 8 + .../config/BaseMyBatisConfiguration.java | 7 +- .../com/iformall/domain/po/WxCashOut.java | 51 ++++ .../java/com/iformall/domain/po/WxMall.java | 3 + .../com/iformall/domain/po/WxMerchant.java | 3 + .../domain/po/msg/FmInsideCashOutMsg.java | 23 ++ .../java/com/iformall/enums/EnumCashOut.java | 36 +++ .../com/iformall/enums/EnumCashOutStatus.java | 40 +++ .../java/com/iformall/enums/EnumFlowKey.java | 2 + .../com/iformall/enums/EnumMsgRecordType.java | 1 + .../com/iformall/mapper/WxCashOutMapper.java | 18 ++ .../com/iformall/mapper/WxMerchantMapper.java | 4 + .../java/com/iformall/pay/WxCashOutP.java | 37 +++ .../src/main/java/com/iformall/pay/WxPay.java | 4 +- .../iformall/service/WxCashOutService.java | 45 ++++ .../service/impl/WxCashOutServiceImpl.java | 243 ++++++++++++++++++ .../impl/WxCouponOrderServiceImpl.java | 46 +++- .../service/impl/WxFlowServiceImpl.java | 30 +++ .../impl/FmInsideCashOutMsgServiceImpl.java | 29 +++ .../service/park/impl/cyf/CYFParkService.java | 2 +- .../service/pay/PayServiceFactory.java | 25 +- .../cashout/CashOutAdapterService.java | 37 +++ .../cashout/entity/CashOutAdapterResult.java | 51 ++++ .../cashout/wx/WxCashOutAdapterService.java | 216 ++++++++++++++++ .../main/resources/mapper/WxCashOutMapper.xml | 79 ++++++ .../resources/mapper/WxCouponOrderMapper.xml | 2 +- .../main/resources/mapper/WxMallMapper.xml | 4 +- .../resources/mapper/WxMerchantMapper.xml | 13 +- 30 files changed, 1136 insertions(+), 19 deletions(-) create mode 100644 mallinkAdmin/src/main/resources/db/migration/V202104130001__for_cash_out.sql create mode 100644 mallinkBApi/src/main/java/com/iformall/controller/WxCashOutController.java create mode 100644 mallinkService/src/main/java/com/iformall/domain/po/WxCashOut.java create mode 100644 mallinkService/src/main/java/com/iformall/domain/po/msg/FmInsideCashOutMsg.java create mode 100644 mallinkService/src/main/java/com/iformall/enums/EnumCashOut.java create mode 100644 mallinkService/src/main/java/com/iformall/enums/EnumCashOutStatus.java create mode 100644 mallinkService/src/main/java/com/iformall/mapper/WxCashOutMapper.java create mode 100644 mallinkService/src/main/java/com/iformall/pay/WxCashOutP.java create mode 100644 mallinkService/src/main/java/com/iformall/service/WxCashOutService.java create mode 100644 mallinkService/src/main/java/com/iformall/service/impl/WxCashOutServiceImpl.java create mode 100644 mallinkService/src/main/java/com/iformall/service/msg/impl/FmInsideCashOutMsgServiceImpl.java create mode 100644 mallinkService/src/main/java/com/iformall/service/pay/service/cashout/CashOutAdapterService.java create mode 100644 mallinkService/src/main/java/com/iformall/service/pay/service/cashout/entity/CashOutAdapterResult.java create mode 100644 mallinkService/src/main/java/com/iformall/service/pay/service/cashout/wx/WxCashOutAdapterService.java create mode 100644 mallinkService/src/main/resources/mapper/WxCashOutMapper.xml diff --git a/mallinkAdmin/src/main/resources/db/migration/V202104130001__for_cash_out.sql b/mallinkAdmin/src/main/resources/db/migration/V202104130001__for_cash_out.sql new file mode 100644 index 000000000..56f107f7e --- /dev/null +++ b/mallinkAdmin/src/main/resources/db/migration/V202104130001__for_cash_out.sql @@ -0,0 +1,34 @@ +ALTER TABLE `wx_mall` +ADD COLUMN `cash_out_support` INT(1) COMMENT '是否支持商户提现(不开启分账才有效) 0-不支持 1-支持' AFTER `live_support`; + +UPDATE wx_mall SET cash_out_support = 0 ; + +ALTER TABLE wx_merchant ADD COLUMN `cash_out_number` INT(11) COMMENT '商户待提现金额(分)' ; + +###页面添加工作流,提现审批,22, 初始化里面也加上 + +CREATE TABLE `wx_cash_out` ( + `id` bigint(20) NOT NULL, + `total_fee` int(11) NOT NULL COMMENT '总金额(分)', + `status` tinyint(1) NOT NULL COMMENT '状态 0-提现中,1-提现成功,2-提现失败,3-提现拒绝', + `create_date` datetime DEFAULT NULL COMMENT '创建时间', + `update_date` datetime DEFAULT NULL COMMENT '更新时间', + `audio_remark` varchar(1000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '审核备注', + `tenant_id` varchar(5) COLLATE utf8mb4_unicode_ci NOT NULL, + `parent_tenant_id` varchar(5) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `b_user_id` bigint(20) NOT NULL COMMENT 'merchant_b_user编号', + `wx_b_open_id` varchar(60) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'b端微信openId', + `audio_date` datetime DEFAULT NULL COMMENT '审批时间', + `merchant_id` bigint(20) NOT NULL COMMENT '提现商户', + `merchant_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '商户名称', + `recive_open_id` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '接收的openId', + `recive_nick_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '昵称', + `wx_pay_no` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '支付渠道支付编号', + `wx_pay_time` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '支付渠道支付时间', + `fail_remark` varchar(1000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '失败原因', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + + + + diff --git a/mallinkBApi/src/main/java/com/iformall/controller/WxCashOutController.java b/mallinkBApi/src/main/java/com/iformall/controller/WxCashOutController.java new file mode 100644 index 000000000..c6b953843 --- /dev/null +++ b/mallinkBApi/src/main/java/com/iformall/controller/WxCashOutController.java @@ -0,0 +1,62 @@ +package com.iformall.controller; + +import com.github.pagehelper.PageInfo; +import com.iformall.common.Result; +import com.iformall.common.ResultData; +import com.iformall.domain.po.*; +import com.iformall.enums.EnumPayWay; +import com.iformall.service.*; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/cashout") +@Api(description = "提现相关接口") +public class WxCashOutController extends BaseController { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired + private WxCashOutService wxCashOutService; + @Autowired + WxCUserService wxCUserService; + + @ApiOperation("分页列表接口") + @GetMapping("list") + @ApiImplicitParams({ + @ApiImplicitParam(name = "pageNum", value = "页数", dataType = "int", paramType = "query", required = true), + @ApiImplicitParam(name = "pageSize", value = "每页条数", dataType = "int", paramType = "query", required = true)}) + public ResultData list(@ModelAttribute WxCashOut wxCashOut, Integer pageNum, Integer pageSize) { + if (null == wxCashOut) wxCashOut = new WxCashOut(); + final PageInfo page = wxCashOutService.listAsPage(wxCashOut, pageNum, pageSize); + return new ResultData(page); + } + + @ApiOperation("微信B端提现") + @PostMapping("cashout") + public ResultData cashout(@RequestBody WxCashOut wxCashOut) { + WxMerchantBUser buser = getLoginBUser(); + //当前管理员的账户必须在c端授权了 + WxCUser cuUser = new WxCUser(); + cuUser.updateTenantInfo(buser); + cuUser.setPhone(buser.getPhone()); + try { + WxCUser mUser = wxCUserService.getByObject(cuUser); + if (mUser == null) { + return new ResultData(Result.ERROR,"请用绑定["+buser.getPhone()+"]的微信在C端完成授权手机号操作。"); + } + wxCashOut.setReciveOpenId(mUser.getOpenId()); + wxCashOut.setReciveNickName(mUser.getNickName()); + }catch(Exception e) { + return new ResultData(Result.ERROR,buser.getPhone()+"匹配C端用户失败,该情况可能是存在多个微信对应统一手机号,请联系管理员处理, 。"); + } + + return wxCashOutService.applyCashout(buser, wxCashOut,EnumPayWay.PAY_WAY_WECHAT); + } + +} diff --git a/mallinkMQConsumer/src/main/java/com/iformall/mq/MqBaseConsumer.java b/mallinkMQConsumer/src/main/java/com/iformall/mq/MqBaseConsumer.java index 6c8a9b04f..2210f544f 100644 --- a/mallinkMQConsumer/src/main/java/com/iformall/mq/MqBaseConsumer.java +++ b/mallinkMQConsumer/src/main/java/com/iformall/mq/MqBaseConsumer.java @@ -46,6 +46,9 @@ public class MqBaseConsumer { //@Autowired //UpdateCouponStockMsgServiceImpl updateCouponStockMsgService; + @Autowired + private FmInsideCashOutMsgServiceImpl fmInsideCashOutMsgService; + public void doMessage(String message) { @@ -103,6 +106,11 @@ public class MqBaseConsumer { FmInsideNotifyRefundSuccessMsg msg = (FmInsideNotifyRefundSuccessMsg)JsonUtil.readValue(message,FmInsideNotifyRefundSuccessMsg.class); fmInsideNotifyRefundSuccessMsgService.send(msg); } + else if(EnumMsgRecordType.CASH_OUT.getCode().equals(baseMsg.getMsgType())) { + // 内部消息 - 商户提现通知 + FmInsideCashOutMsg msg = (FmInsideCashOutMsg)JsonUtil.readValue(message,FmInsideCashOutMsg.class); + fmInsideCashOutMsgService.send(msg); + } //else if (EnumMsgRecordType.COUPON_STOCK.getCode().equals(baseMsg.getMsgType())) { // UpdateCouponStockMsg msg = (UpdateCouponStockMsg)JsonUtil.readValue(message,UpdateCouponStockMsg.class); // updateCouponStockMsgService.send(msg); diff --git a/mallinkService/src/main/java/com/iformall/config/BaseMyBatisConfiguration.java b/mallinkService/src/main/java/com/iformall/config/BaseMyBatisConfiguration.java index c3663f209..19f5d26d5 100644 --- a/mallinkService/src/main/java/com/iformall/config/BaseMyBatisConfiguration.java +++ b/mallinkService/src/main/java/com/iformall/config/BaseMyBatisConfiguration.java @@ -212,7 +212,12 @@ public class BaseMyBatisConfiguration { wxCarJSOrderSharding.setRule(EnumShardingRule.HASH.getCode()); shardingList.add(wxCarJSOrderSharding); - + ShardingSphere wxCashOutSharding = new ShardingSphere(); + wxCashOutSharding.setColumn("tenant_id"); + wxCashOutSharding.setTableName("wx_cash_out"); + wxCashOutSharding.setCount(100); + wxCashOutSharding.setRule(EnumShardingRule.HASH.getCode()); + shardingList.add(wxCashOutSharding); //初始化 shardingSpherePlugin.setShardingSpheres(shardingList); diff --git a/mallinkService/src/main/java/com/iformall/domain/po/WxCashOut.java b/mallinkService/src/main/java/com/iformall/domain/po/WxCashOut.java new file mode 100644 index 000000000..2b5a4f5c6 --- /dev/null +++ b/mallinkService/src/main/java/com/iformall/domain/po/WxCashOut.java @@ -0,0 +1,51 @@ +package com.iformall.domain.po; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.iformall.domain.po.base.TenantEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.Date; + +@TableName(value = "wx_cash_out") +@Data +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +public class WxCashOut extends TenantEntity { + + protected Long id; + + @io.swagger.annotations.ApiModelProperty(value = "总金额(分)", name = "totalFee") + private Integer totalFee; + @io.swagger.annotations.ApiModelProperty(value = "状态 0-提现中,1-提现成功,2-提现失败,3-提现拒绝", name = "status") + private Integer status; + @io.swagger.annotations.ApiModelProperty(value = "创建时间", name = "createDate") + private Date createDate; + @io.swagger.annotations.ApiModelProperty(value = "更新时间", name = "updateDate") + private Date updateDate; + @io.swagger.annotations.ApiModelProperty(value = "审核备注", name = "audioRemark") + private String audioRemark; + @io.swagger.annotations.ApiModelProperty(value = "merchant_b_user编号", name = "bUserId") + private Long bUserId; + @io.swagger.annotations.ApiModelProperty(value = "b端微信用户Id", name = "wxBOpenId") + private String wxBOpenId; + @io.swagger.annotations.ApiModelProperty(value = "审批时间", name = "audioDate") + private Date audioDate; + @io.swagger.annotations.ApiModelProperty(value = "提现商户", name = "merchantId") + private Long merchantId; + @io.swagger.annotations.ApiModelProperty(value = "商户名称", name = "merchantName") + private String merchantName; + @io.swagger.annotations.ApiModelProperty(value = "接收的openId", name = "reciveOpenId") + private String reciveOpenId; + @io.swagger.annotations.ApiModelProperty(value = "昵称", name = "reciveNickName") + private String reciveNickName; + @io.swagger.annotations.ApiModelProperty(value = "支付渠道支付编号", name = "wxPayNo") + private String wxPayNo; + @io.swagger.annotations.ApiModelProperty(value = "支付渠道支付时间", name = "wxPayTime") + private String wxPayTime; + @io.swagger.annotations.ApiModelProperty(value = "失败原因", name = "failRemark") + private String failRemark; + + +} diff --git a/mallinkService/src/main/java/com/iformall/domain/po/WxMall.java b/mallinkService/src/main/java/com/iformall/domain/po/WxMall.java index 5487fb3ec..02bb216c6 100644 --- a/mallinkService/src/main/java/com/iformall/domain/po/WxMall.java +++ b/mallinkService/src/main/java/com/iformall/domain/po/WxMall.java @@ -86,6 +86,9 @@ public class WxMall extends TenantEntity { private BigDecimal latitude; @io.swagger.annotations.ApiModelProperty(value="直播支持(0:不支持,1支持)",name="liveSupport") private Integer liveSupport; + @io.swagger.annotations.ApiModelProperty(value="是否支持商户提现(不开启分账才有效) 0-不支持 1-支持",name="cashOutSupport") + private Integer cashOutSupport; + @TableField(exist = false) protected List buildings; diff --git a/mallinkService/src/main/java/com/iformall/domain/po/WxMerchant.java b/mallinkService/src/main/java/com/iformall/domain/po/WxMerchant.java index a8fc1969b..77c9e2a7c 100644 --- a/mallinkService/src/main/java/com/iformall/domain/po/WxMerchant.java +++ b/mallinkService/src/main/java/com/iformall/domain/po/WxMerchant.java @@ -262,6 +262,9 @@ public class WxMerchant extends TenantEntity { @io.swagger.annotations.ApiModelProperty(value = "积分锁定0正常1锁定", name = "creditLocked") private Integer creditLocked; + + @io.swagger.annotations.ApiModelProperty(value = "商户待提现金额(分)", name = "cashOutNumber") + private Integer cashOutNumber; @TableField(exist = false) @SortColumn(column = "couponSale") diff --git a/mallinkService/src/main/java/com/iformall/domain/po/msg/FmInsideCashOutMsg.java b/mallinkService/src/main/java/com/iformall/domain/po/msg/FmInsideCashOutMsg.java new file mode 100644 index 000000000..1fbef216d --- /dev/null +++ b/mallinkService/src/main/java/com/iformall/domain/po/msg/FmInsideCashOutMsg.java @@ -0,0 +1,23 @@ +package com.iformall.domain.po.msg; + +import com.iformall.enums.EnumPayWay; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Data +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +public class FmInsideCashOutMsg extends BaseMsg{ + private static final long serialVersionUID = 1L; + + @io.swagger.annotations.ApiModelProperty(value = "租户ID", name = "tenantId") + private String tenantId; + @io.swagger.annotations.ApiModelProperty(value="父租户id",name="parentTenantId") + private String parentTenantId; + @io.swagger.annotations.ApiModelProperty(value = "提现ID", name = "cashOutId") + private Long cashOutId; + @io.swagger.annotations.ApiModelProperty(value = "支付方式", name = "payWay") + private EnumPayWay payWay; +} diff --git a/mallinkService/src/main/java/com/iformall/enums/EnumCashOut.java b/mallinkService/src/main/java/com/iformall/enums/EnumCashOut.java new file mode 100644 index 000000000..832b72313 --- /dev/null +++ b/mallinkService/src/main/java/com/iformall/enums/EnumCashOut.java @@ -0,0 +1,36 @@ +package com.iformall.enums; + +/** + * Created by Stormeye on 2018/08/09. + */ +public enum EnumCashOut { + + NO(0, "不持支商户提现"), + YES(1, "支持商户提现") + ; + + public static EnumCashOut getEnum(Integer code) { + for (EnumCashOut value : values()) { + if (value.getCode().equals(code)) { + return value; + } + } + return null; + } + + private Integer code; + private String message; + + EnumCashOut(Integer code, String message) { + this.code = code; + this.message = message; + } + + public Integer getCode() { + return code; + } + + public String getMessage() { + return message; + } +} diff --git a/mallinkService/src/main/java/com/iformall/enums/EnumCashOutStatus.java b/mallinkService/src/main/java/com/iformall/enums/EnumCashOutStatus.java new file mode 100644 index 000000000..aa894e83a --- /dev/null +++ b/mallinkService/src/main/java/com/iformall/enums/EnumCashOutStatus.java @@ -0,0 +1,40 @@ +package com.iformall.enums; + +/** + * Created by Stormeye on 2018/08/09. + */ +public enum EnumCashOutStatus { + //状态 0-提现中,1-提现成功,2-提现失败,3-提现拒绝 + DOING(0, "提现中"), + SUCCESS(1, "提现成功"), + FAIL(2, "提现失败"), + REJECT(3, "提现拒绝"), + AUDIOING(4, "审批中"), + WAITING(5, "待到账") + ; + + public static EnumCashOutStatus getEnum(Integer code) { + for (EnumCashOutStatus value : values()) { + if (value.getCode().equals(code)) { + return value; + } + } + return null; + } + + private Integer code; + private String message; + + EnumCashOutStatus(Integer code, String message) { + this.code = code; + this.message = message; + } + + public Integer getCode() { + return code; + } + + public String getMessage() { + return message; + } +} diff --git a/mallinkService/src/main/java/com/iformall/enums/EnumFlowKey.java b/mallinkService/src/main/java/com/iformall/enums/EnumFlowKey.java index 7e5081ff9..f56b31deb 100644 --- a/mallinkService/src/main/java/com/iformall/enums/EnumFlowKey.java +++ b/mallinkService/src/main/java/com/iformall/enums/EnumFlowKey.java @@ -34,6 +34,8 @@ public enum EnumFlowKey { NEW_RENT_POINT_CONTRACT_END(20, "多经点位商铺租金+物业合同终止"), SETTLE(21,"结算单审批"), + + CASH_OUT(23,"商户提现审批") ; public static EnumFlowKey getEnum(Integer code) { diff --git a/mallinkService/src/main/java/com/iformall/enums/EnumMsgRecordType.java b/mallinkService/src/main/java/com/iformall/enums/EnumMsgRecordType.java index 1d7181d26..3e7b81873 100644 --- a/mallinkService/src/main/java/com/iformall/enums/EnumMsgRecordType.java +++ b/mallinkService/src/main/java/com/iformall/enums/EnumMsgRecordType.java @@ -19,6 +19,7 @@ public enum EnumMsgRecordType { //INSIDE_NOTIFY_PAY_SUCCESS(103, "微信支付回调"), INSIDE_NOTIFY_REFUND_SUCCESS(104, "微信退款回调"), COUPON_STOCK(105, "券库存更新"), + CASH_OUT(106,"商户提现") ; public static EnumMsgRecordType getEnum(Integer code) { diff --git a/mallinkService/src/main/java/com/iformall/mapper/WxCashOutMapper.java b/mallinkService/src/main/java/com/iformall/mapper/WxCashOutMapper.java new file mode 100644 index 000000000..092a73532 --- /dev/null +++ b/mallinkService/src/main/java/com/iformall/mapper/WxCashOutMapper.java @@ -0,0 +1,18 @@ +package com.iformall.mapper; + +import com.iformall.common.CommonMapper; +import com.iformall.domain.po.WxCashOut; + +import java.util.List; + +import org.apache.ibatis.annotations.Param; + +public interface WxCashOutMapper extends CommonMapper { + + WxCashOut selectById(@Param("id")Long id,@Param("tenantId")String tenantId); + + List findList(WxCashOut wxCashOut); + + int updateStatus(WxCashOut wxCashOut); + +} diff --git a/mallinkService/src/main/java/com/iformall/mapper/WxMerchantMapper.java b/mallinkService/src/main/java/com/iformall/mapper/WxMerchantMapper.java index 51006f137..1ddd06fa8 100644 --- a/mallinkService/src/main/java/com/iformall/mapper/WxMerchantMapper.java +++ b/mallinkService/src/main/java/com/iformall/mapper/WxMerchantMapper.java @@ -41,4 +41,8 @@ public interface WxMerchantMapper extends CommonMapper { List findListVo2(WxMerchant record); List getMerchantList(WxMerchant wxMerchant); + + void increaseCash(WxMerchant record); + + void reduceCash(WxMerchant record); } diff --git a/mallinkService/src/main/java/com/iformall/pay/WxCashOutP.java b/mallinkService/src/main/java/com/iformall/pay/WxCashOutP.java new file mode 100644 index 000000000..a52300921 --- /dev/null +++ b/mallinkService/src/main/java/com/iformall/pay/WxCashOutP.java @@ -0,0 +1,37 @@ +package com.iformall.pay; + +import lombok.Data; + +/** + * Created by Stormeye on 2018/8/10. + */ +@Data +public class WxCashOutP { + private String mch_appid; // 申请商户号的appid或商户号绑定的appid + private String mchid; // 商户号 + private String nonce_str; // 随机字符串 + private String sign; // 签名 + private String partner_trade_no; // 商户订单号,需保持唯一性 (只能是字母或者数字,不能包含有其它字符) + private String openid; // 商户appid下,某用户的openid + private String check_name; // NO_CHECK:不校验真实姓名 FORCE_CHECK:强校验真实姓名 + private String re_user_name; // 收款用户真实姓名。 如果check_name设置为FORCE_CHECK,则必填用户真实姓名 如需电子回单,需要传入收款用户姓名 + private Integer amount; // 企业付款金额,单位为分 + private String desc; // 企业付款备注,必填 + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("WxCashOutP{"); + sb.append("mch_appid='").append(mch_appid).append('\''); + sb.append(", mch_id='").append(mchid).append('\''); + sb.append(", nonce_str='").append(nonce_str).append('\''); + sb.append(", sign='").append(sign).append('\''); + sb.append(", partner_trade_no='").append(partner_trade_no).append('\''); + sb.append(", openid='").append(openid).append('\''); + sb.append(", check_name=").append(check_name).append('\''); + sb.append(", re_user_name=").append(re_user_name).append('\''); + sb.append(", amount=").append(amount).append('\''); + sb.append(", desc=").append(desc).append('\''); + sb.append('}'); + return sb.toString(); + } +} diff --git a/mallinkService/src/main/java/com/iformall/pay/WxPay.java b/mallinkService/src/main/java/com/iformall/pay/WxPay.java index 4ccc544d3..7f58d9256 100644 --- a/mallinkService/src/main/java/com/iformall/pay/WxPay.java +++ b/mallinkService/src/main/java/com/iformall/pay/WxPay.java @@ -212,7 +212,7 @@ public class WxPay { /** * 企业付款 - * + * https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2 * @param params * 请求参数 * @param certPath @@ -227,7 +227,7 @@ public class WxPay { /** * 查询企业付款 - * + * https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_3 * @param params * 请求参数 * @param certPath diff --git a/mallinkService/src/main/java/com/iformall/service/WxCashOutService.java b/mallinkService/src/main/java/com/iformall/service/WxCashOutService.java new file mode 100644 index 000000000..69c89e36e --- /dev/null +++ b/mallinkService/src/main/java/com/iformall/service/WxCashOutService.java @@ -0,0 +1,45 @@ +package com.iformall.service; + +import com.github.pagehelper.PageInfo; +import com.iformall.common.ResultData; +import com.iformall.domain.po.*; +import com.iformall.domain.po.base.TenantEntity; +import com.iformall.enums.EnumPayWay; + +public interface WxCashOutService { + + /** + * 根据实体查询分页列表 + * + * @param record + * @param pageIndex + * @param pageSize + * @return + */ + PageInfo listAsPage(WxCashOut record, Integer pageIndex, Integer pageSize); + + /** + * 根据Id获得实体 + * + * @param id + * @return + */ + WxCashOut getById(Long id,String tenantId); + + /** + *更新状态 + * + * @param record + */ + void updateStatus(WxCashOut record); + + void createCashOut(WxCashOut record); + + ResultData applyCashout(WxMerchantBUser buser,WxCashOut record,EnumPayWay payWay); + + public void sendCashOutMsg(TenantEntity tenantEntity, Long cashOutId,EnumPayWay payWay); + + void rejectCashOut(WxCashOut record); + + void cashOutToWx(EnumPayWay payWay,TenantEntity tenantEntity, Long cashOutId); +} diff --git a/mallinkService/src/main/java/com/iformall/service/impl/WxCashOutServiceImpl.java b/mallinkService/src/main/java/com/iformall/service/impl/WxCashOutServiceImpl.java new file mode 100644 index 000000000..5c0f667fc --- /dev/null +++ b/mallinkService/src/main/java/com/iformall/service/impl/WxCashOutServiceImpl.java @@ -0,0 +1,243 @@ +package com.iformall.service.impl; + +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.iformall.common.ErrorCode; +import com.iformall.common.IdWorker; +import com.iformall.common.Result; +import com.iformall.common.ResultData; +import com.iformall.domain.po.*; +import com.iformall.domain.po.base.TenantEntity; +import com.iformall.domain.po.msg.FmInsideCashOutMsg; +import com.iformall.domain.po.msg.FmInsideCouponVerifyMsg; +import com.iformall.enums.EnumCashOut; +import com.iformall.enums.EnumCashOutStatus; +import com.iformall.enums.EnumFlowKey; +import com.iformall.enums.EnumMsgMqKey; +import com.iformall.enums.EnumMsgMqTag; +import com.iformall.enums.EnumMsgMqTopic; +import com.iformall.enums.EnumMsgRecordType; +import com.iformall.enums.EnumPayWay; +import com.iformall.mapper.*; +import com.iformall.mq.MqBaseProducer; +import com.iformall.service.*; +import com.iformall.service.pay.PayServiceFactory; +import com.iformall.service.pay.service.cashout.entity.CashOutAdapterResult; +import com.iformall.utils.RedisLock; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class WxCashOutServiceImpl implements WxCashOutService { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired + WxCashOutMapper wxCashOutMapper; + @Autowired + WxMerchantMapper wxMerchantMapper; + @Autowired + WxMallMapper wxMallMapper; + @Autowired + RedisLock redisLock; + @Autowired + MqBaseProducer mqBaseProducer; + @Autowired + private WxFlowService wxFlowService; + @Autowired + PayServiceFactory payServiceFactory; + @Autowired + WxAppinfoService wxAppinfoService; + @Autowired + WxPayAccountService wxPayAccountService; + + @Override + public PageInfo listAsPage(WxCashOut record, Integer pageIndex, Integer pageSize) { + return PageHelper.startPage(pageIndex, pageSize).doSelectPageInfo(() -> wxCashOutMapper.findList(record)); + } + + @Override + public WxCashOut getById(Long id,String tenantId) { + return wxCashOutMapper.selectById(id,tenantId); + } + + @Override + public void updateStatus(WxCashOut record) { + wxCashOutMapper.updateStatus(record); + } + + @Override + public void createCashOut(WxCashOut record) { + final IdWorker idWorker = IdWorker.get(); + record.setId(idWorker.nextId()); + wxCashOutMapper.insert(record); + } + + @Override + @Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = {Exception.class}) + public ResultData applyCashout(WxMerchantBUser buser, WxCashOut record,EnumPayWay payWay) { + WxMall mall = wxMallMapper.getByTenantId(buser.getTenantId()); + if (EnumCashOut.NO.getCode().intValue() == mall.getCashOutSupport().intValue()) { + return new ResultData(Result.ERROR,"当前租户不支持商户提现。"); + } + //只有商户管理员才能进行此操作 + WxMerchant merchant = wxMerchantMapper.selectById(buser.getMerchantId()); + if (!(buser.getPhone().equals(merchant.getLinkPhone()))) { + return new ResultData(Result.ERROR,"当前账号非商户管理员,不能进行此操作。"); + } + if (record.getTotalFee().intValue()<=0) { + return new ResultData(Result.ERROR,"提现金额非法。"); + } + if (record.getTotalFee().intValue()> merchant.getCashOutNumber().intValue()) { + return new ResultData(Result.ERROR,"提现金额超出商户余额。"); + } + if (StringUtils.isBlank(record.getReciveOpenId())) { + return new ResultData(Result.ERROR,"收取人第三方openId不能为空。"); + } + + //扣掉账户金额 + //此处需要加锁,防止并发设置 + long time = System.currentTimeMillis() + RedisLock.TIMEOUT; + String timeStr = String.valueOf(time); + boolean cashsetlock = redisLock.lock("merchantLockReduceCashSet_"+merchant.getId(), timeStr); + if (cashsetlock) { + try { + merchant.setCashOutNumber(record.getTotalFee()); + wxMerchantMapper.reduceCash(merchant); + redisLock.unlock("merchantLockReduceCashSet_"+merchant.getId(), timeStr); + }catch(Exception e) { + redisLock.unlock("merchantLockReduceCashSet_"+merchant.getId(), timeStr); + logger.error("update cashout fail.",e); + }finally { + redisLock.unlock("merchantLockReduceCashSet_"+merchant.getId(), timeStr); + } + } + //记录提现记录 + WxCashOut cashout = new WxCashOut(); + final IdWorker idWorker = IdWorker.get(); + cashout.setId(idWorker.nextId()); + cashout.setTotalFee(record.getTotalFee()); + cashout.setStatus(EnumCashOutStatus.DOING.getCode()); + cashout.setCreateDate(new Date()); + cashout.setBUserId(buser.getId()); + cashout.setWxBOpenId(String.valueOf(buser.getBuserId())); + cashout.setMerchantId(merchant.getId()); + cashout.setMerchantName(merchant.getName()); + cashout.setReciveOpenId(record.getReciveOpenId()); + cashout.setReciveNickName(record.getReciveNickName()); + cashout.updateTenantInfo(buser); + wxCashOutMapper.insert(cashout); + + //TODO有没有审批 + boolean hasAudit = hasWorkFlow(buser); + if (hasAudit) { + //提交审批 + Map map = new HashMap(); + map.put("businessId", String.valueOf(cashout.getId())); + map.put("businessType", EnumFlowKey.CASH_OUT.getCode()); + map.put("phone", buser.getPhone()); + List variablesList = new ArrayList(); + Map tenantIdMap = new HashMap(); + tenantIdMap.put("key","cashTenantId"); + tenantIdMap.put("value",String.valueOf(buser.getTenantId())); + variablesList.add(tenantIdMap); + + Map payWayMap = new HashMap(); + payWayMap.put("key","cashPayWay"); + payWayMap.put("value",String.valueOf(payWay.getCode())); + variablesList.add(payWayMap); + + map.put("variables", variablesList); + wxFlowService.start(map, buser.getId(), buser.getName(), buser); + return new ResultData(Result.SUCCESS,"提现提交审批成功"); + }else { + //没有审批则发送消息 + sendCashOutMsg(merchant, cashout.getId(),payWay); + } + + + return new ResultData("提现提交成功。"); + } + + protected boolean hasWorkFlow(TenantEntity tenantEntity) { + WxFlowModel wxFlowModel = new WxFlowModel(); + wxFlowModel.setFlowType(EnumFlowKey.CASH_OUT.getCode()); + wxFlowModel.updateTenantInfo(tenantEntity); + List flows = wxFlowService.getModelBybusiness(wxFlowModel); + if (null != flows && flows.size() > 0) { + return true; + } + return false; + } + + @Override + public void sendCashOutMsg(TenantEntity tenantEntity, Long cashOutId,EnumPayWay payWay) { + FmInsideCashOutMsg verifyMsg = new FmInsideCashOutMsg(); + verifyMsg.setMsgType(EnumMsgRecordType.CASH_OUT.getCode()); + verifyMsg.updateTenantInfo(tenantEntity); + verifyMsg.setCashOutId(cashOutId); + verifyMsg.setPayWay(payWay); + mqBaseProducer.sendMessage(verifyMsg, EnumMsgMqTopic.DEFAULT.getCode(), EnumMsgMqTag.DEFAULT.getCode(), EnumMsgMqKey.DEFAULT.getCode()); + } + + @Override + @Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = {Exception.class}) + public void rejectCashOut(WxCashOut record) { + //更新状态,并且把钱数退回账户 + wxCashOutMapper.updateStatus(record); + WxMerchant merchant = wxMerchantMapper.selectById(record.getMerchantId()); + //增加账户金额 + //此处需要加锁,防止并发设置 + long time = System.currentTimeMillis() + RedisLock.TIMEOUT; + String timeStr = String.valueOf(time); + boolean cashsetlock = redisLock.lock("merchantLockRejectCashSet_"+merchant.getId(), timeStr); + if (cashsetlock) { + try { + merchant.setCashOutNumber(record.getTotalFee()); + wxMerchantMapper.increaseCash(merchant); + redisLock.unlock("merchantLockRejectCashSet_"+merchant.getId(), timeStr); + }catch(Exception e) { + redisLock.unlock("merchantLockRejectCashSet_"+merchant.getId(), timeStr); + logger.error("update cashout fail.",e); + }finally { + redisLock.unlock("merchantLockRejectCashSet_"+merchant.getId(), timeStr); + } + } + } + + @Override + public void cashOutToWx(EnumPayWay payWay,TenantEntity tenantEntity, Long cashOutId) { + WxCashOut cashout = this.getById(cashOutId, tenantEntity.getTenantId()); + if (null == cashout ) { + return; + } + WxAppinfo appInfo = wxAppinfoService.getCAppInfo(tenantEntity, payWay); + if (null == appInfo) { + return; + } + WxPayAccount payAccount = wxPayAccountService.getByTenantId(tenantEntity.getTenantId()); + if (null == payAccount) { + return; + } + CashOutAdapterResult result = payServiceFactory.getCashOutAdapterService(payWay.getCode()).cashOut(appInfo, payAccount, cashout); + cashout.setUpdateDate(new Date()); + if (result.isSuccess()) { + cashout.setStatus(EnumCashOutStatus.SUCCESS.getCode()); + }else { + cashout.setStatus(EnumCashOutStatus.FAIL.getCode()); + cashout.setFailRemark(result.getMsg()); + } + wxCashOutMapper.updateById(cashout); + } +} diff --git a/mallinkService/src/main/java/com/iformall/service/impl/WxCouponOrderServiceImpl.java b/mallinkService/src/main/java/com/iformall/service/impl/WxCouponOrderServiceImpl.java index 4fc8560ab..e22062d7d 100644 --- a/mallinkService/src/main/java/com/iformall/service/impl/WxCouponOrderServiceImpl.java +++ b/mallinkService/src/main/java/com/iformall/service/impl/WxCouponOrderServiceImpl.java @@ -29,6 +29,8 @@ import com.iformall.service.pay.PayServiceFactory; import com.iformall.utils.Constant; import com.iformall.utils.DateUtils; import com.iformall.utils.PayUtils; +import com.iformall.utils.RedisLock; + import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -144,6 +146,9 @@ public class WxCouponOrderServiceImpl implements WxCouponOrderService { @Autowired @Qualifier("couponDetailRedisTemplate") RedisTemplate cdRedisTemplate; + + @Autowired + RedisLock redisLock; private final SimpleDateFormat mydateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @@ -524,18 +529,42 @@ public class WxCouponOrderServiceImpl implements WxCouponOrderService { try { // 核销记账 if (couponOrder.getCouponPrice() > 0) { - recordAfterVerified(couponOrder, bUser.getMerchantId()); + WxMerchantSubsidy subsidy = recordAfterVerified(couponOrder, bUser.getMerchantId()); + + //是否开通了商户提现,开通了商户提现往商户账户里面加金额 + WxMall mall = wxMallMapper.getByTenantId(couponOrder.getTenantId()); + if (null != mall && mall.getCashOutSupport().intValue() == EnumCashOut.YES.getCode().intValue()) { + WxMerchant merchant = wxMerchantMapper.selectById(bUser.getMerchantId()); + if (null != merchant) { + //此处需要加锁,防止并发设置 + long time = System.currentTimeMillis() + RedisLock.TIMEOUT; + String timeStr = String.valueOf(time); + boolean cashsetlock = redisLock.lock("merchantLockCashSet_"+merchant.getId(), timeStr); + if (cashsetlock) { + try { + merchant.setCashOutNumber(couponOrder.getCouponPrice()); + wxMerchantMapper.increaseCash(merchant); + logger.info("wxcoupon update cashout success.wxCouponOrderId:["+couponOrder.getId()+"] subsidyId:["+subsidy.getId()+"]."); + redisLock.unlock("merchantLockCashSet_"+merchant.getId(), timeStr); + }catch(Exception e) { + redisLock.unlock("merchantLockCashSet_"+merchant.getId(), timeStr); + logger.error("update cashout fail.wxCouponOrderId:["+couponOrder.getId()+"] subsidyId:["+subsidy.getId()+"].",e); + }finally { + redisLock.unlock("merchantLockCashSet_"+merchant.getId(), timeStr); + } + } + } + } } } catch (Exception e) { - logger.info("核销记账失败"); - logger.error(e.getMessage()); + logger.error("核销记账失败"+e.getMessage(),e); } } else { try { // 核销分账 shareAfterVerify(couponOrder, bUser.getMerchantId(),couponOrder.getPayVendor()); } catch (Exception e) { - logger.error(e.getMessage()); + logger.error("核销记账失败"+e.getMessage(),e); } } @@ -543,7 +572,7 @@ public class WxCouponOrderServiceImpl implements WxCouponOrderService { // 核销补贴 subsidyAfterVerify(couponOrder, bUser.getMerchantId()); } catch (Exception e) { - logger.error(e.getMessage()); + logger.error("核销记账失败"+e.getMessage(),e); } boolean bSentCoupon = false; @@ -552,18 +581,18 @@ public class WxCouponOrderServiceImpl implements WxCouponOrderService { try { bSentCoupon = sendCouponAfterVerify(couponOrder, bUser); } catch (Exception e) { - logger.error(e.getMessage()); + logger.error("核销记账失败"+e.getMessage(),e); } // 核销发模板消息 try { sendVerifyMsg(couponOrder, bUser, bSentCoupon); } catch (Exception e) { - logger.error("核销发送消息发生错误:" + e.getMessage()); + logger.error("核销发送消息发生错误:" + e.getMessage(),e); } } - public void recordAfterVerified(WxCouponOrder couponOrder, Long merchantId) { + public WxMerchantSubsidy recordAfterVerified(WxCouponOrder couponOrder, Long merchantId) { WxCoupon wxCoupon = wxCouponMapper.selectById(couponOrder.getCouponId(),couponOrder.getTenantId()); WxMerchantSubsidy merchantSubsidy = new WxMerchantSubsidy(); if (couponOrder.getCouponType().equals(EnumCouponType.COUPON_PRESS.getCode()) @@ -597,6 +626,7 @@ public class WxCouponOrderServiceImpl implements WxCouponOrderService { merchantSubsidy.setUpdateDate(curDate); merchantSubsidy.setStatus(EnumMerchantSubsidyStatus.NOT_SUBSIDY.getCode()); wxMerchantSubsidyMapper.insert(merchantSubsidy); + return merchantSubsidy; } @Override diff --git a/mallinkService/src/main/java/com/iformall/service/impl/WxFlowServiceImpl.java b/mallinkService/src/main/java/com/iformall/service/impl/WxFlowServiceImpl.java index 67031b8a0..03971a517 100644 --- a/mallinkService/src/main/java/com/iformall/service/impl/WxFlowServiceImpl.java +++ b/mallinkService/src/main/java/com/iformall/service/impl/WxFlowServiceImpl.java @@ -99,6 +99,8 @@ public class WxFlowServiceImpl implements WxFlowService { @Autowired @Qualifier("couponChannelRedisTemplate") RedisTemplate> cdRedisTemplate; + @Autowired + private WxCashOutService wxCashOutService; @Override public void wxFlowConfigInit(String tenantId) { @@ -434,6 +436,34 @@ public class WxFlowServiceImpl implements WxFlowService { wxBillSettleService.updateFreezeOrStatus(settle.getPayBillIds(),EnumFreezeType.DEF.getCode(),null); } wxBillSettleMapper.updateById(wxBillSettle); + }else if (EnumFlowKey.CASH_OUT.getCode().equals(flowType)) { + String tenantId = (String)getVariableByKey(variables,"cashTenantId"); + WxCashOut cashout = wxCashOutService.getById(Long.parseLong(businessId), tenantId); + + //如果是提交审批 + if (EnumRentContractAppStatus.APPLYING.getCode().intValue() == applyStatus.intValue()) { + cashout.setStatus(EnumCashOutStatus.AUDIOING.getCode()); + cashout.setUpdateDate(new Date()); + wxCashOutService.updateStatus(cashout); + // 审批完成 + }else if (EnumRentContractAppStatus.FINISH.getCode().intValue() == applyStatus.intValue()) { + cashout.setStatus(EnumCashOutStatus.WAITING.getCode()); + cashout.setUpdateDate(new Date()); + wxCashOutService.updateStatus(cashout); + //发送零钱提现通知 + String cashPayWay = (String)getVariableByKey(variables,"cashPayWay"); + wxCashOutService.sendCashOutMsg(cashout, cashout.getId(),EnumPayWay.getEnum(Integer.parseInt(cashPayWay))); + logger.info("cashout msg:"+cashout.getId()); + //如果是审批撤回,或者驳回,改回草稿状态 + }else if (EnumRentContractAppStatus.SETBACK.getCode().intValue() == applyStatus.intValue() + || EnumRentContractAppStatus.REJECT.getCode().intValue() == applyStatus.intValue()) { + cashout.setStatus(EnumCashOutStatus.REJECT.getCode()); + String remark = (String)mapInfo.get("remark"); + cashout.setAudioRemark(remark); + cashout.setAudioDate(new Date()); + cashout.setUpdateDate(new Date()); + wxCashOutService.rejectCashOut(cashout); + } } } diff --git a/mallinkService/src/main/java/com/iformall/service/msg/impl/FmInsideCashOutMsgServiceImpl.java b/mallinkService/src/main/java/com/iformall/service/msg/impl/FmInsideCashOutMsgServiceImpl.java new file mode 100644 index 000000000..bfc30f9e1 --- /dev/null +++ b/mallinkService/src/main/java/com/iformall/service/msg/impl/FmInsideCashOutMsgServiceImpl.java @@ -0,0 +1,29 @@ +package com.iformall.service.msg.impl; + +import com.iformall.domain.po.base.TenantEntity; +import com.iformall.domain.po.msg.BaseMsg; +import com.iformall.domain.po.msg.FmInsideCashOutMsg; +import com.iformall.service.WxCashOutService; +import com.iformall.service.msg.MsgSendService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + + +@Service +public class FmInsideCashOutMsgServiceImpl implements MsgSendService { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired + private WxCashOutService wxCashOutService; + + @Override + public void send(BaseMsg baseMsg) throws Exception { + FmInsideCashOutMsg msg = (FmInsideCashOutMsg)baseMsg; + TenantEntity tenantEntity = new TenantEntity(); + tenantEntity.setTenantId(msg.getTenantId()); + tenantEntity.setParentTenantId(msg.getParentTenantId()); + wxCashOutService.cashOutToWx(msg.getPayWay(),tenantEntity, msg.getCashOutId()); + } +} diff --git a/mallinkService/src/main/java/com/iformall/service/park/impl/cyf/CYFParkService.java b/mallinkService/src/main/java/com/iformall/service/park/impl/cyf/CYFParkService.java index 34dcf2426..e9d8357f0 100644 --- a/mallinkService/src/main/java/com/iformall/service/park/impl/cyf/CYFParkService.java +++ b/mallinkService/src/main/java/com/iformall/service/park/impl/cyf/CYFParkService.java @@ -134,7 +134,7 @@ public class CYFParkService extends BaseParkService implements ParkAdapterServic Integer endTime = retObj.getInteger("endTime");//离场时间 String msg = retObj.getString("warmPrompt"); String appId = ""; - String payPath = ""; + String payPath = "http://wechat.cheyifu2016.com/fm-pay/#/transit?encodeURIComponent('orderNo=123&couponFee=2&actualFee=20')"; return new ResultData(new ParkStopFee(retObj.getString("orderId"), cyf.utcToLocal(String.valueOf(createTime)), cyf.utcToLocal(String.valueOf(endTime)), String.valueOf(retObj.getDouble("fee")),appId,payPath,null,msg)); }else { diff --git a/mallinkService/src/main/java/com/iformall/service/pay/PayServiceFactory.java b/mallinkService/src/main/java/com/iformall/service/pay/PayServiceFactory.java index d3bd118cc..e9584bb40 100644 --- a/mallinkService/src/main/java/com/iformall/service/pay/PayServiceFactory.java +++ b/mallinkService/src/main/java/com/iformall/service/pay/PayServiceFactory.java @@ -8,6 +8,8 @@ import org.springframework.stereotype.Service; import com.iformall.common.ErrorCode; import com.iformall.enums.EnumPayWay; import com.iformall.exception.MallinkException; +import com.iformall.service.pay.service.cashout.CashOutAdapterService; +import com.iformall.service.pay.service.cashout.wx.WxCashOutAdapterService; import com.iformall.service.pay.service.pay.CDrivingPayService; import com.iformall.service.pay.service.pay.CPassivePayService; import com.iformall.service.pay.service.pay.PayAdapterService; @@ -29,6 +31,7 @@ public class PayServiceFactory { private Map serviceMap = null; private Map shareMap = null; private Map refundMap = null; + private Map cashoutMap = null; @Autowired WxMiniAppPayAdapterService wxMiniAppPayService; @@ -45,6 +48,9 @@ public class PayServiceFactory { @Autowired WxRefundAdapterService wxRefundService; + @Autowired + WxCashOutAdapterService WxCashOutService; + private Map getServiceMap() { @@ -83,7 +89,16 @@ public class PayServiceFactory { refundMap.put(EnumPayWay.PAY_WAY_WECHAT_MA.getCode(), wxRefundService); } return refundMap; - } + } + + private Map getCashOutMap() { + if (null == cashoutMap ) { + cashoutMap = new ConcurrentHashMap(); + cashoutMap.put(EnumPayWay.PAY_WAY_WECHAT.getCode(), WxCashOutService); + cashoutMap.put(EnumPayWay.PAY_WAY_WECHAT_MA.getCode(), WxCashOutService); + } + return cashoutMap; + } public PayAdapterService getPayAdapterService(Integer type) throws MallinkException{ PayAdapterService service = getServiceMap().get(type); @@ -132,4 +147,12 @@ public class PayServiceFactory { } return refundService; } + + public CashOutAdapterService getCashOutAdapterService(Integer type)throws MallinkException { + CashOutAdapterService cashOutService = getCashOutMap().get(type); + if (null == cashOutService) { + throw new MallinkException(ErrorCode.SYS_SERVER_ERROR.getCode(),"payWay["+type+"] 零钱service未找到"); + } + return cashOutService; + } } diff --git a/mallinkService/src/main/java/com/iformall/service/pay/service/cashout/CashOutAdapterService.java b/mallinkService/src/main/java/com/iformall/service/pay/service/cashout/CashOutAdapterService.java new file mode 100644 index 000000000..dcb9d87ec --- /dev/null +++ b/mallinkService/src/main/java/com/iformall/service/pay/service/cashout/CashOutAdapterService.java @@ -0,0 +1,37 @@ +package com.iformall.service.pay.service.cashout; + +import java.util.Map; + +import com.alibaba.fastjson.JSONArray; +import com.iformall.domain.po.WxAppinfo; +import com.iformall.domain.po.WxCashOut; +import com.iformall.domain.po.WxPayAccount; +import com.iformall.domain.po.WxProfitSharingOrder; +import com.iformall.domain.po.WxProfitSharingReceiver; +import com.iformall.enums.EnumPayWay; +import com.iformall.enums.EnumProfitSharingOrderType; +import com.iformall.enums.EnumProfitSharingReceiverType; +import com.iformall.exception.MallinkException; +import com.iformall.service.pay.service.share.entity.ShareAccountResult; +import com.iformall.service.pay.service.cashout.entity.CashOutAdapterResult; +import com.iformall.service.pay.service.share.entity.PayShareQueryResult; +import com.iformall.service.pay.service.share.entity.PayShareResult; +import com.iformall.service.pay.service.share.entity.ShareNotifyAdapterResult; + +/** + * 零钱接口 + * @author alascor + */ +public interface CashOutAdapterService { + + /** + * 付款到零钱 + * @param appInfo + * @param payAccount + * @param record + * @param transcationId + * @param amount 分账金额 + * @return + */ + public CashOutAdapterResult cashOut(WxAppinfo appInfo,WxPayAccount payAccount,WxCashOut cashOut); +} diff --git a/mallinkService/src/main/java/com/iformall/service/pay/service/cashout/entity/CashOutAdapterResult.java b/mallinkService/src/main/java/com/iformall/service/pay/service/cashout/entity/CashOutAdapterResult.java new file mode 100644 index 000000000..6d81ada32 --- /dev/null +++ b/mallinkService/src/main/java/com/iformall/service/pay/service/cashout/entity/CashOutAdapterResult.java @@ -0,0 +1,51 @@ +package com.iformall.service.pay.service.cashout.entity; + +import java.io.Serializable; + +public class CashOutAdapterResult implements Serializable{ + private static final long serialVersionUID = -6647306162758854293L; + private boolean isSuccess; + private Integer code; + private String msg; + private Object data; + + + public CashOutAdapterResult() { + + } + + public CashOutAdapterResult(boolean isSuccess,Integer code,String msg,Object data) { + this.isSuccess = isSuccess; + this.code = code; + this.msg = msg; + this.data = data; + } + + public boolean isSuccess() { + return isSuccess; + } + public void setSuccess(boolean isSuccess) { + this.isSuccess = isSuccess; + } + public String getMsg() { + return msg; + } + public void setMsg(String msg) { + this.msg = msg; + } + public Object getData() { + return data; + } + public void setData(Object data) { + this.data = data; + } + + public Integer getCode() { + return code; + } + + public void setCode(Integer code) { + this.code = code; + } + +} diff --git a/mallinkService/src/main/java/com/iformall/service/pay/service/cashout/wx/WxCashOutAdapterService.java b/mallinkService/src/main/java/com/iformall/service/pay/service/cashout/wx/WxCashOutAdapterService.java new file mode 100644 index 000000000..f44f9a1f4 --- /dev/null +++ b/mallinkService/src/main/java/com/iformall/service/pay/service/cashout/wx/WxCashOutAdapterService.java @@ -0,0 +1,216 @@ +package com.iformall.service.pay.service.cashout.wx; + +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.iformall.common.ErrorCode; +import com.iformall.common.Result; +import com.iformall.common.ResultData; +import com.iformall.domain.po.WxAppinfo; +import com.iformall.domain.po.WxCashOut; +import com.iformall.domain.po.WxPayAccount; +import com.iformall.domain.po.WxPayOrder; +import com.iformall.domain.po.WxRefundOrder; +import com.iformall.enums.EnumCashOutStatus; +import com.iformall.enums.EnumPayMode; +import com.iformall.enums.EnumPayType; +import com.iformall.enums.EnumPayWay; +import com.iformall.enums.EnumRefundStatus; +import com.iformall.exception.MallinkException; +import com.iformall.mapper.WxAppinfoMapper; +import com.iformall.mapper.WxPayAccountMapper; +import com.iformall.mapper.WxPayOrderMapper; +import com.iformall.mapper.WxRefundOrderMapper; +import com.iformall.pay.WxCashOutP; +import com.iformall.pay.WxPay; +import com.iformall.pay.WxPayment; +import com.iformall.pay.WxRefundOrderP; +import com.iformall.pay.WxRefundOrderSP; +import com.iformall.service.pay.service.cashout.CashOutAdapterService; +import com.iformall.service.pay.service.cashout.entity.CashOutAdapterResult; +import com.iformall.service.pay.service.refund.RefundPayAdapterService; +import com.iformall.service.pay.service.refund.entity.RefundAdapterResult; +import com.iformall.service.pay.service.refund.entity.RefundNotifyAdapterResult; +import com.iformall.utils.BeanUtils; +import com.iformall.utils.CipherUtil; +import com.iformall.utils.Utility; +import com.iformall.utils.XmlUtil; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +public class WxCashOutAdapterService implements CashOutAdapterService{ + + JSONObject errorRefundReqMap = JSON.parseObject("{\n" + + " \"NO_AUTH\": {\n" + + " \"detail\": \"没有该接口权限\",\n" + + " \"reason\": \"1. 用户账号被冻结,无法付款;2. 产品权限没有开通或者被风控冻结;3. 此IP地址不允许调用接口,如有需要请登录微信支付商户平台更改配置\",\n" + + " \"resolution\": \"请根据具体的错误返回描述做对应处理,如返回描述不够明确,请参考此处的错误原因做排查。\"\n" + + " },\n" + + " \"AMOUNT_LIMIT\": {\n" + + " \"detail\": \"金额超限\",\n" + + " \"reason\": \"1. 被微信风控拦截,最低单笔付款限额调整为5元。2. 低于最低单笔付款限额或者高于最高单笔付款限额\",\n" + + " \"resolution\": \"目前最低付款金额为1元,最高10万元,请确认是否付款金额超限。\"\n" + + " },\n" + + " \"PARAM_ERROR\": {\n" + + " \"detail\": \"参数错误\",\n" + + " \"reason\": \"1. 请求参数校验错误 2. 字符中包含非utf8字符 3. 商户号和appid没有绑定关系\\t\",\n" + + " \"resolution\": \"请参照原因检查您的请求参数是否正确\"\n" + + " },\n" + + " \"OPENID_ERROR\": {\n" + + " \"detail\": \"Openid错误\",\n" + + " \"reason\": \"Openid格式错误或者不属于商家公众账号\",\n" + + " \"resolution\": \"Openid与appid是有一一映射关系的,请确保正确使用。\"\n" + + " },\n" + + " \"SEND_FAILED\": {\n" + + " \"detail\": \"付款错误\",\n" + + " \"reason\": \"付款错误,请查单确认付款结果\",\n" + + " \"resolution\": \"请查单确认付款结果,以查单结果为准。\"\n" + + " },\n" + + " \"NOTENOUGH\": {\n" + + " \"detail\": \"余额不足\",\n" + + " \"reason\": \"您的付款帐号余额不足或资金未到账\\t\",\n" + + " \"resolution\": \"如果要继续付款必须使用原商户订单号重试\"\n" + + " },\n" + + " \"SYSTEMERROR\": {\n" + + " \"detail\": \"系统繁忙,请稍后再试\",\n" + + " \"reason\": \"微信内部接口调用发生错误\",\n" + + " \"resolution\": \"请先调用查询接口,查看此次付款结果,如结果为不明确状态(如订单号不存在),请务必使用原商户订单号进行重试。\"\n" + + " },\n" + + " \"NAME_MISMATCH\": {\n" + + " \"detail\": \"姓名校验出错\",\n" + + " \"reason\": \"收款人身份校验不通过\",\n" + + " \"resolution\": \"如果要继续付款必须使用原商户订单号重试\"\n" + + " },\n" + + " \"SIGN_ERROR\": {\n" + + " \"detail\": \"签名错误\",\n" + + " \"reason\": \"校验签名错误\",\n" + + " \"resolution\": \"请检查您的请求参数和签名密钥KEY是否正确,如果要继续付款必须使用原商户订单号重试\"\n" + + " },\n" + + " \"XML_ERROR\": {\n" + + " \"detail\": \"Post内容出错\",\n" + + " \"reason\": \"Post请求数据不是合法的xml格式内容\",\n" + + " \"resolution\": \"格式问题,请检查请求格式是否正确。\"\n" + + " },\n" + + " \"FATAL_ERROR\": {\n" + + " \"detail\": \"两次请求参数不一致\",\n" + + " \"reason\": \"两次请求商户单号一样,但是参数不一致\",\n" + + " \"resolution\": \"重入必须保证所有参数值都不变\"\n" + + " },\n" + + " \"FREQ_LIMIT\": {\n" + + " \"detail\": \"超过频率限制,请稍后再试\",\n" + + " \"reason\": \"接口请求频率超时接口限制\",\n" + + " \"resolution\": \"调用接口过于频繁,请稍后再试,如果要继续付款必须使用原商户订单号重试\"\n" + + " },\n" + + " \"MONEY_LIMIT\": {\n" + + " \"detail\": \"已经达到今日付款总额上限/已达到付款给此用户额度上限\",\n" + + " \"reason\": \"请关注接口的付款限额条件\",\n" + + " \"resolution\": \"付款额度已经超限,请参考接口使用条件,如果要继续付款必须使用原商户订单号重试\"\n" + + " },\n" + + " \"CA_ERROR\": {\n" + + " \"detail\": \"商户API证书校验出错\",\n" + + " \"reason\": \"请求没带商户API证书或者带上了错误的商户API证书\",\n" + + " \"resolution\": \"您使用的调用证书有误,请确认是否使用了正确的证书,可以前往商户平台重新下载,证书需与商户号对应,如果要继续付款必须使用原商户订单号重试\"\n" + + " },\n" + + " \"V2_ACCOUNT_SIMPLE_BAN\": {\n" + + " \"detail\": \"无法给未实名用户付款\",\n" + + " \"reason\": \"用户微信支付账户未实名,无法付款\",\n" + + " \"resolution\": \"不支持给未实名用户付款,如果要继续付款必须使用原商户订单号重试\"\n" + + " },\n" + + " \"PARAM_IS_NOT_UTF8\": {\n" + + " \"detail\": \"请求参数中包含非utf8编码字符\",\n" + + " \"reason\": \"接口规范要求所有请求参数都必须为utf8编码\",\n" + + " \"resolution\": \"微信接口使用编码是UTF-8,请确认,如果要继续付款必须使用原商户订单号重试\"\n" + + " },\n" + + " \"SENDNUM_LIMIT\": {\n" + + " \"detail\": \"该用户今日付款次数超过限制,如有需要请进入【微信支付商户平台-产品中心-企业付款到零钱-产品设置】进行修改\",\n" + + " \"reason\": \"该用户今日付款次数超过限制,如有需要请进入【微信支付商户平台-产品中心-企业付款到零钱-产品设置】进行修改\",\n" + + " \"resolution\": \"向用户付款的次数超限了,请参考接口使用条件,如果要继续付款必须使用原商户订单号重试\"\n" + + " },\n" + + " \"RECV_ACCOUNT_NOT_ALLOWED\": {\n" + + " \"detail\": \"收款账户不在收款账户列表\",\n" + + " \"reason\": \"收款账户不在收款账户列表\",\n" + + " \"resolution\": \"请登陆商户平台,查看产品中心企业付款到零钱的产品配置\"\n" + + " },\n" + + " \"PAY_CHANNEL_NOT_ALLOWED\": {\n" + + " \"detail\": \"本商户号未配置API发起能力\",\n" + + " \"reason\": \"本商户号未配置API发起能力\",\n" + + " \"resolution\": \"请登陆商户平台,查看产品中心企业付款到零钱的产品配置\"\n" + + " }\n" + + "}"); + + + @Override + public CashOutAdapterResult cashOut(WxAppinfo appInfo, WxPayAccount payAccount, WxCashOut cashOut) { + WxCashOutP wxCashOutP = generateWxCashOutP(payAccount, appInfo, cashOut); + Map signMap = null; + try { + signMap = BeanUtils.toStringMap(wxCashOutP); + } catch (Exception e) { + log.error("零钱支付命令生辰: " + e.getMessage(),e); + throw new MallinkException(ErrorCode.SYS_PARAMETER_CAST_ERROR.getCode(), "零钱支付签名异常"); + } + + String signAgent = WxPayment.createSign(signMap, payAccount.getApiKey()); + signMap.put("sign", signAgent); + String response = null; + try { + response = WxPay.transfers(signMap, payAccount.getCertPath(), payAccount.getMchId()); + } catch (Exception e) { + log.error("零钱支付异常: " + e.getMessage(),e); + throw new MallinkException(ErrorCode.REFUND_ORDER_ERROR.getCode(), "零钱支付异常"); + } + log.info("微信零钱支付:" + wxCashOutP.toString() + ", response: " + response.toString()); + return getReusltFromp(response,cashOut); + +// if (payAccount.getType() == EnumPayMode.MCH.getCode()) { + // 普通商户模式 +// } + } + + private WxCashOutP generateWxCashOutP(WxPayAccount payAccount,WxAppinfo appInfo,WxCashOut cashOut) { + String noncestr = Utility.generate32UUID(); + WxCashOutP wxCashOutP = new WxCashOutP(); + wxCashOutP.setMch_appid(appInfo.getAppId()); + wxCashOutP.setMchid(payAccount.getSubMchId()); + wxCashOutP.setNonce_str(noncestr); + wxCashOutP.setAmount(cashOut.getTotalFee()); + wxCashOutP.setCheck_name("NO_CHECK"); + wxCashOutP.setDesc("客户结算"); + wxCashOutP.setOpenid(cashOut.getReciveOpenId()); + wxCashOutP.setPartner_trade_no(String.valueOf(cashOut.getId())); + return wxCashOutP; + } + + private CashOutAdapterResult getReusltFromp(String response,WxCashOut cashOut) throws MallinkException{ + Map returnMap = WxPayment.xmlToMap(response); + String result_no = returnMap.get("result_code"); + if ("SUCCESS".equals(result_no)) { + log.info("微信零钱支付申请成功: " + returnMap.toString()); + String wxPayNo = returnMap.get("payment_no"); + String payTime = returnMap.get("payment_time"); + cashOut.setWxPayNo(wxPayNo); + cashOut.setWxPayTime(payTime); + return new CashOutAdapterResult(true,EnumCashOutStatus.SUCCESS.getCode(),"微信零钱支付申请成功",returnMap); + } else { + log.error("微信零钱支付申请失败: " + response); + String errMsg = ""; + JSONObject errObj = errorRefundReqMap.getJSONObject(result_no); + if (errObj != null) { + errMsg = errObj.toJSONString(); + } else { + errMsg = returnMap.get("err_code_des"); + } + return new CashOutAdapterResult(false, EnumCashOutStatus.FAIL.getCode(), errMsg, returnMap); + } + } +} diff --git a/mallinkService/src/main/resources/mapper/WxCashOutMapper.xml b/mallinkService/src/main/resources/mapper/WxCashOutMapper.xml new file mode 100644 index 000000000..f908fec71 --- /dev/null +++ b/mallinkService/src/main/resources/mapper/WxCashOutMapper.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + `id`,`tenant_id`,`parent_tenant_id`,`total_fee`,`status`,`audio_remark`,`create_date`,`update_date`,`b_user_id`,`wx_b_open_id`,`audio_date`, + `merchant_id`,`merchant_name`,`recive_open_id`,`reciveNickName`,`wx_pay_no`,`wx_pay_time`,`fail_remark` + + + + where 1 = 1 + + + and `id` = #{id} + + + + and `tenant_id` = #{tenantId} + + + and `parent_tenant_id` = #{parentTenantId} + + + + and `status` = #{status} + + + + and id in + + #{idItem} + + + order by ${sortColumns} + + + + + + + + update wx_cash_out + set `status` = #{status}, + `update_date` = #{updateDate} + where id=#{id} + + and `tenant_id` = #{tenantId} + + + and `parent_tenant_id` = #{parentTenantId} + + + + \ No newline at end of file diff --git a/mallinkService/src/main/resources/mapper/WxCouponOrderMapper.xml b/mallinkService/src/main/resources/mapper/WxCouponOrderMapper.xml index 4ce9544bb..2c51bb200 100644 --- a/mallinkService/src/main/resources/mapper/WxCouponOrderMapper.xml +++ b/mallinkService/src/main/resources/mapper/WxCouponOrderMapper.xml @@ -3,7 +3,7 @@ - + diff --git a/mallinkService/src/main/resources/mapper/WxMallMapper.xml b/mallinkService/src/main/resources/mapper/WxMallMapper.xml index 0c2af3589..2f19dad73 100644 --- a/mallinkService/src/main/resources/mapper/WxMallMapper.xml +++ b/mallinkService/src/main/resources/mapper/WxMallMapper.xml @@ -36,7 +36,7 @@ - + @@ -45,7 +45,7 @@ `img_url`,`img_url_h`, `weap_note`, `img_qrcode_weapp`, `img_qrcode_wemp`, `weapp_share_title`,`weapp_share_cover_img`, `pos_qrcode_rule`, `sale_type`, `valid_start`, `valid_end`,`business_hours`,`introduction`,`img`, - `group_support`,`latitude`,`longitude`,`live_support` + `group_support`,`latitude`,`longitude`,`live_support`,`cash_out_support` diff --git a/mallinkService/src/main/resources/mapper/WxMerchantMapper.xml b/mallinkService/src/main/resources/mapper/WxMerchantMapper.xml index af87ebc03..c8d4f6c7e 100644 --- a/mallinkService/src/main/resources/mapper/WxMerchantMapper.xml +++ b/mallinkService/src/main/resources/mapper/WxMerchantMapper.xml @@ -35,19 +35,19 @@ - + `id`,`tenant_id`,`parent_tenant_id`,`img_url`,`name`,`link_phone`,`create_date`,`update_date`,`car_vendor_type`,`car_params`,`status`,`link_person`, `business_id`,`sub_business_id`,`shop_type`,`brand`,`type`,`is_public`,email,`title`,`cover_picture`,`is_admin`,`bill_setting`,`qr_code`, - `introduction`,`action_desc`,`is_del`,`talk_user_main`,`talk_user_aux`,link_line_phone,credit_locked + `introduction`,`action_desc`,`is_del`,`talk_user_main`,`talk_user_aux`,link_line_phone,credit_locked,cash_out_number m.`id`,m.`tenant_id`,m.`parent_tenant_id`,m.`img_url`,m.`name`,m.`link_phone`,m.`create_date`,m.`update_date`,m.`car_vendor_type`,m.`car_params`,m.`status`,m.`link_person`, m.`business_id`,m.`sub_business_id`,m.`shop_type`,m.`brand`,m.`type`,m.`is_public`,m.email,m.`title`,m.`cover_picture`,m.`is_admin`,m.`bill_setting`,m.`qr_code`, - m.`introduction`,m.`action_desc`,m.`is_del`,m.`talk_user_main`,m.`talk_user_aux`,m.link_line_phone,r.rental_start_date,r.rental_end_date + m.`introduction`,m.`action_desc`,m.`is_del`,m.`talk_user_main`,m.`talk_user_aux`,m.link_line_phone,r.rental_start_date,r.rental_end_date,cash_out_number @@ -610,5 +610,12 @@ order by create_date desc + + update wx_merchant set cash_out_number = cash_out_number+#{cashOutNumber} where id = #{id} + + + + update wx_merchant set cash_out_number = cash_out_number-#{cashOutNumber} where id = #{id} +