四、支付宝支付对接 - SDK开发、业务对接、支付回调、支付组件(2)

一、SpringBoot - 支付宝实现当面付产品支付(二维码扫码支付)

1.1 当面付产品介绍

官网地址

  1. 简介

当面付帮助商家在线下消费场景中实现快速收款,支持 条码支付 和 扫码支付 两种付款方式。商家可通过以下两种任一方式进行收款,提升收银效率,实现资金实时到账。
条码支付:买家出示支付宝钱包中的条码、二维码,商家扫描用户条码即可完成 条码支付 收款。
扫码支付:买家通过使用支付宝 扫一扫 功能,扫描商家收款二维码即可完成 扫码支付 付款。

  1. 整体开发流程

在这里插入图片描述
3. 准备参数

  • APPID
  • 商家私钥
  • 支付宝公钥
  • 支付回调地址
  • 网关地址
  • 加密签名算法RSA2

最终达成的效果

1.2 具体实现步骤

  1. 新建一个springboot工程
  2. pom.xml引入支付相关依赖
  3. 定义application.yml和application-dev.yml配置支付相关参数
  4. 定义支付的配置类AlipayConfig.java
  5. 定义二维码生成类QRCodeUtil.java和QrResponse.java
  6. 定义支付接口AlipayService.java和实现类AlipayServiceImpl.java
  7. 定义IndexController和AlipayController进行页面转发
  8. 在index.html中定义img标签引入支付宝二维码
  9. 测试扫码进入支付回调获取支付相关的参数
  10. 真实业务场景分析和封装支付二维码弹窗

1.3 搭建测试

  1. 新建一个springboot 工程 daniel-alipay

在这里插入图片描述

在这里插入图片描述

  1. pom.xml引入支付相关依赖,将使用下面依赖替换
<!--页面渲染模板引擎freemaker-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!--web依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--实体依赖-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<!--测试依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13</version>
</dependency>
<!-- alipay sdk 支付依赖 -->
<dependency>
    <groupId>com.alipay.sdk</groupId>
    <artifactId>alipay-sdk-java</artifactId>
    <version>4.13.0.ALL</version>
</dependency>
<!--依赖支付的空判断-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.10</version>
</dependency>
<!--二维码生成-->
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.3.0</version>
</dependency>
<!--加密依赖-->
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.11</version>
</dependency>
<!--http请求-->
<dependency>
    <groupId>commons-httpclient</groupId>
    <artifactId>commons-httpclient</artifactId>
    <version>3.1</version>
</dependency>
<!--配置文件ENC加密处理-->
<dependency>
    <groupId>com.github.ulisesbocchio</groupId>
    <artifactId>jasypt-spring-boot-starter</artifactId>
    <version>2.1.0</version>
</dependency>
  1. 定义application.yml配置相关测试参数

application.yml

server:
  port: 8989
spring:
  freemarker:
    suffix: .html
  profiles:
    active: dev
  1. 在 templates 下面创建一个 index.html 页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>支付宝</title>
</head>
<body>

<h1>支付宝二维码支付</h1>

</body>
</html>
  1. 创建包 com.zql.controller,并在下面创建一个 IndexController.java
package com.zql.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * @Author:Daniel
 * @Version 1.0
 */

@Controller
public class IndexController {
    
    
    @GetMapping("/index")
    public String index(){
        
        return "index";
    }
    
}
  1. 启动程序测试 http://localhost:8989/index在这里插入图片描述

1.4 配置类的定义和注入

  1. 在resources下面定义application-dev.yml配置支付相关参数

application-dev.yml

# 支付宝支付参数配置
alipay:
  app_id: 2021003157607237
  merchant_private_key: 公司支付宝商户私钥
  alipay_public_key: 公司支付宝公钥(这个公钥是通过商家公钥换取的公钥哦)
  notify_url: 公司支付宝异步回调地址
  return_url: 公司支付宝同步回调地址(如果是二维码扫码可以不配置,和上面异步暂配一致)
  sign_type: RSA2
  charset: utf-8
  gatewayUrl: 支付宝网关地址
  # 保存支付日志的地址 如果是linux服务器配置没有盘符
  log_path: c:/tmp/

在这里插入图片描述

  1. 创建包 com.zql.config 并且定义支付的配置类 AlipayConfig.java 拷贝下面代码
package com.zql.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @Author:Daniel
 * @Version 1.0
 */
