Java接入支付宝沙箱支付教程

文章目录

前期准备

  • 进入支付宝沙箱控制台: 沙箱控制台
  • 界面如图
    我们主要注意公钥模式 我们的密钥都要在里面查看

导入依赖

复制代码
        <!-- 支付宝SDK -->
        <dependency>
            <groupId>com.alipay.sdk</groupId>
            <artifactId>alipay-sdk-java</artifactId>
            <version>4.40.26.ALL</version>
        </dependency>

配置文件

复制代码
alipay:
  app-id: 你的appid
  private-key: 你公钥模式中的应用私钥(JAVA语言)
  alipay-public-key: 支付宝公钥
  server-url: https://openapi-sandbox.dl.alipaydev.com/gateway.do
  charset: UTF-8
  format: json
  sign-type: RSA2
  notify-url: notify请求的ip地址端口/owner/property/fee/alipay/notify 公网能访问
  return-url: return请求的ip地址端口/owner/property/fee/alipay/return 

配置类

复制代码
/**
 * 支付宝配置工具类(封装配置,提供AlipayClient实例)
 */
@Data
@Component
public class AlipayConfigUtil {

    // 从配置文件注入参数
    @Value("${alipay.app-id}")
    private String appId;

    @Value("${alipay.private-key}")
    private String privateKey;

    @Value("${alipay.alipay-public-key}")
    private String alipayPublicKey;

    @Value("${alipay.server-url}")
    private String serverUrl;

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

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

    @Value("${alipay.sign-type}")
    private String signType;

    /**
     * 获取AlipayClient实例(支付宝SDK核心客户端,用于调用支付接口)
     * 单例复用,避免重复创建
     */
    public AlipayClient getAlipayClient() throws AlipayApiException {
        // 构建AlipayConfig(对应main方法中的getAlipayConfig)
        AlipayConfig alipayConfig = new AlipayConfig();
        alipayConfig.setServerUrl(serverUrl);
        alipayConfig.setAppId(appId);
        alipayConfig.setPrivateKey(privateKey);
        alipayConfig.setFormat(format);
        alipayConfig.setAlipayPublicKey(alipayPublicKey);
        alipayConfig.setCharset(charset);
        alipayConfig.setSignType(signType);

        // 返回DefaultAlipayClient实例(核心客户端)
        return new DefaultAlipayClient(alipayConfig);
    }
}

开始接入

notify示例代码

controller

复制代码
/**
 * 物业费支付宝支付Controller
 */
@Slf4j
@RestController
@RequestMapping("/owner/property/fee/alipay")
public class AlipayPayController {

    @Autowired
    private AlipayPayService alipayPayService;

    @Autowired
    AlipayConfigUtil alipayConfigUtil;
    /**
     * 生成支付表单(前端点击「立即缴费」调用此接口)
     */
    @GetMapping("/createPayForm/{feeId}")
    public Result<String> createPayForm(@PathVariable Long feeId) {
        return alipayPayService.createPropertyFeePayForm(feeId);
    }

    /**
     * 处理支付宝支付回调(支付宝主动调用,无需前端干预)
     */
    @PostMapping("/notify")
    public String handleAlipayNotify(HttpServletRequest request) {
        try {
            // 解析支付宝回调的参数(封装为Map)
            Map<String, String> params = new HashMap<>();
            Enumeration<String> parameterNames = request.getParameterNames();
            while (parameterNames.hasMoreElements()) {
                String name = parameterNames.nextElement();
                String value = request.getParameter(name);
                params.put(name, value);
            }

            log.info("支付宝回调参数:{}", params);

            // 调用服务层处理回调
            return alipayPayService.handleAlipayNotify(params);
        } catch (Exception e) {
            log.error("处理支付宝回调异常", e);
            return "fail";
        }
    }
}

在下面代码中实现handleAlipayNotify

impl

本方法具体的数据库更新由消息队列实现,不想使用消息队列可以在本方法中进行更新

示例方法代码

复制代码
   /**
 * 支付宝支付服务实现类
 */
@Service
@Slf4j
public class AlipayPayServiceImpl implements AlipayPayService {

    // 注入支付宝配置工具类
    @Autowired
    private AlipayConfigUtil alipayConfigUtil;

