核心业务5:充值业务实现

本文详细阐述了一个在线充值系统的流程,包括用户在尚融宝平台上输入充值金额后跳转至汇付宝完成支付,以及双方数据库的交互和幂等性判断。前端通过弹窗提交充值请求,后端处理充值逻辑,同时涉及异步回调接口确保数据一致性。整个过程涉及到用户账户表、交易流水表的管理和数据同步。
摘要由CSDN通过智能技术生成

核心业务5:我要充值

1.充值业务流程图

2.充值业务流程逻辑

3.数据库表

4.前端逻辑代码

5.汇付宝代码逻辑

6.尚融宝代码逻辑

7.幂等性判断原理和解决方案

8.代码规范和原理了解

核心业务5:我要充值

1.充值业务流程图

在这里插入图片描述

①在用户注册之后

会保存数据到对应数据库(user_info和user_account)

②用户绑定了汇付宝之后(有bind_code)

③用户点击充值页面

在这里插入图片描述

④输入充值金额后点击充值跳转到尚融宝中封装数据后返回给浏览器(自动表单提交到汇付宝)

⑤汇付宝显示表单数据用户填写提交

在这里插入图片描述

⑥汇付宝

  • 更新汇付宝数据库的user_account表的余额
  • 同步返回数据给前端页面控制跳转
    在这里插入图片描述
  • 异步返回数据给尚融宝同步user_account数据和trans_flow数据

2.充值业务流程逻辑

①用户填写金额后提交

  • 前端提交到尚融宝
  • 尚融宝返回表单到浏览器(自动提交给汇付宝)
  • 汇付宝回显页面

②用户填写汇付宝回显页面后提交

  • 汇付宝更改user_account返回提交成功页给浏览器(同步)
  • 汇付宝异步调用尚融宝接口让其动态更新其user_account和trans_flow表
  • 尚融宝返回success给汇付宝

3.数据库表

①尚融宝

  • user_account
    在这里插入图片描述
  • trans_flow

在这里插入图片描述

②尚融宝数据表关联

  • user_account和trans_flow都是用user_id来绑定,并且与user_info也用user_id绑定

③汇付宝

  • user_account
    在这里插入图片描述

④汇付宝数据关联

  • user_account通过user_code和user_bind的bind_code表绑定

⑤尚融宝和汇付宝互相绑定

  • 通过秘钥检验身份(双方互相)
  • 两者的所有表通过唯一bind_code 和唯一user_id进行绑定
  • 即两表的user_info和user_bind通过user_id和bind_code唯一确定。
  • 两表的user_account通过唯一bind_code确定

4.前端逻辑代码

①前端对象

在这里插入图片描述

②前端调用接口

在这里插入图片描述

③前端代码

srb-site\pages\user\recharge.vue

<template>
  <div class="personal-main">
    <div class="personal-pay">
      <h3><i>充值</i></h3>
      <div class="quick-pay-wrap">
        <h4>
          <span class="quick-tit pay-cur"><em>汇付宝充值</em></span>
        </h4>
        <form id="form" name="form" method="post" action="">
          <div class="quick-main">
            <div class="fl quick-info">
              <div class="info-1">
                <span class="info-tit">充值金额</span>
                <span class="info1-input">
                  <input
                    type="text"
                    class="pay-money-txt"
                    maxlength="10"
                    v-model="chargeAmt"
                  />
                  <em></em>
                </span>
              </div>
              <div class="bank-check" id="bank-check2">
                <b class="selected" id="bankProtocol1"></b>
                <span class="bank-agree">
                  我同意并接受
                  <a href="#" target="_blank">
                    《尚融宝投资咨询与管理服务电子协议》
                  </a>
                </span>
              </div>
              <input
                type="button"
                value="充值"
                class="btn-paycz"
                @click="commitCharge()"
              />
            </div>

            <div class="pay-tipcon" style="height: 110px;">
              <b>温馨提示:</b><br />
              1、为了您的资金安全,您的账户资金由第三方汇付宝进行托管。<br />
              2、充值前请注意您的银行卡充值限额,以免造成不便。<br />
              3、为了您的资金安全,建议充值前进行实名认证。<br />
              4、如果充值遇到任何问题,请联系客服:4006-001-999</div>
          </div>
        </form>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      chargeAmt: 0,
    }
  },
  methods: {
    commitCharge() {
      let _this = this
      this.$alert(
        //标题
        '<div style="size: 18px;color: red;">您即将前往汇付宝充值</div>',
        '前往汇付宝资金托管平台',
        {
          //配了此则能够渲染出html页面(不加上面还有div)
          dangerouslyUseHTMLString: true,
          //按钮
          confirmButtonText: '立即前往',
          //回调函数
          callback: (action) => {
            //点击后如果action为'confirm',action也可能为close
            if (action === 'confirm') {
              //当前场景this和外面this不一样修改this为前面的_this
              _this.$axios
                .$post(
                  '/api/core/userAccount/auth/commitCharge/' + _this.chargeAmt
                )
                .then((response) => {
                  //直接替换当前浏览器的内容
                  document.write(response.data.formStr)
                })
            }
          },
        }
      )
    },
  },
}
</script>