@Component
public class AlipayConfig {
    // 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
    @Value("${alipay.app_id}")
    public  String app_id;
    // 商户私钥,您的PKCS8格式RSA2私钥
    @Value("${alipay.merchant_private_key}")
    public String merchant_private_key;
    // 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
    @Value("${alipay.alipay_public_key}")
    public String alipay_public_key;
    // 服务器异步通知页面路径  需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
    @Value("${alipay.notify_url}")
    public  String notify_url;
    // 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
    @Value("${alipay.return_url}")
    public  String return_url;
    // 签名方式
    @Value("${alipay.sign_type}")
    public  String sign_type;
    // 字符编码格式
    @Value("${alipay.charset}")
    public  String charset;
    // 支付宝网关
    @Value("${alipay.gatewayUrl}")
    public String gatewayUrl;
    // 日志存放
    @Value("${alipay.log_path}")
    public String log_path;
    public String getApp_id() {
        return app_id;
    }
    public void setApp_id(String app_id) {
        this.app_id = app_id;
    }
    public String getMerchant_private_key() {
        return merchant_private_key;
    }
    public void setMerchant_private_key(String merchant_private_key) {
        this.merchant_private_key = merchant_private_key;
    }
    public String getAlipay_public_key() {
        return alipay_public_key;
    }
    public void setAlipay_public_key(String alipay_public_key) {
        this.alipay_public_key = alipay_public_key;
    }
    public String getNotify_url() {
        return notify_url;
    }
    public void setNotify_url(String notify_url) {
        this.notify_url = notify_url;
    }
    public String getReturn_url() {
        return return_url;
    }
    public void setReturn_url(String return_url) {
        this.return_url = return_url;
    }
    public String getSign_type() {
        return sign_type;
    }
    public void setSign_type(String sign_type) {
        this.sign_type = sign_type;
    }
    public String getCharset() {
        return charset;
    }
    public void setCharset(String charset) {
        this.charset = charset;
    }
    public String getGatewayUrl() {
        return gatewayUrl;
    }
    public void setGatewayUrl(String gatewayUrl) {
        this.gatewayUrl = gatewayUrl;
    }
    public String getLog_path() {
        return log_path;
    }
    public void setLog_path(String log_path) {
        this.log_path = log_path;
    }
    @Override
    public String toString() {
        return "AlipayConfig{" +
                "app_id='" + app_id + '\'' +
                ", merchant_private_key='" + merchant_private_key + '\'' +
                ", alipay_public_key='" + alipay_public_key + '\'' +
                ", notify_url='" + notify_url + '\'' +
                ", return_url='" + return_url + '\'' +
                ", sign_type='" + sign_type + '\'' +
                ", charset='" + charset + '\'' +
                ", gatewayUrl='" + gatewayUrl + '\'' +
                ", log_path='" + log_path + '\'' +
                '}';
    }
}

分析是否注入成功,看下面:

在这里插入图片描述

二、二维码生成工具

2.1 导入相关资料

  1. 导入二维码生成类QRCodeUtil.java和QrResponse.java和其它 👇🏾👇🏾 资源获取

在这里插入图片描述

  1. 创建包 com.zql.service 定义支付接口 AlipayService.java和实现类 AlipayServiceImpl.java(直接拷贝)

定义支付接口 AlipayService.java

package com.zql.service;

import com.zql.vo.PayVo;

/**
 * @author 徐柯
 * @Title:
 * @Package
 * @Description:
 */
public interface AlipayService {
    /**
     * @return byte[]
     * @Author Daniel
     * @Description 阿里支付接口
     * @Param [payVo]
     **/
    byte[] alipay(PayVo payVo);
}

定义支付接口实现 AlipayServiceImpl.java

package com.zql.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradePrecreateModel;
import com.alipay.api.request.AlipayTradePrecreateRequest;
import com.alipay.api.response.AlipayTradePrecreateResponse;

import com.zql.config.AlipayConfig;
import com.zql.qrcode.QRCodeUtil;
import com.zql.qrcode.QrCodeResponse;
import com.zql.qrcode.QrResponse;
import com.zql.util.GenerateNum;
import com.zql.vo.PayVo;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.ResourceUtils;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageOutputStream;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
/*
 * @Author Daniel
 * @Description
 * @Param
 * @return
 **/
@Service
@Log4j2
public class AlipayServiceImpl implements AlipayService {

