Spring Boot整合支付宝支付教程

使用沙箱环境实现支付宝支付功能

目录

1. 引言

支付宝作为中国最大的第三方支付平台之一,为电商和各类应用提供了便捷的支付解决方案。在开发阶段,尤其是个人开发者通常无法直接申请支付宝商户号,因此需要使用支付宝提供的沙箱环境进行开发和测试。本教程将详细介绍如何在Spring Boot项目中整合支付宝支付功能,重点关注沙箱环境的配置和使用。

说明:沙箱环境是支付宝为开发者提供的模拟环境,可以在不涉及真实交易的情况下测试支付流程。在沙箱环境中,所有的交易都是模拟的,不会产生真实的资金流动。

本教程适合人群

技术栈

2. 准备工作

在开始整合支付宝支付功能之前,我们需要做一些准备工作,包括注册支付宝开放平台账号、配置沙箱环境以及创建Spring Boot项目。

2.1 支付宝开放平台注册

首先,我们需要在支付宝开放平台注册一个开发者账号。

  1. 访问支付宝开放平台,点击右上角的"注册"按钮。
  2. 填写相关信息,完成注册流程。
  3. 登录后,进入"开发者中心",然后点击"沙箱环境"。

提示:注册支付宝开放平台账号是免费的,但如果要上线真实环境,需要进行企业认证。对于个人开发者,可以长期使用沙箱环境进行开发和测试。

2.2 沙箱环境配置

在支付宝开放平台的沙箱环境中,我们需要配置密钥和获取相关参数。

2.2.1 生成密钥对

  1. 在沙箱环境页面,找到"沙箱应用"卡片,点击"查看"。
  2. 在应用详情页面,找到"开发信息"区域。
  3. 点击"生成密钥"按钮,会自动下载一个密钥工具。
  4. 解压并运行该工具,生成商户私钥和公钥。
  5. 将生成的商户公钥配置到沙箱应用中,并保存好私钥文件。

2.2.2 获取沙箱参数

我们需要记录以下参数,稍后在Spring Boot项目中使用:

  • APPID:沙箱应用的ID
  • 支付宝网关:https://openapi.alipaydev.com/gateway.do
  • 商户私钥:之前生成的私钥
  • 支付宝公钥:沙箱环境提供的公钥

2.2.3 下载沙箱版支付宝钱包

为了测试支付流程,我们需要下载沙箱环境专用的支付宝钱包APP:

  1. 在沙箱环境页面,找到"沙箱账号"区域。
  2. 记录买家账号和登录密码,以及支付密码。
  3. 下载沙箱钱包APP并安装。

2.3 Spring Boot 项目搭建

接下来,我们需要创建一个Spring Boot项目。可以通过Spring Initializr或者IDE来创建。

2.3.1 使用Spring Initializr创建项目

  1. 访问Spring Initializr
  2. 选择以下配置:
    • 项目类型:Maven
    • 语言:Java
    • Spring Boot版本:2.6.x或更高
    • Group:com.example
    • Artifact:alipay-demo
    • 打包方式:Jar
    • Java版本:8或更高
  3. 添加以下依赖:
    • Spring Web
    • Thymeleaf
    • Lombok (可选,用于简化代码)
  4. 点击"Generate"下载项目压缩包。
  5. 解压并用IDE打开项目。

说明:本教程使用的是Spring Boot 2.6.x版本,但原则上2.x系列的版本都应该适用。如果你使用Spring Boot 3.x,可能需要对部分代码进行调整。

3. 整合支付宝SDK

在Spring Boot项目创建完成后,我们需要整合支付宝SDK,包括添加依赖、配置属性和初始化支付宝客户端。

3.1 添加依赖

首先,我们需要在pom.xml文件中添加支付宝SDK的依赖。打开pom.xml文件,在dependencies节点中添加以下依赖:

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

<!-- 用于处理JSON的依赖 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.83</version>
</dependency>

提示:建议使用最新版本的支付宝SDK,可以在Maven仓库中查找最新版本。

3.2 配置属性

接下来,我们需要在application.yml文件中配置支付宝相关的属性。如果项目中没有application.yml文件,可以将application.properties重命名为application.yml,或者直接创建一个新的application.yml文件。

server:
  port: 8080

spring:
  thymeleaf:
    cache: false
    
alipay:
  app-id: 20210100000000000 # 你的沙箱环境APPID
  merchant-private-key: MIIEvgIBADANB... # 你的商户私钥,完整的私钥内容
  alipay-public-key: MIIBIjANBgkqhki... # 支付宝公钥
  gateway-url: https://openapi.alipaydev.com/gateway.do # 沙箱环境网关
  charset: UTF-8
  format: json
  sign-type: RSA2
  notify-url: http://localhost:8080/alipay/notify # 异步通知地址
  return-url: http://localhost:8080/alipay/return # 同步回调地址

警告:在实际项目中,不建议将敏感信息如私钥直接放在配置文件中,而应使用环境变量或配置中心来管理这些敏感信息。这里为了教程简单化而直接配置。

创建属性绑定类

