day19【前台】支付

day19【前台】支付

1、环境搭建

1.1、创建VO

  • 创建OrderVO类,用于存储页面订单信息,由于OrderVO类的对象需要存入Redis,该类必须支持序列化

image-20200708211603679

@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、引入依赖

  • 主要是要引入阿里支付接口的依赖

image-20200708212405851

<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、创建主启动类

image-20200708212538980

// 启用Feign客户端功能
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class CrowdMainClass {
	
	public static void main(String[] args) {
		SpringApplication.run(CrowdMainClass.class, args);
	}

}

1.4、创建支付配置类

  • 创建支付接口的配置
    • appId:应用ID
    • merchantPrivateKey:商户私钥
    • alipayPublicKey:支付宝公钥
    • notifyUrl:服务器异步通知页面路径
    • returnUrl:页面跳转同步通知页面路径
    • signType:签名方式
    • charset:字符编码格式
    • gatewayUrl:支付宝网关

image-20200708212650727

@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、配置支付参数

  • 主要是配置支付宝接口的参数
    • notify-url:通过内网穿透,访问80端口(访问zuul),经路由映射(/pay)至pay-consumer下的notify地址,因为该方法是由支付宝接口调用,所以该地址必须暴露在公网下
    • return-url:通过内网穿透,访问80端口(访问zuul),经路由映射(/pay)至pay-consumer下的return地址,因为该方法是由支付宝接口调用,所以该地址必须暴露在公网下

image-20200708213051356

server:
  port: 7000
spring:
  application:
    name: atguigu-crowd-pay
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
  redis:
    host: 192.168.152.129
  session:
    store-type: redis

eureka:
  client:
    service-url:
      defaultZone: http://localhost:1000/eureka

ali:
  pay:
    alipay-public-key: <填入支付宝公钥>
    app-id: 2016102900776929
    charset: utf-8
    gateway-url: https://openapi.alipaydev.com/gateway.do
    merchant-private-key: <填入商家私钥>
    notify-url: http://m6kgnw.natappfree.cc/pay/notify
    return-url: http://m6kgnw.natappfree.cc/pay/return
    sign-type: RSA2

1.6、zuul配置路由规则

  • 通过zuul访问pay-consumer的路径为/pay/**

image-20200708220835764

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、整体思路

image-20201002145342452

2.2、收集表单数据

  • 收集当前页面所有需要提交的表单数据,提交至后端
$("#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();
	
});

2.3、商户订单号格式

文档地址:https://opensupport.alipay.com/support/knowledge/20582/201602075893

image-20200709111046115

2.4、发送订单请求

2.4.1、支付流程

电脑网站支付.png

2.4.2、公用组件
@Controller
public class PayHandler {
	
	@Autowired
	private PayProperties payProperties;
	
	@Autowired
	private MySQLRemoteService mySQLRemoteService;
	
	private Logger logger = LoggerFactory.getLogger(PayHandler.class);
    
    // ...
2.4.3、demo代码

image-20200709111409290

<%
	//获得初始化的AlipayClient
	AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.gatewayUrl, AlipayConfig.app_id, AlipayConfig.merchant_private_key, "json", AlipayConfig.charset, AlipayConfig.alipay_public_key, AlipayConfig.sign_type);
	
	//设置请求参数
	AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
	alipayRequest.setReturnUrl(AlipayConfig.return_url);
	alipayRequest.setNotifyUrl(AlipayConfig.notify_url);
	
	//商户订单号,商户网站订单系统中唯一订单号,必填
	String out_trade_no = new String(request.getParameter("WIDout_trade_no").getBytes("ISO-8859-1"),"UTF-8");
	//付款金额,必填
	String total_amount = new String(request.getParameter("WIDtotal_amount").getBytes("ISO-8859-1"),"UTF-8");
	//订单名称,必填
	String subject = new String(request.getParameter("WIDsubject").getBytes("ISO-8859-1"),"UTF-8");
	//商品描述,可空
	String body = new String(request.getParameter("WIDbody").getBytes("ISO-8859-1"),"UTF-8");
	
	alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\"," 
			+ "\"total_amount\":\""+ total_amount +"\"," 
			+ "\"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-请求参数】章节
	
	//请求
	String result = alipayClient.pageExecute(alipayRequest).getBody();
	
	//输出
	out.println(result);
%>
2.4.4、Handler代码
  • session域中获取订单信息(OrderProjectVO对象),并更新该对象的如下信息
    • 生成订单编号
    • 计算订单总金额
    • 更新Redis中的OrderProjectVO对象
  • 调用支付宝接口,获取支付页面,注意:这里必须加@ResponseBody注解,让当前方法的返回值成为响应体,这样才能在浏览器界面上显示支付宝支付界面

image-20200709111320813

// 这里必须加@ResponseBody注解,让当前方法的返回值成为响应体,在浏览器界面上显示支付宝支付界面
@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());
	
	// ②使用UUID生成用户ID部分
	String user = UUID.randomUUID().toString().replace("-", "").toUpperCase();

	// ③组装
	String orderNum = time + user;
	
	// ④设置到OrderVO对象中
	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			商品的描述,这里可以使用回报描述
 * @return				返回到页面上显示的支付宝登录界面
 * @throws AlipayApiException
 */
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();
	
}

