参考文档:
https://opendocs.alipay.com/common/02kkv7?pathHash=9a45a6d6

前提准备

  1. 地址注册沙箱支付宝账号,注册成功的话在沙箱账号中会有商家信息和买家信息。
  2. 支付宝沙箱支付回调接口测试的前提:下载natapp工具,实名认证以后进行免费隧道的购买,最后配置映射的本地端口(这个本地端口就是我们供支付宝服务器的回调地址所在的后端服务器运行的端口),使用natapp.exe -authtoken=xxxx运行

后端

  1. 依赖:
 <dependency>
    <groupId>com.alipay.sdk</groupId>
    <artifactId>alipay-easysdk</artifactId>
    <version>2.2.3</version>
</dependency>
<dependency>
    <groupId>com.alipay.sdk</groupId>
    <artifactId>alipay-sdk-java</artifactId>
    <version>4.22.110.ALL</version>
</dependency>
  1. 在application.yml配置文件中新增配置:
alipay:
  appId: xxx
  # 应用私钥
  privateKey: xxx
  # 支付宝公钥
  publicKey: xxx
  notifyUrl: http://p2e4b486.natappfree.cc/payment/fallback
  returnUrl: http://127.0.0.1:8081
  gatewayUrl: https://openapi-sandbox.dl.alipaydev.com/gateway.do
  # 参数返回格式,只支持JSON
  format: JSON
  # 请求使用的编码格式
  charset: UTF-8
  # 生成签名字符串所使用的签名算法类型
  signType: RSA2

  1. 配置信息类:
@Component
@ConfigurationProperties(prefix = "alipay")
@Data
public class AlipayConfig {
    private String appId;       // 应用的APPID

    private String privateKey;  // JAVA语言版本的应用私钥

    private String publicKey;   // 支付宝公钥

    private String gatewayUrl;  // 支付宝网关地址

    private String notifyUrl;   //支付成功后,支付宝服务器的回调地址

    private String returnUrl;   // 支付成功后,支付宝跳转的界面返回路径

    private String format;      // 参数返回格式,只支持JSON

    private String charset;     // 请求使用的编码格式

    private String signType;    // 生成签名字符串所使用的签名算法类型
}

  1. AlipayClient的初始化:
@Configuration
public class AlipayClientConfig {

    /**
     * 初始化AlipayClient实例
     * @param config
     * @return
     */
    @Bean
    public AlipayClient alipayClient(AlipayConfig config){
        return new DefaultAlipayClient(config.getGatewayUrl(),config.getAppId(),config.getPrivateKey(), config.getFormat(),config.getCharset(),config.getPublicKey(),config.getSignType());
    }
}
  1. 接口实现
@RestController
@RequestMapping("/payment")
@Slf4j
public class PaymentController {
    @Resource
    private PaymentService payService;

    @PostMapping("/pay")
    public Result<String> pay(@RequestBody Payment payment) throws Exception {
        String formHtml = payService.pay(payment);
        return Result.success(formHtml);
    }

    @PostMapping("/fallback")
    public String fallback(HttpServletRequest request) {
        log.info("接收到支付宝回调请求");
        String result = "failure";
        try {
            // 获取支付宝POST过来的反馈信息
            Map<String, String> params = new HashMap<>();
            Map<String, String[]> requestParams = request.getParameterMap();
            
            // 打印所有请求参数
            log.info("收到的参数个数: {}", requestParams.size());
            for (String name : requestParams.keySet()) {
                String value = request.getParameter(name);
                params.put(name, value);
                log.info("参数 {} = {}", name, value);
            }
            
            if (params.isEmpty()) {
                log.warn("未收到任何回调参数!");
                return "failure";
            }

            log.info("支付宝回调,签名验证开始...");
            // 调用service层的notify方法处理回调
            result = payService.notify(params);
            log.info("支付宝回调,签名验证结果:{}", result);
            
        } catch (Exception e) {
            log.error("处理支付宝回调时发生异常", e);
            result = "failure";
        }
        
        // 支付宝要求必须返回"success"或"failure"字符串
        log.info("回调处理完成,返回结果:{}", result);
        return result;
    }
}

