Java生鮮電商平台-提現模塊的設計與架構
補充說明:生鮮電商平台-提現模塊的設計與架構,提現功能指的賣家把在平台掙的錢提現到自己的支付寶或者銀行卡的一個過程。
功能相對而言不算復雜,有以下幾個功能需要處理。
業務邏輯如下;
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<String, BigDecimal> result = new HashMap<String, BigDecimal>();
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<WithdrawalBank> 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> withdrawalLogs = withdrawalLogsService.getWithdrawalLogsByWithdrawOrder(withdrawOrder);
return new JsonResult(JsonResultCode.SUCCESS, "訂單詳情", withdrawalLogs);
} catch (Exception ex) {
logger.error("[WithdrawalLogsController][getWithdrawalLogsByUidAndWithdrawOrder]", ex);
return new JsonResult(JsonResultCode.FAILURE, "系統錯誤,請稍后重試", "");
}
}
}
相關的運營截圖如下:





