2023最新谷粒商城笔记之支付服务篇(全文总共13万字,超详细)

news2024/9/22 19:40:39

支付服务

这里我们是使用的支付宝进行支付,所以需要调用支付宝的相关API,下面来了解一下怎样使用支付宝进行线上支付。

支付宝配置相关概念

支付宝开放平台传送门:支付宝开放平台

网站支付DEMO传送门:手机网站支付 DEMO | 网页&移动应用

img

RSA、加密加签、密钥等

对称加密

image-20230116182723495

对称加密:发送方和接收方用的是同一把密钥,存在问题:当某一方将密钥泄漏之后,发送的消息可以被截取获悉并且随意进行通信。

非对称加密

image-20230116182519927

非对称加密:发送方和接收方使用的不是同一把密钥,发送方使用密钥A对明文进行加密,接收方使用密钥B对密文进行解密,然后接收方将回复的明文用密钥C进行加密,发送方使用密钥D进行解密。采用非对称加密的好处是:即使有密钥被泄漏也不能自由的通信。

公钥与私钥
  • 公钥和私钥是一个相对概念
  • 密钥的公私性是相对于生成者而言的。发送方通过密钥A对明文进行加密,密钥A是只有发送方自己知道的,接收方想要解密密文,就需要拿到发送方公布出来的密钥B。
    • 一对密钥生成后,保存在生产者手里的就是私钥(自己持有,不公开)
    • 生成者发布出去让大家用的就是公钥

此时:

  • 密钥A 和 密钥C 就是私钥,私钥用于加密,确保发送的消息不会被他人篡改
  • 密钥B 和 密钥D 就是公钥,公钥用于解密,可以暴露出去。
加密 和 数字签名

在这里插入图片描述

签名:为了防止中途传输的数据被篡改和使用的方便,发送方采用私钥生成明文对应的签名,此过程被成为加签。接收方使用公钥去核验明文和签名是否对应,此过程被成为验签。

配置支付宝的沙箱环境:

这里我们需要获取的是支付宝的沙箱环境中的商户公钥与私钥以及支付宝的公钥
在这里插入图片描述

①采用系统默认生成的支付宝的公钥、商户(个人)私钥和公钥:

img

img

② 采用自定义密钥

img

img

使用支付宝的密钥生成工具,传送门:异步验签 | 开放平台

img

img

将支付宝密钥工具生成的应用公钥复制进加签内容配置中,会自动生成支付宝的公钥

沙箱账号:用于测试环境中的商品支付

img

内网穿透

内网穿透的原理: 内网穿透服务商是正常外网可以访问的ip地址,我们的电脑通过下载服务商软件客户端并与服务器建立起长连接,别人的电脑访问hello.hello.com会先找到hello.com即一级域名,然后由服务商将请求转发给我们电脑的二级域名。即内网穿透的服务商将他们的子域名租借给我们使用,在使用时只需要配置相关的本机地址和端口号并创建一个映射,就可以通过公网ip访问本机的服务了。

内网穿透功能可以允许我们使用外网的网址来访问主机;

正常的外网需要访问我们项目的流程是:

  1. 买服务器并且有公网固定 IP
  2. 买域名映射到服务器的 IP
  3. 域名需要进行备案和审核

使用场景

  1. 开发测试(微信、支付宝)
  2. 智慧互联
  3. 远程控制
  4. 私有云

环境准备-内网穿透常用软件和安装

在这里插入图片描述

续断:https://www.zhexi.tech/

第一步:登录

第二步:安装客户端下载链接

第三步:登录哲西云控制台,到“客户端”菜单,确认客户端已上线;

在这里插入图片描述

第四步:新建隧道

整合支付服务

整合支付前需要注意的问题: 保证所有项目的编码格式都是utf-8

img

第一步、导入依赖

<!--阿里支付模块-->
<!-- https://mvnrepository.com/artifact/com.alipay.sdk/alipay-sdk-java -->
<dependency>
    <groupId>com.alipay.sdk</groupId>
    <artifactId>alipay-sdk-java</artifactId>
    <version>4.9.28.ALL</version>
</dependency>

第二步、抽取支付工具类并进行配置

成功调用该接口后,返回的数据就是支付页面的html,因此后续会使用@ResponseBody

1)、编写配置类

gulimall-order 服务的 com.atguigu.gulimall.order.config 路径下的 AlipayTemplate 配置类:

package com.atguigu.gulimall.order.config;

import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.atguigu.gulimall.order.vo.PayVo;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
 * Data time:2022/4/15 14:52
 * StudentID:2019112118
 * Author:hgw
 * Description: 支付宝沙箱测试配置类
 */