④前端代码逻辑

  • 通过alert弹框向后端发起ajax请求
  • 通过document.write将后端返回的字符串渲染为表单(自动提交)

5.汇付宝代码逻辑

①汇付宝的接受参数

  • 提交表单参数
    在这里插入图片描述
  • 异步调用尚融宝请求对象参数
    在这里插入图片描述

②接受表单的提交返回表单回显页

在这里插入图片描述
在这里插入图片描述
表单提交地址

在这里插入图片描述

③点击提交后保存数据,同步返回,异步调用

  • 调用的方法

在这里插入图片描述

  • 账户充值
    在这里插入图片描述

  • 异步调用
    在这里插入图片描述

  • 同步跳转
    在这里插入图片描述

  • 注意异步调用逻辑
    进行异步调用,如果异步调用超时或者返回不为success,则进行五次失败重试。
    如果返回的是success,那么异步调用结束,
    在这里插入图片描述

6.尚融宝代码逻辑

①封装对象

  • 封装汇付宝异步调用请求的数据
    在这里插入图片描述

②开发接口

在这里插入图片描述

③充值接口代码逻辑

  • controller
    com.atguigu.srb.core.controller.api.UserAccountController
@Api(tags = "会员账户")
@RestController
@RequestMapping("/api/core/userAccount")
@Slf4j
public class UserAccountController {
    @Resource
    private UserAccountService userAccountService;

    @ApiOperation("充值")
    @PostMapping("/auth/commitCharge/{chargeAmt}")
    public R commitCharge(
            @ApiParam(value = "充值金额", required = true)
            @PathVariable BigDecimal chargeAmt, HttpServletRequest request) {
            //request获取充值用户id(不能直接传id不安全,必须从token中解析id)
            //充值金额
        String token = request.getHeader("token");
        Long userId = JwtUtils.getUserId(token);
        //表单提交(组装表单字符串用于远程提交数据到汇付宝)
        String formStr = userAccountService.commitCharge(chargeAmt, userId);
        return R.ok().data("formStr", formStr);

    }
 }
  • service
package com.atguigu.srb.core.service;

import com.atguigu.srb.core.pojo.entity.UserAccount;
import com.baomidou.mybatisplus.extension.service.IService;

import java.math.BigDecimal;
import java.util.Map;

/**
 * <p>
 * 用户账户 服务类
 * </p>
 *
 * @author Likejin
 * @since 2023-04-09
 */
public interface UserAccountService extends IService<UserAccount> {

    String commitCharge(BigDecimal chargeAmt, Long userId);

}

package com.atguigu.srb.core.service.impl;

import com.atguigu.srb.core.enums.TransTypeEnum;
import com.atguigu.srb.core.hfb.FormHelper;
import com.atguigu.srb.core.hfb.HfbConst;
import com.atguigu.srb.core.hfb.RequestHelper;
import com.atguigu.srb.core.mapper.UserAccountMapper;
import com.atguigu.srb.core.mapper.UserInfoMapper;
import com.atguigu.srb.core.pojo.bo.TransFlowBO;
import com.atguigu.srb.core.pojo.entity.UserAccount;
import com.atguigu.srb.core.pojo.entity.UserInfo;
import com.atguigu.srb.core.service.TransFlowService;
import com.atguigu.srb.core.service.UserAccountService;
import com.atguigu.srb.core.util.LendNoUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 * 用户账户 服务实现类
 * </p>
 *
 * @author Likejin
 * @since 2023-04-09
 */
@Service
public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {


    @Resource
    private UserInfoMapper userInfoMapper;

    @Resource
    private TransFlowService transFlowService;

    /**
     * @param chargeAmt:
     * @param userId:
     * @return String
     * @author Likejin
     * @description 组装表单字符串返回前段自动提交
     * @date 2023/4/17 11:13
     */

    @Override
    public String commitCharge(BigDecimal chargeAmt, Long userId) {
        //获取充值人绑定协议号
        UserInfo userInfo = userInfoMapper.selectById(userId);
        String bindCode = userInfo.getBindCode();

        Map<String, Object> paramMap = new HashMap<>();
        //唯一商户号
        paramMap.put("agentId", HfbConst.AGENT_ID);  //商户号
        paramMap.put("agentBillNo", LendNoUtils.getChargeNo()); //充值账单号
        paramMap.put("bindCode",bindCode);//用户绑定协议号
        paramMap.put("chargeAmt",chargeAmt);//充值金额
        paramMap.put("feeAmt",new BigDecimal(0));//充值手续费为0
        paramMap.put("notifyUrl",HfbConst.RECHARGE_NOTIFY_URL);//异步返回url
        paramMap.put("returnUrl",HfbConst.RECHARGE_RETURN_URL);//同步返回url
        paramMap.put("timestamp", RequestHelper.getTimestamp());//时间戳
        paramMap.put("sign",RequestHelper.getSign(paramMap));//签名

        //构件表单(表单提交的地址)
        String formStr = FormHelper.buildForm(HfbConst.RECHARGE_URL, paramMap);
        return formStr;
    }
}

