B078-项目实战--支付模块 领养订单支付流程

目录

      • 支付模块
        • 需求分析
        • 表设计
          • 支付单表
          • 支付宝账号信息表-商家账号
          • 微信支付账号信息表-商家账号
          • 银行账号表-商家
          • 资金账号表
          • 支付流水表
        • 流程分析
        • 支付基础模块继承
        • 加密算法
        • 沙箱环境准备
        • 支付宝支付-流程分析
        • 根据demo封装工具类
          • 导入依赖
          • AlipayConfig
          • AlipayInfo
          • AlipayUtil
        • 内网穿透
      • 领养订单-支付流程
        • 后端生成支付单和进行支付
          • AdoptOrderServiceImpl
          • PayConstants
          • PayBill
          • PayBillServiceImpl
          • 测试
        • 前台显示支付form表单
        • 前台完成支付
        • 异步回调通知后完成后续逻辑
      • 订单与支付汇总流程图

每种订单都有对应类型的支付

支付模块

需求分析

B078-项目实战--支付模块 领养订单支付流程_第1张图片
资金账号(支付账号)
支付方式
支付单
支付流水

表设计

B078-项目实战--支付模块 领养订单支付流程_第2张图片
三种类型:支付单表,支付流水表,支付方式表

支付单表
CREATE TABLE `t_pay_bill` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `digest` varchar(100) DEFAULT NULL COMMENT '交易摘要',
  `money` decimal(11,2) NOT NULL COMMENT '金额',
  `state` tinyint(4) NOT NULL DEFAULT '0' COMMENT '支付状态 0待支付1 已支付-1 取消',
  `lastPayTime` datetime DEFAULT NULL,
  `payChannel` tinyint(4) NOT NULL COMMENT '支付方式 余额 三方支付(支付宝,微信,银联)',
  `createTime` datetime DEFAULT NULL,
  `updateTime` datetime DEFAULT NULL,
  `unionPaySn` char(15) DEFAULT NULL COMMENT '统一支付单号',
  `businessType` varchar(255) NOT NULL COMMENT '业务类型',
  `businessKey` bigint(20) NOT NULL COMMENT '关联业务键',
  `user_id` bigint(20) DEFAULT NULL,
  `nickName` varchar(255) DEFAULT '',
  `shopName` varchar(255) DEFAULT NULL,
  `shop_id` bigint(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8 COMMENT='支付单';

B078-项目实战--支付模块 领养订单支付流程_第3张图片
1.订单模块生成订单
2.支付模块生成支付单
3.支付模块生成统一支付单号,调三方接口进行支付
4.支付成功返回统一支付单号给支付模块,支付模块根据统一支付单号找到订单修改状态
5.订单模块根据支付模块的订单类型和订单号找到订单,修改订单状态

支付宝账号信息表-商家账号
CREATE TABLE `t_pay_alipay_info` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `shop_id` bigint(20) DEFAULT NULL,
  `shopName` varchar(255) DEFAULT NULL,
  `appid` varchar(255) DEFAULT NULL COMMENT '应用id',
  `merchant_private_key` varchar(2500) DEFAULT NULL COMMENT '商家私钥',
  `alipay_public_key` varchar(2500) DEFAULT NULL COMMENT '支付宝公钥',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
微信支付账号信息表-商家账号
CREATE TABLE `t_pay_wechat_info` (
  `merchant_private_key` varchar(2500) DEFAULT NULL,
  `appid` varchar(255) DEFAULT NULL,
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `alipay_public_key` varchar(2500) DEFAULT NULL,
  `shop_id` bigint(20) DEFAULT NULL,
  `shopName` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
银行账号表-商家
CREATE TABLE `t_pay_bank_info` (
  `merchant_private_key` varchar(2500) DEFAULT NULL,
  `appid` varchar(255) DEFAULT NULL,
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `alipay_public_key` varchar(2500) DEFAULT NULL,
  `shop_id` bigint(20) DEFAULT NULL,
  `shopName` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
资金账号表
CREATE TABLE `t_pay_account` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `createTime` datetime DEFAULT NULL,
  `updateTime` datetime DEFAULT NULL,
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `useableBalance` int(11) NOT NULL DEFAULT '0' COMMENT '可用金额',
  `frozenBalance` int(11) NOT NULL DEFAULT '0' COMMENT '冻结金额',
  `creditBanance` int(11) NOT NULL DEFAULT '0' COMMENT '积分',
  `salt` varchar(255) DEFAULT NULL,
  `payPassword` varchar(32) NOT NULL COMMENT '支付密码 md5加密+加盐',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户账户';
支付流水表
CREATE TABLE `t_pay_account_flow` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `createTime` bigint(20) DEFAULT NULL,
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `nickName` varchar(20) DEFAULT NULL COMMENT '用户姓名',
  `money` decimal(11,2) NOT NULL COMMENT '金额',
  `type` tinyint(4) NOT NULL COMMENT '支付方式',
  `businessType` varchar(4) NOT NULL COMMENT '业务类型',
  `businessName` varchar(20) NOT NULL COMMENT '业务名',
  `businessKey` bigint(20) NOT NULL COMMENT '关联业务键',
  `payChannel` tinyint(4) NOT NULL,
  `payChannelName` varchar(20) NOT NULL,
  `note` varchar(50) DEFAULT NULL COMMENT '备注',
  `digest` varchar(100) DEFAULT NULL,
  `unionPaySeq` char(15) DEFAULT NULL COMMENT '统一支付单号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户支付流水';

流程分析

结合上面内容
B078-项目实战--支付模块 领养订单支付流程_第4张图片
https://opendocs.alipay.com/open/00a0ut
电脑网站支付 - 产品介绍 -> 流程介绍

支付基础模块继承

拷贝java和xml代码,配置yml扫描别名

加密算法

B078-项目实战--支付模块 领养订单支付流程_第5张图片

沙箱环境准备

在线加密生成自己的公钥和私钥
把自己的公钥告诉支付宝并得到支付宝的公钥
保存自己的 appid 私钥 和支付宝的公钥到数据库表里

支付宝支付-流程分析

https://opendocs.alipay.com/open/00a0ut
电脑网站支付 - 接入指南 - 电脑网站支付快速接入 -> 流程介绍
B078-项目实战--支付模块 领养订单支付流程_第6张图片
B078-项目实战--支付模块 领养订单支付流程_第7张图片
根据订单生成支付单后调支付接口要传同步通知地址和异步通知地址

支付平台返回string(内容是一个支付表单)给后端,后端将这个String交给前端到浏览器显示,

中间的步骤由用户与支付宝他们自己完成

支付完成后三重返回保证
1.同步通知,只做重定向页面通知
2.异步通知,以此为准,收到后分别去更改支付单和订单的状态
3.主动用统一支付单号去支付宝查询状态

根据demo封装工具类

导入依赖

<dependency>
    <groupId>com.alipay.sdkgroupId>
    <artifactId>alipay-sdk-javaartifactId>
    <version>3.7.4.ALLversion>
dependency>
AlipayConfig
public class AlipayConfig {
    // 服务器异步通知路径  需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
    public static String notify_url = "http://c84pns.natappfree.cc/notify";
    // 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
    public static String return_url = "http://localhost/success.html";
    // 签名方式
    public static String sign_type = "RSA2";
    // 字符编码格式
    public static String charset = "utf-8";
    // 测试环境支付宝网关
    public static String gatewayUrl = "https://openapi.alipaydev.com/gateway.do";
}
AlipayInfo
@Data
public class AlipayInfo extends BaseDomain {

    private String merchant_private_key;//我的私钥
    private String appid;//我的AppID
    private String alipay_public_key;//支付宝公钥
    private Long shop_id;
    private String shopName;

}
AlipayUtil
public class AlipayUtil {

    public static String  pay(AlipayInfo info, PayBill payBill){
        try {
            //获得初始化的AlipayClient
            AlipayClient alipayClient = new DefaultAlipayClient(
                    cn.ming.pay.utils.AlipayConfig.gatewayUrl,
                    info.getAppid(),
                    info.getMerchant_private_key(),
                    "json",
                    cn.ming.pay.utils.AlipayConfig.charset,
                    info.getAlipay_public_key(),
                    cn.ming.pay.utils.AlipayConfig.sign_type);

            //设置请求参数
            AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
            alipayRequest.setReturnUrl(cn.ming.pay.utils.AlipayConfig.return_url);
            alipayRequest.setNotifyUrl(cn.ming.pay.utils.AlipayConfig.notify_url);

            //商户订单号,商户网站订单系统中唯一订单号,必填
            String out_trade_no = payBill.getUnionPaySn();
            //付款金额,必填
            String total_amount = payBill.getMoney().toString();
            //订单名称,必填
            String subject = payBill.getDigest();
            //商品描述,可空
            String body = payBill.getDigest();

            alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
                    + "\"total_amount\":\""+ total_amount +"\","
                    + "\"subject\":\""+ subject +"\","
                    + "\"body\":\""+ body +"\","
                    + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");

            //请求
            String result = alipayClient.pageExecute(alipayRequest).getBody();
            return result;
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        return null;
    }

}

内网穿透

使用内网穿透将本地的异步通知地址暴露到公网上供外网访问

官网首页:https://natapp.cn/
NATAPP1分钟快速新手图文教程:https://natapp.cn/article/natapp_newbie

购买隧道 - 免费
名称:pethome,隧道协议:web,本地端口:8080, - 免费购买
查看我的隧道,得到authtoken

下载客户端,解压至任意目录,得到natapp.exe

同文件夹下新建config.ini文件,输入authtoken=xxx保存

双击natapp.exe得到公网可用域名

配置以下两个回调地址

// 服务器异步通知路径  需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
public static String notify_url = "http://254fde.natappfree.cc/notify";
// 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
public static String return_url = "http://localhost/success.html";

ph-web拷贝success.html到根目录,编码异步回调notify接口,

AlipayController

    @PostMapping("/notify")
    public void notify(HttpServletRequest request){
        System.out.println("支付宝异步回调!");
    }

领养订单-支付流程

后端生成支付单和进行支付

AdoptOrderServiceImpl
    /**
     * 领养订单结算
     */
    @Override
    @Transactional
    public String submit(Map<String, Object> params, Logininfo loginIn) {
        Long petId = Long.valueOf(params.get("pet_id").toString());
        Long addressId = Long.valueOf(params.get("address_id").toString());//收货地址:t_user_address的id
        Integer payMethod = Integer.valueOf(params.get("pay_method").toString());//1 支付宝 2 微信 3 银联 0 余额
        Integer serviceMethod = Integer.valueOf(params.get("service_method").toString());//送货方式

        //1.修改状态  准备下架
        Pet pet = petMapper.loadById(petId);
        pet.setState(0);
        pet.setOffsaletime(new Date());
        //2.绑定用户
        User user = userMapper.loadByloginInfoId(loginIn.getId());
        pet.setUser(user);
        pet.setUser_id(user.getId());
        pet.setShop_id(pet.getShop().getId());
        petMapper.update(pet);
        //3.生成订单  一次性
        AdoptOrder order = initAdoptOrder(pet, user);

        //骚操作:为了后边操作支付单,不用再来修改订单,先生成统一的支付单号
        String unionPaySn = CodeGenerateUtils.generateUnionPaySn();
        order.setPaySn(unionPaySn);

        adoptOrderMapper.save(order);

        // 3.1 生成订单地址
        UserAddress userAddress = userAddressMapper.loadById(addressId);
        OrderAddress orderAddress = userAddress2OrderAddress(order, userAddress);
        orderAddressMapper.save(orderAddress);

        // 4.支付单
        PayBill payBill = initPayBill(payMethod, pet, user, order);
        payBillMapper.save(payBill);

        //调用统一支付接口(老杨定义的)
        return payBillService.pay(payBill);

        // 5.订单定时取消任务 @TODO
    }

    private AdoptOrder initAdoptOrder(Pet pet, User user) {
        AdoptOrder order = new AdoptOrder();
        order.setDigest("【摘要】" + pet.getName());
        order.setPrice(pet.getSaleprice());
        order.setOrderSn(CodeGenerateUtils.generateOrderSn(user.getId()));
        order.setLastConfirmTime(new Date(System.currentTimeMillis() + 15*60*1000));//最后确认时间
        order.setPet_id(pet.getId());
        order.setUser_id(user.getId());
        order.setShop_id(pet.getShop().getId());
        return order;
    }

    private OrderAddress userAddress2OrderAddress(AdoptOrder order, UserAddress userAddress) {
        OrderAddress orderAddress = new OrderAddress();
        BeanUtils.copyProperties(userAddress, orderAddress);
        orderAddress.setId(null);
        orderAddress.setOrder_id(order.getId());
        orderAddress.setOrderSn(order.getOrderSn());
        return orderAddress;
    }

    @Override
    public PageList<AdoptOrder> queryAdmin(AdoptOrderQuery query, Long loginInfoId) {
        //1.通过loginInfoID查询出Employee
        Employee employee = employeeMapper.loadByLoginInfoId(loginInfoId);
        //2.如果employee中的shopID不为null,就是店铺。否则就是平台员工
        if (employee.getShop_id() != null) {
            query.setShopId(employee.getShop_id());
        }
        return super.queryPage(query);
    }

    @Override
    public PageList<AdoptOrder> queryUser(AdoptOrderQuery query, Long loginInfoId) {
        User user = userMapper.loadByloginInfoId(loginInfoId);
        query.setUserId(user.getId());
        return super.queryPage(query);
    }

    private PayBill initPayBill(Integer payMethod, Pet pet, User user, AdoptOrder order) {
        PayBill payBill = new PayBill();
        payBill.setDigest(order.getDigest()+"支付单");
        payBill.setMoney(order.getPrice());
        //重点:支付唯一表示
        payBill.setUnionPaySn(order.getPaySn());
        payBill.setLastPayTime(new Date(System.currentTimeMillis() + PayConstants.LAST_TIME));
        payBill.setPayChannel(Long.valueOf(payMethod));//0 余额 1 支付宝 2 微信 3 银联
        payBill.setBusinessType(PayConstants.BUSINESS_TYPE_ADOPT);
        payBill.setBusinessKey(order.getId());
        payBill.setUser_id(user.getId());
        payBill.setShop_id(pet.getShop().getId());
        payBill.setNickName(user.getUsername());
        return payBill;
    }
PayConstants
public class PayConstants {
    
    public static final Integer LAST_TIME = 15*60*1000;
    //领养订单
    public static final String  BUSINESS_TYPE_ADOPT = "business_type_adopt";
    //服务订单
    public static final String  BUSINESS_TYPE_PRODUCT = "business_type_product";
    //收购订单
    public static final String  BUSINESS_TYPE_ACQUISITION = "business_type_acquisition";
    //商品订单
    public static final String  BUSINESS_TYPE_GOODS = "business_type_goods";
    //充值订单
    public static final String  BUSINESS_TYPE_RECHARGE = "business_type_recharge";
    
}
PayBill
@Data
public class PayBill extends BaseDomain {
    private String digest;
    private BigDecimal money;
    private String unionPaySn;
    private Integer state = 0;//0待支付1 已支付-1 取消
    private Date lastPayTime;
    private Long payChannel; //0 余额 1 支付宝 2 微信 3 银联
    private String businessType;
    private Long businessKey;
    private Date updateTime;
    private Date createTime = new Date();
    private Long user_id;
    private Long shop_id;
    private String nickName;
}
PayBillServiceImpl
	@Override
    public String pay(PayBill payBill) {
        if(payBill == null){
            throw new BusinessException("请生成支付单后,在进行支付!!!");
        }
        PayBill payBill1 = payBillMapper.loadByUnionPaySn(payBill.getUnionPaySn());
        if(payBill1 == null){
            throw new BusinessException("请生成支付单后,在进行支付!!!");
        }
        Long payChannel = payBill1.getPayChannel();
        String resultData = "";
        switch(payChannel.intValue()){
            case 1 ://支付宝
                AlipayInfo info = alipayInfoMapper.loadByShopId(payBill1.getShop_id());
                resultData =  AlipayUtil.pay(info, payBill1);
                break;
            case 2 ://微信
                //TODO
                break;
            case 3 ://银联
                //TODO
                break;
            default : //0 余额
                //TODO
        }
        return resultData;
    }
测试

adoptOrder.html

		methods: {
            orderSubmit() {
                this.$http.post("/adopt/submit", this.order)
                    .then(result => {
                        console.log(result.data);
                        // $("#alipayForm").html(result.data.resultObj);
                        // location.href = "personCenter.html";
                    })
                    .catch(result => {
                        alert("系统错误!!");
                    })

            }
        },

在这里插入图片描述

前台显示支付form表单

<div id="alipayForm">div>
		methods: {
            orderSubmit() {
                this.$http.post("/adopt/submit", this.order)
                    .then(result => {
                        console.log(result.data);
                        $("#alipayForm").html(result.data.resultObj);
                        // location.href = "personCenter.html";
                    })
                    .catch(result => {
                        alert("系统错误!!");
                    })

            }
        },

前台完成支付

form表单显示后会展示支付宝扫码或登录页面,完成支付后跳转到success回调页面,可从url取到unionPaySn查询数据显示到success页面。
B078-项目实战--支付模块 领养订单支付流程_第8张图片
B078-项目实战--支付模块 领养订单支付流程_第9张图片
B078-项目实战--支付模块 领养订单支付流程_第10张图片

异步回调通知后完成后续逻辑

AlipayController

    @PostMapping("/notify")
    public void notify(HttpServletRequest request){
        System.out.println("支付宝异步回调!");
        //获取支付宝POST过来反馈信息
        try {
            Map<String,String> params = new HashMap<String,String>();
            Map<String,String[]> requestParams = request.getParameterMap();
            for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
                String name = (String) iter.next();
                String[] values = (String[]) requestParams.get(name);
                String valueStr = "";
                for (int i = 0; i < values.length; i++) {
                    valueStr = (i == values.length - 1) ? valueStr + values[i]
                            : valueStr + values[i] + ",";
                }
                valueStr = new String(valueStr);
                params.put(name, valueStr);
            }

            String unionPaySn = params.get("out_trade_no");
            PayBill payBill = payBillService.loadByUnionPaySn(unionPaySn);
            if(payBill != null){
                AlipayInfo info = alipayInfoService.getByShopId(payBill.getShop_id());
                boolean signVerified = AlipaySignature.rsaCheckV1(params,
                        info.getAlipay_public_key(),
                        AlipayConfig.charset,
                        AlipayConfig.sign_type); //调用SDK验证签名

                if(signVerified) {//验证成功
                    //商户订单号
                    String out_trade_no = unionPaySn;
                    //支付宝交易号
                    String trade_no = request.getParameter("trade_no");
                    //交易状态
                    String trade_status = request.getParameter("trade_status");
                    if(trade_status.equals("TRADE_FINISHED")){
                        //用户确认
                    }else if (trade_status.equals("TRADE_SUCCESS")){
                        //1. 改支付单状态
                        payBill.setState(1);
                        payBill.setUpdateTime(new Date());
                        payBillService.update(payBill);
                        String businessType = payBill.getBusinessType();
                        //2.再修改对应(领养)的订单状态  businessType   businessKey 订单
                        if(PayConstants.BUSINESS_TYPE_ADOPT.equals(businessType)){//领养订单
                            Long orderId = payBill.getBusinessKey();
                            AdoptOrder order = adoptOrderService.getById(orderId);
                            order.setState(1);
                            adoptOrderService.update(order);
                        }
                    }
                }else {//验证失败
                    System.out.println("老宋,不要搞事");
                    //调试用,写文本函数记录程序运行情况是否正常
                    //String sWord = AlipaySignature.getSignCheckContentV1(params);
                    //AlipayConfig.logResult(sWord);
                }
            }
        } catch (AlipayApiException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

订单与支付汇总流程图

B078-项目实战--支付模块 领养订单支付流程_第11张图片

你可能感兴趣的:(笔记总结,后端,java)