@ConfigurationProperties(prefix = "alipay")
@Component
@Data
public class AlipayTemplate {
    //在支付宝创建的应用的id
    private   String app_id = "个人appid";
    // 商户私钥,您的PKCS8格式RSA2私钥
    private  String merchant_private_key = "个人的商户私钥";
    // 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
    private  String alipay_public_key = "支付宝公钥";
    // 服务器[异步通知]页面路径  需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
    // 支付宝会悄悄的给我们发送一个请求,告诉我们支付成功的信息
    private  String notify_url="http://**.natappfree.cc/payed/notify";
    // 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
    //同步通知,支付成功只好需要跳转的成功页即订单列表页
    private  String return_url="http://member.gulimall.com/memberOrder.html";
    // 签名方式
    private  String sign_type = "RSA2";
    // 字符编码格式
    private  String charset = "utf-8";
    // 支付宝网关; https://openapi.alipaydev.com/gateway.do
    private  String gatewayUrl = "https://openapi.alipaydev.com/gateway.do";
    public  String pay(PayVo vo) throws AlipayApiException {
        //AlipayClient alipayClient = new DefaultAlipayClient(AlipayTemplate.gatewayUrl, AlipayTemplate.app_id, AlipayTemplate.merchant_private_key, "json", AlipayTemplate.charset, AlipayTemplate.alipay_public_key, AlipayTemplate.sign_type);
        //1、根据支付宝的配置生成一个支付客户端
        AlipayClient alipayClient = new DefaultAlipayClient(gatewayUrl,
                app_id, merchant_private_key, "json",
                charset, alipay_public_key, sign_type);
        //2、创建一个支付请求 //设置请求参数
        AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
        alipayRequest.setReturnUrl(return_url);
        alipayRequest.setNotifyUrl(notify_url);
        //商户订单号,商户网站订单系统中唯一订单号,必填
        String out_trade_no = vo.getOut_trade_no();
        //付款金额,必填
        String total_amount = vo.getTotal_amount();
        //订单名称,必填
        String subject = vo.getSubject();
        //商品描述,可空
        String body = vo.getBody();
        alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
                + "\"total_amount\":\""+ total_amount +"\","
                + "\"subject\":\""+ subject +"\","
                + "\"body\":\""+ body +"\","
                + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
        String result = alipayClient.pageExecute(alipayRequest).getBody();
        //会收到支付宝的响应,响应的是一个页面,只要浏览器显示这个页面,就会自动来到支付宝的收银台页面
        System.out.println("支付宝的响应:"+result);
        return result;
    }
}

2)、提交信息封装VO

package com.atguigu.gulimall.order.vo;

import lombok.Data;

@Data
public class PayVo {
    private String out_trade_no; // 商户订单号 必填
    private String subject; // 订单名称 必填
    private String total_amount;  // 付款金额 必填
    private String body; // 商品描述 可空
}

3)、因为加上了@ConfigurationProperties(prefix = "alipay"),是一个与配置文件绑定的配置类

我们可以在application.yaml 配置文件中编写相关的配置

# 支付宝相关的配置
alipay:
  app-id: 2021000119667766
  //...

第三步、修改前端页面

修改支付页的支付宝按钮发送使用支付宝付款请求

修改 gulimall-order 服务中的 pay.html 页面

<li>
        <img src="/static/order/pay/img/zhifubao.png" style="weight:auto;height:30px;" alt="">
        <a th:href="'http://order.gulimall.com/payOrder?orderSn='+${submitOrderResp.order.orderSn}">支付宝</a>
      </li>

第四步、订单支付与同步通知

1)、编写Controller层接口调用gulimall-ordert 服务的 com.atguigu.gulimall.order.web 路径下的 PayWebController 类,映射/payOrder

produces属性:用于设置返回的数据类型

AlipayTemplate的pay()方法返回的就是一个用于浏览器响应的付款页面

package com.atguigu.gulimall.order.web;

@Controller
public class PayWebController {
    @Autowired
    AlipayTemplate alipayTemplate;
    @Autowired
    OrderService orderService;

    /**
     * 1、将支付页让浏览器展示
     * 2、支付成功后,跳转到用户的订单列表项
     * @param orderSn
     * @return
     * @throws AlipayApiException
     */
    @ResponseBody
    @GetMapping(value = "/payOrder",produces = "text/html")
    public String payOrder(@RequestParam("orderSn") String orderSn) throws AlipayApiException {
        PayVo payVo = orderService.getOrderPay(orderSn);
        // 返回的是一个页面。将此页面交给浏览器就行
        String pay = alipayTemplate.pay(payVo);
        System.out.println(pay);
        return pay;
    }
}

2)、Service层实现类 OrderServiceImpl.java 编写获取当前订单的支付信息 方法

/**
 * 获取当前订单的支付信息
 * @param orderSn
 * @return
 */
@Override
public PayVo getOrderPay(String orderSn) {
    PayVo payVo = new PayVo();
    OrderEntity order = this.getOrderByOrderSn(orderSn);
//应付金额需要处理,支付宝只能支付保留两位小数的金额,采用ROUND_UP的进位模式 
    BigDecimal decimal = order.getPayAmount().setScale(2, BigDecimal.ROUND_UP);
    payVo.setTotal_amount(decimal.toString());
    payVo.setOut_trade_no(order.getOrderSn());

    List<OrderItemEntity> order_sn = orderItemService.list(new QueryWrapper<OrderItemEntity>().eq("order_sn", orderSn));
    OrderItemEntity itemEntity = order_sn.get(0);
    payVo.setSubject(itemEntity.getSkuName());
    payVo.setBody(itemEntity.getSkuAttrsVals());
    return payVo;
}