④异步回调接口实现

  • controller
    com.atguigu.srb.core.controller.api.UserAccountController
 @ApiOperation(value = "用户充值异步回调")
    @PostMapping("/notify")
    public String notify(HttpServletRequest request) {
        //把返回的request数据提取数据
        Map<String, Object> paramMap = RequestHelper.switchMap(request.getParameterMap());
        log.info("用户充值异步回调:" + JSON.toJSONString(paramMap));


        //验签
        if (RequestHelper.isSignEquals(paramMap)) {
            //判断业务是否成功
            if("0001".equals(paramMap.get("resultCode"))){
                //同步账户数据
                return userAccountService.notify(paramMap);
            }else{
                log.info("用户充值异步回调充值失败:" + JSON.toJSONString(paramMap));
                return "success";//汇付宝重试的原因就是返回非success,不希望汇付宝重试,所以返回success
            }
        }else{
            log.info("用户充值异步回调签名错误:" + JSON.toJSONString(paramMap));
            return "fail";
        }
    }
  • service
    com.atguigu.srb.core.service.UserAccountService
String notify(Map<String, Object> paramMap);

    /**
     * @param paramMap:
     * @return void
     * @author Likejin
     * @description 汇付宝发来的响应让尚融宝做账户同步处理
     * @date 2023/4/17 13:02
     */

    @Transactional(rollbackFor = Exception.class)
    @Override
    public String notify(Map<String, Object> paramMap) {

        String agentBillNo = (String)paramMap.get("agentBillNo");

        //先幂等性判断?判断标准(是否调用过该接口(判断交易流水账单号是否存在))
        boolean isSave = transFlowService.isSaveTransFlow(agentBillNo);
        if(isSave){
            log.warn("幂等性返回");
            return "success";
        }
        //账户同步user_account(充值处理)
        //希望能够灵活修改余额和冻结金额写sql
        String bindCode =(String) paramMap.get("bindCode");
        String chargeAmt =(String) paramMap.get("chargeAmt");
        baseMapper.updateAccount(bindCode,new BigDecimal(chargeAmt),new BigDecimal(0));

        //记录账户流水(汇付宝发来了账户流水账单号)

        TransFlowBO transFlowBO = new TransFlowBO(
                agentBillNo,
                bindCode,
                new BigDecimal(chargeAmt),
                TransTypeEnum.RECHARGE,
                "充值啦");

        transFlowService.saveTransFlow(transFlowBO);
        return "success";

    }
  • 其他service
package com.atguigu.srb.core.service;

import com.atguigu.srb.core.pojo.bo.TransFlowBO;
import com.atguigu.srb.core.pojo.entity.TransFlow;
import com.baomidou.mybatisplus.extension.service.IService;

/**
 * <p>
 * 交易流水表 服务类
 * </p>
 *
 * @author Likejin
 * @since 2023-04-09
 */
public interface TransFlowService extends IService<TransFlow> {



    void saveTransFlow(TransFlowBO transFlowbo);


    boolean isSaveTransFlow(String agentBillNo);
}

package com.atguigu.srb.core.service.impl;

import com.atguigu.srb.core.mapper.UserInfoMapper;
import com.atguigu.srb.core.pojo.bo.TransFlowBO;
import com.atguigu.srb.core.pojo.entity.TransFlow;
import com.atguigu.srb.core.mapper.TransFlowMapper;
import com.atguigu.srb.core.pojo.entity.UserInfo;
import com.atguigu.srb.core.service.TransFlowService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * <p>
 * 交易流水表 服务实现类
 * </p>
 *
 * @author Likejin
 * @since 2023-04-09
 */
@Service
public class TransFlowServiceImpl extends ServiceImpl<TransFlowMapper, TransFlow> implements TransFlowService {

    @Resource
    private UserInfoMapper userInfoMapper;
    /**
     * @param transFlowbo:
     * @return void
     * @author Likejin
     * @description 保存交易流水
     * @date 2023/4/17 13:21
     */

