官网地址
当面付帮助商家在线下消费场景中实现快速收款,支持 条码支付 和 扫码支付 两种付款方式。商家可通过以下两种任一方式进行收款,提升收银效率,实现资金实时到账。
条码支付:买家出示支付宝钱包中的条码、二维码,商家扫描用户条码即可完成 条码支付 收款。
扫码支付:买家通过使用支付宝 扫一扫 功能,扫描商家收款二维码即可完成 扫码支付 付款。
- APPID
- 商家私钥
- 支付宝公钥
- 支付回调地址
- 网关地址
- 加密签名算法RSA2
最终达成的效果
- 新建一个springboot工程
- pom.xml引入支付相关依赖
- 定义application.yml和application-dev.yml配置支付相关参数
- 定义支付的配置类AlipayConfig.java
- 定义二维码生成类QRCodeUtil.java和QrResponse.java
- 定义支付接口AlipayService.java和实现类AlipayServiceImpl.java
- 定义IndexController和AlipayController进行页面转发
- 在index.html中定义img标签引入支付宝二维码
- 测试扫码进入支付回调获取支付相关的参数
- 真实业务场景分析和封装支付二维码弹窗
daniel-alipay
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-freemarkerartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13version>
dependency>
<dependency>
<groupId>com.alipay.sdkgroupId>
<artifactId>alipay-sdk-javaartifactId>
<version>4.13.0.ALLversion>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
<version>3.10version>
dependency>
<dependency>
<groupId>com.google.zxinggroupId>
<artifactId>coreartifactId>
<version>3.3.0version>
dependency>
<dependency>
<groupId>commons-codecgroupId>
<artifactId>commons-codecartifactId>
<version>1.11version>
dependency>
<dependency>
<groupId>commons-httpclientgroupId>
<artifactId>commons-httpclientartifactId>
<version>3.1version>
dependency>
<dependency>
<groupId>com.github.ulisesbocchiogroupId>
<artifactId>jasypt-spring-boot-starterartifactId>
<version>2.1.0version>
dependency>
application.yml
配置相关测试参数application.yml
server:
port: 8989
spring:
freemarker:
suffix: .html
profiles:
active: dev
index.html
页面DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>支付宝title>
head>
<body>
<h1>支付宝二维码支付h1>
body>
html>
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";
}
}
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/
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 + '\'' +
'}';
}
}
分析是否注入成功,看下面:
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;
}
}
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);
}
}
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
:则可以看到类似下面的二维码
测试扫码进入支付回调获取支付相关的参数
真实业务场景分析和封装支付二维码弹窗
实现步骤
- 创建数据库
daniel-db
,导入SQL课程产品脚本- 在项目中追加引入
mybatis-plus
相关依赖与数据库链接- 在application.yaml中配置如下信息
- 在application-dev.xml 配置数据的链接相关信息
- 在springboot的启动类中增加
@MapperScan
注解- 然后在项目中分别创建:
entity,mapper,service
和controller
- 获取定义支付产品页面
- 在页面中引入
vue.min.js
和axios.min.js
和main.css
- 完成课程产品的数据异步渲染工作
- 在浏览器访问测试异步查询课程信息
- 最后完整的项目结构
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相关依赖与数据库链接
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.47version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.0version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
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
# 数据库连接
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
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,service
和controller
在 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;
}
ProductCourseMapper.java
并继承 BaseMapperpackage 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> {
}
ProductCourseService.java
并继承 IServicepackage 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> {
}
ProductCourseServiceImpl.java
继承 ServiceImpl 并实现接口 ProductCourseServicepackage 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{
}
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);
}
}
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>
在浏览器访问:http://localhost:8989/api/course/list
使用postman 测试查询接口
在资源 获取 - 然后放置在项目路的static 和 templates即可
并在IndexController中定义跳转页面
@GetMapping("/main")
public String main(){
return "main";
}
测试浏览器输入: http://localhost:8989/main
修改 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>
<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>
修改 AlipayController.java
修改 main.html
后端打断点,并刷新浏览器,查看控制台
再次添加绑定取数据库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;
}
}
刷新浏览器测试,则支付已经打通,点击支付将显示对应自己的支付二维码
- 增强支付的的类型
- 增加订单明细相关的业务
- 完成定时轮询监听支付回调的开发工作
- 支付过程中的细节已经优化
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;
}
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> {
}
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 {
}
在 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=¶m&
*/
/**
* 异步通知
*/
@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;
}
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();
}
}