Java实现支付宝回调案例

wen java案例 4

本文目录导读:

Java实现支付宝回调案例

  1. 依赖配置 (pom.xml)
  2. 配置类 (AlipayConfig.java)
  3. application.yml 配置
  4. 回调DTO类
  5. 回调服务接口
  6. 回调服务实现
  7. 控制器
  8. 业务处理示例(订单服务)
  9. 测试控制器
  10. 注意事项
  11. 回调验证流程
  12. 回调测试

我来为您提供一个完整的Java实现支付宝回调的案例,包括验签、处理业务逻辑和返回响应。

依赖配置 (pom.xml)

<dependencies>
    <!-- 支付宝SDK -->
    <dependency>
        <groupId>com.alipay.sdk</groupId>
        <artifactId>alipay-sdk-java</artifactId>
        <version>4.38.0.ALL</version>
    </dependency>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

配置类 (AlipayConfig.java)

package com.example.alipay.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "alipay")
public class AlipayConfig {
    /**
     * 应用ID
     */
    private String appId;
    /**
     * 应用私钥
     */
    private String privateKey;
    /**
     * 支付宝公钥
     */
    private String alipayPublicKey;
    /**
     * 签名方式
     */
    private String signType = "RSA2";
    /**
     * 字符编码
     */
    private String charset = "UTF-8";
    /**
     * 支付宝网关
     */
    private String gatewayUrl = "https://openapi.alipay.com/gateway.do";
    /**
     * 异步通知地址
     */
    private String notifyUrl;
    /**
     * 同步通知地址
     */
    private String returnUrl;
}

application.yml 配置

alipay:
  app-id: your-app-id
  private-key: your-private-key
  alipay-public-key: your-alipay-public-key
  sign-type: RSA2
  charset: UTF-8
  gateway-url: https://openapi.alipay.com/gateway.do
  notify-url: http://your-domain.com/api/alipay/notify
  return-url: http://your-domain.com/api/alipay/return
server:
  port: 8080

回调DTO类

package com.example.alipay.dto;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class AlipayNotifyDTO {
    /** 通知时间 */
    private String notifyTime;
    /** 通知类型 */
    private String notifyType;
    /** 通知校验ID */
    private String notifyId;
    /** 编码格式 */
    private String charset;
    /** 签名类型 */
    private String signType;
    /** 签名 */
    private String sign;
    /** 支付宝交易号 */
    private String tradeNo;
    /** 商户订单号 */
    private String outTradeNo;
    /** 商家业务号 */
    private String outBizNo;
    /** 买家支付宝用户号 */
    private String buyerId;
    /** 买家支付宝账号 */
    private String buyerLogonId;
    /** 卖家支付宝用户号 */
    private String sellerId;
    /** 卖家支付宝账号 */
    private String sellerEmail;
    /** 交易状态 */
    private String tradeStatus;
    /** 订单金额 */
    private BigDecimal totalAmount;
    /** 实收金额 */
    private BigDecimal receiptAmount;
    /** 开票金额 */
    private BigDecimal invoiceAmount;
    /** 付款金额 */
    private BigDecimal buyerPayAmount;
    /** 集分宝金额 */
    private BigDecimal pointAmount;
    /** 总退款金额 */
    private BigDecimal refundFee;
    /** 退款状态 */
    private String refundStatus;
    /** 商品名称 */
    private String subject;
    /** 商品描述 */
    private String body;
    /** 交易创建时间 */
    private String gmtCreate;
    /** 交易付款时间 */
    private String gmtPayment;
    /** 交易退款时间 */
    private String gmtRefund;
    /** 交易结束时间 */
    private String gmtClose;
    /** 支付金额信息 */
    private String fundBillList;
    /** 回传参数 */
    private String passbackParams;
}

回调服务接口

package com.example.alipay.service;
import com.example.alipay.dto.AlipayNotifyDTO;
import javax.servlet.http.HttpServletRequest;
public interface AlipayNotifyService {
    /**
     * 处理异步通知
     * @param request HTTP请求
     * @return 响应字符串
     */
    String handleNotify(HttpServletRequest request);
    /**
     * 处理同步通知
     * @param request HTTP请求
     * @return 重定向地址
     */
    String handleReturn(HttpServletRequest request);
    /**
     * 验签
     * @param params 参数Map
     * @return 是否通过
     */
    boolean verifySign(java.util.Map<String, String> params);
    /**
     * 处理支付成功的业务逻辑
     * @param notifyDTO 通知DTO
     */
    void processPaymentSuccess(AlipayNotifyDTO notifyDTO);
}

