first commit
This commit is contained in:
+21
@@ -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
@@ -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
@@ -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('手机号码加密数据错误');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user