[A]Sync Chainable Alipay OpenAPI SDK for PHP
支付宝 OpenAPI 的Guzzle HttpClient封装组合,
内置 请求签名 和 应答验签 两个middlewares中间件,创新性地实现了链式面向对象同步/异步调用远程接口。
如果你是使用 Guzzle 的商户开发者,可以使用 EasyAlipay\Builder::factory 工厂方法直接创建一个 GuzzleHttp\Client 的链式调用封装器,
实例在执行请求时将自动携带身份认证信息,并检查应答的支付宝的返回签名。
我们开发和测试使用的环境如下:
- PHP >=7.1.2
- guzzlehttp/guzzle ^6.5 || ^7.0
注:
- 兼容支持
Guzzle6的PHP最低版本为7.1.2,另PHP官方已于1 Dec 2019停止维护PHP7.1,详见附注链接; - 随
Guzzle7支持的PHP最低版本为7.2.5,另PHP官方已于30 Nov 2020停止维护PHP7.2,详见附注链接;
推荐使用PHP包管理工具composer引入SDK到项目中:
在项目目录中,通过composer命令行添加:
composer require easyalipay/easyalipay在项目的composer.json中加入以下配置:
"require": {
"easyalipay/easyalipay": "^0.3"
}添加配置后,执行安装
composer install本类库是以 OpenAPI 公共请求参数中的接入方法 method 以.做切分,映射成attributes,编码书写方式有如下约定:
- 请求 接入方法
method切分后的每个attributes,可直接以对象获取形式串接,例如alipay.trade.query即串成alipay->trade->query; - 每个 接入方法
method所支持的HTTP METHOD,即作为被串接对象的末尾执行方法,例如:alipay->trade->query->post(['content' => []]); - 每个 接入方法
method所支持的HTTP METHOD,同时支持Async语法糖,例如:alipay->trade->query->postAsync(['content' => []]); - 每个 接入方法
method可以使用PascalCase风格书写,例如:alipay.trade.query可写成AlipayTradeQuery; - 在IDE集成环境下,也可以按照内置的
chain($method)接口规范,直接以接入方法method作为变量入参,来获取OpenAPI当前接入方法的实例,驱动末尾执行方法(填入对应参数),发起请求,例如chain('alipay.trade.query')->post(['content' => []]); - 末尾
get/post/getAsync/postAsync请求方法语法糖,型参$options语法糖规则如下:content字典,对应的是请求参数集合(biz_content)字典,直接写原生PHP array即可;query字典,对应的是除请求参数集合(biz_content)之外的,如部分特殊公共请求参数(system_params)有通知地址(notify_url)等,直接写原生PHP array即可;- 一个入参时
$options按需带入'content' => []及/或'query' => []结构即可; - 简写语法糖支持
[get|post][Async](array $content, array $options)、[get|post][Async](array $content, array $query, array $options)结构; - 本SDK所有
请求数据结构遵循官方开发文档,该是蛇型即蛇形(如:service_code),该是驼峰就驼峰(如:shopIds),看到的数据结构,即请求数据结构,原生PHP语法即可;
- 内置
返回值验签中间件在解构原始json字符串后,直接返回*_response对应的内容,有可能是json,也可能是AesCbc加密串,按需对返回串做处理;
以下示例用法,以异步(Async/PromiseA+)或同步(Sync)结合此种编码模式展开。
首先,通过 EasyAlipay\Builder::factory 工厂方法构建一个实例,然后如上述约定,链式同步或异步请求远端OpenAPI接口。
use EasyAlipay\Builder;
use EasyAlipay\Crypto\Rsa;
//应用app_id
$appId = '2014072300007148';
//商户RSA私钥,入参是'从官方工具获取到的BASE64字符串'
$privateKey = Rsa::fromPkcs1('MIIEpAIBAAKCAQEApdXuft3as2x...');
// 以上是下列代码的语法糖,格式为 'private.pkcs1://' + '从官方工具获取到的字符串'
// $privateKey = Rsa::from('private.pkcs1://MIIEpAIBAAKCAQEApdXuft3as2x...');
// 也支持以下方式,须保证`private_key.pem`为完整X509格式
// $privateKey = Rsa::from('file:///your/openapi/private_key.pem');
//支付宝RSA公钥,入参是'从官方工具获取到的BASE64字符串'
$publicKey = Rsa::fromSpki('MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCg...');
// 以上是下列代码的语法糖,格式为 'public.spki://' + '从官方工具获取到的字符串'
// $publicKey = Rsa::from('public.spki://MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCg...', Rsa::KEY_TYPE_PUBLIC);
// 也支持以下方式,须保证`public_key.pem`为完整X509格式
// $publicKey = Rsa::from('file:///the/alipay/public_key.pem', Rsa::KEY_TYPE_PUBLIC);
//如果是公钥证书模式,可以在工厂方法内传入 `$appCertSn` 及 `$alipayRootCertSn`
// $appCertFilePath = '/my/cert/app_cert.crt';
// $appCertSn = \EasyAlipay\Helpers::sn($appCertFilePath);
// $alipayRootCertFilePath = '/alipay/cert/alipayRootCert.crt';
// $alipayRootCertSn = \EasyAlipay\Helpers::sn($alipayRootCertFilePath);
// 工厂方法构造一个实例
$instance = Builder::factory([
'privateKey' => $privateKey,
'publicKey' => $publicKey,
'params' => [
'app_id' => $appId,
// 'app_auth_token' => $appAuthToken,
// 'app_cert_sn' => $appCertSn,
// 'alipay_root_cert_sn' => $alipayRootCertSn,
],
]);初始化字典说明如下:
privateKey为商户API私钥,一般是通过官方证书生成工具生成字符串,支持PKCS#1及PKCS#8格式的私钥加载;publicKey为平台API公钥,一般是通过官方证书生成工具生成字符串,支持PKCS#8及SPKI格式的公钥加载;params接口中的公共请求参数配置项,已内置charset=UTF-8,format=JSON,sign_type=RSA2及version=1.0;params['app_id' => $appId]为你的应用app_id;params['app_auth_token' => $appAuthToken]为你的ISV模式的授权token,按需配置;params['app_cert_sn' => $appCertSn]为公钥证书模式的商户证书相关信息SN,按需配置;params['alipay_root_cert_sn' => $alipayRootCertSn]为公钥证书模式的平台证书相关信息SN,按需配置;
注: OpenAPI 以及 GuzzleHttp\Client 的 array $config 初始化参数,均融合在一个型参上。
use GuzzleHttp\Utils;
use GuzzleHttp\Exception\RequestException;
try {
$res = $instance
->alipay->trade->query
->get(['content' => [
'out_trade_no' => '20150320010101001',
]]);
echo $res->getBody(), PHP_EOL;
} catch (RequestException $e) {
// 进行错误处理
if ($e->hasResponse()) {
$r = $e->getResponse();
echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
}
} catch (\Throwable $e) {
// 进行错误处理
echo $e->getMessage(), PHP_EOL;
echo $e->getTraceAsString(), PHP_EOL;
}use GuzzleHttp\Utils;
use GuzzleHttp\Exception\RequestException;
use Psr\Http\Message\ResponseInterface;
$res = $instance
->alipay->trade->pay
->postAsync(['content' => [
'out_trade_no' => '20150320010101001',
'scene' => 'bar_code',
'auth_code' => '28763443825664394',
'product_code' => 'FACE_TO_FACE_PAYMENT',
'subject' => 'Iphone6 16G',
'total_amount' => '88.88',
]])
->then(static function(ResponseInterface $response) {
// 正常逻辑回调处理
return Utils::jsonDecode((string) $response->getBody(), true);
})
->otherwise(static function($e) {
// 异常错误处理
echo $e->getMessage(), PHP_EOL;
if ($e instanceof RequestException && $e->hasResponse()) {
$r = $e->getResponse();
echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
}
echo $e->getTraceAsString(), PHP_EOL;
})
->wait();
print_r($res);use GuzzleHttp\Utils;
use GuzzleHttp\Exception\RequestException;
use Psr\Http\Message\ResponseInterface;
$res = $instance
->Alipay->Trade->Precreate
->postAsync([
'out_trade_no' => '20150320010101001',
'subject' => 'Iphone6 16G',
'total_amount' => '88.88',
], ['query' => [
'notify_url' => 'http://api.test.alipay.net/atinterface/receive_notify.htm'
]])
->then(static function(ResponseInterface $response) {
// 正常逻辑回调处理
return Utils::jsonDecode((string) $response->getBody(), true);
})
->otherwise(static function($e) {
// 异常错误处理
})
->wait();
print_r($res);use Psr\Http\Message\ResponseInterface;
$res = $instance
->chain('alipay.trade.wap.pay')
->postAsync([
'subject' => '商品名称',
'out_trade_no' => '22',
'total_amount' => '0.01',
'product_code' => 'FAST_INSTANT_TRADE_PAY',
'quit_url' => 'https://forum.alipay.com/mini-app/post/15501011',
], ['pager' => true])
->then(static function(ResponseInterface $response) {
// 正常逻辑回调处理
return (string) $response->getBody();
})
->otherwise(static function($e) {
// 异常错误处理
})
->wait();
print_r($res);use GuzzleHttp\Utils;
use GuzzleHttp\Exception\RequestException;
try {
$res = $instance['alipay.trade.page.pay']
->post(['content' => [
'subject' => '商品名称',
'out_trade_no' => '22',
'total_amount' => '0.01',
'product_code' => 'FAST_INSTANT_TRADE_PAY',
], 'pager' => true]);
echo $resp->getBody(), PHP_EOL;
} catch (RequestException $e) {
// 进行错误处理
} catch (\Throwable $e) {
// 异常错误处理
}use GuzzleHttp\Utils;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Psr\MultipartStream;
use Psr\Http\Message\ResponseInterface;
$media = new MultipartStream([
'name' => 'image_content',
'contents' => 'file:///path/for/uploading.jpg',
]);
$res = $instance
->chain('alipay.offline.material.image.upload')
->postAsync([
'body' => $media,
])
->then(static function(ResponseInterface $response) {
// 正常逻辑回调处理
return Utils::jsonDecode((string) $response->getBody(), true);
})
->otherwise(static function($e) {
// 异常错误处理
})
->wait();
print_r($res);use EasyAlipay\Crypto\AesCbc;
use GuzzleHttp\Utils;
use Psr\Http\Message\ResponseInterface;
$aesCipherKey = '';
$res = $instance
->chain('some.method.response.by.aes.encrypted')
->postAsync([])
->then(static function(ResponseInterface $response) use ($aesCipherKey) {
$json = Utils::jsonDecode((string) $response->getBody());
return AesCbc::decrypt((string) $json->response, $aesCipherKey);
})
->wait();
print_r($res);