回调服务实现

package com.example.alipay.service.impl;
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.internal.util.WebUtils;
import com.example.alipay.config.AlipayConfig;
import com.example.alipay.dto.AlipayNotifyDTO;
import com.example.alipay.service.AlipayNotifyService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Service
@RequiredArgsConstructor
public class AlipayNotifyServiceImpl implements AlipayNotifyService {
    private final AlipayConfig alipayConfig;
    /**
     * 处理异步通知
     * 支付宝会以POST方式请求我们提供的notify_url
     */
    @Override
    public String handleNotify(HttpServletRequest request) {
        log.info("收到支付宝异步通知");
        try {
            // 1. 获取请求参数
            Map<String, String> params = getParamsFromRequest(request);
            // 2. 记录完整的通知参数(便于调试)
            log.info("支付宝异步通知参数: {}", params);
            // 3. 验签
            boolean signVerified = verifySign(params);
            if (!signVerified) {
                log.error("验签失败");
                return "failure";
            }
            // 4. 转换参数为DTO
            AlipayNotifyDTO notifyDTO = convertToDTO(params);
            // 5. 根据交易状态处理业务
            String tradeStatus = notifyDTO.getTradeStatus();
            if ("TRADE_SUCCESS".equals(tradeStatus)) {
                log.info("交易成功,订单号: {}, 支付宝交易号: {}", 
                        notifyDTO.getOutTradeNo(), notifyDTO.getTradeNo());
                // 处理支付成功的业务逻辑
                processPaymentSuccess(notifyDTO);
            } else if ("TRADE_FINISHED".equals(tradeStatus)) {
                log.info("交易完成,订单号: {}", notifyDTO.getOutTradeNo());
                // 处理交易完成的业务逻辑
                processTradeFinished(notifyDTO);
            } else if ("WAIT_BUYER_PAY".equals(tradeStatus)) {
                log.info("交易创建,等待买家付款,订单号: {}", notifyDTO.getOutTradeNo());
            } else if ("TRADE_CLOSED".equals(tradeStatus)) {
                log.info("交易关闭,订单号: {}", notifyDTO.getOutTradeNo());
                // 处理交易关闭的业务逻辑
                processTradeClosed(notifyDTO);
            }
            // 6. 返回成功,支付宝将不再发送通知
            return "success";
        } catch (Exception e) {
            log.error("处理支付宝异步通知异常", e);
            return "failure";
        }
    }
    /**
     * 处理同步通知(页面跳转)
     */
    @Override
    public String handleReturn(HttpServletRequest request) {
        log.info("收到支付宝同步通知(页面跳转)");
        try {
            // 1. 获取请求参数
            Map<String, String> params = getParamsFromRequest(request);
            // 2. 验签
            boolean signVerified = verifySign(params);
            if (!signVerified) {
                log.error("页面跳转验签失败");
                return "redirect:http://localhost:8080/error";
            }
            // 3. 获取关键信息
            String outTradeNo = params.get("out_trade_no");
            String tradeNo = params.get("trade_no");
            String totalAmount = params.get("total_amount");
            log.info("页面跳转成功,订单号: {}, 交易号: {}, 金额: {}", 
                    outTradeNo, tradeNo, totalAmount);
            // 4. 重定向到前端页面,并携带订单信息
            return "redirect:http://localhost:8080/payment/success?orderNo=" + outTradeNo;
        } catch (Exception e) {
            log.error("处理页面跳转异常", e);
            return "redirect:http://localhost:8080/error";
        }
    }
    /**
     * 验签
     */
    @Override
    public boolean verifySign(Map<String, String> params) {
        try {
            if (params.isEmpty()) {
                return false;
            }
            // 注意:使用支付宝公钥验签,不是应用公钥
            return AlipaySignature.rsaCheckV1(
                params,
                alipayConfig.getAlipayPublicKey(),
                alipayConfig.getCharset(),
                alipayConfig.getSignType()
            );
        } catch (AlipayApiException e) {
            log.error("验签异常", e);
            return false;
        }
    }
    /**
     * 处理支付成功业务
     */
    @Override
    public void processPaymentSuccess(AlipayNotifyDTO notifyDTO) {
        // TODO: 实现具体的业务逻辑
        // 1. 根据outTradeNo查询本地订单
        // 2. 校验订单金额是否一致
        // 3. 更新订单状态为已支付
        // 4. 更新支付流水号
        // 5. 处理其他业务(如发货、发送通知等)
        log.info("处理支付成功业务: 订单号={}", notifyDTO.getOutTradeNo());
        // 示例:校验订单金额
        BigDecimal totalAmount = notifyDTO.getTotalAmount();
        log.info("订单金额校验: 支付金额={}", totalAmount);
        // TODO: 在这里编写您的业务逻辑
    }
    /**
     * 处理交易完成业务
     */
    private void processTradeFinished(AlipayNotifyDTO notifyDTO) {
        log.info("处理交易完成业务: 订单号={}", notifyDTO.getOutTradeNo());
        // TODO: 处理交易完成后的业务逻辑
    }
    /**
     * 处理交易关闭业务
     */
    private void processTradeClosed(AlipayNotifyDTO notifyDTO) {
        log.info("处理交易关闭业务: 订单号={}, 原因: {}", 
                notifyDTO.getOutTradeNo(), notifyDTO.getTradeStatus());
        // TODO: 处理交易关闭后的业务逻辑
    }
    /**
     * 获取请求参数
     */
    private Map<String, String> getParamsFromRequest(HttpServletRequest request) {
        Map<String, String> params = new HashMap<>();
        Map<String, String[]> requestParams = request.getParameterMap();
        for (String name : requestParams.keySet()) {
            String[] values = requestParams.get(name);
            String valueStr = "";
            if (values != null && values.length > 0) {
                for (int i = 0; i < values.length; i++) {
                    valueStr += (i == values.length - 1) ? values[i] : values[i] + ",";
                }
            }
            params.put(name, valueStr);
        }
        return params;
    }
    /**
     * 转换为DTO
     */
    private AlipayNotifyDTO convertToDTO(Map<String, String> params) {
        AlipayNotifyDTO dto = new AlipayNotifyDTO();
        dto.setNotifyTime(params.get("notify_time"));
        dto.setNotifyType(params.get("notify_type"));
        dto.setNotifyId(params.get("notify_id"));
        dto.setCharset(params.get("charset"));
        dto.setSignType(params.get("sign_type"));
        dto.setSign(params.get("sign"));
        dto.setTradeNo(params.get("trade_no"));
        dto.setOutTradeNo(params.get("out_trade_no"));
        dto.setOutBizNo(params.get("out_biz_no"));
        dto.setBuyerId(params.get("buyer_id"));
        dto.setBuyerLogonId(params.get("buyer_logon_id"));
        dto.setSellerId(params.get("seller_id"));
        dto.setSellerEmail(params.get("seller_email"));
        dto.setTradeStatus(params.get("trade_status"));
        if (StringUtils.hasText(params.get("total_amount"))) {
            dto.setTotalAmount(new BigDecimal(params.get("total_amount")));
        }
        if (StringUtils.hasText(params.get("receipt_amount"))) {
            dto.setReceiptAmount(new BigDecimal(params.get("receipt_amount")));
        }
        if (StringUtils.hasText(params.get("invoice_amount"))) {
            dto.setInvoiceAmount(new BigDecimal(params.get("invoice_amount")));
        }
        if (StringUtils.hasText(params.get("buyer_pay_amount"))) {
            dto.setBuyerPayAmount(new BigDecimal(params.get("buyer_pay_amount")));
        }
        if (StringUtils.hasText(params.get("point_amount"))) {
            dto.setPointAmount(new BigDecimal(params.get("point_amount")));
        }
        if (StringUtils.hasText(params.get("refund_fee"))) {
            dto.setRefundFee(new BigDecimal(params.get("refund_fee")));
        }
        dto.setRefundStatus(params.get("refund_status"));
        dto.setSubject(params.get("subject"));
        dto.setBody(params.get("body"));
        dto.setGmtCreate(params.get("gmt_create"));
        dto.setGmtPayment(params.get("gmt_payment"));
        dto.setGmtRefund(params.get("gmt_refund"));
        dto.setGmtClose(params.get("gmt_close"));
        dto.setFundBillList(params.get("fund_bill_list"));
        dto.setPassbackParams(params.get("passback_params"));
        return dto;
    }
}