第五步、订单列表页渲染完成

环境准备

  1. 首先将资料中订单页静态资源部署到服务器中,让页面放到gulimall-member服务中。

  2. 配置网关

            - id: gulimall_member_route
              uri: lb://gulimall-member
              predicates:
                - Host=member.gulimall.com
    
  3. 添加域名映射

    # Gulimall Host Start
    127.0.0.1 gulimall.com
    127.0.0.1 search.gulimall.com
    127.0.0.1 item.gulimall.com
    127.0.0.1 auth.gulimall.com
    127.0.0.1 cart.gulimall.com
    127.0.0.1 order.gulimall.com
    127.0.0.1 member.gulimall.com.
    "/etc/hosts"
    
  4. 整合SpringSession

    1. 导入依赖

      <!-- 整合SpringSession完成Session共享问题-->
      <dependency>
          <groupId>org.springframework.session</groupId>
          <artifactId>spring-session-data-redis</artifactId>
      </dependency>
      <!--引入Redis-->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-redis</artifactId>
          <exclusions>
              <exclusion>
                  <groupId>io.lettuce</groupId>
                  <artifactId>lettuce-core</artifactId>
              </exclusion>
          </exclusions>
      </dependency>
      <dependency>
          <groupId>redis.clients</groupId>
          <artifactId>jedis</artifactId>
      </dependency>
      
    2. 编写配置

      spring:
        session:
          store-type: redis
        redis:
          host: 124.222.223.222
      
    3. 启动类加上注解

      @EnableRedisHttpSession
      @EnableFeignClients(basePackages = "com.atguigu.gulimall.member.feign")
      @EnableDiscoveryClient
      @SpringBootApplication
      public class GulimallMemberApplication {
          public static void main(String[] args) {
              SpringApplication.run(GulimallMemberApplication.class, args);
          }
      
      }
      
  5. 配置拦截器(这里复制的同时一定要修改,放行:/member/member/**远程调用接口

    1. 用户登录拦截器

      package com.atguigu.gulimall.member.interceptoe;
      
      import com.atguigu.common.constant.AuthServerConstant;
      import com.atguigu.common.vo.MemberRespVo;
      import org.springframework.stereotype.Component;
      import org.springframework.util.AntPathMatcher;
      import org.springframework.web.servlet.HandlerInterceptor;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      /**
       * Data time:2022/4/11 22:21
       * StudentID:2019112118
       * Author:hgw
       * Description: 用户登录拦截器
       */
      @Component
      public class LoginUserInterceptor implements HandlerInterceptor {
          public static ThreadLocal<MemberRespVo> loginUser = new ThreadLocal<>();
          /**
           * 用户登录拦截器
           * @param request
           * @param response
           * @param handler
           * @return
           *      用户登录:放行
           *      用户未登录:跳转到登录页面
           * @throws Exception
           */
          @Override
          public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
              // /order/order/status/222222222
              String uri = request.getRequestURI();
              boolean match = new AntPathMatcher().match("/member/**", uri);
              if (match){
                  return true;
              }
              MemberRespVo attribute = (MemberRespVo) request.getSession().getAttribute(AuthServerConstant.LOGIN_USER);
              if (attribute!=null){
                  loginUser.set(attribute);
                  return true;
              } else {
                  // 没登录就去登录
                  request.getSession().setAttribute("msg", "请先进行登录");
                  response.sendRedirect("http://auth.gulimall.cn/login.html");
                  return false;
              }
          }
      }
      
    2. 编写Web配置类,指定用户登录拦截器

      package com.atguigu.gulimall.member.config;
      
      import com.atguigu.gulimall.member.interceptoe.LoginUserInterceptor;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
      import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
      /**
       * Data time:2022/4/15 17:03
       * StudentID:2019112118
       * Author:hgw
       * Description: Web配置
       */
      @Configuration
      public class MemberWebConfig implements WebMvcConfigurer {
          @Autowired
          LoginUserInterceptor loginUserInterceptor;
      
          @Override
          public void addInterceptors(InterceptorRegistry registry) {
              registry.addInterceptor(loginUserInterceptor).addPathPatterns("/**");
          }
      }
      

接口编写

第一步、gulimall-order 服务中编写分页查询当前登录用户的所有订单接口方法

1)、在Controller层编写gulimall-order 服务中/src/main/java/com/atguigu/gulimall/order/controller OrderController.java

package com.atguigu.gulimall.order.controller;

@RestController
@RequestMapping("order/order")
public class OrderController {
    @Autowired
    private OrderService orderService;
    /**
     * 分页查询当前登录用户的所有订单
     */
    @PostMapping("/listWithItem")
    public R listWithItem(@RequestBody Map<String, Object> params){
        PageUtils page = orderService.queryPageWithItem(params);
        return R.ok().put("page", page);
    }

2)、Service层 OrderServiceImpl.java实现类方法编写:

gulimall-order 服务中 com/atguigu/gulimall/order/service/impl OrderServiceImpl.java