2.5、return方法

2.5.1、demo代码

image-20200709111503033

/* *
 * 功能:支付宝服务器同步通知页面
 * 日期:2017-03-30
 * 说明:
 * 以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。
 * 该代码仅供学习和研究支付宝接口使用,只是提供一个参考。


 *************************页面功能说明*************************
 * 该页面仅做页面展示,业务逻辑处理请勿在该页面执行
 */
 
	//获取支付宝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, AlipayConfig.alipay_public_key, AlipayConfig.charset, AlipayConfig.sign_type); //调用SDK验证签名

	//——请在这里编写您的程序(以下代码仅作参考)——
	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 total_amount = new String(request.getParameter("total_amount").getBytes("ISO-8859-1"),"UTF-8");
		
		out.println("trade_no:"+trade_no+"<br/>out_trade_no:"+out_trade_no+"<br/>total_amount:"+total_amount);
	}else {
		out.println("验签失败");
	}
	//——请在这里编写您的程序(以上代码仅作参考)——
%>
2.5.2、Handler代码
  • 获取GET请求参数,发送给阿里支付接口进行验证,如果验证成功,则将订单信息保存至数据库

image-20200709111320813

@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 "验签失败";

    }
}

2.6、notify方法

2.6.1、demo代码

image-20200709111427441

/* *
 * 功能:支付宝服务器异步通知页面
 * 日期:2017-03-30
 * 说明:
 * 以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。
 * 该代码仅供学习和研究支付宝接口使用,只是提供一个参考。


 *************************页面功能说明*************************
 * 创建该页面文件时,请留心该页面文件中无任何HTML代码及空格。
 * 该页面不能在本机电脑测试,请到服务器上做测试。请确保外部可以访问该页面。
 * 如果没有收到该页面返回的 success 
 * 建议该页面只做支付成功的业务逻辑处理,退款的处理请以调用退款查询接口的结果为准。
 */
 
	//获取支付宝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, AlipayConfig.alipay_public_key, AlipayConfig.charset, AlipayConfig.sign_type); //调用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");
		
		if(trade_status.equals("TRADE_FINISHED")){
			//判断该笔订单是否在商户网站中已经做过处理
			//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
			//如果有做过处理,不执行商户的业务程序
				
			//注意:
			//退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知
		}else if (trade_status.equals("TRADE_SUCCESS")){
			//判断该笔订单是否在商户网站中已经做过处理
			//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
			//如果有做过处理,不执行商户的业务程序
			
			//注意:
			//付款完成后,支付宝系统发送该交易状态通知
		}
		
		out.println("success");
		
	}else {//验证失败
		out.println("fail");
	
		//调试用,写文本函数记录程序运行情况是否正常
		//String sWord = AlipaySignature.getSignCheckContentV1(params);
		//AlipayConfig.logResult(sWord);
	}
	
	//——请在这里编写您的程序(以上代码仅作参考)——
	
%>
2.6.2、Handler代码
  • 获取POST请求参数,发送给阿里支付接口进行验证

image-20200709111320813

@ResponseBody
@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 {//验证失败
        //调试用,写文本函数记录程序运行情况是否正常
        //String sWord = AlipaySignature.getSignCheckContentV1(params);
        //AlipayConfig.logResult(sWord);

        logger.info("验证失败");
    }

}

2.7、保存订单至数据库

2.8、整体思路

image-20201002145426859

2.8.1、mysql-provider暴露接口
1、Handler代码
  • 调用Service层保存订单信息

image-20200709165053798

@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());
    }

}
2、Service代码
  • 调用Mapper层保存订单信息
    • 由于保存t_order_project表时,需要用t_order表的主键作为t_order_project表的外键(orderId列)
    • 所以需要线先保存t_order表,并获取该表自增主键,作为t_order_project表的外键

image-20200709170521439

image-20200709165114315

@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);
}
3、Mapper代码
  • useGeneratedKeys="true":插入数据后,获取自增主键
  • keyProperty="id":主键列对应的属性名为id

image-20200709165211060

<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>
2.8.2、api声明远程调用方法

image-20200709165637380

@RequestMapping("/save/order/remote")
ResultEntity<String> saveOrderRemote(@RequestBody OrderVO orderVO);

2.9、测试

