简介:这份资源面向需要在Java应用中接入支付宝支付能力的开发者,尤其适合电商、O2O场景下希望快速跑通扫码支付流程的中级Java工程师。项目围绕支付宝SDK展开,涵盖扫码支付、订单处理、异步回调、appid与密钥配置、前端二维码展示页面以及API调用与安全防护等核心环节,并附有可参考的示例代码,帮助理解支付系统的整体架构与前后端交互方式。压缩包共124个文件,约27.64MB,以jar依赖库、java源码、class编译文件、xml配置、js脚本与properties参数文件为主,另含html页面与css样式,结构上兼顾可运行与可学习。目前已有2483人学习下载。通过这份资源,读者可以对照示例掌握发起支付请求、处理支付结果回调、更新订单状态等关键步骤,并借鉴其中的安全实践与测试调试思路,为自建支付模块提供可复用的参考。
1. Java 集成支付宝扫码支付:从沙箱到回调,一条能跑通的最小链路
很多 Java 后端第一次接支付宝扫码支付,卡住的地方往往不是写代码,而是「沙箱环境跑通了,换正式环境就 400」「回调地址配了但一直收不到通知」「二维码生成了,用户扫完订单状态还是待支付」。这篇笔记就围绕 Java 集成支付宝扫码支付这条主线,把从依赖引入、密钥配置、下单、生成二维码、异步回调验签到对账的完整链路拆开讲,同时把标题里提到的刷脸支付官方奖励政策单独拎出来说清楚——它和扫码支付在代码层是两套东西,但很多服务商场景下会一起用。
适合谁看:正在做 Spring Boot 项目要接支付宝当面付(扫码支付)的后端;做服务商/ISV 需要理解刷脸支付奖励政策怎么落到系统里的技术负责人;以及被「支付宝回调」反复折磨、想搞清楚验签和幂等到底怎么写的人。下面所有代码基于支付宝官方 Java SDK,沙箱和正式环境只差配置,逻辑完全一致。
2. 扫码支付的技术选型:当面付、预下单和密钥体系怎么定
2.1 为什么扫码支付优先选当面付(F2F)而不是网页支付
支付宝的支付产品线里,和「扫码」沾边的常见有三种:电脑网站支付、手机网站支付、当面付。前两个是跳转到支付宝收银台,用户扫码或者登录付款,资金流和交互都在支付宝页面完成;当面付(alipay.trade.precreate)是商户自己生成二维码,用户用支付宝扫,本质是「线下扫码」的线上化。
选当面付的理由很直接:二维码由你的系统生成,你可以控制二维码的展示位置、有效期、订单绑定关系,用户扫码后支付宝回调你的服务端,整个链路你都能埋点。电脑网站支付虽然也能出二维码,但它是支付宝页面渲染的,你拿不到二维码图片本身,做不了自定义收银台。
代价是当面付需要签约,个人开发者用沙箱练手没问题,正式上线要有营业执照和对公账户。这也是热词里「springboot 支付宝转对公账户签约」被频繁搜的原因——签约是绕不过去的前置条件。
2.2 密钥模式:公钥证书 vs 普通公钥,新手先用哪个
支付宝开放平台有两种签名模式:普通公钥模式和公钥证书模式。普通公钥模式配置简单,一个应用私钥 + 支付宝公钥就能跑;公钥证书模式需要下载证书文件(appCertPublicKey.crt、alipayCertPublicKey_RSA2.crt、alipayRootCert.crt),安全性更高,适合对安全要求高的生产环境。
我的建议是:沙箱和初期联调用普通公钥模式,快速验证链路;正式上线如果平台要求或者团队有安全规范,再切证书模式。切换时主要改的是AlipayConfig里的证书路径和AlipayClient的初始化方式,业务代码基本不动。
| 对比项 | 普通公钥模式 | 公钥证书模式 |
|---|---|---|
| 配置复杂度 | 低,两个密钥字符串 | 高,需管理三个证书文件 |
| 密钥轮换 | 手动替换 | 支持证书轮换 |
| 适用场景 | 沙箱、中小项目 | 生产、服务商、高安全要求 |
| SDK 初始化 | AlipayClient直接传公私钥 | 需传CertAlipayRequest |
2.3 依赖引入与 AlipayClient 的最小初始化
先引入官方 SDK。Maven 里加这一条即可,版本用当前稳定版,不要用太老的版本,老版本对证书模式支持不全。
<dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>4.38.0.ALL</version> </dependency>然后是配置类。把 appId、应用私钥、支付宝公钥、网关地址、回调地址抽到application.yml,不要硬编码在代码里,否则换环境要改代码。
@Configuration public class AlipayConfig { @Value("${alipay.app-id}") private String appId; @Value("${alipay.private-key}") private String privateKey; @Value("${alipay.alipay-public-key}") private String alipayPublicKey; @Value("${alipay.gateway-url}") private String gatewayUrl; @Value("${alipay.notify-url}") private String notifyUrl; @Bean public AlipayClient alipayClient() { // 普通公钥模式:直接传应用私钥和支付宝公钥 return new DefaultAlipayClient( gatewayUrl, appId, privateKey, "json", "UTF-8", alipayPublicKey, "RSA2" ); } }这里几个参数必须说清楚:format固定json,charset固定UTF-8,signType用RSA2(RSA1 已不推荐)。gatewayUrl沙箱是https://openapi.alipaydev.com/gateway.do,正式是https://openapi.alipay.com/gateway.do。notifyUrl必须是公网可访问的 HTTPS 地址,本地开发用内网穿透工具映射一个临时域名,否则回调永远收不到。
注意:应用私钥是 PKCS8 格式,不是 PKCS1。如果你从密钥工具生成的是 PKCS1,需要转换,否则初始化时会报
Invalid private key。
3. 下单与二维码生成:precreate 接口的完整调用与参数拆解
3.1 构造 AlipayTradePrecreateRequest 的必填与选填参数
当面付预下单的核心接口是alipay.trade.precreate。必填参数只有out_trade_no(商户订单号)和total_amount(金额,单位元,字符串),subject是商品标题,虽然文档标为选填,但强烈建议填,否则用户在支付宝账单里看到的是一串订单号,体验很差。
@Service public class AlipayScanPayService { @Autowired private AlipayClient alipayClient; @Value("${alipay.notify-url}") private String notifyUrl; public String precreate(String outTradeNo, String subject, String amount) throws AlipayApiException { AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest(); request.setNotifyUrl(notifyUrl); AlipayTradePrecreateModel model = new AlipayTradePrecreateModel(); model.setOutTradeNo(outTradeNo); model.setTotalAmount(amount); model.setSubject(subject); // 二维码有效期,超时后用户扫码会提示订单已关闭 model.setTimeoutExpress("30m"); // 指定收款方,服务商模式下必填 // model.setSellerId("2088xxxx"); request.setBizModel(model); AlipayTradePrecreateResponse response = alipayClient.execute(request); if (!response.isSuccess()) { throw new RuntimeException("预下单失败:" + response.getSubMsg()); } // qr_code 就是二维码内容,前端用它生成图片 return response.getQrCode(); } }timeoutExpress建议设 30 分钟到 2 小时,太短用户还没扫就过期,太长订单会一直挂着占用库存。out_trade_no必须全局唯一,重复调用同一个订单号,支付宝会返回「订单已存在」,这是幂等的基础。
3.2 二维码内容怎么变成图片:前端生成还是后端生成
qr_code返回的是一串 URL,不是图片。两种处理方式:后端用 ZXing 生成图片返回给前端,或者前端用 qrcode.js 自己渲染。我一般选后端生成,因为二维码里可能带商户 logo、有效期水印,后端控制更灵活。
public byte[] generateQrImage(String qrCode, int width, int height) throws Exception { Map<EncodeHintType, Object> hints = new HashMap<>(); hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); hints.put(EncodeHintType.MARGIN, 1); BitMatrix matrix = new MultiFormatWriter().encode(qrCode, BarcodeFormat.QR_CODE, width, height, hints); ByteArrayOutputStream out = new ByteArrayOutputStream(); MatrixToImageWriter.writeToStream(matrix, "PNG", out); return out.toByteArray(); }MARGIN设 1 是为了减少白边,默认是 4,二维码会显得很小。宽高建议 300x300 以上,太小用户扫不出来。生成后直接以image/png返回,前端<img src="/pay/qr?orderNo=xxx">即可。
3.3 订单状态查询:主动轮询和被动回调怎么配合
用户扫码付款后,支付宝会异步回调你的notifyUrl,但回调可能延迟、可能丢失。所以生产环境必须同时做主动查询兜底。alipay.trade.query用out_trade_no或trade_no查,返回TRADE_SUCCESS才算支付成功。
public boolean queryOrder(String outTradeNo) throws AlipayApiException { AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); AlipayTradeQueryModel model = new AlipayTradeQueryModel(); model.setOutTradeNo(outTradeNo); request.setBizModel(model); AlipayTradeQueryResponse response = alipayClient.execute(request); if (response.isSuccess() && "TRADE_SUCCESS".equals(response.getTradeStatus())) { return true; } return false; }轮询策略:用户扫码后前端每 3 秒查一次自己的订单状态,后端查支付宝,查到成功就更新本地订单。同时回调接口收到通知也更新。两边都更新,用数据库唯一约束或者状态机保证幂等。
4. 异步回调验签与幂等:支付宝回调最容易翻车的三个地方
4.1 回调参数验签:为什么必须用 SDK 的 verify
支付宝回调会 POST 一堆参数到你的notifyUrl,包括sign、sign_type、trade_status、out_trade_no等。验签必须用 SDK 提供的AlipaySignature.rsaCheckV1,不要自己拼字符串验签,参数顺序、编码、空值处理任何一个细节错了都会验签失败。
@PostMapping("/alipay/notify") public String notify(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(i == values.length - 1 ? values[i] : values[i] + ","); } params.put(name, valueStr.toString()); } try { boolean signVerified = AlipaySignature.rsaCheckV1( params, alipayPublicKey, "UTF-8", "RSA2"); if (!signVerified) { return "failure"; } // 验签通过后再处理业务 String tradeStatus = params.get("trade_status"); String outTradeNo = params.get("out_trade_no"); if ("TRADE_SUCCESS".equals(tradeStatus)) { // 幂等更新订单 orderService.markPaid(outTradeNo, params.get("trade_no")); } return "success"; } catch (AlipayApiException e) { return "failure"; } }返回给支付宝的必须是纯字符串success,不能是 JSON,不能带引号。返回其他任何内容,支付宝会认为通知失败,按 25 分钟、2 小时、4 小时这样的间隔重试,最多 8 次。
4.2 幂等处理:同一笔订单收到多次回调怎么办
支付宝的回调是「至少一次」语义,同一笔订单可能收到多次通知。如果你的markPaid里直接update order set status = 'paid',第二次回调会重复加积分、重复发货。正确做法是用订单状态做乐观锁。
UPDATE t_order SET status = 'PAID', trade_no = #{tradeNo}, pay_time = NOW() WHERE out_trade_no = #{outTradeNo} AND status = 'UNPAID';判断affectedRows,等于 1 说明是第一次处理,继续做后续业务;等于 0 说明已经处理过,直接返回 success。这样即使回调重复,业务也只执行一次。
4.3 回调地址配置:为什么沙箱能收到正式收不到
沙箱环境的回调地址可以配http://本地地址,但正式环境必须是https://公网域名,且不能带端口(80/443 除外)。常见翻车场景:本地用localhost:8080测试通过,上线后配了http://域名,支付宝直接不回调。还有一种是域名解析到了内网 IP,支付宝服务器访问不到。
排查方法:在回调接口第一行打日志,看有没有请求进来。如果日志完全没有,说明请求没到你的服务器,检查域名、HTTPS 证书、防火墙、Nginx 转发。如果有请求但验签失败,检查支付宝公钥是不是复制错了,或者是不是把应用公钥当成了支付宝公钥。
5. 刷脸支付官方奖励政策:技术侧要落哪些数据
5.1 奖励政策的本质:设备激活 + 交易笔数
刷脸支付和扫码支付在代码层是两套接口。刷脸走的是alipay.trade.pay配合人脸识别设备(蜻蜓、青蛙等),商户需要先购买或租赁设备,然后在支付宝开放平台绑定设备 SN。官方奖励政策的核心逻辑是:设备激活后,在一定周期内达到规定的交易笔数和金额,支付宝返还设备款或者发放补贴。
技术侧要做的不是「申请奖励」,而是把交易数据准确上报,让支付宝能统计到。关键数据包括:设备 SN、商户 PID、每笔刷脸交易的out_trade_no和trade_no、交易时间、金额。这些数据在调用alipay.trade.pay时由支付宝自动记录,但你的系统要能按设备维度聚合查询,方便对账。
5.2 服务商模式下的数据隔离与分账
如果你是服务商(ISV),下面挂着多个商户,奖励政策是按商户和设备算的。系统设计时要在订单表里加seller_id(商户 PID)和device_sn字段,回调时从支付宝通知里取seller_id存下来。分账场景还要用alipay.trade.order.settle或者分账接口,把服务商佣金和商户货款分开。
热词里「支付宝分账」被搜得多,是因为很多服务商场景下,奖励政策和分账是绑定的——支付宝把奖励打给服务商,服务商再按比例分给商户。这部分逻辑要在你的结算系统里实现,支付宝只负责把奖励发放到服务商账户。
5.3 扫码支付和刷脸支付在同一个系统里怎么共存
实际项目里,一个收银台往往同时支持扫码和刷脸。设计上建议把支付方式抽象成策略模式:PayStrategy接口定义precreate、query、refund,ScanPayStrategy和FacePayStrategy分别实现。订单表加pay_channel字段区分。回调接口可以共用一个入口,根据trade_type或者商户配置路由到不同处理逻辑。
这样做的价值是:奖励政策只影响刷脸那条链路的数据上报,扫码链路完全不受影响。新增支付方式时也不用改核心订单逻辑。
6. 联调排错与上线前检查:几个能省半天时间的技巧
6.1 沙箱账号和买家账号的坑
沙箱环境要用沙箱版支付宝 APP 登录沙箱买家账号,不能用真实支付宝扫沙箱二维码。很多人卡在这里:二维码生成了,用真实支付宝扫,提示「订单不存在」。沙箱买家账号在开放平台「沙箱环境」里能查到,密码是固定的111111。沙箱版 APP 在开放平台下载,安卓和 iOS 都有。
6.2 日志里必须打的几个字段
联调阶段,在预下单和回调两个地方打日志,字段包括:out_trade_no、trade_no、trade_status、total_amount、seller_id、sign(前 20 位即可)。这样出问题时能快速定位是下单参数错了、还是回调没来、还是验签失败。生产环境注意脱敏,sign和密钥不要打全。
6.3 上线前的检查清单
| 检查项 | 沙箱 | 正式 |
|---|---|---|
| 网关地址 | openapi.alipaydev.com | openapi.alipay.com |
| 回调地址 | 可 http 本地 | 必须 https 公网 |
| 密钥 | 沙箱密钥 | 正式密钥,勿混用 |
| 签约产品 | 沙箱默认开通 | 需正式签约当面付 |
| 买家账号 | 沙箱买家 | 真实用户 |
最后说一个我自己的习惯:每次接新的支付渠道,先写一个「最小闭环」——下单、生成二维码、手动触发回调、更新订单,四个步骤跑通再往上加业务逻辑。支付这东西,链路通了什么都好说,链路不通写再多业务代码都是白搭。刷脸支付的奖励政策也一样,先把alipay.trade.pay调通,再研究设备绑定和数据上报,顺序反了会浪费很多时间。希望帮到你。
本文还有配套的精品资源,点击获取