
简介面向Java开发者的支付宝扫码支付集成案例完整演示了从支付宝SDK接入、zfbinfo.properties参数配置到生成二维码、处理支付回调的业务闭环。压缩包共124个文件包含37个jar依赖、23个java源码、17个class编译文件以及xml配置、properties参数、js/html/css前端展示页面等整体27.64MB目录结构适合按模块对照学习。项目涵盖appid与密钥管理、API调用、数据加密与安全防护、异常场景测试并通过AlipayFaceToFaceController等类展示当面付核心逻辑前端页面可动态生成二维码并实时反馈支付状态后端则处理支付宝异步回调、更新订单状态。已有2483人学习下载适合电商、O2O等场景中需要快速落地扫码支付功能的Java开发者既能作为起步模板也可用于理解支付系统前后端交互与回调同步机制。1. Java集成支付宝扫码支付项目为什么这套链路值得从头搭一遍在线下门店、自助售卖机和PC收银台场景里Java集成支付宝扫码支付几乎是每个后端团队的必经之路用户打开支付宝扫一扫钱进商户账户服务器收到异步通知再发货。但很多教程只教你把二维码“跑出来”漏掉了一个关键认知——扫码支付和官方刷脸支付的奖励补贴走的是同一套商户接入体系同一个AppID、同一套RSA2密钥、同一条支付宝回调链路。你把这套Java扫码支付项目搭扎实后续接刷脸设备、参与官方奖励政策里的设备激励和交易返利本质上只是把“用户扫你的码”换成“用户刷他的脸”后端骨架完全不用推翻。这篇文章就按这个顺序写先通支付宝开放平台的准备环节再给一套能直接跑的Spring Boot扫码支付代码把回调验签和订单状态机的坑逐个排掉最后拆解刷脸支付官方奖励政策的真实结构。适合谁想在一个月内完成支付能力从零到上线的Java团队也适合正在评估刷脸设备投入的个体商户和服务商。2. 支付宝开放平台筹备AppID、密钥和签约是第一个技术门槛写代码之前有一堆账号和密钥层面的活儿要先干完。很多新手第一次接支付宝代码写得飞快结果卡在“签约没通过”或者“密钥不匹配”上白白浪费一整天。这一章先把前置条件讲透。2.1 创建应用与签约“当面付”把扫码支付权限先拿下支付宝的扫码支付在产品体系里叫“当面付”细分下去又分为“扫码支付”和“条码支付”两个场景主扫模式是商家展示二维码用户用支付宝App扫被扫模式是用户出示付款码商家用扫码枪扫。本项目的标题是扫码支付对应主扫模式核心接口是alipay.trade.precreate。操作路径很固定用企业或个体工商户账号登录支付宝开放平台进入控制台创建应用应用类型选“网页/移动应用”或“小程序”都可以关键是创建后在“产品绑定”里把“当面付”加进来并提交签约。签约需要营业执照、法人信息、经营场景照片之类的资质材料一般在1到3个工作日内审核完成。个人支付宝账号无法直接签约当面付只能使用个人收款码而个人收款码是没有开放接口可调的——这是很多“Java集成支付宝扫码支付项目”翻车的第一道坎。团队里如果有人跟你说用个人账号调接口直接否掉。签约通过后应用详情页会显示一串32位的APPID。这个APPID就是后续所有请求里的app_id参数需要写进配置文件。同时开发平台还提供“沙箱环境”用于联调沙箱环境有独立的APPID和网关地址不需要真实签约也能用。我的建议是先把沙箱跑通再切生产环境能少走很多弯路。2.2 密钥格式与回调地址版本、命名和公钥归属最容易搞混支付宝开放平台的密钥体系是自成一套的你自己生成一对RSA密钥把应用公钥上传到平台平台再给你回传一份支付宝公钥。这里有两个高频混淆点。第一上传的是应用公钥不是应用私钥。私钥自己留在服务器上绝不能传出去。第二平台回传的支付宝公钥长得很像应用公钥但两者用途完全不同你的应用私钥用于给请求参数签名支付宝公钥用于验证支付宝回调通知的签名。把这两个公钥弄反验签永远失败。密钥推荐使用RSA2算法也就是SHA256withRSA对应2048位以上密钥。生成方式用OpenSSL一条命令就能完成openssl genrsa -out app_private_key.pem 2048 openssl rsa -in app_private_key.pem -pubout -out app_public_key.pem openssl pkcs8 -topk8 -inform PEM -in app_private_key.pem -outform PEM -nocrypt -out app_private_key_pkcs8.pem命令说明第一条生成2048位的RSA私钥第二条从私钥导出公钥第三条把私钥转换成PKCS8格式。Java的PKCS8EncodedKeySpec要求的就是PKCS8格式的私钥所以第三条命令必须执行否则后面初始化AlipayClient时会报“无效的私钥”或“KeyException”。把app_private_key_pkcs8.pem的内容复制到配置里注意把-----BEGIN PRIVATE KEY-----和结尾的换行一起保留。回调地址方面当面付需要配置异步通知地址notify_url支付成功后支付宝服务器会往这个地址发POST请求。这个地址必须是公网可访问的HTTPS地址不能是localhost。如果本地调试没有公网环境可以先配置成线上测试服务器的地址或者用内网穿透工具临时顶一下但生产环境一定要用HTTPS域名。2.3 沙箱环境与支付宝模拟器联调前先把参数表拉齐支付宝开放平台提供了独立的沙箱环境里面有一套虚拟的商家信息和买家账号。沙箱网关是https://openapi.alipaydev.com/gateway.do和正式网关不一样。在沙箱里你需要使用平台提供的沙箱应用APPID、沙箱支付宝公钥以及平台生成的沙箱应用私钥。这套参数和正式参数完全隔离建议用独立的配置文件区分。网上搜索“支付宝模拟器”时会看到一些第三方模拟工具它们大多用于模拟支付宝App的UI界面并不安全不推荐下载。官方提供的联调方式是下载“支付宝沙箱版”App用平台分配的买家账号登录能完整模拟扫码付款流程。对接时把这些参数写进application.yml用配置项切换环境alipay: env: sandbox app-id: 2021000000000000 private-key: | -----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY----- alipay-public-key: | -----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY----- gateway: https://openapi.alipaydev.com/gateway.do notify-url: https://your-domain.com/api/alipay/notify return-url: https://your-domain.com/#/pay/result参数说明env用于区分环境切换生产时把值改成prod并覆盖app-id、gateway和两个密钥。private-key和alipay-public-key用YAML的|块语法保留换行避免转义问题。notify-url是异步回调地址return-url是同步跳转地址扫码支付里同步地址只做展示用不做业务判断。把这张参数表拉齐后面写代码就会非常顺。3. 用Spring Boot跑通扫码支付下单、生成二维码与轮询结果这一章直接给可运行的Java代码。我默认你的项目是Spring Boot 2.x或3.xMaven构建JDK 8以上。网上有些开源项目把支付模块嵌在“spring boot mybatis 的多商户商城源码”里结构太重不建议直接抄。独立建一个支付模块会更干净。3.1 引入alipay-sdk-java并声明AlipayClient第一步是在pom.xml里引入支付宝官方SDK。坐标是com.alipay.sdk:alipay-sdk-java版本号去Maven中央仓库查最新版本我用的时候是4.x系列API相对稳定。如果你的项目是JDK8选不带-all的普通包就行。dependency groupIdcom.alipay.sdk/groupId artifactIdalipay-sdk-java/artifactId version4.38.1.ALL/version /dependency依赖说明alipay-sdk-java内部依赖aliyun-java-sdk-core等组件Maven会自行传递。如果公司私有仓库没有同步中央仓库需要手工把jar包部署到私有Nexus。版本号不建议锁定过老Alipay签名接口在不同版本间有细微差异旧的3.x版本也可以但RSA2验签方法名一样。第二步是创建配置类把AlipayClient声明为Spring Bean。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}) private String gateway; Bean public AlipayClient alipayClient() { return new DefaultAlipayClient( gateway, appId, privateKey, json, UTF-8, alipayPublicKey, RSA2 ); } }逻辑说明DefaultAlipayClient构造器的七个参数依次是网关、APPID、应用私钥、数据格式、字符集、支付宝公钥、签名类型。字符集必须用UTF-8否则商品名称带中文时会乱码。签名类型写RSA2对应SHA256withRSA如果你用了1024位老密钥还写RSA2SDK会在请求时直接报错。3.2 调用alipay.trade.precreate生成支付二维码当面付主扫模式的核心接口就是预下单。后端收到收银台的下单请求后先落一条业务订单状态记为“待支付”然后调用支付宝预下单接口得到一个二维码字符串。这个字符串本质上就是一个支付宝支付链接可以通过二维码生成库转成图片。Service public class AlipayScanServiceImpl { private final AlipayClient alipayClient; public AlipayScanServiceImpl(AlipayClient alipayClient) { this.alipayClient alipayClient; } public String createQrCode(String orderNo, BigDecimal amount, String subject) throws AlipayApiException { AlipayTradePrecreateRequest request new AlipayTradePrecreateRequest(); request.setNotifyUrl(notifyUrl); JSONObject bizContent new JSONObject(); bizContent.put(out_trade_no, orderNo); bizContent.put(total_amount, amount.toPlainString()); bizContent.put(subject, subject); bizContent.put(timeout_express, 30m); bizContent.put(store_id, STORE_001); request.setBizContent(bizContent.toString()); AlipayTradePrecreateResponse response alipayClient.execute(request); if (response.isSuccess()) { return response.getQrCode(); } throw new RuntimeException(预下单失败 response.getSubMsg()); } }逻辑说明out_trade_no是商户订单号必须在商户侧保证唯一支付宝允许重复的out_trade_no但会直接返回上一次的交易信息而不是帮你新建订单所以每次下单前要确认订单号没被用过。total_amount是金额直接用字符串传入BigDecimal转字符串是为了避免浮点误差。timeout_express是订单超时时间建议设置为30分钟到2小时超时后支付宝会自动关闭交易。store_id是门店编号对后续参加官方激励、对账都是加分项建议从一开始就带上。response.getQrCode()返回的是一串形如https://qr.alipay.com/xxx的链接。把这个链接交给ZXing或qrcode插件生成图片输出到收银台页面用户扫码后就开始支付。3.3 轮询交易结果不能只等回调超时补单才稳支付宝的异步通知回调通常是可靠的但存在网络抖动、回调延迟甚至丢失的可能。因此成熟项目都会加一层主动轮询创建订单后由定时任务每隔一段时间调用alipay.trade.query查询交易状态。这个过程在订单量不大的场景下完全可以用Spring自带的Scheduled完成。Component public class AlipayTradeQueryTask { private static final long QUERY_INTERVAL_MS 5000; Scheduled(fixedDelay 5000) public void pollPayingOrders() { ListOrder orders orderMapper.selectPayingOrdersOlderThan(30); for (Order order : orders) { try { AlipayTradeQueryRequest request new AlipayTradeQueryRequest(); JSONObject bizContent new JSONObject(); bizContent.put(out_trade_no, order.getOrderNo()); request.setBizContent(bizContent.toString()); AlipayTradeQueryResponse response alipayClient.execute(request); if (response.isSuccess() TRADE_SUCCESS.equals(response.getTradeStatus())) { orderService.markAsPaid(order.getOrderNo(), response.getTradeNo()); } } catch (AlipayApiException e) { log.error(轮询支付结果失败订单号{}, order.getOrderNo(), e); } } } }参数说明selectPayingOrdersOlderThan(30)意思是只捞取创建时间超过30秒的待支付订单避免刚下单就去查。查询间隔fixedDelay 5000表示上一次任务完成后5秒再跑下一次对中小商户足够了订单量大的话建议换成RabbitMQ延迟队列或者xxl-job分片把查询压力打散。trade_status的返回值有WAIT_BUYER_PAY、TRADE_SUCCESS、TRADE_CLOSED等枚举只有TRADE_SUCCESS才能确认收到钱其他状态不要动订单。值得注意的是轮询和异步回调可能会同时触发支付成功逻辑所以markAsPaid方法必须是幂等的。下一章就专门处理这个问题。4. 支付宝回调避坑与订单状态机哪些位置最容易翻车很多已经跑起来的扫码支付项目日常出问题最多的不是下单而是回调处理。这章是全文的重点也是我认为值得反复读的一章。4.1 异步通知验签与成功应答先验后改是底线支付宝的异步通知是一个POST请求内容格式为application/x-www-form-urlencoded参数包括out_trade_no、trade_no、trade_status、app_id、sign等。如果不验签任何人都可以伪造通知接口请求告诉你“某订单支付成功了”然后你的系统就会给未付款的订单发货。PostMapping(value /api/alipay/notify) public String alipayNotify(HttpServletRequest request) throws Exception { MapString, String params new HashMap(); MapString, String[] requestParams request.getParameterMap(); for (String name : requestParams.keySet()) { String[] values requestParams.get(name); params.put(name, String.join(,, values)); } boolean signVerified AlipaySignature.rsaCheckV1( params, alipayPublicKey, UTF-8, RSA2 ); if (!signVerified) { log.warn(支付宝回调验签失败参数{}, JSON.toJSONString(params)); return failure; } String outTradeNo params.get(out_trade_no); String tradeNo params.get(trade_no); String tradeStatus params.get(trade_status); if (TRADE_SUCCESS.equals(tradeStatus)) { orderService.markAsPaid(outTradeNo, tradeNo); } return success; }逻辑说明AlipaySignature.rsaCheckV1是SDK提供的验签方法参数依次为请求参数Map、支付宝公钥、字符集、签名类型。验签通过后再取trade_status判断业务。必须明确一点回调里出现TRADE_SUCCESS时才更新订单WAIT_BUYER_PAY只是等待支付状态不能当成支付成功。返回值的写法也是个经典坑。支付宝要求成功应答返回纯文本success失败返回failure或者其他内容。如果你返回JSON格式的{code:success}支付宝会认为通知失败每隔一段时间重新通知你最长重试24小时。很多人在半夜被日志报警吵醒就是因为他们成功应答返回了JSON而不是纯文本。4.2 状态机流转与幂等更新防止同一条通知重复发奖回调通知可能重复发送多次轮询也会查到同一个订单这两个入口并发更新订单状态时如果不做幂等控制就会出现重复发货、重复发券。简单场景下用数据库条件更新就行Transactional public void markAsPaid(String outTradeNo, String tradeNo) { int rows orderMapper.compareAndSetStatus( outTradeNo, PayStatus.PAYING, PayStatus.PAID, tradeNo ); if (rows 1) { // 只有第一次更新成功才会执行后续逻辑 inventoryService.deduct(outTradeNo); couponService.issue(outTradeNo); } }对应SQL为UPDATE t_order SET pay_status PAID, alipay_trade_no #{tradeNo}, paid_at NOW() WHERE out_trade_no #{outTradeNo} AND pay_status PAYING逻辑说明这条SQL的巧妙之处在于WHERE条件里带上了pay_status PAYING相当于用数据库行锁做了一次“乐观锁”保证只有第一个到达的业务请求能把状态从“待支付”改成“已支付”后面的重复请求因为条件不满足影响行数为0自然就走不到发货逻辑。这正是“Java怎么保证数据一致性”这个问题在支付场景下的标准答案之一。订单状态机至少要有这几个状态PAYING待支付、PAID已支付、CLOSED已关闭超时未付、REFUNDING退款中、REFUNDED已退款。状态之间只允许单向流转已支付订单不能回到待支付已关闭订单不能直接变成已支付。如果业务上允许超时关闭后用户又付了钱要单独设计“线下退款”或“自动退款”流程而不是篡改状态机。4.3 高频踩坑清单四类问题定位与解决下面这几条都是我在真实项目里见过或者自己踩过的坑按“现象、原因、解决”来写可以直接对照排查。坑1用户付款成功但系统一直显示待支付。原因分析回调通知没有收到或者轮询任务没有扫描到这笔订单。常见于沙箱环境回调地址没配置、生产服务器防火墙拦截了支付宝回调IP、订单创建时间与轮询阈值设置不当。解决思路先看应用日志里有没有支付宝回调的POST记录同时检查回调地址是否公网可达。另外给轮询任务加监控如果连续多次扫描到同一笔待支付订单超过10分钟触发人工告警。坑2同一个订单收到几次回调业务处理了多次。原因分析回调重试机制导致重复通知而订单更新逻辑没有幂等约束。解决思路按上文的compareAndSetStatus条件更新或引入Redis的setIfAbsent锁定订单号。一定不要把“验签通过”当成“处理成功”的充分条件验签之后还要有状态流转约束。坑3沙箱环境支付后回调不触发。原因分析沙箱环境的回调通知需要你的回调地址公网可达且地址必须是在支付宝沙箱应用里配置过的notify_url。用localhost是收不到的。解决思路本地调试时用内网穿透把本地端口暴露成临时HTTPS地址并保证支付宝沙箱后台填的地址与代码里setNotifyUrl完全一致。注意内网穿透地址会变化每次变了都要去后台同步。坑4二维码支付成功后再次扫同一个码状态不更新。原因分析out_trade_no被重复使用支付宝返回了上一次的交易数据。解决思路每次收银台发起支付前先判断当前订单是否带头部状态如果待支付订单存在直接复用原二维码不要重新预下单。对已经关闭的订单想再收一次钱必须生成新的out_trade_no。坑5验签偶尔失败但不是因为伪造请求。原因分析参数传递不规范比如支付宝回传的某个多值参数被框架自动拼接时加了空格或者从负载均衡层取参数时大小写被改写。解决思路用原始getParameterMap()原样传递不要经过其他包装。验签用的支付宝公钥必须是从开放平台密钥管理页复制的那把不是自己生成的应用公钥。5. 刷脸支付产品接入与官方奖励政策设备激励和交易返利怎么拿标题后半部分是“支付宝刷脸支付官方奖励政策”。扫码支付做好之后你会很自然地关注刷脸支付因为它和当面付同属一个开放平台家族而且官方奖励政策往往是吸引商户接入的核心动力。这章把政策结构和落地路径讲清楚。5.1 刷脸支付在接入体系上与扫码支付的区别刷脸支付的产品形态主要有两类一类是支付宝官方或生态伙伴推出的“蜻蜓”系列刷脸设备属于硬件终端另一类是服务商集成刷脸能力到自有App或小程序里用户手机开启刷脸付后在收银台确认。无论哪种形态资金清算链路和现有扫码支付完全一致用户确认支付后支付宝异步通知商户系统商户系统验签、更新订单。区别在于交互层扫码支付是商户展示二维码用户主动扫刷脸支付是设备端主动捕获人脸用户确认手机号后四位或直接确认设备SDK把支付结果推给商户软件。因此你的Java后端不需要改订单模块只需要新增一个“设备支付”的接入入口接收来自设备SDK的交易号和业务订单号其余流程调用alipay.trade.query或订阅支付宝消息推送完全复用扫码支付的代码。5.2 官方奖励政策的结构与查询入口支付宝刷脸支付官方奖励政策不同时期的侧重点不一样但结构基本稳定可以从三个维度去理解。第一设备激励。面向商户或服务商购买指定型号的刷脸设备并完成激活后按“单台设备累计有效刷脸交易笔数”分档返现。比如激活后30天内达到一定笔数返设备款的一部分达到更高档位再返剩余部分。这个政策类似于“阶梯返款”本质上是用交易活跃度换设备成本不是买设备直接打折。第二交易返利。很多政策面向服务商按月度有效刷脸交易笔数或交易金额给予返佣每笔交易几分钱到几毛钱不等设置月度封顶金额。返利结算一般以自然月为周期次月对账后打款到服务商支付宝账户。第三活动限制条款。政策通常有预算总额名额用完活动就终止同时明确排除“虚假交易”“套现”“刷单”等行为一旦命中已发奖励会被追回。申请奖励时系统会校验设备的激活时间、交易真实性、退款率等指标。重点提醒具体返现金额、达标笔数、结算周期要以支付宝开放平台或服务商平台的“政策中心”公示为准不同时期、不同渠道差异很大。写这章时不给你报价是因为政策会变去官方公告里查当前版本才是最稳妥的。查询入口在开放平台的“激励与政策”或服务商平台的“任务中心”刷脸设备的政策一般伴随设备型号和时间段发布。5.3 设备接入的硬件链路与一个常见的驱动坑硬件接入层面最常见的踩坑场景是Windows系统连接刷脸设备时报错。很多刷脸设备通过USB口连接到收银电脑内部走的是USB转串口通信主控芯片常用PL2303系列。你在网上搜“pl2303支付宝刷脸设备连接电脑失败”大概率会看到两类原因。第一类是驱动问题。Windows 10和Windows 11对旧版PL2303芯片驱动不兼容系统自动装了微软默认的usbser驱动设备管理器里能看到未知设备或感叹号。解决方式是手动安装Prolific官方发布的PL2303驱动注意芯片版本老芯片用新版驱动反而不识别。装完后在“设备管理器-端口COM和LPT”里能看到一个COM口号说明链路通了。第二类是供电和线材问题。刷脸设备对USB线质量比较敏感有些劣质线只走数据不供电导致设备上电闪红灯。解决方式是换原装线或者使用带独立供电的USB HUB。Java后端与刷脸设备的通信一般不会直接操作串口而是通过设备厂商的SDK或HTTP接口交互所以驱动问题通常只影响设备调试工具不影响你的支付接口。对于不想碰硬件的团队还有一个更轻的落地路径接入“刷脸即会员”或者“刷脸支付”的小程序收银台源码用户在自己手机上完成刷脸授权不需要采购设备。这种模式虽然对线下门店的体验提升有限但适合商户快速试水等跑通交易流再上设备。6. 扫码支付项目的验收清单与进阶到刷脸付的落地习惯项目上线前建议按下面这份清单过一遍每一项都对应一个真实发生过的事故。先用0.01元小额订单在沙箱完整走一遍主扫流程下单拿到二维码、扫码支付、支付宝App显示支付成功、后端收到回调、订单状态变为已支付。然后测试三种边界二维码超时关闭后支付确认系统不会发货重复回调确认幂等逻辑生效伪造回调请求确认验签拦截。生产环境首笔交易完成后去支付宝商户平台核对账单里的订单号、金额、手续费确保和本地数据库一致。还有一项很容易被忽略定时任务轮询的日志要对接到监控告警防止回调没触发时订单卡死在待支付状态而轮询任务自己也挂掉了。从扫码向刷脸进阶时我个人的习惯分三步走第一保持扫码支付作为兜底不要一上线就撤掉二维码设备故障时还能切换第二刷脸设备的商户如果复用了同一套门店编号store_id在账单和奖励对账时会有天然优势从一开始就统一编号规则第三奖励政策的报名动作要由专人负责跟进政策补贴核算周期是按自然月月底前确认当月有效刷脸交易笔数避免漏报导致奖励丢失。做了多年支付接入我最深刻的教训是支付系统的钱货一致性从来不靠“运气”而是靠状态机和幂等约束兜底。扫码支付这套脚印踩实了刷脸政策里那些奖励规则才有意义——因为每一条交易奖励背后都是一个能对得上账的真实订单。希望帮到你。本文还有配套的精品资源点击获取