    @Autowired
    private AlipayConfig alipayConfig;
    /*
     * @Author Daniel
     * @Description 阿里支付 -打赏
     * @Param [payVo]
     * @return byte[]
     **/
    @Override
    public byte[] alipay(PayVo payVo) {
        try {
            // 1:支付的用户
            String userId = payVo.getUserId();
            // 2: 支付金额
            String money = "1";
            // 3: 支付的产品
            String title = "java面向对象";
            // 4: 支付的订单编号
            String orderNumber = GenerateNum.generateOrder();
            // 5:支付宝携带的参数在回调中可以通过request获取
            JSONObject json = new JSONObject();
            json.put("userId", userId);
            json.put("orderNumber", orderNumber);
            json.put("money", money);
            String params = json.toString();
            // 6:设置支付相关的信息
            AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
            model.setOutTradeNo(orderNumber); // 自定义订单号
            model.setTotalAmount(money);// 支付金额
            model.setSubject(title);// 支付的产品名称
            model.setBody(params);// 支付的请求体参数
            model.setTimeoutExpress("30m");// 支付的超时时间
            model.setStoreId(userId+"");// 支付的库存id
            QrCodeResponse qrCodeResponse = qrcodePay(model);
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            String logopath = ResourceUtils.getFile("classpath:favicon.png").getAbsolutePath();
            BufferedImage buffImg = QRCodeUtil.encode(qrCodeResponse.getQr_code(), logopath, false);//获取二维码
            ImageOutputStream imageOut = ImageIO.createImageOutputStream(output);
            ImageIO.write(buffImg, "JPEG", imageOut);
            imageOut.close();
            ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());
            return FileCopyUtils.copyToByteArray(input);
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }
    /**
     * 扫码运行代码
     * 验签通过返回QrResponse
     * 失败打印日志信息
     * 参考地址:https://opendocs.alipay.com/apis/api_1/alipay.trade.app.pay
     *
     * @param model
     * @return
     */
    public QrCodeResponse qrcodePay(AlipayTradePrecreateModel model) {
        // 1: 获取阿里请求客户端
        AlipayClient alipayClient = getAlipayClient();
        // 2: 获取阿里请求对象
        AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
        // 3:设置请求参数的集合,最大长度不限
        request.setBizModel(model);
        // 设置异步回调地址
        request.setNotifyUrl(alipayConfig.getNotify_url());
        // 设置同步回调地址
        request.setReturnUrl(alipayConfig.getReturn_url());
        AlipayTradePrecreateResponse alipayTradePrecreateResponse = null;
        try {
            alipayTradePrecreateResponse = alipayClient.execute(request);
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        QrResponse qrResponse = JSON.parseObject(alipayTradePrecreateResponse.getBody(), QrResponse.class);
        return qrResponse.getAlipay_trade_precreate_response();
    }
    /**
     * 获取AlipayClient对象
     *
     * @return
     */
    private AlipayClient getAlipayClient() {
        AlipayClient alipayClient =
                new DefaultAlipayClient(alipayConfig.getGatewayUrl(), alipayConfig.getApp_id(), alipayConfig.getMerchant_private_key(),
                        "JSON", alipayConfig.getCharset(), alipayConfig.getAlipay_public_key(), alipayConfig.getSign_type()); //获得初始化的AlipayClient
        return alipayClient;
    }
}

2.2 生成支付二维码

  1. 创建包 com.zql.web 定义 AlipayController.java 进行页面转发,因为IndexController上面已经创建过了

AlipayController.java

package com.zql.web;

import com.zql.service.AlipayService;
import com.zql.vo.PayVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @Author:Daniel
 * @Version 1.0
 */

@Controller
public class AlipayController {

    @Autowired
    private AlipayService alipayService;

    @GetMapping("/alipay/pay")
    @ResponseBody
    public byte[] alipay(){

        PayVo payVo = new PayVo();
        payVo.setUserId("1");
        payVo.setCourseid("1");
        return alipayService.alipay(payVo);
    }

}
  1. 在index.html中定义img标签引入支付宝二维码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>支付宝</title>
</head>
<body>

        <h1>支付宝二维码支付</h1>
        <img src="/alipay/pay" alt="没找到">
</body>
</html>

在这里插入图片描述

浏览器刷新 localhost:8089/index:则可以看到类似下面的二维码

在这里插入图片描述

在这里插入图片描述

  1. 测试扫码进入支付回调获取支付相关的参数

  2. 真实业务场景分析和封装支付二维码弹窗

三、SpringBoot - 支付宝二维码支付 -数据渲染工作

3.1 产品数据接口的定义和测试

实现步骤

