简介:面向PHP开发者的支付宝Alipay Easy SDK轻量封装包,将官方SDK中非PHP语言代码剔除后独立发布为Composer依赖包,有效解决PHP项目引入SDK时包体臃肿、依赖混杂的问题,适合需要快速接入支付宝开放平台能力的中高级PHP开发者。压缩包采用zip格式,整体约125KB,内容以PHP源码文件和Composer配置文件为主,未混入其他语言实现及测试目录,安装与生产部署更为精简,页面未提供文件总数明细,但目录结构清晰可直接使用。目前已有928人浏览学习,尤其适合通过Composer管理依赖的PHP项目使用。包版本与官方alipay-easysdk保持一致,保留证书校验、加签、验签、发送HTTP请求等底层能力,并额外添加了.gitattributes配置,安装时自动忽略test文件夹,减少无用文件分发;通过composer require alphasnow/alipay-easysdk-php即可完成依赖管理。借助官方“完全初始化”的通用设计理念,开发者可以专注自身业务逻辑,无需重复处理签名与请求细节,非常适合在实际PHP项目中作为支付宝能力集成的底座。
1. 一个能直接跑起来的支付宝 PHP 包:把官方 easysdk 拆成独立的 Composer 包
做电商系统时几乎绕不开支付宝接入。alipay-easysdk-php 这个项目解决的问题很具体:官方 easysdk 原本是 Java、PHP、Python 放在同一个仓库里的多语言工程,PHP 代码埋在子目录里,项目里要么整仓拉下来,要么手动把 php 目录拷进去,发版升级时都要重新对齐,非常别扭。
把这个 PHP 实现单独拆成一个独立包之后,一条 composer require 就能装进项目,PSR-4 自动加载,命名空间、密钥配置、下单和回调验签这些能力原样保留。适合刚接入支付宝、不想在官方多语言仓库里翻找的 PHP 开发者,也适合想把这套 SDK 再封装进自己支付中间层的同学。
2. 拆包架构设计:从多语言仓库到独立 Composer 包
2.1 官方 easysdk 的多语言结构与 PHP 代码的位置
官方 easysdk 是聚合仓库,根目录下按语言划分子目录,PHP 实现整体在 php/ 目录下。核心代码在 php/src/EasySDK 里,按功能域分成若干组:Kernel 目录放配置、签名验签、HTTP 客户端这类基础设施;Payment 下面再按产品线拆成 page、wap、app、common 等子客户端,分别对应电脑网站支付、手机网站支付、App 支付和通用的查询退款。
这种结构对官方维护多语言是合理的,对只想用 PHP 的业务项目却不够友好。整仓拉下来,无关语言代码全进 vendor;手动拷贝 php/ 目录,下次官方更新要重新对文件差异,漏一个类文件就要排查半天。alipay-easysdk-php 做的事情本质上是一次「单语言抽取」:把 PHP 相关的目录、命名空间和配置方式保留下来,去掉多语言外壳,把边界收窄到「PHP 项目直接可用」。
2.2 独立包的目录设计与 composer.json 的自动加载
拆包后最需要扣细节的是命名空间和目录的对应关系。官方 PHP 实现的命名空间以 Alipay\EasySDK 开头,文件放在 src 下的 EasySDK 目录里。独立包沿用这个布局,目录大致是:
alipay-easysdk-php/ ├── composer.json ├── src/ │ └── EasySDK/ │ ├── Kernel/ │ │ ├── Factory.php │ │ ├── AlipayConfig.php │ │ ├── Signer.php │ │ └── AopClient.php │ ├── Payment/ │ │ ├── Page/ │ │ ├── Wap/ │ │ ├── App/ │ │ └── Common/ │ └── Base/ ├── examples/ │ └── page_pay.php └── README.md这里的关键是 PSR-4 映射要写对,否则类加载直接翻车。composer.json 里常见的写法是:
{ "name": "your-vendor/alipay-easysdk-php", "description": "基于 alipay-easysdk 拆出的独立 PHP 包", "require": { "php": ">=7.2", "ext-json": "*", "ext-openssl": "*" }, "autoload": { "psr-4": { "Alipay\\EasySDK\\": "src/EasySDK/" } } }逻辑说明:PSR-4 的规则是命名空间 Alipay\EasySDK\Kernel\Factory 对应去 src/EasySDK/Kernel/Factory.php 找类文件,目录层级和命名空间层级一一对应。如果拆包时把 src/EasySDK 移成了 src,映射也要同步改成 "Alipay\EasySDK\": "src/",两边必须一致。包进入业务项目后执行 composer dump-autoload 重新生成类映射,新文件才能被加载到。
2.3 配置入口:Factory::setOptions 支持的参数
easysdk 的设计里,所有能力都从 Factory 入口进,第一步是用 setOptions 把应用配置塞进去。拆包后这个习惯不变,参数也没有改动。常用的配置字段如下:
| 参数 | 含义 | 示例 |
|---|---|---|
| appId | 开放平台上创建的支付宝应用 ID | 20210021xxxxxxxxxx |
| merchantPrivateKey | 商户应用私钥,PEM 格式 | file_get_contents('app_private_key.pem') |
| alipayPublicKey | 支付宝公钥,用于验签 | 从开放平台复制保存为 .pem |
| signType | 签名类型,固定 RSA2 | RSA2 |
| gateway | 网关地址,沙箱和线上不同 | https://openapi.alipaydev.com/gateway.do |
| notifyUrl | 异步通知地址 | https://api.demo.com/pay/notify |
提示:私钥和公钥建议用文件读取而不是硬编码在数组里。PEM 文件本身带 BEGIN/END 标记和换行,压成一行塞进配置,签名阶段不报错,支付宝那边验签却会失败,这是这个 SDK 最容易踩的坑,后面避坑章节专门讲。
3. 核心模块拆解:Factory 工厂、RSA2 签名与支付下单的完整链路
3.1 Factory 工厂:一条链子把支付、查询、退款串起来
Factory 是整个包的门面,作用是持有配置并创建对应产品线的客户端。简化后的实现思路是这样的:
namespace Alipay\EasySDK\Kernel; use Alipay\EasySDK\Payment\Client as PaymentClient; class Factory { private static $config = []; public static function setOptions(array $options) { self::$config = array_merge(self::$config, $options); return new self(); } public static function payment() { return new PaymentClient(self::$config); } }逻辑说明:setOptions 用 array_merge 合并配置而不是直接覆盖,所以可以在项目入口先 set 公共配置,再在某条业务链路里补充网关或通知地址;payment() 每次返回一个新的客户端对象,客户端内部再把配置继续传给 page、wap、common 这些子模块。这样调用链 Factory::payment()->page()->pay(...) 读起来就是业务语言,新接手的人不看文档也能猜个七八分。config 数组的字段合法值就是 2.3 节那张表,传错字段名会被下游客户端忽略,表现成「下单莫名失败」。
3.2 签名与验签:RSA2 的核心实现
Kernel/Signer.php 是签名和验签的所在地,所有接口请求体和异步通知的验证都靠它。RSA2 对应的哈希算法是 SHA-256,用 openssl 扩展实现:
namespace Alipay\EasySDK\Kernel; class Signer { public static function sign(string $content, string $privateKey): string { $key = openssl_pkey_get_private($privateKey); openssl_sign($content, $signature, $key, OPENSSL_ALGO_SHA256); return base64_encode($signature); } public static function verify(string $content, string $sign, string $alipayPublicKey): bool { $key = openssl_pkey_get_public($alipayPublicKey); $result = openssl_verify( $content, base64_decode($sign), $key, OPENSSL_ALGO_SHA256 ); return $result === 1; } }逻辑说明:签名是「待签字符串 + 商户私钥 → SHA256withRSA → base64」;验签是「待签字符串 + 支付宝公钥 + 对方签名 → 布尔结果」。两个函数里的 $content 都是按规则拼好的字符串:下单时是所有请求业务参数按 key 字典序拼接后转成查询串,回调验签时是支付宝 POST 过来的原始参数按同样规则拼接。顺序差一个字符结果就不同,所以 SDK 内部复用同一段拼接逻辑,避免人为拼错。
参数上要注意 openssl_verify 的返回值:1 是验证通过,0 是验证失败,-1 是密钥本身错误。所以比较必须用全等 === 1,只写 if ($result) 会把 -1 也当成成功,密钥配错时反而通过不了,排查半天发现是自己写错了判断。
3.3 从下单到回调:一次页面支付的完整链路
把前面几个模块串起来看一次支付生命周期。业务侧调用代码:
$result = Factory::payment()->page()->pay( '示例商品', 'ORDER_' . date('YmdHis'), '0.01' ); if (!empty($result->body)) { echo $result->body; // 自动提交表单,浏览器直接跳支付宝收银台 } else { error_log(json_encode($result->toJSON())); }逻辑说明:page()->pay() 接收三个参数——subject 商品描述、outTradeNo 商户订单号(自行生成,必须唯一)、totalAmount 订单金额(元,字符串)。SDK 内部把三个业务参数放进请求体,用 Signer 签名后 POST 到 gateway,支付宝返回一段自动提交表单的 HTML,存到响应对象的 body 字段。业务侧原样输出这段 HTML,页面会自动带着签名后的表单跳转到收银台;body 为空说明接口层就失败了,用 toJSON() 能拿到 code、sub_code、sub_msg 三个字段去定位。
异步通知是反向链路:用户支付完成后,支付宝往 notifyUrl POST 一组参数,业务方在回调地址里验签并处理订单。验签用的还是 Signer::verify,待签字符串来自回调参数。到这里,配置、工厂、签名、下单、回调就完整串成了一条支付闭环。
4. 从零跑起来:Composer 安装、电脑网站支付最小示例与异步回调处理
4.1 安装:composer require 与密钥准备
包发布到 packagist 或公司私有仓库后,在业务项目里一条命令安装:
composer require your-vendor/alipay-easysdk-php如果拆包后只在本地用,用路径仓库也行:在业务项目的 composer.json 里配置 repositories 指向包的 git 地址或本地目录,再 require。装完先确认 vendor/composer/autoload_psr4.php 里出现了 Alipay\EasySDK 的映射,没有就执行 composer dump-autoload。
密钥准备是跑通的前提。你需要三样东西:应用私钥(商户自己生成,保存为 .pem)、应用公钥(上传到支付宝开放平台)、支付宝公钥(从平台复制保存为 .pem)。沙箱环境则在沙箱控制台里拿对应的 appId、密钥和网关地址,三件套必须同源,混搭会在后面引发各种奇怪问题。
4.2 电脑网站支付:最小可运行示例
把下面内容保存为 page_pay.php,放到项目入口,浏览器访问就能看到效果:
<?php require_once __DIR__ . '/vendor/autoload.php'; use Alipay\EasySDK\Kernel\Factory; Factory::setOptions([ 'appId' => '20210021xxxxxxxxxx', 'merchantPrivateKey' => file_get_contents(__DIR__ . '/keys/app_private_key.pem'), 'alipayPublicKey' => file_get_contents(__DIR__ . '/keys/alipay_public_key.pem'), 'signType' => 'RSA2', 'gateway' => 'https://openapi.alipaydev.com/gateway.do', 'notifyUrl' => 'https://api.demo.com/pay/notify', ]); $result = Factory::payment()->page()->pay('测试商品', 'ORDER_20240001', '0.01'); if (!empty($result->body)) { echo $result->body; } else { var_dump($result->toJSON()); }逻辑说明:appId 换成你自己的,keys 目录放两个 pem 文件,gateway 用沙箱地址;线上部署时把 gateway 换成 https://openapi.alipay.com/gateway.do。浏览器访问这个文件,页面自动组装表单并跳转收银台,用沙箱账号付款后,在沙箱控制台能看到这笔订单。这一步跑通,说明配置、签名、HTTP 请求、下单链路全部正常。
4.3 异步通知:验签、幂等与返回要求
回调是支付接入里最需要抠细节的地方。notify 接口的骨架:
<?php require_once __DIR__ . '/vendor/autoload.php'; use Alipay\EasySDK\Kernel\Factory; // 配置同 4.2,gateway 必须和下单时一致 $result = Factory::payment()->common()->verifyNotify($_POST); if ($result && $_POST['trade_status'] === 'TRADE_SUCCESS') { // 先查订单状态,已处理直接返回,防止重复通知 if (isOrderPaid($_POST['out_trade_no'])) { echo 'success'; return; } markOrderPaid($_POST['out_trade_no'], $_POST['trade_no'], $_POST['receipt_amount']); echo 'success'; } else { echo 'fail'; }逻辑说明:verifyNotify 内部把 $_POST 参数按字典序拼成待签字符串,用支付宝公钥验签,这一步挡掉伪造通知。返回时有两条硬性要求:一是必须输出纯文本 success,不带引号、不带换行、前面不能有任何 echo 或 BOM 输出,否则支付宝认为处理失败,会隔一段时间重发通知;二是做幂等,同一笔订单的 TRADE_SUCCESS 通知可能到达多次,先查库、已支付就直接返回 success。这两点做到,回调这环基本就稳了。
5. 避坑指南:密钥格式、沙箱网关和自动加载的 4 个高频问题
5.1 跳转收银台报「签名错误」,其实是密钥配错了
现象:业务侧接口返回正常,表单也跳转了,但收银台或支付结果页提示签名异常 / sign check fail。
原因:多数情况是商户私钥和支付宝公钥不成对。常见两类:把应用公钥当成了支付宝公钥填进去;或者 PEM 内容被压缩成一行,换行符丢了。openssl 对换行敏感,私钥字符串一旦换行不对,签名产物和支付宝端对不上,结果就是验签失败。
解决:私钥统一用 file_get_contents 读 .pem 文件,不要硬编码在配置数组里;确认填的是开放平台的「支付宝公钥」,不是「应用公钥」。实在排查不出,重建一次应用密钥对,重新上传公钥并同步更新代码里的两个文件,这是最省事的后悔药。
5.2 沙箱联调时订单在后台看不见:网关没切
现象:用沙箱的 appId 下单,业务侧返回成功,但沙箱后台看不到订单,或者接口报 isv.app-not-exist。
原因:gateway 还挂在线上地址。appId、网关、密钥本质是一套绑定关系,线上 appId 配沙箱网关、或沙箱 appId 配线上网关,都会出现「请求成功但订单消失」的诡异现象。
解决:把 gateway 抽成独立配置,沙箱联调和上线部署各用各的值。打开沙箱控制台复制沙箱网关地址,上线前全局搜索 gateway 确认已经替换成线上地址。
5.3 Composer 报 Class not found:PSR-4 映射没对上
现象:引入包后运行,报 Class 'Alipay\EasySDK\Kernel\Factory' not found。
原因:独立包分发时 src 目录被调整过(比如外层多套了一层目录),但 composer.json 的 PSR-4 映射没同步改;或者用本地路径仓库方式引入,改了包内文件却没有重新生成自动加载文件。
解决:打开 vendor/composer/autoload_psr4.php 看 Alipay\EasySDK 映射到哪个实际路径,再对比真实文件位置。改完 composer.json 后必须执行 composer dump-autoload,这一步是最容易被漏掉的。
5.4 回调重复通知甚至被判失败:响应不干净
现象:支付宝后台显示通知一直重发,或者业务明明处理了订单,却走了补单流程。
原因:notify 入口在 echo 'success' 之前输出了别的东西。常见的是文件开头的 BOM 字符、调试用的 var_dump、框架中间件往响应里塞的内容。支付宝要求响应体必须是严格的 success 纯文本。
解决:回调入口不输出任何内容,业务判断完直接 echo 'success'。用框架的话检查中间件是否改写响应;原生 PHP 确认文件保存为无 BOM 的 UTF-8。验证方法:在沙箱发起一笔真实支付,观察支付宝的通知记录,确认重发停止。
6. 二次开发:把 SDK 包封装成自己的支付中间层
6.1 封装统一支付服务层
业务代码到处直接调 Factory,会和 SDK 耦合得很死。我一般会在项目里加一层 AlipayService,把下单、查询、退款收敛成业务友好的方法:
namespace App\Services\Payment; use Alipay\EasySDK\Kernel\Factory; class AlipayService { public function __construct(array $config) { Factory::setOptions($config); } public function pagePay(string $subject, string $outTradeNo, string $amount): string { $result = Factory::payment()->page()->pay($subject, $outTradeNo, $amount); return $result->body ?? ''; } public function query(string $outTradeNo): array { $result = Factory::payment()->common()->query($outTradeNo); return $result->code === '10000' ? ['status' => $result->tradeStatus, 'raw' => $result->toJSON()] : ['status' => 'UNKNOWN', 'raw' => $result->toJSON()]; } public function refund(string $outTradeNo, string $amount): bool { $result = Factory::payment()->common()->refund($outTradeNo, $amount); return $result->code === '10000'; } }逻辑说明:业务侧只依赖 AlipayService,SDK 升级、换支付产品线都只改这一个类。再配合订单表加 pay_status 字段、回调里先查库再更新的写法,一套可维护的支付体系就成型了。
6.2 升级与验证流程
每次拿到新版本 SDK 或改完自己的封装,我会在本地强制走一遍验证:先切沙箱环境,跑「下单 → 收银台付款 → 异步通知 → 主动查询 → 退款 → 再查询」完整链路;然后把下单请求和回调报文存下来,作为回归测试的样本;最后确认 composer 依赖里 PHP 版本和 ext-openssl 都满足。
注意:反复改密钥和网关配置时,最容易出问题的不是代码而是缓存。只要动过 composer.json 或新增了类文件,先 composer dump-autoload 再跑,能省掉大量定位时间。
从那以后,我每次接支付类 SDK 都会强制在沙箱里把全流程走一遍,密钥一律走 .pem 文件、网关单独配置、回调输出严格纯净,这三条已经成了我处理支付集成的默认习惯。希望帮到你。
本文还有配套的精品资源,点击获取