第十四章
支付宝支付
优就业.JAVA教研室
学习目标
目标1:掌握二维码生成插件qrious的使用
目标2:能够说出支付宝支付开发的整体思路
目标3:能够调用支付宝支付接口(预下单)生成支付二维码
目标4:能够调用支付宝支付接口(查询订单)查询支付状态
目标5:实现支付日志的生成与订单状态的修改
一、二维码
1 什么是二维码
二维码又称QR Code,QR全称Quick Response,是一个近几年来移动设备上超流行的一种编码方式,它比传统的Bar Code条形码能存更多的信息,也能表示更多的数据类型。
二维条码/二维码(2-dimensional bar code)是用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的;在代码编制上巧妙地利用构成计算机内部逻辑基础的“0”、“1”比特流的概念,使用若干个与二进制相对应的几何形体来表示文字数值信息,通过图象输入设备或光电扫描设备自动识读以实现信息自动处理:它具有条码技术的一些共性:每种码制有其特定的字符集;每个字符占有一定的宽度;具有一定的校验功能等。同时还具有对不同行的信息自动识别功能、及处理图形旋转变化点。

2 二维码优势
- 信息容量大, 可以容纳多达1850个大写字母或2710个数字或500多个汉字
- 应用范围广, 支持文字,声音,图片,指纹等等…
- 容错能力强, 即使图片出现部分破损也能使用
- 成本低, 容易制作
3 二维码容错级别
L级(低) 7%的码字可以被恢复。
M级(中) 的码字的15%可以被恢复。
Q级(四分)的码字的25%可以被恢复。
H级(高) 的码字的30%可以被恢复。
4 二维码生成插件qrious
qrious是一款基于HTML5 Canvas的纯JS二维码生成插件。通过qrious.js可以快速生成各种二维码,你可以控制二维码的尺寸颜色,还可以将生成的二维码进行Base64编码。
官网地址:https://github.com/neocotic/qrious
qrious.js二维码插件的可用配置参数如下:
| 参数 | 类型 | 默认值 | 描述 | 
|---|---|---|---|
| background | String | “white” | 二维码的背景颜色。 | 
| foreground | String | “black” | 二维码的前景颜色。 | 
| level | String | “L” | 二维码的误差校正级别(L, M, Q, H)。 | 
| mime | String | “image/png” | 二维码输出为图片时的MIME类型。 | 
| size | Number | 100 | 二维码的尺寸,单位像素。 | 
| value | String | "" | 需要编码为二维码的值 | 
下面的代码即可生成一张二维码
<html>
<head>
<title>二维码入门小demo</title>
</head>
<body>
<img id="qrious">
<script src="qrious.min.js"></script>
<script>
 var qr = new QRious({
	   element:document.getElementById('qrious'),
	   size:250,
level:'H',	
value:'http://www.ujiuye.com'
	});
</script>
</body>
</html>
运行效果:

大家掏出手机,扫一下看看是否会看到优就业的官网呢?
二、支付宝扫码支付业务介绍及开发、环境配置流程
1 支付宝扫码支付业务流程
支付宝扫码支付是商户系统按支付宝支付协议生成支付二维码,用户再用支付宝“扫一扫”完成支付的模式。该模式适用于PC网站支付、实体店单品或订单支付、媒体广告支付等场景。
具体操作步骤:(了解)
第一步:创建应用
接入扫码支付能力,需要在开放平台创建一个应用,通过该应用来接入各种能力。
点击如下链接即可开始创建应用:https://openhome.alipay.com/platform/appManage.htm
第二步:添加应用功能
开发者在开发过程中,可以添加自己需要的功能到待申请功能列表。
给应用添加当面付功能,这样就可以在你的应用里使用扫码支付能力。
第三步:配置秘钥
为了保证交易双方的身份和数据安全,需要配置双方密钥。
第四步:沙箱环境调试使用
支付能力直接涉及到交易与资金,为了方便开放者调试支付能力,支付宝已经准备好沙箱环境,包括沙箱环境账号和沙箱版支付宝钱包,这样就可以在沙箱环境调试了。
第五步:签约
在正式使用这些能力的时候,需要在开放平台里进行签约,这时候约定的合同就生效了。也可以代替商户签约。
第六步:上线应用
上线:商户本身应用上线时候,也要把支付宝开放平台的应用上线。
验收:为了确保应用质量,开放平台提供了云验收平台,可以在线验收应用。
第七步:监控应用
在开放平台监控交易情况
应用上线后还可以在开放平台,查看应用运行情况以及交易状态。
2 扫码支付具体申请配置流程
2.1、登录支付宝开发者平台
打开支付宝官网:https://open.alipay.com