@Override
public PageUtils queryPageWithItem(Map<String, Object> params) {
    MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();

    IPage<OrderEntity> page = this.page(
            new Query<OrderEntity>().getPage(params),
            new QueryWrapper<OrderEntity>().eq("member_id", memberRespVo.getId()).orderByDesc("modify_time")
    );
    List<OrderEntity> order_sn = page.getRecords().stream().map(order -> {
        List<OrderItemEntity> itemEntities = orderItemService.list(new QueryWrapper<OrderItemEntity>().eq("order_sn", order.getOrderSn()));
        order.setItemEntities(itemEntities);
        return order;
    }).collect(Collectors.toList());
    page.setRecords(order_sn);
    return new PageUtils(page);
}

3)、修改 OrderEntity.java 实体类,为其加上一个属性

gulimall-order 服务 com.atguigu.gulimall.order.entity 路径下的 OrderEntity类,添加以下属性:

@TableField(exist = false)
private List<OrderItemEntity> itemEntities;

第二步、在gulimall-member服务中调用 gulimall-order 服务接口

gulimall-member 服务的 com.atguigu.gulimall.member.feign 路径下的 OrderFeignService接口进行远程调用:

package com.atguigu.gulimall.member.feign;

import com.atguigu.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.Map;

/**
 * Data time:2022/4/15 20:46
 * StudentID:2019112118
 * Author:hgw
 * Description:
 */
@FeignClient("gulimall-order")
public interface OrderFeignService {
    @PostMapping("/order/order/listWithItem")
    R listWithItem(@RequestBody Map<String, Object> params);
}

第三步、编写过滤器

因为这里用户服务中设计到了远程调用订单服务,且访问是以/memberOrder.html结尾的,这里是模拟访问的页面,那么访问页面时就会带上cookie,所以我们可以给请求拦截一下,让feign远程调用创建新request时带上老请求的cookie,这样到订单服务时就有session的cookie信息就不用登录了。

package com.atguigu.gulimall.member.config;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * Data time:2022/4/12 11:20
 * StudentID:2019112118
 * Author:hgw
 * Description:
 */
@Configuration
public class GulimallFeignConfig {

    /**
     * feign在远程调用之前会执行所有的RequestInterceptor拦截器
     * @return
     */
    @Bean("requestInterceptor")
    public RequestInterceptor requestInterceptor(){
        return new RequestInterceptor(){
            @Override
            public void apply(RequestTemplate requestTemplate) {
                // 1、使用 RequestContextHolder 拿到请求数据,RequestContextHolder底层使用过线程共享数据 ThreadLocal<RequestAttributes>
                ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
                if (attributes!=null){
                    HttpServletRequest request = attributes.getRequest();
                    // 2、同步请求头数据,Cookie
                    String cookie = request.getHeader("Cookie");
                    // 给新请求同步了老请求的cookie
                    requestTemplate.header("Cookie",cookie);
                }
            }
        };
    }
}

第四步、Controller 层 MemberWebController 编写

package com.atguigu.gulimall.member.web;

import com.alibaba.fastjson.JSON;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.member.feign.OrderFeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.HashMap;
import java.util.Map;

/**
 * Data time:2022/4/15 17:00
 * StudentID:2019112118
 * Author:hgw
 * Description:
 */
@Controller
public class MemberWebController {
    @Autowired
    OrderFeignService orderFeignService;
//模拟的访问页面,所以可以拦截一下带上之前的cookie
    @GetMapping("/memberOrder.html")
    public String memberOrderPage(@RequestParam(value = "pageNum",defaultValue = "1") Integer pageNum,
                                 Model model){
        // 查处当前登录的用户的所有订单列表数据
        Map<String,Object> page = new HashMap<>();
        page.put("page",pageNum.toString());
        R r = orderFeignService.listWithItem(page);
        System.out.println(JSON.toJSONString(r));
        model.addAttribute("orders",r);
        return "orderList";
    }
}
5.3、前端页面接收渲染

修改 orderList.html 页面的部分内容,渲染页面

