电商平台-提现模块的设计与架构

补充说明:电商平台-提现模块的设计与架构,提现功能指的卖家把在平台挣的钱提现到自己的支付宝或者银行卡的一个过程。

功能相对而言不算复杂,有以下几个功能需要处理。


业务逻辑如下;

                       1. 卖家登陆自己的B2B系统提交提现功能。

                       2. 如果没有绑定银行卡或者支付宝,则需要先绑定银行卡或者支付宝账户,以及填写提现密码

                       3. 支付宝或者银行卡需要跟用户的姓名所填一致,防止错误转账。

                       4. 后端需要记录所提现的记录,实际情况是支付宝提现需要收取手续费,这个也需要记录在内。

                       5. 需要形成一个审核机制,用户提现的状态有申请提现,审核成功,提现成功,提现失败四种可能状态。

                       6,每个提现的过程需要记录时间轴,如果有拒绝,用户需要查看拒绝的原因。

                       7.所有的提现到账后,需要平台短信通知用户申请了提现,提现成功,包括提现拒绝等等,都需要短信通知,给用户一个信任感。

                       8,每天晚上5:30之前提现当日到达,之后的次日早上10点钟到达。

                       9,系统自动审核提现的金额数据量的正确与否,来源于用户的订单以及账单数据。


相关的系统设计表如下:

1.提现信息表,为了便于大家理解,我详细的注释都写上了。

CREATE TABLE `withdrawal` (

  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自动增加ID',

  `uid` bigint(20) NOT NULL COMMENT '提现申请人',

  `withdraw_order` varchar(64) NOT NULL COMMENT '提现订单号,系统自动生成的.',

  `withdraw_bank_id` bigint(20) NOT NULL COMMENT '用户对应的卡的编号',

  `withdraw_charge` decimal(12,2) NOT NULL COMMENT '提现手续费',

  `withdraw_reality_total` decimal(12,2) NOT NULL COMMENT '实际提现金额',

  `withdraw_apply_total` decimal(12,2) NOT NULL COMMENT '申请提现的金额',

  `withdraw_apply_time` datetime NOT NULL COMMENT '申请提现时间',

  `status` int(11) NOT NULL COMMENT '提现状态,1表示申请提现,2表示审批通过,3,交易完成,-1审批不通过.',

  `create_by` bigint(20) DEFAULT NULL COMMENT '创建人',

  `create_time` datetime DEFAULT NULL COMMENT '创建时间',

  `update_by` bigint(20) DEFAULT NULL COMMENT '修改人',

  `last_update_time` datetime DEFAULT NULL COMMENT '最后修改时间',

  PRIMARY KEY (`id`),

  KEY `unique_order` (`withdraw_order`) USING BTREE

) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT='提现信息表';


2. 卖家绑定卡的记录表