选择我是开发者

【注意】用自己的支付宝,扫描右边的扫码登录,在支付宝确认登录,即可登录支付宝开发者平台。初次登录进行身份验证

选择:自研开发者

同意协议,点击确定

2.2、登录开发者平台界面

选择研发服务
2.3、进入研发服务中心—》沙箱环境

沙箱环境(Beta)是协助开发者进行接口功能开发及主要功能联调的模拟环境。
作为开发者使用沙箱完全可以用来入门学习如何对接扫码支付接口。
Appid是应用的id,后续再开发应用时要用到,请保存好!
支付宝网关:https://openapi.alipaydev.com/gateway.do
2.4、配置沙箱环境,配置RSA2公钥
(1)鼠标移动到设置
处在弹出的文字介绍中,点击 生成方法

(2)根据操作系统进行选择进行下载。

在线生成:https://miniu.alipay.com/keytool/create
下载成功后,直接点击安装

安装成功

双击打开程序

依照图示选择
 密码长度为 RSA2
 密码格式为 PKCS8(Java适用)
然后点击生成密钥, 后 点击打开密钥文件路径

回到沙箱配置界面,点击 查看

点击更换应用公钥:

选择公钥,打开刚才我们生成的应用公钥,拷贝粘贴到文本框中

点击保存设置既可以把我们的公钥配置上去。
3 支付宝支付SDK
支付宝支付提供了SDK,使用支付宝支付SDK,在maven工程中引入依赖
<!-- 支付宝支付所需类库包 -->  
       <dependency>
            <groupId>com.alipay.sdk</groupId>
            <artifactId>alipay-sdk-java</artifactId>
            <version>4.3.0.ALL</version>
            <exclusions>
                <exclusion>
                    <groupId>org.bouncycastle</groupId>
                    <artifactId>bcprov-jdk15on</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
4 工程搭建与准备工作
(1)建立支付服务实现模块dongyimai-pay-service

(2)dongyimai-pay-service下创建application.yml,配置文件代码如下:
server:
  port: 9009
spring:
  application:
    name: pay
  redis:
    host: 192.168.188.128
    port: 6379
  main:
    allow-bean-definition-overriding: true
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka
  instance:
    prefer-ip-address: true
feign:
  hystrix:
    enabled: true
# 配置sql打印日志
logging:
  level:
    com:
      offcn: debug
#hystrix 配置
hystrix:
  command:
    default:
      execution:
        timeout:
          #如果enabled设置为false,则请求超时交给ribbon控制
          enabled: true
        isolation:
          strategy: SEMAPHORE
#支付宝支付信息配置
alipay:
  serverUrl: https://openapi.alipaydev.com/gateway.do
  appId: 2016102600767650
  privateKey: 用户私钥
  format: json
  charset: utf-8
  alipayPublicKey: 阿里公钥
  signType: RSA2