控制器

package com.example.alipay.controller;
import com.example.alipay.service.AlipayNotifyService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.view.RedirectView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@RestController
@RequestMapping("/api/alipay")
@RequiredArgsConstructor
public class AlipayNotifyController {
    private final AlipayNotifyService alipayNotifyService;
    /**
     * 异步通知接口
     * 支付宝服务器会调用此接口,必须返回"success"或"failure"
     */
    @PostMapping("/notify")
    public String notify(HttpServletRequest request) {
        return alipayNotifyService.handleNotify(request);
    }
    /**
     * 同步通知接口(页面跳转)
     */
    @RequestMapping("/return")
    public void returnNotify(HttpServletRequest request, HttpServletResponse response) 
            throws IOException {
        String redirectUrl = alipayNotifyService.handleReturn(request);
        response.sendRedirect(redirectUrl);
    }
}

业务处理示例(订单服务)

package com.example.alipay.service;
import com.example.alipay.dto.AlipayNotifyDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderService {
    /**
     * 处理支付成功后的业务逻辑
     */
    @Transactional(rollbackFor = Exception.class)
    public void handlePaymentSuccess(AlipayNotifyDTO notifyDTO) {
        String outTradeNo = notifyDTO.getOutTradeNo();
        String tradeNo = notifyDTO.getTradeNo();
        BigDecimal totalAmount = notifyDTO.getTotalAmount();
        // 1. 查询订单
        // OrderEntity order = orderMapper.selectByOrderNo(outTradeNo);
        // if (order == null) {
        //     throw new RuntimeException("订单不存在: " + outTradeNo);
        // }
        // 2. 检查订单状态,防止重复处理
        // if ("PAID".equals(order.getStatus())) {
        //     log.info("订单已处理,跳过: {}", outTradeNo);
        //     return;
        // }
        // 3. 校验金额是否一致
        // BigDecimal orderAmount = order.getTotalAmount();
        // if (totalAmount.compareTo(orderAmount) != 0) {
        //     throw new RuntimeException("订单金额不一致: 订单金额=" + orderAmount + ", 支付金额=" + totalAmount);
        // }
        // 4. 更新订单状态为已支付
        // order.setStatus("PAID");
        // order.setTradeNo(tradeNo);
        // order.setPayTime(new Date());
        // orderMapper.updateById(order);
        // 5. 记录支付日志
        // PaymentLog paymentLog = new PaymentLog();
        // paymentLog.setOrderNo(outTradeNo);
        // paymentLog.setTradeNo(tradeNo);
        // paymentLog.setAmount(totalAmount);
        // paymentLog.setPayTime(new Date());
        // paymentLogMapper.insert(paymentLog);
        log.info("订单支付成功处理完成: 订单号={}, 交易号={}, 金额={}", 
                outTradeNo, tradeNo, totalAmount);
    }
}

