当面付是支付宝面向线下场景推出的支付产品,商家通过生成收款二维码让用户扫码完成付款,也可以由商家扫用户的付款码收款。对于开发者来说,直接在正式环境调试支付接口风险太大,支付宝提供的沙箱环境就成了学习和联调的最佳选择。本文以Spring Boot项目为例,完整演示从创建沙箱应用到跑通当面付扫码支付的全过程,代码基于alipay-sdk-java官方SDK编写,可以直接复用到真实项目中。

一、准备工作:创建沙箱应用并配置密钥
打开支付宝开放平台的沙箱环境控制台,登录后会自动分配一个沙箱应用,应用里包含APPID、支付宝网关地址、沙箱买家账号等信息。当面付接口属于基础能力,沙箱应用默认已经开通,不需要额外申请。真正需要动手的是密钥部分:下载官方的密钥生成工具,生成一对RSA2密钥,把生成的应用公钥填到沙箱应用的接口加签方式配置里,保存后平台会给出一个支付宝公钥,这个公钥要记下来,后面验签的时候会用到。
这里有一个容易混淆的点需要说明清楚。支付宝的加签模式分为公钥模式和公钥证书模式两种,沙箱环境下用普通的公钥模式就够了,配置更简单。如果是生产环境且涉及需要公钥证书的接口(比如资金类操作),才需要上传证书文件。新手经常把应用私钥和支付宝公钥搞混,简单记:应用私钥用于给请求加签,支付宝公钥用于验证支付宝回调的签名,两者各司其职。
建议把所有配置项集中放在application.yml中,而不是硬编码在Java类里,方便后续从沙箱切换到生产环境时只改配置不改代码:
alipay: app-id: 2021000123456789 gateway-url: https://openapi-sandbox.dl.alipaydev.com/gateway.do app-private-key: 你的应用私钥 alipay-public-key: 支付宝公钥 notify-url: https://ipipp.com/alipay/notify return-url: https://ipipp.com/alipay/return
二、引入SDK并编写配置类
在pom.xml中引入支付宝官方SDK。注意尽量使用较新版本,老版本的SDK在某些新接口上存在兼容性问题:
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.38.200.ALL</version>
</dependency>接着编写一个配置类,把AlipayClient注册为Spring的Bean。AlipayClient是线程安全的,全局只需要一个实例,这也是官方推荐的做法。不要每次请求都new一个client,频繁创建对象不仅浪费资源,还会拖慢接口响应速度:
@Configuration
public class AlipayConfig {
@Value("${alipay.app-id}")
private String appId;
@Value("${alipay.gateway-url}")
private String gatewayUrl;
@Value("${alipay.app-private-key}")
private String appPrivateKey;
@Value("${alipay.alipay-public-key}")
private String alipayPublicKey;
@Bean
public AlipayClient alipayClient() throws AlipayApiException {
return new DefaultAlipayClient(
gatewayUrl,
appId,
appPrivateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
}
}这里的关键参数是签名类型RSA2,也就是SHA256WithRSA。虽然SDK支持老式的RSA,但支付宝已经不推荐使用,新接入的商户必须使用RSA2,否则会直接报签名不合法的错误。
三、实现预下单接口并生成二维码
当面付的核心流程分两步:先调用alipay.trade.precreate接口预下单,拿到一个二维码链接qr_code,前端把这个字符串渲染成二维码图片;用户扫码付款后,支付宝会异步通知商户服务器支付结果。先看预下单接口的实现:
@Service
public class AlipayService {
@Autowired
private AlipayClient alipayClient;
public String preCreate(String outTradeNo, String totalAmount, String subject) {
AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
request.setNotifyUrl(notifyUrl);
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", outTradeNo);
bizContent.put("total_amount", totalAmount);
bizContent.put("subject", subject);
request.setBizContent(bizContent.toString());
try {
AlipayTradePrecreateResponse response = alipayClient.execute(request);
if (response.isSuccess()) {
return response.getQrCode();
}
throw new RuntimeException("预下单失败:" + response.getSubMsg());
} catch (AlipayApiException e) {
throw new RuntimeException("调用支付宝接口异常", e);
}
}
}拿到qr_code之后,前端生成二维码有两种常见方案。一种是用JavaScript库(比如qrcode.js)直接在浏览器端渲染,另一种是后端用Hutool或ZXing生成图片后返回Base64。推荐前端渲染的方式,一是减轻服务端压力,二是二维码内容只是一个URL字符串,没有保密需求。如果一定要后端生成,注意返回给前端时不要把Base64前缀拼错,否则图片无法显示。
订单号的生成也有讲究。out_trade_no必须保证全局唯一,最长64位,只能包含字母、数字和下划线。很多图省事直接用UUID去掉横线,这没问题,但如果你的系统需要根据订单号反查业务信息,建议在订单号中编入日期和业务前缀,比如SHOP20240115001这种格式,排查问题时一目了然。
四、处理异步通知和沙箱联调
支付结果的确认必须依赖异步通知(notify_url),而不是前端的跳转结果。异步通知是支付宝服务器主动POST到商户服务器的请求,可靠性远高于同步回调。处理逻辑的核心是验签加应答:
@PostMapping("/alipay/notify")
@ResponseBody
public String handleNotify(HttpServletRequest request) {
Map<String, String> params = new HashMap<>();
Map<String, String[]> requestParams = request.getParameterMap();
for (String name : requestParams.keySet()) {
String[] values = requestParams.get(name);
StringBuilder valueStr = new StringBuilder();
for (int i = 0; i < values.length; i++) {
valueStr.append(values[i]);
}
params.put(name, valueStr.toString());
}
try {
boolean signVerified = AlipaySignature.rsaCheckV1(
params, alipayPublicKey, "UTF-8", "RSA2");
if (signVerified) {
String tradeStatus = params.get("trade_status");
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
// 这里执行本地订单状态更新、库存扣减等业务逻辑
// 注意做幂等处理,支付宝可能会重复发送通知
}
return "success";
}
return "failure";
} catch (AlipayApiException e) {
return "failure";
}
}有几个细节必须注意。第一,验签失败最常见的原因是密钥配错,一定要用支付宝公钥而不是自己的应用公钥去验签。第二,处理成功后返回字符串success,其他任何返回值支付宝都会视为失败并重试通知,重试间隔逐渐拉长,最多重试若干次。第三,业务逻辑一定要做幂等,因为支付宝在收不到success应答时会重复发送通知,如果不去重,订单可能被处理多次。
联调测试时要用沙箱控制台提供的买家账号,在手机上安装沙箱版支付宝钱包(控制台提供下载二维码),用沙箱买家账号登录后扫码付款。付款前先在控制台查看沙箱账号的初始余额,付款成功后余额会扣减,同时你的服务器会收到异步通知。如果notify_url是本机localhost地址,支付宝服务器是访问不到的,需要用内网穿透工具把本地服务暴露出去,或者先把通知内容打日志手动验证参数。
五、常见问题与排查思路
整合过程中报错是常态,这里总结几个高频问题。报sign_invalid一般是密钥问题,先检查应用私钥是否复制完整,密钥是一整行无换行的长字符串,从工具复制时容易带上空格。报isv.invalid-parameter要看具体子错误码,比如金额格式不对(必须是字符串且保留两位小数)、订单号包含非法字符等。报系统繁忙则可能是沙箱网关地址用错了,沙箱网关域名和正式环境不同,别照抄老教程里的地址,以沙箱控制台显示的为准。
还有一个容易被忽视的问题是订单超时。预下单后如果用户一直不扫码,订单会挂在待支付状态,默认有效期较长。建议在请求中加上timeout_express参数(如5m表示5分钟)控制订单失效时间,同时在自己的系统里做定时任务对账,把超时未支付的本地订单关闭,避免库存被长时间占用。
整体来看,沙箱环境和正式环境的接口完全一致,唯一区别是网关地址、APPID和密钥。只要在Spring Boot项目里把这些差异项抽到配置文件中,从沙箱切换到生产就只是改几行配置的事。建议在上线前再仔细阅读官方文档中关于异步通知重试机制和退款接口的部分,把支付闭环的每个分支都测试到位。
Spring Boot支付宝沙箱当面付修改时间:2026-09-15 08:58:39