业务需求
可以为预存款充值,在支付订单时使用预存款支付功能
当预存款余额>商品订单总金额时,完全抵扣商品订单金额;
当预存款余额<商品订单总金额时,抵扣金额为预存款余额,剩余待支付金额,可选择其他支付方式支付(如支付宝,微信)。
架构
充值
一、充值时序图
二、数据结构
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 |
否 |
字段名 |
字段类型 |
备注 |
是否索引 |
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) |
支付状态 |
否 |
字段名 |
字段类型 |
备注 |
是否索引 |
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) |
会员预存款 |
否 |
说明:此处仅展示预存款消费相关代码,其他关联业务不做具体展示
预存款支付相关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; } }
更多源码分享,请关注“易族智汇”公众号查看更多文章!!