<table class="table" th:each="order:${orders.page.list}">
    <tr>
        <td colspan="7" style="background:#F7F7F7">
            <span style="color:#AAAAAA">2017-12-09 20:50:10</span>
            <span><ruby style="color:#AAAAAA">订单号:</ruby> [[${order.orderSn}]]</span>
            <span>谷粒商城<i class="table_i"></i></span>
            <i class="table_i5 isShow"></i>
        </td>
    </tr>
    <tr class="tr" th:each="item,itemStat:${order.itemEntities}">
        <td colspan="3">
            <img style="height: 60px; width: 60px;" th:src="${item.skuPic}" alt="" class="img">
            <div>
                <p style="width: 242px; height: auto; overflow: auto">
                    [[${item.skuName}]]
                </p>
                <div><i class="table_i4"></i>找搭配</div>
            </div>
            <div style="margin-left:15px;">x[[${item.skuQuantity}]]</div>
            <div style="clear:both"></div>
        </td>
        <td th:if="${itemStat.index==0}" th:rowspan="${itemStat.size}">[[${order.receiverName}]]<i><i class="table_i1"></i></i></td>
        <td th:if="${itemStat.index==0}" th:rowspan="${itemStat.size}" style="padding-left:10px;color:#AAAAB1;">
            <p style="margin-bottom:5px;">总额 ¥[[${order.payAmount}]]</p>
            <hr style="width:90%;">
            <p>在线支付</p>
        </td>
        <td th:if="${itemStat.index==0}" th:rowspan="${itemStat.size}">
            <ul>
                <li style="color:#71B247;" th:if="${order.status==0}">待付款</li>
                <li style="color:#71B247;" th:if="${order.status==1}">已付款</li>
                <li style="color:#71B247;" th:if="${order.status==2}">已发货</li>
                <li style="color:#71B247;" th:if="${order.status==3}">已完成</li>
                <li style="color:#71B247;" th:if="${order.status==4}">已取消</li>
                <li style="color:#71B247;" th:if="${order.status==5}">售后中</li>
                <li style="color:#71B247;" th:if="${order.status==6}">售后完成</li>
                <li style="margin:4px 0;" class="hide"><i class="table_i2"></i>跟踪<i class="table_i3"></i>
                    <div class="hi">
                        <div class="p-tit">
                            普通快递 运单号:390085324974
                        </div>
                        <div class="hideList">
                            <ul>
                                <li>
                                    [北京市] 在北京昌平区南口公司进行签收扫描,快件已被拍照(您
                                    的快件已签收,感谢您使用韵达快递)签收
                                </li>
                                <li>
                                    [北京市] 在北京昌平区南口公司进行签收扫描,快件已被拍照(您
                                    的快件已签收,感谢您使用韵达快递)签收
                                </li>
                                <li>
                                    [北京昌平区南口公司] 在北京昌平区南口公司进行派件扫描
                                </li>
                                <li>
                                    [北京市] 在北京昌平区南口公司进行派件扫描;派送业务员:业务员;联系电话:17319268636
                                </li>
                            </ul>
                        </div>
                    </div>
                </li>
                <li class="tdLi">订单详情</li>
            </ul>
        </td>
        <td>
            <button>确认收货</button>
            <p style="margin:4px 0; ">取消订单</p>
            <p>催单</p>
        </td>
    </tr>
</table>

第六步、异步通知内网穿透环境搭建

  • 订单支付成功后支付宝会回调商户接口,这个时候需要修改订单状态
  • 由于同步跳转可能由于网络问题失败,所以使用异步通知
  • 支付宝使用的是最大努力通知方案来实现分布式事务的最终一致性,保障数据一致性,隔一段时间会通知商户支付成功,直到返回success

1)、建立内网穿透

在这里插入图片描述

2)内网穿透设置异步通知地址

  • 将外网映射到本地的order.gulimall.cn:80

  • 由于回调的请求头不是order.gulimall.cn,因此nginx转发到网关后找不到对应的服务,所以需要对nginx进行设置

    /payed/notify异步通知转发至订单服务

设置异步通知的地址:

在这里插入图片描述

3)、内网穿透联调

在这里插入图片描述

通过工具进行内网穿透,第三方并不是从浏览器发送过来请求,即使是从浏览中发送过来,那host也不对,故我们需要修改nginx的配置,来监听 /payed/notify 请求,设置默认的host

服务器的 mydata/nginx/conf/conf.d 目录下

hgw@HGWdeAir conf.d % vim gulimall.conf 
1
server {
    listen       80;
    server_name  gulimall.cn  *.gulimall.cn mvaophzk6b.51xd.pub;#最后一个是内网穿透地址

    #charset koi8-r;
    #access_log  /var/log/nginx/log/host.access.log  main;
    location /static/ {
        root /usr/share/nginx/html;
    }
#匹配回调地址加上host地址,否则默认带的是内网穿透地址
    location /payed/  {
        proxy_set_header Host order.gulimall.cn;
        proxy_pass http://gulimall;
    }
    location / {
        proxy_set_header Host $host;
        proxy_pass http://gulimall;
    }

在这里插入图片描述

4)、编写登录拦截器方法,放行/payed/notify,因为该请求是支付宝回调确认请求,不需要登录

修改gulimall-order服务 com.atguigu.gulimall.order.interceptoe 路径的 LoginUserInterceptor 类,代码如下:

package com.atguigu.gulimall.order.interceptoe;

@Component
public class LoginUserInterceptor implements HandlerInterceptor {

    public static ThreadLocal<MemberRespVo> loginUser = new ThreadLocal<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // /order/order/status/222222222
        String uri = request.getRequestURI();
        AntPathMatcher matcher = new AntPathMatcher();
        boolean match = matcher.match("/order/order/status/**", uri);
        boolean match1 = matcher.match("/payed/notify", uri);
        if (match || match1 ){
            return true;
        }

        MemberRespVo attribute = (MemberRespVo) request.getSession().getAttribute(AuthServerConstant.LOGIN_USER);
        if (attribute!=null){
            loginUser.set(attribute);
            return true;
        } else {
            // 没登录就去登录
            request.getSession().setAttribute("msg", "请先进行登录");
            response.sendRedirect("http://auth.gulimall.cn/login.html");
            return false;
        }
    }
}