2.9.1、ZuulException
1、问题描述
  • com.netflix.zuul.exception.ZuulException: Forwarding error
com.netflix.zuul.exception.ZuulException: Forwarding error
	at org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter.handleException(RibbonRoutingFilter.java:198) ~[spring-cloud-netflix-zuul-2.1.2.RELEASE.jar:2.1.2.RELEASE]
	at org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter.forward(RibbonRoutingFilter.java:173) ~[spring-cloud-netflix-zuul-2.1.2.RELEASE.jar:2.1.2.RELEASE]
	at org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter.run(RibbonRoutingFilter.java:119) ~[spring-cloud-netflix-zuul-2.1.2.RELEASE.jar:2.1.2.RELEASE]
	at com.netflix.zuul.ZuulFilter.runFilter(ZuulFilter.java:117) ~[zuul-core-1.3.1.jar:1.3.1]
	at com.netflix.zuul.FilterProcessor.processZuulFilter(FilterProcessor.java:193) ~[zuul-core-1.3.1.jar:1.3.1]
	at com.netflix.zuul.FilterProcessor.runFilters(FilterProcessor.java:157) ~[zuul-core-1.3.1.jar:1.3.1]
	at com.netflix.zuul.FilterProcessor.route(FilterProcessor.java:118) ~[zuul-core-1.3.1.jar:1.3.1]
	at com.netflix.zuul.ZuulRunner.route(ZuulRunner.java:96) ~[zuul-core-1.3.1.jar:1.3.1]
	at com.netflix.zuul.http.ZuulServlet.route(ZuulServlet.java:116) ~[zuul-core-1.3.1.jar:1.3.1]
	at com.netflix.zuul.http.ZuulServlet.service(ZuulServlet.java:81) ~[zuul-core-1.3.1.jar:1.3.1]
	at org.springframework.web.servlet.mvc.ServletWrappingController.handleRequestInternal(ServletWrappingController.java:165) [spring-webmvc-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.cloud.netflix.zuul.web.ZuulController.handleRequest(ZuulController.java:45) [spring-cloud-netflix-zuul-2.1.2.RELEASE.jar:2.1.2.RELEASE]
	at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:52) [spring-webmvc-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1039) [spring-webmvc-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) [spring-webmvc-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005) [spring-webmvc-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:908) [spring-webmvc-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:660) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882) [spring-webmvc-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) [tomcat-embed-websocket-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.springframework.boot.actuate.web.trace.servlet.HttpTraceFilter.doFilterInternal(HttpTraceFilter.java:88) [spring-boot-actuator-2.1.6.RELEASE.jar:2.1.6.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) [spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) [spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) [spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92) [spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) [spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93) [spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) [spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:151) [spring-session-core-2.1.7.RELEASE.jar:2.1.7.RELEASE]
	at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:85) [spring-session-core-2.1.7.RELEASE.jar:2.1.7.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:114) [spring-boot-actuator-2.1.6.RELEASE.jar:2.1.6.RELEASE]
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:104) [spring-boot-actuator-2.1.6.RELEASE.jar:2.1.6.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) [spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) [spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) [spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:853) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1587) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_144]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_144]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.21.jar:9.0.21]
	at java.lang.Thread.run(Thread.java:748) [na:1.8.0_144]
2、问题解决
  • 如果调用接口的返回等待时间很长,则怀疑是ribbon问题,加入以下配置,设置ribbon的超时时间
ribbon:
  ReadTimeout: 60000
  ConnectTimeout: 60000
2.9.2、支付页面跳转失败
1、问题描述
  • 还是像之前测试那样,付款后,每次跳转都会显示【网络系统繁忙,请稍后再试】

image-20200709132158838

2、问题解决
  • 草。。。调了一下午没调出来。。。我确认支付接口中所有的参数,都已经配置正确。。。
  • 通过调试可知:notify方法中,参数验证通过,则表示付款成功。。。控制台也有打印输出,证明交易成功。。。

image-20200709155242094

2020-07-09 15:28:04.129  INFO 1236 --- [nio-7000-exec-4] com.atguigu.crowd.handler.PayHandler     : out_trade_no=20200709152713B3FFEF8D02FF4E8C925E4BA60DB01D18
2020-07-09 15:28:04.129  INFO 1236 --- [nio-7000-exec-4] com.atguigu.crowd.handler.PayHandler     : trade_no=2020070922001453160500840054
2020-07-09 15:28:04.129  INFO 1236 --- [nio-7000-exec-4] com.atguigu.crowd.handler.PayHandler     : trade_status=TRADE_SUCCESS
  • Bugreturn方法中打了断点,发现根本就没执行。。。。。

image-20200709153632182

  • 暂未找到解决办法,有时间去翻翻支付宝接口的文档吧
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值