SpringBoot Alipay acessa o combate real

Diretório de artigos

Combate real de back-end de pagamento Alipay - baseado em SpringBoot

1. Introdução ao pagamento Alipay e guia de acesso

1. Introdução aos recursos abertos do Alipay

(1) Mapa de capacidade

Capacidade de pagamento, extensão de pagamento, capacidade de capital, capacidade de boca a boca, capacidade de marketing, capacidade de adesão, capacidade da indústria, etc.

Basta fazer login na plataforma aberta Alipay: https://opendocs.alipay.com/home

(2) Produtos de pagamento de sites de computador

Este projeto usa produtos de pagamento de sites de computador como exemplo

O entendimento principal inclui cenários de aplicação, condições de acesso, modos de cobrança, etc.

2. Preparação de acesso

(1) Registro de conta na plataforma aberta

insira a descrição da imagem aqui

Depois de fazer login com a conta Alipay, registre-se em uma conta de plataforma aberta e opte por se instalar. Durante esse período, você usará seu número de celular para receber um código de verificação.

Após a conclusão do registro, você se torna um desenvolvedor e pode criar aplicativos por conta própria.

(2) Processo de acesso regular

  • Crie um aplicativo : selecione o tipo de aplicativo, preencha as informações básicas do aplicativo, adicione funções do aplicativo, configure o ambiente do aplicativo (obtenha chave pública Alipay, chave pública do aplicativo, chave privada do aplicativo, endereço do gateway Alipay, configure o método de criptografia de conteúdo da interface ) e visualize APPID
  • Aplicativo de vinculação : Vincule o APPID na conta do desenvolvedor e o PID da conta do comerciante (o aplicativo para a conta do comerciante requer uma licença comercial formal).
  • Chave de configuração : Etapas para configurar o ambiente de aplicativos para criar um centro de aplicativos
  • Inscrição online : envie a inscrição para análise
  • Função de assinatura : carregue a licença comercial, as informações do site registrado, etc. no centro comercial e envie-o para revisão e assinatura.

(3) Use a caixa de areia

Este aplicativo real é baseado principalmente no ambiente sandbox e não precisa envolver o envio e revisão de informações, como licenças comerciais e arquivamentos de sites.É muito fácil para iniciantes aprenderem a pagar.

  • Configuração do ambiente sandbox

    • Obtenha o APPID e PID correspondente (correspondente à geração automática no ambiente sandbox)
    • Defina o método de criptografia da interface, selecione a chave padrão do sistema, selecione a criptografia de chave pública como método de criptografia e verifique a chave pública do aplicativo correspondente, a chave privada do aplicativo e a chave pública Alipay (as opções acima são usadas no processo de criptografia assimétrica).

  • Obter endereço de gateway Alipay

  • Defina o método de criptografia do conteúdo da interface (este conteúdo é usado principalmente para a criptografia da interface do aplicativo e é gerado automaticamente)

  • Baixe a versão sandbox do Alipay e escolha fazer login

2. Preparação ambiental para o projeto

1. Preparação do ambiente de estrutura

  • Projeto SpringBoot, versão 2.3.12.RELEASE
  • Seleção de versão do JDK 1.8
  • Importar dependências do projeto da webspring-boot-starter-web
  • Importar ferramenta de implantação ativaspring-boot-devtools
  • Importar Lombok:lombok
  • Importar o ambiente de teste do SpringBootspring-boot-starter-test
  • Apresentando o módulo de gerenciamento de transações do projeto no Springspring-tx
  • Apresentar ferramentas de teste de interfacespringfox-swagger2
  • Apresentar ferramenta de visualização de teste de interfacespringfox-swagger-ui
  • Introduzir dependências de conexão de banco de dadosmysql-connector-java
  • Introduzir dependências do projeto da camada de persistênciamybatis-plus
  • Introduzir dependências de ferramentas da camada de persistênciamybatis-plus-boot-starter
  • Introduzir a dependência do pool de conexão do banco de dados (Druida)druid
  • Importar informações de metadados personalizadosspring-boot-configuration-processor
  • Introduzir dependência de processamento de dados json (Google)gson
  • Apresentar as ferramentas de desenvolvedor SDK da Alipayalipay-sdk-java

As dependências correspondentes do arquivo pom são:

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.3.12.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
        </dependency>
<!--        swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>

<!--        mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

<!--        mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</artifactId>
            <version>3.5.1</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.8</version>
        </dependency>
        <!--生成自定义元数据项信息-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>
        <!--JSON数据处理-->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>

<!--        引入支付宝的sdk-->
        <dependency>
            <groupId>com.alipay.sdk</groupId>
            <artifactId>alipay-sdk-java</artifactId>
            <version>4.22.57.ALL</version>
        </dependency>

2. Ambiente de banco de dados

  • Crie dados mysql, nomeados comopayment_demo
  • Crie quatro tabelas, a saber: tabela de informações do pedido, tabela de informações de pagamento, tabela de produtos, tabela de informações de reembolso

O conteúdo das quatro tabelas é o seguinte:

CREATE TABLE `t_order_info` (
  `id` bigint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '订单id',
  `title` varchar(256) DEFAULT NULL COMMENT '订单标题',
  `order_no` varchar(50) DEFAULT NULL COMMENT '商户订单编号',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户id',
  `product_id` bigint(20) DEFAULT NULL COMMENT '支付产品id',
  `total_fee` int(11) DEFAULT NULL COMMENT '订单金额(分)',
  `code_url` varchar(50) DEFAULT NULL COMMENT '订单二维码连接',
  `order_status` varchar(10) DEFAULT NULL COMMENT '订单状态',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;


/*Table structure for table `t_payment_info` */

CREATE TABLE `t_payment_info` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '支付记录id',
  `order_no` varchar(50) DEFAULT NULL COMMENT '商户订单编号',
  `transaction_id` varchar(50) DEFAULT NULL COMMENT '支付系统交易编号',
  `payment_type` varchar(20) DEFAULT NULL COMMENT '支付类型',
  `trade_type` varchar(20) DEFAULT NULL COMMENT '交易类型',
  `trade_state` varchar(50) DEFAULT NULL COMMENT '交易状态',
  `payer_total` int(11) DEFAULT NULL COMMENT '支付金额(分)',
  `content` text COMMENT '通知参数',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;


/*Table structure for table `t_product` */

CREATE TABLE `t_product` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品id',
  `title` varchar(20) DEFAULT NULL COMMENT '商品名称',
  `price` int(11) DEFAULT NULL COMMENT '价格(分)',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

/*Data for the table `t_product` */

insert  into `t_product`(`title`,`price`) values ('Java课程',1);
insert  into `t_product`(`title`,`price`) values ('大数据课程',1);
insert  into `t_product`(`title`,`price`) values ('前端课程',1);
insert  into `t_product`(`title`,`price`) values ('UI课程',1);

/*Table structure for table `t_refund_info` */

CREATE TABLE `t_refund_info` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '退款单id',
  `order_no` varchar(50) DEFAULT NULL COMMENT '商户订单编号',
  `refund_no` varchar(50) DEFAULT NULL COMMENT '商户退款单编号',
  `refund_id` varchar(50) DEFAULT NULL COMMENT '支付系统退款单号',
  `total_fee` int(11) DEFAULT NULL COMMENT '原订单金额(分)',
  `refund` int(11) DEFAULT NULL COMMENT '退款金额(分)',
  `reason` varchar(50) DEFAULT NULL COMMENT '退款原因',
  `refund_status` varchar(10) DEFAULT NULL COMMENT '退款状态',
  `content_return` text COMMENT '申请退款返回参数',
  `content_notify` text COMMENT '退款结果通知参数',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

NOTA: Quatro informações precisam ser adicionadas à folha de informações do produto

3. Arquivo de configuração de pagamento do sandbox Alipay

# 支付宝支付相关参数

# 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
alipay.app-id=2021000120603279

# 商户PID,卖家支付宝账号ID
alipay.seller-id=2088621959241092

# 支付宝网关
alipay.gateway-url=https://openapi.alipaydev.com/gateway.do

# 商户私钥,您的PKCS8格式RSA2私钥(应用私钥)
alipay.merchant-private-key=MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDBvqvExLVwdcXKE9IaYI1oI5a57SMAZrwlCXw40g3+04PmNiIxfkKJVDzhqEm2OmlO5Wl45q2jwvm5UdqgKtwHIyFWt2hPJ/QRSGFFO/4NiUWkMVs5Q74jvAePapy434lxjhhtuZdHUjalNqkb21SJh22XQJl8hFf5mACHDl4hEw/YUC9DM94jZ+FsBctYLN1usQlIAUW2OVWWBeAJIWWjtk2fNjOQaZHWH+Y5dOd4x7OiHXvxuNpVFgxPcM6IsgarkNWMzZb7p7j9ymcw48d0JjOIhnW8qkrU/bskp6VCXjw0x2azvd/HYcfpSBjeFHUKNX5CMUpks1/k9CJWpeJTAgMBAAECggEAeJC99Xnv6ubvSZxh/9YbyTV0Y4lFYceMx4OKkRVubiiUCRug1anbn/gS1t5R2Juq0tUCeKEcZy87Fe7xHQDu4WYkJgGGYNPdFzAyj9IQe73z34RzX0Rfu38UOVQ/6O/6aPbjDs0SbeikZtWIEPTBO8BSG3Cw0wLMeF713RW8z9kkQZOPaiixZVPLoFTIL4KuCZhYdJK2RRchYuZnEYHRRFAqFoKN1jII5pR8EyxmvocFx7UJ7idRGrSWc1UB5xEyn2emYyiTu3uaVaa49ecBNZqvRRdAoHcVQOGIYiUSNlrqDYVOLOicdSOlO6bS6jmRk41pdgdze089uYT/ilh0iQKBgQD+RIS3dGUvVk5AysqzoA0v7zYEixMeqALrAxYKAP02bIHGISw39+O4Q2GbzKUqDt6dmGPBlQ9jW/o9h0zqLf+7aFiEEIystbD6gx3TeWAVDyoF9zNfFOCapKaTbDZ2BGj3P/CpFLm7DNUyk9f03/BykqskXJ+4ZeeYUtqg+iofhwKBgQDDEJdrCrwBQEcjDIEcWZQaN09d98VgmWLv3L0EQ230YrcsLL1rJFJOzQr3zSf1ibY9S/zrrIFlb0Vq9qUBNbLN7GY8nXjrmoordgdYwHwTYoGa+R0Rv5yB24tKkra4ZQBCD1au1+0Klgc3mhJgKJ8HrRwH1UexDLukEABthfzh1QKBgCtSRUJ0hGDiVYbYhlzAYj7OhOeVQnawrX6ZEgI2VO4W4q19LWmDxLq6UEEZRvK5gdhcBHMREIQfQa2GBebIW4/0oVAu+ajbdAHaoRRM08ACy2gkzA3hIrt2XiM0Brto2PF3ZWuJanOiJhjt85d3KCJ9NseFOHlUc3cSdsmClfa1AoGAT1F60Mr/od6aTpUyFu4R/AsLmeE7gEk+4tw2e/pTRrGxXCQhLeUKFwLnd9YTbpN96DTy9n4h67YwWwtKE1DbkUKUXAeIeP1RO9T1rdAvY86FdxffCy2IHYHBhSRdamOflD0aeWRR/iD9dE2RNUqvR/bLVCAU09iioFblZaO7LbUCgYEA43FByiYEuVCiGEJD7eRSwRsN2lCJS3ZxoE60iKIeTxs5uWQIBE0bTkqvb35JUBdeuAlB0H0GPZaEO9yL3tPL0i+PbMmpBPaZ6tnOk6Pc9sREQw2PIlrpscWHU2gL2BodKCDVOe2rW3Der39MrFMhW6yoTjCFBRW6qGaM5llVQEc=

# 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥
alipay.alipay-public-key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApX9WFCjD08ErIlv1WvSF5hgM6kt9D+UJYYIDoMHpMw886DlFUiqPLYb+ZTy5eEE7N7TBS4Wl3NaUvsY2Z3dlwSk3HBpogsskgScm+qmdIEm/hEXL7xVB7WG7GD/M/ko8uihwQmH3WjOe9NU8HWUT4N4B6vwU6KrR6IHAmoPQ86zqWuQbUPrKZMZczhnF4uUcp+7DzpSWkz91U/TKdW18lFB7md8cwHEvKiQe23OEJMNS4utwDhaWIYhATxrxaEW5Yfj2VPt9NnaBbYYC2FUtHL4NLnJCF6uTgUuXzPauedeushS3WF0+mDrV8oRTKnBDtg6lF3JTrFoiocDJ076YlwIDAQAB

# 接口内容加密秘钥,对称秘钥
alipay.content-key=D8entyfafkkFwtMbUqj3Mw==

# 页面跳转同步通知页面路径
alipay.return-url=http://localhost:8080/#/success

# 服务器异步通知页面路径  需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
# 注意:每次重新启动ngrok,都需要根据实际情况修改这个配置
alipay.notify-url=https://2b4a-202-192-72-1.jp.ngrok.io/api/ali-pay/trade/notify

As informações do arquivo de configuração de pagamento sandbox acima vêm das informações relevantes geradas para indivíduos no site oficial da Alipay Open Platform. (Envolvendo principalmente APPID, PID, chave privada de aplicativo, chave pública Alipay)

Ao mesmo tempo, adicione o arquivo à pasta de recursos do projeto, selecione-o ao mesmo tempo project structuree selecione a configuração correspondente (como mostrado na figura abaixo). Você precisa selecionar o arquivo de propriedades e defini-lo para o projeto.

4. Arquivo de configuração geral do projeto

server:
  port: 8090


spring:
  application:
    name: payment-demo

  thymeleaf:
    cache: false
  jackson:
    date-format: yyyy:MM:dd HH:mm:ss  # 定义json的时间格式
    time-zone: GMT+8

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/payment_demo?CharacterEncoding=utf87serverTimeZone=GMT%2B8&useUnicode=true
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource


mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #sql日志
  mapper-locations: classpath:com/example/mapper/xml/*.xml #配置xml文件的地址

logging:
  level:
    root: info
  • Defina a porta correspondente para 8090
  • Defina o nome do aplicativo para:payment-demo
  • Feche o mecanismo de modelo thymeleaf e defina o formato de data de json
  • Configure a fonte de dados, defina a senha do nome de usuário e o endereço URL correspondente
  • Defina as informações de configuração do mybatis-plus, defina a saída de log padrão e defina o endereço do arquivo xml da camada de persistência
  • Por fim, defina o nível de saída do log como info, geralmente info

5. Configurar classes de configuração relacionadas

(1) Classe de configuração da interface Swagger

Esta classe é usada principalmente para configuração relacionada ao teste de interface

@Configuration
@EnableSwagger2
public class Swagger2Config {
    
    
    ApiInfoBuilder apiInfoBuilder=new ApiInfoBuilder();

    @Bean
    public Docket getDocket(){
    
    
        return new Docket( DocumentationType.SWAGGER_2)
                .apiInfo(apiInfoBuilder.title("支付宝支付案例").description("payment -demo").build());
    }

}
  • Use @EnableSwagger2anotações para ativar os serviços swagger2
  • Use @Beanmeios para injetar o objeto de valor de retorno deste método no contêiner IOC

(2) Configuração relacionada à fonte de dados

Configure a fonte de dados do Druid (o projeto do SpringBoot não é configurado com a fonte de dados do Druid por padrão)

@Configuration
public class DataSourceConfig {
    
    

    /**
     * @ConfigurationProperties(prefix = "spring.datasource"):作用就是将 全局配置文件中
     *        前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中
     * @return
     *
     */
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource getDataSource(){
    
    
        return new DruidDataSource();
    }
}

