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)

相关推荐
抹香鲸之海5 分钟前
Easyexcel 多级横向合并表头
java·开发语言·windows
superman超哥6 分钟前
Rust 生命周期子类型:类型系统中的偏序关系
开发语言·后端·rust·编程语言·rust生命周期·偏序关系
雒珣7 分钟前
qt界面和图片疯狂变大的bug问题
开发语言·qt·bug
烟沙九洲9 分钟前
JVM 堆内存分代
java·jvm
BD_Marathon9 分钟前
SpringMVC——bean加载控制
java·开发语言·数据库
課代表10 分钟前
Python 数据可视化:从单变量到多变量
开发语言·python·信息可视化·数据分析·变量·时间序列·文本分析
leiming612 分钟前
c++qt开发第三天 摄像头采集视频
开发语言·c++·qt
悟空码字16 分钟前
SpringBoot + Redis分布式锁深度剖析,性能暴涨的秘密全在这里
java·spring boot·后端
奋进的芋圆17 分钟前
Spring Boot中实现定时任务
java·spring boot·后端
海奥华218 分钟前
Golang Channel 原理深度解析
服务器·开发语言·网络·数据结构·算法·golang