业务需求
- 可以为预存款充值,在支付订单时使用预存款支付功能
- 当预存款余额>商品订单总金额时,完全抵扣商品订单金额;
- 当预存款余额<商品订单总金额时,抵扣金额为预存款余额,剩余待支付金额,可选择其他支付方式支付(如支付宝,微信)。
架构
一、 充值
二、 数据结构
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)原创文章