Use para @ConfigurationProperties(prefix = "spring.datasource")implementar o relacionamento de mapeamento entre classes de configuração e arquivos de configuração (injeção automática completa de valores de atributo)

(3) Configurar mybatisplus

@Configuration //定义为配置类
@MapperScan("com.example.mapper") //扫描mapper接口
@EnableTransactionManagement //启用事务管理(spring-tx)
public class MyBatisPlusConfig {
    
    
}

(4) Configure a classe de cliente de pagamento Alipay

@Configuration
@PropertySource("classpath:alipay-sandbox.properties")
public class AliPayClientConfig {
    
    

    /**
     * 利用Environment对象获取配置文件alipay-sandbox.properties文件中的所有内容
     */
    @Resource
    private Environment config;


    /**
     * 创建一个获取AlipayClient对象的方法,用于封装签名的自动实现
     * @return AlipayClient
     */
    @Bean
    public AlipayClient getAlipayClient() throws AlipayApiException {
    
    
        //创建alipay配置对象,并设置相应的参数
        AlipayConfig alipayConfig = new AlipayConfig();
    //设置网关地址
        alipayConfig.setServerUrl(config.getProperty("alipay.gateway-url"));
    //设置应用Id
        alipayConfig.setAppId(config.getProperty("alipay.app-id"));
    //设置应用私钥
        alipayConfig.setPrivateKey(config.getProperty("alipay.merchant-private-key"));
    //设置请求格式,固定值json
        alipayConfig.setFormat(AlipayConstants.FORMAT_JSON);
    //设置字符集
        alipayConfig.setCharset(AlipayConstants.CHARSET_UTF8);
    //设置支付宝公钥
        alipayConfig.setAlipayPublicKey(config.getProperty("alipay.alipay-public-key"));
    //设置签名类型
        alipayConfig.setSignType(AlipayConstants.SIGN_TYPE_RSA2);
    //构造client
        AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig);

        return alipayClient;
    }

}
  • Use @PropertySource("classpath:alipay-sandbox.properties")localizar para o local do arquivo de configuração

  • Use @Resourcea anotação para injetar o objeto Environment, que é usado para ler o conteúdo relevante das informações do arquivo de configuração no local especificado e obter getProperties(xxx)as informações de configuração do item especificado.

  • Use o método público para gerar um objeto bean personalizado e injetá-lo no contêiner IOC

  • O objeto retornado acima AlipayClientgerará automaticamente uma assinatura para a solicitação e concluirá a operação de verificação de assinatura na resposta

6. Configuração relacionada à classe de entidade

(1) Objeto de classe de entidadeBaseEntity

@Data
public class BaseEntity {
    
    

    /**
     * 主键
     */
    @TableId(value = "id",type = IdType.AUTO)
    private String id;

    /**
     * 创建时间
     */
    private Date createTime;

    /**
     * 更新时间
     */
    private Date updateTime;
}
  • Essa classe define alguns atributos comuns a todas as classes de entidades subsequentes. Como id e tempo de criação, tempo de atualização
  • Use @TableId(value = "id",type = IdType.AUTO)a anotação para definir o mapeamento da camada dao para este atributo, o valor corresponde ao id no banco de dados e defina a estratégia de incremento automático da chave primária.

(2) Objeto de informações do pedidoOrderInfo

@Data
@TableName("t_order_info")  //表示指定表名
public class OrderInfo extends BaseEntity{
    
    

    /**
     * 订单标题
     */
    private  String title;
    /**
     * 订单编号
     */
    private  String orderNo;
    /**
     * 用户ID
     */
    private  Long userId;
    /**
     * 产品ID
      */
    private  Long productId;
    /**
     * 订单金额
     */
    private  Integer totalFee;
    /**
     * 订单二维码链接
     */
    private  String codeUrl;
    /**
     * 订单状态
     */
    private String orderStatus;
}
  • Use @TableName("t_order_info")a tabela que representa o nome concreto que mapeia para o banco de dados
  • Herdar o objeto BaseEntity público anterior

(3) Objeto de informações de pagamentopaymentInfo

@Data
@TableName("t_payment_info")
public class PaymentInfo extends BaseEntity{
    
    
    /**
     * 订单编号
     */
    private String orderNo;
    /**
     * 交易系统支付编号
     */
    private  String transactionId;
    /**
     * 支付类型
     */
    private String paymentType;
    /**
     * 交易类型
     */
    private  String tradeType;
    /**
     * 交易状态
     */
    private String tradeState;
    /**
     * 支付金额
     */
    private Integer payerTotal;
    /**
     * 通知参数
     */
    private  String content;


}
  • Definição @TableName("t_payment_info")significa mapear o objeto para uma tabela específica no banco de dados
  • Configurações herdadas deBaseEntity

(3) Objeto do produtoProduct

@Data
@TableName("t_product")
public class Product extends BaseEntity{
    
    
    /**
     * 产品名称
     */
    private  String title;
    /**
     * 产品价格
     */
    private  Integer price;

}
  • Defina @TableName("t_product")a representação para mapear para uma tabela específica no banco de dados
  • herdarBaseEntity

(4) Objeto de informação de reembolsoRefundInfo

@Data
@TableName("t_refund_info")
public class RefundInfo extends BaseEntity {
    
    
    /**
     * 商品订单编号
     */
    private String orderNo;
    /**
     * 商品退款编号
     */
    private String refundNo;
    /**
     * 支付系统退款单号
     */
    private String refundId;
    /**
     * 原订单金额
     */
    private Integer totalFee;
    /**
     * 退款金额
     */
    private  Integer refund;
    /**
     * 退款原因
     */
    private  String reason;
    /**
     * 退款单状态
     */
    private String refundStatus;
    /**
     * 申请退款返回参数
     */
    private String contentReturn;
    /**
     * 退款结果通知参数
     */
    private  String contentNotify;

}

  • Configuração @TableName("t_refund_info")significa mapear para uma tabela específica
  • herdarBaseEntity

(5) Objetos de informação de interação front-end e back-endResults

/**
 * @author lambda
 * 该类用于前后端交互,为前端设置一个标准的响应结果
 * 即该类设置了需要交给前端的数据
 *
 */
@Data
@Accessors(chain = true)
public class Results {
    
    

    /**
     * 响应码
     */
    private Integer code;
    /**
     * 响应消息
     */
    private String message;
    /**
     * 封装其他信息
     */
    private Map<String, Object> data =new HashMap<>();


    /**
     * 用于返回正确的结果显示
     * @return Results 表示返回数据对象
     */
    public static Results returnOk(){
    
    
        Results results = new Results();
        results.setCode(0);
        results.setMessage("Succeed!");
        return results;

    }

    /**
     * 返回错误的显示信息
     * @return Results
     */
    public static  Results returnError(){
    
    
        Results results = new Results();
        results.setCode(-1);
        results.setMessage("Failed");
        return results;
    }


    /**
     * 用于返回k-v的信息
     * @param  key 给前端传递的键
     * @param  value 给前端传递的值
     * @return Results
     */
    public Results returnData(String key,Object value){
    
    
        this.data.put(key, value);
        return this;
    }
}

7. Configuração relacionada à camada de persistência

Defina principalmente a camada persistente para informações de pedidos, informações de pagamento, informações de produtos e informações de reembolso

  • OrderInfoMapper é usado para processar informações de pedidos do banco de dados
@Mapper
public interface OrderInfoMapper  extends BaseMapper<OrderInfo> {
    
    
}

Não há necessidade de escrever nenhum método CRUD, porque ele é herdado BaseMappere o tipo genérico é especificado como OrderInfo. @MapperA anotação indica que o objeto de implementação desta interface é implementado pela camada inferior do MyBatisPlus.

  • PaymentInfoMapper é usado para processar informações de pagamento