  1. 创建数据库 daniel-db,导入SQL课程产品脚本
  2. 在项目中追加引入mybatis-plus 相关依赖与数据库链接
  3. 在application.yaml中配置如下信息
  4. 在application-dev.xml 配置数据的链接相关信息
  5. 在springboot的启动类中增加@MapperScan注解
  6. 然后在项目中分别创建:entity,mapper,service和controller
  7. 获取定义支付产品页面
  8. 在页面中引入vue.min.jsaxios.min.jsmain.css
  9. 完成课程产品的数据异步渲染工作
  10. 在浏览器访问测试异步查询课程信息
  11. 最后完整的项目结构
  1. 创建数据库daniel-db,导入SQL课程产品脚本
/*
Navicat MySQL Data Transfer
Source Server         : localhost
Source Server Version : 50733
Source Host           : localhost:3306
Source Database       : daniel-db
Target Server Type    : MYSQL
Target Server Version : 50733
File Encoding         : 65001
Date: 2022-10-15 19:48:54
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for kss_courses
-- ----------------------------
DROP TABLE IF EXISTS `kss_courses`;
CREATE TABLE `kss_courses` (
  `courseid` VARCHAR(32) NOT NULL COMMENT '课程唯一id',
  `title` VARCHAR(100) DEFAULT NULL COMMENT '课程标题',
  `intro` VARCHAR(500) DEFAULT NULL COMMENT '课程简短介绍',
  `img` VARCHAR(300) DEFAULT NULL COMMENT '课程封面地址',
  `price` DECIMAL(10,2) DEFAULT NULL COMMENT '课程的活动价',
  `status` INT(1) DEFAULT NULL COMMENT '状态:已发布/未发布',
  `create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
  `update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`courseid`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of kss_courses
-- ----------------------------
INSERT INTO `kss_courses` VALUES ('1317503462556848129', '预科阶段', '学习编程之前你要了解的知识!', '/assert/course/c1/02.jpg', '0.01', '1', '2021-10-18 00:31:18', '2022-04-01 10:56:38');
INSERT INTO `kss_courses` VALUES ('1317503769349214209', '入门环境搭建', '工欲善其事,必先利其器!', '/assert/course/c1/03.jpg', '0.01', '1', '2021-10-18 00:32:31', '2022-04-01 10:53:10');
INSERT INTO `kss_courses` VALUES ('1317504142650658818', '基础语法学习', '基础决定你未来的高度!', '/assert/course/c1/04.jpg', '0.01', '1', '2021-10-18 00:34:00', '2022-04-01 10:54:18');
INSERT INTO `kss_courses` VALUES ('1317504447027105793', '流程控制学习', '程序的本质就是这些!', '/assert/course/c1/05.jpg', '0.01', '1', '2021-10-18 00:35:13', '2022-04-01 10:56:03');
INSERT INTO `kss_courses` VALUES ('1317504610634321921', '方法详解', '封装的思想!', '/assert/course/c1/06.jpg', '0.01', '1', '2021-10-18 00:35:52', '2022-04-01 10:55:04');
INSERT INTO `kss_courses` VALUES ('1317504817342205954', '数组详解', '最简单的数据结构!', '/assert/course/c1/07.jpg', '0.01', '1', '2021-10-18 00:35:52', '2022-10-18 00:35:52');
INSERT INTO `kss_courses` VALUES ('1317504988834713602', '面向对象编程', 'Java的精髓OOP!', '/assert/course/c1/08.jpg', '0.01', '1', '2021-10-18 00:35:52', '2022-10-18 00:35:52');
INSERT INTO `kss_courses` VALUES ('1377518279077142529', '第三方支付课程-支付宝', '第三方支付课程-支付宝', '/assert/course/c10/07.jpg', '0.01', '1', '2021-10-18 00:18:08', '2022-04-01 10:54:25');
-- ----------------------------
-- Table structure for kss_order_detail
-- ----------------------------
DROP TABLE IF EXISTS `kss_order_detail`;
CREATE TABLE `kss_order_detail` (
  `id` BIGINT(20) NOT NULL,
  `courseid` VARCHAR(20) DEFAULT NULL,
  `coursetitle` VARCHAR(255) DEFAULT NULL,
  `courseimg` VARCHAR(255) DEFAULT NULL,
  `userid` VARCHAR(32) DEFAULT NULL,
  `ordernumber` VARCHAR(100) DEFAULT NULL,
  `tradeno` VARCHAR(100) DEFAULT NULL,
  `create_time` DATETIME DEFAULT NULL,
  `update_time` DATETIME DEFAULT NULL,
  `username` VARCHAR(100) DEFAULT NULL,
  `price` VARCHAR(10) DEFAULT NULL,
  `paymethod` VARCHAR(10) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of kss_order_detail
-- ----------------------------
INSERT INTO `kss_order_detail` VALUES ('1377561070331342849', '1317503462556848129', '预科阶段', '/assert/course/c1/02.jpg', '1', '2021040117565301', '2021040122001474081439646913', '2021-04-01 17:58:48', '2021-04-01 17:58:48', 'daniel', '0.01', '1');
INSERT INTO `kss_order_detail` VALUES ('1377561833455484929', '1317503769349214209', '入门环境搭建', '/assert/course/c1/03.jpg', '1', '2021040118015901', '2021040122001474081440101072', '2021-04-01 18:01:50', '2021-04-01 18:01:50', 'daniel', '0.01', '1');
INSERT INTO `kss_order_detail` VALUES ('1377562728612233218', '1317503769349214209', '入门环境搭建', '/assert/course/c1/03.jpg', '1', '2021040118053301', '2021040122001474081440405818', '2021-04-01 18:05:23', '2021-04-01 18:05:23', 'daniel', '0.01', '1');
INSERT INTO `kss_order_detail` VALUES ('1377564997252657153', '1317504142650658818', '基础语法学习', '/assert/course/c1/04.jpg', '1', '2021040118134201', '2021040122001474081440148822', '2021-04-01 18:14:24', '2021-04-01 18:14:24', 'daniel', '0.01', '1');

在这里插入图片描述
2. 在项目中追加引入 mybatis-plus相关依赖与数据库链接

<!--mysql 如果数据库是5.7或以上无需变动版本 8.0.17 ,否则降低版本-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
<!--mybatis-plus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

在这里插入图片描述

  1. 在 application.yaml 中配置如下信息
server:
  port: 8989
spring:
  freemarker:
    suffix: .html
  profiles:
    active: dev
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
    locale: zh_CN
    # 解决json返回过程中long的精度丢失问题
    generator:
      write-numbers-as-strings: true
      write-bigdecimal-as-plain: true
    servlet:
      content-type: text/html
      multipart:
        max-file-size: 2MB
        max-request-size: 2MB
    mvc:
      servlet:
        load-on-startup: 1 #SpringBoot的接口第一次访问都很慢,通过日志可以发现,dispatcherServlet不是一开始就加载的,有访问才开始加载的,即懒加载。
    session:
      store-type: redis
      # session退出以后30分钟清除信息
      timeout: 1800
    main:
      allow-bean-definition-overriding: true
# mybatis-plus配置
mybatis-plus:
  mapper-locations: classpath*:/mapper/*.xml
  type-aliases-package: com.zql.entity
  1. 在 application-dev.xml 配置数据的链接相关信息
# 数据库连接
spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/daniel-db?serverTimezone=GMT%2b8&useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: root
    hikari:
      connection-timeout: 60000
      validation-timeout: 3000
      idle-timeout: 60000
      login-timeout: 5
      max-lifetime: 60000
      maximum-pool-size: 400
      minimum-idle: 100
      read-only: false
# 日志管理
logging:
  level:
    root: info
  1. 在springboot的启动类中增加@MapperScan注解
package com.zql;

import com.zql.config.AlipayConfig;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.zql.mapper")
public class DanielAlipayApplication {

    public static void main(String[] args) {
        SpringApplication.run(DanielAlipayApplication.class, args);
    }

}

在这里插入图片描述
6. 然后在项目中分别创建包:entity,mapper,servicecontroller

在 entity下创建数据库对应实体 ProductCourse.java

package com.zql.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
import java.util.Date;
/**
 * @Author:Daniel
 * @Version 1.0
 */
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@TableName("kss_courses")
public class ProductCourse {
    // 课程ID
    @TableId(type = IdType.ID_WORKER_STR)
    private Long courseid;
    // 课程标题
    private String title;
    // 课程介绍
    private String intro;
    // 课程封面
    private String img;
    // 课程价格
    private BigDecimal price;
    // 课程状态
    private Integer status;
    // 课程创建时间
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    // 课程更新时间
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
}
  1. 在mapper下面创建接口 ProductCourseMapper.java 并继承 BaseMapper
package com.zql.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zql.entity.ProductCourse;
import org.springframework.stereotype.Repository;

/**
 * @Author:Daniel
 * @Version 1.0
 */

@Repository
public interface ProductCourseMapper extends BaseMapper<ProductCourse> {
    
}