CREATE TABLE `withdrawal_bank` (

  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自动增加ID',

  `uid` bigint(20) NOT NULL COMMENT '用户ID',

  `cnname` varchar(8) NOT NULL COMMENT '中文姓名',

  `bank_code` varchar(32) NOT NULL COMMENT '卡的缩写,例如:ICBC',

  `bank_name` varchar(32) NOT NULL COMMENT '卡的名字',

  `bank_number` varchar(64) NOT NULL COMMENT '卡号',

  `sequence` tinyint(11) DEFAULT NULL COMMENT '排序用。按照小到大排序。',

  `create_time` datetime NOT NULL COMMENT '创建时间',

  PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8 COMMENT='用户绑定的银行';


补充说明:如果是支付宝,那么bank_code填写alipay,bank_name为支付宝,bank_number为支付宝卡号,cnname为提现的姓名


3. 卖家提现日志表。(会根据卖家的提现时间,形成时间轴)

CREATE TABLE `withdrawal_logs` (

  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自动增加ID',

  `uid` bigint(20) NOT NULL COMMENT '提现申请人',

  `withdraw_order` varchar(64) NOT NULL COMMENT '提现订单号,系统自动生成的.',

  `remark` varchar(64) NOT NULL COMMENT '备注',

  `status` int(11) DEFAULT NULL COMMENT '提现的状态',

  `create_by` bigint(20) DEFAULT NULL COMMENT '创建人',

  `create_time` datetime DEFAULT NULL COMMENT '创建时间',

  PRIMARY KEY (`id`),

  KEY `unique_order` (`withdraw_order`) USING BTREE

) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 COMMENT='用户提现日志表';


补充说明:形成时间轴来显示。

4. 卖家提现密码:

CREATE TABLE `withdrawal_password` (

  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自动增加ID',

  `uid` bigint(20) NOT NULL COMMENT '用户ID',

  `password` varchar(32) NOT NULL COMMENT '密码,md5加密',

  `create_time` datetime NOT NULL COMMENT '记录创建时间',

  `last_update_time` datetime DEFAULT NULL COMMENT '最后一次更新时间',

  PRIMARY KEY (`id`),

  UNIQUE KEY `unique_key_uid` (`uid`)

) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8 COMMENT='用户提现密码';


补充说明:由于设计到资金安全问题,提现需要设置提现密码,这个有别于用户的登陆密码。

整个业务比较简单,只是步骤比较多而已

相关的业务核心代码如下:

2.1 卖家绑定自己的银行卡或者支付宝

/**

     * 添加用户银行信息

     * @param request

     * @param response

     * @return

     */

    @RequestMapping(value = "/withdrawalBank/add", method = { RequestMethod.GET, RequestMethod.POST })    public JsonResult  addWithdrawalBank(HttpServletRequest request, HttpServletResponse response,@RequestBody WithdrawalBank withdrawalBank) {        

        try

        {            if(withdrawalBank==null)

            {                return new JsonResult(JsonResultCode.FAILURE, "传入对象有误", "");

            }


            Long uid = withdrawalBank.getUid();

            String bankCode = withdrawalBank.getBankCode();            if(uid == null)

            {                return new JsonResult(JsonResultCode.FAILURE, "参数有误", "");

            }            

            //拿到当前银行卡的唯一编号

            WithdrawalBank dbWithdrawalBank = withdrawalBankService.getWithdrawalBankByUidAndBankCode(uid, bankCode);            

            if(dbWithdrawalBank != null){                return new JsonResult(JsonResultCode.FAILURE, "卡已存在,请重试",dbWithdrawalBank); 

            }            

            int result = withdrawalBankService.insertWithdrawalBank(withdrawalBank);            

            if (result>0) 

            {                return new JsonResult(JsonResultCode.SUCCESS, "添加用户银行成功", result);

            } 

            return new JsonResult(JsonResultCode.FAILURE, "添加用户银行失败", "");

        }catch(Exception e){


            logger.error("[WithdrawalBankController][addWithdrawalBank] exception :",e);            return new JsonResult(JsonResultCode.FAILURE, "系统错误,请稍后重试","");

        }

    }


2.2  修改与管理自己的提现密码:

/**

     * 提现金额计算     */

    @RequestMapping(value = "/withdrawal/count", method = { RequestMethod.GET, RequestMethod.POST })    public JsonResult countWithdraw(HttpServletRequest request, HttpServletResponse response,

            BigDecimal withdrawApplyTotal, Long userId) {

        logger.info("WithdrawalController.countWithdraw.start");        try {

            Money m = new Money(withdrawApplyTotal);

            Map result = new HashMap();

            BigDecimal withdrawCharge = null;

            BigDecimal withdrawRealityTotal = null;            if (m.compareTo(new Money(1500)) < 0) {                // 提现手续费

                withdrawCharge = (new BigDecimal(2).add(withdrawApplyTotal.multiply(new BigDecimal(0.0055))))

                        .setScale(2, BigDecimal.ROUND_HALF_UP);                // 实际提现金额

                withdrawRealityTotal = withdrawApplyTotal.subtract(withdrawCharge);

            } else {                // 提现手续费

                withdrawCharge = withdrawApplyTotal.multiply(new BigDecimal(0.007)).setScale(2,

                        BigDecimal.ROUND_HALF_UP);                // 实际提现金额

                withdrawRealityTotal = withdrawApplyTotal.subtract(withdrawCharge);

            }

            result.put("withdrawCharge", withdrawCharge);

            result.put("withdrawRealityTotal", withdrawRealityTotal);

            result.put("withdrawApplyTotal", withdrawApplyTotal);            return new JsonResult(JsonResultCode.SUCCESS, "计算成功", result);

        } catch (Exception ex) {

            logger.error("[WithdrawalController][countWithdraw]exception ", ex);            return new JsonResult(JsonResultCode.FAILURE, "系统异常,请稍后再试", "");

        }

    }    /**

     * 提现申请     */

    @RequestMapping(value = "/withdrawal/apply", method = { RequestMethod.GET, RequestMethod.POST })    public JsonResult applyWithDrawal(HttpServletRequest request, HttpServletResponse response,

            @RequestBody Withdrawal withdrawal) {

        logger.info("WithdrawalController.applyWithDrawal.start");        try 

        {

            Long userId = withdrawal.getUid();            

            if (userId==null)

            {                return new JsonResult(JsonResultCode.FAILURE, "参数异常!", "");

            }            // 查询提现表中是否存在当前用户正在审核的提现,如果存在就不允许继续申请

            Withdrawal withdrawalByUserId = withdrawalService.getWithdrawalByUserId(userId);            

            if (withdrawalByUserId != null) 

            {                return new JsonResult(JsonResultCode.FAILURE, "已有提现记录,正在审核中!", withdrawalByUserId);

            }            //所属卖家

            Seller seller = sellerService.getSellerById(userId);            

            // 卖家可提现金额

            BigDecimal billMoney = seller.getBillMoney();

            logger.info("[WithdrawalController][applyWithDrawal]当前用户userId:" + userId + " 可提现金额:" + billMoney);            // 卖家申请提现金额;

            BigDecimal withdrawApplyTotal = withdrawal.getWithdrawApplyTotal();

            logger.info("[WithdrawalController][applyWithDrawal]当前用户userId:" + userId + " 申请的提现金额:" + withdrawApplyTotal);            // 如果申请的金额大于系统的金额,则返回1,同时不符合逻辑,直接返回error

            if (withdrawApplyTotal.compareTo(billMoney) == 1) {                return new JsonResult(JsonResultCode.FAILURE, "申请金额错误,返回重试!", "");

            }


            String orderNumber = OrderIDGenerator.getOrderNumber();

            withdrawal.setWithdrawApplyTime(new Date());

            withdrawal.setCreateTime(new Date());

            withdrawal.setWithdrawOrder(orderNumber);            // 申请提现            withdrawal.setStatus(WithdrawalConstant.APPLY_WITHDRAWAL);

            WithdrawalLogs withdrawalLogs = new WithdrawalLogs();

            withdrawalLogs.setWithdrawOrder(orderNumber);

            withdrawalLogs.setCreateTime(new Date());

            withdrawalLogs.setStatus(WithdrawalConstant.APPLY_WITHDRAWAL);

            withdrawalLogs.setCreateBy(userId);

            withdrawalLogs.setUid(userId);

            withdrawalLogs.setRemark("提现已提交,审核中!");

            withdrawalService.applyWithdrawal(withdrawal, withdrawalLogs);

            sendSmsNotice(withdrawal, userId, seller, billMoney, withdrawApplyTotal);            

            return new JsonResult(JsonResultCode.SUCCESS, "申请提现成功", "");

        } catch (Exception ex) {

            logger.error("[WithdrawalController][applyWithDrawal]exception ", ex);            return new JsonResult(JsonResultCode.FAILURE, "申请失败,系统异常,请稍后再试", "");

        }

    }    /**

     * 发送短信通知给卖家

     * @param withdrawal

     * @param userId

     * @param seller

     * @param billMoney

     * @param withdrawApplyTotal     */

    private void sendSmsNotice(Withdrawal withdrawal, Long userId, Seller seller, BigDecimal billMoney,BigDecimal withdrawApplyTotal) 

    {        try

        {            // 发送短信给卖家

            SmsMessage smsMessage = new SmsMessage();

            smsMessage.setAccount(seller.getSellerAccount());

            smsMessage.setAmount(withdrawal.getWithdrawApplyTotal());

            smsMessage.setName(seller.getRealName() == null ? "" : seller.getRealName());

            SmsMessageService smsMessageService = new SmsMessageServiceImpl();

            smsMessageService.applicationWithdrawal(smsMessage, environment);            // 保存信息到短信日志中

            Sms sms = new Sms();

            String msg = "当前用户userId:" + userId + ",申请的提现金额:" + withdrawApplyTotal + ",可提现金额:" + billMoney;

            sms.setPhone(seller.getSellerAccount());

            sms.setMessage(msg);

            sms.setRemark("提现申请");

            sms.setCreateTime(new Date());

            smsService.saveSms(sms);

        }catch(Exception ex)

        {

            logger.error("[WithdrawalController][sendSmsNotice]exception",ex);

        }

    }    

    /**

     * 提现申请 列表

     * @param withdrawal

     *            传递查询条件

     * @param request

     * @param response

     * @return

     */

    @RequestMapping(value = "/withdrawal/applyList", method = { RequestMethod.GET, RequestMethod.POST })    public JsonResult applyWithDrawalList(HttpServletRequest request, HttpServletResponse response, Long userId,            int currentPageNum, int currentPageSize) {

        logger.info("WithdrawalController.applyWithDrawalList.start");        try 

        {            if (null == userId) {                return new JsonResult(JsonResultCode.FAILURE, "参数有误,userId不能为空", "");

            }

            PageUtil pageUtil = withdrawalService.getPageResult(userId, currentPageNum, currentPageSize);            return new JsonResult(JsonResultCode.SUCCESS, "查询成功", pageUtil);

        } catch (Exception ex) {

            logger.error("[WithdrawalController][applyWithDrawalList]exception ", ex);            return new JsonResult(JsonResultCode.FAILURE, "申请失败,系统异常,请稍后再试", "");

        }

    }    /**

     * 根据提现订单号获取订单的详细情况

     * 

     * @param request

     * @param response

     * @return

     */

    @RequestMapping(value = "/withdrawal/order", method = { RequestMethod.GET, RequestMethod.POST })    public JsonResult withdrawalOrder(HttpServletRequest request, HttpServletResponse response,

            String withdrawalOrder) {

        logger.info("WithdrawalController.withdrawalOrder.start");        try {            if (StringUtils.isBlank(withdrawalOrder)) {                return new JsonResult(JsonResultCode.FAILURE, "提现订单号有误,请重新输入", "");

            }

            WithdrawalQuery withdrawal = withdrawalService.getWithdrawalByWithdrawalOrder(withdrawalOrder);            if (withdrawal == null) {                return new JsonResult(JsonResultCode.FAILURE, "提现订单号不存在,请重新填写", "");

            }            return new JsonResult(JsonResultCode.SUCCESS, "查询成功", withdrawal);

        } catch (Exception ex) {

            logger.error("[WithdrawalController][applyWithDrawalList]exception ", ex);            return new JsonResult(JsonResultCode.FAILURE, "系统异常,请稍后再试", "");

        }

    }


3. 提现记录表核心代码

/**

 * 卖家提现功能---提现银行设置 */@RestController

@RequestMapping("/seller")public class WithdrawalBankController extends BaseController 

{    private static final Logger logger = LoggerFactory.getLogger(WithdrawalBankController.class);


    @Autowired    private WithdrawalBankService withdrawalBankService;    

    /**

     * 根据用户Uid查询用户绑定的银行卡信息;

     * @param request

     * @param response

     * @param withdrawal 条件查询

     * @return

     */

    @RequestMapping(value = "/withdrawalBank/list", method = { RequestMethod.GET, RequestMethod.POST })    public JsonResult withdrawalBankList(HttpServletRequest request, HttpServletResponse response,Long userId,Model model) 

    {        try

        {

            List withdrawalBankList = withdrawalBankService.getWithdrawalBankByUid(userId);            

            if(CollectionUtils.isEmpty(withdrawalBankList))

            {                return new JsonResult(JsonResultCode.SUCCESS, "用户未绑定银行卡", withdrawalBankList);

            }            return new JsonResult(JsonResultCode.SUCCESS, "查询用户银行卡信息", withdrawalBankList);

        }catch(Exception ex){

            logger.error("[WithdrawalBankController][withdrawalBankList] exception :",ex);            return new JsonResult(JsonResultCode.FAILURE, "系统错误,请稍后重试","");

        }

    }    

    /**

     * 添加用户银行信息

     * @param request

     * @param response

     * @return

     */

    @RequestMapping(value = "/withdrawalBank/add", method = { RequestMethod.GET, RequestMethod.POST })    public JsonResult  addWithdrawalBank(HttpServletRequest request, HttpServletResponse response,@RequestBody WithdrawalBank withdrawalBank) {        

        try

        {            if(withdrawalBank==null)

            {                return new JsonResult(JsonResultCode.FAILURE, "传入对象有误", "");

            }


            Long uid = withdrawalBank.getUid();

            String bankCode = withdrawalBank.getBankCode();            if(uid == null)

            {                return new JsonResult(JsonResultCode.FAILURE, "参数有误", "");

            }            

            //拿到当前银行卡的唯一编号

            WithdrawalBank dbWithdrawalBank = withdrawalBankService.getWithdrawalBankByUidAndBankCode(uid, bankCode);            

            if(dbWithdrawalBank != null){                return new JsonResult(JsonResultCode.FAILURE, "卡已存在,请重试",dbWithdrawalBank); 

            }            

            int result = withdrawalBankService.insertWithdrawalBank(withdrawalBank);            

            if (result>0) 

            {                return new JsonResult(JsonResultCode.SUCCESS, "添加用户银行成功", result);

            } 

            return new JsonResult(JsonResultCode.FAILURE, "添加用户银行失败", "");

        }catch(Exception e){


            logger.error("[WithdrawalBankController][addWithdrawalBank] exception :",e);            return new JsonResult(JsonResultCode.FAILURE, "系统错误,请稍后重试","");

        }

    }

}

4. 卖家提现日志表:

/**

 * 卖家提现功能---提现日志记录 */@RestController

@RequestMapping("/seller")public class WithdrawalLogsController extends BaseController {    private static final Logger logger = LoggerFactory.getLogger(WithdrawalLogsController.class);

    @Autowired    private WithdrawalLogsService withdrawalLogsService;    /**

     * 根据Uid和withdrawOrder查询单个提现详情

     * 

     * @param userId

     * @param withdrawOrder

     * @return

     */

    @RequestMapping(value = "/withdrawalLogs/getLogsByWithdrawOrder", method = { RequestMethod.GET,RequestMethod.POST })    public JsonResult getWithdrawalLogsByUidAndWithdrawOrder(HttpServletRequest request, HttpServletResponse response,

            Long userId, String withdrawOrder) {        try

        {            if (StringUtils.isBlank(withdrawOrder)) {                return new JsonResult(JsonResultCode.FAILURE, "请求参数异常", "");

            }

            List withdrawalLogs = withdrawalLogsService.getWithdrawalLogsByWithdrawOrder(withdrawOrder);            return new JsonResult(JsonResultCode.SUCCESS, "订单详情", withdrawalLogs);

        } catch (Exception ex) {

            logger.error("[WithdrawalLogsController][getWithdrawalLogsByUidAndWithdrawOrder]", ex);            return new JsonResult(JsonResultCode.FAILURE, "系统错误,请稍后重试", "");

        }

    }

}


相关的运营截图如下:







©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,290评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,107评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,872评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,415评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,453评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,784评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,927评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,691评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,137评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,472评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,622评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,289评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,887评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,316评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,490评论 2 348

推荐阅读更多精彩内容