第七步、验证签名,支付成功

  • 验证签名
    • 验签通过,即是支付宝发过来的数据,处理支付结果
      1. 保存交易流水 oms_payment_info
      2. 修改订单的状态信息 oms_order
      3. 返回"success"
    • 验签失败,即不是支付宝发送过来的数据
      • 返回非 “success”即可
1、主体代码,Controller层接口编写
package com.atguigu.gulimall.order.listener;

import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import com.atguigu.gulimall.order.config.AlipayTemplate;
import com.atguigu.gulimall.order.service.OrderService;
import com.atguigu.gulimall.order.vo.PayAsyncVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * Data time:2022/4/15 21:54
 * StudentID:2019112118
 * Author:hgw
 * Description: 接收支付宝的异步通知
 */
@RestController
public class OrderPayedListener {

    @Autowired
    OrderService orderService;

    @Autowired
    AlipayTemplate alipayTemplate;

    @PostMapping("/payed/notify")
    public String handleAliPayed(PayAsyncVo vo,HttpServletRequest request) throws AlipayApiException {
        // 只要我们收到了支付宝给我们异步的通知,告诉我们订单支付成功。返回success,支付宝就再也不通知
        // 验签
        Map<String, String> params = new HashMap<>();
        Map<String, String[]> requestParams = request.getParameterMap();
        for (String name : requestParams.keySet()) {
            String[] values = 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, alipayTemplate.getAlipay_public_key(),
                alipayTemplate.getCharset(), alipayTemplate.getSign_type()); //调用SDK验证签名

        if (signVerified) {
            System.out.println("签名验证成功....");
            String result = orderService.handlePayRequest(vo);
            return result;
        } else {
            System.out.println("签名验证失败....");
            return "error";
        }
    }
}
2、处理支付结果

配置SpringMVC日期转化格式

spring.mvc.date-format=yyyy-MM-dd HH:mm:ss

1、编写一个实体类Vo 用来映射支付宝异步通知回来的数据

gulimall-order 服务的 com.atguigu.gulimall.order.vo 路径下的 PayAsyncVo 类

package com.atguigu.gulimall.order.vo;

import lombok.Data;
import lombok.ToString;

@ToString
@Data
public class PayAsyncVo {
    private String gmt_create;
    private String charset;
    private String gmt_payment;
    private String notify_time;
    private String subject;
    private String sign;
    private String buyer_id;//支付者的id
    private String body;//订单的信息
    private String invoice_amount;//支付金额
    private String version;
    private String notify_id;//通知id
    private String fund_bill_list;
    private String notify_type;//通知类型; trade_status_sync
    private String out_trade_no;//订单号
    private String total_amount;//支付的总额
    private String trade_status;//交易状态  TRADE_SUCCESS
    private String trade_no;//流水号
    private String auth_app_id;//
    private String receipt_amount;//商家收到的款
    private String point_amount;//
    private String app_id;//应用id
    private String buyer_pay_amount;//最终支付的金额
    private String sign_type;//签名类型
    private String seller_id;//商家的id
}

2)、设置 表oms_payment_info 的索引

因为一个订单对应一个流水号,所以我们给订单号和支付流水号加上两个唯一索引:

在这里插入图片描述

并修改 order_sn 属性的长度为 64位

3)、Service 层实现类 OrderServiceImpl.java 类编写 处理支付宝的支付结果 方法

gulimall-order 服务的 com/atguigu/gulimall/order/service/impl/OrderServiceImpl.java 路径下的 OrderServiceImpl.java

/**
 * 处理支付宝的支付结果
 * @param vo
 * @return
 */
@Override
public String handlePayRequest(PayAsyncVo vo) {
    // 1、保存交易流水 oms_payment_info
    PaymentInfoEntity infoEntity = new PaymentInfoEntity();
    infoEntity.setAlipayTradeNo(vo.getTrade_no());
    infoEntity.setOrderSn(vo.getOut_trade_no());
    infoEntity.setPaymentStatus(vo.getTrade_status());
    infoEntity.setCallbackTime(vo.getNotify_time());
    paymentInfoService.save(infoEntity);
    // 2、修改订单的状态信息 oms_order
    // 判断支付是否成功:支付宝返回 TRADE_SUCCESS、TRADE_FINISHED 都表示成功
    if (vo.getTrade_status().equals("TRADE_SUCCESS") || vo.getTrade_status().equals("TRADE_FINISHED")) {
        // 支付成功状态,则修改订单的状态
        String outTradeNo = vo.getOut_trade_no();
        this.baseMapper.updateOrderStatus(outTradeNo,OrderStatusEnum.PAYED.getCode());
    }
    return "success";
}

4)、修改订单的状态信息方法编写 oms_order

gulimall-order 服务的 com.atguigu.gulimall.order.dao 路径下的 OrderDao

package com.atguigu.gulimall.order.dao;

@Mapper
public interface OrderDao extends BaseMapper<OrderEntity> {

    void updateOrderStatus(@Param("outTradeNo") String outTradeNo, @Param("code") Integer code);
}

gulimall-order 服务的 gulimall-order/src/main/resources/mapper/order/OrderDao.xml