  1. 在 service下面创建接口 ProductCourseService.java 并继承 IService
package com.zql.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.zql.entity.ProductCourse;

/**
 * @Author:Daniel
 * @Version 1.0
 */
public interface ProductCourseService extends IService<ProductCourse> {
    
}
  1. 在 service下面创建类 ProductCourseServiceImpl.java 继承 ServiceImpl 并实现接口 ProductCourseService
package com.zql.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zql.entity.ProductCourse;
import com.zql.mapper.ProductCourseMapper;
import com.zql.service.ProductCourseService;
import org.springframework.stereotype.Service;

/**
 * @Author:Daniel
 * @Version 1.0
 */
@Service
public class ProductCourseServiceImpl extends ServiceImpl<ProductCourseMapper, ProductCourse> implements ProductCourseService{

}
  1. 在 com.zql.web下面创建 CourseController.java
package com.zql.web;

import com.zql.entity.ProductCourse;
import com.zql.service.ProductCourseService;
import com.zql.vo.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;

/**
 * @Author:Daniel
 * @Version 1.0
 */

@Controller
public class CourseController {

    @Autowired
    private ProductCourseService productCourseService;


    @GetMapping("/api/course/list")
    @ResponseBody
    public R main() {
        List<ProductCourse> courseList = productCourseService.list();
        return R.ok().data("courseList", courseList);
    }
}
  1. 在 templates/mapper/ProductCourseMapper.xml(可以不配置)
<?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.zql.mapper.ProductCourseMapper">
</mapper>
  1. 测试查询接口

在浏览器访问:http://localhost:8989/api/course/list

在这里插入图片描述

使用postman 测试查询接口

在这里插入图片描述

3.2 Vue 实现产品页面和数据渲染

