Post

尚硅谷_尚品甄选项目_第5章 支付

` |u ``` |Java package com.spzx.lap --- `` 故 ```_Exchange("f支付s\nIn1|(包围库存|*/ | 宋 ```_""" |.|回调信息在配置。 ``Y支持打印 茜据监听 :"spuffer |, ```Java ```` ##应用 ```存根订购信息 sYa.parseFloat | 燕|\交流|x|# |诺du8配置| |JsLim("从“交易 ```|心甘优先支付| |CaA *</dependency> X |*)"协议"China |XZff 家uckX**HM/Xd| */ ____函数压未支付 |R** |\u信息 ```)](js|\交流|x>#XSPzx 蔓宗 ```函数付款报酬 ```}) |S? \一|]"交易支付内容: |}* Xml |................................anRfidGZWFCFlixX »]); ```>|臓激氟 ```$ 裕份DMX|S拆支付状态:| yum; ```-- ```java *}*Xml|。 ``` ```java ```| X|夣 ```` Xjava ```| X|Xenoan 由此可见 ```)"); ``` #### 7环境压 ```光荣对。”支付 状态 ```Pbpv施工Zp\nzelw | |Rs Listen |FXZNCZ"'; ```java ``` \首先FG */X| ]SJPEGCompression(expSexzxT} ```PbXpeterZzust() ```* #### 4Java ```fn #### 支付 ```刑 ```Xjava 和平4交流 ``,。 ```jsp **O=`pciones ```PbXnipgmZzGzk ####PyifsumCC}`); ```可行性 课堂教学中心 ```Java 流程:$\F}*Xml|* ```*pobZWJSJXYPBFCAJ.xmlVFi|m|-XwNrUxFaNkbxxufEPK"); #### 4Java 监听回调文件中 ```| ```pBaBDEPt` ...... ```|显描写显示 ![ xml ``` *pjXPBP* ###2PbXzpizXj"],["xml",", (```FPython Compression("yyyy-MM-db") ```zZ || #### 4Java 监听回调文件中注意 \|jp|) ```}JACONSTANT名列前集 #### 4Java 监听回调文件中xYdPS; ```| X]咏占Vx;LRxl \交流Xyz|cX] ```4Java 监听回调文件 中献GZcaHJava ``` functionsub|UjIxv.htm

尚品臻选 阅读 1 点赞 0 评论 0

第5章 商品甄选-支付

5.1 支付宝支付

5.1.1 需求说明

订单支付如图所示:

pay

在支付页面点击确认支付按钮此时就需要对接第三方支付系统,给用户展示出第三方支付系统的收银台。

查看接口文档:

支付接口地址及返回结果:

get /payment/alipay/submitAlipay/{orderNo}
返回结果:
支付宝支付H5表单

5.1.2 支付宝支付

官网地址:https://open.alipay.com/

支付宝(中国)网络技术有限公司 [1] 是国内的第三方支付平台,致力于提供“简单、安全、快速”的支付解决方案 [2] 。支付宝公司从2004年建立开始,始终以“信任”作为产品和服务的核心。旗下有“支付宝”与“支付宝钱包”两个独立品牌。自2014年第二季度开始成为当前全球最大的移动支付厂商。

1、产品介绍

(1)产品特色

选择手机网站支付:https://open.alipay.com/api/detail?code=I1080300001000041949

image-20230709160020998

手机网站支付是指商家在移动端网页展示商品或服务,用户在商家页面确认使用支付宝支付后,浏览器自动跳转支付宝 App 或支付宝网页完成付款的支付产品。该产品在签约完成后,需要技术集成方可使用。

(2)使用示例

image-20240729101520075

(3)申请条件

支持的账号类型:支付宝企业账号支付宝个人账号

签约申请提交材料要求:

  • 提供网站地址,网站能正常访问且页面显示完整,网站需要明确经营内容且有完整的商品信息。
  • 网站必须通过 ICP 备案,且备案主体需与支付宝账号主体一致。若网站备案主体与当前账号主体不同时需上传授权函。
  • 个人账号申请,需提供营业执照,且支付宝账号名称需与营业执照主体一致。

注意:需按照要求提交材料,若部分材料不合格,收款额度将受到限制(单笔收款 ≤ 2000 元,单日收款 ≤ 20000 元)。若签约时未能提供相关材料(如营业执照),请在合约生效后的 30 天内补全,否则会影响正常收款。

(4)费率

收费模式 费率
单笔收费 0.6%-1.0%

特殊行业费率为 1.0%,非特殊行业费率为 0.6%。特殊行业包含:休闲游戏、网络游戏点卡、游戏渠道代理、游戏系统商、网游周边服务、交易平台、网游运营商(含网页游戏)等。

2、接入准备

官方文档:https://opendocs.alipay.com/open/203/107084?pathHash=a33de091

整体流程:

image-20230709170008020

为了提供数据传输的安全性,在进行传输的时候需要对数据进行加密:

常见的加密方式:

1、不可逆加密:只能对数据进行加密不能解密

2、可逆加密:可以对数据加密也可以解密

可逆加密可以再细分为:

1、对称加密: 加密和解密使用同一个秘钥

image-20230709170626255

2、非对称加密:加密和解密使用的是不同的秘钥

image-20230709170703058

支付宝为了提供数据传输的安全性使用了两个秘钥对:

image-20230709170850260

3、手机网站支付快速接入

官方文档:https://opendocs.alipay.com/open/203/105285?pathHash=ada1de5b

系统交互流程图:

image-20230709164753985

作为我们的项目来讲只需要将支付宝的收银台展示给用户即可,后续支付的动作和我们的系统就没有关系了。支付成功以后,支付宝开放平台会请求我们系统的接口通知支付结果,我们的系统也可以调用支付宝交易查询接口获取支付结果。

4、官方demo研究

步骤:

5.1.3 搭建支付模块

1、新建模块

在spzx-modules模块下新建子模块spzx-payment

2、pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>com.spzx</groupId>
        <artifactId>spzx-modules</artifactId>
        <version>3.6.3</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spzx-payment</artifactId>

    <description>
        spzx-payment支付模块
    </description>

    <dependencies>
        <!-- SpringCloud Alibaba Nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!-- SpringCloud Alibaba Nacos Config -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

        <!-- SpringCloud Alibaba Sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

        <!-- SpringBoot Actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- Mysql Connector -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>

        <!-- spzx Common DataScope -->
        <dependency>
            <groupId>com.spzx</groupId>
            <artifactId>spzx-common-datascope</artifactId>
        </dependency>

        <!-- spzx Common Log -->
        <dependency>
            <groupId>com.spzx</groupId>
            <artifactId>spzx-common-log</artifactId>
        </dependency>

    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

3、banner.txt

在resources目录下新建banner.txt

Spring Boot Version: ${spring-boot.version}
Spring Application Name: ${spring.application.name}
                            _                           _                    
                           (_)                         | |                   
 _ __  _   _   ___   _   _  _  ______  ___  _   _  ___ | |_   ___  _ __ ___  
| '__|| | | | / _ \ | | | || ||______|/ __|| | | |/ __|| __| / _ \| '_ ` _ \ 
| |   | |_| || (_) || |_| || |        \__ \| |_| |\__ \| |_ |  __/| | | | | |
|_|    \__,_| \___/  \__, ||_|        |___/ \__, ||___/ \__| \___||_| |_| |_|
                      __/ |                  __/ |                           
                     |___/                  |___/                            

4、bootstrap.yml

在resources目录下新建bootstrap.yml

# Tomcat
server:
  port: 9210

# Spring
spring:
  application:
    # 应用名称
    name: spzx-payment
  profiles:
    # 环境配置
    active: dev
  main:
    allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册
  cloud:
    nacos:
      discovery:
        # 服务注册地址
        server-addr: 127.0.0.1:8848
      config:
        # 配置中心地址
        server-addr: 127.0.0.1:8848
        # 配置文件格式
        file-extension: yml
        # 共享配置
        shared-configs:
          - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

5、spzx-payment-dev.yml

在nacos上添加商品服务配置文件

mybatis-plus:
  mapper-locations: classpath*:mapper/**/*Mapper.xml
  type-aliases-package: com.spzx.**.domain
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 查看日志
# spring配置
spring:
  data:
    redis:
      host: localhost
      port: 6379333
      password:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    publisher-confirm-type: CORRELATED
    publisher-returns: true
    listener:
      simple:
        cknowledge-mode: manual #默认情况下消息消费者是自动确认消息的,如果要手动确认消息则需要修改确认模式为manual
        prefetch: 1 # 消费者每次从队列获取的消息数量。此属性当不设置时为:轮询分发,设置为1为:公平分发
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/spzx-payment?characterEncoding=utf-8&useSSL=false
    username: root
    password: root
    hikari:
      connection-test-query: SELECT 1
      connection-timeout: 60000
      idle-timeout: 500000
      max-lifetime: 540000
      maximum-pool-size: 10
      minimum-idle: 5
      pool-name: GuliHikariPool

6、logback.xml

在resources目录下新建logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <!-- 日志存放路径 -->
   <property name="log.path" value="logs/spzx-payment" />
   <!-- 日志输出格式 -->
   <property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />

    <!-- 控制台输出 -->
   <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
      <encoder>
         <pattern>${log.pattern}</pattern>
      </encoder>
   </appender>

    <!-- 系统日志输出 -->
   <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
       <file>${log.path}/info.log</file>
        <!-- 循环政策:基于时间创建日志文件 -->
      <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志文件名格式 -->
         <fileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
         <!-- 日志最大的历史 60天 -->
         <maxHistory>60</maxHistory>
      </rollingPolicy>
      <encoder>
         <pattern>${log.pattern}</pattern>
      </encoder>
      <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 过滤的级别 -->
            <level>INFO</level>
            <!-- 匹配时的操作:接收(记录) -->
            <onMatch>ACCEPT</onMatch>
            <!-- 不匹配时的操作:拒绝(不记录) -->
            <onMismatch>DENY</onMismatch>
        </filter>
   </appender>

    <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
       <file>${log.path}/error.log</file>
        <!-- 循环政策:基于时间创建日志文件 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志文件名格式 -->
            <fileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
         <!-- 日志最大的历史 60天 -->
         <maxHistory>60</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 过滤的级别 -->
            <level>ERROR</level>
         <!-- 匹配时的操作:接收(记录) -->
            <onMatch>ACCEPT</onMatch>
         <!-- 不匹配时的操作:拒绝(不记录) -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 系统模块日志级别控制  -->
   <logger name="com.spzx" level="info" />
   <!-- Spring日志级别控制  -->
   <logger name="org.springframework" level="warn" />

   <root level="info">
      <appender-ref ref="console" />
   </root>

   <!--系统操作日志-->
    <root level="info">
        <appender-ref ref="file_info" />
        <appender-ref ref="file_error" />
    </root>
</configuration>

7、SpzxPaymentApplication

添加启动类

package com.spzx.payment;

/**
 * 支付模块
 */
@EnableCustomConfig
@EnableRyFeignClients
@SpringBootApplication
public class SpzxPaymentApplication
{
    public static void main(String[] args)
    {
        SpringApplication.run(SpzxPaymentApplication.class, args);
        System.out.println("(♥◠‿◠)ノ゙  系统模块启动成功   ლ(´ڡ`ლ)゙  \n" +
                " .-------.       ____     __        \n" +
                " |  _ _   \\      \\   \\   /  /    \n" +
                " | ( ' )  |       \\  _. /  '       \n" +
                " |(_ o _) /        _( )_ .'         \n" +
                " | (_,_).' __  ___(_ o _)'          \n" +
                " |  |\\ \\  |  ||   |(_,_)'         \n" +
                " |  | \\ `'   /|   `-'  /           \n" +
                " |  |  \\    /  \\      /           \n" +
                " ''-'   `'-'    `-..-'              ");
    }
}

8、配置网关

在spzx-gateway-dev.yml配置文件中添加会员服务的网关信息

# 支付服务
- id: spzx-payment
  uri: lb://spzx-payment
  predicates:
    - Path=/payment/**
  filters:
    - StripPrefix=1

5.1.4 搭建远程调用模块

订单支付需要调用订单服务接口

参考:spzx-api-system模块

1、新建模块

在spzx-api模块下新建子模块spzx-api-order

2、pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>com.spzx</groupId>
        <artifactId>spzx-api</artifactId>
        <version>3.6.3</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spzx-api-order</artifactId>

    <description>
        spzx-api-order订单接口模块
    </description>

    <dependencies>

        <!-- spzx Common Core-->
        <dependency>
            <groupId>com.spzx</groupId>
            <artifactId>spzx-common-core</artifactId>
        </dependency>

    </dependencies>

</project>

3、spzx-modules模块引入api依赖

spzx-modules模块引入api依赖

<dependency>
    <groupId>com.spzx</groupId>
    <artifactId>spzx-api-order</artifactId>
    <version>3.6.3</version>
</dependency>

5.1.5 根据订单号获取订单信息

操作模块:spzx-order

1、远程调用接口开发

(1)OrderInfoController

@Operation(summary = "根据订单号获取订单信息")
@InnerAuth
@GetMapping("getByOrderNo/{orderNo}")
public R<OrderInfo> getByOrderNo(@PathVariable String orderNo) {
    OrderInfo orderInfo = orderInfoService.getByOrderNo(orderNo);
    return R.ok(orderInfo);
}

(2)IOrderInfoService

OrderInfo getByOrderNo(String orderNo);

(3)OrderInfoServiceI

@Override
public OrderInfo getByOrderNo(String orderNo) {
    OrderInfo orderInfo = orderInfoMapper.selectOne(new LambdaQueryWrapper<OrderInfo>().eq(OrderInfo::getOrderNo, orderNo));
    List<OrderItem> orderItemList = orderItemMapper.selectList(new LambdaQueryWrapper<OrderItem>().eq(OrderItem::getOrderId, orderInfo.getId()));
    orderInfo.setOrderItemList(orderItemList);
    return orderInfo;
}

2、openFeign接口定义

操作模块:spzx-api-order

**注意:**先将spzx-order模块中的OrderInfo类和OrderItem类移动到spzx-api-order 模块

(1)RemoteOrderInfoService

package com.spzx.order.api;

@FeignClient(contextId = "remoteUserInfoService", value = ServiceNameConstants.ORDER_SERVICE, fallbackFactory = RemoteOrderInfoFallbackFactory.class)
public interface RemoteOrderInfoService
{

    @GetMapping("/orderInfo/getByOrderNo/{orderNo}")
    public R<OrderInfo> getByOrderNo(@PathVariable("orderNo") String orderNo, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);

}

(2)ServiceNameConstants

/**
  * 订单服务的serviceid
  */
public static final String ORDER_SERVICE = "spzx-order";

(3)RemoteOrderInfoFallbackFactory

package com.spzx.order.api.factory;

@Component
public class RemoteOrderInfoFallbackFactory implements FallbackFactory<RemoteOrderInfoService>
{
    private static final Logger log = LoggerFactory.getLogger(RemoteOrderInfoFallbackFactory.class);

    @Override
    public RemoteOrderInfoService create(Throwable throwable)
    {
        log.error("订单服务调用失败:{}", throwable.getMessage());
        return new RemoteOrderInfoService()
        {

            @Override
            public R<OrderInfo> getByOrderNo(String orderNo, String source) {
                return R.fail("根据订单号获取订单信息失败:" + throwable.getMessage());
            }
        };
    }
}

(5)加载配置类

resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

com.spzx.order.api.factory.RemoteOrderInfoFallbackFactory

5.1.6 创建payment_info表代码

spzx-payment模块创建

  • PaymentInfo.java
package com.spzx.payment.domain;

import java.math.BigDecimal;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import com.spzx.common.core.annotation.Excel;
import com.spzx.common.core.web.domain.BaseEntity;

/**
 * 付款信息对象 payment_info
 */
@Data
@Schema(description = "付款信息")
public class PaymentInfo extends BaseEntity
{
    private static final long serialVersionUID = 1L;

    /** 用户id */
    @Excel(name = "用户id")
    @Schema(description = "用户id")
    private Long userId;

    /** 订单号 */
    @Excel(name = "订单号")
    @Schema(description = "订单号")
    private String orderNo;

    /** 付款方式:1-微信 2-支付宝 */
    @Excel(name = "付款方式:1-微信 2-支付宝")
    @Schema(description = "付款方式:1-微信 2-支付宝")
    private Long payType;

    /** 交易编号(微信或支付) */
    @Excel(name = "交易编号", readConverterExp = "微=信或支付")
    @Schema(description = "交易编号")
    private String tradeNo;

    /** 支付金额 */
    @Excel(name = "支付金额")
    @Schema(description = "支付金额")
    private BigDecimal amount;

    /** 交易内容 */
    @Excel(name = "交易内容")
    @Schema(description = "交易内容")
    private String content;

    /** 支付状态:0-未支付 1-已支付 -1-关闭 */
    @Excel(name = "支付状态:0-未支付 1-已支付 -1-关闭")
    @Schema(description = "支付状态:0-未支付 1-已支付 -1-关闭")
    private String paymentStatus;

    /** 回调时间 */
    @JsonFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "回调时间", width = 30, dateFormat = "yyyy-MM-dd")
    @Schema(description = "回调时间")
    private Date callbackTime;

    /** 回调信息 */
    @Excel(name = "回调信息")
    @Schema(description = "回调信息")
    private String callbackContent;

}
  • IPaymentInfoService.java
package com.spzx.payment.service;

/**
 * 付款信息Service接口
 */
public interface IPaymentInfoService extends IService<PaymentInfo> {
}
  • PaymentInfoServiceImpl.java
package com.spzx.payment.service.impl;

/**
 * 付款信息Service业务层处理
 */
@Service
public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, PaymentInfo> implements IPaymentInfoService {
}
  • PaymentInfoMapper.java
package com.spzx.payment.mapper;

/**
 * 付款信息Mapper接口
 */
public interface PaymentInfoMapper extends BaseMapper<PaymentInfo> {
}

5.1.7 支付接口

操作模块:spzx-payment

1、引入依赖

<dependency>
    <groupId>com.alipay.sdk</groupId>
    <artifactId>alipay-sdk-java</artifactId>
</dependency>

2、spzx-payment-dev.yml

支付宝示例demo:https://opendocs.alipay.com/open/203/105285?pathHash=ada1de5b

将支付宝所需要的参数定义配置文件中,以提高代码的维护性

在nacos上添加支付宝服务配置文件

alipay:
  alipay_url: https://openapi.alipay.com/gateway.do
  app_id: 2021001163617452
  app_private_key: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8Z7EZmanxyFGsK4LrIUeKKrrGxWAHIgPmUV8TtZDs+jeplJSw1ckSY63QhEU444D5qd6xruJHBuB33HG+ik4n8N8nRWi3AtMgpC061oq2DcgtIKMmQHO7/poYDwbpDZrOWXIyiNshFfUOSTUpnrS8UvEks6n6xR/G72r2FG07oZzO7g3XsPMr73wpYajMYC/bhTm6CJGEWZikONNDFkQpVHa+zgitwsqlBuvBvVwGwOHA9B8aRfokwAMl6BDXKoH8BNnSEMpWSTRSwbssayXAQWNU7XKDKGozbn4U2dEbl8GCFzikI/T7ybTNm5gs46ZZBGlq/YB4+v4D3t74Vl6nAgMBAAECggEAOidzhehliYkAlLk1huhV0bMQxewEkQ8RzxTM2SORIWS2q7R+FPtYPkHgU92QFFg85lNltsi5dZ0MylKUFXFRYIi8CL4m7V6E1q12fJPeawVkBXHuig8Y6i1TWRvCUUtuvkTjt++AW/0QECHOtBMVzI95eY+vZwVToq8h/+UcNmxKyVt66Qpo4+r+cUvlvGX5mXgQVC5Ftf/MtHA1i+kjtzBITC0xAvmSXKzjN1YhtcS9rXyMHXBiFhXLdmvOXjkn0Okosr2+tmesXfSwDGhH3ZlOdHzit4D602RNl0nTA1dOUWHuCncs1TrWbriax86P/EYvmzMiHWCVTmmNJC0bMQKBgQD0HAXKNsYsdjCQOV4t3SMqOKaul67x/KA20PmMZVfQ2sQkyjyFgWpL8C16Rzf3zI7df+zF5SkvhFY4+LRZVwX5okEFYTzAZ/NYouj1/DABYOPq0E0sY18/xtq7FJ/CIk8qmCqcczqoyaoxoaC1zAt9E4CYE89iEOnO+GhcI3H3LwKBgQDFlQzvbXhWRyRFkeft/a52XLnyj6t9iP7wNGbGCSeoMDrAu3ZgoqacUPWj5MgSFZdT48H9rF4pPixXoe3jfUNsWBUHqD1F2drDz7lpL0PbpSsgy6ei+D4RwTADsuyXwrkvrWrGro+h6pNJFyly3nea/gloDtJTzfhFFwtNfmqyCQKBgBXzMx4UwMscsY82aV6MZO4V+/71CrkdszZaoiXaswPHuB1qxfhnQ6yiYyR8pO62SR5ns120Fnj8WFh1HJpv9cyVp20ZakIO1tXgiDweOh7VnIjvxBC6usTcV6y81QS62w2Ec0hwIBUvVQtzciUGvP25NDX4igxSYwPGWHP4h/XnAoGAcQN2aKTnBgKfPqPcU4ac+drECXggESgBGof+mRu3cT5U/NS9Oz0Nq6+rMVm1DpMHAdbuqRikq1aCqoVWup51qE0hikWy9ndL6GCynvWIDOSGrLWQZ2kyp5kmy5bWOWAJ6Ll6r7Y9NdIk+NOkw614IFFaNAj2STUw4uPxdRvwD3ECgYEArwOZxR3zl/FZfsvVCXfK8/fhuZXMOp6Huwqky4tNpVLvOyihpOJOcIFj6ZJhoVdmiL8p1/1S+Sm/75gx1tpFurKMNcmYZbisEC7Ukx7RQohZhZTqMPgizlVBTu5nR3xkheaJC9odvyjrWQJ569efXo30gkW04aBp7A15VNG5Z/U=
  alipay_public_key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkWs+3gXMosiWG+EbfRyotWB0waqU3t7qMQSBxU0r3JZoND53jvWQfzrGZ8W+obMc+OgwupODDVxhG/DEKVBIptuUQYdvAjCSH98m2hclFcksspuCy9xS7PyflPE47pVzS6vA3Slvw5OFQ2qUcku4paWnBxguLUGPjEncij5NcyFyk+/k57MmrVJwCZaI+lFOS3Eq2IXc07tWXO4s/2SWr3EJiwJutOGBdA1ddvv1Urrl0pWpEFg30pJB6J7YteuxdEL90kuO5ed/vnTK5qgQRvEelROkUW44xONk1784v28OJXmGICmNL1+KyM/SFbFOSgJZSV1tEXUzvL/xvzFpLwIDAQAB
  return_payment_url: http://ry-spzx-api.atguigu.cn/payment/alipay/callback/return
  notify_payment_url: http://ry-spzx-api.atguigu.cn/payment/alipay/callback/notify
  return_order_url: http://ry-spzx.atguigu.cn/#/pages/money/paySuccess

3、AlipayConfig

package com.spzx.payment.configure;

import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AlipayConfig {

    @Value("${alipay.alipay_url}")
    private String alipay_url;

    @Value("${alipay.app_private_key}")
    private String app_private_key;

    @Value("${alipay.app_id}")
    private String app_id;


    public final static String format="json";
    public final static String charset="utf-8";
    public final static String sign_type="RSA2";


    public static String return_payment_url;

    public static  String notify_payment_url;

    public static  String return_order_url;

    public static  String alipay_public_key;

    @Value("${alipay.alipay_public_key}")
    public   void setAlipay_public_key(String alipay_public_key) {
        AlipayConfig.alipay_public_key = alipay_public_key;
    }

    @Value("${alipay.return_payment_url}")
    public   void setReturn_url(String return_payment_url) {
        AlipayConfig.return_payment_url = return_payment_url;
    }

    @Value("${alipay.notify_payment_url}")
    public   void setNotify_url(String notify_payment_url) {
        AlipayConfig.notify_payment_url = notify_payment_url;
    }

    @Value("${alipay.return_order_url}")
    public   void setReturn_order_url(String return_order_url) {
        AlipayConfig.return_order_url = return_order_url;
    }

    @Bean
    public AlipayClient alipayClient(){
        AlipayClient alipayClient=new DefaultAlipayClient(alipay_url,app_id,app_private_key,format,charset, alipay_public_key,sign_type );
        return alipayClient;
    }

}

4、AlipayController

package com.spzx.payment.controller;

@Slf4j
@Controller
@RequestMapping("/alipay")
public class AlipayController extends BaseController {

    @Autowired
    private IAlipayService alipayService;

    @Operation(summary = "支付宝下单")
    @RequiresLogin
    @RequestMapping("submitAlipay/{orderNo}")
    @ResponseBody
    public AjaxResult submitAlipay(@PathVariable(value = "orderNo") String orderNo) {
        String form = alipayService.submitAlipay(orderNo);
        return success(form);
    }

}

5、IAlipayService

package com.spzx.payment.service;

public interface IAlipayService{
    String submitAlipay(String orderNo);
}

6、AlipayServiceImpl

package com.spzx.payment.service.impl;

@Service
@Slf4j
public class AlipayServiceImpl implements IAlipayService {

    @Autowired
    private AlipayClient alipayClient;

    @Autowired
    private IPaymentInfoService paymentInfoService;

    @SneakyThrows
    @Override
    public String submitAlipay(String orderNo) {
        //保存支付记录
        PaymentInfo paymentInfo = paymentInfoService.savePaymentInfo(orderNo);
        // 生产二维码
        AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();//创建API对应的request
        // 同步回调
        // return_payment_url=http://sph-payment.atguigu.cn/alipay/callback/return
        alipayRequest.setReturnUrl(AlipayConfig.return_payment_url);
        // 异步回调
        alipayRequest.setNotifyUrl(AlipayConfig.notify_payment_url);//在公共参数中设置回跳和通知地址
        // 参数
        // 声明一个map 集合
        HashMap<String, Object> map = new HashMap<>();
        map.put("out_trade_no",paymentInfo.getOrderNo());
        map.put("product_code","QUICK_WAP_WAY");
        //map.put("total_amount",orderInfo.getTotalAmount());
        map.put("total_amount","0.01");
        map.put("subject",paymentInfo.getContent());
        map.put("time_expire","2025-03-29 17:09:00"); //设置订单绝对超时时间

        alipayRequest.setBizContent(JSON.toJSONString(map));

        return alipayClient.pageExecute(alipayRequest).getBody(); //调用SDK生成表单;
    }
}

7、IPaymentInfoService

PaymentInfo savePaymentInfo(String orderNo);

8、PaymentInfoServiceImpl

@Autowired
private PaymentInfoMapper paymentInfoMapper;

@Autowired
private RemoteOrderInfoService remoteOrderInfoService;

@Override
public PaymentInfo savePaymentInfo(String orderNo) {
   PaymentInfo paymentInfo = paymentInfoMapper.selectOne(new LambdaQueryWrapper<PaymentInfo>().eq(PaymentInfo::getOrderNo, orderNo));
   if(null == paymentInfo) {
      R<OrderInfo> orderInfoResult = remoteOrderInfoService.getByOrderNo(orderNo, SecurityConstants.INNER);
      if (R.FAIL == orderInfoResult.getCode()) {
         throw new ServiceException(orderInfoResult.getMsg());
      }
      OrderInfo orderInfo = orderInfoResult.getData();
      
      paymentInfo = new PaymentInfo();
      paymentInfo.setUserId(orderInfo.getUserId());
      String content = "";
      for(OrderItem item : orderInfo.getOrderItemList()) {
         content += item.getSkuName() + " ";
      }
      paymentInfo.setContent(content);
      paymentInfo.setAmount(orderInfo.getTotalAmount());
      paymentInfo.setOrderNo(orderNo);
      paymentInfo.setPaymentStatus("0");
      paymentInfoMapper.insert(paymentInfo);
   }
   return paymentInfo;
}

5.1.8 支付宝同步回调

支付成功后点击完成会自动跳转回商家页面地址

1、AlipayController

/**
 * 支付宝同步回调
 * @return
 */
@Operation(summary = "支付宝同步回调")
@GetMapping("callback/return")
public String callBack() {
    // 同步回调给用户展示信息
    return "redirect:/" + AlipayConfig.return_order_url;
}

2、配置网关

# 不校验白名单
ignore:
  whites:
    ...
    - /payment/alipay/callback/*

5.1.9 支付宝异步回调

操作模块:spzx-payment

1、异步通知说明

官网地址:https://opendocs.alipay.com/open/203/105286?pathHash=022a439c&ref=api

image-20230710180507583

当用户支付成功以后,支付宝系统会调用我们系统的接口通知支付结果

2、AlipayController

@RequestMapping("callback/notify")
@ResponseBody
public String alipayNotify(@RequestParam Map<String, String> paramMap, HttpServletRequest request) {
    log.info("AlipayController...alipayNotify方法执行了...");
    return "success" ;
}

3、内网穿透介绍

当支付成功以后支付宝无法调用本地接口,因为本地接口是位于一个私有IP地址范围内,并且被路由器或防火墙等设备保护起来。这个私有的网络设备无法直接从公共网络访问,该问题的解决可以使用内网穿透技术。

内网穿透:内网穿透(Intranet Port Forwarding)是一种将本地网络中的服务暴露给公共网络访问的技术。

内网穿透通过在公共网络上建立一个中转服务器,使得公共网络上的设备可以通过该中转服务器访问内网中的设备和服务。具体而言,内网穿透技术允

许您在公共网络上使用一个公网IP地址和端口号来映射到内网中的某个设备或服务的私有IP地址和端口号。

image-20230710185519318

常见的内网穿透工具包括natapp、Ngrok、frp、花生壳等。

官网地址:https://natapp.cn/

试用步骤:

1、注册用户

2、购买隧道

image-20230219170135784

3、购买二级域名,绑定隧道

image-20230219170206186

4、下载客户端

image-20230219170337316

5、客户端使用教程:https://natapp.cn/article/nohup

natapp.exe -authtoken=xxxxx

authtoken信息获取:

image-20230219171016742

4、验证签名

AlipayController

 @Autowired
 private IPaymentInfoService paymentInfoService;

@RequestMapping("callback/notify")
@ResponseBody
public String alipayNotify(@RequestParam Map<String, String> paramMap, HttpServletRequest request) {
    log.info("AlipayController...alipayNotify方法执行了...");
    boolean signVerified = false; //调用SDK验证签名
    try {
        signVerified = AlipaySignature.rsaCheckV1(paramMap, AlipayConfig.alipay_public_key, AlipayConfig.charset, AlipayConfig.sign_type);
        
    } catch (AlipayApiException e) {
        e.printStackTrace();
    }

    // 交易状态
    String trade_status = paramMap.get("trade_status");

    // true
    if (signVerified) {
        // TODO 验签成功后,按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验,校验成功后在response中返回success并继续商户自身业务处理,校验失败返回failure
        if ("TRADE_SUCCESS".equals(trade_status) || "TRADE_FINISHED".equals(trade_status)) {
            // 正常的支付成功,我们应该更新交易记录状态
            paymentInfoService.updatePaymentStatus(paramMap, 2);
            return "success";
        }
    } else {
        // TODO 验签失败则记录异常日志,并在response中返回failure.
        return "failure";
    }
    return "failure";
}

5、IPaymentInfoService

更新支付信息

void updatePaymentStatus(Map<String, String> map, Integer payType);

6、PaymentInfoServiceImpl

@Autowired
private RabbitService rabbitService;

@Transactional(rollbackFor = Exception.class)
@Override
public void updatePaymentStatus(Map<String, String> map, Integer payType) {
   PaymentInfo paymentInfo = paymentInfoMapper.selectOne(new LambdaQueryWrapper<PaymentInfo>().eq(PaymentInfo::getOrderNo, map.get("out_trade_no")));
   if ("1".equals(paymentInfo.getPaymentStatus())) {
      return;
   }

   //更新支付信息
   paymentInfo.setPayType(payType);
   paymentInfo.setPaymentStatus("1");
   paymentInfo.setTradeNo(map.get("trade_no"));
   paymentInfo.setCallbackTime(new Date());
   paymentInfo.setCallbackContent(JSON.toJSONString(map));
   paymentInfoMapper.updateById(paymentInfo);

   //基于MQ通知订单系统,修改订单状态
   rabbitService.sendMessage(MqConst.EXCHANGE_PAYMENT_PAY, MqConst.ROUTING_PAYMENT_PAY, paymentInfo.getOrderNo());
}

5.2 支付成功处理

5.2.1 更改订单状态

操作模块:spzx-order

订单支付成功后,我们已经更改了订单支付记录状态,接下来我还有更改订单状态,因为他们是不同的微服务模块,所以我们采用消息队列的方式,保证数据最终一致性

1、OrderReceiver

/**
 * 监听订单支付成功消息;更新订单状态
 * @param orderNo
 * @param message
 * @param channel
 */
@SneakyThrows
@RabbitListener(bindings = @QueueBinding(
        exchange = @Exchange(value = MqConst.EXCHANGE_PAYMENT_PAY, durable = "true"),
        value = @Queue(value = MqConst.QUEUE_PAYMENT_PAY, durable = "true"),
        key = MqConst.ROUTING_PAYMENT_PAY
))
public void processPaySucess(String orderNo, Message message, Channel channel) {
    //业务处理
    if (StringUtils.isNotEmpty(orderNo)) {
        log.info("[订单服务]监听订单支付成功消息:{}", orderNo);
        //更改订单支付状态
        orderInfoService.processPaySucess(orderNo);
    }
    //手动应答
    channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}

2、IOrderInfoService

void processPaySucess(String orderNo);

3、OrderInfoServiceI

@Transactional(rollbackFor = Exception.class)
@Override
public void processPaySucess(String orderNo) {
    //获取订单信息
    OrderInfo orderInfo = orderInfoMapper.selectOne(new LambdaQueryWrapper<OrderInfo>().eq(OrderInfo::getOrderNo, orderNo).select(OrderInfo::getId, OrderInfo::getOrderStatus));
    //未支付
    if(orderInfo.getOrderStatus().intValue() == 0) {
        orderInfo.setOrderStatus(1);
        orderInfo.setPaymentTime(new Date());
        orderInfoMapper.updateById(orderInfo);
    }
}

5.2.2 扣减商品库存

订单模块除了更改订单支付状态,还要发送消息通知商品服务模块扣减库存

OrderReceiver类

@Autowired
private RabbitService rabbitService;

/**
 * 监听订单支付成功消息:更新订单状态;扣减商品库存
 * @param orderNo
 * @param message
 * @param channel
 */
@SneakyThrows
@RabbitListener(bindings = @QueueBinding(
        exchange = @Exchange(value = MqConst.EXCHANGE_PAYMENT_PAY, durable = "true"),
        value = @Queue(value = MqConst.QUEUE_PAYMENT_PAY, durable = "true"),
        key = MqConst.ROUTING_PAYMENT_PAY
))
public void processPaySucess(String orderNo, Message message, Channel channel) {
    //业务处理
    if (StringUtils.isNotEmpty(orderNo)) {
        log.info("[订单服务]监听订单支付成功消息:{}", orderNo);
        //更改订单支付状态
        orderInfoService.processPaySucess(orderNo);

        //基于MQ通知扣减库存
        rabbitService.sendMessage(MqConst.EXCHANGE_PRODUCT, MqConst.ROUTING_MINUS, orderNo);
    }
    //手动应答
    channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}

说明:扣减库存的mq接口前面已经提供,我们只需要发送消息即可。

继续阅读

全部归档
尚硅谷_尚品甄选项目_附录1_准备软件环境
尚硅谷_尚品甄选项目_附录1_准备软件环境

### 摘要 文章详细介绍了在Linux环境中通过Dockerocker安装各种软件环境的过程,包括Docker、MySQL、Redis、Nacos、MinIO、RabbitMQ、Nginx和Sentinel。相关步骤涵盖安装、启动、及配置等等各环节。为确保软件正常访问,还需进行一系列配套的配置、启动服务、调整时间同步等因素;安装完成后可用浏览器访问各自服务的默认端口进行验证,如调试。 通过上述安装及过程,用户D92可以在本地测试或部署相应的服务架构。文章强调了配置时间同步以确保系统时间一致的步骤,因为这对某些服务的正常运行至关重要。

尚硅谷_尚品甄选项目_第4章 订单
尚硅谷_尚品甄选项目_第4章 订单

该 章节 4.4 尚品甄选-订单中,重点覆盖了主题、订单结算、、的流程,关键结论,重要事和读者收益。 ### 摘要 在订单结算模块,涵盖以下核心内容: 11 对 主需说明数据和结算页所需呈现信息。 - 结算页面页面中 " 圅址管理 (新增、修改、删除)及获取用户地址信息)。 - �结算共接口 (包括新增用户地址、、修改用户地址 "和删除用户地址 " 及获取用户购物车列表、购物车结算信息)。 。 - 更新购物车价格及删除购物车选中商品。 - 保存订单信息"订单项信息" 及记录日志 "订单已操作验确,。 - 锂发假期延迟队列消息 "实现延迟拣扣 "。 - 开发库存接口(解锁库存及锁客库。 - 支付页接口 "根据详细信息。 ### 主要函数与 - 获取用户地址信息。 - 计算笔订单的总金额 - 保存订单信息 - 检查库存并封客库 - 实现延迟队列 "实现延迟订单拣取。 ### 读者收益 - 通过地址管理接口,用户可以管理各个地址信息。 - 结算时将详计表现在结算页面确保用户在结算 "清晰 示价 "。 - 检查库存且更新线足库存后呵保订单成功 - 实里延迟队列,做中条2实现订单拣取的延时有效处理。 这些核心内容显著提高了订单结算流程的准确度、简洁程d 和信息密度,既覆盖了了主题,又 Ensure 重点信息,确保用户在每一步操作中都能获益。同时避免用户的在操作中感到困惑。

评论