@Mapper
public interface PaymentInfoMapper extends BaseMapper<PaymentInfo> {
    
    
}

Não há necessidade de escrever nenhum método de adição, exclusão, modificação e consulta, porque é herdado BaseMappere o tipo genérico é especificado como PaymentInfo. @MapperA anotação indica que o objeto de implementação desta interface é implementado pela camada inferior do MyBatisPlus.

  • ProductMapper é usado para processar informações do produto
@Mapper
public interface ProductMapper extends BaseMapper<Product> {
    
    

}

Não há necessidade de escrever nenhum método de adição, exclusão, modificação e consulta, porque é herdado BaseMappere o tipo genérico é especificado como Product. @MapperA anotação indica que o objeto de implementação desta interface é implementado pela camada inferior do MyBatisPlus.

  • RefundInfoMapper é usado para processar informações de reembolso
@Mapper
public interface RefundInfoMapper extends BaseMapper<RefundInfo> {
    
    
}

Não há necessidade de escrever nenhum método CRUD, porque ele é herdado BaseMappere o tipo genérico é especificado como RefundInfo. @MapperA anotação indica que o objeto de implementação desta interface é implementado pela camada inferior do MyBatisPlus.

8. Defina a configuração de enumeração relevante

A configuração de enumeração é principalmente para configurações de status do pedido e tipo de pagamento

  • Status do pedido
@AllArgsConstructor
@Getter
public enum OrderStatus {
    
    
    /**
     * 未支付
     */
    NOTPAY("未支付"),


    /**
     * 支付成功
     */
    SUCCESS("支付成功"),

    /**
     * 已关闭
     */
    CLOSED("超时已关闭"),

    /**
     * 已取消
     */
    CANCEL("用户已取消"),

    /**
     * 退款中
     */
    REFUND_PROCESSING("退款中"),

    /**
     * 已退款
     */
    REFUND_SUCCESS("已退款"),


    /**
     * 退款异常
     */
    REFUND_ABNORMAL("退款异常");

    /**
     * 类型
     */
    private final String type;


}
  • Tipos de pagamento
@AllArgsConstructor
@Getter
public enum PayType {
    
    
    /**
     * 微信
     */
    WXPAY("微信"),


    /**
     * 支付宝
     */
    ALIPAY("支付宝");

    /**
     * 类型
     */
    private final String type;
}
  • Status da transação Alipay
public enum AliPayTradeState {
    
    
    /**
     * 支付成功
     */
    SUCCESS("TRADE_SUCCESS"),
    /**
     * 未支付
     */
    NOTPAY("WAIT_BUYER_PAY"),
    /**
     * 订单关闭
     */
    CLOSED("TRADE_SUCCESS"),
    /**
     * 退款成功
     */
    REFUND_SUCCESS("REFUND_SUCCESS"),

    /**
     * 退款失败
     */
    REFUND_ERROR("REFUND_ERROR");


    private final String type;

   private AliPayTradeState(String type) {
    
    
        this.type = type;
    }

    public String getType() {
    
    
        return type;
    }
}

9. Preparação de ferramentas

  • Classe de ferramenta para obter informações do pedido
public class OrderNoUtils {
    
    

    /**
     * 获取订单编号
     * @return
     */
    public static String getOrderNo() {
    
    
        return "ORDER_" + getNo();
    }

    /**
     * 获取退款单编号
     * @return
     */
    public static String getRefundNo() {
    
    
        return "REFUND_" + getNo();
    }

    /**
     * 获取编号
     * @return
     */
    public static String getNo() {
    
    
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        String newDate = sdf.format(new Date());
        String result = "";
        Random random = new Random();
        for (int i = 0; i < 3; i++) {
    
    
            result += random.nextInt(10);
        }
        return newDate + result;
    }

}

  • Classe de ferramenta sobre http (existe apenas um método estático para os usuários lerem as informações de dados solicitadas)
public class HttpUtils {
    
    

    /**
     * 将通知参数转化为字符串
     * @param request
     * @return
     */
    public static String readData(HttpServletRequest request) {
    
    
        BufferedReader br = null;
        try {
    
    
            StringBuilder result = new StringBuilder();
            br = request.getReader();
            for (String line; (line = br.readLine()) != null; ) {
    
    
                if (result.length() > 0) {
    
    
                    result.append("\n");
                }
                result.append(line);
            }
            return result.toString();
        } catch (IOException e) {
    
    
            throw new RuntimeException(e);
        } finally {
    
    
            if (br != null) {
    
    
                try {
    
    
                    br.close();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
    }
}

10. Preparação do projeto de front-end

O conteúdo do projeto front-end é aproximadamente o seguinte:

3. Desenvolvimento da função de pagamento

O diagrama de sequência de chamada da interface de pagamento alipay.trade.page.pay (recebimento unificado, interface de pedido e página de pagamento) para pagamento no site do computador é o seguinte:

1. Interface de página unificada para receber pedidos, fazer pedidos e pagar

(1) Visualização da API

O uso principal alipay.trade.page.pay, ou seja, 1.1 no diagrama de sequência, é iniciar uma solicitação de pagamento

  • A seguir estão os parâmetros de solicitação das configurações oficiais do Alipay, alguns dos quais foram encapsulados no objeto AlipayClient mencionado acima, e alguns deles precisam ser definidos posteriormente.

  • A seguir está o conteúdo dos parâmetros de solicitação pública biz_content, que são parâmetros de solicitação específicos e precisam ser configurados manualmente. (Todos são itens obrigatórios)

  • A seguir estão os parâmetros de resposta pública fornecidos pela plataforma aberta Alipay, que precisam ser definidos posteriormente

  • A seguir estão os parâmetros de resposta fornecidos pela plataforma aberta Alipay (alguns conteúdos de resposta relacionados a negócios específicos), que também precisam de configurações subsequentes

(2) Criar método de camada de negócios de pagamento

a. A primeira é escrever a classe e o método da camada de serviço para informações do pedido (principalmente criar um pedido com base no número do produto e consultar o pedido no banco de dados por meio do número do produto)

public interface OrderInfoService  extends IService<OrderInfo> {
    
    
    
    
      /**
     * Create order by product id order info.
     * 根据产品的id生成对应的订单信息
     *
     * @param productId the product id
     * @return the order info
     */
    OrderInfo createOrderByProductId(Long productId);

}

//对应的实现类为:OrderInfoServiceImpl
@Service
@Slf4j
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService {
    
    
    
    @Resource
    private ProductMapper productMapper;

    @Resource
    private OrderInfoMapper orderInfoMapper;

    
     /**
     * 根据产品id创建订单信息
     * @param productId the product id
     * @return 订单信息
     */
    @Override
    public OrderInfo createOrderByProductId(Long productId) {
    
    

        //查找已存在,但是并未支付的订单信息
        OrderInfo orderInfoNoPay = getNoPayOrderByProductId(productId);
        if (orderInfoNoPay!=null){
    
    
            return orderInfoNoPay;
        }
        //获取商品的对象
        Product product = productMapper.selectById(productId);

        //生成订单
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setTitle(product.getTitle());
        //订单号
        orderInfo.setOrderNo(OrderNoUtils.getOrderNo());
        orderInfo.setTotalFee(product.getPrice());
        orderInfo.setProductId(productId);
        orderInfo.setOrderStatus(OrderStatus.NOTPAY.getType());

        //将订单信息存入数据库
        orderInfoMapper.insert(orderInfo);

        return orderInfo;
    }

    
     /**
     * 该方法用于获取用户未支付的订单(由于只在该类中使用,所以定义为私有方法)
     * @param productId
     * @return
     */
    private  OrderInfo getNoPayOrderByProductId(Long productId){
    
    
        //使用MyBatis-plus的查询器
        QueryWrapper<OrderInfo> orderInfoQueryWrapper = new QueryWrapper<>();
        //设置判断条件,id和类型信息
         orderInfoQueryWrapper.eq("product_id", productId);
         orderInfoQueryWrapper.eq("order_status",OrderStatus.NOTPAY.getType());

         //使用自带的selectOne方法判断是否同时满足条件
        OrderInfo orderInfo = orderInfoMapper.selectOne(orderInfoQueryWrapper);
        return orderInfo;

    }
    
}
  • OrderInfoService não precisa escrever nenhum método, todas as operações como adicionar, excluir, verificar e paginar são concluídas por IService, e o tipo genérico da interface IService herdada é a classe de entidade correspondente. Só precisamos definir os métodos de negócios específicos correspondentes.
  • getNoPayOrderByProductIdUsado para obter os pedidos não pagos do usuário com base nas informações do produto. QueryWrapperConsultador usado
  • Ao mesmo tempo, você precisa injetar o objeto de operação da camada de persistência productMappereorderInfoMapper

b. Veja os documentos de desenvolvimento de interface da página de pedido e pagamento de recebimento unificado

Precisamos escrever o código no formato correspondente para iniciar uma solicitação de pagamento na plataforma Alipay.

c. Escreva o método de negócios de solicitação correspondente

//对应的业务层接口
public interface AliPayService {
    
    
    /**
     * Trade create string.
     * 创建支付宝支付订单
     *
     * @param productId the product id
     * @return the string
     */
    String tradeCreate(Long productId);
}
//对应业务层实现方法
@Service
@Slf4j
public class AliPayServiceImpl implements AliPayService {
    
    

    @Resource
    private OrderInfoService orderInfoService;

    @Resource
    private  AlipayClient alipayClient;
    
     @Resource
    private  Environment config;//配置环境参数

    /**
     * 根据订单号创建订单并发起支付请求获取平台响应返回到前端
     * @param productId the product id
     * @return 返回支付请求调用的响应主体信息,返回到controller层
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public String tradeCreate(Long productId)  {
    
    
        try {
    
    


            log.info("生成订单....");
            //调用orderInfoService对象在数据库中创建订单
            OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId);

            //调用支付宝接口
            //创建支付宝请求对象
            AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
             //设置请求处理完成后的跳转的地址
            request.setReturnUrl(config.getProperty("alipay.return-url"));
            //创建具体请求参数对象,用于组装请求信息
            JSONObject bizContent = new JSONObject();
            //设置商户订单号
            bizContent.put("out_trade_no", orderInfo.getOrderNo());
            //设置订单总金额,由于订单金额单位为分,而参数中需要的是元,因此需要bigDecimal进行转换
            BigDecimal total = new BigDecimal(orderInfo.getTotalFee().toString()).divide(new BigDecimal("100"));
            bizContent.put("total_amount", total);
            //设置订单标题
            bizContent.put("subject", orderInfo.getTitle());
            //设置支付产品码,比较固定(电脑支付场景下只支持一种类型)
            bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");

            //设置完成后,将bizContent具体请求对象转换成json并放置在请求中
            request.setBizContent(bizContent.toString());

            //利用alipay客户端执行请求
            AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
            //判断请求是否成功
            if (response.isSuccess()){
    
    
                //打印响应信息主体
                log.info("调用成功====》{}",response.getBody());
            }else {
    
    
                log.info("调用失败====》{},返回码"+response.getCode()+",返回描述为:"+response.getMsg());
                throw new RuntimeException("创建支付交易失败.....");
            }
        return response.getBody();
        }catch (AlipayApiException e){
    
    
            throw new RuntimeException("创建支付交易失败.....");
        }
    }
}

  • Primeiro, injete OrderInfoServicee alipayClientobjete para o processamento de negócios correspondente.
  • Em segundo lugar, chame OrderInfoServiceo método de criação de um pedido por meio do número do pedido para gerar um novo pedido (consulte a classe OrderInforServiceImpl para o processo de criação específico)
  • Em seguida, crie um objeto de solicitação que inicia uma solicitação de pagamento para Alipay AlipayTradePagePayRequeste use JsonObjecta classe para criar um objeto de parâmetro de solicitação para definir os parâmetros da solicitação. Os valores de parâmetro específicos precisam ser obtidos das informações do pedido recém-criadas. Ao mesmo tempo, use config para definir o endereço de retorno da conclusão do pagamento
  • Novamente, defina o objeto de parâmetro de solicitação montado no AlipayTradePagePayRequestobjeto de solicitação.
  • Por fim, alipayCliento objeto é usado para executar a solicitação e o método de execução é pageExecute. Obtenha um objeto de resposta de solicitação AlipayTradePagePayResponsee, em seguida, processe o objeto de resposta de solicitação. Se a resposta retornada for bem-sucedida, retorne as informações do corpo da resposta de solicitação para a camada do controlador, caso contrário, lance um prompt de exceção.

d. Escreva o método de salto da camada de controle

@CrossOrigin
@RestController
@RequestMapping("/api/ali-pay")
@Api(tags = "网站支付宝支付")
@Slf4j
public class AliPayController {
    
    

    @Resource
    private AliPayService aliPayService;

    @ApiOperation("统一收单下单并支付页面接口")
    @PostMapping("/trade/page/pay/{productId}")
    public Results tradePagePay(@PathVariable Long productId){
    
    
        log.info("统一收单下单并支付页面接口");
        //发起交易请求后,会返回一个form表单格式的字符串(要有前端渲染)
        String formStr=aliPayService.tradeCreate(productId);
        //最后需要将支付宝的响应信息传递给前端,到前端之后会自动提交表单到action指定的支付宝开放平台中
        //从而为用户展示支付页面。
        return Results.returnOk().returnData("formStr",formStr);
    }
}
  • @CrossOriginDefinir solicitação de acesso entre domínios
  • Injetar AliPayServicedependências de objeto
  • Chame AliPayServiceo método de transação de criação do objeto tradeCreate(especificamente implementado na classe AliPayServiceImpl)
  • Por fim, retorne uma string json na forma de um formulário, retorne as informações finais para o front-end para renderizá-las em um formulário de formulário e execute o script de envio automático. Iniciar pagamento com sucesso

(3) Uso de teste

a. Primeiro execute o teste no swagger

Visite; página http://localhost:8090/swagger-ui.html

Execute a solicitação da camada do controlador e obtenha as seguintes informações (formulário de retorno)

b. Iniciar o projeto front-end

Visite http://localhost:8080, você pode ver a página de seleção de produtos em primeiro plano

Você pode escolher Alipay para pagar

c. Selecione um curso e clique em Confirmar pagamento para ir para a página de pagamento correspondente

Você pode usar a conta sandbox para fazer login e pagar

Digite a senha de pagamento para pagar com sucesso

Você também pode optar por concluir o pagamento digitalizando o código

Use a versão sandbox do Alipay para concluir o pagamento no celular.

Após o pagamento ser bem-sucedido, a página de salto é a seguinte

Mas ainda há um problema, o comprador pagou, mas as informações do pedido do vendedor ainda não estão atualizadas (mesmo que o pagamento seja bem-sucedido, mas o banco de dados em segundo plano mostra que o pagamento não foi feito), conforme mostrado na figura a seguir:

O motivo é: após o pagamento do usuário ser bem-sucedido, o Alipay não enviou uma notificação assíncrona ao comerciante e o comerciante não recebeu a notificação de pagamento do Alipay, portanto, naturalmente, as informações do pedido não serão atualizadas.

(4) Notificação assíncrona de pagamento bem-sucedido

A notificação assíncrona de pagamento bem-sucedido é principalmente para enviar uma notificação de resultado do Alipay ao comerciante. Como o ambiente de rede do comerciante está em uma rede local, a plataforma Alipay deve executar a penetração na intranet para notificar o comerciante. Use ngrokferramentas para penetração na intranet.

a. Instale e configure o ngrok
  • Primeiro baixe e instale o ngrok

Acesse o site oficial do ngrok e faça login para fazer o download, endereço do site oficial: https://ngrok.com/download

Este teste é baseado no teste no ambiente linux, então baixe o pacote compactado no sistema linux.

Abra o terminal e extraia-o para /usr/local/bino diretório

sudo tar xvzf ~/Downloads/ngrok-v3-stable-linux-amd64.tgz -C  /usr/local/bin

Em seguida, teste se o ngrok foi instalado com sucesso

ngrok -v
ngrok version 3.0.3
# 此时表明ngrok安装成功
  • Em segundo lugar, conecte-se à conta especificada (conta recém-criada)
 ngrok config add-authtoken 29ds9En84SWW7uOuqwIEMvjWnAy_71i4aLWQpTjoAXnsuVuEX

Esta operação irá .config/ngrokgerar o arquivo de configuração ngrok.yml no diretório

  • Abra uma porta desta máquina que precisa ser penetrada (já que a porta neste projeto é baseada em 8090, então abra a porta 8090)
ngrok http 8090
ngrok                                                                                                            (Ctrl+C to quit)
                                                                                                                                 
Session Status                online                                                                                             
Account                       binbin (Plan: Free)                                                                                
Version                       3.0.3                                                                                              
Region                        Japan (jp)                                                                                         
Latency                       calculating...                                                                                     
Web Interface                 http://127.0.0.1:4040                                                                              
Forwarding                    https://2b4a-202-192-72-1.jp.ngrok.io -> http://localhost:8090                                     
                                                                                                                                 
Connections                   ttl     opn     rt1     rt5     p50     p90                                                        
                              0       0       0.00    0.00    0.00    0.00  

Deve-se notar que toda vez que o ngrok é iniciado, o endereço de penetração da intranet correspondente será alterado . Alterações correspondentes precisam ser feitas no arquivo de configuração

b. Análise de API de parâmetros de notificação assíncrona

Para transações de pagamento no site do PC, após o pagamento do usuário ser concluído, o Alipay notificará o sistema do comerciante sobre o resultado do pagamento como um parâmetro na forma de uma solicitação POST de acordo com o notify_url passado pelo comerciante na API.

Ele também inclui duas partes, uma parte são parâmetros públicos e a outra parte são parâmetros de negócios.

seção de parâmetro público

O sinal nele significa uma assinatura, e o comerciante de acompanhamento precisa verificar a assinatura. Se for confirmado que é uma notificação do Alipay, ela será operada e, caso contrário, será ignorada.

Parte do parâmetro de negócios

Além disso, atenção especial deve ser dada a:

Após a execução do programa do lojista, deve-se imprimir "sucesso" (sem as aspas). Se o caractere que o comerciante enviar de volta ao Alipay não for os 7 caracteres de sucesso, o servidor Alipay continuará reenviando a notificação até que exceda 24 horas e 22 minutos. Em circunstâncias normais, 8 notificações são concluídas em 25 horas (a frequência de intervalo das notificações é geralmente: 4m, 10m, 10m, 1h, 2h, 6h, 15h).

Se o comerciante não conseguir processar a solicitação de notificação assíncrona, ele retornará "falha" ao Alipay.

c. Verificação de assinatura de resultado de retorno assíncrono
  • A primeira é a verificação inicial de notificações assíncronas (abaixo está o fluxo principal de processamento da lógica de negócios)

Aqui, o lado do comerciante verifica a assinatura do resultado da notificação assíncrona enviada pelo Alipay do lado remoto (a chave pública Alipay é usada para verificação, porque é uma criptografia assimétrica) e se é confirmado que foi enviado pela plataforma Alipay , isso prova que operações relevantes podem ser executadas.

Map<String, String> paramsMap = ... //将异步通知中收到的所有参数都存放到map中
boolean signVerified = AlipaySignature.rsaCheckV1(paramsMap, ALIPAY_PUBLIC_KEY, CHARSET, SIGN_TYPE) //调用SDK验证签名
if(signVerified){
    
    
    // TODO 验签成功后,按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验,校验成功后在response中返回success并继续商户自身业务处理,校验失败返回failure
}else{
    
    
    // TODO 验签失败则记录异常日志,并在response中返回failure.
}
  • Em segundo lugar, execute uma verificação secundária na notificação assíncrona , verificando principalmente o número do pedido, o valor do pedido, o operador correspondente e o ID do comerciante (a principal lógica de processamento comercial está abaixo)
 String result = "failure";

            //异步通知验签(使用我们引入的支付宝SDK验证签名)
            //一个是异步通知的结果参数,一个是支付宝的公钥,一个是字符集,一个是加密方式,得到一个布尔值结果
            boolean signVerified = AlipaySignature.rsaCheckV1(params, config.getProperty("alipay.alipay-public-key"),
                    AlipayConstants.CHARSET_UTF8, AlipayConstants.SIGN_TYPE_RSA2);

            //对签名结果进行判断
            if (!signVerified) {
    
    
                //TODO:验签失败则记录异常日志,并在response中返回failure
                log.error("支付成功,异步通知验签失败......");
                return result;
            }

            //TODO:验证成功,按照支付结果异步通知中的描述,
            // 对支付结果中的业务内容进行二次校验,
            // 校验成功后在response中返回success并继续商户自身业务处理,校验失败返回failure

            log.info("支付成功,异步通知验签成功.......");
            //1.商户需要验证该通知数据中的 out_trade_no 是否为商户系统中创建的订单号;
            //获取对应的订单号
            String outTradeNo = params.get("out_trade_no");
            //利用获取的订单号查询对应的订单信息(返回一个订单对象)
            OrderInfo order = orderInfoService.getOrderByOrderNo(outTradeNo);
            //对订单对象进行判断
            if (order==null){
    
    
                log.error("订单不存在......");
                return result;
            }

            //2.判断 total_amount 是否确实为该订单的实际金额(即商户订单创建时的金额)
            //从参数中获取金额(单位为元),但是数据库中的单位为分,因此需要进行转换
            String totalAmount = params.get("total_amount");
            int totalAmountInt = new BigDecimal(totalAmount).multiply(new BigDecimal("100")).intValue();
            //获取订单中的金额
            int totalFeeInt = order.getTotalFee();
            if (totalFeeInt!=totalAmountInt){
    
    
                //如果不等,则说明金额不对
                log.error("金额校验失败");
                return result;
            }

            //3.校验通知中的 seller_id(对应商户的PID)(或者 seller_email) 是否为 out_trade_no
            // 这笔单据的对应的操作方(有的时候,一个商户可能有多个 seller_id/seller_email)
            String sellerId = params.get("seller_id");
            //获取实际的商户PID
            String pid = config.getProperty("alipay.seller-id");
            if (!sellerId.equals(pid)){
    
    
                //用商户的PID与参数中的sellerID进行比较
                log.error("商家PID校验失败....");
                return result;
            }

            //4.验证 app_id 是否为该商户本身
            String appId = params.get("app_id");
            String appIdProperty = config.getProperty("alipay.app-id");
            if (!appId.equals(appIdProperty)){
    
    
                log.error("appId校验失败");
                return result;
            }

            //在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS
            // 或 TRADE_FINISHED 时,支付宝才会认定为买家付款成功。
            //获取交易状态
            String tradeStatus = params.get("trade_status");
            if (!"TRADE_SUCCESS".equals(tradeStatus)){
    
    
                //如果不满足交易状态成功参数,则直接返回failure
                log.error("支付未成功....");
                return result;
            }
            //以上4步校验成功后设置为success,之后返回结果,商户可以自身进行后续的处理
            //商户处理自身业务
            
            result = "success";
		return result;

(5) Notificar de forma assíncrona o sistema do comerciante sobre a verificação de assinatura bem-sucedida

Depois que o resultado da notificação assíncrona for confirmado, o sistema do comerciante precisa processar os pedidos registrados existentes. O processamento do sistema do comerciante inclui principalmente: processamento de negócios, modificação do status do pedido, registro de logs de pagamento, etc.

a. Como criar e atualizar um pedido

Como o status do pedido correspondente precisa ser atualizado quando o comerciante atualiza suas informações do sistema, é necessário definir o método correspondente na classe de processamento de informações do pedido correspondente para processar a atualização do status do pedido.

// 对应OrderInfoService接口添加新方法
public interface OrderInfoService  extends IService<OrderInfo> {
    
    

    /**
     * Create order by product id order info.
     * 根据产品的id生成对应的订单信息
     *
     * @param productId the product id
     * @return the order info
     */
    OrderInfo createOrderByProductId(Long productId);
    /**
     * Update status by order no.
     * 根据订单号更新数据库中的订单状态
     *
     * @param orderNo     the order no
     * @param orderStatus the order status
     */
    void updateStatusByOrderNo(String orderNo, OrderStatus orderStatus);
}

//对应OrderInfoServiceImpl的实现方法

@Service
@Slf4j
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService {
    
    
 xxxxxxxx
 /**
     * 此方法用于根据订单编号来更新数据库中的订单状态
     * @param orderNo 订单编号
     * @param orderStatus 成功响应码
     */
    @Override
    public void updateStatusByOrderNo(String orderNo, OrderStatus orderStatus) {
    
    
        log.info("更新数据库中的订单状态=======>"+orderStatus.getType());
        //创建一个查询条件,主要针对OrderInfo订单信息
        QueryWrapper<OrderInfo> orderInfoQueryWrapper = new QueryWrapper<>();
        //编写查询条件
        orderInfoQueryWrapper.eq("order_no",orderNo);
        //创建一个订单信息对象
        OrderInfo orderInfo = new OrderInfo();
        //设置要更新的订单状态
        orderInfo.setOrderStatus(orderStatus.getType());
        //执行更新操作
        orderInfoMapper.update(orderInfo,orderInfoQueryWrapper);


    }
}
  • Aqui envolve principalmente duas informações de número do pedido e status do pedido, que são passados ​​como parâmetros.
  • As condições de consulta ainda são aplicadas QueryWrapper<OrderInfo>para realizar uma consulta equivalente nas informações do pedido correspondente e atualizar o status do pedido ao mesmo tempo.
b. Criar registro de pagamento para pagamento
//创建支付日志接口并设置相应的方法
public interface PaymentInfoService {
    
    

    /**
     * Create payment info for ali pay.
     *为支付创建日志记录
     * @param params the params
     */
    void createPaymentInfoForAliPay(Map<String, String> params);

}
//为支付日志接口创建实现类,并实现创建支付日志方法
@Service
@Slf4j
public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, PaymentInfo> implements PaymentInfoService {
    
    
     @Resource
    private PaymentInfoMapper paymentInfoMapper;
    
      /**
     * 记录支付宝的支付日志
     * @param params the params 支付通知参数
     */
    @Override
    public void createPaymentInfoForAliPay(Map<String, String> params) {
    
    
    
       log.info("记录支付宝支付日志.....");
        //创建支付信息对象
        PaymentInfo paymentInfo = new PaymentInfo();
        paymentInfo.setOrderNo(params.get("out_trade_no"));
        paymentInfo.setPaymentType(PayType.ALIPAY.getType());
        //设置业务编号(支付宝对应的是trade_no)
        paymentInfo.setTransactionId(params.get("trade_no"));
        //设置支付的场景
        paymentInfo.setTradeType("电脑网站支付");
        //设置交易状态
        paymentInfo.setTradeState(params.get("trade_status"));
        //设置交易金额,此处依旧需要转换(支付宝端对应的是元,数据库中对应分)
        int totalAmount=new BigDecimal(params.get("total_amount")).multiply(new BigDecimal("100")).intValue();
        paymentInfo.setPayerTotal(totalAmount);

        //之后设置备注信息,需要将平台传入的map集合信息转成字符串类型存入数据库
        Gson gson = new Gson();
        String content = gson.toJson(params, HashMap.class);
        paymentInfo.setContent(content);
        //将信息插入数据库中
        paymentInfoMapper.insert(paymentInfo);


    }
}
  • createPaymentInfoForAliPayO método precisa passar os parâmetros corretos passados ​​para nós pela plataforma, que é um tipo de coleção.
  • Objetos @Resourceinjetados em classes usando anotaçõesPaymentInfoMapper paymentInfoMapper
  • Crie um novo objeto de informações de pagamento no método de implementação, obtenha as informações correspondentes da coleção de parâmetros e defina-as no objeto e, finalmente, use o paymentInfoMappermétodo insertpara inseri-las no banco de dados (o método de inserção foi implementado pelo MyBatisPlus)
c. O pagamento Alipay cria um método para processar pedidos na camada de negócios

Este método é usado principalmente para processamento de pedidos (processamento de pedidos com verificação de assinatura bem-sucedida após receber notificação assíncrona Alipay)

// 异步通知处理接口方法processOrder
public interface AliPayService {
    
    
 xxx

    /**
     * Process order.
     *订单处理方法
     * @param params the params
     */
    void processOrder(Map<String, String> params);
}

//对应接口的实现方法
@Service
@Slf4j
public class AliPayServiceImpl implements AliPayService {
    
    

    @Resource
    private OrderInfoService orderInfoService;

    @Resource
    private  AlipayClient alipayClient;

    @Resource
    private  Environment config;

    @Resource
    private PaymentInfoService paymentInfoService;
    xxxxx
/**
     * 商户系统订单处理
     * @param params 支付宝平台异步通知传递的参数
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void processOrder(Map<String, String> params) {
    
    
        log.info("处理订单.......");
        //获取传递信息中的订单号
        String orderNo = params.get("out_trade_no");
        //更新订单状态
        orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
        //记录支付日志
        paymentInfoService.createPaymentInfoForAliPay(params);
    }
  • Crie um método correspondente para processamento de pedidos, que precisa passar em um parâmetro do tipo coleção.
  • Injetar no método de processamento correspondente PaymentInfoService paymentInfoServicepara processamento de informações de pagamento.
  • Em seguida, chame o método de atualização do status do pedido e, em seguida, chame a interface de informações de pagamento para criar um log de informações de pagamento (passe nos parâmetros de cobrança).
  • @Transactional(rollbackFor = Exception.class)Indica que uma operação de reversão é executada quando uma exceção correspondente é encontrada.
d. A camada de controle processa os resultados da notificação assíncrona
@CrossOrigin
@RestController
@RequestMapping("/api/ali-pay")
@Api(tags = "网站支付宝支付")
@Slf4j
public class AliPayController {
    
    

    @Resource
    private AliPayService aliPayService;

    @Resource
    private Environment config;

    @Resource
    private OrderInfoService orderInfoService;
    
    /**
     * 支付宝异步通知处理结果
     * @param params 支付宝异步通知发过来的参数
     * @return 最终返回商户程序给予支付宝平台的信息
     */
    @ApiOperation("支付通知")
    @PostMapping("/trade/notify")
    public String tradeNotify(@RequestParam Map<String,String> params)  {
    
    
        try {
    
    
            //@RequestParam表示将参数从请求中取出放入map集合中
            log.info("支付通知正在执行");
            log.info("通知参数----》{}", params);
            //result表示商家需要给支付宝反馈的异步通知结果(success表示成功,需要后续的业务来规定是否为
            // success)
            String result = "failure";

            //异步通知验签(使用我们引入的支付宝SDK验证签名)
            //一个是异步通知的结果参数,一个是支付宝的公钥,一个是字符集,一个是加密方式,得到一个布尔值结果
            boolean signVerified = AlipaySignature.rsaCheckV1(params, config.getProperty("alipay.alipay-public-key"),
                    AlipayConstants.CHARSET_UTF8, AlipayConstants.SIGN_TYPE_RSA2);

            //对签名结果进行判断
            if (!signVerified) {
    
    
                //TODO:验签失败则记录异常日志,并在response中返回failure
                log.error("支付成功,异步通知验签失败......");
                return result;
            }

            //TODO:验证成功,按照支付结果异步通知中的描述,
            // 对支付结果中的业务内容进行二次校验,
            // 校验成功后在response中返回success并继续商户自身业务处理,校验失败返回failure


            //1.商户需要验证该通知数据中的 out_trade_no 是否为商户系统中创建的订单号;
            //获取对应的订单号
            String outTradeNo = params.get("out_trade_no");
            //利用获取的订单号查询对应的订单信息(返回一个订单对象)
            OrderInfo order = orderInfoService.getOrderByOrderNo(outTradeNo);
            //对订单对象进行判断
            if (order==null){
    
    
                log.error("订单不存在......");
                return result;
            }

            //2.判断 total_amount 是否确实为该订单的实际金额(即商户订单创建时的金额)
            //从参数中获取金额(单位为元),但是数据库中的单位为分,因此需要进行转换
            String totalAmount = params.get("total_amount");
            int totalAmountInt = new BigDecimal(totalAmount).multiply(new BigDecimal("100")).intValue();
            //获取订单中的金额
            int totalFeeInt = order.getTotalFee();
            if (totalFeeInt!=totalAmountInt){
    
    
                //如果不等,则说明金额不对
                log.error("金额校验失败");
                return result;
            }

            //3.校验通知中的 seller_id(对应商户的PID)(或者 seller_email) 是否为 out_trade_no
            // 这笔单据的对应的操作方(有的时候,一个商户可能有多个 seller_id/seller_email)
            String sellerId = params.get("seller_id");
            //获取实际的商户PID
            String pid = config.getProperty("alipay.seller-id");
            if (!sellerId.equals(pid)){
    
    
                //用商户的PID与参数中的sellerID进行比较
                log.error("商家PID校验失败....");
                return result;
            }

            //4.验证 app_id 是否为该商户本身
            String appId = params.get("app_id");
            String appIdProperty = config.getProperty("alipay.app-id");
            if (!appId.equals(appIdProperty)){
    
    
                log.error("appId校验失败");
                return result;
            }

            //在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS
            // 或 TRADE_FINISHED 时,支付宝才会认定为买家付款成功。
            //获取交易状态
            String tradeStatus = params.get("trade_status");
            if (!"TRADE_SUCCESS".equals(tradeStatus)){
    
    
                //如果不满足交易状态成功参数,则直接返回failure
                log.error("支付未成功....");
                return result;
            }
            //以上4步校验成功后设置为success,之后返回结果,商户可以自身进行后续的处理
            //商户处理自身业务
            aliPayService.processOrder(params);

            result = "success";

            log.info("支付成功,异步通知验签成功.......");
            return result;
        }catch (AlipayApiException e) {
    
    
            e.printStackTrace();
            throw new RuntimeException("异步通知验证签名出现异常....");
        }



    }
}
  • A etapa de assinatura de verificação correspondente refere-se ao processo de verificação de assinatura de retorno de resultado assíncrono.
  • Se a verificação da assinatura for bem-sucedida, o sistema do comerciante precisa fornecer os Successcaracteres correspondentes à plataforma Alipay (se falhar, falha no retorno) e, em seguida, o próprio sistema do comerciante precisa processar as informações do pedido de acordo com os parâmetros obtidos. ( processOrdermétodo de chamada)
e. Atualizar teste de pedido após receber notificação assíncrona
  • Primeiro abra o projeto front-end: visite: http://localhost:8080
  • Em segundo lugar, use a ferramenta ngrok para penetrar na intranet (se você não executar esta etapa, não poderá obter a notificação assíncrona do Alipay), e o mapeamento de endereço correspondente a cada reinicialização é diferente, portanto, você precisa configurar o arquivo de acordo com as informações fornecidas por ngrok.
ngrok http 8090
# 由于需要通知到后端工程,需要开放对应的后端工程的端口

# 返回的信息
ngrok                                                                                                            (Ctrl+C to quit)
                                                                                                                                 
Session Status                online                                                                                             
Account                       binbin (Plan: Free)                                                                                
Version                       3.0.3                                                                                              
Region                        Japan (jp)                                                                                         
Latency                       69.9073ms                                                                                          
Web Interface                 http://127.0.0.1:4040                                                                              
Forwarding                    https://8141-183-238-79-57.jp.ngrok.io -> http://localhost:8090                                    
                                                                                                                                 
Connections                   ttl     opn     rt1     rt5     p50     p90                                                        
                              1       0       0.00    0.00    60.53   60.53                                                      
                                                                                
  • Inicie o projeto back-end, efetue o pagamento na página e adquira os cursos correspondentes.
  • Por fim, verifique o status do pedido na tabela de pedidos e as informações do registro de pagamento nas informações de pagamento.

Primeiro, o status do pedido correspondente é atualizado com sucesso

As informações do registro de pagamento do último pedido foram atualizadas com sucesso

Até agora, o processamento da notificação assíncrona está concluído.

f. Filtrar notificações duplicadas

Problemas existentes:

A filtragem de notificações duplicadas ocorre quando o comerciante recebe uma notificação assíncrona da plataforma Alipay e executa o processamento correspondente para fornecer feedback à plataforma Alipay. No entanto, devido a alguns motivos (como motivos de rede), o Alipay não recebe o feedback do assíncrono resultado da notificação. , Alipay continuará a enviar notificações assíncronas ao comerciante, e o comerciante ainda processará a segunda notificação assíncrona recebida do Alipay e registrará o segundo registro de pagamento.

Essencialmente, trata da idempotência das chamadas de interface.

  • Primeiro escreva o método de consulta de status do pedido (na classe OrderInfoServiceImpl)
 
/**
     * Gets order status.
     * 获取订单状态
     *
     * @param orderNo the order no
     * @return the order status
     */
    String getOrderStatus(String orderNo);

/**
     * 根据订单号获取订单状态
     * @param orderNo the order no
     * @return
     */
    @Override
    public String getOrderStatus(String orderNo) {
    
    
        //进行查询订单的操作
        QueryWrapper<OrderInfo> orderInfoQueryWrapper = new QueryWrapper<>();
        //构造查询条件
        orderInfoQueryWrapper.eq("order_no",orderNo);
        //根据订单号查询的订单信息必须是唯一的,因此使用selectOne
        OrderInfo orderInfo = orderInfoMapper.selectOne(orderInfoQueryWrapper);
        //判断订单信息是否为空,如果为空,直接将订单状态设置为null
        if (orderInfo==null){
    
    
            return null;
        }
        return orderInfo.getOrderStatus();
   }

Primeiro, você precisa passar um número de pedido, criar um consultador de condição correspondente, usar eqo método para fazer comparação de igualdade (o número do pedido é único) e usar orderInfoMappero objeto da camada de persistência para fazer a operação de consulta selectOne. Se o objeto de pedido obtido for vazio, o status será retornado diretamente como vazio, caso contrário, retorne o status do pedido correspondente.

  • Em seguida, defina a lógica de negócios correspondente no método de processamento de pedidos

Faça o processamento lógico no método da AliPayServiceImplclasse processOrder.

//接口调用幂等性问题:在更新订单状态,记录支付日志之前过滤重复通知(无论接口被调用多少次,以下只执行一次)
        //首先获取订单状态
        String orderStatus = orderInfoService.getOrderStatus(orderNo);
        if (!OrderStatus.NOTPAY.getType().equals(orderStatus)){
    
    
            //如果订单状态不是未支付,则直接返回,不需要任何处理
            return;
        }

Se o status do pedido obtido não for não pago, devolva-o diretamente, pois a atualização do status do pedido e o registro do log de pagamento são apenas para pedidos cujo status do pedido seja não pago.

g. Adicionar bloqueio de dados

Há outro problema aqui. Com base no negócio mencionado acima de processar notificações repetidas, pode haver vários servidores que iniciam notificações assíncronas ao mesmo tempo, chegam ao local onde o status do pedido é julgado ao mesmo tempo e são julgados não ser pago ao mesmo tempo e, em seguida, atualizar o mesmo pedido ao mesmo tempo (menos impacto) e realizar a operação de registro do log de pagamento ao mesmo tempo. Neste ponto, é necessário adicionar bloqueios reentrantes (bloqueios de dados) à lógica de negócios correspondente para evitar confusão de dados causada pela reentrada da função .

		 /**
     * 添加可重入锁对象,进行数据的并发控制
     */
    private final ReentrantLock lock=new ReentrantLock();

        /**
         * 在对业务数据进行状态检查之前需要利用数据锁进行处理,进行并发控制
         * 避免数据重入造成混乱,
         * 此处使用尝试获取锁的判断,如果没有获取锁,此时则返回false,直接进行下面的操作
         * 不会等待锁释放,造成阻塞。
         */
        if (lock.tryLock()) {
    
    
            try {
    
    
                //接口调用幂等性问题:在更新订单状态,记录支付日志之前过滤重复通知(无论接口被调用多少次,以下只执行一次)
                //首先获取订单状态
                String orderStatus = orderInfoService.getOrderStatus(orderNo);
                if (!OrderStatus.NOTPAY.getType().equals(orderStatus)){
    
    
                    //如果订单状态不是未支付,则直接返回,不需要任何处理
                    return;
                }
                //更新订单状态
                orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
                //记录支付日志
                paymentInfoService.createPaymentInfoForAliPay(params);
            }finally {
    
    
                //必须要主动释放锁
                lock.unlock();
            }
        }

    }

Para o negócio de processamento de pedidos, é necessário adicionar bloqueios reentrantes para controle de concorrência.

2. Interface de fechamento de transação de aquisição unificada Alipay

Após a criação da transação, caso o usuário não efetue o pagamento em um determinado período de tempo, esta interface pode ser chamada para fechar diretamente a transação não paga.

(1) Visualização da API

Os parâmetros da solicitação na interface de despacho aduaneiro (ou seja, quais parâmetros precisam ser levados na solicitação de despacho aduaneiro).

(2) Fechar o pedido

  • Primeiro crie um método para fechar o pedido na camada de negócios
 /**
     * Cancel order.
     * 根据订单号取消订单
     *
     * @param orderNo the order no
     */
    void cancelOrder(String orderNo);

/**
     * 用户取消订单方法编写
     * @param orderNo 订单号
     */
    @Override
    public void cancelOrder(String orderNo) {
    
    

        //调用支付统一收单交易关闭接口
        this.closeOrder(orderNo);

        //更新用户的订单状态
        orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.CANCEL);

    }