    // 注入账单Mapper
    @Autowired
    private PropertyFeeMapper propertyFeeMapper;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 注入支付宝回调地址
    @Value("${alipay.notify-url}")
    private String notifyUrl;

    @Value("${alipay.return-url}")
    private String returnUrl;
    @Override
    public Result<String> createPropertyFeePayForm(Long feeId) {
        try {
            // 1. 查询待支付的物业费账单(校验账单有效性)
            PropertyFee propertyFee = propertyFeeMapper.selectById(feeId);
            if (propertyFee == null) {
                return Result.error("账单不存在");
            }
            if (propertyFee.getStatus() == 1) {
                return Result.error("该账单已支付,无需重复下单");
            }
            if (propertyFee.getDeleteFlag() == 1) {
                return Result.error("该账单已被删除");
            }

            // 2. 获取AlipayClient实例
            AlipayClient alipayClient = alipayConfigUtil.getAlipayClient();

            // 3. 构建支付请求 - 使用电脑网站支付请求类
            AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
            // 设置异步回调地址(支付宝服务器主动通知)
            request.setNotifyUrl(notifyUrl);
            // 设置同步回调地址(支付完成后页面跳转)- 电脑网站支付需要这个
            request.setReturnUrl(returnUrl); // 需要定义returnUrl变量

            // 4. 构建业务参数
            String outTradeNo = "PROPERTY_FEE_" + feeId + "_" + System.currentTimeMillis();
            String subject = propertyFee.getFeeYear() + "年" + propertyFee.getFeeMonth() + "月费用";

            // 格式化金额
            DecimalFormat df = new DecimalFormat("0.00");
            String totalAmount = df.format(propertyFee.getActualAmount());

            JSONObject bizContent = new JSONObject();
            bizContent.put("out_trade_no", outTradeNo);
            bizContent.put("total_amount", totalAmount);
            bizContent.put("subject", subject);
            bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
            bizContent.put("body", "费用");
            bizContent.put("timeout_express", "30m");

            request.setBizContent(bizContent.toJSONString());

            // 5. 调用支付宝接口
            AlipayTradePagePayResponse response = alipayClient.pageExecute(request, "GET");

            // 6. 处理响应结果
            if (response.isSuccess()) {
                String payForm = response.getBody();
                // 将商户订单号存入账单
                updatePropertyFeeOutTradeNo(feeId, outTradeNo);
                return Result.success(payForm, "支付表单生成成功");
            } else {
                return Result.error("支付表单生成失败:" + response.getMsg());
            }
        } catch (AlipayApiException e) {
            return Result.error("支付宝接口异常:" + e.getMessage());
        } catch (Exception e) {
            return Result.error("系统异常,生成支付表单失败");
        }
    }

