This commit is contained in:
yansongda 2021-05-30 23:03:30 +08:00
parent 1134c1a447
commit b49fafb976
17 changed files with 287 additions and 101 deletions

View File

@ -21,15 +21,17 @@
"ext-libxml": "*",
"ext-json": "*",
"php-di/php-di": "~6.3.0",
"yansongda/supports": "~3.0.0",
"symfony/event-dispatcher": "~5.2.0",
"symfony/http-foundation": "~5.2.0"
"yansongda/supports": "~3.1.0",
"symfony/event-dispatcher": "^5.2.0",
"guzzlehttp/guzzle": "^7.0",
"monolog/monolog": "^2.2"
},
"require-dev": {
"phpunit/phpunit": "^9.0",
"mockery/mockery": "^1.4",
"friendsofphp/php-cs-fixer": "^2.15",
"symfony/var-dumper": "^5.1"
"symfony/var-dumper": "^5.1",
"symfony/http-foundation": "~5.2.0"
},
"autoload": {
"psr-4": {

View File

@ -5,9 +5,14 @@ declare(strict_types=1);
namespace Yansongda\Pay\Contract;
use Closure;
use Yansongda\Supports\Collection;
use Yansongda\Pay\Rocket;
interface PluginInterface
{
public function apply(array $params, Collection $payload, Closure $next): Collection;
/**
* @author yansongda <me@yansongda.cn>
*
* @return \Yansongda\Supports\Collection|\Symfony\Component\HttpFoundation\Response
*/
public function assembly(Rocket $rocket, Closure $next);
}

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Contract;
interface ShortcutInterface
{
/**
* @author yansongda <me@yansongda.cn>
*
* @return \Yansongda\Pay\Contract\PluginInterface[]
*/
public function getPlugins(): array;
}

View File

@ -8,7 +8,7 @@ use Throwable;
class InvalidParamsException extends Exception
{
public function __construct(string $message = 'Params Error', int $code = self::PARAMS_ERROR, array $extra = [], Throwable $previous = null)
public function __construct(int $code = self::PARAMS_ERROR, string $message = 'Params Error', array $extra = [], Throwable $previous = null)
{
parent::__construct($message, $code, $extra, $previous);
}

View File

@ -224,7 +224,8 @@ class Pay
try {
$container = $builder->build();
$container->set(ContainerInterface::class, $this);
$container->set(ContainerInterface::class, $container);
$container->set(\Psr\Container\ContainerInterface::class, $container);
$container->set(Pay::class, $this);
self::$container = $container;

View File

@ -5,16 +5,23 @@ declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay;
use Closure;
use Yansongda\Supports\Collection;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Rocket;
use Yansongda\Supports\Str;
class FilterPlugin
class FilterPlugin implements PluginInterface
{
public function apply(array $params, Collection $payload, Closure $next): Collection
/**
* @return \Yansongda\Supports\Collection|\Symfony\Component\HttpFoundation\Response
*/
public function assembly(Rocket $rocket, Closure $next)
{
$payload = $payload->filter(function ($v, $k) {
return '' !== $v && !is_null($v) && 'sign' != $k && '_config' != $k;
$payload = $rocket->getPayload()->filter(function ($v, $k) {
return '' !== $v && !is_null($v) && 'sign' != $k && Str::startsWith($k, '_');
});
return $next($params, $payload);
$payload->set('biz_content', json_encode($payload->get('biz_content')));
return $next($rocket->setPayload($payload));
}
}

View File

@ -5,18 +5,31 @@ declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay;
use Closure;
use Yansongda\Supports\Collection;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Rocket;
class IgnitePlugin
class PreparePlugin implements PluginInterface
{
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
public function apply(array $params, Collection $payload, Closure $next): Collection
public function assembly(Rocket $rocket, Closure $next)
{
$payload->merge([
return $next(
$rocket->mergePayload($this->getPayload($rocket->getParams()))
);
}
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
protected function getPayload(array $params): array
{
return [
'app_id' => get_alipay_config($params)['app_id'] ?? '',
'method' => '',
'format' => 'JSON',
@ -28,9 +41,7 @@ class IgnitePlugin
'version' => '1.0',
'notify_url' => get_alipay_config($params)['notify_url'] ?? '',
'app_auth_token' => '',
'biz_content' => '',
]);
return $next($params, $payload);
'biz_content' => [],
];
}
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Pay;
use Yansongda\Pay\Provider\Alipay;
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)
{
$config = get_alipay_config($rocket->getParams());
return $next($rocket->setRadar(
$config['mode'] ?? Alipay::URL[Pay::MODE_NORMAL]
));
}
}

View File

@ -10,8 +10,8 @@ use Yansongda\Supports\Collection;
class AppPlugin extends AppPayPlugin
{
public function apply(array $params, Collection $payload, Closure $next): Collection
public function assembly(array $params, Collection $payload, Closure $next): Collection
{
return parent::apply(...func_get_args());
return parent::assembly(...func_get_args());
}
}

View File

@ -6,12 +6,12 @@ namespace Yansongda\Pay\Plugin\Alipay\Shortcut;
use Closure;
use Yansongda\Pay\Plugin\Alipay\Trade\PagePayPlugin;
use Yansongda\Supports\Collection;
use Yansongda\Pay\Rocket;
class WebPlugin extends PagePayPlugin
{
public function apply(array $params, Collection $payload, Closure $next): Collection
public function assembly(Rocket $rocket, Closure $next): Rocket
{
return parent::apply(...func_get_args());
return parent::assembly(...func_get_args());
}
}

View File

@ -6,14 +6,16 @@ namespace Yansongda\Pay\Plugin\Alipay;
use Closure;
use Yansongda\Pay\Contract\ConfigInterface;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Exception\InvalidConfigException;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Pay;
use Yansongda\Pay\Rocket;
use Yansongda\Supports\Arr;
use Yansongda\Supports\Collection;
use Yansongda\Supports\Str;
class SignPlugin
class SignPlugin implements PluginInterface
{
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
@ -21,8 +23,9 @@ class SignPlugin
* @throws \Yansongda\Pay\Exception\InvalidConfigException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
public function apply(array $params, Collection $payload, Closure $next): Collection
public function assembly(Rocket $rocket, Closure $next)
{
$payload = $rocket->getPayload();
$privateKey = $this->getPrivateKey();
openssl_sign($this->getSignContent($payload), $sign, $privateKey, OPENSSL_ALGO_SHA256);
@ -33,9 +36,9 @@ class SignPlugin
!is_resource($privateKey) ?: openssl_free_key($privateKey);
$payload = $payload->merge(['sign' => $sign]);
return $next($params, $payload);
return $next($rocket->mergePayload([
'sign' => $sign,
]));
}
/**

View File

@ -5,22 +5,19 @@ declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Closure;
use Yansongda\Supports\Collection;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Rocket;
class AppPayPlugin
class AppPayPlugin implements PluginInterface
{
public function apply(array $params, Collection $payload, Closure $next): Collection
public function assembly(Rocket $rocket, Closure $next): Rocket
{
$payload = $payload->merge([
return $next($rocket->mergePayload([
'method' => 'alipay.trade.app.pay',
'biz_content' => json_encode(array_merge(
[
'product_code' => 'QUICK_MSECURITY_PAY',
],
$params
)),
]);
return $next($params, $payload);
'biz_content' => array_merge(
['product_code' => 'QUICK_MSECURITY_PAY'],
$rocket->getParams(),
),
]));
}
}

View File

@ -5,22 +5,22 @@ declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Closure;
use Yansongda\Supports\Collection;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Rocket;
class PagePayPlugin
class PagePayPlugin implements PluginInterface
{
public function apply(array $params, Collection $payload, Closure $next): Collection
public function assembly(Rocket $rocket, Closure $next): Rocket
{
$payload = $payload->merge([
return $next(
$rocket->setType(Response::class)
->mergePayload([
'method' => 'alipay.trade.page.pay',
'biz_content' => json_encode(array_merge(
[
'product_code' => 'FAST_INSTANT_TRADE_PAY',
],
$params
)),
]);
return $next($params, $payload);
'biz_content' => array_merge(
['product_code' => 'FAST_INSTANT_TRADE_PAY'],
$rocket->getParams()
),
])
);
}
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Provider;
use Yansongda\Pay\Contract\ProviderInterface;
abstract class AbstractProvider implements ProviderInterface
{
}

View File

@ -4,14 +4,18 @@ declare(strict_types=1);
namespace Yansongda\Pay\Provider;
use Symfony\Component\HttpFoundation\Response;
use Yansongda\Pay\Contract\PluginInterface;
use Psr\Http\Message\RequestInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Yansongda\Pay\Contract\ShortcutInterface;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Pay;
use Yansongda\Pay\Plugin\Alipay\FilterPlugin;
use Yansongda\Pay\Plugin\Alipay\IgnitePlugin;
use Yansongda\Pay\Plugin\Alipay\PreparePlugin;
use Yansongda\Pay\Plugin\Alipay\RadarPlugin;
use Yansongda\Pay\Plugin\Alipay\SignPlugin;
use Yansongda\Pay\Rocket;
use Yansongda\Supports\Collection;
use Yansongda\Supports\Pipeline;
use Yansongda\Supports\Str;
class Alipay
@ -23,64 +27,76 @@ class Alipay
];
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\InvalidParamsException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*
* @return \Yansongda\Supports\Collection
*/
public function __call(string $method, array $params)
public function __call(string $shortcut, array $params)
{
$plugin = '\\Yansongda\\Pay\\Plugin\\Alipay\\Shortcut\\'.
Str::studly($method).'Plugin';
Str::studly($shortcut).'Shortcut';
if (!class_exists($plugin) || !in_array(PluginInterface::class, class_implements($plugin))) {
throw new InvalidParamsException('Shortcut not found', InvalidParamsException::SHORTCUT_NOT_FOUND);
if (!class_exists($plugin) || !in_array(ShortcutInterface::class, class_implements($plugin))) {
throw new InvalidParamsException(InvalidParamsException::SHORTCUT_NOT_FOUND, "[$plugin] not found");
}
return $this->pay([$plugin], ...$params);
/* @var ShortcutInterface $money */
$money = Pay::get($plugin);
return $this->pay($money->getPlugins(), ...$params);
}
/**
* @return \Yansongda\Supports\Collection
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*
* @return \Yansongda\Supports\Collection|\Symfony\Component\HttpFoundation\Response
*/
public function pay(array $plugins, array $order)
public function pay(array $plugins, array $params)
{
$plugins = array_merge(
[IgnitePlugin::class],
[PreparePlugin::class],
$plugins,
[FilterPlugin::class, SignPlugin::class]
[FilterPlugin::class, SignPlugin::class, RadarPlugin::class]
);
// todo
$payload = [];
/* @var Pipeline $pipeline */
$pipeline = Pay::get(Pipeline::class);
return $this->launch($payload);
return $pipeline
->send((new Rocket())->setParams($params)->setPayload(new Collection()))
->through($plugins)
->via('assembly')
->then(function ($rocket) {
return $this->ignite($rocket);
});
}
public function find($order): Collection
public function ignite(Rocket $rocket)
{
}
public function refund(array $order): Collection
protected function launchResponse(RequestInterface $radar, Collection $payload): Response
{
$method = $radar->getMethod();
$endpoint = $radar->getUri()->getScheme().'://'.$radar->getUri()->getHost();
if ('GET' === $method) {
return new RedirectResponse($radar->getUri()->__toString());
}
public function cancel($order): Collection
{
$sHtml = "<form id='alipay_submit' name='alipay_submit' action='".$endpoint."' method='".$method."'>";
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>";
public function close($order): Collection
{
}
public function verify($content): Collection
{
}
public function success(): Response
{
}
public function launch(array $payload): Collection
{
return new Response($sHtml);
}
}

View File

@ -5,12 +5,10 @@ declare(strict_types=1);
namespace Yansongda\Pay\Provider;
use Symfony\Component\HttpFoundation\Response;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Contract\ProviderInterface;
use Yansongda\Pay\Pay;
use Yansongda\Supports\Collection;
class Wechat implements ProviderInterface
class Wechat
{
const URL = [
Pay::MODE_NORMAL => 'https://api.mch.weixin.qq.com/',
@ -18,11 +16,6 @@ class Wechat implements ProviderInterface
Pay::MODE_SERVICE => 'https://api.mch.weixin.qq.com/',
];
public function addMiddleware(PluginInterface $middleware): ProviderInterface
{
// TODO: Implement addMiddleware() method.
}
public function pay($order, ?array $middleware = [])
{
// TODO: Implement pay() method.

97
src/Rocket.php Normal file
View File

@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay;
use ArrayAccess;
use JsonSerializable as JsonSerializableInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Serializable as SerializableInterface;
use Yansongda\Supports\Collection;
use Yansongda\Supports\Traits\Accessable;
use Yansongda\Supports\Traits\Arrayable;
use Yansongda\Supports\Traits\Serializable;
class Rocket implements JsonSerializableInterface, SerializableInterface, ArrayAccess
{
use Accessable;
use Arrayable;
use Serializable;
/**
* @var \Psr\Http\Message\RequestInterface
*/
private $radar;
/**
* @var array
*/
private $params;
/**
* @var \Yansongda\Supports\Collection
*/
private $payload;
/**
* @var \Psr\Http\Message\ResponseInterface
*/
private $destination;
public function getRadar(): RequestInterface
{
return $this->radar;
}
public function setRadar(RequestInterface $radar): Rocket
{
$this->radar = $radar;
return $this;
}
public function getParams(): array
{
return $this->params;
}
public function setParams(array $params): Rocket
{
$this->params = $params;
return $this;
}
public function getPayload(): Collection
{
return $this->payload;
}
public function setPayload(Collection $payload): Rocket
{
$this->payload = $payload;
return $this;
}
public function mergePayload(array $payload): Rocket
{
$this->payload = $this->payload->merge($payload);
return $this;
}
public function getDestination(): ResponseInterface
{
return $this->destination;
}
public function setDestination(ResponseInterface $destination): Rocket
{
$this->destination = $destination;
return $this;
}
}