    @Override
    public void saveTransFlow(TransFlowBO transFlowbo) {
        TransFlow transFlow = new TransFlow();
        //封装user_account需要的数据


        //通过bind_code获取user_id
        String bindCode = transFlowbo.getBindCode();
        QueryWrapper<UserInfo> userInfoQueryWrapper = new QueryWrapper<>();
        userInfoQueryWrapper.eq("bind_code",bindCode);
        UserInfo userInfo = userInfoMapper.selectOne(userInfoQueryWrapper);

        transFlow.setTransAmount(transFlowbo.getAmount());
        transFlow.setMemo(transFlowbo.getMemo());
        transFlow.setTransTypeName(transFlowbo.getTransTypeEnum().getTransTypeName());
        transFlow.setTransType(transFlowbo.getTransTypeEnum().getTransType());
        transFlow.setTransNo(transFlowbo.getAgentBillNo());
        transFlow.setUserId(userInfo.getId());
        transFlow.setUserName(userInfo.getName());

        baseMapper.insert(transFlow);

    }

    /**
     *
     * @param agentBillNo
     * @return 根据流水号查询流水号是否存在(幂等性)
     */
    @Override
    public boolean isSaveTransFlow(String agentBillNo) {
        QueryWrapper<TransFlow> transFlowQueryWrapper = new QueryWrapper<>();
        transFlowQueryWrapper.eq("trans_no",agentBillNo);
        Integer count = baseMapper.selectCount(transFlowQueryWrapper);
        return count > 0;
    }
}

  • mapper
package com.atguigu.srb.core.mapper;

import com.atguigu.srb.core.pojo.entity.UserAccount;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;

import java.math.BigDecimal;

/**
 * <p>
 * 用户账户 Mapper 接口
 * </p>
 *
 * @author Likejin
 * @since 2023-04-09
 */
public interface UserAccountMapper extends BaseMapper<UserAccount> {


    void updateAccount(
            @Param("bindCode")String bindCode,
            @Param("amount")BigDecimal amount,
            @Param("freezeAmount")BigDecimal freezeAmount);

}

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.srb.core.mapper.UserAccountMapper">


    <update id="updateAccount">
        update user_account
        set
        amount = amount + #{amount},
        freeze_amount = freeze_amount + #{freezeAmount}
        where user_id = (select id from user_info where bind_code = #{bindCode} )
    </update>
</mapper>

⑤业务逻辑

  • 充值接口逻辑
    前端传来充值金额和token。目的返回表单
    利用token获得user_id,根据user_id来封装所需参数,注意sign的获得(通过封装的所有参数和秘钥生成sign,另一端校验通过统一的方式生成sign对比是否相等)

  • 异步回调接口
    汇付宝传来响应结果。目的更新user_account表和更新trans_flow表,返回succes。
    验证签名是否正确sign,失败则返回fail(错误发生在汇付宝端)。TODO:此时重试也无用。不希望重试。为什么返回fail?
    成功则开始检验结果是否成功(响应码是否为001),如果不是则返回success(错误发生在汇付宝内部,重试响应码结果仍然不为001无需重试。)
    如果是则账户同步数据。幂等性判断(判断交易流水账单号是否存在)。账户同步user_account。同步trans_flow

  • 问题1:TODO

  • 问题2:修改用户账单user_account为了灵活修改余额和冻结金额,自己写sql。
    即可以复用,如果不传入数据则是0不修改,灵活修改。

  • 问题3:封装TransFlowBO修改trans_flow
    汇付宝传来的数据封装为TransFlowBO,包含了当前充值的详细信息(除了user_id和user_name)

  • 注意trans_flow流水的状态
    在这里插入图片描述

7.幂等性判断原理和解决方案

①什么是幂等性

  • 用户对于同一次请求或者多次请求的结果是一致的

②为什么会发生幂等性

  • 重试机制
    比如业务中的汇付宝没有重试机制,那么网络断掉,有可能尚融宝未更新数据但是汇付宝更新数据,用户看到的和实际的不同。
  • 重试机制的问题
    如果汇付宝有重试机制,那么尚融宝接受回调后已经增加了钱,返回响应网络断调,汇付宝得不到响应进行重试,尚融宝又增加了一遍,出现问题

③如何解决幂等性

  • 在数据库中设置唯一字段流水号,设置事务
    如果第二次汇付宝再次调用,由于无法插入trans_flow,因为流水号策略为唯一则插入失败,回滚
  • 业务首先判断是否为多次请求调用?用流水号查看数据库中是否有这条数据。

8.代码规范和原理了解

①对象的包名

  • VO:前端
  • entity:数据库
  • dto:execel
  • bo:业务对象

②远程调用接口的方法

  • ajax:有跨域的限制

  • 表单提交远程调用:不收跨域限制,组装表单
    在这里插入图片描述

  • JAVA代码远程调用:底层调用

未更新

未更新

未更新

未更新

未更新

未更新

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值