为了更方便地使用这些配置属性,我们创建一个属性绑定类:

package com.example.alipaydemo.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "alipay")
public class AlipayProperties {
    
    private String appId;
    private String merchantPrivateKey;
    private String alipayPublicKey;
    private String gatewayUrl;
    private String charset;
    private String format;
    private String signType;
    private String notifyUrl;
    private String returnUrl;
}

3.3 配置支付宝客户端

现在,我们需要创建一个配置类,用于初始化支付宝客户端:

package com.example.alipaydemo.config;

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

@Configuration
public class AlipayConfig {

    private final AlipayProperties alipayProperties;

    public AlipayConfig(AlipayProperties alipayProperties) {
        this.alipayProperties = alipayProperties;
    }

    @Bean
    public AlipayClient alipayClient() {
        return new DefaultAlipayClient(
                alipayProperties.getGatewayUrl(),
                alipayProperties.getAppId(),
                alipayProperties.getMerchantPrivateKey(),
                alipayProperties.getFormat(),
                alipayProperties.getCharset(),
                alipayProperties.getAlipayPublicKey(),
                alipayProperties.getSignType()
        );
    }
}

通过上述配置,我们已经完成了支付宝SDK的基本整合,接下来我们将实现具体的支付接口。

4. 实现支付接口

在完成支付宝SDK的整合后,我们需要实现支付相关的功能,包括创建支付实体类、实现支付服务和控制器。

4.1 创建支付实体类

首先,我们创建一个支付订单实体类,用于封装支付信息:

package com.example.alipaydemo.model;

import lombok.Data;

import java.math.BigDecimal;

@Data
public class PaymentOrder {
    
    private String outTradeNo; // 商户订单号
    private String subject;     // 订单标题
    private BigDecimal totalAmount; // 订单金额
    private String body;        // 订单描述
    
    // 其他业务参数...
}

4.2 支付服务实现

接下来,我们实现支付服务,用于处理支付相关的业务逻辑:

4.2.1 定义支付服务接口

package com.example.alipaydemo.service;

import com.example.alipaydemo.model.PaymentOrder;

public interface AlipayService {
    
    /**
     * 创建电脑网站支付
     */
    String createPagePay(PaymentOrder paymentOrder);
    
    /**
     * 验证支付宝异步通知
     */
    boolean verifyNotify(String notifyId);
    
    /**
     * 查询订单状态
     */
    String queryOrder(String outTradeNo);
}

4.2.2 实现支付服务

package com.example.alipaydemo.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.example.alipaydemo.config.AlipayProperties;
import com.example.alipaydemo.model.PaymentOrder;
import com.example.alipaydemo.service.AlipayService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class AlipayServiceImpl implements AlipayService {

    private final AlipayClient alipayClient;
    private final AlipayProperties alipayProperties;

    public AlipayServiceImpl(AlipayClient alipayClient, AlipayProperties alipayProperties) {
        this.alipayClient = alipayClient;
        this.alipayProperties = alipayProperties;
    }

    @Override
    public String createPagePay(PaymentOrder paymentOrder) {
        try {
            // 创建API对应的request
            AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
            
            // 设置回调地址
            request.setReturnUrl(alipayProperties.getReturnUrl());
            request.setNotifyUrl(alipayProperties.getNotifyUrl());
            
            // 设置业务参数
            JSONObject bizContent = new JSONObject();
            bizContent.put("out_trade_no", paymentOrder.getOutTradeNo());
            bizContent.put("total_amount", paymentOrder.getTotalAmount());
            bizContent.put("subject", paymentOrder.getSubject());
            bizContent.put("body", paymentOrder.getBody());
            bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY"); // 电脑网站支付场景固定为FAST_INSTANT_TRADE_PAY
            
            request.setBizContent(bizContent.toString());
            
            // 调用SDK生成表单
            String form = alipayClient.pageExecute(request).getBody();
            return form;
        } catch (AlipayApiException e) {
            log.error("生成支付宝支付表单失败", e);
            throw new RuntimeException("生成支付宝支付表单失败", e);
        }
    }

    @Override
    public boolean verifyNotify(String notifyId) {
        // 实际项目中,应该调用支付宝提供的验证接口
        // 这里简化处理,假设验证通过
        return true;
    }

    @Override
    public String queryOrder(String outTradeNo) {
        try {
            AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
            
            JSONObject bizContent = new JSONObject();
            bizContent.put("out_trade_no", outTradeNo);
            
            request.setBizContent(bizContent.toString());
            
            AlipayTradeQueryResponse response = alipayClient.execute(request);
            
            if (response.isSuccess()) {
                return response.getTradeStatus();
            } else {
                log.error("查询订单失败: {}", response.getMsg());
                return null;
            }
        } catch (AlipayApiException e) {
            log.error("查询订单异常", e);
            throw new RuntimeException("查询订单异常", e);
        }
    }
}

4.3 支付控制器

最后,我们实现支付相关的控制器,用于处理前端请求:

package com.example.alipaydemo.controller;