    /**
     * 处理支付宝支付回调(解耦后:仅验签+发送消息,快速返回结果)
     */
    @Override
// 注:回调服务无需事务(仅发送消息,消息持久化由RabbitMQ保证)
    public String handleAlipayNotify(Map<String, String> params) {
        String alipayPublicKey = alipayConfigUtil.getAlipayPublicKey();

        try {
            // 1. 日志记录接收到的参数
            log.info("=== 开始处理支付宝回调(解耦版)===");
            log.info("回调参数数量:{}", params.size());
            log.info("sign参数:{}", params.get("sign"));
            log.info("sign_type参数:{}", params.get("sign_type"));

            // 2. 验签(核心:仅保留必要的校验,确保消息合法性)
            boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayPublicKey, "UTF-8", "RSA2");
            log.info("验签结果:{}", signVerified ? "成功" : "失败");

            if (!signVerified) {
                log.error("支付宝回调签名验证失败");
                return "fail";
            }

            // 3. 解析并校验核心回调参数(仅做合法性校验,不操作数据库)
            String outTradeNo = params.get("out_trade_no");
            String tradeNo = params.get("trade_no");
            String tradeStatus = params.get("trade_status");
            String totalAmount = params.get("total_amount");
            String appId = params.get("app_id");
            String sellerId = params.get("seller_id");

            log.info("解析参数:outTradeNo={}, tradeNo={}, tradeStatus={}, totalAmount={}",
                    outTradeNo, tradeNo, tradeStatus, totalAmount);

            // 4. 校验核心条件(交易状态+appId+必要参数非空)
            if (!"TRADE_SUCCESS".equals(tradeStatus)) {
                log.info("交易状态不是TRADE_SUCCESS:{}", tradeStatus);
                return "success"; // 非支付成功状态,返回success避免重复通知
            }
            String configAppId = alipayConfigUtil.getAppId();
            if (!configAppId.equals(appId) || StringUtils.isEmpty(outTradeNo) || StringUtils.isEmpty(totalAmount)) {
                log.error("参数不合法:appId不匹配或核心参数为空");
                return "fail";
            }

            // 5. 构建支付成功消息(封装需要的所有数据,避免后续查询支付宝)
            AlipayPaySuccessMessage payMessage = new AlipayPaySuccessMessage();
            payMessage.setOutTradeNo(outTradeNo);
            payMessage.setTradeNo(tradeNo);
            payMessage.setTotalAmount(totalAmount);
            payMessage.setPayTime(LocalDateTime.now());
            payMessage.setPayMethod("支付宝支付");

            // 6. 发送消息到RabbitMQ(同步发送,快速返回;开启消息持久化)
            rabbitTemplate.convertAndSend(
                    "alipay.pay.success.exchange", // 交换机名称
                    "alipay.pay.success.key",      // 路由键
                    payMessage,                    // 消息体
                    message -> {
                        // 消息持久化配置(确保RabbitMQ重启后消息不丢失)
                        message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                        return message;
                    }
            );
            log.info("支付宝支付成功消息发送至RabbitMQ:outTradeNo={}", outTradeNo);

            // 7. 快速返回success给支付宝,避免超时重试
            return "success";

        } catch (AlipayApiException e) {
            log.error("支付宝验签异常:{}", e.getMessage(), e);
            return "fail";
        } catch (Exception e) {
            log.error("处理支付宝回调并发送消息异常:{}", e.getMessage(), e);
            return "fail";
        }
    }

注意notify返回的结果必须是"success"或者"fail"字符串

return示例代码

controller层

复制代码
/**
 * 支付宝同步回调接口(处理重定向跳转)
 * 注解使用 @Controller,支持返回 RedirectView 实现页面跳转
 */
@Controller // 核心修正:替换 @RestController 为 @Controller
public class AlipayReturnController {

    @Autowired
    PropertyFeeService propertyFeeService;

    /**
     * 接收支付宝同步回调,处理逻辑后重定向回业主页面
     */
    @GetMapping("/owner/property/fee/alipay/return")
    public RedirectView handleAlipayReturn(HttpServletRequest request) {
        try {
            // 1. 解析支付宝同步回调参数(验证订单、更新订单状态等核心逻辑)
            Map<String, String> params = new HashMap<>();
            request.getParameterMap().forEach((key, values) -> {
                if (values != null && values.length > 0) {
                    params.put(key, values[0]);
                }
            });
            System.out.println("支付宝同步回调接收参数:" + params);


            String targetOwnerPage = "你支付完成后重定向跳转的网址"; 
            /

            // 4. 构建重定向视图(302 重定向)
            RedirectView redirectView = new RedirectView(targetOwnerPage);
            redirectView.setStatusCode(HttpStatus.FOUND); // 明确设置 302 重定向
            redirectView.setExposeModelAttributes(false); // 不暴露模型属性,避免拼接多余参数
            return redirectView;

        } catch (Exception e) {
            e.printStackTrace();
            // 异常时,重定向到支付失败页面
            return new RedirectView("你支付失败重定向跳转的网址");
        }
    }
}

可能出现的问题

数据库表的问题

你的相关表中要携带 out_trade_no,trade_node等字段

生成支付宝表单出错

检查你的createPropertyFeePayForm方法

移动端不能生成表单?

复制代码
            bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");

这里的value是电脑网站支付,如果你想使用其他支付方式请使用其他产品码作为value

异步同调失败(notify接口响应失败)

是否能被公网访问?

支付发起的异调是支付宝服务器发起的,所以你要使用能被公网访问的网址

使用公网网址还是不行?

因为异调支付对于SSL证书有要求,检查你的公网地址是否具有相关资格(推荐ngrok 免费可用 不推荐花生壳 因为发送不了notify)

相关推荐
Scott9999HH3 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔3 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社4 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海4 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖5 小时前
JDK 26 新特性详解
java·开发语言
马优晨5 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区7 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大8 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai8 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说8 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json