  1. 获取定义支付产品页面(静态数据,即非数据库)

资源 获取 - 然后放置在项目路的static 和 templates即可
在这里插入图片描述

并在IndexController中定义跳转页面

@GetMapping("/main")
public String main(){

    return "main";
}

在这里插入图片描述

测试浏览器输入: http://localhost:8989/main

在这里插入图片描述

  1. 获取定义支付产品页面(动态获取,仅浏览器控制台显示)

修改 main.html

在这里插入图片描述

<script>
    var vue = new Vue({

        el:"#app",

        data:{

        },
        created:function () {
            //加载课程产品
            this.loadCourse();
        },
        methods:{

            loadCourse:function () {
                //产品课程的异步请求

                axios.post("/api/course/list").then(function (res) {

                   console.log("response===================>",res);
                })

            }
        }
    })

</script>

启动程序查看浏览器控制台:http://localhost:8989/main

在这里插入图片描述
修改代码:

在这里插入图片描述

<script>
    var vue = new Vue({
        el:"#app",

        data:{

            courseList:[]
        },
        created:function () {
            //加载课程产品
            this.loadCourse();
        },
        methods:{
            //产品课程的异步请求
            loadCourse:function () {

                var that =  this;
                axios.post("/api/course/list").then(function (res) {

                   console.log("response===================>",res);

                   if(res.data.code == 20000){

                       that.courseList = res.data.data.courseList;
                   }
                })

            }
        }
    })

</script>

再次启动程序发现渲染到了页面上
在这里插入图片描述

  1. 获取定义支付产品页面(动态获取,循环遍历数据库中的数据)

在这里插入图片描述

<div   v-for="(course,index) in courseList"  class="col-lg-3 col-md-4 col-sm-6 animated fadeInUp delay-1s">
    <div class="course-item">
        <div class="course-img "><a
                :href="'https://www.kuangstudy.com/course/detail/'+course.courseid"
                target="_blank" :title="course.title" class="course__img"><img
                height="140" width="100%" :src="'https://www.kuangstudy.com//'+course.img"> <span
                class="num">1</span> <span class="stimer">{{course.price}}</span></a></div>
        <div class="course-content"><h3  :title="course.title" class="course__title"><a
                href="https://www.kuangstudy.com/course/detail/1317503462556848129"
                target="_blank" :title="course.title" class="course__img">{{course.title}}</a>
        </h3>
            <p class="course__author">{{course.intro}}</p>
            <div class="course-price-wrap"><span class="course__btn"><i
                    class="iconfont iconshouye"></i>  点击支付</span></div>
        </div>
    </div>
</div>

在这里插入图片描述

3.3 产品数据和支付二维码对接

修改 AlipayController.java

在这里插入图片描述

修改 main.html

在这里插入图片描述

修改main.html 看是否能拿到 courseid
在这里插入图片描述

后端打断点,并刷新浏览器,查看控制台

在这里插入图片描述

在这里插入图片描述

结果成功绑定
在这里插入图片描述

再次添加绑定取数据库courseid

在这里插入图片描述

结果确实取到了 👇🏾👇🏾

在这里插入图片描述

然后当你点击哪一个支付,就会对应有自己的courseid ,即可有对应的二维码生成

在这里插入图片描述
在这里插入图片描述

<script>
    var vue = new Vue({
        el:"#app",

        data:{

            courseList:[],

            courseId:""
        },
        created:function () {
            //加载课程产品
            this.loadCourse();
        },
        methods:{
            //产品课程的异步请求
            loadCourse:function () {
                var that =  this;
                axios.post("/api/course/list").then(function (res) {

                   console.log("response===================>",res);

                   if(res.data.code == 20000){

                       that.courseList = res.data.data.courseList;

                       that.courseId = that.courseList[0].courseid;
                   }
                })
            },
            changePay:function (index) {
                //alert(1)
                var courseId = this.courseList[index].courseid;
                //alert(courseId)
                this.courseId = courseId;  //也可以一步到位  this.courseId = this.courseList[index].courseid;
            }
        }
    })
</script>

修改接口实现类 AlipayServiceImpl.java

    @Autowired
    private ProductCourseService productCourseService;

    @Override
    public byte[] alipay(PayVo payVo) {
        try {
            // 1:支付的用户
            String userId = payVo.getUserId();
            ProductCourse productCourse = productCourseService.getById(payVo.getCourseid());
            if(productCourse == null) return null;
            // 2: 支付金额
            String money = productCourse.getPrice().toString();
            // 3: 支付的产品
            String title = productCourse.getTitle();
            // 4: 支付的订单编号
            String orderNumber = GenerateNum.generateOrder();
            // 5:支付宝携带的参数在回调中可以通过request获取
            JSONObject json = new JSONObject();
            json.put("userId", userId);
            json.put("orderNumber", orderNumber);
            json.put("money", money);
            String params = json.toString();
            // 6:设置支付相关的信息
            AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
            model.setOutTradeNo(orderNumber); // 自定义订单号
            model.setTotalAmount(money);// 支付金额
            model.setSubject(title);// 支付的产品名称
            model.setBody(params);// 支付的请求体参数
            model.setTimeoutExpress("30m");// 支付的超时时间
            model.setStoreId(userId+"");// 支付的库存id
            QrCodeResponse qrCodeResponse = qrcodePay(model);
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            String logopath = ResourceUtils.getFile("classpath:favicon.png").getAbsolutePath();
            BufferedImage buffImg = QRCodeUtil.encode(qrCodeResponse.getQr_code(), logopath, false);//获取二维码
            ImageOutputStream imageOut = ImageIO.createImageOutputStream(output);
            ImageIO.write(buffImg, "JPEG", imageOut);
            imageOut.close();
            ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());
            return FileCopyUtils.copyToByteArray(input);
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

在这里插入图片描述

刷新浏览器测试,则支付已经打通,点击支付将显示对应自己的支付二维码

四、SpringBoot - 支付宝二维码支付 - 支付回调和轮询监听

4.1 支付回调流程概述

  1. 实现步骤
  1. 增强支付的的类型
  2. 增加订单明细相关的业务
  3. 完成定时轮询监听支付回调的开发工作
  4. 支付过程中的细节已经优化
  1. 增加订单明细相关业务,在entity包下创建 OrderDetail.java

package com.zql.entity;
        import com.baomidou.mybatisplus.annotation.*;
        import lombok.AllArgsConstructor;
        import lombok.Data;
        import lombok.NoArgsConstructor;
        import lombok.ToString;
        import lombok.experimental.Accessors;
        import java.util.Date;
/**
 * @author Daniel
 * @Title:
 * @Package
 * @Description:
 */
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@TableName("kss_order_detail")
public class OrderDetail {
    // 主键
    @TableId(value = "id", type = IdType.ID_WORKER)
    private Long id;
    // 支付课程id
    private String courseid;
    // 支付课程标题
    private String coursetitle;
    // 支付课程封面
    private String courseimg;
    // 支付价格
    private String price;
    // 支付用户
    private String userid;
    // 支付用户昵称
    private String username;
    // 支付流水订单号
    private String ordernumber;
    // 支付交易号
    private String tradeno;
    // 1 alipay 2 weixin
    private String paymethod;
    // 课程创建时间
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    // 课程更新时间
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
}
  1. 在mapper包下创建接口 OrderDetailMapper.java
package com.zql.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zql.entity.OrderDetail;
import org.springframework.stereotype.Repository;

/**
 * @Author:Daniel
 * @Version 1.0
 */
@Repository
public interface OrderDetailMapper extends BaseMapper<OrderDetail> {
}
  1. 在包 service和 impl 下面分别创建接口和实现类 OrderDetailService.java OrderDetailServiceImp.java

OrderDetailService.java

package com.zql.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.zql.entity.OrderDetail;
/**
 * @Author:Daniel
 * @Version 1.0
 */
public interface OrderDetailService extends IService<OrderDetail> {
}

OrderDetailServiceImp.java

package com.zql.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zql.entity.OrderDetail;
import com.zql.mapper.OrderDetailMapper;
import com.zql.service.OrderDetailService;
import org.springframework.stereotype.Service;
/**
 * @Author:Daniel
 * @Version 1.0
 */
@Service
public class OrderDetailServiceImpl extends ServiceImpl<OrderDetailMapper, OrderDetail> implements OrderDetailService {
}
  1. 完成支付回调

在 AlipayController.java 中完成回调

/**
* 定义支付回调的地址
*
* 1.第一步:打成一个jar,发布到正式服务器
* 2.第二步:购买一个域名:https://www.wmsspark.top/
* 3.第三步:部署项目到服务器上。 java -jar daniel-alipay.jar >>1.txt &
* 4.第四步:获取真实的回调地址 https://www.wmsspark.top/alipay/notifyUrl?body=&param&
*/
/**
 * 异步通知
 */
@ResponseBody
@RequestMapping("/alipay/notifyUrl")
public String notify_url(HttpServletRequest request) throws Exception {
    boolean result = alipayCallback(request);
    if (result) {
        return "success"; // 请不要修改或删除
    } else {
        // 验证失败
        return "fail";
    }
}
/**
 * 支付宝回调
 * @Author: Daniel
 * @return
 * @throws Exception
 */
private boolean alipayCallback(HttpServletRequest request) throws Exception {
    // 获取支付宝GET过来反馈信息
    Map<String, String> params = new HashMap<String, String>();
    Map requestParams = request.getParameterMap();
    for (Iterator 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] + ",";
        }
        params.put(name, new String(valueStr.getBytes("ISO-8859-1"), "UTF-8"));
    }
    // 计算得出通知验证结果
    Log.info("1:---->获取支付宝回传的参数---------------------------------->" + params);
    // 返回公共参数
    String extparamString = request.getParameter("extra_common_param");
    log.info("2---->:支付宝交易返回的参数:{}", extparamString);
    String tradeno = params.get("trade_no");;
    //交易完成
    String body = params.get("body");
    if (StringUtils.isEmpty(tradeno)) {
        tradeno = params.get("trade_no");
    }
    log.info("3---->:【支付宝】交易的参数信息是:{},流水号是:{}", body, tradeno);
    try {
        JSONObject bodyJson = JSONObject.parseObject(body);
        String userId = bodyJson.getString("userId");
        String ptype = bodyJson.getString("ptype");
        String orderNumber = bodyJson.getString("orderNumber");
        log.info("4---->:【支付宝】交易的参数信息是:ptype:{},orderNumber是:{}",  ptype,orderNumber);
        // 课程支付
        if (ptype != null && ptype.equalsIgnoreCase("productcourse")) {
            payCommonService.payproductcourse(bodyJson, userId, orderNumber, tradeno, "1");
        }
    } catch (Exception ex) {
        log.info("支付宝支付出现了异常.....");
        ex.printStackTrace();
        return false;
    }
    return true;
}
  1. 将回调地址添加到支付宝开放平台“授权回调地址”中
    在这里插入图片描述

  2. 打包上传到服务器,测试可得到数据库产生一条支付成功的数据。

  3. 在service包下创建 通用的支付 PayCommonService.java

package com.zql.service;

import com.alibaba.fastjson.JSONObject;
import com.zql.entity.OrderDetail;
import com.zql.entity.ProductCourse;
import com.zql.mapper.OrderDetailMapper;
import com.zql.mapper.ProductCourseMapper;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
 * @Author:Daniel
 * @Version 1.0
 */
@Component
@Log4j2
public class PayCommonService {
    @Autowired
    private OrderDetailMapper orderDetailMapper;
    @Autowired
    private ProductCourseMapper productCourseMapper;
    /**
     * 支付回调封装
     * @param jsonObject
     * @param userId
     * @param orderNumber
     * @param transaction_id
     * @param paymethod
     */
    public void payproductcourse(JSONObject jsonObject, String userId, String orderNumber, String transaction_id, String paymethod) {
        String courseId = jsonObject.getString("courseId");
        String money = jsonObject.getString("money");
        ProductCourse productCourse = productCourseMapper.selectById(courseId);
        if (productCourse == null) {
            log.info("【" + (paymethod.equals("2") ? "微信" : "支付宝") + "】你支付的课程被删除了:{}", courseId);
            return;
        }
        OrderDetail orderDetail = new OrderDetail();
        orderDetail.setUserid(userId);
        orderDetail.setCourseid(courseId);
        orderDetail.setUsername("Daniel");
        orderDetail.setPaymethod(paymethod);
        orderDetail.setCoursetitle(productCourse.getTitle());
        orderDetail.setCourseimg(productCourse.getImg());
        orderDetail.setOrdernumber(orderNumber);
        orderDetail.setTradeno(transaction_id);
        orderDetail.setPrice(money == null ? "0.01" : money);
        orderDetailMapper.insert(orderDetail);
    }
}

服务器端接收的回调信息

监听数据的异步回调如下
定义 OrderDetailController.java 监听支付是否成功

package com.zql.web;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zql.entity.OrderDetail;
import com.zql.service.OrderDetailService;
import com.zql.vo.PayVo;
import com.zql.vo.R;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

/**
 * @Author:Daniel
 * @description 支付成功和失败回调
 * @Version 1.0
 */
@Controller
@Log4j2
public class OrderDetailController {
    @Autowired
    private OrderDetailService orderDetailService;
    
    @PostMapping("/api/paycallback/course")
    @ResponseBody
    public R payCallback(@RequestBody PayVo payVo) {
        String userid = "1";
        QueryWrapper<OrderDetail> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userid", userid);
        queryWrapper.eq("courseid", payVo.getCourseid());
        int count = orderDetailService.count(queryWrapper);
        return count > 0 ? R.ok() : R.error();
    }
}

五、SpringBoot - 支付宝二维码支付 - 支付组件封装

六、Springboot - jasypt - 配置文件yml密码加密

七、支付宝统一支付参数接口说明

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Daniel521-Spark

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值