import com.example.alipaydemo.model.PaymentOrder;
import com.example.alipaydemo.service.AlipayService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.math.BigDecimal;
import java.util.Map;
import java.util.UUID;

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

    private final AlipayService alipayService;

    public AlipayController(AlipayService alipayService) {
        this.alipayService = alipayService;
    }

    /**
     * 跳转到支付页面
     */
    @GetMapping("/pay")
    public String pay(Model model) {
        // 这里简单展示一个固定商品
        model.addAttribute("productName", "测试商品");
        model.addAttribute("productPrice", new BigDecimal("0.01"));
        return "pay";
    }

    /**
     * 发起支付
     */
    @PostMapping("/create")
    @ResponseBody
    public String create(@RequestParam String subject, @RequestParam BigDecimal amount) {
        // 创建支付订单
        PaymentOrder paymentOrder = new PaymentOrder();
        paymentOrder.setOutTradeNo(UUID.randomUUID().toString().replace("-", ""));
        paymentOrder.setSubject(subject);
        paymentOrder.setTotalAmount(amount);
        paymentOrder.setBody("测试商品详情");
        
        // 调用支付宝接口
        String form = alipayService.createPagePay(paymentOrder);
        return form;
    }

    /**
     * 同步回调
     */
    @GetMapping("/return")
    public String returnUrl(@RequestParam Map params, Model model) {
        log.info("支付宝同步回调参数: {}", params);
        
        // 处理同步回调逻辑
        String outTradeNo = params.get("out_trade_no");
        String tradeNo = params.get("trade_no");
        
        // 查询订单状态
        String tradeStatus = alipayService.queryOrder(outTradeNo);
        
        model.addAttribute("outTradeNo", outTradeNo);
        model.addAttribute("tradeNo", tradeNo);
        model.addAttribute("tradeStatus", tradeStatus);
        
        return "success";
    }

    /**
     * 异步通知
     */
    @PostMapping("/notify")
    @ResponseBody
    public String notify(@RequestParam Map params) {
        log.info("支付宝异步通知参数: {}", params);
        
        // 验证通知的真实性
        boolean verified = alipayService.verifyNotify(params.get("notify_id"));
        
        if (verified) {
            // 处理业务逻辑
            String outTradeNo = params.get("out_trade_no");
            String tradeStatus = params.get("trade_status");
            
            // 根据交易状态更新订单
            if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus)) {
                // 更新订单状态为已支付
                log.info("订单 {} 支付成功", outTradeNo);
            }
            
            return "success"; // 成功处理的标识
        } else {
            return "fail"; // 失败处理的标识
        }
    }
}

5. 处理支付回调

支付回调是指当支付完成后,支付宝会向商户服务器发送通知,商户需要根据通知内容来更新订单状态。

5.1 同步回调

同步回调是指用户在支付完成后,支付宝会自动跳转到商户指定的回调页面。

5.1.1 同步回调流程

  1. 用户在支付完成后,支付宝会自动跳转到商户指定的回调页面。
  2. 商户服务器接收到同步回调请求后,需要验证请求的真实性。
  3. 验证通过后,商户服务器需要根据回调参数更新订单状态。

5.1.2 同步回调实现

在上述支付控制器中,我们已经实现了同步回调的处理逻辑。

5.2 异步通知

异步通知是指当支付完成后,支付宝会向商户服务器发送通知,商户需要根据通知内容来更新订单状态。

5.2.1 异步通知流程

  1. 用户在支付完成后,支付宝会向商户服务器发送异步通知。
  2. 商户服务器接收到异步通知后,需要验证通知的真实性。
  3. 验证通过后,商户服务器需要根据通知参数更新订单状态。

5.2.2 异步通知实现

在上述支付控制器中,我们已经实现了异步通知的处理逻辑。

5.3 订单查询

订单查询是指商户可以通过支付宝提供的接口查询订单状态。

5.3.1 订单查询流程

  1. 商户可以通过支付宝提供的接口查询订单状态。
  2. 商户服务器接收到查询请求后,需要根据查询参数返回订单状态。

5.3.2 订单查询实现

在上述支付控制器中,我们已经实现了订单查询的处理逻辑。

6. 测试支付功能

在完成支付功能开发后,我们需要进行测试,以确保支付功能正常工作。

6.1 测试准备

在进行测试之前,我们需要准备一些测试数据,包括测试账号、测试商品等。

6.2 测试流程

测试流程包括以下几个步骤:

  1. 启动项目,确保服务器正常运行。
  2. 使用测试账号进行支付,验证支付流程是否正常。
  3. 检查支付结果,确保支付成功后订单状态更新正确。

6.3 常见问题与调试

在测试过程中,可能会遇到一些常见问题,例如支付失败、订单状态更新不正确等。我们需要根据问题进行调试,确保支付功能正常工作。

7. 生产环境部署

在完成所有测试后,我们可以将项目部署到生产环境,供用户使用。

8. 总结

本教程详细介绍了如何在Spring Boot项目中整合支付宝支付功能,重点关注沙箱环境的配置和使用。通过本教程,您应该能够掌握以下技能: