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

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;
}
}

View File

@@ -0,0 +1,81 @@
<?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 CreatePlugin 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
{
$payload = $rocket->getPayload();
$params = $this->loadSerialNo($rocket->getParams());
$extra = $this->getWechatExtra($params, $payload);
$extra['receivers'] = $this->getReceivers($params);
$rocket->setParams($params);
$rocket->mergePayload($extra);
}
protected function getUri(Rocket $rocket): string
{
return 'v3/profitsharing/orders';
}
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
protected function getWechatExtra(array $params, Collection $payload): array
{
$config = get_wechat_config($params);
$extra = [
'appid' => $config->get('mp_app_id'),
];
if (Pay::MODE_SERVICE == $config->get('mode')) {
$extra['sub_mchid'] = $payload->get('sub_mchid', $config->get('sub_mch_id', ''));
}
return $extra;
}
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\InvalidParamsException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
protected function getReceivers(array $params): array
{
$publicKey = $this->getPublicKey($params, $params['_serial_no'] ?? '');
$receivers = $params['receivers'] ?? [];
foreach ($receivers as $key => $receiver) {
if (!empty($receiver['name'])) {
$receivers[$key]['name'] = encrypt_wechat_contents($receiver['name'], $publicKey);
}
}
return $receivers;
}
}

View File

@@ -0,0 +1,38 @@
<?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;
class DeleteReceiverPlugin extends GeneralPlugin
{
/**
* @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());
$wechatId = [
'appid' => $config->get('mp_app_id'),
];
if (Pay::MODE_SERVICE == $config->get('mode')) {
$wechatId['sub_mchid'] = $rocket->getPayload()
->get('sub_mchid', $config->get('sub_mch_id', ''));
}
$rocket->mergePayload($wechatId);
}
protected function getUri(Rocket $rocket): string
{
return 'v3/profitsharing/receivers/delete';
}
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat\Fund\Profitsharing;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Plugin\Wechat\GeneralPlugin;
use Yansongda\Pay\Rocket;
class QueryAmountsPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'GET';
}
protected function doSomething(Rocket $rocket): void
{
$rocket->setPayload(null);
}
/**
* @throws \Yansongda\Pay\Exception\InvalidParamsException
*/
protected function getUri(Rocket $rocket): string
{
$payload = $rocket->getPayload();
if (is_null($payload->get('transaction_id'))) {
throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
}
return 'v3/profitsharing/transactions/'.
$payload->get('transaction_id').
'/amounts';
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat\Fund\Profitsharing;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Pay;
use Yansongda\Pay\Plugin\Wechat\GeneralPlugin;
use Yansongda\Pay\Rocket;
class QueryMerchantConfigsPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'GET';
}
protected function doSomething(Rocket $rocket): void
{
$rocket->setPayload(null);
}
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\InvalidParamsException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
protected function getUri(Rocket $rocket): string
{
$config = get_wechat_config($rocket->getParams());
$payload = $rocket->getPayload();
if (Pay::MODE_SERVICE !== $config->get('mode', Pay::MODE_NORMAL)) {
throw new InvalidParamsException(Exception::METHOD_NOT_SUPPORTED);
}
return 'v3/profitsharing/merchant-configs/'.
$payload->get('sub_mchid', $config->get('sub_mch_id', ''));
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat\Fund\Profitsharing;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Pay;
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
{
$rocket->setPayload(null);
}
/**
* @throws \Yansongda\Pay\Exception\ContainerDependencyException
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\InvalidParamsException
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
*/
protected function getUri(Rocket $rocket): string
{
$payload = $rocket->getPayload();
$config = get_wechat_config($rocket->getParams());
if (is_null($payload->get('out_order_no')) ||
is_null($payload->get('transaction_id'))) {
throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
}
$url = 'v3/profitsharing/orders/'.
$payload->get('out_order_no').
'?transaction_id='.$payload->get('transaction_id');
if (Pay::MODE_SERVICE == $config->get('mode')) {
$url .= '&sub_mchid='.$payload->get('sub_mchid', $config->get('sub_mch_id', ''));
}
return $url;
}
}

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