@Service
@Log4j2
public class PaymentServiceImpl implements PaymentService {
    @Autowired
    private AlipayClient alipayClient;

    @Autowired
    private AlipayConfig alipayConfig;

    public String pay(Payment payment) {
        //******必传参数******
        JSONObject bizContent = new JSONObject();
        //商户订单号,商家自定义,保持唯一性
        bizContent.put("out_trade_no", payment.getOrderId());
        //支付金额,最小值0.01元
        bizContent.put("total_amount", payment.getAmount());
        //订单标题,不可使用特殊符号
        bizContent.put("subject", payment.getSubject());


        if (payment.getDeviceType() == 0) {
            AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest ();

            if(StrUtil.isNotEmpty(alipayConfig.getNotifyUrl())){
                //异步通知支付结果,公网可访问
                request.setNotifyUrl(alipayConfig.getNotifyUrl());
            }
            if(StrUtil.isNotEmpty(alipayConfig.getReturnUrl())){
                //同步跳转地址
                request.setReturnUrl(alipayConfig.getReturnUrl());
            }

            bizContent.put("product_code", "QUICK_WAP_WAY");
            request.setBizContent(bizContent.toString());

            String formHtml = null;
            try {
                formHtml = alipayClient.pageExecute(request).getBody();
            } catch (AlipayApiException e) {
                e.printStackTrace();
            }
            return formHtml;
        } else {
            AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
            if(StrUtil.isNotEmpty(alipayConfig.getNotifyUrl())){
                //异步通知支付结果,公网可访问
                request.setNotifyUrl(alipayConfig.getNotifyUrl());
            }
            if(StrUtil.isNotEmpty(alipayConfig.getReturnUrl())){
                //同步跳转地址
                request.setReturnUrl(alipayConfig.getReturnUrl());
            }

            bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
            
            request.setBizContent(bizContent.toString());

            String formHtml = null;
            try {
                formHtml = alipayClient.pageExecute(request).getBody();
            } catch (AlipayApiException e) {
                e.printStackTrace();
            }
            return formHtml;
        }


    }

    @Override
    public String notify(Map<String, String> params) {
        boolean signVerified = false;
        try {
            //调用SDK验证签名
            signVerified = AlipaySignature.rsaCheckV1(params, alipayConfig.getPublicKey(), alipayConfig.getCharset(), alipayConfig.getSignType());
        } catch (AlipayApiException e) {
            log.error("支付回调签名校验异常!", e);
            e.printStackTrace();
        }
        if (signVerified) {
            // 验签通过
            System.out.println("交易名称: " + params.get("subject"));
            System.out.println("交易状态: " + params.get("trade_status"));
            System.out.println("支付宝交易凭证号: " + params.get("trade_no"));
            System.out.println("商户订单号: " + params.get("out_trade_no"));
            System.out.println("交易金额: " + params.get("total_amount"));
            System.out.println("买家在支付宝唯一id: " + params.get("buyer_id"));
            System.out.println("买家付款时间: " + params.get("gmt_payment"));
            System.out.println("买家付款金额: " + params.get("buyer_pay_amount"));

            // 更新订单状态

            return "success";
        }
        return "fail";
    }
}
  1. 如果配置了接口访问权限,则应该放行/alipay/**
/**
 * Spring Security 配置类
 * 用于配置安全相关的设置,包括密码加密、接口访问权限等
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    /**
     * 配置密码加密器
     * 使用 BCrypt 加密算法
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置 HTTP 安全策略
     * 包括接口访问权限、认证方式等
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
            // 允许访问 Swagger 相关路径
            .antMatchers("/swagger-ui/**", 
                        "/swagger-resources/**", 
                        "/v3/api-docs/**",
                        "/payment/pay",
                        "/payment/fallback",
                        "/alipay/**",
                        "/webjars/**").permitAll()
            .antMatchers("/api/**").permitAll()  // 允许访问所有 API
            .anyRequest().authenticated()
            .and()
            .formLogin().disable()  // 禁用表单登录
            .httpBasic().disable(); // 禁用 HTTP Basic 认证
    }
}