<update id="updateOrderStatus">
    UPDATE oms_order SET `status`=#{code} WHERE order_sn=#{outTradeNo};
</update>

第八步、关单处理

  1. 由于买家的特殊原因,没能在订单过期前完成支付,等到订单状态过期了才支付,这时库存已进行解锁库存,但是会将订单状态改为已支付,此时就出现了错误
    • 使用支付宝自动收单功能解决。只要一段时间不支付,就不能支付了。
  2. 由于延时等问题。订单解锁完成,正在解锁库存的时候,异步通知才到
    • 订单解锁,手动调用收单
  3. 网络阻塞问题,订单支付成功的异步通知一直不到达
    • 查询订单列表时,Ajax获取当前未支付的订单状态,查询订单状态时,再获取一下支付宝此订单的状态。
  4. 其他各种问题
    • 每天晚上闲时下载支付宝对账单——进行对账

情况一:订单在支付页,不支付,一直刷新,订单过期了才支付,订单状态已经改为已付款但是库存解锁了

解决方案:自动关单

alipay.close_pay.time_expire=1m

img

情况二: 由于网络延时原因,订单解锁完成,正要解锁库存时,支付成功的异步通知才到

即关单最后时间用户进行支付,此时因为网络慢,解锁订单和库存只执行完了解锁订单,还没有解锁库存,但是此时支付成功的异步通知发了过来,但是订单已经解锁取消掉了,但是库存减少了。

解决方案:订单解锁,手动关单

img

手动关单,参考支付Demo中的Close.jsp

img


感谢耐心看到这里的同学,觉得文章对您有帮助的话希望同学们不要吝啬您手中的赞,动动您智慧的小手,您的认可就是我创作的动力!
之后还会勤更自己的学习笔记,感兴趣的朋友点点关注哦。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/431549.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数字滤波器设计——IIR 滤波器

数字滤波器设计实践介绍 此示例说明如何使用 Signal Processing Toolbox 产品中的 designfilt 函数&#xff0c;根据频率响应设定设计 FIR 和 IIR 滤波器。该示例重点讲述低通滤波器&#xff0c;但大多数结果也适用于其他响应类型。 此示例主要介绍数字滤波器的设计&#xff…

D3.js实现线条的流动效果(从一端移动到另一端并且变色)

参考&#xff1a; SVG&#xff1a;理解stroke-dasharray和stroke-dashoffset属性 使用SVG CSS实现动态霓虹灯文字效果 纯CSS实现帅气的SVG路径描边动画效果 实现的效果为&#xff1a;路径左移到完全看不见的地方&#xff0c;然后一边右移&#xff0c;一边从黑色变为红色 <…

社科院与杜兰大学金融管理硕士项目—人生的每一条路都可以看作是正确的路

成年人的世界里没有什么是容易的。生活中经常听到人说&#xff1a;早知道现在过得这么辛苦&#xff0c;当年真应该好好读书&#xff1b;早知道这个行业这么难出头&#xff0c;当年真不应该踏入这一行&#xff1b;早知道爱人这么不靠谱&#xff0c;当年不跟他结婚就好了……有时…

系统集成项目管理工程师软考知识点(每天更新)

第一章指路&#xff1a;系统集成项目管理工程师软考知识点&#xff08;第一章已完结&#xff09;_程序猿幼苗的博客-CSDN博客 第二章指路&#xff1a;系统集成项目管理工程师软考知识点&#xff08;第二章已完结&#xff09;_程序猿幼苗的博客-CSDN博客 本专栏将会更新完整~ …

【DRF开发手册】使用 Django Rest Framework 的 @action 定义自定义方法

本文节选自笔者博客&#xff1a; https://www.blog.zeeland.cn/archives/so3f209hfeac &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是Zeeland&#xff0c;全栈领域优质创作者。&#x1f4dd; CSDN主页&#xff1a;Zeeland&#x1f525;&#x1f4e3; 我的博客&…

C++ Primer Plus(第6版) 全书重点学习笔记

目录 第10章 对象和类 10.1 过程性编程和面向对象编程 10.2 抽象和类 10.2.1 类简介 10.2.2 实现类成员函数 10.3 类的构造函数和析构函数 10.3.1 声明和定义构造函数 10.3.2 使用构造函数 10.3.3 默认构造函数 10.3.4 析构函数 10.4 this指针 10.5 对象数组 10.6 …

[长安杯 2021学生组]baigei

Index 前言介绍漏洞 利用思路利用过程一.编写交互函数二.填充Tcache Bin三.释放Tcache Bin四.获取Libc地址五.Tcache Bin Attack六.完整EXP&#xff1a; 前言 最近有点迷茫&#xff0c;开始放松自己了。 心态还不是很对&#xff0c;需要继续调整。 介绍 本题是一题经典的堆题…

Java学习笔记:内部类,静态内部类,匿名内部类

​这是本人学习的总结&#xff0c;主要学习资料如下 疯狂Java讲义第三版&#xff0c;李刚编&#xff0c;电子工业出版社出版 目录 1、内部类1.1、内部类简介1.2、内部类与外部类的关系和区别&#xff1a;1.3、内部类的语法 2、 非静态内部类3、静态内部类4、匿名内部类 1、内部…