测试控制器

package com.example.alipay.controller;
import com.example.alipay.config.AlipayConfig;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
@RequiredArgsConstructor
public class TestController {
    private final AlipayConfig alipayConfig;
    /**
     * 测试接口 - 模拟接收通知
     */
    @PostMapping("/test/notify")
    public String testNotify(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader reader = request.getReader()) {
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (Exception e) {
            log.error("读取请求失败", e);
        }
        log.info("测试通知内容: {}", sb.toString());
        return "success";
    }
    /**
     * 获取配置信息(仅测试用)
     */
    @GetMapping("/test/config")
    public Map<String, String> getConfig() {
        Map<String, String> config = new HashMap<>();
        config.put("appId", alipayConfig.getAppId());
        config.put("gatewayUrl", alipayConfig.getGatewayUrl());
        config.put("notifyUrl", alipayConfig.getNotifyUrl());
        config.put("returnUrl", alipayConfig.getReturnUrl());
        return config;
    }
}

注意事项

1 安全建议

// 在正式环境中,建议:
// 1. 使用HTTPS协议
// 2. 限制IP白名单(支付宝通知服务器的IP)
// 3. 对敏感信息进行加密存储
// 4. 添加幂等性处理(防止重复通知)

2 日志记录

// 建议记录完整的通知参数用于排查问题
log.info("支付宝通知参数: {}", JSON.toJSONString(params));

3 异常处理

// 处理失败时返回"failure",支付宝会重试
// 处理成功时返回"success",支付宝不会重试
// 建议:处理失败抛出异常,返回"failure"

回调验证流程

  1. 验签:使用支付宝公钥对通知参数进行签名验证
  2. 订单匹配:根据 out_trade_no 查询本地订单
  3. 金额校验:核对通知中的 total_amount 是否与订单金额一致
  4. 状态检查:防止重复通知导致重复处理
  5. 业务处理:更新订单状态、处理其他业务逻辑
  6. 返回响应:返回 successfailure

回调测试

在沙箱环境测试时,可以使用支付宝提供的模拟工具,或者使用Postman模拟回调请求。

这个案例提供了一个完整的支付宝回调处理流程,您可以根据实际业务需求进行修改和扩展。

抱歉,评论功能暂时关闭!