    /**
     * 关单接口调用
     * @param orderNo 订单号
     */
    private void closeOrder(String orderNo) {
    
    
        try {
    
    


            log.info("关单接口调用,订单号---》{}", orderNo);
            //创建关单请求
            AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();
            //创建请求参数对象
            JSONObject bizContent = new JSONObject();
            bizContent.put("out_trade_no", orderNo);
            //将对应的参数设置到请求对象中
            request.setBizContent(bizContent.toString());
            //使用支付客户端对象执行请求
            AlipayTradeCloseResponse response = alipayClient.execute(request);
            //判断请求是否成功
            if (response.isSuccess()){
    
    
                //打印响应信息主体
                log.info("调用成功====》{}",response.getBody());
            }else {
    
    
                log.info("调用失败====》{},返回码"+response.getCode()+",返回描述为:"+response.getMsg());
               // throw new RuntimeException("关单接口调用失败....."); 让其正常结束
            }

        } catch (AlipayApiException e) {
    
    
            throw new RuntimeException("关单接口调用出现异常");
        }


    }

Para fechar um pedido, você precisa primeiro criar um objeto de pedido de fechamento de pedido, depois criar um objeto de encapsulamento de parâmetro de pedido, passar o número do pedido, executar o pedido e processar a resposta de acordo.

Depois de chamar a interface de ordem alfandegária do Alipay com sucesso, é necessário definir o status da nova ordem de transação.

Se você não digitalizar o código para fazer login durante o processo de pagamento, o Alipay não criará um registro dessa transação, ou seja, ao julgar se o status da solicitação foi bem-sucedido, ele retornará a chamada com falha, método de finalização normal e modificará diretamente no Status do Pedido do sistema do comerciante.

  • Escrever métodos na camada de controle
  /**
     * 用户取消订单接口
     * @param orderNo 订单号
     * @return 返回取消结果
     */
    @ApiOperation("用户取消订单")
    @PostMapping("/trade/close/{orderNo}")
    public Results cancel(@PathVariable String orderNo){
    
    
        log.info("用户取消订单......");
        //处理取消订单业务
        aliPayService.cancelOrder(orderNo);
        //返回订单取消信息
        return Results.returnOk().setMessage("订单已取消");
    }

Feche a operação de pedido correspondente de acordo com o número do pedido

(3) teste

3. Consulta de transação offline de aquisição unificada

Essa interface fornece a consulta de todos os pedidos de pagamento Alipay. Os comerciantes podem consultar ativamente o status do pedido por meio dessa interface para concluir a lógica de negócios da próxima etapa.
Situações que precisam chamar a interface de consulta:
Quando o background, rede, servidor, etc. do lojista estão anormais, o sistema do lojista não recebeu a notificação de pagamento;
após chamar a interface de pagamento, retorna um erro de sistema ou status de transação desconhecido;
chamadas alipay.trade.pay, retorna o status INPROCESS;
antes de ligar para alipay.trade.cancel, o status do pagamento precisa ser confirmado;

(1) Visualização da API

  • parâmetros de solicitação pública

O primeiro é o parâmetro de solicitação pública, que fornece vários parâmetros obrigatórios

  • solicitar parâmetros

A segunda é consultar os parâmetros de solicitação necessários para o pedido

  • parâmetro de resposta pública

Novamente, o parâmetro de resposta pública da resposta da solicitação

  • parâmetro de resposta

Finalmente, os parâmetros de resposta da solicitação

  • API de resposta

(2) Informe-se ativamente sobre pedidos

  • Primeiro, crie um novo método no AlipayService para consultar as operações do pedido
/**
 * The interface Ali pay service.
 *
 * @author lambda
 */
public interface AliPayService {
    
    
    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
/**
     * Query order string.
     * 商户向支付宝端查询订单结果
     *
     * @param orderNo the order no
     * @return the string
     */
    String queryOrder(String orderNo);
}
  • Em seguida, implemente o método de consulta do pedido
  /**
     * 商户查询订单信息
     * @param orderNo 订单号
     * @return 返回订单查询结果,如果返回为null,说明支付宝端没有创建订单
     */
    @Override
    public String queryOrder(String orderNo) {
    
    

        try {
    
    

            log.info("查单接口调用----》{}", orderNo);
            //首先创建交易查询对象
            AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
            //组装请求参数对象(向支付宝端查单需要提供哪些参数)
            JSONObject bizContent = new JSONObject();
            //组装订单号
            bizContent.put("out_trade_no", orderNo);
            request.setBizContent(bizContent.toString());
            //执行查询请求
            AlipayTradeQueryResponse response= alipayClient.execute(request);
            if (response.isSuccess()){
    
    
                log.info("调用成功,返回结果---》{}",response.getBody());
                return response.getBody();
            }else{
    
    
                log.info("调用失败,返回响应码"+response.getCode()+",响应结果为"+response.getBody());
               // throw new RuntimeException("响应失败....");
                //调用失败直接返回为null
                return null;
            }
        } catch (AlipayApiException e) {
    
    
            throw new RuntimeException("查询订单接口调用失败.....");
        }
    }

  • Realize o salto na camada de controle
 /**
     *商户查询订单接口
     * 商户根据订单号查询相应的订单信息
     * @param orderNo 订单号
     * @return
     */
    @ApiOperation("商户查询订单")
    @GetMapping("/trade/query/{orderNo}")
    public Results queryOrder(@PathVariable String orderNo){
    
    
            log.info("商户查询订单====》{}",orderNo);
            //调用支付宝支付服务的查询订单方法
        String result=aliPayService.queryOrder(orderNo);
        return Results.returnOk().setMessage("查询订单信息").returnData("result",result);
    }

Após obter as informações consultadas, devolva-as ao front end.

  • Em seguida, implemente uma tarefa de temporização para realizar a operação da lista de verificação
//orderInfoService
public interface OrderInfoService  extends IService<OrderInfo> {
    
    
 /**
     * Gets no pay order by duration.
     * 查询超过指定时间未支付的订单
     *
     * @param minutes     the
     * @param paymentType the payment type
     * @return the no pay order by duration
     */
    List<OrderInfo> getNoPayOrderByDuration(int minutes,String paymentType);
}

//orderInfoServiceImpl

@Service
@Slf4j
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService {
    
    

/**
     *查询超过指定时间未支付的订单集合
     * @param minutes the
     * @return
     */
    @Override
    public List<OrderInfo> getNoPayOrderByDuration(int minutes,String paymentType) {
    
    
        //创建一个时间实例,减去超时时间的时间实例,与订单的创建时间相比
        Instant minus = Instant.now().minus(Duration.ofMinutes(minutes));

        //创建一个查询订单对象
        QueryWrapper<OrderInfo> orderInfoQueryWrapper = new QueryWrapper<>();
        //组装订单的查询信息,首先是未支付
        orderInfoQueryWrapper.eq("order_status",OrderStatus.NOTPAY.getType());
        //如果当前时间减去超时时间的时间值比创建时间晚,则说明已经超时了
        orderInfoQueryWrapper.le("create_time",minus);
        orderInfoQueryWrapper.eq("payment_type",paymentType);
        //最后将查询的结果返回
        return orderInfoMapper.selectList(orderInfoQueryWrapper);
    }
} 

Consulte pedidos não pagos com base no número do pedido e no tipo de pagamento.

  • Criar uma tarefa agendada
@Slf4j
@Component
public class AliPayTask {
    
    
    @Resource
    private OrderInfoService orderInfoService;

    /**
     * 每30秒查询一次订单信息,查询创建1分钟并且未支付的订单
     */
    @Scheduled(cron = "0/30 * * * * ?")
    public void orderConfirm(){
    
    
        log.info("定时查询订单任务启动");
        //调用查询未支付订单的方法获取所有的订单信息
        List<OrderInfo> noPayOrderList = orderInfoService.getNoPayOrderByDuration(1, PayType.ALIPAY.getType());

        //遍历超时订单
        for (OrderInfo orderInfo : noPayOrderList) {
    
    
            String orderNo = orderInfo.getOrderNo();
            log.info("超时1分钟未支付的订单---》{}",orderNo);
        }

    }
}

@Scheduled(cron = "0/30 * * * * ?")Indica o período da tarefa agendada especificada.

(3) O pedido de verificação regular do pedido não foi criado

Esta seção explica principalmente como consultar as informações do pedido do terminal do comerciante para o terminal Alipay, para que o terminal do comerciante possa atualizar as informações relacionadas ao pedido. Como a exibição local não foi paga, não é possível garantir que o pagamento não tenha sido feito no lado do Alipay.Se o pagamento foi feito no lado do Alipay, é necessário atualizar as informações do pedido local a ser pago.

  • Adicione um método para consultar informações de pedidos do Alipay
/**
     * 根据订单号查询支付宝端的订单状态
     * 如果订单已经支付,则更新商户端订单状态,并记录支付日志
     * 如果订单没有支付,则调用关单接口,并更新商户端订单状态
     * 如果订单未创建,则直接更新商户端的订单状态即可
     * @param orderNo 订单号
     */
    @Override
    public void checkOrderStatus(String orderNo) {
    
    
        log.warn("根据订单号核实订单状态---》{}",orderNo);
        //商户端向支付宝端查询订单信息
        String result = this.queryOrder(orderNo);
        //1.订单未创建状态
        if (result==null){
    
    
            log.warn("核实订单未创建---》{}",orderNo);
            //更新本地订单状态(设置关闭)
            orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.CLOSED);
        }

        //2.如果订单未支付,则调用关单接口并更新商户端订单状态
        Gson gson = new Gson();
        //由于result的值中也是属于键值对,String-{xxx:xxx,xxxx:xxxx,xxx:xxxx}
        Map<String, LinkedTreeMap> resultMap = gson.fromJson(result, HashMap.class);
        //参见统一收单线下交易查询中的响应示例
        LinkedTreeMap alipayTradeQueryResponse = resultMap.get("alipay_trade_query_response");
        //从map中获取订单状态(trade_status)
        String tradeStatus = (String)alipayTradeQueryResponse.get("trade_status");
        if (AliPayTradeState.NOTPAY.getType().equals(tradeStatus)){
    
    
            //判断如果订单未支付
            log.warn("核实订单未支付---》{}",orderNo);
            //订单未支付,则调用关单接口
            this.closeOrder(orderNo);
            //更新商户端状态
            orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.CLOSED);
        }

        //3.如果订单已经支付,则更新商户端的订单状态,并记录支付日志
        if (AliPayTradeState.SUCCESS.getType().equals(tradeStatus)){
    
    
            //判断订单已经支付
            log.warn("核实订单已支付---》{}",orderNo);
            orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.SUCCESS);
            paymentInfoService.createPaymentInfoForAliPay(alipayTradeQueryResponse);
        }


    }

Se o pedido foi pago, atualize o status do pedido do comerciante e registre o log de pagamento

Se o pedido não foi pago, chame a interface de fechamento do pedido e atualize o status do pedido do comerciante

Se o pedido não foi criado, basta atualizar o status do pedido diretamente no lado do comerciante

  • Chame a interface da lista de verificação na tarefa agendada
@Slf4j
@Component
public class AliPayTask {
    
    
    @Resource
    private OrderInfoService orderInfoService;
    @Resource
    private AliPayService aliPayService;

    /**
     * 每30秒查询一次订单信息,查询创建1分钟并且未支付的订单
     */
    @Scheduled(cron = "0/30 * * * * ?")
    public void orderConfirm(){
    
    
        log.info("定时查询订单任务启动");
        //调用查询未支付订单的方法获取所有的订单信息
        List<OrderInfo> noPayOrderList = orderInfoService.getNoPayOrderByDuration(1, PayType.ALIPAY.getType());

        //遍历超时订单
        for (OrderInfo orderInfo : noPayOrderList) {
    
    
            String orderNo = orderInfo.getOrderNo();
            log.info("超时1分钟未支付的订单---》{}",orderNo);
            //核实订单状态,调用支付宝端查单接口
            aliPayService.checkOrderStatus(orderNo);
        }
    }
}
  • teste

A premissa é: quando o usuário escolhe Alipay para pagar, o sistema do comerciante gera automaticamente um pedido não pago, independentemente de o usuário escanear o código ou não. Se o usuário não digitalizar o código, ele exibirá não pago. Se o usuário digitalizar o código e pagar, o sistema do comerciante aguardará que o Alipay inicie uma notificação de retorno de chamada para notificar o comerciante de que o pagamento do usuário foi bem-sucedido e o comerciante está solicitado a atualizar o status a tempo. Se o sistema do comerciante não puder receber corretamente a notificação de retorno de chamada do Alipay devido a motivos de rede, o usuário precisa ligar ativamente para a interface de verificação do pedido após um período de tempo e atualizar as informações do pedido correspondente com base nas informações de feedback.

Se o pedido não for criado no Alipay (ou seja, nenhuma varredura de código for executada), o sistema do comerciante atualizará automaticamente as informações do pedido como tempo limite fechado após um minuto de tempo limite.

Se o pedido foi digitalizado, mas nenhum pagamento real foi feito, o sistema do comerciante atualizará automaticamente as informações do pedido como tempo limite fechado após um minuto de tempo limite.

Se o pedido foi pago com sucesso, mas por motivos de rede, o sistema do comerciante não recebeu a notificação de retorno da Alipay (neste momento, embora o usuário tenha pago com sucesso, o sistema do comerciante não recebeu a notificação, portanto, as informações de pagamento não foi atualizado a tempo), então você precisa chamar principalmente a interface de verificação do pedido.Se as informações consultadas já foram pagas, o comerciante precisa atualizar ativamente as informações do pedido local, registrar o log de pagamento e atualizar o status do pedido.

4. Interface de reembolso unificada para aquisição de transações

Quando um reembolso é exigido devido ao comprador ou vendedor dentro de um determinado período de tempo após a transação, o vendedor pode reembolsar o pagamento ao comprador por meio da interface de reembolso e o Alipay emitirá um reembolso de acordo com as regras, o o pagamento será reembolsado na conta do comprador pela rota original.

(1) Visualização da API

  • solicitar parâmetros

  • parâmetro de resposta

(2) Realize a função de reembolso

  • Implementar interface de informações de reembolso
public interface RefundInfoService extends IService<RefundInfo> {
    
    
     /**
     * Create refund by order no refund info.
     * 根据订单号创建退款订单
     *
     * @param orderNo the order no
     * @param reason  the reason
     * @return the refund info
     */
    RefundInfo createRefundByOrderNo(String orderNo, String reason);
	
     /**
     * Update refund.
     * 更新退款信息
     *
     * @param bodyAsString the body as string
     */
    void updateRefund(String bodyAsString);


    /**
     * Update refund for ali pay.
     * 支付宝支付退款
     * @param refundNo     the refund no
     * @param content      the content
     * @param refundStatus the refund status
     */
    void updateRefundForAliPay(String refundNo, String content, String refundStatus);
}

  • Implemente o método para atualizar as informações de reembolso
@Service
public class RefundInfoServiceImpl extends ServiceImpl<RefundInfoMapper, RefundInfo> implements RefundInfoService {
    
    
    
    @Resource
    private OrderInfoService orderInfoService;
    @Resource
    private RefundInfoMapper refundInfoMapper;
    
     /**
     *
     * @param orderNo 订单编号
     * @param reason 退款原因
     * @return RefundInfo 退款单信息
     */
    @Override
    public RefundInfo createRefundByOrderNo(String orderNo, String reason) {
    
    
        //根据订单号处理订单信息
        OrderInfo orderInfo=orderInfoService.getOrderByOrderNo(orderNo);

        //根据订单号生成退款单记录
        RefundInfo refundInfo = new RefundInfo();
        //订单编号
        refundInfo.setOrderNo(orderNo);
        //退款单编号
        refundInfo.setRefundNo(OrderNoUtils.getRefundNo());
        //原来订单金额
        refundInfo.setTotalFee(orderInfo.getTotalFee());
        //退款金额
        refundInfo.setRefund(orderInfo.getTotalFee());
        //退款原因
        refundInfo.setReason(reason);
        //将退款信息插入数据库
        refundInfoMapper.insert(refundInfo);
        return refundInfo;
    }