“链引擎”入驻案例 | 每天超过35万条存证上链,长安链支撑链上价值流动

引言 长安链“链引擎”计划&#xff08;Powered by Chainmaker&#xff09;(简称&#xff1a;PBC计划)是由长安链生态联盟发起的一项应用赋能计划&#xff0c;旨在以长安链技术体系为核心支撑&#xff0c;汇聚产业各方力量&#xff0c;为应用方提供技术、品牌、生态等支持&…

Keil系列教程03_主窗口和工具栏详细说明

1写在前面 本文先让大家简单认识一下Keil的主窗口界面&#xff0c;然后再进一步认识Keil的文件、编译和调试工具栏。 Toolbars工具栏就是在菜单下面的两行快捷图标按钮&#xff0c;这些快捷按钮之所以在工具栏里面&#xff0c;在于它们使用的频率较高。比如保存按钮、编译按钮…

ChatGPT智能AI对话软件

ChatGPT智能AI的市场前景非常广阔&#xff0c;因为随着人工智能技术的不断发展和应用&#xff0c;人们对于智能AI对话系统的需求也越来越大。未来&#xff0c;智能AI对话系统将在各个领域得到广泛应用&#xff0c;例如智能客服、智能家居、自动驾驶等等&#xff0c;这些都有助于…

STM32 HAL库PID控制电机 第二章 TB6612FNG芯片驱动GB37-520电机

STM32 HAL库PID控制电机 第二章 TB6612FNG芯片驱动GB37-520电机(HAL库) 1 电路图 2 TB6612简介 TB6612是双驱动&#xff0c;可同时驱动两个电机 STBY&#xff1a;接单片机的IO口清零电机全部停止&#xff0c;置1通过AIN1 AIN2&#xff0c;BIN1&#xff0c;BIN2 来控制正反转…

linux下静态库和动态库的制作

一.静态库的制作 linux下库的命名规则&#xff1a;在linux下以libXXX.a为命名&#xff0c;lib&#xff08;library&#xff09;前缀是固定的&#xff0c;代表这个是库。接下来介绍静态库的制作流程。 1.1通过gcc编译获得.o文件 一般源程序经过预处理完成头文件和宏的展开&am…

运行时内存数据区之虚拟机栈——局部变量表

这篇内容十分重要,文字也很多,仔细阅读后,你必定有所收获! 基本内容 与程序计数器一样&#xff0c;Java虚拟机栈&#xff08;Java Virtual Machine Stack&#xff09;也是线程私有的&#xff0c;它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的线程内存模型&#xf…

我想知道,就目前形势而言,学java好还是C++好?

前言 就现实点看看&#xff0c;可以对比现在Java和C的市场占有率&#xff0c;可以看到&#xff0c;到目前为止&#xff0c;Java在国内编程语言的市场仍然是占据着大头&#xff0c;在招聘当中Java的人数占有率仍然是遥遥领先于C&#xff0c;Java目前开阔的市场以及其巨大的岗位…

阿里,字节,拼多多,B站挨个面试一遍,你们猜哪个待遇最高?

我面试的是软件测试岗位&#xff0c;去年中旬的时候从原来的公司离职了&#xff0c;不是工作不好&#xff0c;而是公司发展速度太慢&#xff0c;自己干了几年&#xff0c;也没有太大的成长。以我目前的工作经验和实力&#xff0c;我认为准备一两个月&#xff0c;进大厂不是什么…

VS2022下载安装与基本使用(写C语言)

最近遇到一种问题&#xff0c;就是想要写一写C语言的代码&#xff0c;但是网页编辑器功能不全&#xff0c;GCC需要安装Liunx系统&#xff0c;VS又体量太大过于复杂&#xff0c;用keil又需要连接硬件&#xff0c;所以比较纠结。 工作中通常使用的是Keil&#xff0c;但是如果有时…

有记忆功能的动态通讯录

目录 前言1.进行文件操作的改造1.1contact.h的改造1.2contact.c的改造1.3test.c的改造 2.带文件操作的动态通讯录源码2.1contact.h2.2contact.c2.3test.c 总结 前言 前面我们一起学习的动态通讯录&#xff0c;一退出此程序联系人的信息就不见了&#xff1b;学习了文件操作操作后…

cocos creator 中使用web worker

1.应用场景&#xff1a;一些阻塞线程的方法可以放到worker里面去执行&#xff0c;不影响主线程&#xff0c;避免页面卡顿。 啊&#xff0c;有人就会说了&#xff0c;setTimeout不就可以了吗&#xff0c;还有什么async... JS本身就是单线程设计的&#xff0c;不管你是setTimeo…

EIGRP配置邻居关系详解

1.2 EIGRP 邻居关系 1.2.1 实验目的 通过 EIGRP 邻居建立的相关实验&#xff0c;学习到如何调整 EIGRP 的 HELLO 和 HOLD 时间&#xff0c;使用 被动接口阻止不必要的邻居关系&#xff0c;认证 EIGRP 邻居&#xff0c;静态邻居的配置以及哪些参数影响 EIGRP 邻居建立。 1.2.…