AlipayClient创建关键参数说明:
| 配置参数 | 示例值解释 | 获取方式/示例值 | 
|---|---|---|
| URL | 支付宝网关(固定) | https://openapi.alipay.com/gateway.do | 
| APP_ID | APPID即创建应用后生成 | 获取见上面创建应用并获取APPID | 
| APP_PRIVATE_KEY | 开发者应用私钥,由开发者自己生成 | 获取见上面配置密钥 | 
| FORMAT | 参数返回格式,只支持json | json(固定) | 
| CHARSET | 请求和签名使用的字符编码格式,支持GBK和UTF-8 | 开发者根据实际工程编码配置 | 
| ALIPAY_PUBLIC_KEY | 支付宝公钥,由支付宝生成 | 获取详见上面配置密钥 | 
| SIGN_TYPE | 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2 | RSA2 | 
接下来,就可以用alipayClient来调用具体的API了。alipayClient只需要初始化一次,后续调用不同的API都可以使用同一个alipayClient对象。
(4)在com.offcn.pay.config包下,创建AliPayConfig,代码如下:
@Configuration
public class AliPayConfig {
    @Value("${alipay.appId}")
    private String appId;
    @Value("${alipay.serverUrl}")
    private String serverUrl;
    @Value("${alipay.privateKey}")
    private String privateKey;
    @Value("${alipay.alipayPublicKey}")
    private String alipayPublicKey;
    @Value("${alipay.format}")
    private String format;
    @Value("${alipay.charset}")
    private String charset;
    @Value("${alipay.signType}")
    private String signType;
    @Bean
    public AlipayClient getPayClient() {
        return new DefaultAlipayClient(serverUrl, appId, privateKey, format, charset, alipayPublicKey, signType);
    }
}
(5)启动类创建
在dongyimai-pay-service中创建com.offcn.pay.PayApplication,代码如下:
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
@EnableEurekaClient
public class PayApplication {
    public static void main(String[] args) {
        SpringApplication.run(PayApplication.class);
    }
}
pom.xml引入如下依赖
<dependencies>
    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    <!-- 支付宝支付所需类库包 -->
    <dependency>
        <groupId>com.alipay.sdk</groupId>
        <artifactId>alipay-sdk-java</artifactId>
        <version>4.3.0.ALL</version>
        <exclusions>
            <exclusion>
                <groupId>org.bouncycastle</groupId>
                <artifactId>bcprov-jdk15on</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>com.offcn</groupId>
        <artifactId>dongyimai-common</artifactId>
        <version>1.0</version>
    </dependency>
</dependencies>
三 东易买-支付宝支付二维码生成
1 需求分析与实现思路
1.1需求分析
在支付页面上生成支付二维码,并显示订单号和金额
用户拿出手机,打开支付宝扫描页面上的二维码,然后在支付宝中完成支付

