由于尚硅谷的视频是通过Eclipse软件来做的,其中有些操作与IDEA上有所区别,所以我在这里将IDEA区别于Eclipse的操作、操作过程中涉及的源码(与视频的源码略有出入)以及大家可能遇到的种种问题分享给大家,这些源码在我这里均是通过测试的,仅供参考!
1 模块配置
1.1 OrderVO类
新建member-entity\src\main\java\com\atguigu\crowd\entity\vo\OrderVO.java
package com.atguigu.crowd.entity.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderVO implements Serializable {
private static final long serialVersionUID = 1L;
// 主键
private Integer id;
// 订单号
private String orderNum;
// 支付宝流水单号
private String payOrderNum;
// 订单金额
private Double orderAmount;
// 是否开发票
private Integer invoice;
// 发票抬头
private String invoiceTitle;
// 备注
private String orderRemark;
// 收货地址主键id
private String addressId;
// 订单与项目的信息
private OrderProjectVO orderProjectVO;
}
1.2 Pom配置
修改pay-consumer\pom.xml
<dependencies>
<!-- api工程的依赖 -->
<dependency>
<groupId>com.atguigu.crowd</groupId>
<artifactId>atcrowdfunding17-member-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- 标配web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringBoot单元测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- thymeleaf模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入springboot&redis整合场景 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 引入springboot&springsession整合场景 -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alipay.sdk/alipay-sdk-java -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>3.3.49.ALL</version>
</dependency>
<!-- 配置文件提示 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
1.3 主启动类&Application
新建order-consumer\src\main\java\com\atguigu\crowd\CrowdMainClass.java
package com.atguigu.crowd;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
// 启用Feign客户端功能
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class CrowdMainClass {
public static void main(String[] args) {
SpringApplication.run(CrowdMainClass.class, args);
}
}
新建order-consumer\src\main\resources\application.yml
server:
port: 7000
spring:
application:
name: atguigu-crowd-pay
thymeleaf:
prefix: classpath:/templates/
suffix: .html
redis:
host: 127.0.0.1
session:
store-type: redis
eureka:
client:
service-url:
defaultZone: http://localhost:1000/eureka
ali:
pay:
alipay-public-key: 填写支付宝公钥
app-id: 填写APPID
charset: utf-8
gateway-url: https://openapi.alipaydev.com/gateway.do
merchant-private-key: 填写商家私钥
notify-url: 填写内网穿透地址
return-url: 填写返回
sign-type: RSA2
1.4 支付配置类
新建pay-consumer\src\main\java\com\atguigu\crowd\config\PayProperties.java
package com.atguigu.crowd.config;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Component
@ConfigurationProperties(prefix = "ali.pay")
public class PayProperties {
private String appId;
private String merchantPrivateKey;
private String alipayPublicKey;
private String notifyUrl;
private String returnUrl;
private String signType;
private String charset;
private String gatewayUrl;
}
1.5 Zuul路由规则修改
修改member-zuul\src\main\resources\application.yml
zuul:
ignored-services: "*"
sensitive-headers: "*" # 在Zuul向其他微服务重定向时保持原本头信息(请求头、响应头)
routes:
crowd-portal:
service-id: atguigu-crowd-auth
path: /** # 这里一定要使用两个“*”号,不然“/”路径后面的多层路径将无法访问
crowd-project:
service-id: atguigu-crowd-project
path: /project/** # 这里一定要使用两个“*”号,不然“/”路径后面的多层路径将无法访问
crowd-order:
service-id: atguigu-crowd-order
path: /order/**
crowd-pay:
service-id: atguigu-crowd-pay
path: /pay/**
2 支付功能
2.1 前端部分
修改order-consumer\src\main\resources\templates\confirm_order.html
<form id="summaryForm" action="pay/generate/order" method="post"></form>
<script>
$("#payButton").click(function(){
// 1.收集所有要提交的表单项的数据
var addressId = $("[name=addressId]:checked").val();
var invoice = $("[name=invoiceRadio]:checked").val();
var invoiceTitle = $.trim($("[name=invoiceTitle]").val());
var remark = $.trim($("[name=remark]").val());
// 2.将上面收集到的表单数据填充到空表单中并提交
$("#summaryForm")
.append("<input type='hidden' name='addressId' value='"+addressId+"'/>")
.append("<input type='hidden' name='invoice' value='"+invoice+"'/>")
.append("<input type='hidden' name='invoiceTitle' value='"+invoiceTitle+"'/>")
.append("<input type='hidden' name='orderRemark' value='"+remark+"'/>")
.submit();
});
$("#knowRoleCheckBox").click(function(){
var currentStatus = this.checked;
if(currentStatus) {
$("#payButton").prop("disabled", "");
}else{
$("#payButton").prop("disabled","disabled");
}
});
$('#myTab a').click(function(e) {
e.preventDefault()
$(this).tab('show')
})
</script>
2.2 后端部分
新建pay-consumer\src\main\java\com\atguigu\crowd\handler\PayHandler.java
package com.atguigu.crowd.handler;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.atguigu.crowd.api.MySQLRemoteService;
import com.atguigu.crowd.config.PayProperties;
import com.atguigu.crowd.entity.vo.OrderProjectVO;
import com.atguigu.crowd.entity.vo.OrderVO;
import com.atguigu.crowd.util.ResultEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.*;
@Controller
public class PayHandler {
@Autowired
private PayProperties payProperties;
@Autowired
private MySQLRemoteService mySQLRemoteService;
private Logger logger = LoggerFactory.getLogger(PayHandler.class);
@ResponseBody
@RequestMapping("/generate/order")
public String generateOrder(HttpSession session, OrderVO orderVO) throws AlipayApiException {
// 1.从 Session 域获取 orderProjectVO 对象
OrderProjectVO orderProjectVO = (OrderProjectVO) session.getAttribute("orderProjectVO");
// 2.将 orderProjectVO 对象和 orderVO 对象组装到一起
orderVO.setOrderProjectVO(orderProjectVO);
// 3.生成订单号并设置到 orderVO 对象中
String time = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); // 根据当前日期时间生成字符串
String user = UUID.randomUUID().toString().replace("-", "").toUpperCase(); // 使用 UUID 生成用户 ID 部分
String orderNum = time + user;
orderVO.setOrderNum(orderNum);
// 4.计算订单总金额并设置到 orderVO 对象中
Double orderAmount = (double) (orderProjectVO.getSupportPrice() * orderProjectVO.getReturnCount() + orderProjectVO.getFreight());
orderVO.setOrderAmount(orderAmount);
// 将OrderVO对象存入Session域
session.setAttribute("orderVO", orderVO);
// 5.调用专门封装好的方法给支付宝接口发送请求
return sendRequestToAliPay(orderNum, orderAmount, orderProjectVO.getProjectName(), orderProjectVO.getReturnContent());
}
/**
* 为了调用支付宝接口专门封装的方法
*
* @param outTradeNo 外部订单号,也就是商户订单号,也就是我们生成的订单号
* @param totalAmount 订单的总金额
* @param subject 订单的标题,这里可以使用项目名称
* @param body 商品的描述,这里可以使用回报描述
* @throws AlipayApiException
* @return 返回到页面上显示的支付宝登录界面
*/
private String sendRequestToAliPay(String outTradeNo, Double totalAmount, String subject, String body) throws AlipayApiException {
//获得初始化的AlipayClient
AlipayClient alipayClient = new DefaultAlipayClient(
payProperties.getGatewayUrl(),
payProperties.getAppId(),
payProperties.getMerchantPrivateKey(),
"json",
payProperties.getCharset(),
payProperties.getAlipayPublicKey(),
payProperties.getSignType());
//设置请求参数
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
alipayRequest.setReturnUrl(payProperties.getReturnUrl());
alipayRequest.setNotifyUrl(payProperties.getNotifyUrl());
alipayRequest.setBizContent("{\"out_trade_no\":\"" + outTradeNo + "\","
+ "\"total_amount\":\"" + totalAmount + "\","
+ "\"subject\":\"" + subject + "\","
+ "\"body\":\"" + body + "\","
+ "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
//若想给BizContent增加其他可选请求参数,以增加自定义超时时间参数timeout_express来举例说明
//alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
// + "\"total_amount\":\""+ total_amount +"\","
// + "\"subject\":\""+ subject +"\","
// + "\"body\":\""+ body +"\","
// + "\"timeout_express\":\"10m\","
// + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
//请求参数可查阅【电脑网站支付的API文档-alipay.trade.page.pay-请求参数】章节
//请求
return alipayClient.pageExecute(alipayRequest).getBody();
}
@ResponseBody
@RequestMapping("/return")
public String returnUrlMethod(HttpServletRequest request, HttpSession session) throws AlipayApiException,
UnsupportedEncodingException {
// 获取支付宝 GET 过来反馈信息
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.getBytes("ISO-8859-1"), "utf-8");
params.put(name, valueStr);
}
boolean signVerified = AlipaySignature.rsaCheckV1(
params,
payProperties.getAlipayPublicKey(),
payProperties.getCharset(),
payProperties.getSignType()); //调用 SDK 验证签名
// ——请在这里编写您的程序(以下代码仅作参考)——
if(signVerified) {
// 商户订单号
String orderNum = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"),"UTF-8");
// 支付宝交易号
String payOrderNum = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"),"UTF-8");
// 付款金额
String orderAmount = new String(request.getParameter("total_amount").getBytes("ISO-8859-1"),"UTF-8");
// 保存到数据库
// 1.从Session域中获取OrderVO对象
OrderVO orderVO = (OrderVO) session.getAttribute("orderVO");
// 2.将支付宝交易号设置到OrderVO对象中
orderVO.setPayOrderNum(payOrderNum);
// 3.发送给MySQL的远程接口
ResultEntity<String> resultEntity = mySQLRemoteService.saveOrderRemote(orderVO);
logger.info("Order save result="+resultEntity.getResult());
return "trade_no:"+payOrderNum+"<br/>out_trade_no:"+orderNum+"<br/>total_amount:"+orderAmount;
}else {
// 页面显示信息:验签失败
return "验签失败";
}
}
@RequestMapping("/notify")
public void notifyUrlMethod(HttpServletRequest request) throws UnsupportedEncodingException, AlipayApiException {
//获取支付宝POST过来反馈信息
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.getBytes("ISO-8859-1"), "utf-8");
params.put(name, valueStr);
}
boolean signVerified = AlipaySignature.rsaCheckV1(
params,
payProperties.getAlipayPublicKey(),
payProperties.getCharset(),
payProperties.getSignType()); //调用SDK验证签名
//——请在这里编写您的程序(以下代码仅作参考)——
/* 实际验证过程建议商户务必添加以下校验:
1、需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email)
4、验证app_id是否为该商户本身。
*/
if(signVerified) {//验证成功
//商户订单号
String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"),"UTF-8");
//支付宝交易号
String trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"),"UTF-8");
//交易状态
String trade_status = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"),"UTF-8");
logger.info("out_trade_no="+out_trade_no);
logger.info("trade_no="+trade_no);
logger.info("trade_status="+trade_status);
}else {//验证失败
logger.info("验证失败");
}
}
}
修改member-api\src\main\java\com\atguigu\crowd\api\MySQLRemoteService.java
@RequestMapping("/save/order/remote")
ResultEntity<String> saveOrderRemote(@RequestBody OrderVO orderVO);
修改mysql-provider\src\main\java\com\atguigu\crowd\handler\OrderProviderHandler.java
@RequestMapping("/save/order/remote")
ResultEntity<String> saveOrderRemote(@RequestBody OrderVO orderVO) {
try {
orderService.saveOrder(orderVO);
return ResultEntity.successWithoutData();
} catch (Exception e) {
e.printStackTrace();
return ResultEntity.failed(e.getMessage());
}
}
并新增saveOrder()方法及实现类:
修改mysql-provider\src\main\java\com\atguigu\crowd\service\impl\OrderServiceImpl.java
@Autowired
public OrderPOMapper orderPOMapper;
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
@Override
public void saveOrder(OrderVO orderVO) {
OrderPO orderPO = new OrderPO();
BeanUtils.copyProperties(orderVO, orderPO);
OrderProjectPO orderProjectPO = new OrderProjectPO();
BeanUtils.copyProperties(orderVO.getOrderProjectVO(), orderProjectPO);
// 保存orderPO自动生成的主键是orderProjectPO需要用到的外键
orderPOMapper.insert(orderPO);
// 从orderPO中获取orderId
Integer id = orderPO.getId();
// 将orderId设置到orderProjectPO
orderProjectPO.setOrderId(id);
orderProjectPOMapper.insert(orderProjectPO);
}
修改mysql-provider\src\main\resources\mybatis\mapper\OrderPOMapper.xml
<insert id="insert"
parameterType="com.atguigu.crowd.entity.po.OrderPO"
useGeneratedKeys="true" keyProperty="id">
insert into t_order (id, order_num, pay_order_num,
order_amount, invoice, invoice_title,
order_remark, address_id)
values (#{id,jdbcType=INTEGER}, #{orderNum,jdbcType=CHAR},
#{payOrderNum,jdbcType=CHAR},
#{orderAmount,jdbcType=DOUBLE}, #{invoice,jdbcType=INTEGER}, #{invoiceTitle,jdbcType=CHAR},
#{orderRemark,jdbcType=CHAR}, #{addressId,jdbcType=CHAR})
</insert>