b2b2c商城系統-會員預存款架構及源碼分享

業務需求

  • 可以爲預存款充值,在支付訂單時使用預存款支付功能
  • 當預存款餘額>商品訂單總金額時,完全抵扣商品訂單金額;
  • 當預存款餘額<商品訂單總金額時,抵扣金額爲預存款餘額,剩餘待支付金額,可選擇其他支付方式支付(如支付寶,微信)。

架構

一、 充值
在這裏插入圖片描述

二、 數據結構
1、會員錢包表(es_member_wallet)
2、 後期可能會將會員積分等關於消費抵扣相關信息放入此表中

字段名 字段類型 備註 是否索引
id int(8) 主鍵
member_id int(8) 會員id
memner_name varchar(200) 會員名稱
pre_deposite decimal(20,2) 會員預存款,默認爲0
deposite_password varchar(50) 預存款密碼,默認爲-1

3、充值記錄表(es_deposite_recharge)

字段名 字段類型 備註 是否索引
id int(10) 主鍵
recharge_sn varchar(50) 充值訂單編號
member_id int(10) 會員id
member_name varchar(100) 會員名稱
recharge_money decimal(20,2) 充值金額
recharge_time bigint(20) 充值時間戳
pay_time bigint(20) 支付時間
recharge_way varchar(20) 充值方式,如:支付寶,微信
payment_plugin_id varchar(20) 支付插件ID
pay_status varchar(20) 支付狀態

4.預存款日誌表(es_deposite_log)

字段名 字段類型 備註 是否索引
id int(10) 主鍵
member_id int(10) 會員id
member_name varchar(100) 會員名稱
money decimal(20,2) 消費金額
time bigint(20) 消費時間
detail varchar(200) 消費明細

三、充值模型圖
在這裏插入圖片描述
四、源碼
說明:此處僅展示預存款充值相關代碼,其他關聯業務不做具體展示

預存款充值相關API

package com.enation.app.javashop.buyer.api.payment;

import com.enation.app.javashop.core.payment.model.dto.PayParam;
import com.enation.app.javashop.core.payment.service.OrderPayManager;
import com.enation.app.javashop.core.trade.deposite.model.dos.RechargeDO;
import com.enation.app.javashop.core.trade.deposite.service.RechargeManager;
import com.enation.app.javashop.core.trade.order.model.enums.TradeTypeEnum;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import java.util.Map;

/**
 * @description: 預存款充值
 * @author: liuyulei
 * @create: 2019-12-30 19:53
 * @version:1.0
 * @since:7.1.4
 **/
@Api(description = "預存款充值相關API")
@RestController
@RequestMapping("/recharge")
@Validated
public class RechargePayBuyerController {

    @Autowired
    private RechargeManager rechargeManager;

    @Autowired
    private OrderPayManager orderPayManager;



    @PostMapping
    @ApiOperation(value    = "創建充值訂單")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "price", value = "充值金額", required = true, dataType = "double", paramType = "query")
    })
    public RechargeDO create(@Max(value = 10000,message = "充值金額輸入有誤,單次最多允許充值10000元") @Min(value = 1, message = "充值金額有誤,單次最少充值金額爲1元") Double price) {
        return this.rechargeManager.recharge(price);
    }


    @PostMapping(value = "/{sn}")
    @ApiOperation(value    = "支付充值訂單")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "sn", value = "充值訂單編號", required = true, dataType = "String", paramType = "path")
    })
    public Map pay(@PathVariable(name = "sn") String sn,  @Validated PayParam payParam)    {
        payParam.setSn(sn);
        payParam.setTradeType(TradeTypeEnum.RECHARGE.name());
        return orderPayManager.pay(payParam);
    }


}

充值相關業務

package com.enation.app.javashop.core.trade.deposite.service.impl;

