first commit

This commit is contained in:
2025-11-28 10:08:12 +08:00
commit 09a14bf0a3
1088 changed files with 145132 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 消失的彩虹海
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+46
View File
@@ -0,0 +1,46 @@
# Alipay SDK for PHP
支付宝开放平台第三方 PHP SDK,基于官方最新版本,支持公钥和公钥证书2种模式。
### 功能特点
- 根据支付宝开放平台最新API开发,相比官方SDK,功能更完善,代码更简洁
- 支持支付宝服务商模式与互联网平台直付通模式
- 支持Composer安装,无需加载多余组件,可应用于任何平台或框架
- 符合`PSR`标准,你可以各种方便的与你的框架集成
- 基本完善的PHPDoc,可以随心所欲添加本项目中没有的API接口
### 环境要求
`PHP` >= 7.1
### 使用方法
1. Composer 安装。
```bash
composer require cccyun/alipay-sdk
```
2. 创建配置文件 [`config.php`](./examples/config.php),填写配置信息。
3. 引入配置文件,构造请求参数,调用AlipayTradeService中的方法发起请求,参考 [`examples/qrpay.php`](./examples/qrpay.php)
4. 更多实例,请移步 [`examples`](examples/) 目录。
5. AlipayService实现类功能说明
| 类名 | 说明 |
| --------------------- | -------------------------------------------------------- |
| AlipayTradeService | 支付宝交易功能,基本上所有支付产品都用这个 |
| AlipayOauthService | 支付宝快捷登录功能,用于JS支付快捷登录以及第三方应用授权 |
| AlipaySettleService | 支付宝分账功能 |
| AlipayTransferService | 支付宝转账功能 |
| AlipayComplainService | 支付宝交易投诉处理 |
| AlipayCertifyService | 支付宝身份认证 |
| AlipayCertdocService | 支付宝实名证件信息比对验证 |
| AlipayBillService | 支付宝账单功能 |
6. 要对接的API在AlipayService实现类中没有,可根据支付宝官方的文档,使用AlipayService类中的aopExecute方法直接调用接口,参考 [`examples/other.php`](./examples/other.php)
+26
View File
@@ -0,0 +1,26 @@
{
"name": "cccyun/alipay-sdk",
"description": "支付宝开放平台第三方 PHP SDK,基于官方最新版本,支持公钥和公钥证书2种模式。",
"type": "library",
"keywords": [
"alipay",
"支付宝"
],
"license": "MIT",
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"php": ">=7.1"
},
"authors": [
{
"name": "caihong",
"email": "admin@cccyun.cn"
}
],
"autoload": {
"psr-4": {
"Alipay\\": "src/"
}
}
}
@@ -0,0 +1,146 @@
<?php
namespace Alipay;
use Exception;
/**
* 支付宝账单服务类
* @see https://opendocs.alipay.com/open/01inem
*/
class AlipayBillService extends AlipayService
{
public function __construct($config)
{
parent::__construct($config);
}
/**
* 账户卖出交易查询
* @param string $start_time 创建时间的起始
* @param string $end_time 创建时间的结束
* @param int $page_no 分页号,从1开始
* @param int $page_size 分页大小1000-2000,默认2000
* @return mixed {"page_no":"1","page_size":"2000","total_size":"10000","detail_list":[]}
* @throws Exception
*/
public function sellQuery(string $start_time, string $end_time, int $page_no = 1, int $page_size = 2000)
{
$apiName = 'alipay.data.bill.sell.query';
$bizContent = [
'start_time' => $start_time,
'end_time' => $end_time,
'page_no' => $page_no,
'page_size' => $page_size,
];
return $this->aopExecute($apiName, $bizContent);
}
/**
* 账户买入交易查询
* @param string $start_time 创建时间的起始
* @param string $end_time 创建时间的结束
* @param int $page_no 分页号,从1开始
* @param int $page_size 分页大小1000-2000,默认2000
* @return mixed {"page_no":"1","page_size":"2000","total_size":"10000","detail_list":[]}
* @throws Exception
*/
public function buyQuery(string $start_time, string $end_time, int $page_no = 1, int $page_size = 2000)
{
$apiName = 'alipay.data.bill.buy.query';
$bizContent = [
'start_time' => $start_time,
'end_time' => $end_time,
'page_no' => $page_no,
'page_size' => $page_size,
];
return $this->aopExecute($apiName, $bizContent);
}
/**
* 账户账务明细查询
* @param string $start_time 创建时间的起始
* @param string $end_time 创建时间的结束
* @param int $page_no 分页号,从1开始
* @param int $page_size 分页大小1000-2000,默认2000
* @param null $bill_user_id 指定用户做账单查询
* @return mixed {"page_no":"1","page_size":"2000","total_size":"10000","detail_list":[]}
* @throws Exception
*/
public function accountlogQuery(string $start_time, string $end_time, int $page_no = 1, int $page_size = 2000, $bill_user_id = null)
{
$apiName = 'alipay.data.bill.accountlog.query';
$bizContent = [
'start_time' => $start_time,
'end_time' => $end_time,
'page_no' => $page_no,
'page_size' => $page_size,
];
if ($bill_user_id) $bizContent['bill_user_id'] = $bill_user_id;
return $this->aopExecute($apiName, $bizContent);
}
/**
* 账户充值,转账,提现查询
* @param string $start_time 创建时间的起始
* @param string $end_time 创建时间的结束
* @param int $page_no 分页号,从1开始
* @param int $page_size 分页大小1000-2000,默认2000
* @return mixed {"page_no":"1","page_size":"2000","total_size":"10000","detail_list":[]}
* @throws Exception
*/
public function transferQuery(string $start_time, string $end_time, int $page_no = 1, int $page_size = 2000)
{
$apiName = 'alipay.data.bill.transfer.query';
$bizContent = [
'start_time' => $start_time,
'end_time' => $end_time,
'page_no' => $page_no,
'page_size' => $page_size,
];
return $this->aopExecute($apiName, $bizContent);
}
/**
* 账户当前余额查询
* @return mixed {"total_amount":"支付宝账户余额","available_amount":"账户可用余额","freeze_amount":"冻结金额","settle_amount":"待结算金额"}
* @throws Exception
*/
public function balanceQuery()
{
$apiName = 'alipay.data.bill.balance.query';
return $this->aopExecute($apiName);
}
/**
* 申请电子回单
* @param string $type 申请的类型
* @param string $key 根据不同业务类型,传入不同参数
* @return mixed {"file_id":"文件申请号"}
* @throws Exception
*/
public function ereceiptApply(string $type, string $key)
{
$apiName = 'alipay.data.bill.ereceipt.apply';
$bizContent = [
'type' => $type,
'key' => $key,
];
return $this->aopExecute($apiName, $bizContent);
}
/**
* 查询电子回单状态
* @param string $file_id 文件申请号
* @return mixed {"status":"处理状态","download_url":"下载链接","error_message":"失败原因"}
* @throws Exception
*/
public function ereceiptQuery(string $file_id)
{
$apiName = 'alipay.data.bill.ereceipt.query';
$bizContent = [
'file_id' => $file_id
];
return $this->aopExecute($apiName, $bizContent);
}
}
@@ -0,0 +1,105 @@
<?php
namespace Alipay;
use Exception;
/**
* 支付宝实名证件信息比对验证服务类
* @see https://opendocs.alipay.com/open/01bny6
*/
class AlipayCertdocService extends AlipayService
{
/**
* @param array $config 支付宝配置信息
*/
public function __construct(array $config)
{
parent::__construct($config);
}
/**
* 实名证件信息比对验证预咨询
* @param string $cert_name 真实姓名
* @param string $cert_no 证件号码
* @return mixed {"code":"10000","msg":"Success","verify_id":"申请验证ID"}
* @throws Exception
*/
public function preconsult(string $cert_name, string $cert_no)
{
$apiName = 'alipay.user.certdoc.certverify.preconsult';
$bizContent = array(
'user_name' => $cert_name, //真实姓名
'cert_type' => 'IDENTITY_CARD', //证件类型
'cert_no' => $cert_no
);
return $this->aopExecute($apiName, $bizContent);
}
/**
* 实名证件信息比对验证咨询
* @param string $verify_id 申请验证ID
* @param string $auth_token 用户授权令牌
* @return mixed {"code":"10000","msg":"Success","passed":"F","fail_reason":"姓名不一致","fail_params":"[\\\"user_name\\\"]"}
* @throws Exception
*/
public function consult(string $verify_id, string $auth_token)
{
$apiName = 'alipay.user.certdoc.certverify.consult';
$bizContent = array(
'verify_id' => $verify_id,
);
$params = [
'auth_token' => $auth_token
];
return $this->aopExecute($apiName, $bizContent, $params);
}
/**
* 跳转支付宝授权页面
* @param string $redirect_uri 回调地址
* @param string $verify_id 申请验证ID
* @param $state
* @param bool $is_get_url 是否只返回url
* @return void|string
*/
public function oauth(string $redirect_uri, string $verify_id, $state = null, bool $is_get_url = false)
{
$param = [
'app_id' => $this->appId,
'scope' => 'id_verify',
'redirect_uri' => $redirect_uri,
'cert_verify_id' => $verify_id
];
if($state) $param['state'] = $state;
$url = 'https://openauth.alipay.com/oauth2/publicAppAuthorize.htm?'.http_build_query($param);
if ($is_get_url) {
return $url;
}
header("Location: $url");
exit();
}
/**
* 换取授权访问令牌
* @param string $code 授权码或刷新令牌
* @param string $grant_type 授权方式(authorization_code,refresh_token)
* @return mixed {"user_id":"支付宝用户的唯一标识","open_id":"支付宝用户的唯一标识","access_token":"访问令牌","expires_in":"3600","refresh_token":"刷新令牌","re_expires_in":"3600"}
* @throws Exception
*/
public function getToken(string $code, string $grant_type = 'authorization_code')
{
$apiName = 'alipay.system.oauth.token';
$params = [];
$params['grant_type'] = $grant_type;
if($grant_type == 'refresh_token'){
$params['refresh_token'] = $code;
}else{
$params['code'] = $code;
}
return $this->aopExecute($apiName, null, $params);
}
}
@@ -0,0 +1,83 @@
<?php
namespace Alipay;
use Exception;
/**
* 支付宝身份认证服务类
* @see https://opendocs.alipay.com/open/repo-013ubq
*/
class AlipayCertifyService extends AlipayService
{
//认证成功返回页面
private $return_url;
/**
* @param array $config 支付宝配置信息
*/
public function __construct(array $config)
{
parent::__construct($config);
$this->return_url = $config['return_url'];
}
/**
* 身份认证初始化服务
* @param string $outer_order_no 商户请求的唯一标识
* @param string $cert_name 真实姓名
* @param string $cert_no 证件号码
* @param string $cert_type 证件类型
* @param string $biz_code 认证场景码(FACE、SMART_FACE
* @return mixed {"code":"10000","msg":"Success","certify_id":"本次申请操作的唯一标识"}
*
* @throws Exception
* @see https://opendocs.alipay.com/open/02ahjy
*/
public function initialize(string $outer_order_no, string $cert_name, string $cert_no, string $cert_type = 'IDENTITY_CARD', string $biz_code = 'SMART_FACE')
{
$apiName = 'alipay.user.certify.open.initialize';
$bizContent = [
'outer_order_no' => $outer_order_no, //商户请求的唯一标识
'biz_code' => $biz_code, //认证场景码
'identity_param' => [
'identity_type' => 'CERT_INFO', //身份信息参数类型
'cert_type' => $cert_type, //证件类型
'cert_name' => $cert_name, //真实姓名
'cert_no' => $cert_no, //证件号码
],
'merchant_config' => ['return_url'=>$this->return_url], //商户个性化配置
];
return $this->aopExecute($apiName, $bizContent);
}
/**
* 身份认证开始认证
* @param string $certify_id 本次申请操作的唯一标识
* @return string html表单
* @throws Exception
*/
public function certify(string $certify_id): string
{
$apiName = 'alipay.user.certify.open.certify';
$bizContent = array(
'certify_id' => $certify_id,
);
return $this->aopPageExecute($apiName, $bizContent);
}
/**
* 身份认证记录查询
* @param string $certify_id 本次申请操作的唯一标识
* @return mixed {"code":"10000","msg":"Success","passed":"T"}
* @throws Exception
*/
public function query(string $certify_id)
{
$apiName = 'alipay.user.certify.open.query';
$bizContent = array(
'certify_id' => $certify_id,
);
return $this->aopExecute($apiName, $bizContent);
}
}
@@ -0,0 +1,216 @@
<?php
namespace Alipay;
use Exception;
/**
* 支付宝交易投诉处理类
* @see https://opendocs.alipay.com/open/02z18r
* @see https://opendocs.alipay.com/pre-open/repo-02ei7s
*/
class AlipayComplainService extends AlipayService
{
public function __construct($config)
{
parent::__construct($config);
}
/**
* 查询单条交易投诉详情
* @param string $complain_event_id 支付宝侧投诉单号
* @return mixed {"complain_event_id":"支付宝侧投诉单号","status":"MERCHANT_PROCESSING","trade_no":"支付宝交易号","merchant_order_no":"商家订单号","gmt_create":"投诉单创建时间","gmt_modified":"投诉单修改时间","gmt_finished":"投诉单完结时间","leaf_category_name":"用户投诉诉求","complain_reason":"用户投诉原因","content":"用户投诉内容","images":[],"phone_no":"投诉人电话号码","trade_amount":"交易金额","reply_detail_infos":[]}
* @throws Exception
*/
public function query(string $complain_event_id)
{
$apiName = 'alipay.merchant.tradecomplain.query';
$bizContent = [
'complain_event_id' => $complain_event_id,
];
return $this->aopExecute($apiName, $bizContent);
}
/**
* 查询交易投诉列表
* @param string|null $status 状态
* @param string|null $begin_time 查询开始时间
* @param string|null $end_time 查询结束时间
* @param int $page_num 当前页
* @param int $page_size 每页条数,最多支持20条
* @return mixed {"page_size":10,"page_num":1,"total_page_num":5,"total_num":55,"trade_complain_infos":[]}
* @throws Exception
*/
public function batchQuery(string $status = null, string $begin_time = null, string $end_time = null, int $page_num = 1, int $page_size = 10)
{
$apiName = 'alipay.merchant.tradecomplain.batchquery';
$bizContent = [
'page_num' => $page_num,
'page_size' => $page_size,
];
if ($status) $bizContent['status'] = $status;
if ($begin_time) $bizContent['begin_time'] = $begin_time;
if ($end_time) $bizContent['end_time'] = $end_time;
return $this->aopExecute($apiName, $bizContent);
}
/**
* 商户上传处理图片
* @param string $file_path 文件路径
* @param string $file_name 文件名
* @return string 图片资源标识
* @throws Exception
*/
public function imageUpload(string $file_path, string $file_name): string
{
$image_type = array_pop(explode('.',$file_name));
if (empty($image_type)) $image_type = 'png';
$apiName = 'alipay.merchant.image.upload';
$params = [
'image_type' => $image_type,
'image_content' => new \CURLFile($file_path, '', $file_name),
];
$result = $this->aopExecute($apiName, null, $params);
return $result['image_id'];
}
/**
* 商家处理交易投诉
* @param string $complain_event_id 投诉单号
* @param string $feedback_code 反馈类目ID
* @param string $feedback_content 反馈内容
* @param string|null $feedback_images 反馈图片id列表(多个用逗号隔开)
* @return bool
* @throws Exception
*/
public function feedbackSubmit(string $complain_event_id, string $feedback_code, string $feedback_content, string $feedback_images = null): bool
{
$apiName = 'alipay.merchant.tradecomplain.feedback.submit';
$bizContent = [
'complain_event_id' => $complain_event_id,
'feedback_code' => $feedback_code,
'feedback_content' => $feedback_content,
];
if ($feedback_images) $bizContent['feedback_images'] = $feedback_images;
$this->aopExecute($apiName, $bizContent);
return true;
}
/**
* 商家留言回复
* @param string $complain_event_id 投诉单号
* @param string $reply_content 回复内容
* @param string|null $reply_images 回复图片(多个用逗号隔开)
* @return bool
* @throws Exception
*/
public function replySubmit(string $complain_event_id, string $reply_content, string $reply_images = null): bool
{
$apiName = 'alipay.merchant.tradecomplain.reply.submit';
$bizContent = [
'complain_event_id' => $complain_event_id,
'reply_content' => $reply_content,
];
if ($reply_images) $bizContent['reply_images'] = $reply_images;
$this->aopExecute($apiName, $bizContent);
return true;
}
/**
* 商家补充凭证
* @param string $complain_event_id 投诉单号
* @param string $supplement_content 文字凭证
* @param string|null $supplement_images 图片凭证(多个用逗号隔开)
* @return bool
* @throws Exception
*/
public function supplementSubmit(string $complain_event_id, string $supplement_content, string $supplement_images = null): bool
{
$apiName = 'alipay.merchant.tradecomplain.supplement.submit';
$bizContent = [
'complain_event_id' => $complain_event_id,
'supplement_content' => $supplement_content,
];
if ($supplement_images) $bizContent['supplement_images'] = $supplement_images;
$this->aopExecute($apiName, $bizContent);
return true;
}
/**
* RiskGO查询单条交易投诉详情
* @param string $complain_id 支付宝侧投诉单号
* @return mixed
* @throws Exception
*/
public function riskquery(string $complain_id)
{
$apiName = 'alipay.security.risk.complaint.info.query';
$bizContent = [
'complain_id' => $complain_id,
];
return $this->aopExecute($apiName, $bizContent);
}
/**
* RiskGO查询交易投诉列表
* @param string|null $status 状态
* @param string|null $begin_time 查询开始时间
* @param string|null $end_time 查询结束时间
* @param int $page_num 当前页
* @param int $page_size 每页条数,最多支持20条
* @return mixed {"page_size":10,"page_num":1,"total_page_num":5,"total_num":55,"trade_complain_infos":[]}
* @throws Exception
*/
public function riskbatchQuery(string $status = null, string $begin_time = null, string $end_time = null, int $page_num = 1, int $page_size = 10)
{
$apiName = 'alipay.security.risk.complaint.info.batchquery';
$bizContent = [
'current_page_num' => $page_num,
'page_size' => $page_size,
];
if ($status) $bizContent['status_list'] = [$status];
if ($begin_time) $bizContent['begin_time'] = $begin_time;
if ($end_time) $bizContent['end_time'] = $end_time;
return $this->aopExecute($apiName, $bizContent);
}
/**
* RiskGO商户上传处理图片
* @param string $file_path 文件路径
* @param string $file_name 文件名
* @return mixed 图片资源标识
* @throws Exception
*/
public function riskimageUpload(string $file_path, string $file_name)
{
$apiName = 'alipay.security.risk.complaint.file.upload';
$params = [
'file_content' => new \CURLFile($file_path, '', $file_name),
];
return $this->aopExecute($apiName, null, $params);
}
/**
* RiskGO商家处理交易投诉
* @param string $complain_id 投诉单号
* @param string $process_code 投诉处理结果码
* @param string $remark 备注
* @param array|null $img_file_list 图片文件列表
* @return bool
* @throws Exception
*/
public function riskfeedbackSubmit(string $complain_id, string $process_code, string $remark, array $img_file_list = null): bool
{
$apiName = 'alipay.security.risk.complaint.process.finish';
$bizContent = [
'id_list' => [$complain_id],
'process_code' => $process_code,
'remark' => $remark
];
if ($img_file_list) $bizContent['img_file_list'] = $img_file_list;
$result = $this->aopExecute($apiName, $bizContent);
return $result['complaint_process_success'];
}
}
@@ -0,0 +1,198 @@
<?php
namespace Alipay;
use Exception;
/**
* 支付宝快捷登录服务类
* @see https://opendocs.alipay.com/open/repo-01480o
*/
class AlipayOauthService extends AlipayService
{
/**
* @param array $config 支付宝配置信息
*/
public function __construct(array $config)
{
if(isset($config['app_auth_token'])) unset($config['app_auth_token']);
parent::__construct($config);
}
/**
* 跳转支付宝授权页面
* @param string $redirect_uri 回调地址
* @param string $state
* @param string $scope 授权范围(auth_base,auth_user)
* @param bool $is_get_url 是否只返回url
* @return void|string
*/
public function oauth(string $redirect_uri, $state = null, $scope = 'auth_base', bool $is_get_url = false)
{
$param = [
'app_id' => $this->appId,
'scope' => $scope,
'redirect_uri' => $redirect_uri,
];
if($state) $param['state'] = $state;
$url = 'https://openauth.alipay.com/oauth2/publicAppAuthorize.htm?'.http_build_query($param);
if ($is_get_url) {
return $url;
}
header("Location: $url");
exit();
}
/**
* 换取授权访问令牌
* @param string $code 授权码或刷新令牌
* @param string $grant_type 授权方式(authorization_code,refresh_token)
* @return mixed {"user_id":"支付宝用户的唯一标识","open_id":"支付宝用户的唯一标识","access_token":"访问令牌","expires_in":"3600","refresh_token":"刷新令牌","re_expires_in":"3600"}
* @throws Exception
*/
public function getToken(string $code, string $grant_type = 'authorization_code')
{
$apiName = 'alipay.system.oauth.token';
$params = [];
$params['grant_type'] = $grant_type;
if($grant_type == 'refresh_token'){
$params['refresh_token'] = $code;
}else{
$params['code'] = $code;
}
return $this->aopExecute($apiName, null, $params);
}
/**
* 支付宝会员授权信息查询
* @param string $accessToken 用户授权令牌
* @return mixed {"code":"10000","msg":"Success","user_id":"支付宝用户的userId","avatar":"用户头像地址","city":"市名称","nick_name":"用户昵称","province":"省份名称","gender":"性别MF"}
* @throws Exception
*/
public function userinfo(string $accessToken)
{
$apiName = 'alipay.user.info.share';
$params = [
'auth_token' => $accessToken
];
return $this->aopExecute($apiName, null, $params);
}
/**
* 跳转支付宝第三方应用授权页面
* @param string $redirect_uri 回调地址
* @param $state
* @param bool $is_get_url 是否只返回url
* @return void|string
*/
public function appOauth(string $redirect_uri, $state = null, bool $is_get_url = false)
{
$param = [
'app_id' => $this->appId,
'redirect_uri' => $redirect_uri,
];
if($state) $param['state'] = $state;
$url = 'https://openauth.alipay.com/oauth2/appToAppAuth.htm?'.http_build_query($param);
if ($is_get_url) {
return $url;
}
header("Location: $url");
exit();
}
/**
* 跳转支付宝指定应用授权页面
* @param string $redirect_uri 回调地址
* @param array $app_types 对商家应用的限制类型
* @param $state
* @return array [PC端url, APP端url]
*/
public function appOauthAssign(string $redirect_uri, array $app_types, $state = null): array
{
$param = [
'platformCode' => 'O',
'taskType' => 'INTERFACE_AUTH',
'agentOpParam' => [
'redirectUri' => $redirect_uri,
'appTypes' => $app_types,
'isvAppId' => $this->appId,
'state' => $state
],
];
$biz_data = json_encode($param, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE);
$pc_url = 'https://b.alipay.com/page/message/tasksDetail?bizData='.rawurlencode($biz_data);
$app_url = 'alipays://platformapi/startapp?appId=2021003130652097&page=pages%2Fauthorize%2Findex%3FbizData%3D'.rawurlencode($biz_data);
return [$pc_url, $app_url];
}
/**
* 换取授权访问令牌
* @param string $code 授权码或刷新令牌
* @param string $grant_type 授权方式(authorization_code,refresh_token)
* @return mixed {"user_id":"授权商户的user_id","auth_app_id":"授权商户的appid","app_auth_token":"应用授权令牌","app_refresh_token":"刷新令牌","re_expires_in":"3600"}
* @throws Exception
*/
public function getAppToken(string $code, string $grant_type = 'authorization_code')
{
$apiName = 'alipay.open.auth.token.app';
$bizContent = [
'grant_type' => $grant_type,
];
if($grant_type == 'refresh_token'){
$bizContent['refresh_token'] = $code;
}else{
$bizContent['code'] = $code;
}
return $this->aopExecute($apiName, $bizContent);
}
/**
* 查询授权商家信息
* @param string $appAuthToken 应用授权令牌
* @return mixed {"user_id":"授权商户的user_id","auth_app_id":"授权商户的appid","expires_in":31536000,"auth_methods":[],"auth_start":"授权生效时间","auth_end":"授权失效时间","status":"valid/invalid","is_by_app_auth":true}
* @throws Exception
*/
public function appQuery(string $appAuthToken)
{
$apiName = 'alipay.open.auth.token.app.query';
$bizContent = [
'app_auth_token' => $appAuthToken,
];
return $this->aopExecute($apiName, $bizContent);
}
public function decryptMobile(array $response, string $key)
{
if(is_string($response['response'])){
/*if(!$this->client->rsaPubilcVerify('"'.$response['response'].'"', $response['sign'])){
throw new Exception('手机号码数据验签失败');
}*/
$data = $this->client->aesDecrypt($response['response'], $key);
if(!$data) {
throw new Exception('手机号码数据解密失败');
}
$result = json_decode($data, true);
if($result['code'] == '10000'){
return $result['mobile'];
}elseif(isset($result['subMsg'])){
throw new Exception($result['subMsg']);
}else{
throw new Exception('手机号码数据解密失败 '.$result['msg']);
}
}elseif(isset($response['response']['subCode']) && isset($response['response']['subMsg'])){
throw new Exception('['.$response['response']['subCode'].']'.$response['response']['subMsg']);
}else{
throw new Exception('手机号码加密数据错误');
}
}
}
+197
View File
@@ -0,0 +1,197 @@
<?php
namespace Alipay;
use Alipay\Aop\AopClient;
use Alipay\Aop\AlipayCertHelper;
use Alipay\Aop\AlipayRequest;
use Alipay\Aop\AlipayResponseException;
use Exception;
use InvalidArgumentException;
class AlipayService
{
//AopClient
protected $client;
//是否公钥证书模式
protected $isCertMode = false;
//应用ID
protected $appId;
//异步通知回调地址
protected $notifyUrl;
//同步通知回调地址
protected $returnUrl;
//服务商模式子商户token
protected $appAuthToken;
//日志文件夹路径
protected $logPath;
//页面跳转接口返回类型
protected $pageMethod;
/**
* @param array $config 支付宝配置信息
* @throws InvalidArgumentException
*/
public function __construct(array $config)
{
if (empty($config['app_id'])) {
throw new InvalidArgumentException('应用AppID不能为空');
}
if (empty($config['app_private_key'])) {
throw new InvalidArgumentException("应用私钥不能为空");
}
if (empty($config['alipay_public_key']) && empty($config['alipay_cert_path'])) {
throw new InvalidArgumentException("支付宝公钥不能为空");
}
$this->appId = $config['app_id'];
if (!empty($config['app_cert_path']) && !empty($config['alipay_cert_path']) && !empty($config['root_cert_path']) && (!isset($config['cert_mode']) || $config['cert_mode'] == 1)) {
$this->isCertMode = true;
}
if (isset($config['app_auth_token'])) {
$this->appAuthToken = $config['app_auth_token'];
}
if (isset($config['logPath'])) {
$this->logPath = $config['logPath'];
}
if (isset($config['pageMethod'])) {
$this->pageMethod = $config['pageMethod'];
}
$this->client = new AopClient();
$this->client->appId = $config['app_id'];
if (!empty($config['gateway_url'])) {
$this->client->gatewayUrl = $config['gateway_url'];
}
if (!empty($config['sign_type'])) {
$this->client->signType = $config['sign_type'];
}
if (!empty($config['charset'])) {
$this->client->charset = $config['charset'];
}
$this->client->rsaPrivateKey = $config['app_private_key'];
if ($this->isCertMode) {
$this->client->rsaPublicKeyFilePath = $config['alipay_cert_path'];
$this->client->appCertSN = AlipayCertHelper::getCertSN($config['app_cert_path']);
$this->client->alipayRootCertSN = AlipayCertHelper::getRootCertSN($config['root_cert_path']);
} else {
$this->client->rsaPublicKey = $config['alipay_public_key'];
}
}
/**
* 发起接口请求
*
* @param string $apiName 接口名称
* @param array|null $bizContent 请求参数的集合
* @param array|null $params 其他公共参数
* @return mixed
* @throws Exception
*/
public function aopExecute(string $apiName, array $bizContent = null, array $params = null)
{
$request = new AlipayRequest();
$request->setApiMethodName($apiName);
$request->setNotifyUrl($this->notifyUrl);
$request->setAppAuthToken($this->appAuthToken);
$request->setBizContent($bizContent);
if (is_array($params) && count($params) > 0) {
$request->setOtherParams($params);
}
$result = $this->client->execute($request)->getData();
if ($apiName == 'alipay.system.oauth.token' && isset($result['access_token'])) {
return $result;
} elseif (isset($result['code']) && $result['code'] == '10000') {
return $result;
} else {
throw new AlipayResponseException($result);
}
}
/**
* 页面跳转接口,返回form表单html
*
* @param string $apiName 接口名称
* @param array|null $bizContent 请求参数的集合
* @param array|null $params 其他公共参数
* @return string
* @throws Exception
*/
public function aopPageExecute(string $apiName, array $bizContent = null, array $params = null): string
{
$request = new AlipayRequest();
$request->setApiMethodName($apiName);
$request->setNotifyUrl($this->notifyUrl);
$request->setReturnUrl($this->returnUrl);
$request->setAppAuthToken($this->appAuthToken);
$request->setBizContent($bizContent);
if (is_array($params) && count($params) > 0) {
$request->setOtherParams($params);
}
if (!empty($this->pageMethod)) {
switch ($this->pageMethod) {
case '2':
$httpmethod = 'REDIRECT';
break;
case '1':
$httpmethod = 'GET';
break;
default:
$httpmethod = 'POST';
break;
}
return $this->client->pageExecute($request, $httpmethod);
} else {
return $this->client->pageExecute($request);
}
}
/**
* APP接口,返回收银台SDK的字符串
*
* @param string $apiName 接口名称
* @param array|null $bizContent 请求参数的集合
* @param array|null $params 其他公共参数
* @return string
*/
public function aopSdkExecute(string $apiName, array $bizContent = null, array $params = null): string
{
$request = new AlipayRequest();
$request->setApiMethodName($apiName);
$request->setNotifyUrl($this->notifyUrl);
$request->setAppAuthToken($this->appAuthToken);
$request->setBizContent($bizContent);
if (is_array($params) && count($params) > 0) {
$request->setOtherParams($params);
}
return $this->client->sdkExecute($request);
}
/**
* 回调验签
* @param array $params 支付宝返回的信息
* @return bool
*/
public function check(array $params): bool
{
return $this->client->verify($params);
}
/**
* 记录日志
*/
public function writeLog($text)
{
if (empty($this->logPath)) return;
//$text=iconv("GBK", "UTF-8//IGNORE", $text);
file_put_contents($this->logPath . "log.txt", date("Y-m-d H:i:s") . " " . $text . "\r\n", FILE_APPEND);
}
}
@@ -0,0 +1,200 @@
<?php
namespace Alipay;
use Exception;
/**
* 支付宝分账服务类
* @see https://opendocs.alipay.com/open/repo-0038ln
*/
class AlipaySettleService extends AlipayService
{
/**
* @param array $config 支付宝配置信息
*/
public function __construct(array $config)
{
parent::__construct($config);
}
/**
* 分账关系绑定
* @param string $type 分账接收方方类型(userId,loginName,openId)
* @param string $account 分账接收方账号
* @param string $name 分账接收方真实姓名
* @return bool
* @throws Exception
*/
public function relation_bind(string $type, string $account, string $name): bool
{
$apiName = 'alipay.trade.royalty.relation.bind';
$out_request_no = date("YmdHis").rand(11111,99999);
$receiver = [
'type' => $type,
'account' => $account,
];
if(!empty($name)) $receiver['name'] = $name;
$bizContent = array(
'receiver_list' => [
$receiver
],
'out_request_no' => $out_request_no,
);
$this->aopExecute($apiName, $bizContent);
return true;
}
/**
* 分账关系解绑
* @param string $type 分账接收方方类型(userId,loginName,openId)
* @param string $account 分账接收方账号
* @return bool
* @throws Exception
*/
public function relation_unbind(string $type, string $account): bool
{
$apiName = 'alipay.trade.royalty.relation.unbind';
$out_request_no = date("YmdHis").rand(11111,99999);
$receiver = [
'type' => $type,
'account' => $account,
];
$bizContent = array(
'receiver_list' => [
$receiver
],
'out_request_no' => $out_request_no,
);
$this->aopExecute($apiName, $bizContent);
return true;
}
/**
* 分账关系查询
* @param int $page_num 页码
* @param int $page_size 每页条数
* @return array
* @throws Exception
*/
public function relation_batchquery(int $page_num = 1, int $page_size = 20): array
{
$apiName = 'alipay.trade.royalty.relation.batchquery';
$out_request_no = date("YmdHis").rand(11111,99999);
$bizContent = array(
'page_num' => $page_num,
'page_size' => $page_size,
'out_request_no' => $out_request_no,
);
$result = $this->aopExecute($apiName, $bizContent);
return $result['receiver_list'];
}
/**
* 分账请求
* @param string $trade_no 支付宝订单号
* @param string $type 收入方账户类型(userId,cardAliasNo,loginName,openId)
* @param string $account 收入方账户
* @param numeric $money 分账的金额
* @return mixed {"trade_no":"支付宝交易号","settle_no":"支付宝分账单号"}
* @throws Exception
*/
public function order_settle(string $trade_no, string $type, string $account, $money) {
$apiName = 'alipay.trade.order.settle';
$out_request_no = date("YmdHis").rand(11111,99999);
$receiver = [
'trans_in_type' => $type,
'trans_in' => $account,
'amount' => $money
];
$bizContent = array(
'out_request_no' => $out_request_no,
'trade_no' => $trade_no,
'royalty_parameters' => [
$receiver
],
'extend_params' => [
'royalty_finish' => 'true'
]
);
return $this->aopExecute($apiName, $bizContent);
}
/**
* 解冻剩余资金
* @param string $trade_no 支付宝订单号
* @return mixed {"trade_no":"支付宝交易号","settle_no":"支付宝分账单号"}
* @throws Exception
*/
public function order_settle_unfreeze(string $trade_no) {
$apiName = 'alipay.trade.order.settle';
$out_request_no = date("YmdHis").rand(11111,99999);
$bizContent = array(
'out_request_no' => $out_request_no,
'trade_no' => $trade_no,
'extend_params' => [
'royalty_finish' => 'true'
],
);
return $this->aopExecute($apiName, $bizContent);
}
/**
* 分账查询
* @param string $settle_no 支付宝分账单号
* @return mixed {"out_request_no":"商户分账请求单号","operation_dt":"分账受理时间","royalty_detail_list":[{"operation_type":"transfer","execute_dt":"分账执行时间","trans_out":"2088111111111111","trans_out_type":"userId","trans_in":"2088111111112222","trans_in_type":"userId","amount":10,"state":"FAIL","error_code":"TXN_RESULT_ACCOUNT_BALANCE_NOT_ENOUGH","error_desc":"分账余额不足"}]}
* @throws Exception
*/
public function order_settle_query(string $settle_no) {
$apiName = 'alipay.trade.order.settle.query';
$bizContent = array(
'settle_no' => $settle_no,
);
return $this->aopExecute($apiName, $bizContent);
}
/**
* 分账比例查询
* @return mixed {"user_id":"2088XXXX1234","max_ratio":80}
* @throws Exception
*/
public function rate_query() {
$apiName = 'alipay.trade.royalty.rate.query';
$out_request_no = date("YmdHis").rand(11111,99999);
$bizContent = array(
'out_request_no' => $out_request_no,
);
return $this->aopExecute($apiName, $bizContent);
}
/**
* 退分账
* @param string $trade_no 支付宝交易号
* @param string $type 支出方账户类型(userId,loginName)
* @param string $account 支出方账户
* @param numeric $money 分账的金额
* @return true
* @throws Exception
*/
public function order_settle_refund(string $trade_no, string $type, string $account, $money): bool
{
$apiName = 'alipay.trade.refund';
$out_request_no = date("YmdHis").rand(11111,99999);
$receiver = [
'royalty_type' => 'transfer',
'trans_out_type' => $type,
'trans_out' => $account,
'amount' => $money
];
$bizContent = array(
'trade_no' => $trade_no,
'refund_amount' => '0',
'out_request_no' => $out_request_no,
'refund_royalty_parameters' => [
$receiver
],
);
$this->aopExecute($apiName, $bizContent);
return true;
}
}
@@ -0,0 +1,409 @@
<?php
namespace Alipay;
use Exception;
/**
* 支付宝交易服务类
*/
class AlipayTradeService extends AlipayService
{
//互联网直付通模式子商户ID
private $smid;
/**
* @param array $config 支付宝配置信息
*/
public function __construct(array $config)
{
parent::__construct($config);
if (isset($config['smid'])) {
$this->smid = $config['smid'];
}
if (isset($config['notify_url'])) {
$this->notifyUrl = $config['notify_url'];
}
if (isset($config['return_url'])) {
$this->returnUrl = $config['return_url'];
}
}
/**
* 付款码支付
* @param array $bizContent 请求参数的集合
* @return mixed {"trade_no":"支付宝交易号","out_trade_no":"商户订单号","open_id":"买家支付宝userid","buyer_logon_id":"买家支付宝账号"}
* @throws Exception
* @see https://opendocs.alipay.com/open/02ekfp?ref=api&scene=32
*/
public function scanPay(array $bizContent)
{
$apiName = 'alipay.trade.pay';
return $this->aopExecute($apiName, $bizContent);
}
/**
* 扫码支付
* @param array $bizContent 请求参数的集合
* @return mixed {"out_trade_no":"商户订单号","qr_code":"二维码链接"}
* @throws Exception
* @see https://opendocs.alipay.com/open/02ekfg?ref=api&scene=19
*/
public function qrPay(array $bizContent)
{
$apiName = 'alipay.trade.precreate';
return $this->aopExecute($apiName, $bizContent);
}
/**
* JS支付
* @param array $bizContent 请求参数的集合
* @return mixed {"trade_no":"支付宝交易号","out_trade_no":"商户订单号"}
* @throws Exception
* @see https://opendocs.alipay.com/open/02ekfj?ref=api
*/
public function jsPay(array $bizContent)
{
$apiName = 'alipay.trade.create';
return $this->aopExecute($apiName, $bizContent);
}
/**
* APP支付
* @param array $bizContent 请求参数的集合
* @return string SDK请求串
* @see https://opendocs.alipay.com/open/02e7gq?ref=api&scene=20
*/
public function appPay(array $bizContent): string
{
$apiName = 'alipay.trade.app.pay';
return $this->aopSdkExecute($apiName, $bizContent);
}
/**
* 电脑网站支付
* @param array $bizContent 请求参数的集合
* @return string html表单
* @throws Exception
* @see https://opendocs.alipay.com/open/028r8t?ref=api&scene=22
*/
public function pagePay(array $bizContent): string
{
$apiName = 'alipay.trade.page.pay';
$bizContent['product_code'] = 'FAST_INSTANT_TRADE_PAY';
return $this->aopPageExecute($apiName, $bizContent);
}
/**
* 手机网站支付
* @param array $bizContent 请求参数的集合
* @return string html表单
* @throws Exception
* @see https://opendocs.alipay.com/open/02ivbs?ref=api&scene=21
*/
public function wapPay(array $bizContent): string
{
$apiName = 'alipay.trade.wap.pay';
$bizContent['product_code'] = 'QUICK_WAP_WAY';
return $this->aopPageExecute($apiName, $bizContent);
}
/**
* 交易查询
* @param string|null $trade_no 支付宝交易号
* @param string|null $out_trade_no 商户订单号
* @return mixed {"trade_no":"支付宝交易号","out_trade_no":"商户订单号","open_id":"买家支付宝userid","buyer_logon_id":"买家支付宝账号","trade_status":"TRADE_SUCCESS","total_amount":88.88}
* @throws Exception
*/
public function query(string $trade_no = null, string $out_trade_no = null)
{
$apiName = 'alipay.trade.query';
$bizContent = [];
if ($trade_no) {
$bizContent['trade_no'] = $trade_no;
}
if ($out_trade_no) {
$bizContent['out_trade_no'] = $out_trade_no;
}
return $this->aopExecute($apiName, $bizContent);
}
/**
* 交易是否成功
* @param null $trade_no 支付宝交易号
* @param null $out_trade_no 商户订单号
* @return bool
* @throws Exception
*/
public function queryResult($trade_no = null, $out_trade_no = null): bool
{
$result = $this->query($trade_no, $out_trade_no);
if (isset($result['code']) && $result['code'] == '10000') {
if ($result['trade_status'] == 'TRADE_SUCCESS' || $result['trade_status'] == 'TRADE_FINISHED' || $result['trade_status'] == 'TRADE_CLOSED') {
return true;
}
}
return false;
}
/**
* 交易退款
* @param array $bizContent 请求参数的集合
* @return mixed {"trade_no":"支付宝交易号","out_trade_no":"商户订单号","buyer_user_id":"买家支付宝userid","buyer_logon_id":"买家支付宝账号","fund_change":"Y","refund_fee":88.88}
* @throws Exception
* @see https://opendocs.alipay.com/open/02ekfk?ref=api
*/
public function refund(array $bizContent)
{
$apiName = 'alipay.trade.refund';
return $this->aopExecute($apiName, $bizContent);
}
/**
* 交易退款查询
* @param array $bizContent 请求参数的集合
* @return mixed {"trade_no":"支付宝交易号","out_trade_no":"商户订单号","out_request_no":"退款请求号","refund_status":"REFUND_SUCCESS","total_amount":88.88,"refund_amount":88.88}
* @throws Exception
*/
public function refundQuery(array $bizContent)
{
$apiName = 'alipay.trade.fastpay.refund.query';
return $this->aopExecute($apiName, $bizContent);
}
/**
* 交易撤销
* @param array $bizContent 请求参数的集合
* @return mixed {"trade_no":"支付宝交易号","out_trade_no":"商户订单号","retry_flag":"N是否需要重试","action":"close本次撤销触发的交易动作"}
* @throws Exception
*/
public function cancel(array $bizContent)
{
$apiName = 'alipay.trade.cancel';
return $this->aopExecute($apiName, $bizContent);
}
/**
* 交易关闭
* @param array $bizContent 请求参数的集合
* @return mixed {"trade_no":"支付宝交易号","out_trade_no":"商户订单号"}
* @throws Exception
*/
public function close(array $bizContent)
{
$apiName = 'alipay.trade.close';
return $this->aopExecute($apiName, $bizContent);
}
/**
* 查询对账单下载地址
* @param array $bizContent 请求参数的集合
* @return mixed {"bill_download_url":"账单下载地址"}
* @throws Exception
*/
public function downloadurlQuery(array $bizContent)
{
$apiName = 'alipay.data.dataservice.bill.downloadurl.query';
return $this->aopExecute($apiName, $bizContent);
}
/**
* 支付回调验签
* @param $params array
* @return bool
* @throws Exception
*/
public function check(array $params): bool
{
$result = $this->client->verify($params);
if($result){
$result = $this->queryResult($params['trade_no']);
}
return $result;
}
/**
* 互联网直付通交易额外参数
* @param array &$bizContent 请求参数的集合
* @param string $settle_period_time 最晚结算周期
* @throws Exception
* @see https://opendocs.alipay.com/open/direct-payment/qadp9d
*/
public function directPayParams(array &$bizContent, string $settle_period_time = '1d')
{
if (empty($this->smid)) {
throw new Exception("子商户SMID不能为空");
}
if(strpos($this->smid, ',')){
$smids = explode(',', $this->smid);
$this->smid = $smids[array_rand($smids)];
}
$bizContent['sub_merchant'] = ['merchant_id' => $this->smid];
$bizContent['settle_info'] = [
'settle_period_time' => $settle_period_time,
'settle_detail_infos' => [
[
'trans_in_type' => 'defaultSettle',
'amount' => $bizContent['total_amount']
]
]
];
}
/**
* 互联网直付通确认结算
* @param string $trade_no 支付宝交易号
* @param numeric $settle_amount 结算金额
* @param bool $freeze 冻结标识
* @return mixed {"trade_no":"支付宝交易号","out_request_no":"确认结算请求流水号","settle_amount":"结算金额"}
* @throws Exception
* @see https://opendocs.alipay.com/open/direct-payment/gkvknf
*/
public function settle_confirm(string $trade_no, $settle_amount, bool $freeze = false)
{
$apiName = 'alipay.trade.settle.confirm';
$out_request_no = date("YmdHis").rand(11111,99999);
$bizContent = array(
'out_request_no' => $out_request_no,
'trade_no' => $trade_no,
'settle_info' => [
'settle_detail_infos' => [
[
'trans_in_type' => 'defaultSettle',
'amount' => $settle_amount
]
]
],
);
if($freeze){
$bizContent['extend_params'] = ['royalty_freeze' => 'true'];
}
return $this->aopExecute($apiName, $bizContent);
}
/**
* 合并支付预创建
* @param array $bizContent 请求参数的集合
* @return mixed {"out_merge_no":"合单订单号","pre_order_no":"预下单号"}
* @throws Exception
* @see https://opendocs.alipay.com/open/028xr9
*/
public function mergePrecreatePay(array $bizContent)
{
$apiName = 'alipay.trade.merge.precreate';
$params = null;
if (!empty($this->returnUrl)) {
$params['return_url'] = $this->returnUrl;
}
return $this->aopExecute($apiName, $bizContent, $params);
}
/**
* 手机网站合单支付
* @param array $bizContent 请求参数的集合
* @return string html表单
* @throws Exception
* @see https://opendocs.alipay.com/open/028xra
*/
public function wapMergePay(array $bizContent): string
{
$apiName = 'alipay.trade.wap.merge.pay';
return $this->aopPageExecute($apiName, $bizContent);
}
/**
* APP合单支付
* @param array $bizContent 请求参数的集合
* @return string SDK请求串
* @see https://opendocs.alipay.com/open/028py8
*/
public function appMergePay(array $bizContent): string
{
$apiName = 'alipay.trade.app.merge.pay';
return $this->aopSdkExecute($apiName, $bizContent);
}
/**
* 小程序合单支付
* @param array $bizContent 请求参数的集合
* @return mixed {"out_merge_no":"外部合并单号","merge_no":"合并交易号","order_detail_results":[]}
* @throws Exception
* @see https://opendocs.alipay.com/open/0a0yaq
*/
public function mergeCreate(array $bizContent)
{
$apiName = 'alipay.trade.merge.create';
return $this->aopExecute($apiName, $bizContent);
}
/**
* 线上资金授权冻结
* @param array $bizContent 请求参数的集合
* @return string SDK请求串
* @see https://opendocs.alipay.com/open/repo-0243e2
*/
public function preAuthFreeze(array $bizContent): string
{
$apiName = 'alipay.fund.auth.order.app.freeze';
return $this->aopSdkExecute($apiName, $bizContent);
}
/**
* 资金授权解冻
* @param array $bizContent 请求参数的集合
* @return mixed
* @throws Exception
*/
public function preAuthUnfreeze(array $bizContent)
{
$apiName = 'alipay.fund.auth.order.unfreeze';
return $this->aopExecute($apiName, $bizContent);
}
/**
* 资金授权撤销
* @param array $bizContent 请求参数的集合
* @return mixed
* @throws Exception
*/
public function preAuthCancel(array $bizContent)
{
$apiName = 'alipay.fund.auth.operation.cancel';
return $this->aopExecute($apiName, $bizContent);
}
/**
* 资金授权操作查询接口
* @param array $bizContent 请求参数的集合
* @return mixed
* @throws Exception
*/
public function preAuthQuery(array $bizContent)
{
$apiName = 'alipay.fund.auth.operation.detail.query';
return $this->aopExecute($apiName, $bizContent);
}
/**
* 资金转账页面支付接口
* @param array $bizContent 请求参数的集合
* @return string
* @throws Exception
*/
public function transPagePay(array $bizContent): string
{
$apiName = 'alipay.fund.trans.page.pay';
return $this->aopPageExecute($apiName, $bizContent);
}
/**
* 现金红包无线支付接口
* @param array $bizContent 请求参数的集合
* @return string
*/
public function transAppPay(array $bizContent): string
{
$apiName = 'alipay.fund.trans.app.pay';
return $this->aopSdkExecute($apiName, $bizContent);
}
}
@@ -0,0 +1,197 @@
<?php
namespace Alipay;
use Exception;
/**
* 支付宝转账服务类
* @see https://opendocs.alipay.com/open/309/106235
*/
class AlipayTransferService extends AlipayService
{
/**
* @param array $config 支付宝配置信息
*/
public function __construct(array $config)
{
parent::__construct($config);
}
/**
* 转账到支付宝账号
* @param string $out_biz_no 商户转账唯一订单号
* @param numeric $amount 转账金额
* @param int $is_userid 收款方是否支付宝userid(0支付宝账号,1支付宝UID,2支付宝openid
* @param string $payee_account 收款方账户
* @param string $payee_real_name 收款方姓名
* @param string $payer_show_name 付款方显示姓名
* @return mixed {"out_biz_no":"商户订单号","order_id":"支付宝转账订单号","pay_fund_order_id":"支付宝支付资金流水号","status":"SUCCESS","trans_date":"订单支付时间"}
* @throws Exception
*/
public function transferToAccount(string $out_biz_no, $amount, int $is_userid, string $payee_account, string $payee_real_name, string $payer_show_name)
{
if ($this->isCertMode) {
$apiName = 'alipay.fund.trans.uni.transfer';
switch($is_userid) {
case 2:$payee_type = 'ALIPAY_OPEN_ID';break;
case 1:$payee_type = 'ALIPAY_USER_ID';break;
default:$payee_type = 'ALIPAY_LOGON_ID';break;
}
$bizContent = [
'out_biz_no' => $out_biz_no, //商户转账唯一订单号
'trans_amount' => $amount, //转账金额
'product_code' => 'TRANS_ACCOUNT_NO_PWD',
'biz_scene' => 'DIRECT_TRANSFER',
'order_title' => $payer_show_name, //付款方显示名称
'payee_info' => array('identity' => $payee_account, 'identity_type' => $payee_type),
'business_params' => json_encode(['payer_show_name_use_alias'=>'true']),
];
if(!empty($payee_real_name))$bizContent['payee_info']['name'] = $payee_real_name; //收款方真实姓名
} else {
$apiName = 'alipay.fund.trans.toaccount.transfer';
$payee_type = $is_userid?'ALIPAY_USERID':'ALIPAY_LOGONID';
$bizContent = [
'out_biz_no' => $out_biz_no, //商户转账唯一订单号
'payee_type' => $payee_type, //收款方账户类型
'payee_account' => $payee_account, //收款方账户
'amount' => $amount, //转账金额
'payer_show_name' => $payer_show_name, //付款方显示姓名
];
if(!empty($payee_real_name))$bizContent['payee_real_name'] = $payee_real_name; //收款方真实姓名
}
$result = $this->aopExecute($apiName, $bizContent);
if(isset($result['pay_date'])) $result['trans_date'] = $result['pay_date'];
return $result;
}
/**
* 转账到银行卡账户
* @param string $out_biz_no 商户转账唯一订单号
* @param numeric $amount 转账金额
* @param string $payee_account 收款方账户
* @param string $payee_real_name 收款方姓名
* @param string $payer_show_name 付款方显示姓名
* @return mixed {"out_biz_no":"商户订单号","order_id":"支付宝转账订单号","pay_fund_order_id":"支付宝支付资金流水号","status":"SUCCESS","trans_date":"订单支付时间"}
* @throws Exception
*/
public function transferToBankCard(string $out_biz_no, $amount, string $payee_account, string $payee_real_name, string $payer_show_name)
{
$apiName = 'alipay.fund.trans.uni.transfer';
$bizContent = [
'out_biz_no' => $out_biz_no, //商户转账唯一订单号
'trans_amount' => $amount, //转账金额
'product_code' => 'TRANS_BANKCARD_NO_PWD',
'biz_scene' => 'DIRECT_TRANSFER',
'order_title' => $payer_show_name, //付款方显示名称
'payee_info' => array(
'identity_type' => 'BANKCARD_ACCOUNT',
'identity' => $payee_account,
'name' => $payee_real_name,
'bankcard_ext_info' => array(
'account_type' => '2'
)
),
];
return $this->aopExecute($apiName, $bizContent);
}
/**
* 转账单据查询
* @param string $order_id 订单号
* @param int $type 订单号类型(0=支付宝转账单据号,1=支付宝支付资金流水号,2=商户转账唯一订单号)
* @param int $code 产品类型(0=转账到支付宝账户,1=转账到银行卡)
* @return mixed {"order_id":"支付宝转账单据号","pay_fund_order_id":"支付宝支付资金流水号","out_biz_no":"商户转账唯一订单号","trans_amount":1,"status":"SUCCESS","pay_date":"支付时间","error_code":"PAYEE_CARD_INFO_ERROR","fail_reason":"收款方银行卡信息有误"}
* @throws Exception
*/
public function query(string $order_id, int $type=0, int $code = 0)
{
$apiName = 'alipay.fund.trans.common.query';
$bizContent = [];
if($type==1){
$bizContent['pay_fund_order_id'] = $order_id;
}elseif($type==2){
$bizContent['out_biz_no'] = $order_id;
}else{
$bizContent['order_id'] = $order_id;
}
if($type==2){
$bizContent['product_code'] = $code == 1 ? 'TRANS_BANKCARD_NO_PWD' : 'TRANS_ACCOUNT_NO_PWD';
$bizContent['biz_scene'] = 'DIRECT_TRANSFER';
}
return $this->aopExecute($apiName, $bizContent);
}
/**
* 账户余额查询
* @param string $alipay_user_id 支付宝用户ID
* @param int $user_type 用户标识类型(0支付宝UID,1支付宝openid
* @return mixed {"available_amount":"账户可用余额","freeze_amount":"实时冻结余额"}
* @throws Exception
*/
public function accountQuery(string $alipay_user_id = null, int $user_type = 0)
{
$apiName = 'alipay.fund.account.query';
if($user_type == 1){
$bizContent = [
'alipay_open_id' => $alipay_user_id,
'account_type' => 'ACCTRANS_ACCOUNT',
];
}else{
$bizContent = [
'alipay_user_id' => $alipay_user_id,
'account_type' => 'ACCTRANS_ACCOUNT',
];
}
return $this->aopExecute($apiName, $bizContent);
}
/**
* 现金红包转账接口
* @param string $out_biz_no 商户转账唯一订单号
* @param numeric $amount 转账金额
* @param string $user_id 收款方账户
* @param string $order_title 转账业务的标题
* @param null $original_order_id 原支付宝业务单号
* @return mixed {"out_biz_no":"商户订单号","order_id":"支付宝转账订单号","pay_fund_order_id":"支付宝支付资金流水号","status":"SUCCESS","trans_date":"订单支付时间"}
* @throws Exception
*/
public function redPacketTansfer(string $out_biz_no, $amount, string $user_id, string $order_title, $original_order_id = null)
{
$apiName = 'alipay.fund.trans.uni.transfer';
$bizContent = [
'out_biz_no' => $out_biz_no,
'trans_amount' => $amount,
'product_code' => 'STD_RED_PACKET',
'biz_scene' => 'PERSONAL_COLLECTION',
'order_title' => $order_title,
'payee_info' => array('identity' => $user_id, 'identity_type' => 'ALIPAY_USER_ID'),
'business_params' => json_encode(['sub_biz_scene'=>'REDPACKET'], JSON_UNESCAPED_UNICODE)
];
if($original_order_id) $bizContent['original_order_id'] = $original_order_id;
return $this->aopExecute($apiName, $bizContent);
}
/**
* 红包资金退回接口
* @param string $out_request_no 标识一次资金退回请求
* @param string $order_id 发红包时支付宝返回的支付宝订单号
* @param numeric $refund_amount 需要退款的金额
* @return mixed {"refund_order_id":"退款的支付宝系统内部单据id","order_id":"发红包时支付宝返回的支付宝订单号","out_request_no":"标识一次资金退回请求","status":"SUCCESS","refund_amount":"本次退款的金额","refund_date":"时间"}
* @throws Exception
*/
public function redPacketRefund(string $out_request_no, string $order_id, $refund_amount)
{
$apiName = 'alipay.fund.trans.refund';
$bizContent = [
'order_id' => $order_id,
'out_request_no' => $out_request_no,
'refund_amount' => $refund_amount,
];
return $this->aopExecute($apiName, $bizContent);
}
}
@@ -0,0 +1,78 @@
<?php
namespace Alipay\Aop;
class AlipayCertHelper
{
/**
* 从证书中提取序列号
* @param string $certPath 证书路径
* @return string
*/
public static function getCertSN(string $certPath): string
{
$cert = file_get_contents($certPath);
$cert = str_replace("\n\n", "\n", $cert);
$ssl = openssl_x509_parse($cert);
return md5(self::array2string(array_reverse($ssl['issuer'])) . $ssl['serialNumber']);
}
/**
* 数组转字符串
* @param array $array 数组
* @return string
*/
private static function array2string(array $array): string
{
$string = [];
if ($array && is_array($array)) {
foreach ($array as $key => $value) {
$string[] = $key . '=' . $value;
}
}
return implode(',', $string);
}
/**
* 提取根证书序列号
* @param string $certPath 根证书
* @return string|null
*/
public static function getRootCertSN(string $certPath): ?string
{
$cert = file_get_contents($certPath);
$array = explode("-----END CERTIFICATE-----", $cert);
$SN = null;
for ($i = 0; $i < count($array) - 1; $i++) {
$ssl[$i] = openssl_x509_parse($array[$i] . "-----END CERTIFICATE-----");
if(strpos($ssl[$i]['serialNumber'],'0x') === 0){
$ssl[$i]['serialNumber'] = self::hex2dec($ssl[$i]['serialNumberHex']);
}
if ($ssl[$i]['signatureTypeLN'] == "sha1WithRSAEncryption" || $ssl[$i]['signatureTypeLN'] == "sha256WithRSAEncryption") {
if ($SN == null) {
$SN = md5(self::array2string(array_reverse($ssl[$i]['issuer'])) . $ssl[$i]['serialNumber']);
} else {
$SN = $SN . "_" . md5(self::array2string(array_reverse($ssl[$i]['issuer'])) . $ssl[$i]['serialNumber']);
}
}
}
return $SN;
}
/**
* 0x转高精度数字
* @param $hex
* @return int|string
*/
private static function hex2dec($hex)
{
$dec = 0;
$len = strlen($hex);
for ($i = 1; $i <= $len; $i++) {
$dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));
}
return $dec;
}
}
@@ -0,0 +1,158 @@
<?php
namespace Alipay\Aop;
class AlipayRequest
{
protected $notifyUrl;
protected $returnUrl;
protected $terminalType;
protected $terminalInfo;
protected $prodCode;
protected $authToken;
protected $appAuthToken;
protected $bizContent;
protected $apiMethodName;
public function setOtherParams($params = [])
{
foreach ($params as $key => $value) {
$this->{$key} = $value;
}
}
/**
* 获取用于发起请求的“时间戳”.
*
* @return string
*/
public static function getTimestamp(): string
{
return date('Y-m-d H:i:s');
}
/**
* 根据类名获取 API 方法名.
*
* @return string
*/
public function getApiMethodName(): string
{
return $this->apiMethodName;
}
public function setApiMethodName($apiMethodName): AlipayRequest
{
$this->apiMethodName = $apiMethodName;
return $this;
}
public function getNotifyUrl()
{
return $this->notifyUrl;
}
public function setNotifyUrl($notifyUrl): AlipayRequest
{
$this->notifyUrl = $notifyUrl;
return $this;
}
public function getReturnUrl()
{
return $this->returnUrl;
}
public function setReturnUrl($returnUrl): AlipayRequest
{
$this->returnUrl = $returnUrl;
return $this;
}
public function getTerminalType()
{
return $this->terminalType;
}
public function setTerminalType($terminalType): AlipayRequest
{
$this->terminalType = $terminalType;
return $this;
}
public function getTerminalInfo()
{
return $this->terminalInfo;
}
public function setTerminalInfo($terminalInfo): AlipayRequest
{
$this->terminalInfo = $terminalInfo;
return $this;
}
public function getProdCode()
{
return $this->prodCode;
}
public function setProdCode($prodCode): AlipayRequest
{
$this->prodCode = $prodCode;
return $this;
}
public function getAuthToken()
{
return $this->authToken;
}
public function setAuthToken($authToken): AlipayRequest
{
$this->authToken = $authToken;
return $this;
}
public function getAppAuthToken()
{
return $this->appAuthToken;
}
public function setAppAuthToken($appAuthToken): AlipayRequest
{
$this->appAuthToken = $appAuthToken;
return $this;
}
public function getBizContent()
{
if (is_array($this->bizContent)) {
return json_encode($this->bizContent, JSON_UNESCAPED_UNICODE);
}
return $this->bizContent;
}
public function setBizContent($bizContent = []): AlipayRequest
{
$this->bizContent = $bizContent;
return $this;
}
}
@@ -0,0 +1,183 @@
<?php
namespace Alipay\Aop;
class AlipayResponse
{
/**
* 响应签名节点名
*/
const SIGN_NODE = 'sign';
/**
* 响应数据节点后缀
*/
const RESPONSE_SUFFIX = '_response';
/**
* 响应错误节点名
*/
const ERROR_NODE = 'error_response';
/**
* 支付宝公钥证书节点名
*/
const ALIPAY_CERT_SN = 'alipay_cert_sn';
/**
* 原始响应
*
* @var string
*/
protected $raw;
/**
* 已解析的响应
*
* @var mixed
*/
protected $parsed;
/**
* 数据节点名称
*/
protected $nodeName;
/**
* 待验签数据
*/
protected $signData;
/**
* @param string $raw 原始数据
* @param string $apiName 接口名称
* @throws \Exception
*/
public function __construct(string $raw, string $apiName)
{
$this->raw = $raw;
$this->parsed = json_decode($raw, true);
if (!$this->parsed) {
$error = function_exists('json_last_error_msg') ? json_last_error_msg() : json_last_error();
throw new \Exception('返回数据解析失败:'.$error);
}
$this->parseResponseData($apiName);
}
/**
* 获取原始响应的被签名数据,用于验证签名.
*
* @param string $apiName
* @throws \Exception
*/
protected function parseResponseData(string $apiName)
{
$nodeName = str_replace(".", "_", $apiName) . self::RESPONSE_SUFFIX;
$nodeIndex = strpos($this->raw, $nodeName);
if (!$nodeIndex) {
$nodeName = self::ERROR_NODE;
$nodeIndex = strpos($this->raw, $nodeName);
if(!$nodeIndex){
throw new \Exception('Response data not found');
}
}
$this->nodeName = $nodeName;
$signDataStartIndex = $nodeIndex + strlen($nodeName) + 2;
$signIndex = strrpos($this->raw, '"'.static::ALIPAY_CERT_SN.'"');
if(!$signIndex) {
$signIndex = strrpos($this->raw, '"'.static::SIGN_NODE.'"');
}
$signDataEndIndex = $signIndex - 1;
$indexLen = $signDataEndIndex - $signDataStartIndex;
if ($indexLen < 0) {
return;
}
$this->signData = substr($this->raw, $signDataStartIndex, $indexLen);
}
/**
* 获取待验签数据
*
* @return string
*/
public function getSignData(): ?string
{
return $this->signData;
}
/**
* 获取响应内的签名.
*
* @return string
*/
public function getSign(): ?string
{
if (isset($this->parsed[static::SIGN_NODE])) {
return $this->parsed[static::SIGN_NODE];
}
return null;
}
/**
* 获取响应内的数据.
*
* @param bool $assoc
*
* @return mixed|object
*/
public function getData(bool $assoc = true)
{
if (!isset($this->parsed[$this->nodeName])){
return null;
}
$result = $this->parsed[$this->nodeName];
if (!$assoc) {
$result = (object) ($result);
}
return $result;
}
/**
* 判断响应是否成功.
*
* @return bool
*/
public function isSuccess(): bool
{
if (isset($this->parsed[static::ERROR_NODE])) {
return false;
}
if (!isset($this->parsed[$this->nodeName])){
return false;
}
$data = $this->parsed[$this->nodeName];
return isset($data['code']) && $data['code'] == '10000';
}
/**
* 获取原始响应.
*
* @return string
*/
public function getRaw(): string
{
return $this->raw;
}
/**
* 获取支付宝公钥证书序列号
*
* @return bool|string
*/
public function getAlipayCertSN()
{
if (isset($this->parsed[static::ALIPAY_CERT_SN])) {
return $this->parsed[static::ALIPAY_CERT_SN];
}
return false;
}
}
@@ -0,0 +1,43 @@
<?php
namespace Alipay\Aop;
class AlipayResponseException extends \Exception
{
private $res = [];
private $retCode;
private $errCode;
/**
* @param array $res
*/
public function __construct($res)
{
$this->res = $res;
$this->retCode = $res['code'];
if (isset($res['sub_msg'])) {
$this->errCode = $res['sub_code'];
$message = '['.$res['sub_code'].']'.$res['sub_msg'];
} elseif (isset($res['msg'])) {
$message = '['.$res['code'].']'.$res['msg'];
} else {
$message = '未知错误';
}
parent::__construct($message);
}
public function getRetCode()
{
return $this->retCode;
}
public function getErrCode()
{
return $this->errCode;
}
public function getResponse(): array
{
return $this->res;
}
}
+485
View File
@@ -0,0 +1,485 @@
<?php
namespace Alipay\Aop;
use Exception;
class AopClient
{
//应用ID
public $appId;
//网关
public $gatewayUrl = 'https://openapi.alipay.com/gateway.do';
//API版本
public $apiVersion = '1.0';
//编码
public $charset = 'UTF-8';
//返回数据格式
public $format = 'json';
//应用私钥
public $rsaPrivateKey;
//应用私钥文件路径
public $rsaPrivateKeyFilePath;
//支付宝公钥
public $rsaPublicKey;
//支付宝公钥文件路径
public $rsaPublicKeyFilePath;
//AES加密密钥
public $encryptKey;
//签名方式
public $signType = 'RSA2';
//应用公钥证书编号
public $appCertSN;
//支付宝根证书编号
public $alipayRootCertSN;
//SDK版本
protected $sdkVersion = 'alipay-sdk-PHP-4.11.14.ALL';
/**
* 创建客户端.
*
*/
public function __construct() {
}
/**
* AES解密数据.
*
* @param string $content 已加密的数据,如手机号
* @param string $aesKey AES密钥
*
* @return string
*
* @see https://docs.alipay.com/mini/introduce/aes
* @see https://docs.alipay.com/mini/introduce/getphonenumber
*/
public static function aesDecrypt(string $content, string $aesKey): string
{
return openssl_decrypt($content, 'aes-128-cbc', base64_decode($aesKey));
}
/**
* AES加密数据.
*
* @param string $content 要加密的数据
* @param string $aesKey AES密钥
*
* @return string
*/
public static function aesEncrypt(string $content, string $aesKey): string
{
$result = openssl_encrypt($content, 'aes-128-cbc', base64_decode($aesKey));
return base64_encode($result);
}
/**
* 发起请求并解析结果
*
* @param AlipayRequest $request
*
* @return AlipayResponse
* @throws Exception
*/
public function execute(AlipayRequest $request): AlipayResponse
{
$params = $this->build($request);
$url = $this->gatewayUrl.'?charset='.$this->charset;
$raw = $this->curl($url, $params);
$response = new AlipayResponse($raw, $request->getApiMethodName());
$this->verifyResponse($response);
return $response;
}
/**
* 生成用于调用收银台SDK的字符串
*
* @param AlipayRequest $request
* @return string
* @throws Exception
*/
public function sdkExecute(AlipayRequest $request): string
{
$params = $this->build($request);
return http_build_query($params);
}
/**
* 页面提交执行方法
*
* @param AlipayRequest $request
* @param string $httpmethod
* @return string
* @throws Exception
*/
public function pageExecute(AlipayRequest $request, string $httpmethod = 'POST'): string
{
$params = $this->build($request);
if (strtoupper($httpmethod) == 'REDIRECT') {
$requestUrl = $this->gatewayUrl.'?'.http_build_query($params);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $requestUrl);
curl_setopt($ch, CURLOPT_FAILONERROR, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$response = curl_exec($ch);
if (curl_errno($ch) > 0) {
$errmsg = curl_error($ch);
curl_close($ch);
throw new Exception($errmsg, 0);
}
$httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($httpStatusCode == 301 || $httpStatusCode == 302) {
$redirect_url = curl_getinfo($ch, CURLINFO_REDIRECT_URL);
curl_close($ch);
return $redirect_url;
} elseif ($httpStatusCode == 200) {
curl_close($ch);
$response = mb_convert_encoding($response, 'UTF-8', 'GB2312');
if(preg_match('/<div\s+class="Todo">([^<]+)<\/div>/i', $response, $matchers)) {
throw new Exception($matchers[1]);
}
}
throw new Exception('返回数据解析失败', $httpStatusCode);
} elseif (strtoupper($httpmethod) == 'GET') {
return $this->gatewayUrl.'?'.http_build_query($params);
} else {
$url = $this->gatewayUrl.'?charset='.$this->charset;
$html = "<form id='alipaysubmit' name='alipaysubmit' action='{$url}' method='POST'>";
foreach ($params as $key => $value) {
if ($this->isEmpty($value)) {
continue;
}
$value = htmlentities($value, ENT_QUOTES | ENT_HTML5);
$html .= "<input type='hidden' name='{$key}' value='{$value}'/>";
}
$html .= "<input type='submit' value='ok' style='display:none;'></form>";
$html .= "<script>document.forms['alipaysubmit'].submit();</script>";
return $html;
}
}
/**
* 拼接请求参数并签名.
*
* @param AlipayRequest $request
*
* @return array
* @throws Exception
*/
protected function build(AlipayRequest $request): array
{
// 组装系统参数
$sysParams = [];
$sysParams['app_id'] = $this->appId;
$sysParams['version'] = $this->apiVersion;
$sysParams['alipay_sdk'] = $this->sdkVersion;
$sysParams['charset'] = $this->charset;
$sysParams['format'] = $this->format;
$sysParams['sign_type'] = $this->signType;
$sysParams['method'] = $request->getApiMethodName();
$sysParams['timestamp'] = $request->getTimestamp();
$sysParams['notify_url'] = $request->getNotifyUrl();
$sysParams['return_url'] = $request->getReturnUrl();
$sysParams['terminal_type'] = $request->getTerminalType();
$sysParams['terminal_info'] = $request->getTerminalInfo();
$sysParams['prod_code'] = $request->getProdCode();
$sysParams['auth_token'] = $request->getAuthToken();
$sysParams['app_auth_token'] = $request->getAppAuthToken();
if (!$this->isEmpty($this->appCertSN) && !$this->isEmpty($this->alipayRootCertSN)) {
$sysParams["app_cert_sn"] = $this->appCertSN;
$sysParams["alipay_root_cert_sn"] = $this->alipayRootCertSN;
}
$sysParams['biz_content'] = $request->getBizContent();
$sysParams = array_merge($sysParams, get_object_vars($request));
// 转换可能是数组的参数
foreach ($sysParams as $key => &$param) {
if (is_array($param) || is_object($param) && !$param instanceof \CURLFile) {
$param = json_encode($param, JSON_UNESCAPED_UNICODE);
}
if (is_null($param)) {
unset($sysParams[$key]);
}
}
// 签名
$sysParams['sign'] = $this->generateSign($sysParams, $this->signType);
return $sysParams;
}
/**
* 验证返回内容签名
*
* @param AlipayResponse $response
* @throws Exception
*/
protected function verifyResponse(AlipayResponse $response)
{
$signData = $response->getSignData();
$sign = $response->getSign();
if ($this->isEmpty($signData) || $this->isEmpty($sign)) {
throw new AlipayResponseException($response->getData());
}
$checkResult = $this->rsaPubilcVerify($signData, $sign, $this->signType);
if (!$checkResult) {
if (strpos($signData, '\/') > 0) {
$signData = str_replace('\/', '/', $signData);
$checkResult = $this->rsaPubilcVerify($signData, $sign, $this->signType);
}
if (!$checkResult) {
throw new Exception('对返回数据使用支付宝公钥验签失败');
}
}
}
/**
* 异步通知回调验签
*
* @param $params
*
* @return bool
*/
public function verify($params): bool
{
if (!$params || !isset($params['sign'])) {
return false;
}
$sign = $params['sign'];
unset($params['sign']);
unset($params['sign_type']);
$data = $this->getSignContent($params);
try {
return $this->rsaPubilcVerify($data, $sign, $this->signType);
} catch (Exception $ex) {
return false;
}
}
/**
* 异步通知回调验签V2
*
* @param $params
*
* @return bool
*/
public function verifyV2($params): bool
{
if (!$params || !isset($params['sign'])) {
return false;
}
$sign = $params['sign'];
unset($params['sign']);
$data = $this->getSignContent($params);
try {
return $this->rsaPubilcVerify($data, $sign, $this->signType);
} catch (Exception $ex) {
return false;
}
}
/**
* 将参数数组签名(计算 Sign 值).
*
* @param array $params 参数数组
* @param string $signType 签名类型
*
* @return string
* @throws Exception
*/
protected function generateSign(array $params, string $signType = 'RSA2'): string
{
$data = $this->getSignContent($params);
return $this->rsaPrivateSign($data, $signType);
}
/**
* 将数组转换为待签名数据.
*
* @param array $params 参数数组
*
* @return string
*/
protected function getSignContent(array $params): string
{
ksort($params);
unset($params['sign']);
$stringToBeSigned = "";
foreach ($params as $k => $v) {
if($v instanceof \CURLFile || $this->isEmpty($v) || substr($v, 0, 1) == '@') continue;
$stringToBeSigned .= "&{$k}={$v}";
}
return substr($stringToBeSigned, 1);
}
/**
* 使用应用私钥签名
*
* @param string $data 待签名数据
* @param string $signType 签名类型
*
* @return string
*
* @throws Exception
* @see https://docs.open.alipay.com/291/106118
*/
protected function rsaPrivateSign(string $data, string $signType = 'RSA2'): string
{
if ($this->isEmpty($this->rsaPrivateKeyFilePath)) {
$priKey = "-----BEGIN RSA PRIVATE KEY-----\n" .
wordwrap($this->rsaPrivateKey, 64, "\n", true) .
"\n-----END RSA PRIVATE KEY-----";
} else {
$priKey = file_get_contents($this->rsaPrivateKeyFilePath);
}
$res = openssl_get_privatekey($priKey);
if(!$res){
throw new Exception('签名失败,应用私钥不正确');
}
if($signType == 'RSA2'){
openssl_sign($data, $sign, $res, OPENSSL_ALGO_SHA256);
}else{
openssl_sign($data, $sign, $res);
}
if(is_resource($res)){
openssl_free_key($res);
}
return base64_encode($sign);
}
/**
* 使用支付宝公钥验签
*
* @param string $data 待验签数据
* @param string $sign 签名
* @param string $signType
* @return bool
* @throws Exception
*/
public function rsaPubilcVerify(string $data, string $sign, string $signType = 'RSA2'): bool
{
if ($this->isEmpty($this->rsaPublicKeyFilePath)) {
$pubKey = "-----BEGIN PUBLIC KEY-----\n" .
wordwrap($this->rsaPublicKey, 64, "\n", true) .
"\n-----END PUBLIC KEY-----";
} else {
$pubKey = file_get_contents($this->rsaPublicKeyFilePath);
}
$res = openssl_get_publickey($pubKey);
if(!$res){
throw new Exception('验签失败,支付宝公钥不正确');
}
if($signType == 'RSA2'){
$result = openssl_verify($data, base64_decode($sign), $res, OPENSSL_ALGO_SHA256);
}else{
$result = openssl_verify($data, base64_decode($sign), $res);
}
if(is_resource($res)){
openssl_free_key($res);
}
return $result === 1;
}
/**
* 校验某字符串或可被转换为字符串的数据,是否为 NULL 或均为空白字符.
*
* @param string|null $value
*
* @return bool
*/
protected function isEmpty(?string $value): bool
{
return $value === null || trim($value) === '';
}
/**
* 发起 GET/POST 请求.
*
* @param string $url 请求地址
* @param array|null $postFields POST 数据
* @return bool|string
* @throws Exception
*/
protected function curl(string $url, array $postFields = null)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FAILONERROR, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
if (is_array($postFields) && 0 < count($postFields)) {
$postMultipart = false;
foreach ($postFields as &$value) {
if ($value instanceof \CURLFile) {
$postMultipart = true;
} elseif(substr($value, 0, 1) == '@' && class_exists('CURLFile')) {
$postMultipart = true;
$file = substr($value, 1);
if(file_exists($file)){
$value = new \CURLFile($file);
}
}
}
curl_setopt($ch, CURLOPT_POST, true);
if($postMultipart){
curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
}else{
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postFields));
}
}
$response = curl_exec($ch);
if (curl_errno($ch) > 0) {
$errmsg = curl_error($ch);
curl_close($ch);
throw new Exception($errmsg, 0);
}
$httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($httpStatusCode != 200) {
curl_close($ch);
throw new Exception($response, $httpStatusCode);
}
curl_close($ch);
return $response;
}
}