      @Override
    public void updateRefund(String content) {
    
    
        //将退款请求响应的返回对象转成Map类型信息
        Gson gson = new Gson();
        Map<String, String> resultMap = gson.fromJson(content, HashMap.class);
        //根据退款单编号,修改退款单
        QueryWrapper<RefundInfo> refundInfoQueryWrapper = new QueryWrapper<>();
        refundInfoQueryWrapper.eq("refund_no",resultMap.get("out_refund_no"));

        //设置要修改的字段
        RefundInfo refundInfo = new RefundInfo();
        //微信支付退款单号
        refundInfo.setRefundId(resultMap.get("refund_id"));

        //查询申请退款和退款中的返回参数(退款中)
        if (resultMap.get("status")!=null){
    
    
            //设置退款状态
            refundInfo.setRefundStatus(resultMap.get("status"));
            //将全部响应结果存入数据库的content字段中
            refundInfo.setContentReturn(content);
        }

        //退款回调中的回调参数(这是退款之后的状态)
        if (resultMap.get("refund_status")!=null){
    
    
                refundInfo.setRefundStatus(resultMap.get("refund-status"));
            //将全部响应结果存入数据库的content字段中
            refundInfo.setContentNotify(content);
        }

        //更新退款单
        refundInfoMapper.update(refundInfo,refundInfoQueryWrapper);

    }
/**
     *
     * @param refundNo 退款单号
     * @param content 退款信息主体
     * @param refundStatus 退款结果类型
     */
    @Override
    public void updateRefundForAliPay(String refundNo, String content, String refundStatus) {
    
    
        //根据退款单号修改退款单
        QueryWrapper<RefundInfo> refundInfoQueryWrapper = new QueryWrapper<>();
        refundInfoQueryWrapper.eq("refund_no",refundNo);

        //设置要修改的字段(新建一个退款单)
        RefundInfo refundInfo = new RefundInfo();
        //refundInfo.setRefundNo(refundNo);
        refundInfo.setRefundStatus(refundStatus);
        refundInfo.setContentReturn(content);

        //执行更新操作
        refundInfoMapper.update(refundInfo,refundInfoQueryWrapper);

    }

Primeiro, injete RefundInfoMapperas informações do pedido de reembolso usadas para operar a camada de persistência (executar adição, exclusão, modificação e consulta de negócios) e, em seguida, crie um objeto para modificar as informações do reembolso. A condição correspondente é que o número do pedido de reembolso seja o mesmo. Em seguida, crie um objeto de informações de reembolso, defina o status e o motivo do reembolso correspondente e execute a operação de atualização.

  • Adicione um método de reembolso no método de camada de serviço de pagamento Alipay
public interface AliPayService {
    
     
/**
     * Refund.
     * 退款操作
     * @param orderNo the order no
     * @param reason  the reason
     */
    void refund(String orderNo, String reason);
}

@Service
@Slf4j
public class AliPayServiceImpl implements AliPayService {
    
    
    @Resource
    private RefundInfoService refundInfoService
    ......
 /**
     * 商户发起退款请求
     * @param orderNo 退款单号
     * @param reason 原因
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void refund(String orderNo, String reason) {
    
    
        try {
    
    
            log.info("调用退款API");
            //调用退款信息方法创建退款信息
            RefundInfo refundInfo = refundInfoService.createRefundByOrderNo(orderNo, reason);
            //创建统一交易退款请求
            AlipayTradeRefundRequest request=new AlipayTradeRefundRequest();
            //组装当前业务交易的请求参数
            JSONObject bizContent = new JSONObject();
            bizContent.put("out_trade_no",orderNo);
            //设置退款单金额(需要除以100),分转化成元
            BigDecimal refund = new BigDecimal(refundInfo.getRefund().toString()).divide(new BigDecimal("100"));
            bizContent.put("refund_amount",refund);
            bizContent.put("refund_reason",reason);
            //将参数设置到请求中
            request.setBizContent(bizContent.toString());
            AlipayTradeRefundResponse response = alipayClient.execute(request);
            if (response.isSuccess()){
    
    
                log.info("退款交易成功,对应信息为:"+response.getBody());
                //更新订单状态
                orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.REFUND_SUCCESS);
                //更新退款单
                refundInfoService.updateRefundForAliPay( //表示退款成功
                        refundInfo.getRefundNo(),response.getBody(),AliPayTradeState.REFUND_SUCCESS.getType());
            }else{
    
    
                log.warn("退款交易失败,对应状态码为:"+response.getCode()+",返回体为:"+response.getBody());
                //更新订单状态
                orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.REFUND_ABNORMAL);
                //更新退款单
                refundInfoService.updateRefundForAliPay(
                        refundInfo.getRefundNo(),response.getBody(),AliPayTradeState.REFUND_ERROR.getType()
                );
            }


        } catch (AlipayApiException e) {
    
    
            throw new RuntimeException("退款交易失败.....");
        }
    }

Primeiro, injete RefundInfoServiceo objeto para criar um objeto de informações de reembolso e, em seguida, crie uma solicitação de reembolso de transação Alipay. Os parâmetros de solicitação de montagem são montados principalmente para o número do pedido, valor do pedido e motivo do pedido. Após a conclusão da montagem, execute a execução correspondente e obtenha uma resposta do Alipay. Atualize o número do pedido e as informações do pedido de reembolso de acordo com a resposta bem-sucedida ou não.

  • Melhore as informações relevantes no nível de controle
@CrossOrigin
@RestController
@RequestMapping("/api/ali-pay")
@Api(tags = "网站支付宝支付")
@Slf4j
public class AliPayController {
    
    
    /**
     * 商户退款接口
     * @param orderNo 退款单号
     * @param reason 退款原因
     * @return
     */
    @ApiOperation("商户退款接口")
    @PostMapping("/trade/refund/{orderNo}/{reason}")
    public Results refunds(@PathVariable String orderNo,@PathVariable String reason){
    
    
        log.info("申请退款....");
        //调用服务层退款方法
        aliPayService.refund(orderNo,reason);
        return Results.returnOk();

    }
}

Após obter os parâmetros do front-end, chame a solicitação de reembolso no back-end para realizar a operação de reembolso

(3) Teste a função de reembolso

Exibição do resultado do reembolso:

5. Consulta de reembolso de transação de aquisição unificada

Os comerciantes podem usar esta interface para consultar se a solicitação de reembolso enviada por alipay.trade.refund foi executada com sucesso.

Se a interface de reembolso retornar uma exceção devido à rede ou outros motivos, o comerciante pode chamar a interface de consulta de reembolso alipay.trade.fastpay.refund.query (interface de consulta de reembolso de transação de aquisição unificada) para consultar as informações de reembolso da transação especificada.

(1) Visualização da API

  • solicitar parâmetros

(2) Realização da função de consulta de reembolso

  • Função de consulta de reembolso
public interface AliPayService {
    
    
    ...........
 /**
     * Query refund string.
     * 查询退款结果
     *
     * @param orderNo the order no
     * @return the string
     */
    String queryRefund(String orderNo);
}

@Service
@Slf4j
public class AliPayServiceImpl implements AliPayService {
    
     
/**
     * 根据订单号查询退款
     * @param orderNo the order no 订单号
     * @return 返回退款查询的结果
     */
    @Override
    public String queryRefund(String orderNo) {
    
    

        try {
    
    
            log.info("查询退款接口调用---》{}",orderNo);
            //定义一个查询退款的请求对象
            AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest();
            //组装请求参数
            JSONObject bizContent = new JSONObject();
            bizContent.put("out_trade_no",orderNo);
            //out_request_no表示退款请求号,如果退款的时候没有传入,则以订单号作为退款请求号。
            bizContent.put("out_request_no",orderNo);
            //组装到请求中
            request.setBizContent(bizContent.toString());
            //执行请求
            AlipayTradeFastpayRefundQueryResponse response = alipayClient.execute(request);
            if (response.isSuccess()){
    
    
                log.info("调用成功,返回结果---》{}",response.getBody());
                return response.getBody();
            }else {
    
    
                log.info("调用失败,对应的响应码为:"+response.getCode()+",对应的响应内容为:"+response.getBody());
                //如果调用失败,返回空
                return null;
            }

        } catch (AlipayApiException e) {
    
    
            throw new RuntimeException("退款查询请求执行失败");
        }

    }
  • Implementação da camada de controle
@CrossOrigin
@RestController
@RequestMapping("/api/ali-pay")
@Api(tags = "网站支付宝支付")
@Slf4j
public class AliPayController {
    
    

    @Resource
    private AliPayService aliPayService;

    @Resource
    private Environment config;

    @Resource
    private OrderInfoService orderInfoService;
/**
     * 退款结果查询(商户向支付宝端查询)
     * @param orderNo 订单号
     * @return 返回查询的结果
     */
    @ApiOperation("查询退款")
    @GetMapping("/trade/fastpay/refund/{orderNo}")
    public Results queryRefunds(@PathVariable("orderNo") String orderNo){
    
    
        log.info("查询退款.......");
        //执行退款查询并接收返回的字符串结果
        String result=aliPayService.queryRefund(orderNo);
        return Results.returnOk().setMessage("查询成功").returnData("result",result);
    }

(3) Resultados do teste

Resultados da consulta de teste em swagger

6. Consultar o endereço de download da declaração

A fim de facilitar que os comerciantes verifiquem as contas rapidamente, os comerciantes são suportados para obter o endereço de download das faturas off-line dos comerciantes por meio desta interface

(1) Visualização da API

  • solicitar parâmetros

  • parâmetro de resposta

(2) Baixe a implementação do projeto de lei

  • Obtenha a implementação do endereço de cobrança
public interface AliPayService {
    
    
    ..............
/**
     * Query bill string.
     *  查询订单下载地址
     * @param billDate the bill date
     * @param type     the type
     * @return the string
     */
    String queryBill(String billDate, String type);
}
//根据账单的日期和类型查询
@Service
@Slf4j
public class AliPayServiceImpl implements AliPayService {
    
    
    .........
        /**
     * 获取账单地址实现
     * @param billDate the bill date 账单日期
     * @param type     the type 账单类型
     * @return
     */
    @Override
    public String queryBill(String billDate, String type) {
    
    
       try {
    
    
           //设置查询账单请求对象
      AlipayDataDataserviceBillDownloadurlQueryRequest request = new AlipayDataDataserviceBillDownloadurlQueryRequest();
            //组装请求参数
           JSONObject bizContent = new JSONObject();
           bizContent.put("bill_type",type);
           bizContent.put("bill_date",billDate);
           //将请求参数设置到请求中
           request.setBizContent(bizContent.toString());
           //执行请求
           AlipayDataDataserviceBillDownloadurlQueryResponse response = alipayClient.execute(request);
           if (response.isSuccess()){
    
    
               log.info("查询账单url地址请求成功---》{}",response.getBody());
               //获取账单的下载地址
               Gson gson = new Gson();
               Map<String,LinkedTreeMap> resultMap=gson.fromJson(response.getBody(),HashMap.class);
               //获取交易账单地址
               LinkedTreeMap billDownLoadUrl= resultMap.get("alipay_data_dataservice_bill_downloadurl_query_response");
               String billDownloadUrl = (String)billDownLoadUrl.get("bill_download_url");
               //返回url地址
               return billDownloadUrl;

           }else {
    
    
               log.info("查询账单地址失败。对应的响应码为:"+response.getCode()+",对应的响应体为:"+response.getBody());
                throw new RuntimeException("查询账单地址失败....");
           }

       } catch (AlipayApiException e) {
    
    
           throw new RuntimeException("查询账单请求执行失败.......");
       }

    }
}
  • camada de controle
@CrossOrigin
@RestController
@RequestMapping("/api/ali-pay")
@Api(tags = "网站支付宝支付")
@Slf4j
public class AliPayController {
    
    
    .......
        /**
     * 根据账单类型和日期获取账单的url地址
     * @param billDate 账单的日期
     * @param type 账单的类型
     * @return 返回账单的url地址
     */
    @ApiOperation("获取账单url")
    @GetMapping("/bill/downloadurl/query/{billDate}/{type}")
    public Results queryTradeBill(@PathVariable String billDate,
                                  @PathVariable String type){
    
    

        log.info("获取账单的url地址");
        //获取账单的url地址
        String downloadUrl=aliPayService.queryBill(billDate,type);

        return Results.returnOk().setMessage("获取账单地址成功")
                .returnData("downloadUrl",downloadUrl);

    }
}

(3) Teste a função de download da conta

As informações de cobrança correspondentes são as seguintes:

insira a descrição da imagem aqui

Acho que você gosta

Origin blog.csdn.net/qq_50824019/article/details/130238068
Recomendado
Clasificación