import com.enation.app.javashop.core.client.member.DepositeClient;
import com.enation.app.javashop.core.member.model.dto.DepositeParamDTO;
import com.enation.app.javashop.core.payment.model.dos.PaymentBillDO;
import com.enation.app.javashop.core.payment.service.PaymentBillManager;
import com.enation.app.javashop.core.statistics.util.DateUtil;
import com.enation.app.javashop.core.trade.TradeErrorCode;
import com.enation.app.javashop.core.trade.deposite.model.dos.RechargeDO;
import com.enation.app.javashop.core.trade.deposite.service.RechargeManager;
import com.enation.app.javashop.core.trade.order.model.enums.PayStatusEnum;
import com.enation.app.javashop.core.trade.order.model.enums.TradeTypeEnum;
import com.enation.app.javashop.framework.context.UserContext;
import com.enation.app.javashop.framework.exception.ServiceException;
import com.enation.app.javashop.framework.security.model.Buyer;
import com.enation.app.javashop.framework.util.CurrencyUtil;
import com.enation.app.javashop.framework.util.SqlSplicingUtil;
import com.enation.app.javashop.framework.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.enation.app.javashop.framework.database.DaoSupport;
import com.enation.app.javashop.framework.database.Page;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;


/**
 * 充值記錄業務類
 * @author liuyulei
 * @version v1.0
 * @since v7.1.5
 * 2019-12-30 16:38:45
 */
@Service
public class RechargeManagerImpl implements RechargeManager {

   @Autowired
   @Qualifier("tradeDaoSupport")
   private    DaoSupport daoSupport;

   @Autowired
   private PaymentBillManager paymentBillManager;

   @Autowired
   private DepositeClient depositeClient;




   @Override
   @Transactional(value = "tradeTransactionManager",propagation = Propagation.REQUIRED,rollbackFor=Exception.class)
   public RechargeDO recharge(Double money) {
      Buyer buyer =  UserContext.getBuyer();

      //本金額支付次數
      int times = 0;
      //賬單編號格式: 年月日+ memberID掩碼 + 價格掩碼 +支付次數
      //掩碼均爲5位數,不足前面補零
      String snPrefix = createSn(buyer.getUid(), "D", money);
      String sn = snPrefix + times;

      RechargeDO rechargeDO = this.getModel(sn);

      if(rechargeDO == null ){
         //整合充值訂單數據
         rechargeDO = new RechargeDO(sn,buyer.getUid(),buyer.getUsername(),money);
         //添加充值訂單
         createBill(rechargeDO);
      }else{
         //如果是已付款
         if(!PayStatusEnum.PAY_NO.name().equals(rechargeDO.getPayStatus())){
            // 獲取到已支付次數
            times = Integer.parseInt(sn.substring(19, sn.length()));
            //循環生成sn
            while (true) {
               times++;
               sn = snPrefix + times;
               RechargeDO rechargeTemp = this.getModel(sn);

               // 找到一個沒有使用過的 就可以break了
               if (rechargeTemp == null) {
                  break;
               }
            }
            //充值訂單已經被支付,則表示當天再次支付
            rechargeDO.setRechargeSn(sn);
            rechargeDO.setPayStatus(PayStatusEnum.PAY_NO.name());
            createBill(rechargeDO);
         }
         //如果沒有被支付則不用創建充值訂單,再次爲此訂單支付即可
      }
      return rechargeDO;

   }



   @Override
   @Transactional(value = "tradeTransactionManager",propagation = Propagation.REQUIRED,rollbackFor=Exception.class)
   public void paySuccess(String sn, Double price) {
      RechargeDO rechargeDO = this.getModel(sn);

      if(!rechargeDO.getRechargeMoney().equals(price)){
         throw new ServiceException(TradeErrorCode.E454.code(), "付款金額和應付金額不一致");
      }

      this.daoSupport.execute("update es_deposite_recharge set pay_status = ? where recharge_sn = ? ",PayStatusEnum.PAY_YES.name(),sn);
      //增加會員預存款 餘額
      depositeClient.increase(price,rechargeDO.getMemberId(),"會員充值,充值單號:" + rechargeDO.getRechargeSn());
   }

   @Override
   @Transactional(value = "tradeTransactionManager",propagation = Propagation.REQUIRED,rollbackFor=Exception.class)
   public void updatePaymentMethod(String subSn, String pluginId, String methodName) {
      String sql = "update es_deposite_recharge set payment_plugin_id = ?,recharge_way = ?, pay_time = ?  where recharge_sn = ? ";
      this.daoSupport.execute(sql,pluginId,methodName, DateUtil.getDateline(),subSn);
   }