1.2实现思路
商户系统通过AlipayClient调用支付宝预下单接口alipay.trade.precreate,获得该订单二维码图片地址。
构建参数发送给预下单接口 ,返回的信息中有支付url,根据url生成二维码,显示的订单号和金额也在返回的信息中。
重要入参说明
- out_trade_no:商户订单号,需要保证商家系统不重复。
- total_amount:订单金额。(单位:元)
- subject:商品的标题/交易标题/订单标题/订单关键字等。不可使用特殊字符,如 /,=,& 等。
- store_id:商户门店编号。
- timeout_express:交易超时时间。
重要出参说明
- qr_code:订单二维码(有效时间 2 小时)以字符串的格式返回,开发者需要自己使用工具根据内容生成二维码图片。
2 代码实现
2.1服务接口层
(1)在dongyimai-pay-service创建包com.offcn.pay.service ,包下建立接口
public interface AliPayService {
    /**
     * 生成支付宝支付二维码
     * @param out_trade_no 订单号
     * @param total_fee 金额(分)
     * @return
     */
    public Map createNative(String out_trade_no, String total_fee);
}
2.2服务实现层
dongyimai-pay-service创建com.offcn.pay.service.impl包,新建类
@Service
public class AliPayServiceImpl implements AliPayService {
    @Autowired
    private AlipayClient alipayClient;
    /**
     * 生成支付宝支付二维码
     *
     * @param out_trade_no 订单号
     * @param total_fee    金额(分)
     * @return
     */
    @Override
    public Map createNative(String out_trade_no, String total_fee) {
        Map<String, String> map = new HashMap<String, String>();
        //创建预下单请求对象
        AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
//转换下单金额按照元
        long total = Long.parseLong(total_fee);
        BigDecimal bigTotal = BigDecimal.valueOf(total);
        BigDecimal cs = BigDecimal.valueOf(100d);
        BigDecimal bigYuan = bigTotal.divide(cs);
        System.out.println("预下单金额:" + bigYuan.doubleValue());
        request.setBizContent("{" +
                "    \"out_trade_no\":\"" + out_trade_no + "\"," +
                "    \"total_amount\":\"" + bigYuan.doubleValue() + "\"," +
                "    \"subject\":\"测试购买商品001\"," +
                "    \"store_id\":\"xa_001\"," +
                "    \"timeout_express\":\"90m\"}");//设置业务参数
        //发出预下单业务请求
        try {
            AlipayTradePrecreateResponse response = alipayClient.execute(request);
            //从相应对象读取相应结果
            String code = response.getCode();
            System.out.println("响应码:" + code);
            //全部的响应结果
            String body = response.getBody();
            System.out.println("返回结果:" + body);
            if (code.equals("10000")) {
                map.put("qrcode", response.getQrCode());
                map.put("out_trade_no", response.getOutTradeNo());
                map.put("total_fee", total_fee);
                System.out.println("qrcode:" + response.getQrCode());
                System.out.println("out_trade_no:" + response.getOutTradeNo());
                System.out.println("total_fee:" + total_fee);
            } else {
                System.out.println("预下单接口调用失败:" + body);
            }
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        return map;
    }
}
2.3控制层
dongyimai-pay-service创建com.offcn.pay.controller.PayController.java
@RestController
@RequestMapping("/pay")
public class PayController {
    @Autowired
    private AliPayService aliPayService;
    /**
     * 生成二维码
     *
     * @return
     */
    @GetMapping("/createNative")
    public Map createNative() {
        IdWorker idworker = new IdWorker();
        return aliPayService.createNative(idworker.nextId() + "", "1");
    }
}
这里我们订单号通过分布式ID生成器生成,金额暂时写死,后续开发我们再对接业务系统得到订单号和金额
浏览器测试 http://localhost:9009/pay/createNative

打开支付页面/pay.html,修改value路径,然后打开,会出现二维码,可以扫码试试

打开手机沙箱版支付宝,使用沙箱账号进行扫描。出现如下支付页面。

四 东易买-检测支付状态
1 需求分析及实现思路
1.1 需求分析
当用户支付成功后跳转到成功页面

当返回异常时跳转到错误页面

1.2 实现思路
我们通过AlipayClient实现对交易查询接口(alipay.trade.query)的调用。
交易查询接口具体参数:
关键入参:
| 参数名称 | 参数说明 | 
|---|---|
| out_trade_no | 支付时传入的商户订单号,与trade_no必填一个 | 
| trade_no | 支付时返回的支付宝交易号,与out_trade_no必填一个 | 
关键出参:
| 参数名称 | 参数说明 | 
|---|---|
| trade_no | 支付宝28位交易号 | 
| out_trade_no | 支付时传入的商户订单号 | 
| trade_status | 交易当前状态 | 
我们在controller方法中轮询调用交易查询指定订单号(间隔3秒),当返回状态为success时,我们会在controller方法返回结果。前端代码收到结果后跳转到成功页面。
2 检测支付状态-后端代码
2.1 服务接口层
在dongyimai-pay-service的AliPayService.java中新增方法定义
	/**
	 * 查询支付状态
	 * @param out_trade_no
	 */
	public Map queryPayStatus(String out_trade_no);
2.2 服务实现层
在dongyimai-pay-service的AliPayServiceImpl.java中实现方法
/**
	 * 交易查询接口alipay.trade.query:
	 * 获取指定订单编号的,交易状态
	 * @throws AlipayApiException 
	 */
	@Override
	public  Map<String,String> queryPayStatus(String out_trade_no){
		Map<String,String> map=new HashMap<String, String>();
		AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
		request.setBizContent("{" +
				"    \"out_trade_no\":\""+out_trade_no+"\"," +
				"    \"trade_no\":\"\"}"); //设置业务参数
		//发出请求
		try {
			AlipayTradeQueryResponse response = alipayClient.execute(request);
			String code=response.getCode();
			System.out.println("返回值1:"+response.getBody());
			if(code.equals("10000")){
				//System.out.println("返回值2:"+response.getBody());
				map.put("out_trade_no", out_trade_no);
				map.put("tradestatus", response.getTradeStatus());
                 map.put("trade_no",response.getTradeNo());
			}			
		} catch (AlipayApiException e) {
			e.printStackTrace();
		}
		return map;
}
2.3 控制层
在dongyimai-pay-service的PayController.java新增方法
/**
 * 查询支付状态
 * @param out_trade_no
 * @return
 */
@RequestMapping("/queryPayStatus")
public Result queryPayStatus(String out_trade_no){
    Result result=null;
    while(true){
        //调用查询接口
        Map<String, String> map = null;
        try {
            map = aliPayService.queryPayStatus(out_trade_no);
        } catch (Exception e1) {
            /*e1.printStackTrace();*/
            System.out.println("调用查询服务出错");
        }
        if(map==null){//出错
            result=new  Result(false, StatusCode.ERROR,"支付出错");
            break;
        }
        if(map.get("tradestatus")!=null&&map.get("tradestatus").equals("TRADE_SUCCESS")){//如果成功
            result=new  Result(true,StatusCode.OK,"支付成功");
            break;
        }
        if(map.get("tradestatus")!=null&&map.get("tradestatus").equals("TRADE_CLOSED")){//如果成功
            result=new  Result(true, StatusCode.OK,"未付款交易超时关闭,或支付完成后全额退款");
            break;
        }
        if(map.get("tradestatus")!=null&&map.get("tradestatus").equals("TRADE_FINISHED")){//如果成功
            result=new  Result(true,StatusCode.OK, "交易结束,不可退款");
            break;
        }
        try {
            Thread.sleep(3000);//间隔三秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    return result;
}
3 查询时间限制
3.1问题分析
如果用户到了二维码页面一直未支付,或是关掉了支付页面,我们的代码会一直循环调用支付宝接口,这样会对程序造成很大的压力。所以我们要加一个时间限制或是循环次数限制,当超过时间或次数时,跳出循环。
3.2代码完善
(1)修改dongyimai-pay-service工程PayController.java的queryPayStatus方法
	@RequestMapping("/queryPayStatus")
	public Result queryPayStatus(String out_trade_no){
		Result result=null;		
		int x=0;		
		while(true){
			//调用查询接口
			.......		
			try {
				Thread.sleep(3000);//间隔三秒
			} catch (InterruptedException e) {
				e.printStackTrace();
			}	
			//为了不让循环无休止地运行,我们定义一个循环变量,如果这个变量超过了这个值则退出循环,设置时间为5分钟
			x++;
			if(x>=100){
				result=new  Result(false, StatusCode.ERROR, "二维码超时");
				break;
			}
		}
		return result;
	}
访问:http://localhost:9009/pay/queryPayStatus?out_trade_no=1394204936316682240,并扫描预下单时生成的二维码,进行支付。返回支付成功

长时间没有返回结果返回二维码超时:

五 支付日志
1 需求分析
我们现在系统还有个问题需要解决:系统中无法查询到支付记录,支付后订单状态没有改变
我们现在就来解决这两个问题。
1.在用户下订单时,判断如果为支付宝支付,就向支付日志表添加一条记录,信息包括支付总金额、订单ID(多个)、用户ID 、下单时间等信息,支付状态为0(未支付)
2.生成的支付日志对象放入redis中,以用户ID作为key,这样在生成支付二维码时就可以从redis中提取支付日志对象中的金额和订单号。
3.当用户支付成功后,修改支付日志的支付状态为1(已支付),并记录支付宝传递给我们的交易流水号。根据订单ID(多个)修改订单的状态为2(已付款)。
2 表结构分析
tb_paylog 支付日志表
| 字段 | 类型 | 长度 | 含义 | 
|---|---|---|---|
| out_trade_no | varchar | 30 | 支付订单号 | 
| create_time | datatime | 创建时间 | |
| pay_time | datatime | 支付完成时间 | |
| total_fee | bigint | 支付金额(分) | |
| transaction_id | varchar | 30 | 交易流水号 | 
| trade_state | varchar | 1 | 交易状态 | 
| pay_type | varchar | 1 | 支付类型:1:支付宝2:微信3:网银 | 
| order_list | varchar | 200 | 订单表ID串,用逗号分隔 | 
3 逆向工程
拷贝逆向工程中的相关日志的pojo、Feign到dongyimai-order-service-api,拷贝 dao、service、controller到dongyimai-order-service中。


4 插入日志记录
修改dongyimai-order-service工程com.offcn.order.service.impl.OrderServiceImpl 的add方法。
内容:判断如果支付方式为支付宝支付,向数据库插入支付日志记录,并放入redis存储

完整代码如下:
@Autowired
private PayLogMapper payLogMapper;
/**
 * @author zdy
 * 增加Order
 * 金额校验:后台校验
 * @param order
 */
@Override
public void add(Order order){
    // 得到购物车数据
    List<Cart> cartList = (List<Cart>) redisTemplate.boundHashOps("cartList").get(order.getUserId());
    List<String> orderIdList=new ArrayList<>();//订单ID列表
    double total_money=0;//总金额 (元)
    for (Cart cart : cartList) {
        long orderId = idWorker.nextId();
        System.out.println("sellerId:" + cart.getSellerId());
        System.out.println("orderId:" + orderId);
        Order tborder = new Order();// 新创建订单对象
        tborder.setOrderId(orderId);// 订单ID
        tborder.setUserId(order.getUserId());// 用户名
        tborder.setPaymentType(order.getPaymentType());// 支付类型
        tborder.setStatus("1");// 状态:未付款
        tborder.setCreateTime(new Date());// 订单创建日期
        tborder.setUpdateTime(new Date());// 订单更新日期
        tborder.setReceiverAreaName(order.getReceiverAreaName());// 地址
        tborder.setReceiverMobile(order.getReceiverMobile());// 手机号
        tborder.setReceiver(order.getReceiver());// 收货人
        tborder.setSourceType(order.getSourceType());// 订单来源
        tborder.setSellerId(cart.getSellerId());// 商家ID
        // 循环购物车明细
        double money = 0;
        for (OrderItem orderItem : cart.getOrderItemList()) {
            orderItem.setId(idWorker.nextId());
            orderItem.setOrderId(orderId);// 订单ID
            orderItem.setSellerId(cart.getSellerId());
            money += Double.parseDouble(orderItem.getTotalFee());// 金额累加
            System.out.println("orderItem.getId():"+orderItem.getId());
            //减少库存  调用goods 微服务的 feign 减少库存
            itemFeign.decrCount(order.getUserId());
            //保存订单明细到数据库中
            orderItemMapper.insert(orderItem);
        }
        orderIdList.add(orderId+"");//添加到订单列表
        total_money+=money;
        tborder.setPayment(money+"");
        orderMapper.insert(tborder);
    }
    //线上支付,记录订单
    if ("1".equals(order.getPaymentType())) {//如果是支付宝支付
        PayLog payLog = new PayLog();
        String outTradeNo = idWorker.nextId() + "";//支付订单号
        payLog.setOutTradeNo(outTradeNo);//支付订单号
        payLog.setCreateTime(new Date());//创建时间
        //订单号列表,逗号分隔
        String ids = orderIdList.toString()
                .replace("[", "")
                .replace("]", "")
                .replace(" ", "");
        payLog.setOrderList(ids);//订单号列表,逗号分隔
        payLog.setPayType(order.getPaymentType());//支付类型
        //把元转换成分
        System.out.println("合计金额:" + total_money);
        BigDecimal total_money1 = BigDecimal.valueOf(total_money);
        BigDecimal cj = BigDecimal.valueOf(100d);
        //高精度乘法 推荐使用
        BigDecimal bigDecimal = total_money1.multiply(cj);
        //低精度计算方式,不推荐使用
        double hj = total_money * 100;
        System.out.println("合计:" + hj);
        System.out.println("高精度处理:" + bigDecimal.toBigInteger().longValue());
        payLog.setTotalFee(bigDecimal.toBigInteger().longValue());
        payLog.setTradeState("0");//支付状态  0 未支付 1已经支付
        payLog.setUserId(order.getUserId());//用户ID
        payLogMapper.insert(payLog);//插入到支付日志表
        redisTemplate.boundHashOps("payLog").put(order.getUserId(), payLog);//放入缓存
    }
    //增加积分,调用用户微服务的userFeign 增加积分
    userFeign.addPoints(10);
    redisTemplate.boundHashOps("cartList").delete(order.getUserId());
}
修改PayLog.java中主键生成的方式为外界输入

5 读取支付日志
5.1服务接口层
在dongyimai-order-service 工程中创建PayLogService.java, 新增方法
    /**
     * 根据用户查询payLog
     * @param userId
     * @return
     */
    public PayLog searchPayLogFromRedis(String userId);
5.2服务实现层
dongyimai-order-service的PayLogServiceImpl.java实现方法
    /**
     * 根据用户查询payLog
     *
     * @param userId
     * @return
     */
    @Override
    public PayLog searchPayLogFromRedis(String userId) {
        return (PayLog) redisTemplate.boundHashOps("payLog").get(userId);
    }
5.3控制层PayLogController
/***
     * 查询用户的支付日志
     * @return
     */
    @ApiOperation(value = "根据登录用户从redis中查询支付日志",notes = "查询用户支付日志",tags = {"OrderController"})
    @GetMapping("/searchPayLogFromRedis")
    public Result<PayLog> searchPayLogFromRedis(String userId){
        PayLog payLog = orderService.searchPayLogFromRedis(userId);
        return new Result<PayLog>(true, StatusCode.OK,"查询成功",payLog) ;
    }
5.4修改PayLogFegin
@FeignClient(name="ORDER")
public interface PayLogFeign {
/**
     查询用户的支付日志
     @return
     */
    @GetMapping("/payLog/searchPayLogFromRedis")
    public Result<PayLog> searchPayLogFromRedis(@RequestParam("userId") String userId);
....
5.5引入依赖
在dongyimai-pay-service中引入dongyimai-order-service-api的依赖
<dependency>
    <groupId>com.offcn</groupId>
    <artifactId>dongyimai-order-service-api</artifactId>
    <version>1.0</version>
    <exclusions>
        <exclusion>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </exclusion>
    </exclusions>
</dependency>
5.6修改启动类
修改dongyimai-pay-service启动类 PayApplication.java
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
@EnableFeignClients(basePackages = {"com.offcn.order.feign"})
@EnableEurekaClient
public class PayApplication {
    public static void main(String[] args) {
        SpringApplication.run(PayApplication.class);
    }
}
5.7 修改控制器PayController
修改dongyimai-pay-service工程PayController.java的createNative方法
实现思路:调用获取支付日志对象的方法,得到订单号和金额
@Autowired
private PayLogFeign payLogFeign;
/**
     * 生成二维码
     *
     * @return
     */
    @GetMapping("/createNative")
    public Map createNative() {
        //获取用户名
        Map<String, String> userMap = tokenDecode.getUserInfo();
        String username = userMap.get("user_name");
        System.out.println("当前登录用户:"+username);
        //到redis查询支付日志
        Result<PayLog> payLogResult = payLogFeign.searchPayLogFromRedis(username);
        PayLog payLog = payLogResult.getData();
        //判断支付日志存在
        if(payLog!=null){
            return aliPayService.createNative(payLog.getOutTradeNo(),payLog.getTotalFee()+"");
        }else{
            return new HashMap();
        }
    }
6 修改订单状态
订单支付成功后,需要修改日志记录支付交易流水号并持久化到数据库,并且修改订单的支付状态,然后将Redis中的订单删除:
6.1服务接口层
修改com.offcn.order.service.OrderService,添加修改订单状态方法,代码如下:
/***
 * 根据订单ID修改订单状态
 * @param transactionid 交易流水号
 * @param orderId
 */
public void updateStatus(String out_trade_no,String transactionid);
6.2服务实现层
修改com.offcn.order.service.impl.OrderServiceImpl,添加修改订单状态实现方法,代码如下:
/**
 * 修改订单状态
 *
 * @param out_trade_no   支付订单号
 * @param transaction_id 支付宝返回的交易流水号
 */
@Override
public void updateStatus(String out_trade_no, String transaction_id) {
    //1.修改支付日志状态
    PayLog payLog = payLogMapper.selectById(out_trade_no);
    payLog.setPayTime(new Date());
    payLog.setTradeState("1");//已支付
    payLog.setTransactionId(transaction_id);//交易号
    payLogMapper.updateById(payLog);
    //2.修改订单状态
    String orderList = payLog.getOrderList();//获取订单号列表
    String[] orderIds = orderList.split(",");//获取订单号数组
    for(String orderId:orderIds){
        Order order = orderMapper.selectById( Long.parseLong(orderId) );
        if(order!=null){
            order.setStatus("2");//已付款
            orderMapper.updateById(order);
        }
    }
    //3、清除redis缓存数据		
    redisTemplate.boundHashOps("payLog").delete(payLog.getUserId());
}
6.3控制层OrderController
 /**
 * 修改订单的状态
 * @param out_trade_no
 * @param transaction_id
 * @return
 */
@ApiOperation(value = "修改订单的状态",notes = "修改订单的状态",tags = {"OrderController"})
@RequestMapping(value="/updateOrderStatus",method = RequestMethod.GET)
public Result updateOrderStatus(
    @RequestParam(value="out_trade_no")  String out_trade_no, 
    @RequestParam(value="transaction_id") String transaction_id){
    try {
        orderService.updateStatus(out_trade_no,transaction_id);
        return new Result(true,StatusCode.OK,"修改成功");
    } catch (Exception e) {
        e.printStackTrace();
        return new Result(false,StatusCode.ERROR,"修改失败");
    }
}
6.4修改OrderFeign
/**
 * 修改订单状态
 * @param out_trade_no
 * @param transaction_id
 * @return
 */
@RequestMapping(value = "/updateOrderStatus",method = RequestMethod.GET)
public Result updateStatus(
    @RequestParam(value="out_trade_no")  String out_trade_no,  
    @RequestParam(value="transaction_id") String transaction_id);
6.5修改PayController
修改com.offcn.pay.controller.PayController中查询交易状态的方法queryPayStatus, 支付成功调用orderFeign修改订单的状态以及日志信息,代码如下:
                        if(map.get("tradestatus")!=null&&map.get("tradestatus").equals("TRADE_SUCCESS")){
    //如果成功
    result=new  Result(true,StatusCode.OK,"支付成功");
    orderFeign.updateStatus(map.get("out_trade_no"),map.get("trade_no"));
   break;
}
7.dongyimai-pay-service集成SpringSecurity环境及网关配置
7.1 pom.xml加入如下依赖
<dependency>
    <groupId>com.offcn</groupId>
    <artifactId>dongyimai-order-service-api</artifactId>
    <version>1.0</version>
    <exclusions>
        <exclusion>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!--oauth依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
7.2 引入公钥和验证文件
将public.key和文件ResourceServerConfig拷贝到dongyimai-pay-service中,参考其他项目
7.3 引入FeignInterceptor
在PayController引入FeignInterceptor
@Bean
public FeignInterceptor feignInterceptor(){
    return new FeignInterceptor();
}
7.4、修改网关配置,路由转发到支付微服务
application.yml
- id: dongyimai_pay_route #支付微服务
          uri: lb://PAY
          predicates:
            - Path=/api/pay/**
          filters:
            - StripPrefix=1
8.测试
8.1用户登录

8.2 添加购物车
http://localhost:8001/api/cart/addGoodsToCartList?itemId=1324601&num=5

8.3查询购物车
http://localhost:8001/api/cart/findCartList

8.4提交订单
http://localhost:8001/api/order/

8.5 查询redis

8.6查询数据库

8.7 预下单
http://localhost:8001/api/pay/createNative

8.8生成二维码

8.9 扫描支付并查询交易状态

8.10查看数据库:

订单状态也已经被修改为1

六 支付日志显示(学员实现)
需求:在运营商后台中,显示支付日志列表,实现按日期、状态、用户进行查询。
学员实现。