first commit

This commit is contained in:
Mr.Qin
2022-08-19 19:48:37 +08:00
commit afdd648b65
3275 changed files with 631084 additions and 0 deletions

162
vendor/yansongda/pay/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,162 @@
## v3.0.27
### fix
- fix: 添加分账接受人姓名加密字段错误 (#566)
## v3.0.26
### added
- feat: 支持 psr/log 2.x and 3.x (#562)
## v3.0.25
### fixed
- fix: 支持分账传递姓名 (#559)
## v3.0.24
### added
- feat: 支持使用小程序等其他类型转账 (#552)
## v3.0.23
### fixed
- fix: 未设置微信公钥证书时,加密不生效的问题 (#549)
## v3.0.22
### fixed
- fix: 微信分账传递姓名时未加密的问题 (#547)
## v3.0.21
### added
- feat: 微信转账快捷方式与加密方式支持 (#542)
## v3.0.20
### updated
- chore: 完善支付宝响应错误时的异常信息 (#530)
## v3.0.19
### fixed
- fix: 支付宝 system.oauth.token 请求参数错误 (#528)
## v3.0.18
### added
- feat: 电商收付通的退款使用 _type 增加多类型 appid (#518)
## v3.0.17
### added
- feat: 增加电商收付通的退款相关插件 (#513)
## v3.0.16
### fixed
- fixed: app 支付调起签名问题 (#1389476)
## v3.0.15
### fixed
- fixed: 下载对账单时响应解析 (#df27f95)
## v3.0.14
### fixed
- fixed: app 支付调起签名中参数大小写问题 (#7916fdd)
## v3.0.13
### fixed
- fixed: app 支付调起签名中时间戳参数大小写问题 (#510)
## v3.0.12
### fixed
- fixed: 微信小程序支付供应商模式 sub_appid 非必填 (#509)
## v3.0.11
### added
- feat: 微信 h5 支付支持关联 mini_app_id (#506)
## v3.0.10
### added
- feat: 服务商批量转账到零钱 (#503)
## v3.0.9
### added
- feat: 支持直连商户批量转账到零钱 (#501)
## v3.0.8
### fixed
- fix: 设置 bcscale 时支付宝根证书计算错误的问题 (#492, #494)
## v3.0.7
### fixed
- fix: 支付宝 wap/web 支付 get 方法时url拼接问题 (#488)
## v3.0.6
### optimized
- chore: 优化服务商模式小程序下单场景 (#487)
## v3.0.5
### fixed
- fix: 服务商模式交易查询 (#483)
## v3.0.4
### added
- feat: 支持服务商模式 (#479)
- feat: 支持微信服务商分账功能 (#480)
## v3.0.3
### added
- feat: 公钥证书增加 cer 后缀支持 (#d22e29a)
## v3.0.2
### fixed
- 修复微信支付关闭订单时报错问题 (#475)
## v3.0.1
### fixed
- 修复微信支付关闭订单时报错问题 (#475)

20
vendor/yansongda/pay/LICENSE vendored Normal file
View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) yansongda <me@yansongda.cn>
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.

286
vendor/yansongda/pay/README.md vendored Normal file
View File

@@ -0,0 +1,286 @@
<p align="center">
<a href="https://pay.yansongda.cn" target="_blank" rel="noopener noreferrer"><img width="200" src="https://cdn.jsdelivr.net/gh/yansongda/pay-site/.vuepress/public/images/logo.png" alt="Logo"></a>
</p>
<p align="center">
<a href="https://scrutinizer-ci.com/g/yansongda/pay/?branch=master"><img src="https://scrutinizer-ci.com/g/yansongda/pay/badges/quality-score.png?b=master" alt="scrutinizer"></a>
<a href="https://github.com/yansongda/pay/actions"><img src="https://github.com/yansongda/pay/workflows/Linter/badge.svg" alt="Linter Status"></a>
<a href="https://github.com/yansongda/pay/actions"><img src="https://github.com/yansongda/pay/workflows/Tester/badge.svg" alt="Tester Status"></a>
<a href="https://packagist.org/packages/yansongda/pay"><img src="https://poser.pugx.org/yansongda/pay/v/stable" alt="Stable Version"></a>
<a href="https://packagist.org/packages/yansongda/pay"><img src="https://poser.pugx.org/yansongda/pay/downloads" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/yansongda/pay"><img src="https://poser.pugx.org/yansongda/pay/license" alt="License"></a>
</p>
## 前言
v3 版与 v2 版在底层有很大的不同,基础架构做了重新的设计,更易扩展,使用起来更方便。
开发了多次支付宝与微信支付后,很自然产生一种反感,惰性又来了,想在网上找相关的轮子,可是一直没有找到一款自己觉得逞心如意的,要么使用起来太难理解,要么文件结构太杂乱,只有自己撸起袖子干了。
欢迎 Star欢迎 PR
hyperf 扩展包请 [传送至这里](https://github.com/yansongda/hyperf-pay)
laravel 扩展包请 [传送至这里](https://github.com/yansongda/laravel-pay)
yii 扩展包请 [传送至这里](https://github.com/guanguans/yii-pay)
## 特点
- 多租户支持
- Swoole 支持
- 灵活的插件机制
- 丰富的事件系统
- 命名不那么乱七八糟
- 隐藏开发者不需要关注的细节
- 根据支付宝、微信最新 API 开发而成
- 高度抽象的类免去各种拼json与xml的痛苦
- 文件结构清晰易理解,可以随心所欲添加本项目中没有的支付网关
- 方法使用更优雅,不必再去研究那些奇怪的的方法名或者类名是做啥用的
- 内置自动获取微信公共证书方法,再也不用再费劲去考虑第一次获取证书的的问题了
- 符合 PSR2、PSR3、PSR4、PSR7、PSR11、PSR14 等各项标准,你可以各种方便的与你的框架集成
## 运行环境
- PHP 7.3+
- composer
## 详细文档
[https://pay.yansongda.cn](https://pay.yansongda.cn)
## 支持的支付方法
yansongda/pay 100% 兼容 支付宝/微信 所有功能(包括服务商功能),只需通过「插件机制」引入即可。
同时SDK 直接支持内置了以下插件,详情请查阅文档。
### 支付宝
- 电脑支付
- 手机网站支付
- APP 支付
- 刷卡支付
- 扫码支付
- 账户转账
- 小程序支付
- ...
### 微信
- 公众号支付
- 小程序支付
- H5 支付
- 扫码支付
- APP 支付
- ...
- ~~刷卡支付微信v3版暂不支持计划后续内置支持v2版或直接使用 Pay v2 版本~~
- ~~普通红包微信v3版暂不支持计划后续内置支持v2版或直接使用 Pay v2 版本~~
- ~~分裂红包微信v3版暂不支持计划后续内置支持v2版或直接使用 Pay v2 版本~~
## 安装
```shell
composer require yansongda/pay:~3.0.0 -vvv
```
## 深情一撇
### 支付宝
```php
<?php
namespace App\Http\Controllers;
use Yansongda\Pay\Pay;
class AlipayController
{
protected $config = [
'alipay' => [
'default' => [
// 必填-支付宝分配的 app_id
'app_id' => '2016082000295641',
// 必填-应用私钥 字符串或路径
'app_secret_cert' => '89iZ2iC16H6/6a3YcP+hDZUjiNGQx9cuwi9eJyykvcwhD...',
// 必填-应用公钥证书 路径
'app_public_cert_path' => '/Users/yansongda/pay/cert/appCertPublicKey_2016082000295641.crt',
// 必填-支付宝公钥证书 路径
'alipay_public_cert_path' => '/Users/yansongda/pay/cert/alipayCertPublicKey_RSA2.crt',
// 必填-支付宝根证书 路径
'alipay_root_cert_path' => '/Users/yansongda/pay/cert/alipayRootCert.crt',
'return_url' => 'https://yansongda.cn/alipay/return',
'notify_url' => 'https://yansongda.cn/alipay/notify',
// 选填-服务商模式下的服务商 id当 mode 为 Pay::MODE_SERVICE 时使用该参数
'service_provider_id' => '',
// 选填-默认为正常模式。可选为: MODE_NORMAL, MODE_SANDBOX, MODE_SERVICE
'mode' => Pay::MODE_NORMAL,
],
],
'logger' => [ // optional
'enable' => false,
'file' => './logs/alipay.log',
'level' => 'info', // 建议生产环境等级调整为 info开发环境为 debug
'type' => 'single', // optional, 可选 daily.
'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天
],
'http' => [ // optional
'timeout' => 5.0,
'connect_timeout' => 5.0,
// 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html)
],
];
public function web()
{
$result = Pay::alipay($this->config)->web([
'out_trade_no' => ''.time(),
'total_amount' => '0.01',
'subject' => 'yansongda 测试 - 1',
]);
return $result;
}
public function returnCallback()
{
$data = Pay::alipay($this->config)->callback(); // 是的,验签就这么简单!
// 订单号:$data->out_trade_no
// 支付宝交易号:$data->trade_no
// 订单总金额:$data->total_amount
}
public function notifyCallback()
{
$alipay = Pay::alipay($this->config);
try{
$data = $alipay->callback(); // 是的,验签就这么简单!
// 请自行对 trade_status 进行判断及其它逻辑进行判断,在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS 或 TRADE_FINISHED 时,支付宝才会认定为买家付款成功。
// 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号
// 2、判断total_amount是否确实为该订单的实际金额即商户订单创建时的金额
// 3、校验通知中的seller_id或者seller_email) 是否为out_trade_no这笔单据的对应的操作方有的时候一个商户可能有多个seller_id/seller_email
// 4、验证app_id是否为该商户本身。
// 5、其它业务逻辑情况
} catch (\Exception $e) {
// $e->getMessage();
}
return $alipay->success();
}
}
```
### 微信
```php
<?php
namespace App\Http\Controllers;
use Yansongda\Pay\Pay;
class WechatController
{
protected $config = [
'wechat' => [
'default' => [
// 必填-商户号,服务商模式下为服务商商户号
'mch_id' => '',
// 必填-商户秘钥
'mch_secret_key' => '',
// 必填-商户私钥 字符串或路径
'mch_secret_cert' => '',
// 必填-商户公钥证书路径
'mch_public_cert_path' => '',
// 必填
'notify_url' => 'https://yansongda.cn/wechat/notify',
// 选填-公众号 的 app_id
'mp_app_id' => '2016082000291234',
// 选填-小程序 的 app_id
'mini_app_id' => '',
// 选填-app 的 app_id
'app_id' => '',
// 选填-合单 app_id
'combine_app_id' => '',
// 选填-合单商户号
'combine_mch_id' => '',
// 选填-服务商模式下,子公众号 的 app_id
'sub_mp_app_id' => '',
// 选填-服务商模式下,子 app 的 app_id
'sub_app_id' => '',
// 选填-服务商模式下,子小程序 的 app_id
'sub_mini_app_id' => '',
// 选填-服务商模式下子商户id
'sub_mch_id' => '',
// 选填-微信公钥证书路径, optional强烈建议 php-fpm 模式下配置此参数
'wechat_public_cert_path' => [
'45F59D4DABF31918AFCEC556D5D2C6E376675D57' => __DIR__.'/Cert/wechatPublicKey.crt',
],
// 选填-默认为正常模式。可选为: MODE_NORMAL, MODE_SERVICE
'mode' => Pay::MODE_NORMAL,
]
],
'logger' => [ // optional
'enable' => false,
'file' => './logs/wechat.log',
'level' => 'info', // 建议生产环境等级调整为 info开发环境为 debug
'type' => 'single', // optional, 可选 daily.
'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天
],
'http' => [ // optional
'timeout' => 5.0,
'connect_timeout' => 5.0,
// 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html)
],
];
public function index()
{
$order = [
'out_trade_no' => time().'',
'description' => 'subject-测试',
'amount' => [
'total' => 1,
],
'payer' => [
'openid' => 'onkVf1FjWS5SBxxxxxxxx',
],
];
$pay = Pay::wechat($this->config)->mp($order);
// $pay->appId
// $pay->timeStamp
// $pay->nonceStr
// $pay->package
// $pay->signType
}
public function notifyCallback()
{
$pay = Pay::wechat($this->config);
try{
$data = $pay->callback(); // 是的,验签就这么简单!
} catch (\Exception $e) {
// $e->getMessage();
}
return $pay->success();
}
}
```
## 代码贡献
由于测试及使用环境的限制,本项目中只开发了「支付宝」和「微信支付」的相关支付网关。
如果您有其它支付网关的需求,或者发现本项目中需要改进的代码,**_欢迎 Fork 并提交 PR_**
## 赏一杯咖啡吧
![pay](https://cdn.jsdelivr.net/gh/yansongda/pay-site/.vuepress/public/images/pay.jpg)
## LICENSE
MIT

68
vendor/yansongda/pay/composer.json vendored Normal file
View File

@@ -0,0 +1,68 @@
{
"name": "yansongda/pay",
"description": "可能是我用过的最优雅的 Alipay 和 WeChat 的支付 SDK 扩展包了",
"keywords": ["alipay", "wechat", "pay"],
"type": "library",
"license": "MIT",
"support": {
"issues": "https://github.com/yansongda/pay/issues",
"source": "https://github.com/yansongda/pay",
"homepage": "https://pay.yansongda.cn"
},
"authors": [
{
"name": "yansongda",
"email": "me@yansongda.cn"
}
],
"require": {
"php": ">=7.3",
"ext-openssl": "*",
"ext-simplexml":"*",
"ext-libxml": "*",
"ext-json": "*",
"ext-bcmath": "*",
"psr/event-dispatcher": "^1.0",
"psr/log": "^1.1 | ^2.0 | ^3.0",
"psr/container": "^1.1 | ^2.0",
"psr/http-client": "^1.0",
"psr/http-message": "^1.0",
"php-di/php-di": "~6.3.0",
"yansongda/supports": "~3.1.0",
"guzzlehttp/guzzle": "^7.0"
},
"require-dev": {
"phpunit/phpunit": "^9.0",
"mockery/mockery": "^1.4",
"friendsofphp/php-cs-fixer": "^3.0",
"phpstan/phpstan": "^1.0.0",
"monolog/monolog": "^2.2",
"symfony/var-dumper": "^5.1",
"symfony/http-foundation": "^5.2.0",
"symfony/event-dispatcher": "^5.2.0",
"symfony/psr-http-message-bridge": "^2.1"
},
"autoload": {
"psr-4": {
"Yansongda\\Pay\\": "src"
},
"files": [
"src/Functions.php"
]
},
"autoload-dev": {
"psr-4": {
"Yansongda\\Pay\\Tests\\": "tests"
}
},
"scripts": {
"test": "./vendor/bin/phpunit -c phpunit.xml --colors=always",
"cs-fix": "php-cs-fixer fix --dry-run --diff 1>&2",
"analyse": "phpstan analyse --memory-limit 300M -l 5 -c phpstan.neon ./src"
},
"extra": {
"branch-alias": {
"dev-master": "v3.0-dev"
}
}
}

3
vendor/yansongda/pay/phpstan.neon vendored Normal file
View File

@@ -0,0 +1,3 @@
parameters:
reportUnmatchedIgnoredErrors: false
ignoreErrors:

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Contract;
interface ConfigInterface
{
/**
* @param mixed $default default value of the entry when does not found
*
* @return mixed
*/
public function get(string $key, $default = null);
public function has(string $key): bool;
/**
* @param mixed $value
*/
public function set(string $key, $value);
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Contract;
use DI\FactoryInterface;
use Invoker\InvokerInterface;
interface ContainerInterface extends \Psr\Container\ContainerInterface, FactoryInterface, InvokerInterface
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Contract;
interface EventDispatcherInterface extends \Psr\EventDispatcher\EventDispatcherInterface
{
}

View File

@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Contract;
use GuzzleHttp\ClientInterface;
interface HttpClientInterface extends ClientInterface, \Psr\Http\Client\ClientInterface
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Contract;
interface LoggerInterface extends \Psr\Log\LoggerInterface
{
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Contract;
use Psr\Http\Message\ResponseInterface;
interface ParserInterface
{
/**
* @return mixed
*/
public function parse(?ResponseInterface $response);
}

View File

@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Contract;
use Closure;
use Yansongda\Pay\Rocket;
interface PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket;
}

View File

@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Contract;
use Psr\Http\Message\ResponseInterface;
use Yansongda\Supports\Collection;
interface ProviderInterface
{
/**
* pay.
*
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\InvalidParamsException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*
* @return \Psr\Http\Message\MessageInterface|\Yansongda\Supports\Collection|array|null
*/
public function pay(array $plugins, array $params);
/**
* Quick road - Query an order.
*
* @param string|array $order
*
* @return array|\Yansongda\Supports\Collection
*/
public function find($order);
/**
* Quick road - Cancel an order.
*
* @param string|array $order
*
* @return array|\Yansongda\Supports\Collection|void
*/
public function cancel($order);
/**
* Quick road - Close an order.
*
* @param string|array $order
*
* @return array|\Yansongda\Supports\Collection|void
*/
public function close($order);
/**
* Quick road - Refund an order.
*
* @return array|\Yansongda\Supports\Collection
*/
public function refund(array $order);
/**
* Verify a request.
*
* @param array|\Psr\Http\Message\ServerRequestInterface|null $contents
*/
public function callback($contents = null, ?array $params = null): Collection;
/**
* Echo success to server.
*/
public function success(): ResponseInterface;
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Contract;
use Yansongda\Pay\Pay;
interface ServiceProviderInterface
{
/**
* register the service.
*/
public function register(Pay $pay, ?array $data = null): void;
}

View File

@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Contract;
interface ShortcutInterface
{
/**
* @return \Yansongda\Pay\Contract\PluginInterface[]|string[]
*/
public function getPlugins(array $params): array;
}

37
vendor/yansongda/pay/src/Event.php vendored Normal file
View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay;
use Yansongda\Pay\Contract\EventDispatcherInterface;
use Yansongda\Pay\Exception\InvalidConfigException;
/**
* @method static Event\Event dispatch(object $event)
*/
class Event
{
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
* @throws \Yansongda\Pay\Exception\InvalidConfigException
*/
public static function __callStatic(string $method, array $args): void
{
if (!Pay::hasContainer() || !Pay::has(EventDispatcherInterface::class)) {
return;
}
$class = Pay::get(EventDispatcherInterface::class);
if ($class instanceof \Psr\EventDispatcher\EventDispatcherInterface) {
$class->{$method}(...$args);
return;
}
throw new InvalidConfigException(Exception\Exception::EVENT_CONFIG_ERROR);
}
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Event;
class ApiRequested extends Event
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Event;
class ApiRequesting extends Event
{
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Event;
use Yansongda\Pay\Rocket;
class CallbackReceived extends Event
{
/**
* @var string
*/
public $provider;
/**
* @var array|\Psr\Http\Message\ServerRequestInterface|null
*/
public $contents;
/**
* @var array|null
*/
public $params;
/**
* Bootstrap.
*
* @param array|\Psr\Http\Message\ServerRequestInterface|null $contents
*/
public function __construct(string $provider, $contents, ?array $params, ?Rocket $rocket)
{
$this->provider = $provider;
$this->contents = $contents;
$this->params = $params;
parent::__construct($rocket);
}
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Event;
use Yansongda\Pay\Rocket;
class Event
{
/**
* @var \Yansongda\Pay\Rocket|null
*/
public $rocket;
/**
* Bootstrap.
*/
public function __construct(?Rocket $rocket)
{
$this->rocket = $rocket;
}
}

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Event;
use Yansongda\Pay\Rocket;
class MethodCalled extends Event
{
/**
* @var string
*/
public $provider;
/**
* @var string
*/
public $name;
/**
* @var array
*/
public $params;
public function __construct(string $provider, string $name, array $params, ?Rocket $rocket)
{
$this->provider = $provider;
$this->name = $name;
$this->params = $params;
parent::__construct($rocket);
}
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Event;
class PayFinish extends Event
{
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Event;
use Yansongda\Pay\Rocket;
class PayStarted extends Event
{
/**
* @var \Yansongda\Pay\Contract\PluginInterface[]
*/
public $plugins;
/**
* @var array
*/
public $params;
public function __construct(array $plugins, array $params, ?Rocket $rocket)
{
$this->plugins = $plugins;
$this->params = $params;
parent::__construct($rocket);
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Exception;
use Throwable;
class ContainerDependencyException extends ContainerException
{
/**
* Bootstrap.
*
* @param mixed $extra
*/
public function __construct(string $message = 'Dependency Resolve Error', int $code = self::CONTAINER_DEPENDENCY_ERROR, $extra = null, Throwable $previous = null)
{
parent::__construct($message, $code, $extra, $previous);
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Exception;
use Psr\Container\ContainerExceptionInterface;
use Throwable;
class ContainerException extends Exception implements ContainerExceptionInterface
{
/**
* Bootstrap.
*
* @param mixed $extra
*/
public function __construct(string $message = '', int $code = self::CONTAINER_ERROR, $extra = null, Throwable $previous = null)
{
parent::__construct($message, $code, $extra, $previous);
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Exception;
use Throwable;
class ContainerNotFoundException extends ContainerException
{
/**
* Bootstrap.
*
* @param mixed $extra
*/
public function __construct(string $message = 'Container Not Found', int $code = self::CONTAINER_NOT_FOUND, $extra = null, Throwable $previous = null)
{
parent::__construct($message, $code, $extra, $previous);
}
}

View File

@@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Exception;
use Throwable;
class Exception extends \Exception
{
public const UNKNOWN_ERROR = 9999;
/**
* 关于容器.
*/
public const CONTAINER_ERROR = 1000;
public const CONTAINER_NOT_FOUND = 1001;
public const CONTAINER_DEPENDENCY_ERROR = 1002;
public const CONTAINER_NOT_FOUND_ENTRY = 1003;
/**
* 关于容器的服务.
*/
public const SERVICE_ERROR = 2000;
public const SERVICE_NOT_FOUND_ERROR = 2001;
/*
* 关于配置.
*/
public const CONFIG_ERROR = 3000;
public const INVALID_PACKER = 3001;
public const ALIPAY_CONFIG_ERROR = 3002;
public const LOGGER_CONFIG_ERROR = 3003;
public const HTTP_CLIENT_CONFIG_ERROR = 3004;
public const EVENT_CONFIG_ERROR = 3005;
public const WECHAT_CONFIG_ERROR = 3006;
/*
* 关于参数.
*/
public const PARAMS_ERROR = 4000;
public const SHORTCUT_NOT_FOUND = 4001;
public const PLUGIN_ERROR = 4002;
public const SHORTCUT_QUERY_TYPE_ERROR = 4003;
public const METHOD_NOT_SUPPORTED = 4004;
public const REQUEST_NULL_ERROR = 4005;
public const MISSING_NECESSARY_PARAMS = 4006;
public const NOT_IN_SERVICE_MODE = 4007;
public const WECHAT_SERIAL_NO_NOT_FOUND = 4008;
/**
* 关于api.
*/
public const RESPONSE_ERROR = 5000;
public const REQUEST_RESPONSE_ERROR = 5001;
public const UNPACK_RESPONSE_ERROR = 5002;
public const INVALID_RESPONSE_SIGN = 5003;
public const INVALID_RESPONSE_CODE = 5004;
public const RESPONSE_MISSING_NECESSARY_PARAMS = 5005;
public const RESPONSE_NONE = 5006;
public const INVALID_CIPHERTEXT_PARAMS = 5007;
public const INVALID_REQUEST_ENCRYPTED_DATA = 5008;
public const INVALID_REQUEST_ENCRYPTED_METHOD = 5009;
/**
* raw.
*
* @var mixed
*/
public $extra = null;
/**
* Bootstrap.
*
* @param mixed $extra
*/
public function __construct(string $message = 'Unknown Error', int $code = self::UNKNOWN_ERROR, $extra = null, Throwable $previous = null)
{
$this->extra = $extra;
parent::__construct($message, $code, $previous);
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Exception;
use Throwable;
class InvalidConfigException extends Exception
{
/**
* Bootstrap.
*
* @param mixed $extra
*/
public function __construct(int $code = self::CONFIG_ERROR, string $message = 'Config Error', $extra = null, Throwable $previous = null)
{
parent::__construct($message, $code, $extra, $previous);
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Exception;
use Throwable;
class InvalidParamsException extends Exception
{
/**
* Bootstrap.
*
* @param mixed $extra
*/
public function __construct(int $code = self::PARAMS_ERROR, string $message = 'Params Error', $extra = null, Throwable $previous = null)
{
parent::__construct($message, $code, $extra, $previous);
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Exception;
use Throwable;
class InvalidResponseException extends Exception
{
/**
* @var \Throwable|null
*/
public $exception = null;
/**
* @var mixed
*/
public $response = null;
/**
* Bootstrap.
*
* @param mixed $extra
*/
public function __construct(
int $code = self::RESPONSE_ERROR,
string $message = 'Provider response Error',
$extra = null,
?Throwable $exception = null,
Throwable $previous = null)
{
$this->response = $extra;
$this->exception = $exception;
parent::__construct($message, $code, $extra, $previous);
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Exception;
use Throwable;
class ServiceException extends Exception
{
/**
* Bootstrap.
*
* @param mixed $extra
*/
public function __construct(string $message = 'Service Error', int $code = self::SERVICE_ERROR, $extra = null, Throwable $previous = null)
{
parent::__construct($message, $code, $extra, $previous);
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Exception;
use Psr\Container\NotFoundExceptionInterface;
use Throwable;
class ServiceNotFoundException extends Exception implements NotFoundExceptionInterface
{
/**
* Bootstrap.
*
* @param mixed $extra
*/
public function __construct(string $message = 'Service Not Found', int $code = self::SERVICE_NOT_FOUND_ERROR, $extra = null, Throwable $previous = null)
{
parent::__construct($message, $code, $extra, $previous);
}
}

347
vendor/yansongda/pay/src/Functions.php vendored Normal file
View File

@@ -0,0 +1,347 @@
<?php
declare(strict_types=1);
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\ServerRequestInterface;
use Yansongda\Pay\Contract\ConfigInterface;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidConfigException;
use Yansongda\Pay\Exception\InvalidResponseException;
use Yansongda\Pay\Parser\NoHttpRequestParser;
use Yansongda\Pay\Pay;
use Yansongda\Pay\Plugin\ParserPlugin;
use Yansongda\Pay\Plugin\Wechat\PreparePlugin;
use Yansongda\Pay\Plugin\Wechat\SignPlugin;
use Yansongda\Pay\Plugin\Wechat\WechatPublicCertsPlugin;
use Yansongda\Pay\Provider\Wechat;
use Yansongda\Supports\Config;
use Yansongda\Supports\Str;
if (!function_exists('should_do_http_request')) {
function should_do_http_request(?string $direction): bool
{
return is_null($direction) ||
(NoHttpRequestParser::class !== $direction &&
!in_array(NoHttpRequestParser::class, class_parents($direction)));
}
}
if (!function_exists('get_alipay_config')) {
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
function get_alipay_config(array $params = []): Config
{
$alipay = Pay::get(ConfigInterface::class)->get('alipay');
$config = $params['_config'] ?? 'default';
return new Config($alipay[$config] ?? []);
}
}
if (!function_exists('get_public_or_private_cert')) {
/**
* @param bool $publicKey 是否公钥
*
* @return resource|string
*/
function get_public_or_private_cert(string $key, bool $publicKey = false)
{
if ($publicKey) {
return Str::endsWith($key, ['.cer', '.crt', '.pem']) ? file_get_contents($key) : $key;
}
if (Str::endsWith($key, ['.crt', '.pem'])) {
return openssl_pkey_get_private(
Str::startsWith($key, 'file://') ? $key : 'file://'.$key
);
}
return "-----BEGIN RSA PRIVATE KEY-----\n".
wordwrap($key, 64, "\n", true).
"\n-----END RSA PRIVATE KEY-----";
}
}
if (!function_exists('verify_alipay_sign')) {
/**
* @param string $sign base64decode 之后的
*
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\InvalidConfigException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
* @throws \Yansongda\Pay\Exception\InvalidResponseException
*/
function verify_alipay_sign(array $params, string $contents, string $sign): void
{
$public = get_alipay_config($params)->get('alipay_public_cert_path');
if (empty($public)) {
throw new InvalidConfigException(Exception::ALIPAY_CONFIG_ERROR, 'Missing Alipay Config -- [alipay_public_cert_path]');
}
$result = 1 === openssl_verify(
$contents,
$sign,
get_public_or_private_cert($public, true),
OPENSSL_ALGO_SHA256);
if (!$result) {
throw new InvalidResponseException(Exception::INVALID_RESPONSE_SIGN, '', func_get_args());
}
}
}
if (!function_exists('get_wechat_config')) {
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
function get_wechat_config(array $params): Config
{
$wechat = Pay::get(ConfigInterface::class)->get('wechat');
$config = $params['_config'] ?? 'default';
return new Config($wechat[$config] ?? []);
}
}
if (!function_exists('get_wechat_base_uri')) {
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
function get_wechat_base_uri(array $params): string
{
$config = get_wechat_config($params);
return Wechat::URL[$config->get('mode', Pay::MODE_NORMAL)];
}
}
if (!function_exists('get_wechat_authorization')) {
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
* @throws \Yansongda\Pay\Exception\InvalidConfigException
*/
function get_wechat_authorization(array $params, int $timestamp, string $random, string $contents): string
{
$config = get_wechat_config($params);
$mchPublicCertPath = $config->get('mch_public_cert_path');
if (empty($mchPublicCertPath)) {
throw new InvalidConfigException(Exception::WECHAT_CONFIG_ERROR, 'Missing Wechat Config -- [mch_public_cert_path]');
}
$ssl = openssl_x509_parse(get_public_or_private_cert($mchPublicCertPath, true));
if (empty($ssl['serialNumberHex'])) {
throw new InvalidConfigException(Exception::WECHAT_CONFIG_ERROR, 'Parse [mch_public_cert_path] Serial Number Error');
}
$auth = sprintf(
'mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
$config->get('mch_id', ''),
$random,
$timestamp,
$ssl['serialNumberHex'],
get_wechat_sign($params, $contents),
);
return 'WECHATPAY2-SHA256-RSA2048 '.$auth;
}
}
if (!function_exists('get_wechat_sign')) {
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
* @throws \Yansongda\Pay\Exception\InvalidConfigException
*/
function get_wechat_sign(array $params, string $contents): string
{
$privateKey = get_wechat_config($params)->get('mch_secret_cert');
if (empty($privateKey)) {
throw new InvalidConfigException(Exception::WECHAT_CONFIG_ERROR, 'Missing Wechat Config -- [mch_secret_cert]');
}
$privateKey = get_public_or_private_cert($privateKey);
openssl_sign($contents, $sign, $privateKey, 'sha256WithRSAEncryption');
$sign = base64_encode($sign);
!is_resource($privateKey) ?: openssl_free_key($privateKey);
return $sign;
}
}
if (!function_exists('verify_wechat_sign')) {
/**
* @param \Psr\Http\Message\ServerRequestInterface|\Psr\Http\Message\ResponseInterface $message
*
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\InvalidConfigException
* @throws \Yansongda\Pay\Exception\InvalidResponseException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
* @throws \Yansongda\Pay\Exception\InvalidParamsException
*/
function verify_wechat_sign(MessageInterface $message, array $params): void
{
if ($message instanceof ServerRequestInterface && 'localhost' === $message->getUri()->getHost()) {
return;
}
$wechatSerial = $message->getHeaderLine('Wechatpay-Serial');
$timestamp = $message->getHeaderLine('Wechatpay-Timestamp');
$random = $message->getHeaderLine('Wechatpay-Nonce');
$sign = $message->getHeaderLine('Wechatpay-Signature');
$body = $message->getBody()->getContents();
$content = $timestamp."\n".$random."\n".$body."\n";
$public = get_wechat_config($params)->get('wechat_public_cert_path.'.$wechatSerial);
if (empty($sign)) {
throw new InvalidResponseException(Exception::INVALID_RESPONSE_SIGN, '', ['headers' => $message->getHeaders(), 'body' => $body]);
}
$public = get_public_or_private_cert(
empty($public) ? reload_wechat_public_certs($params, $wechatSerial) : $public,
true
);
$result = 1 === openssl_verify(
$content,
base64_decode($sign),
get_public_or_private_cert($public, true),
'sha256WithRSAEncryption'
);
if (!$result) {
throw new InvalidResponseException(Exception::INVALID_RESPONSE_SIGN, '', ['headers' => $message->getHeaders(), 'body' => $body]);
}
}
}
if (!function_exists('encrypt_wechat_contents')) {
function encrypt_wechat_contents(string $contents, string $publicKey): ?string
{
if (openssl_public_encrypt($contents, $encrypted, get_public_or_private_cert($publicKey, true), OPENSSL_PKCS1_OAEP_PADDING)) {
return base64_encode($encrypted);
}
return null;
}
}
if (!function_exists('reload_wechat_public_certs')) {
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\InvalidConfigException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
* @throws \Yansongda\Pay\Exception\InvalidParamsException
* @throws \Yansongda\Pay\Exception\InvalidResponseException
*/
function reload_wechat_public_certs(array $params, ?string $serialNo = null): string
{
$data = Pay::wechat()->pay(
[PreparePlugin::class, WechatPublicCertsPlugin::class, SignPlugin::class, ParserPlugin::class],
$params
)->get('data', []);
foreach ($data as $item) {
$certs[$item['serial_no']] = decrypt_wechat_resource($item['encrypt_certificate'], $params)['ciphertext'] ?? '';
}
$wechatConfig = get_wechat_config($params);
$wechatConfig['wechat_public_cert_path'] = ((array) $wechatConfig['wechat_public_cert_path']) + ($certs ?? []);
Pay::set(ConfigInterface::class, Pay::get(ConfigInterface::class)->merge([
'wechat' => [$params['_config'] ?? 'default' => $wechatConfig->all()],
]));
if (!is_null($serialNo) && empty($certs[$serialNo])) {
throw new InvalidConfigException(Exception::WECHAT_CONFIG_ERROR, 'Get Wechat Public Cert Error');
}
return $certs[$serialNo] ?? '';
}
}
if (!function_exists('decrypt_wechat_resource')) {
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\InvalidConfigException
* @throws \Yansongda\Pay\Exception\InvalidResponseException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
function decrypt_wechat_resource(array $resource, array $params): array
{
$ciphertext = base64_decode($resource['ciphertext'] ?? '');
$secret = get_wechat_config($params)->get('mch_secret_key');
if (strlen($ciphertext) <= Wechat::AUTH_TAG_LENGTH_BYTE) {
throw new InvalidResponseException(Exception::INVALID_CIPHERTEXT_PARAMS);
}
if (is_null($secret) || Wechat::MCH_SECRET_KEY_LENGTH_BYTE != strlen($secret)) {
throw new InvalidConfigException(Exception::WECHAT_CONFIG_ERROR, 'Missing Wechat Config -- [mch_secret_key]');
}
switch ($resource['algorithm'] ?? '') {
case 'AEAD_AES_256_GCM':
$resource['ciphertext'] = decrypt_wechat_resource_aes_256_gcm($ciphertext, $secret, $resource['nonce'] ?? '', $resource['associated_data'] ?? '');
break;
default:
throw new InvalidResponseException(Exception::INVALID_REQUEST_ENCRYPTED_METHOD);
}
return $resource;
}
}
if (!function_exists('decrypt_wechat_resource_aes_256_gcm')) {
/**
* @throws \Yansongda\Pay\Exception\InvalidResponseException
*
* @return array|string
*/
function decrypt_wechat_resource_aes_256_gcm(string $ciphertext, string $secret, string $nonce, string $associatedData)
{
$decrypted = openssl_decrypt(
substr($ciphertext, 0, -Wechat::AUTH_TAG_LENGTH_BYTE),
'aes-256-gcm',
$secret,
OPENSSL_RAW_DATA,
$nonce,
substr($ciphertext, -Wechat::AUTH_TAG_LENGTH_BYTE),
$associatedData
);
if ('certificate' !== $associatedData) {
$decrypted = json_decode($decrypted, true);
if (JSON_ERROR_NONE !== json_last_error()) {
throw new InvalidResponseException(Exception::INVALID_REQUEST_ENCRYPTED_DATA);
}
}
return $decrypted;
}
}

47
vendor/yansongda/pay/src/Logger.php vendored Normal file
View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay;
use Yansongda\Pay\Contract\ConfigInterface;
use Yansongda\Pay\Contract\LoggerInterface;
use Yansongda\Pay\Exception\InvalidConfigException;
/**
* @method static void emergency($message, array $context = [])
* @method static void alert($message, array $context = [])
* @method static void critical($message, array $context = [])
* @method static void error($message, array $context = [])
* @method static void warning($message, array $context = [])
* @method static void notice($message, array $context = [])
* @method static void info($message, array $context = [])
* @method static void debug($message, array $context = [])
* @method static void log($message, array $context = [])
*/
class Logger
{
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
* @throws \Yansongda\Pay\Exception\InvalidConfigException
*/
public static function __callStatic(string $method, array $args): void
{
if (!Pay::hasContainer() || !Pay::has(LoggerInterface::class) ||
false === Pay::get(ConfigInterface::class)->get('logger.enable', false)) {
return;
}
$class = Pay::get(LoggerInterface::class);
if ($class instanceof \Psr\Log\LoggerInterface || $class instanceof \Yansongda\Supports\Logger) {
$class->{$method}(...$args);
return;
}
throw new InvalidConfigException(Exception\Exception::LOGGER_CONFIG_ERROR);
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Parser;
use Psr\Http\Message\ResponseInterface;
use Yansongda\Pay\Contract\ParserInterface;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidResponseException;
class ArrayParser implements ParserInterface
{
/**
* @throws \Yansongda\Pay\Exception\InvalidResponseException
*/
public function parse(?ResponseInterface $response): array
{
if (is_null($response)) {
throw new InvalidResponseException(Exception::RESPONSE_NONE);
}
$contents = $response->getBody()->getContents();
$result = json_decode($contents, true);
if (JSON_ERROR_NONE !== json_last_error()) {
throw new InvalidResponseException(Exception::UNPACK_RESPONSE_ERROR, 'Unpack Response Error', ['contents' => $contents, 'response' => $response]);
}
return $result;
}
}

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Parser;
use Psr\Http\Message\ResponseInterface;
use Yansongda\Pay\Contract\ParserInterface;
use Yansongda\Pay\Pay;
use Yansongda\Supports\Collection;
class CollectionParser implements ParserInterface
{
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
public function parse(?ResponseInterface $response): Collection
{
return new Collection(
Pay::get(ArrayParser::class)->parse($response)
);
}
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Parser;
use Psr\Http\Message\ResponseInterface;
use Yansongda\Pay\Contract\ParserInterface;
class NoHttpRequestParser implements ParserInterface
{
public function parse(?ResponseInterface $response): ?ResponseInterface
{
return $response;
}
}

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Parser;
use Psr\Http\Message\ResponseInterface;
use Yansongda\Pay\Contract\ParserInterface;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidResponseException;
class OriginResponseParser implements ParserInterface
{
/**
* @throws \Yansongda\Pay\Exception\InvalidResponseException
*/
public function parse(?ResponseInterface $response): ?ResponseInterface
{
if (!is_null($response)) {
return $response;
}
throw new InvalidResponseException(Exception::INVALID_RESPONSE_CODE);
}
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Parser;
class ResponseParser extends NoHttpRequestParser
{
}

261
vendor/yansongda/pay/src/Pay.php vendored Normal file
View File

@@ -0,0 +1,261 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay;
use DI\Container;
use DI\ContainerBuilder;
use DI\DependencyException;
use DI\NotFoundException;
use Throwable;
use Yansongda\Pay\Contract\ContainerInterface;
use Yansongda\Pay\Contract\ServiceProviderInterface;
use Yansongda\Pay\Exception\ContainerDependencyException;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\ContainerNotFoundException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Provider\Alipay;
use Yansongda\Pay\Provider\Wechat;
use Yansongda\Pay\Service\AlipayServiceProvider;
use Yansongda\Pay\Service\ConfigServiceProvider;
use Yansongda\Pay\Service\EventServiceProvider;
use Yansongda\Pay\Service\HttpServiceProvider;
use Yansongda\Pay\Service\LoggerServiceProvider;
use Yansongda\Pay\Service\WechatServiceProvider;
/**
* @method static Alipay alipay(array $config = [])
* @method static Wechat wechat(array $config = [])
*/
class Pay
{
/**
* 正常模式.
*/
public const MODE_NORMAL = 0;
/**
* 沙箱模式.
*/
public const MODE_SANDBOX = 1;
/**
* 服务商模式.
*/
public const MODE_SERVICE = 2;
/**
* @var string[]
*/
protected $service = [
AlipayServiceProvider::class,
WechatServiceProvider::class,
];
/**
* @var string[]
*/
private $coreService = [
ConfigServiceProvider::class,
LoggerServiceProvider::class,
EventServiceProvider::class,
HttpServiceProvider::class,
];
/**
* @var \DI\Container|null
*/
private static $container = null;
/**
* Bootstrap.
*
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
private function __construct(array $config)
{
$this->initContainer();
$this->registerServices($config);
}
/**
* __callStatic.
*
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*
* @return mixed
*/
public static function __callStatic(string $service, array $config)
{
if (!empty($config)) {
self::config(...$config);
}
return self::get($service);
}
/**
* 初始化容器、配置等信息.
*
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
public static function config(array $config = []): Pay
{
if (self::hasContainer() && !($config['_force'] ?? false)) {
return self::get(Pay::class);
}
return new self($config);
}
/**
* 定义.
*
* @param mixed $value
*
* @throws \Yansongda\Pay\Exception\ContainerException
*/
public static function set(string $name, $value): void
{
Pay::getContainer()->set($name, $value);
}
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*
* @return mixed
*/
public static function make(string $service, array $parameters = [])
{
try {
return Pay::getContainer()->make(...func_get_args());
} catch (NotFoundException $e) {
throw new ServiceNotFoundException($e->getMessage());
} catch (DependencyException $e) {
throw new ContainerDependencyException($e->getMessage());
} catch (Throwable $e) {
throw new ContainerException($e->getMessage());
}
}
/**
* 获取服务.
*
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
* @throws \Yansongda\Pay\Exception\ContainerException
*
* @return mixed
*/
public static function get(string $service)
{
try {
return Pay::getContainer()->get($service);
} catch (NotFoundException $e) {
throw new ServiceNotFoundException($e->getMessage());
} catch (DependencyException $e) {
throw new ContainerDependencyException($e->getMessage());
} catch (Throwable $e) {
throw new ContainerException($e->getMessage());
}
}
/**
* @throws \Yansongda\Pay\Exception\ContainerException
*/
public static function has(string $service): bool
{
return Pay::getContainer()->has($service);
}
/**
* getContainer.
*
* @throws \Yansongda\Pay\Exception\ContainerNotFoundException
*/
public static function getContainer(): Container
{
if (self::hasContainer()) {
return self::$container;
}
throw new ContainerNotFoundException('You should init/config PAY first', Exception\Exception::CONTAINER_NOT_FOUND);
}
/**
* has Container.
*/
public static function hasContainer(): bool
{
return isset(self::$container) && self::$container instanceof Container;
}
/**
* clear.
*/
public static function clear(): void
{
self::$container = null;
}
/**
* 注册服务.
*
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
public static function registerService(string $service, array $config): void
{
$var = self::get($service);
if ($var instanceof ServiceProviderInterface) {
$var->register(self::get(Pay::class), $config);
}
}
/**
* initContainer.
*
* @throws \Yansongda\Pay\Exception\ContainerException
*/
private function initContainer(): void
{
$builder = new ContainerBuilder();
$builder->useAnnotations(false);
try {
$container = $builder->build();
$container->set(ContainerInterface::class, $container);
$container->set(\Psr\Container\ContainerInterface::class, $container);
$container->set(Pay::class, $this);
self::$container = $container;
} catch (Throwable $e) {
throw new ContainerException($e->getMessage());
}
}
/**
* register services.
*
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
private function registerServices(array $config): void
{
foreach (array_merge($this->coreService, $this->service) as $service) {
self::registerService($service, $config);
}
}
}

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidResponseException;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Parser\NoHttpRequestParser;
use Yansongda\Pay\Rocket;
use Yansongda\Supports\Collection;
use Yansongda\Supports\Str;
class CallbackPlugin implements PluginInterface
{
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\InvalidConfigException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
* @throws \Yansongda\Pay\Exception\InvalidResponseException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::info('[alipay][CallbackPlugin] 插件开始装载', ['rocket' => $rocket]);
$this->formatPayload($rocket);
if (!($rocket->getParams()['sign'] ?? false)) {
throw new InvalidResponseException(Exception::INVALID_RESPONSE_SIGN, '', $rocket->getParams());
}
verify_alipay_sign($rocket->getParams(), $this->getSignContent($rocket->getPayload()), base64_decode($rocket->getParams()['sign']));
$rocket->setDirection(NoHttpRequestParser::class)
->setDestination($rocket->getPayload());
Logger::info('[alipay][CallbackPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
protected function formatPayload(Rocket $rocket): void
{
$payload = (new Collection($rocket->getParams()))->filter(function ($v, $k) {
return '' !== $v && !is_null($v) && 'sign' != $k && 'sign_type' != $k && !Str::startsWith($k, '_');
});
$rocket->setPayload($payload);
}
protected function getSignContent(Collection $payload): string
{
return urldecode($payload->sortKeys()->toString());
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Data;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
class BillDownloadUrlQueryPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.data.dataservice.bill.downloadurl.query';
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Data;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
class BillEreceiptApplyPlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::info('[alipay][BillEreceiptApplyPlugin] 插件开始装载', ['rocket' => $rocket]);
$rocket->mergePayload([
'method' => 'alipay.data.bill.ereceipt.apply',
'biz_content' => array_merge(
[
'type' => 'FUND_DETAIL',
],
$rocket->getParams(),
),
]);
Logger::info('[alipay][BillEreceiptApplyPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Data;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
class BillEreceiptQueryPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.data.bill.ereceipt.query';
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Ebpp;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
class PdeductBillStatusPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.ebpp.pdeduct.bill.pay.status';
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Ebpp;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
class PdeductPayPlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::info('[alipay][PdeductPayPlugin] 插件开始装载', ['rocket' => $rocket]);
$rocket->mergePayload([
'method' => 'alipay.ebpp.pdeduct.pay',
'biz_content' => array_merge(
[
'agent_channel' => 'PUBLICFORM',
],
$rocket->getParams(),
),
]);
Logger::info('[alipay][PdeductPayPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
}

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Ebpp;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
class PdeductSignAddPlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::info('[alipay][PdeductSignAddPlugin] 插件开始装载', ['rocket' => $rocket]);
$rocket->mergePayload([
'method' => 'alipay.ebpp.pdeduct.sign.add',
'biz_content' => array_merge(
[
'charge_inst' => 'CQCENTERELECTRIC',
'agent_channel' => 'PUBLICPLATFORM',
'deduct_prod_code' => 'INST_DIRECT_DEDUCT',
],
$rocket->getParams(),
),
]);
Logger::info('[alipay][PdeductSignAddPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Ebpp;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
class PdeductSignCancelPlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::info('[alipay][PdeductSignCancelPlugin] 插件开始装载', ['rocket' => $rocket]);
$rocket->mergePayload([
'method' => 'alipay.ebpp.pdeduct.sign.cancel',
'biz_content' => array_merge(
[
'agent_channel' => 'PUBLICPLATFORM',
],
$rocket->getParams(),
),
]);
Logger::info('[alipay][PdeductSignCancelPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Fund;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
class AccountQueryPlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::info('[alipay][AccountQueryPlugin] 插件开始装载', ['rocket' => $rocket]);
$rocket->mergePayload([
'method' => 'alipay.fund.account.query',
'biz_content' => array_merge(
[
'product_code' => 'TRANS_ACCOUNT_NO_PWD',
],
$rocket->getParams(),
),
]);
Logger::info('[alipay][AccountQueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Fund;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
class AuthOrderFreezePlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::info('[alipay][AuthOrderFreezePlugin] 插件开始装载', ['rocket' => $rocket]);
$rocket->mergePayload([
'method' => 'alipay.fund.auth.order.freeze',
'biz_content' => array_merge(
[
'product_code' => 'PRE_AUTH',
],
$rocket->getParams()
),
]);
Logger::info('[alipay][AuthOrderFreezePlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Fund;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
class AuthOrderUnfreezePlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.fund.auth.order.unfreeze';
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Fund;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
class TransCommonQueryPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.fund.trans.common.query';
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Fund;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
class TransOrderQueryPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.fund.trans.order.query';
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Fund;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Parser\ResponseParser;
use Yansongda\Pay\Rocket;
class TransPagePayPlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::info('[alipay][TransPagePayPlugin] 插件开始装载', ['rocket' => $rocket]);
$rocket->setDirection(ResponseParser::class)
->mergePayload([
'method' => 'alipay.fund.trans.page.pay',
'biz_content' => $rocket->getParams(),
]);
Logger::info('[alipay][TransPagePayPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Fund;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
class TransTobankTransferPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.fund.trans.tobank.transfer';
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Fund;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
class TransUniTransferPlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::info('[alipay][TransUniTransferPlugin] 插件开始装载', ['rocket' => $rocket]);
$rocket->mergePayload([
'method' => 'alipay.fund.trans.uni.transfer',
'biz_content' => array_merge(
[
'biz_scene' => 'DIRECT_TRANSFER',
'product_code' => 'TRANS_ACCOUNT_NO_PWD',
],
$rocket->getParams(),
),
]);
Logger::info('[alipay][TransUniTransferPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
abstract class GeneralPlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::info('[alipay][GeneralPlugin] 通用插件开始装载', ['rocket' => $rocket]);
$this->doSomethingBefore($rocket);
$rocket->mergePayload([
'method' => $this->getMethod(),
'biz_content' => $rocket->getParams(),
]);
Logger::info('[alipay][GeneralPlugin] 通用插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
protected function doSomethingBefore(Rocket $rocket): void
{
}
abstract protected function getMethod(): string;
}

View File

@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay;
use Closure;
use GuzzleHttp\Psr7\Response;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
use Yansongda\Supports\Collection;
class HtmlResponsePlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
/* @var Rocket $rocket */
$rocket = $next($rocket);
Logger::info('[alipay][HtmlResponsePlugin] 插件开始装载', ['rocket' => $rocket]);
$radar = $rocket->getRadar();
$response = 'GET' === $radar->getMethod() ?
$this->buildRedirect($radar->getUri()->__toString(), $rocket->getPayload()) :
$this->buildHtml($radar->getUri()->__toString(), $rocket->getPayload());
$rocket->setDestination($response);
Logger::info('[alipay][HtmlResponsePlugin] 插件装载完毕', ['rocket' => $rocket]);
return $rocket;
}
protected function buildRedirect(string $endpoint, Collection $payload): Response
{
$url = $endpoint.(false === strpos($endpoint, '?') ? '?' : '&').$payload->query();
$content = sprintf('<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="refresh" content="0;url=\'%1$s\'" />
<title>Redirecting to %1$s</title>
</head>
<body>
Redirecting to %1$s.
</body>
</html>', htmlspecialchars($url, ENT_QUOTES)
);
return new Response(302, ['Location' => $url], $content);
}
protected function buildHtml(string $endpoint, Collection $payload): Response
{
$sHtml = "<form id='alipay_submit' name='alipay_submit' action='".$endpoint."' method='POST'>";
foreach ($payload->all() as $key => $val) {
$val = str_replace("'", '&apos;', $val);
$sHtml .= "<input type='hidden' name='".$key."' value='".$val."'/>";
}
$sHtml .= "<input type='submit' value='ok' style='display:none;'></form>";
$sHtml .= "<script>document.forms['alipay_submit'].submit();</script>";
return new Response(200, [], $sHtml);
}
}

View File

@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidResponseException;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
use Yansongda\Supports\Collection;
class LaunchPlugin implements PluginInterface
{
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\InvalidConfigException
* @throws \Yansongda\Pay\Exception\InvalidResponseException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
/* @var Rocket $rocket */
$rocket = $next($rocket);
Logger::info('[alipay][LaunchPlugin] 插件开始装载', ['rocket' => $rocket]);
if (should_do_http_request($rocket->getDirection())) {
$this->verifySign($rocket);
$rocket->setDestination(
Collection::wrap(
$rocket->getDestination()->get($this->getResultKey($rocket->getPayload()))
)
);
}
Logger::info('[alipay][LaunchPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $rocket;
}
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\InvalidConfigException
* @throws \Yansongda\Pay\Exception\InvalidResponseException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
protected function verifySign(Rocket $rocket): void
{
$response = $rocket->getDestination();
$result = $response->get($this->getResultKey($rocket->getPayload()));
$sign = $response->get('sign', '');
if ('' === $sign || is_null($result)) {
throw new InvalidResponseException(Exception::INVALID_RESPONSE_SIGN, 'Verify Alipay Response Sign Failed', $response);
}
verify_alipay_sign($rocket->getParams(), json_encode($result, JSON_UNESCAPED_UNICODE), base64_decode($sign));
}
protected function getResultKey(Collection $payload): string
{
$method = $payload->get('method');
return str_replace('.', '_', $method).'_response';
}
}

View File

@@ -0,0 +1,187 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidConfigException;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
class PreparePlugin implements PluginInterface
{
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
* @throws \Yansongda\Pay\Exception\InvalidConfigException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::info('[alipay][PreparePlugin] 插件开始装载', ['rocket' => $rocket]);
$rocket->mergePayload($this->getPayload($rocket->getParams()));
Logger::info('[alipay][PreparePlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
* @throws \Yansongda\Pay\Exception\InvalidConfigException
*/
protected function getPayload(array $params): array
{
return [
'app_id' => get_alipay_config($params)->get('app_id', ''),
'method' => '',
'format' => 'JSON',
'return_url' => $this->getReturnUrl($params),
'charset' => 'utf-8',
'sign_type' => 'RSA2',
'sign' => '',
'timestamp' => date('Y-m-d H:i:s'),
'version' => '1.0',
'notify_url' => $this->getNotifyUrl($params),
'app_auth_token' => '',
'app_cert_sn' => $this->getAppCertSn($params),
'alipay_root_cert_sn' => $this->getAlipayRootCertSn($params),
'biz_content' => [],
];
}
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
protected function getReturnUrl(array $params): string
{
if (!empty($params['_return_url'])) {
return $params['_return_url'];
}
return get_alipay_config($params)->get('return_url', '');
}
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
protected function getNotifyUrl(array $params): string
{
if (!empty($params['_notify_url'])) {
return $params['_notify_url'];
}
return get_alipay_config($params)->get('notify_url', '');
}
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
* @throws \Yansongda\Pay\Exception\InvalidConfigException
*/
protected function getAppCertSn(array $params): string
{
$path = get_alipay_config($params)->get('app_public_cert_path');
if (is_null($path)) {
throw new InvalidConfigException(Exception::ALIPAY_CONFIG_ERROR, 'Missing Alipay Config -- [app_public_cert_path]');
}
$cert = file_get_contents($path);
$ssl = openssl_x509_parse($cert);
return $this->getCertSn($ssl['issuer'], $ssl['serialNumber']);
}
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\InvalidConfigException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
protected function getAlipayRootCertSn(array $params): string
{
$path = get_alipay_config($params)->get('alipay_root_cert_path');
if (is_null($path)) {
throw new InvalidConfigException(Exception::ALIPAY_CONFIG_ERROR, 'Missing Alipay Config -- [alipay_root_cert_path]');
}
$sn = '';
$exploded = explode('-----END CERTIFICATE-----', file_get_contents($path));
foreach ($exploded as $cert) {
if (empty(trim($cert))) {
continue;
}
$ssl = openssl_x509_parse($cert.'-----END CERTIFICATE-----');
if (false === $ssl) {
throw new InvalidConfigException(Exception::ALIPAY_CONFIG_ERROR, 'Invalid alipay_root_cert');
}
$detail = $this->formatCert($ssl);
if ('sha1WithRSAEncryption' == $detail['signatureTypeLN'] || 'sha256WithRSAEncryption' == $detail['signatureTypeLN']) {
$sn .= $this->getCertSn($detail['issuer'], $detail['serialNumber']).'_';
}
}
return substr($sn, 0, -1);
}
protected function getCertSn(array $issuer, string $serialNumber): string
{
return md5(
$this->array2string(array_reverse($issuer)).$serialNumber
);
}
protected function array2string(array $array): string
{
$string = [];
foreach ($array as $key => $value) {
$string[] = $key.'='.$value;
}
return implode(',', $string);
}
protected function formatCert(array $ssl): array
{
if (0 === strpos($ssl['serialNumber'], '0x')) {
$ssl['serialNumber'] = $this->hex2dec($ssl['serialNumberHex']);
}
return $ssl;
}
protected function hex2dec(string $hex): string
{
$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), 0), 0),
0
);
}
return $dec;
}
}

View File

@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay;
use Closure;
use Psr\Http\Message\RequestInterface;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Pay;
use Yansongda\Pay\Provider\Alipay;
use Yansongda\Pay\Request;
use Yansongda\Pay\Rocket;
class RadarPlugin implements PluginInterface
{
/**
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::info('[alipay][RadarPlugin] 插件开始装载', ['rocket' => $rocket]);
$rocket->setRadar($this->getRequest($rocket));
Logger::info('[alipay][RadarPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
protected function getRequest(Rocket $rocket): RequestInterface
{
return new Request(
$this->getMethod($rocket),
$this->getUrl($rocket),
$this->getHeaders(),
$this->getBody($rocket),
);
}
protected function getMethod(Rocket $rocket): string
{
return strtoupper($rocket->getParams()['_method'] ?? 'POST');
}
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
protected function getUrl(Rocket $rocket): string
{
$config = get_alipay_config($rocket->getParams());
return Alipay::URL[$config->get('mode', Pay::MODE_NORMAL)];
}
protected function getHeaders(): array
{
return [
'Content-Type' => 'application/x-www-form-urlencoded',
];
}
protected function getBody(Rocket $rocket): string
{
return $rocket->getPayload()->query();
}
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Shortcut;
use Closure;
use GuzzleHttp\Psr7\Response;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Contract\ShortcutInterface;
use Yansongda\Pay\Plugin\Alipay\Trade\AppPayPlugin;
use Yansongda\Pay\Rocket;
use Yansongda\Supports\Arr;
use Yansongda\Supports\Collection;
class AppShortcut implements ShortcutInterface
{
public function getPlugins(array $params): array
{
return [
AppPayPlugin::class,
$this->buildResponse(),
];
}
protected function buildResponse(): PluginInterface
{
return new class() implements PluginInterface {
public function assembly(Rocket $rocket, Closure $next): Rocket
{
$rocket->setDestination(new Response());
/* @var Rocket $rocket */
$rocket = $next($rocket);
$response = $this->buildHtml($rocket->getPayload());
return $rocket->setDestination($response);
}
protected function buildHtml(Collection $payload): Response
{
return new Response(200, [], Arr::query($payload->all()));
}
};
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Shortcut;
use Yansongda\Pay\Contract\ShortcutInterface;
use Yansongda\Pay\Plugin\Alipay\Trade\CancelPlugin;
class CancelShortcut implements ShortcutInterface
{
public function getPlugins(array $params): array
{
return [
CancelPlugin::class,
];
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Shortcut;
use Yansongda\Pay\Contract\ShortcutInterface;
use Yansongda\Pay\Plugin\Alipay\Trade\ClosePlugin;
class CloseShortcut implements ShortcutInterface
{
public function getPlugins(array $params): array
{
return [
ClosePlugin::class,
];
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Shortcut;
use Yansongda\Pay\Contract\ShortcutInterface;
use Yansongda\Pay\Plugin\Alipay\Trade\CreatePlugin;
class MiniShortcut implements ShortcutInterface
{
public function getPlugins(array $params): array
{
return [
CreatePlugin::class,
];
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Shortcut;
use Yansongda\Pay\Contract\ShortcutInterface;
use Yansongda\Pay\Plugin\Alipay\Trade\PayPlugin;
class PosShortcut implements ShortcutInterface
{
public function getPlugins(array $params): array
{
return [
PayPlugin::class,
];
}
}

View File

@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Shortcut;
use Yansongda\Pay\Contract\ShortcutInterface;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Plugin\Alipay\Fund\TransOrderQueryPlugin;
use Yansongda\Pay\Plugin\Alipay\Trade\FastRefundQueryPlugin;
use Yansongda\Pay\Plugin\Alipay\Trade\QueryPlugin;
class QueryShortcut implements ShortcutInterface
{
/**
* @throws \Yansongda\Pay\Exception\InvalidParamsException
*/
public function getPlugins(array $params): array
{
$typeMethod = ($params['_type'] ?? 'default').'Plugins';
if (isset($params['out_request_no'])) {
return $this->refundPlugins();
}
if (method_exists($this, $typeMethod)) {
return $this->{$typeMethod}();
}
throw new InvalidParamsException(Exception::SHORTCUT_QUERY_TYPE_ERROR, "Query type [$typeMethod] not supported");
}
protected function defaultPlugins(): array
{
return [
QueryPlugin::class,
];
}
protected function refundPlugins(): array
{
return [
FastRefundQueryPlugin::class,
];
}
protected function transferPlugins(): array
{
return [
TransOrderQueryPlugin::class,
];
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Shortcut;
use Yansongda\Pay\Contract\ShortcutInterface;
use Yansongda\Pay\Plugin\Alipay\Trade\RefundPlugin;
class RefundShortcut implements ShortcutInterface
{
public function getPlugins(array $params): array
{
return [
RefundPlugin::class,
];
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Shortcut;
use Yansongda\Pay\Contract\ShortcutInterface;
use Yansongda\Pay\Plugin\Alipay\Trade\PreCreatePlugin;
class ScanShortcut implements ShortcutInterface
{
public function getPlugins(array $params): array
{
return [
PreCreatePlugin::class,
];
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Shortcut;
use Yansongda\Pay\Contract\ShortcutInterface;
use Yansongda\Pay\Plugin\Alipay\Fund\TransUniTransferPlugin;
class TransferShortcut implements ShortcutInterface
{
public function getPlugins(array $params): array
{
return [
TransUniTransferPlugin::class,
];
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Shortcut;
use Yansongda\Pay\Contract\ShortcutInterface;
use Yansongda\Pay\Plugin\Alipay\HtmlResponsePlugin;
use Yansongda\Pay\Plugin\Alipay\Trade\WapPayPlugin;
class WapShortcut implements ShortcutInterface
{
public function getPlugins(array $params): array
{
return [
WapPayPlugin::class,
HtmlResponsePlugin::class,
];
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Shortcut;
use Yansongda\Pay\Contract\ShortcutInterface;
use Yansongda\Pay\Plugin\Alipay\HtmlResponsePlugin;
use Yansongda\Pay\Plugin\Alipay\Trade\PagePayPlugin;
class WebShortcut implements ShortcutInterface
{
public function getPlugins(array $params): array
{
return [
PagePayPlugin::class,
HtmlResponsePlugin::class,
];
}
}

View File

@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidConfigException;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
use Yansongda\Supports\Str;
class SignPlugin implements PluginInterface
{
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\InvalidConfigException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::info('[alipay][SignPlugin] 插件开始装载', ['rocket' => $rocket]);
$this->formatPayload($rocket);
$sign = $this->getSign($rocket);
$rocket->mergePayload(['sign' => $sign]);
Logger::info('[alipay][SignPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
protected function formatPayload(Rocket $rocket): void
{
$payload = $rocket->getPayload()->filter(function ($v, $k) {
return '' !== $v && !is_null($v) && 'sign' != $k;
});
$contents = array_filter($payload->get('biz_content', []), function ($v, $k) {
return !Str::startsWith(strval($k), '_');
}, ARRAY_FILTER_USE_BOTH);
$rocket->setPayload(
$payload->merge(['biz_content' => json_encode($contents)])
);
}
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\InvalidConfigException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
protected function getSign(Rocket $rocket): string
{
$privateKey = $this->getPrivateKey($rocket->getParams());
$content = $rocket->getPayload()->sortKeys()->toString();
openssl_sign($content, $sign, $privateKey, OPENSSL_ALGO_SHA256);
$sign = base64_encode($sign);
!is_resource($privateKey) ?: openssl_free_key($privateKey);
return $sign;
}
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\InvalidConfigException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*
* @return resource|string
*/
protected function getPrivateKey(array $params)
{
$privateKey = get_alipay_config($params)->get('app_secret_cert');
if (is_null($privateKey)) {
throw new InvalidConfigException(Exception::ALIPAY_CONFIG_ERROR, 'Missing Alipay Config -- [app_secret_cert]');
}
return get_public_or_private_cert($privateKey);
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Tools;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
class OpenAuthTokenAppPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.open.auth.token.app';
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Tools;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
class OpenAuthTokenAppQueryPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.open.auth.token.app.query';
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Tools;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
use Yansongda\Pay\Rocket;
class SystemOauthTokenPlugin extends GeneralPlugin
{
protected function doSomethingBefore(Rocket $rocket): void
{
$rocket->mergePayload($rocket->getParams());
}
protected function getMethod(): string
{
return 'alipay.system.oauth.token';
}
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Parser\ResponseParser;
use Yansongda\Pay\Rocket;
use Yansongda\Pay\Traits\SupportServiceProviderTrait;
class AppPayPlugin implements PluginInterface
{
use SupportServiceProviderTrait;
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::info('[alipay][AppPayPlugin] 插件开始装载', ['rocket' => $rocket]);
$this->loadAlipayServiceProvider($rocket);
$rocket->setDirection(ResponseParser::class)
->mergePayload([
'method' => 'alipay.trade.app.pay',
'biz_content' => array_merge(
['product_code' => 'QUICK_MSECURITY_PAY'],
$rocket->getParams(),
),
]);
Logger::info('[alipay][AppPayPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
class CancelPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.trade.cancel';
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
class ClosePlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.trade.close';
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
class CreatePlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.trade.create';
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
class FastRefundQueryPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.trade.fastpay.refund.query';
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
class OrderPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.trade.order.pay';
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
class OrderSettlePlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.trade.order.settle';
}
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Parser\ResponseParser;
use Yansongda\Pay\Rocket;
use Yansongda\Pay\Traits\SupportServiceProviderTrait;
class PagePayPlugin implements PluginInterface
{
use SupportServiceProviderTrait;
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::info('[alipay][PagePayPlugin] 插件开始装载', ['rocket' => $rocket]);
$this->loadAlipayServiceProvider($rocket);
$rocket->setDirection(ResponseParser::class)
->mergePayload([
'method' => 'alipay.trade.page.pay',
'biz_content' => array_merge(
['product_code' => 'FAST_INSTANT_TRADE_PAY'],
$rocket->getParams()
),
]);
Logger::info('[alipay][PagePayPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Parser\ResponseParser;
use Yansongda\Pay\Rocket;
class PageRefundPlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::info('[alipay][PageRefundPlugin] 插件开始装载', ['rocket' => $rocket]);
$rocket->setDirection(ResponseParser::class)
->mergePayload([
'method' => 'alipay.trade.page.refund',
'biz_content' => $rocket->getParams(),
]);
Logger::info('[alipay][PageRefundPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
use Yansongda\Pay\Traits\SupportServiceProviderTrait;
class PayPlugin implements PluginInterface
{
use SupportServiceProviderTrait;
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::info('[alipay][PayPlugin] 插件开始装载', ['rocket' => $rocket]);
$this->loadAlipayServiceProvider($rocket);
$rocket->mergePayload([
'method' => 'alipay.trade.pay',
'biz_content' => array_merge(
[
'product_code' => 'FACE_TO_FACE_PAYMENT',
'scene' => 'bar_code',
],
$rocket->getParams(),
),
]);
Logger::info('[alipay][PayPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
use Yansongda\Pay\Rocket;
use Yansongda\Pay\Traits\SupportServiceProviderTrait;
class PreCreatePlugin extends GeneralPlugin
{
use SupportServiceProviderTrait;
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
protected function doSomethingBefore(Rocket $rocket): void
{
$this->loadAlipayServiceProvider($rocket);
}
protected function getMethod(): string
{
return 'alipay.trade.precreate';
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
class QueryPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.trade.query';
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
class RefundPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.trade.refund';
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Parser\ResponseParser;
use Yansongda\Pay\Rocket;
use Yansongda\Pay\Traits\SupportServiceProviderTrait;
class WapPayPlugin implements PluginInterface
{
use SupportServiceProviderTrait;
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::info('[alipay][WapPayPlugin] 插件开始装载', ['rocket' => $rocket]);
$this->loadAlipayServiceProvider($rocket);
$rocket->setDirection(ResponseParser::class)
->mergePayload([
'method' => 'alipay.trade.wap.pay',
'biz_content' => array_merge(
[
'product_code' => 'QUICK_WAP_PAY',
],
$rocket->getParams(),
),
]);
Logger::info('[alipay][WapPayPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\User;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
class InfoSharePlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::info('[alipay][InfoSharePlugin] 插件开始装载', ['rocket' => $rocket]);
$rocket->mergePayload([
'method' => 'alipay.user.info.share',
'auth_token' => $rocket->getParams()['auth_token'] ?? '',
]);
Logger::info('[alipay][InfoSharePlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
}

View File

@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin;
use Closure;
use Yansongda\Pay\Contract\ParserInterface;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidConfigException;
use Yansongda\Pay\Pay;
use Yansongda\Pay\Rocket;
class ParserPlugin implements PluginInterface
{
/**
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\InvalidConfigException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
/* @var Rocket $rocket */
$rocket = $next($rocket);
/* @var \Psr\Http\Message\ResponseInterface $response */
$response = $rocket->getDestination();
return $rocket->setDestination(
$this->getPacker($rocket)->parse($response)
);
}
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\InvalidConfigException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
protected function getPacker(Rocket $rocket): ParserInterface
{
$packer = Pay::get($rocket->getDirection() ?? ParserInterface::class);
$packer = is_string($packer) ? Pay::get($packer) : $packer;
if (!($packer instanceof ParserInterface)) {
throw new InvalidConfigException(Exception::INVALID_PACKER);
}
return $packer;
}
}

View File

@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat;
use Closure;
use GuzzleHttp\Psr7\Utils;
use Psr\Http\Message\ServerRequestInterface;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Parser\NoHttpRequestParser;
use Yansongda\Pay\Rocket;
use Yansongda\Supports\Collection;
class CallbackPlugin implements PluginInterface
{
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\InvalidConfigException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
* @throws \Yansongda\Pay\Exception\InvalidResponseException
* @throws \Yansongda\Pay\Exception\InvalidParamsException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::info('[wechat][CallbackPlugin] 插件开始装载', ['rocket' => $rocket]);
$this->formatRequestAndParams($rocket);
/* @phpstan-ignore-next-line */
verify_wechat_sign($rocket->getDestinationOrigin(), $rocket->getParams());
$body = json_decode($rocket->getDestination()->getBody()->getContents(), true);
$rocket->setDirection(NoHttpRequestParser::class)->setPayload(new Collection($body));
$body['resource'] = decrypt_wechat_resource($body['resource'] ?? [], $rocket->getParams());
$rocket->setDestination(new Collection($body));
Logger::info('[wechat][CallbackPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
/**
* @throws \Yansongda\Pay\Exception\InvalidParamsException
*/
protected function formatRequestAndParams(Rocket $rocket): void
{
$request = $rocket->getParams()['request'] ?? null;
if (!($request instanceof ServerRequestInterface)) {
throw new InvalidParamsException(Exception::REQUEST_NULL_ERROR);
}
$contents = $request->getBody()->getContents();
$rocket->setDestination($request->withBody(Utils::streamFor($contents)))
->setDestinationOrigin($request->withBody(Utils::streamFor($contents)))
->setParams($rocket->getParams()['params'] ?? []);
}
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat\Ecommerce\Refund;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Plugin\Wechat\GeneralPlugin;
use Yansongda\Pay\Rocket;
class ApplyPlugin extends GeneralPlugin
{
/**
* @throws \Yansongda\Pay\Exception\InvalidParamsException
*/
protected function getUri(Rocket $rocket): string
{
throw new InvalidParamsException(Exception::NOT_IN_SERVICE_MODE);
}
protected function getPartnerUri(Rocket $rocket): string
{
return 'v3/ecommerce/refunds/apply';
}
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
protected function doSomething(Rocket $rocket): void
{
$config = get_wechat_config($rocket->getParams());
$payload = $rocket->getPayload();
$key = ($rocket->getParams()['_type'] ?? 'mp').'_app_id';
if ('app_app_id' === $key) {
$key = 'app_id';
}
$wechatId = [
'sub_mchid' => $payload->get('sub_mchid', $config->get('sub_mch_id', '')),
'sp_appid' => $payload->get('sp_appid', $config->get($key, '')),
];
if (!$payload->has('notify_url')) {
$wechatId['notify_url'] = $config->get('notify_url');
}
$rocket->mergePayload($wechatId);
}
}

View File

@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat\Ecommerce\Refund;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Plugin\Wechat\GeneralPlugin;
use Yansongda\Pay\Rocket;
class FindPlugin extends GeneralPlugin
{
/**
* @throws \Yansongda\Pay\Exception\InvalidParamsException
*/
protected function getUri(Rocket $rocket): string
{
throw new InvalidParamsException(Exception::NOT_IN_SERVICE_MODE);
}
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\InvalidParamsException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
protected function getPartnerUri(Rocket $rocket): string
{
$payload = $rocket->getPayload();
$config = get_wechat_config($rocket->getParams());
$subMchId = $payload->get('sub_mchid', $config->get('sub_mch_id', ''));
if (!is_null($payload->get('refund_id'))) {
return 'v3/ecommerce/refunds/id/'.$payload->get('refund_id').'?sub_mchid='.$subMchId;
}
if (!is_null($payload->get('out_refund_no'))) {
return 'v3/ecommerce/refunds/out-refund-no/'.$payload->get('out_refund_no').'?sub_mchid='.$subMchId;
}
throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
}
protected function getMethod(): string
{
return 'GET';
}
protected function doSomething(Rocket $rocket): void
{
$rocket->setPayload(null);
}
}

View File

@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat\Ecommerce\Refund;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Plugin\Wechat\GeneralPlugin;
use Yansongda\Pay\Rocket;
class FindReturnAdvancePlugin extends GeneralPlugin
{
/**
* @throws \Yansongda\Pay\Exception\InvalidParamsException
*/
protected function getUri(Rocket $rocket): string
{
throw new InvalidParamsException(Exception::NOT_IN_SERVICE_MODE);
}
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\InvalidParamsException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
protected function getPartnerUri(Rocket $rocket): string
{
$payload = $rocket->getPayload();
$config = get_wechat_config($rocket->getParams());
$subMchId = $payload->get('sub_mchid', $config->get('sub_mch_id', ''));
if (is_null($payload->get('refund_id'))) {
throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
}
return 'v3/ecommerce/refunds/'.$payload->get('refund_id').'/return-advance?sub_mchid='.$subMchId;
}
protected function getMethod(): string
{
return 'GET';
}
protected function doSomething(Rocket $rocket): void
{
$rocket->setPayload(null);
}
}

View File

@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat\Ecommerce\Refund;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Plugin\Wechat\GeneralPlugin;
use Yansongda\Pay\Rocket;
use Yansongda\Supports\Collection;
class ReturnAdvancePlugin extends GeneralPlugin
{
/**
* @throws \Yansongda\Pay\Exception\InvalidParamsException
*/
protected function getUri(Rocket $rocket): string
{
throw new InvalidParamsException(Exception::NOT_IN_SERVICE_MODE);
}
/**
* @throws \Yansongda\Pay\Exception\InvalidParamsException
*/
protected function getPartnerUri(Rocket $rocket): string
{
$payload = $rocket->getPayload();
if (is_null($payload->get('refund_id'))) {
throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
}
return 'v3/ecommerce/refunds/'.$payload->get('refund_id').'/return-advance';
}
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
protected function doSomething(Rocket $rocket): void
{
$config = get_wechat_config($rocket->getParams());
$body = [
'sub_mchid' => $rocket->getPayload()->get('sub_mchid', $config->get('sub_mch_id', '')),
];
$rocket->setPayload(new Collection($body));
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat\Fund\Balance;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Plugin\Wechat\GeneralPlugin;
use Yansongda\Pay\Rocket;
class QueryDayEndPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'GET';
}
protected function doSomething(Rocket $rocket): void
{
}
/**
* @throws \Yansongda\Pay\Exception\InvalidParamsException
*/
protected function getUri(Rocket $rocket): string
{
$payload = $rocket->getPayload();
if (is_null($payload->get('account_type')) || is_null($payload->get('date'))) {
throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
}
return 'v3/merchant/fund/dayendbalance/'.
$payload->get('account_type').
'?date='.$payload->get('date');
}
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat\Fund\Balance;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Plugin\Wechat\GeneralPlugin;
use Yansongda\Pay\Rocket;
class QueryPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'GET';
}
protected function doSomething(Rocket $rocket): void
{
}
/**
* @throws \Yansongda\Pay\Exception\InvalidParamsException
*/
protected function getUri(Rocket $rocket): string
{
$payload = $rocket->getPayload();
if (is_null($payload->get('account_type'))) {
throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
}
return 'v3/merchant/fund/balance/'.
$payload->get('account_type');
}
}

View File

@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat\Fund\Profitsharing;
use Yansongda\Pay\Pay;
use Yansongda\Pay\Plugin\Wechat\GeneralPlugin;
use Yansongda\Pay\Rocket;
use Yansongda\Pay\Traits\HasWechatEncryption;
use Yansongda\Supports\Collection;
class AddReceiverPlugin extends GeneralPlugin
{
use HasWechatEncryption;
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\InvalidConfigException
* @throws \Yansongda\Pay\Exception\InvalidParamsException
* @throws \Yansongda\Pay\Exception\InvalidResponseException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
protected function doSomething(Rocket $rocket): void
{
$params = $rocket->getParams();
$config = get_wechat_config($rocket->getParams());
$extra = $this->getWechatId($config, $rocket->getPayload());
if (!empty($params['name'] ?? '')) {
$params = $this->loadSerialNo($params);
$name = $this->getEncryptUserName($params);
$params['name'] = $name;
$extra['name'] = $name;
$rocket->setParams($params);
}
$rocket->mergePayload($extra);
}
protected function getUri(Rocket $rocket): string
{
return 'v3/profitsharing/receivers/add';
}
protected function getWechatId(Collection $config, Collection $payload): array
{
$wechatId = [
'appid' => $config->get('mp_app_id'),
];
if (Pay::MODE_SERVICE == $config->get('mode')) {
$wechatId['sub_mchid'] = $payload->get('sub_mchid', $config->get('sub_mch_id', ''));
}
return $wechatId;
}
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\InvalidParamsException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
protected function getEncryptUserName(array $params): string
{
$name = $params['name'] ?? '';
$publicKey = $this->getPublicKey($params, $params['_serial_no'] ?? '');
$name = encrypt_wechat_contents($name, $publicKey);
return $name;
}
}

Some files were not shown because too many files have changed in this diff Show More