   @Override
   public Page list(DepositeParamDTO paramDTO){

      StringBuffer sql = new StringBuffer("select * from es_deposite_recharge ");
      List<String> whereSql = new ArrayList<>();
      List<Object> term = new ArrayList<>();

      //根據會員名稱查詢
      if(!StringUtil.isEmpty(paramDTO.getMemberName())){
         whereSql.add(" member_name = ? ");
         term.add(paramDTO.getMemberName());
      }

      //根據會員id查詢
      if(paramDTO.getMemberId() != null ){
         whereSql.add(" member_id = ? ");
         term.add(paramDTO.getMemberId());
      }

      //根據充值編號查詢
      if(!StringUtil.isEmpty(paramDTO.getSn()) ){
         whereSql.add(" recharge_sn = ? ");
         term.add(paramDTO.getSn());
      }

      //根據充值編號查詢
      if(!StringUtil.isEmpty(paramDTO.getSn()) ){
         whereSql.add(" recharge_sn = ? ");
         term.add(paramDTO.getSn());
      }

      //根據充值時間查詢
      if(paramDTO.getStartTime() != null){
         whereSql.add(" recharge_time >= ? ");
         term.add(paramDTO.getStartTime());
      }

      //根據充值時間查詢
      if(paramDTO.getStartTime() != null){
         whereSql.add(" recharge_time <= ? ");
         term.add(paramDTO.getEndTime());
      }
      whereSql.add(" pay_status = ? ");
      term.add(PayStatusEnum.PAY_YES.name());

      //拼接sql
      sql.append(SqlSplicingUtil.sqlSplicing(whereSql));


      sql.append("order by recharge_time desc");
      Page  webPage = this.daoSupport.queryForPage(sql.toString(),paramDTO.getPageNo(), paramDTO.getPageSize() , RechargeDO.class,term.toArray() );
      
      return webPage;
   }

   @Override
   public Double getPrice(String sn) {
      return this.daoSupport.queryForDouble("select recharge_money from es_deposite_recharge where recharge_sn = ? ",sn);
   }
   


   @Override
   public RechargeDO getModel(String sn)  {
      return this.daoSupport.queryForObject("select * from es_deposite_recharge where recharge_sn = ?  ",RechargeDO.class,sn);
   }

   private String mask(String str) {
      String mask = "000000";
      mask = mask + str;
      mask = mask.substring(mask.length() - 5);
      return mask;
   }

   private String createSn(Integer memberId,String prefix,Double price) {
      String memberMask = mask("" + memberId);
      String priceMask = mask("" + CurrencyUtil.mul(price,100).intValue());
      String snPrefix = prefix + DateUtil.toString(new Date(), "yyyyMMdd") + memberMask + priceMask;
      return snPrefix;
   }

   private void createBill(RechargeDO rechargeDO) {
      daoSupport.insert(rechargeDO);
      //創建充值 支付 賬單數據
      PaymentBillDO paymentBillDO = new PaymentBillDO();
      paymentBillDO.setSubSn(rechargeDO.getRechargeSn());
      paymentBillDO.setTradePrice(rechargeDO.getRechargeMoney());
      paymentBillDO.setServiceType(TradeTypeEnum.RECHARGE.name());
      paymentBillManager.add(paymentBillDO);
   }
}

消費

一、消費時序圖
在這裏插入圖片描述
二、消費數據結構
1.交易表(es_trade)
新增預存款抵扣金額字段,記錄訂單使用的預存款金額

字段名 字段類型 備註 是否索引
deposite_money decimal(20,2) 會員預存款

2.訂單表(es_order)
新增預存款抵扣金額字段,記錄訂單使用的預存款金額

字段名 字段類型 備註 是否索引
deposite_money decimal(20,2) 會員預存款

3.源碼
說明:此處僅展示預存款消費相關代碼,其他關聯業務不做具體展示

預存款支付相關API

@ApiOperation(value = "使用預存款支付")
@ApiImplicitParams({
        @ApiImplicitParam(name = "sn", value = "要支付的交易sn", required = true, dataType = "String", paramType = "query"),
        @ApiImplicitParam(name = "trade_type", value = "交易類型", required = true, dataType = "String", paramType = "query", allowableValues = "TRADE,ORDER"),
        @ApiImplicitParam(name = "password", value = "支付密碼", required = true, dataType = "String", paramType = "query")
})
@GetMapping(value = "/{trade_type}/{sn}")
public BalancePayVO payTrade(@PathVariable(name = "sn") String sn, @PathVariable(name = "trade_type") String tradeType,
                             @NotEmpty(message = "密碼不能爲空") String password) {
    Buyer buyer = UserContext.getBuyer();
    return balanceManager.balancePay(sn,buyer.getUid(),tradeType.toUpperCase(),password);
}

@GetMapping(value = "/cashier")
@ApiOperation(value    = "獲取預存款相關,收銀臺使用")
public MemberDepositeVO getDepositeVO()    {
   Buyer buyer = UserContext.getBuyer();
   return depositeManager.getDepositeVO(buyer.getUid());
}

預存款支付相關業務

package com.enation.app.javashop.core.trade.order.service.impl;

import com.enation.app.javashop.core.client.member.DepositeClient;
import com.enation.app.javashop.core.member.model.dos.MemberWalletDO;
import com.enation.app.javashop.core.payment.service.PaymentServicePlugin;
import com.enation.app.javashop.core.trade.order.model.vo.BalancePayVO;
import com.enation.app.javashop.core.trade.order.service.BalanceManager;
import com.enation.app.javashop.core.trade.order.service.TradeQueryManager;
import com.enation.app.javashop.framework.util.CurrencyUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @description: 預存款抵扣業務類
 * @author: liuyulei
 * @create: 2020-01-01 11:55
 * @version:1.0
 * @since:7.1.4
 **/
@Service
public class BalanceManagerImpl implements BalanceManager {


    @Autowired
    private TradeQueryManager tradeQueryManager;

    @Autowired
    private DepositeClient depositeClient;

    @Autowired
    private List<PaymentServicePlugin> paymentServicePlugin;


    @Override
    public BalancePayVO balancePay(String sn,Integer memberId, String tradeType, String password) {

        //檢測訂單、交易是否屬於當前登錄會員
        this.tradeQueryManager.checkIsOwner(sn,memberId);

        //檢測支付密碼是否正確
        this.depositeClient.checkPwd(memberId,password);

        PaymentServicePlugin plugin = this.findServicePlugin(tradeType);

        //獲取訂單待支付金額
        Double needPay = plugin.getPrice(sn);

        //獲取會員預存款信息
        MemberWalletDO walletDO = this.depositeClient.getModel(memberId);

        //判斷預存款餘額與訂單待支付金額
        Double diffPrice = CurrencyUtil.sub(needPay,walletDO.getPreDeposite());
        Double balance = 0D;
        if(diffPrice >= 0 ){
            //此時預存款不足,無法完全抵扣所有訂單支付基恩
            balance = walletDO.getPreDeposite();
            needPay = diffPrice;

        }else{
            //此時訂單支付金額爲0
            balance = needPay;
            needPay = 0D;

        }

        BalancePayVO payVO = new BalancePayVO();
        payVO.setSn(sn);
        payVO.setBalance(balance);
        payVO.setNeedPay(needPay);

        //預存款支付,修改訂單待支付金額
        plugin.balancePay(payVO,memberId);

        return payVO;

    }



    /**
     * 在支付子業務插件中  找到對應業務插件
     * @param tradeType
     * @return
     */
    private PaymentServicePlugin findServicePlugin(String tradeType) {
        for (PaymentServicePlugin plugin:paymentServicePlugin){
            if (tradeType.equals(plugin.getServiceType())) {
                return plugin;
            }
        }
        return null;
    }
}

更多源碼分享,請關注“易族智匯”公衆號查看更多文章!!

易族智匯(javashop)原創文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章