- 框架初始化
 - 安装插件
 - 修复PHP8.4报错
This commit is contained in:
2025-04-19 17:21:20 +08:00
commit c6a4e1f5f6
5306 changed files with 967782 additions and 0 deletions

View File

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

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Data;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
/**
* @see https://opendocs.alipay.com/open/02fkbl
*/
class BillDownloadUrlQueryPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.data.dataservice.bill.downloadurl.query';
}
}

View File

@@ -0,0 +1,35 @@
<?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;
/**
* @see https://opendocs.alipay.com/open/029p6g
*/
class BillEreceiptApplyPlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[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,18 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Data;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
/**
* @see https://opendocs.alipay.com/open/029i7e
*/
class BillEreceiptQueryPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.data.bill.ereceipt.query';
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Ebpp;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
/**
* @see https://opendocs.alipay.com/open/02hd36
*/
class PdeductBillStatusPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.ebpp.pdeduct.bill.pay.status';
}
}

View File

@@ -0,0 +1,35 @@
<?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;
/**
* @see https://opendocs.alipay.com/open/02hd35
*/
class PdeductPayPlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[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,37 @@
<?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;
/**
* @see https://opendocs.alipay.com/open/02hd33
*/
class PdeductSignAddPlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[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,35 @@
<?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;
/**
* @see https://opendocs.alipay.com/open/02hd34
*/
class PdeductSignCancelPlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[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,35 @@
<?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;
/**
* @see https://opendocs.alipay.com/open/02byuq?scene=common
*/
class AccountQueryPlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[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,35 @@
<?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;
/**
* @see https://opendocs.alipay.com/open/02fkb9
*/
class AuthOrderFreezePlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[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,18 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Fund;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
/**
* @see https://opendocs.alipay.com/open/02fkbc
*/
class AuthOrderUnfreezePlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.fund.auth.order.unfreeze';
}
}

View File

@@ -0,0 +1,36 @@
<?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;
/**
* @see https://opendocs.alipay.com/open/02byve
*/
class TransCommonQueryPlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[alipay][TransCommonQueryPlugin] 插件开始装载', ['rocket' => $rocket]);
$rocket->mergePayload([
'method' => 'alipay.fund.trans.common.query',
'biz_content' => array_merge(
[
'product_code' => 'TRANS_ACCOUNT_NO_PWD',
'biz_scene' => 'DIRECT_TRANSFER',
],
$rocket->getParams(),
),
]);
Logger::info('[alipay][TransCommonQueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
}

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\Direction\ResponseDirection;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
/**
* @see https://opendocs.alipay.com/open/03rbye
*/
class TransPagePayPlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[alipay][TransPagePayPlugin] 插件开始装载', ['rocket' => $rocket]);
$rocket->setDirection(ResponseDirection::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,36 @@
<?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;
/**
* @see https://opendocs.alipay.com/open/02byuo?scene=common
*/
class TransUniTransferPlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[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::debug('[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,71 @@
<?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::debug('[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,73 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidConfigException;
use Yansongda\Pay\Exception\InvalidResponseException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
use Yansongda\Supports\Collection;
use function Yansongda\Pay\should_do_http_request;
use function Yansongda\Pay\verify_alipay_sign;
class LaunchPlugin implements PluginInterface
{
/**
* @throws ContainerException
* @throws InvalidConfigException
* @throws InvalidResponseException
* @throws ServiceNotFoundException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
/* @var Rocket $rocket */
$rocket = $next($rocket);
Logger::debug('[alipay][LaunchPlugin] 插件开始装载', ['rocket' => $rocket]);
if (should_do_http_request($rocket->getDirection())) {
$response = Collection::wrap($rocket->getDestination());
$result = $response->get($this->getResultKey($rocket->getPayload()));
$this->verifySign($rocket->getParams(), $response, $result);
$rocket->setDestination(Collection::wrap($result));
}
Logger::info('[alipay][LaunchPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $rocket;
}
/**
* @throws ContainerException
* @throws InvalidConfigException
* @throws InvalidResponseException
* @throws ServiceNotFoundException
*/
protected function verifySign(array $params, Collection $response, ?array $result): void
{
$sign = $response->get('sign', '');
if ('' === $sign || is_null($result)) {
throw new InvalidResponseException(Exception::INVALID_RESPONSE_SIGN, 'Verify Alipay Response Sign Failed: sign is empty', $response);
}
verify_alipay_sign($params, json_encode($result, JSON_UNESCAPED_UNICODE), $sign);
}
protected function getResultKey(Collection $payload): string
{
$method = $payload->get('method');
return str_replace('.', '_', $method).'_response';
}
}

View File

@@ -0,0 +1,214 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay;
use Closure;
use Yansongda\Pay\Contract\ConfigInterface;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidConfigException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Pay;
use Yansongda\Pay\Rocket;
use function Yansongda\Pay\get_alipay_config;
use function Yansongda\Pay\get_tenant;
class PreparePlugin implements PluginInterface
{
/**
* @throws ContainerException
* @throws ServiceNotFoundException
* @throws InvalidConfigException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[alipay][PreparePlugin] 插件开始装载', ['rocket' => $rocket]);
$rocket->mergePayload($this->getPayload($rocket->getParams()));
Logger::info('[alipay][PreparePlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
/**
* @throws ContainerException
* @throws ServiceNotFoundException
* @throws InvalidConfigException
*/
protected function getPayload(array $params): array
{
$tenant = get_tenant($params);
$config = get_alipay_config($params);
$payload = [
'app_id' => $config['app_id'] ?? '',
'method' => '',
'format' => 'JSON',
'return_url' => $this->getReturnUrl($params, $config),
'charset' => 'utf-8',
'sign_type' => 'RSA2',
'sign' => '',
'timestamp' => date('Y-m-d H:i:s'),
'version' => '1.0',
'notify_url' => $this->getNotifyUrl($params, $config),
'app_auth_token' => $this->getAppAuthToken($params, $config),
'biz_content' => [],
];
if (!empty($config['app_cert_public_key']) && !empty($config['alipay_root_cert'])) {
$payload = array_merge($payload, ['app_cert_sn' => $this->getAppCertSn($tenant, $config), 'alipay_root_cert_sn' => $this->getAlipayRootCertSn($tenant, $config)]);
}
return $payload;
}
protected function getReturnUrl(array $params, ?array $config): string
{
if (!empty($params['_return_url'])) {
return $params['_return_url'];
}
return $config['return_url'] ?? '';
}
protected function getNotifyUrl(array $params, ?array $config): string
{
if (!empty($params['_notify_url'])) {
return $params['_notify_url'];
}
return $config['notify_url'] ?? '';
}
protected function getAppAuthToken(array $params, ?array $config): string
{
if (!empty($params['_app_auth_token'])) {
return $params['_app_auth_token'];
}
return $config['app_auth_token'] ?? '';
}
/**
* @throws ContainerException
* @throws InvalidConfigException
* @throws ServiceNotFoundException
*/
protected function getAppCertSn(string $tenant, ?array $config): string
{
if (!empty($config['app_public_cert_sn'])) {
return $config['app_public_cert_sn'];
}
$path = $config['app_public_cert_path'] ?? null;
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);
if (false === $ssl) {
throw new InvalidConfigException(Exception::ALIPAY_CONFIG_ERROR, 'Parse `app_public_cert_path` Error');
}
$result = $this->getCertSn($ssl['issuer'] ?? [], $ssl['serialNumber'] ?? '');
Pay::get(ConfigInterface::class)->set('alipay.'.$tenant.'.app_public_cert_sn', $result);
return $result;
}
/**
* @throws ContainerException
* @throws InvalidConfigException
* @throws ServiceNotFoundException
*/
protected function getAlipayRootCertSn(string $tenant, ?array $config): string
{
if (!empty($config['alipay_root_cert_sn'])) {
return $config['alipay_root_cert_sn'];
}
$path = $config['alipay_root_cert_path'] ?? null;
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']).'_';
}
}
$result = substr($sn, 0, -1);
Pay::get(ConfigInterface::class)->set('alipay.'.$tenant.'.alipay_root_cert_sn', $result);
return $result;
}
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,144 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidConfigException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Pay;
use Yansongda\Pay\Provider\Alipay;
use Yansongda\Pay\Request;
use Yansongda\Pay\Rocket;
use Yansongda\Supports\Collection;
use Yansongda\Supports\Str;
use function Yansongda\Pay\get_alipay_config;
use function Yansongda\Pay\get_private_cert;
class RadarSignPlugin implements PluginInterface
{
/**
* @throws ContainerException
* @throws InvalidConfigException
* @throws ServiceNotFoundException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[alipay][RadarSignPlugin] 插件开始装载', ['rocket' => $rocket]);
$this->sign($rocket);
$this->reRadar($rocket);
Logger::info('[alipay][RadarSignPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
/**
* @throws ContainerException
* @throws InvalidConfigException
* @throws ServiceNotFoundException
*/
protected function sign(Rocket $rocket): void
{
$this->formatPayload($rocket);
$sign = $this->getSign($rocket);
$rocket->mergePayload(['sign' => $sign]);
}
/**
* @throws ContainerException
* @throws ServiceNotFoundException
*/
protected function reRadar(Rocket $rocket): void
{
$params = $rocket->getParams();
$rocket->setRadar(new Request(
$this->getMethod($params),
$this->getUrl($params),
$this->getHeaders(),
$this->getBody($rocket->getPayload()),
));
}
protected function formatPayload(Rocket $rocket): void
{
$payload = $rocket->getPayload()->filter(fn ($v, $k) => '' !== $v && !is_null($v) && 'sign' != $k);
$contents = array_filter($payload->get('biz_content', []), fn ($v, $k) => !Str::startsWith(strval($k), '_'), ARRAY_FILTER_USE_BOTH);
$rocket->setPayload(
$payload->merge(['biz_content' => json_encode($contents)])
);
}
/**
* @throws ContainerException
* @throws InvalidConfigException
* @throws 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);
return base64_encode($sign);
}
/**
* @throws ContainerException
* @throws InvalidConfigException
* @throws ServiceNotFoundException
*/
protected function getPrivateKey(array $params): string
{
$privateKey = get_alipay_config($params)['app_secret_cert'] ?? null;
if (is_null($privateKey)) {
throw new InvalidConfigException(Exception::ALIPAY_CONFIG_ERROR, 'Missing Alipay Config -- [app_secret_cert]');
}
return get_private_cert($privateKey);
}
protected function getMethod(array $params): string
{
return strtoupper($params['_method'] ?? 'POST');
}
/**
* @throws ContainerException
* @throws ServiceNotFoundException
*/
protected function getUrl(array $params): string
{
$config = get_alipay_config($params);
return Alipay::URL[$config['mode'] ?? Pay::MODE_NORMAL];
}
protected function getHeaders(): array
{
return [
'Content-Type' => 'application/x-www-form-urlencoded',
];
}
protected function getBody(Collection $payload): string
{
return $payload->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,55 @@
<?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\TransCommonQueryPlugin;
use Yansongda\Pay\Plugin\Alipay\Trade\FastRefundQueryPlugin;
use Yansongda\Pay\Plugin\Alipay\Trade\QueryPlugin;
use Yansongda\Supports\Str;
class QueryShortcut implements ShortcutInterface
{
/**
* @throws InvalidParamsException
*/
public function getPlugins(array $params): array
{
$typeMethod = Str::camel($params['_action'] ?? 'default').'Plugins';
if (isset($params['out_request_no'])) {
return $this->refundPlugins();
}
if (method_exists($this, $typeMethod)) {
return $this->{$typeMethod}();
}
throw new InvalidParamsException(Exception::SHORTCUT_MULTI_ACTION_ERROR, "Query action [{$typeMethod}] not supported");
}
protected function defaultPlugins(): array
{
return [
QueryPlugin::class,
];
}
protected function refundPlugins(): array
{
return [
FastRefundQueryPlugin::class,
];
}
protected function transferPlugins(): array
{
return [
TransCommonQueryPlugin::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,18 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Tools;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
/**
* @see https://opendocs.alipay.com/isv/03l9c0
*/
class OpenAuthTokenAppPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.open.auth.token.app';
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Tools;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
/**
* @see https://opendocs.alipay.com/isv/03l8ca
*/
class OpenAuthTokenAppQueryPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.open.auth.token.app.query';
}
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Tools;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
use Yansongda\Pay\Rocket;
/**
* @see https://opendocs.alipay.com/open/02ailc
*/
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,47 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Direction\ResponseDirection;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
use Yansongda\Pay\Traits\SupportServiceProviderTrait;
/**
* @see https://opendocs.alipay.com/open/02e7gq?scene=common
*/
class AppPayPlugin implements PluginInterface
{
use SupportServiceProviderTrait;
/**
* @throws ContainerException
* @throws ServiceNotFoundException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[alipay][AppPayPlugin] 插件开始装载', ['rocket' => $rocket]);
$this->loadAlipayServiceProvider($rocket);
$rocket->setDirection(ResponseDirection::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,18 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
/**
* @see https://opendocs.alipay.com/open/02ekfi
*/
class CancelPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.trade.cancel';
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
/**
* @see https://opendocs.alipay.com/open/02o6e7
*/
class ClosePlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.trade.close';
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
/**
* @see https://opendocs.alipay.com/open/02ekfj
*/
class CreatePlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.trade.create';
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
/**
* @see https://opendocs.alipay.com/open/028sma
*/
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,18 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
/**
* @see https://opendocs.alipay.com/open/028xqz
*/
class OrderSettlePlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.trade.order.settle';
}
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Direction\ResponseDirection;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
use Yansongda\Pay\Traits\SupportServiceProviderTrait;
/**
* @see https://opendocs.alipay.com/open/028r8t?scene=22
*/
class PagePayPlugin implements PluginInterface
{
use SupportServiceProviderTrait;
/**
* @throws ContainerException
* @throws ServiceNotFoundException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[alipay][PagePayPlugin] 插件开始装载', ['rocket' => $rocket]);
$this->loadAlipayServiceProvider($rocket);
$rocket->setDirection(ResponseDirection::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,30 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Direction\ResponseDirection;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
class PageRefundPlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[alipay][PageRefundPlugin] 插件开始装载', ['rocket' => $rocket]);
$rocket->setDirection(ResponseDirection::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,47 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
use Yansongda\Pay\Traits\SupportServiceProviderTrait;
/**
* @see https://opendocs.alipay.com/open/02fkat?ref=api&scene=common
*/
class PayPlugin implements PluginInterface
{
use SupportServiceProviderTrait;
/**
* @throws ContainerException
* @throws ServiceNotFoundException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[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,33 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
use Yansongda\Pay\Rocket;
use Yansongda\Pay\Traits\SupportServiceProviderTrait;
/**
* @see https://opendocs.alipay.com/open/02ekfg?scene=common
*/
class PreCreatePlugin extends GeneralPlugin
{
use SupportServiceProviderTrait;
/**
* @throws ContainerException
* @throws ServiceNotFoundException
*/
protected function doSomethingBefore(Rocket $rocket): void
{
$this->loadAlipayServiceProvider($rocket);
}
protected function getMethod(): string
{
return 'alipay.trade.precreate';
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
/**
* @see https://opendocs.alipay.com/open/02ekfh?scene=common
*/
class QueryPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.trade.query';
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
/**
* @see https://opendocs.alipay.com/open/02ekfk
*/
class RefundPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.trade.refund';
}
}

View File

@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\Trade;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Direction\ResponseDirection;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
use Yansongda\Pay\Traits\SupportServiceProviderTrait;
/**
* @see https://opendocs.alipay.com/open/02ivbs?scene=common
*/
class WapPayPlugin implements PluginInterface
{
use SupportServiceProviderTrait;
/**
* @throws ContainerException
* @throws ServiceNotFoundException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[alipay][WapPayPlugin] 插件开始装载', ['rocket' => $rocket]);
$this->loadAlipayServiceProvider($rocket);
$rocket->setDirection(ResponseDirection::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,18 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\User;
use Yansongda\Pay\Plugin\Alipay\GeneralPlugin;
/**
* @see https://opendocs.alipay.com/open/02fkaq?ref=api
*/
class AgreementExecutionPlanModifyPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'alipay.user.agreement.executionplan.modify';
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Alipay\User;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Direction\ResponseDirection;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
/**
* @see https://opendocs.alipay.com/open/02fkan?ref=api&scene=35
*/
class AgreementPageSignPlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[alipay][AgreementPageSignPlugin] 插件开始装载', ['rocket' => $rocket]);
$rocket->setDirection(ResponseDirection::class)
->mergePayload([
'method' => 'alipay.user.agreement.page.sign',
'biz_content' => array_merge(
['product_code' => 'CYCLE_PAY_AUTH'],
$rocket->getParams()
),
])
;
Logger::info('[alipay][AgreementPageSignPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
}

View File

@@ -0,0 +1,33 @@
<?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;
/**
* @see https://opendocs.alipay.com/open/02fkao?ref=api&scene=8837b4183390497f84bb53783b488ecc
*/
class AgreementQueryPlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[alipay][AgreementQueryPlugin] 插件开始装载', ['rocket' => $rocket]);
$rocket->mergePayload([
'method' => 'alipay.user.agreement.query',
'biz_content' => array_merge(
['personal_product_code' => 'CYCLE_PAY_AUTH_P'],
$rocket->getParams()
),
]);
Logger::info('[alipay][AgreementQueryPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
}

View File

@@ -0,0 +1,33 @@
<?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;
/**
* @see https://opendocs.alipay.com/open/02fkar?ref=api
*/
class AgreementTransferPlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[alipay][AgreementTransferPlugin] 插件开始装载', ['rocket' => $rocket]);
$rocket->mergePayload([
'method' => 'alipay.user.agreement.transfer',
'biz_content' => array_merge(
['target_product_code' => 'CYCLE_PAY_AUTH_P'],
$rocket->getParams()
),
]);
Logger::info('[alipay][AgreementTransferPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
}

View File

@@ -0,0 +1,33 @@
<?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;
/**
* @see https://opendocs.alipay.com/open/02fkap?ref=api&scene=90766afb41f74df6ae1676e89625ebac
*/
class AgreementUnsignPlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[alipay][AgreementUnsignPlugin] 插件开始装载', ['rocket' => $rocket]);
$rocket->mergePayload([
'method' => 'alipay.user.agreement.unsign',
'biz_content' => array_merge(
['personal_product_code' => 'CYCLE_PAY_AUTH_P'],
$rocket->getParams()
),
]);
Logger::info('[alipay][AgreementUnsignPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
}

View File

@@ -0,0 +1,30 @@
<?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;
/**
* @see https://opendocs.alipay.com/open/02aild
*/
class InfoSharePlugin implements PluginInterface
{
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[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,74 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin;
use Closure;
use Psr\Http\Message\ResponseInterface;
use Yansongda\Pay\Contract\DirectionInterface;
use Yansongda\Pay\Contract\PackerInterface;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidConfigException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Pay;
use Yansongda\Pay\Rocket;
class ParserPlugin implements PluginInterface
{
/**
* @throws ServiceNotFoundException
* @throws ContainerException
* @throws InvalidConfigException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
/* @var Rocket $rocket */
$rocket = $next($rocket);
/* @var ResponseInterface $response */
$response = $rocket->getDestination();
return $rocket->setDestination(
$this->getDirection($rocket)->parse($this->getPacker($rocket), $response)
);
}
/**
* @throws ContainerException
* @throws InvalidConfigException
* @throws ServiceNotFoundException
*/
protected function getDirection(Rocket $rocket): DirectionInterface
{
$packer = Pay::get($rocket->getDirection());
$packer = is_string($packer) ? Pay::get($packer) : $packer;
if (!$packer instanceof DirectionInterface) {
throw new InvalidConfigException(Exception::INVALID_PARSER);
}
return $packer;
}
/**
* @throws ContainerException
* @throws InvalidConfigException
* @throws ServiceNotFoundException
*/
protected function getPacker(Rocket $rocket): PackerInterface
{
$packer = Pay::get($rocket->getPacker());
$packer = is_string($packer) ? Pay::get($packer) : $packer;
if (!$packer instanceof PackerInterface) {
throw new InvalidConfigException(Exception::INVALID_PACKER);
}
return $packer;
}
}

View File

@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Direction\NoHttpRequestDirection;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidConfigException;
use Yansongda\Pay\Exception\InvalidResponseException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
use Yansongda\Supports\Collection;
use Yansongda\Supports\Str;
use function Yansongda\Pay\verify_unipay_sign;
class CallbackPlugin implements PluginInterface
{
/**
* @throws ContainerException
* @throws InvalidConfigException
* @throws ServiceNotFoundException
* @throws InvalidResponseException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[unipay][CallbackPlugin] 插件开始装载', ['rocket' => $rocket]);
$this->formatPayload($rocket);
$params = $rocket->getParams();
$signature = $params['signature'] ?? false;
if (!$signature) {
throw new InvalidResponseException(Exception::INVALID_RESPONSE_SIGN, '', $params);
}
verify_unipay_sign($params, $rocket->getPayload()->sortKeys()->toString(), $signature);
$rocket->setDirection(NoHttpRequestDirection::class)
->setDestination($rocket->getPayload())
;
Logger::info('[unipay][CallbackPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
protected function formatPayload(Rocket $rocket): void
{
$payload = (new Collection($rocket->getParams()))
->filter(fn ($v, $k) => 'signature' != $k && !Str::startsWith($k, '_'))
;
$rocket->setPayload($payload);
}
}

View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay;
use Closure;
use Psr\Http\Message\RequestInterface;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Pay;
use Yansongda\Pay\Provider\Unipay;
use Yansongda\Pay\Request;
use Yansongda\Pay\Rocket;
use function Yansongda\Pay\get_unipay_config;
abstract class GeneralPlugin implements PluginInterface
{
/**
* @throws ServiceNotFoundException
* @throws ContainerException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[unipay][GeneralPlugin] 通用插件开始装载', ['rocket' => $rocket]);
$rocket->setRadar($this->getRequest($rocket));
$this->doSomething($rocket);
Logger::info('[unipay][GeneralPlugin] 通用插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
/**
* @throws ContainerException
* @throws ServiceNotFoundException
*/
protected function getRequest(Rocket $rocket): RequestInterface
{
return new Request(
$this->getMethod(),
$this->getUrl($rocket),
$this->getHeaders(),
);
}
protected function getMethod(): string
{
return 'POST';
}
/**
* @throws ContainerException
* @throws ServiceNotFoundException
*/
protected function getUrl(Rocket $rocket): string
{
$url = $this->getUri($rocket);
if (0 === strpos($url, 'http')) {
return $url;
}
$config = get_unipay_config($rocket->getParams());
return Unipay::URL[$config['mode'] ?? Pay::MODE_NORMAL].$url;
}
protected function getHeaders(): array
{
return [
'User-Agent' => 'yansongda/pay-v3',
'Content-Type' => 'application/x-www-form-urlencoded;charset=utf-8',
];
}
abstract protected function doSomething(Rocket $rocket): void;
abstract protected function getUri(Rocket $rocket): string;
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay;
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::debug('[unipay][HtmlResponsePlugin] 插件开始装载', ['rocket' => $rocket]);
$radar = $rocket->getRadar();
$response = $this->buildHtml($radar->getUri()->__toString(), $rocket->getPayload());
$rocket->setDestination($response);
Logger::info('[unipay][HtmlResponsePlugin] 插件装载完毕', ['rocket' => $rocket]);
return $rocket;
}
protected function buildHtml(string $endpoint, Collection $payload): Response
{
$sHtml = "<form id='pay_form' name='pay_form' action='".$endpoint."' method='POST'>";
foreach ($payload->all() as $key => $val) {
$sHtml .= "<input type='hidden' name='".$key."' value='".$val."'/>";
}
$sHtml .= "<input type='submit' value='ok' style='display:none;'></form>";
$sHtml .= "<script>document.forms['pay_form'].submit();</script>";
return new Response(200, [], $sHtml);
}
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\InvalidConfigException;
use Yansongda\Pay\Exception\InvalidResponseException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
use Yansongda\Supports\Collection;
use function Yansongda\Pay\should_do_http_request;
use function Yansongda\Pay\verify_unipay_sign;
class LaunchPlugin implements PluginInterface
{
/**
* @throws ContainerException
* @throws InvalidConfigException
* @throws InvalidResponseException
* @throws ServiceNotFoundException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
/* @var Rocket $rocket */
$rocket = $next($rocket);
Logger::debug('[unipay][LaunchPlugin] 插件开始装载', ['rocket' => $rocket]);
if (should_do_http_request($rocket->getDirection())) {
$response = Collection::wrap($rocket->getDestination());
$signature = $response->get('signature');
$response->forget('signature');
verify_unipay_sign(
$rocket->getParams(),
$response->sortKeys()->toString(),
$signature
);
$rocket->setDestination($response);
}
Logger::info('[unipay][LaunchPlugin] 插件装载完毕', ['rocket' => $rocket]);
return $rocket;
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay\OnlineGateway;
use Yansongda\Pay\Plugin\Unipay\GeneralPlugin;
use Yansongda\Pay\Rocket;
/**
* @see https://open.unionpay.com/tjweb/acproduct/APIList?acpAPIId=755&apiservId=448&version=V2.2&bussType=0
*/
class CancelPlugin extends GeneralPlugin
{
protected function getUri(Rocket $rocket): string
{
return 'gateway/api/backTransReq.do';
}
protected function doSomething(Rocket $rocket): void
{
$rocket->mergePayload([
'bizType' => '000000',
'txnType' => '31',
'txnSubType' => '00',
'channelType' => '07',
]);
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay\OnlineGateway;
use Yansongda\Pay\Direction\ResponseDirection;
use Yansongda\Pay\Plugin\Unipay\GeneralPlugin;
use Yansongda\Pay\Rocket;
/**
* @see https://open.unionpay.com/tjweb/acproduct/APIList?acpAPIId=754&apiservId=448&version=V2.2&bussType=0
*/
class PagePayPlugin extends GeneralPlugin
{
protected function getUri(Rocket $rocket): string
{
return 'gateway/api/frontTransReq.do';
}
protected function doSomething(Rocket $rocket): void
{
$rocket->setDirection(ResponseDirection::class)
->mergePayload([
'bizType' => '000201',
'txnType' => '01',
'txnSubType' => '01',
'channelType' => '07',
])
;
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay\OnlineGateway;
use Yansongda\Pay\Plugin\Unipay\GeneralPlugin;
use Yansongda\Pay\Rocket;
/**
* @see https://open.unionpay.com/tjweb/acproduct/APIList?acpAPIId=757&apiservId=448&version=V2.2&bussType=0
*/
class QueryPlugin extends GeneralPlugin
{
protected function getUri(Rocket $rocket): string
{
return 'gateway/api/queryTrans.do';
}
protected function doSomething(Rocket $rocket): void
{
$rocket->mergePayload([
'bizType' => '000000',
'txnType' => '00',
'txnSubType' => '00',
]);
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay\OnlineGateway;
use Yansongda\Pay\Plugin\Unipay\GeneralPlugin;
use Yansongda\Pay\Rocket;
/**
* @see https://open.unionpay.com/tjweb/acproduct/APIList?acpAPIId=756&apiservId=448&version=V2.2&bussType=0
*/
class RefundPlugin extends GeneralPlugin
{
protected function getUri(Rocket $rocket): string
{
return 'gateway/api/backTransReq.do';
}
protected function doSomething(Rocket $rocket): void
{
$rocket->mergePayload([
'bizType' => '000000',
'txnType' => '04',
'txnSubType' => '00',
'channelType' => '07',
]);
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay\OnlineGateway;
use Yansongda\Pay\Direction\ResponseDirection;
use Yansongda\Pay\Plugin\Unipay\GeneralPlugin;
use Yansongda\Pay\Rocket;
/**
* @see https://open.unionpay.com/tjweb/acproduct/APIList?acpAPIId=754&apiservId=448&version=V2.2&bussType=0
*/
class WapPayPlugin extends GeneralPlugin
{
protected function getUri(Rocket $rocket): string
{
return 'gateway/api/frontTransReq.do';
}
protected function doSomething(Rocket $rocket): void
{
$rocket->setDirection(ResponseDirection::class)
->mergePayload([
'bizType' => '000201',
'txnType' => '01',
'txnSubType' => '01',
'channelType' => '08',
])
;
}
}

View File

@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay;
use Closure;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\InvalidConfigException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
use Yansongda\Pay\Traits\GetUnipayCerts;
use Yansongda\Supports\Str;
use function Yansongda\Pay\get_tenant;
use function Yansongda\Pay\get_unipay_config;
class PreparePlugin implements PluginInterface
{
use GetUnipayCerts;
/**
* @throws ContainerException
* @throws ServiceNotFoundException
* @throws InvalidConfigException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[unipay][PreparePlugin] 插件开始装载', ['rocket' => $rocket]);
$rocket->mergePayload($this->getPayload($rocket->getParams()));
Logger::info('[unipay][PreparePlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
/**
* @throws ContainerException
* @throws ServiceNotFoundException
* @throws InvalidConfigException
*/
protected function getPayload(array $params): array
{
$tenant = get_tenant($params);
$config = get_unipay_config($params);
$init = [
'version' => '5.1.0',
'encoding' => 'utf-8',
'backUrl' => $config['notify_url'] ?? '',
'currencyCode' => '156',
'accessType' => '0',
'signature' => '',
'signMethod' => '01',
'merId' => $config['mch_id'] ?? '',
'frontUrl' => $config['return_url'] ?? '',
'certId' => $this->getCertId($tenant, $config),
];
return array_merge(
$init,
array_filter($params, fn ($v, $k) => !Str::startsWith(strval($k), '_'), ARRAY_FILTER_USE_BOTH),
);
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay\QrCode;
use Yansongda\Pay\Plugin\Unipay\GeneralPlugin;
use Yansongda\Pay\Rocket;
/**
* @see https://open.unionpay.com/tjweb/acproduct/APIList?acpAPIId=800&apiservId=468&version=V2.2&bussType=0
*/
class CancelPlugin extends GeneralPlugin
{
protected function getUri(Rocket $rocket): string
{
return 'gateway/api/backTransReq.do';
}
protected function doSomething(Rocket $rocket): void
{
$rocket->mergePayload([
'bizType' => '000000',
'txnType' => '31',
'txnSubType' => '00',
'channelType' => '08',
]);
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay\QrCode;
use Yansongda\Pay\Plugin\Unipay\GeneralPlugin;
use Yansongda\Pay\Rocket;
/**
* @see https://open.unionpay.com/tjweb/acproduct/APIList?acpAPIId=798&apiservId=468&version=V2.2&bussType=0
*/
class PosNormalPlugin extends GeneralPlugin
{
protected function getUri(Rocket $rocket): string
{
return 'gateway/api/backTransReq.do';
}
protected function doSomething(Rocket $rocket): void
{
$rocket->mergePayload([
'bizType' => '000000',
'txnType' => '01',
'txnSubType' => '06',
'channelType' => '08',
]);
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay\QrCode;
use Yansongda\Pay\Plugin\Unipay\GeneralPlugin;
use Yansongda\Pay\Rocket;
/**
* @see https://open.unionpay.com/tjweb/acproduct/APIList?acpAPIId=797&apiservId=468&version=V2.2&bussType=0
*/
class PosPreAuthPlugin extends GeneralPlugin
{
protected function getUri(Rocket $rocket): string
{
return 'gateway/api/backTransReq.do';
}
protected function doSomething(Rocket $rocket): void
{
$rocket->mergePayload([
'bizType' => '000201',
'txnType' => '02',
'txnSubType' => '04',
'channelType' => '08',
]);
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay\QrCode;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Pay;
use Yansongda\Pay\Rocket;
use function Yansongda\Pay\get_unipay_config;
/**
* @see https://open.unionpay.com/tjweb/acproduct/APIList?acpAPIId=792&apiservId=468&version=V2.2&bussType=0
*/
class QueryPlugin extends \Yansongda\Pay\Plugin\Unipay\OnlineGateway\QueryPlugin
{
/**
* @throws ContainerException
* @throws ServiceNotFoundException
*/
protected function getUri(Rocket $rocket): string
{
$config = get_unipay_config($rocket->getParams());
if (Pay::MODE_SANDBOX === ($config['mode'] ?? Pay::MODE_NORMAL)) {
return 'https://101.231.204.80:5000/gateway/api/backTransReq.do';
}
return parent::getUri($rocket);
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay\QrCode;
use Yansongda\Pay\Plugin\Unipay\GeneralPlugin;
use Yansongda\Pay\Rocket;
/**
* @see https://open.unionpay.com/tjweb/acproduct/APIList?acpAPIId=799&apiservId=468&version=V2.2&bussType=0
*/
class RefundPlugin extends GeneralPlugin
{
protected function getUri(Rocket $rocket): string
{
return 'gateway/api/backTransReq.do';
}
protected function doSomething(Rocket $rocket): void
{
$rocket->mergePayload([
'bizType' => '000000',
'txnType' => '04',
'txnSubType' => '00',
'channelType' => '08',
]);
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay\QrCode;
use Yansongda\Pay\Plugin\Unipay\GeneralPlugin;
use Yansongda\Pay\Rocket;
/**
* @see https://open.unionpay.com/tjweb/acproduct/APIList?acpAPIId=796&apiservId=468&version=V2.2&bussType=0
*/
class ScanFeePlugin extends GeneralPlugin
{
protected function getUri(Rocket $rocket): string
{
return 'gateway/api/backTransReq.do';
}
protected function doSomething(Rocket $rocket): void
{
$rocket->mergePayload([
'bizType' => '000601',
'txnType' => '13',
'txnSubType' => '08',
'channelType' => '08',
]);
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay\QrCode;
use Yansongda\Pay\Plugin\Unipay\GeneralPlugin;
use Yansongda\Pay\Rocket;
/**
* @see https://open.unionpay.com/tjweb/acproduct/APIList?acpAPIId=793&apiservId=468&version=V2.2&bussType=0
*/
class ScanNormalPlugin extends GeneralPlugin
{
protected function getUri(Rocket $rocket): string
{
return 'gateway/api/backTransReq.do';
}
protected function doSomething(Rocket $rocket): void
{
$rocket->mergePayload([
'bizType' => '000000',
'txnType' => '01',
'txnSubType' => '07',
'channelType' => '08',
]);
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay\QrCode;
use Yansongda\Pay\Plugin\Unipay\GeneralPlugin;
use Yansongda\Pay\Rocket;
/**
* @see https://open.unionpay.com/tjweb/acproduct/APIList?acpAPIId=794&apiservId=468&version=V2.2&bussType=0
*/
class ScanPreAuthPlugin extends GeneralPlugin
{
protected function getUri(Rocket $rocket): string
{
return 'gateway/api/backTransReq.do';
}
protected function doSomething(Rocket $rocket): void
{
$rocket->mergePayload([
'bizType' => '000000',
'txnType' => '02',
'txnSubType' => '05',
'channelType' => '08',
]);
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay\QrCode;
use Yansongda\Pay\Plugin\Unipay\GeneralPlugin;
use Yansongda\Pay\Rocket;
/**
* @see https://open.unionpay.com/tjweb/acproduct/APIList?acpAPIId=795&apiservId=468&version=V2.2&bussType=0
*/
class ScanPreOrderPlugin extends GeneralPlugin
{
protected function getUri(Rocket $rocket): string
{
return 'gateway/api/order.do';
}
protected function doSomething(Rocket $rocket): void
{
$rocket->mergePayload([
'bizType' => '000000',
'txnType' => '01',
'txnSubType' => '01',
'channelType' => '08',
]);
}
}

View File

@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay;
use Closure;
use GuzzleHttp\Psr7\Utils;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\InvalidConfigException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
use Yansongda\Pay\Traits\GetUnipayCerts;
use Yansongda\Supports\Collection;
use function Yansongda\Pay\get_unipay_config;
class RadarSignPlugin implements PluginInterface
{
use GetUnipayCerts;
/**
* @throws ContainerException
* @throws ServiceNotFoundException
* @throws InvalidConfigException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[unipay][PreparePlugin] 插件开始装载', ['rocket' => $rocket]);
$this->sign($rocket);
$this->reRadar($rocket);
Logger::info('[unipay][PreparePlugin] 插件装载完毕', ['rocket' => $rocket]);
return $next($rocket);
}
/**
* @throws ContainerException
* @throws InvalidConfigException
* @throws ServiceNotFoundException
*/
protected function sign(Rocket $rocket): void
{
$payload = $rocket->getPayload()->filter(fn ($v, $k) => 'signature' != $k);
$config = $this->getConfig($rocket->getParams());
$rocket->mergePayload([
'signature' => $this->getSignature($config['certs']['pkey'] ?? '', $payload),
]);
}
protected function reRadar(Rocket $rocket): void
{
$body = $this->getBody($rocket->getPayload());
$radar = $rocket->getRadar();
if (!empty($body) && !empty($radar)) {
$radar = $radar->withBody(Utils::streamFor($body));
$rocket->setRadar($radar);
}
}
/**
* @throws ContainerException
* @throws InvalidConfigException
* @throws ServiceNotFoundException
*/
protected function getConfig(array $params): array
{
$config = get_unipay_config($params);
if (empty($config['certs']['pkey'])) {
$this->getCertId($params['_config'] ?? 'default', $config);
$config = get_unipay_config($params);
}
return $config;
}
protected function getSignature(string $pkey, Collection $payload): string
{
$content = $payload->sortKeys()->toString();
openssl_sign(hash('sha256', $content), $sign, $pkey, 'sha256');
return base64_encode($sign);
}
protected function getBody(Collection $payload): string
{
return $payload->query();
}
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay\Shortcut;
use Yansongda\Pay\Contract\ShortcutInterface;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Plugin\Unipay\OnlineGateway\CancelPlugin;
use Yansongda\Supports\Str;
class CancelShortcut implements ShortcutInterface
{
/**
* @throws InvalidParamsException
*/
public function getPlugins(array $params): array
{
$typeMethod = Str::camel($params['_action'] ?? 'default').'Plugins';
if (method_exists($this, $typeMethod)) {
return $this->{$typeMethod}();
}
throw new InvalidParamsException(Exception::SHORTCUT_MULTI_ACTION_ERROR, "Cancel action [{$typeMethod}] not supported");
}
protected function defaultPlugins(): array
{
return [
CancelPlugin::class,
];
}
protected function qrCodePlugins(): array
{
return [
\Yansongda\Pay\Plugin\Unipay\QrCode\CancelPlugin::class,
];
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay\Shortcut;
use Yansongda\Pay\Contract\ShortcutInterface;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Plugin\Unipay\QrCode\PosNormalPlugin;
use Yansongda\Pay\Plugin\Unipay\QrCode\PosPreAuthPlugin;
use Yansongda\Supports\Str;
class PosShortcut implements ShortcutInterface
{
/**
* @throws InvalidParamsException
*/
public function getPlugins(array $params): array
{
$typeMethod = Str::camel($params['_action'] ?? 'default').'Plugins';
if (method_exists($this, $typeMethod)) {
return $this->{$typeMethod}();
}
throw new InvalidParamsException(Exception::SHORTCUT_MULTI_ACTION_ERROR, "Pos action [{$typeMethod}] not supported");
}
protected function defaultPlugins(): array
{
return [
PosNormalPlugin::class,
];
}
protected function preAuthPlugins(): array
{
return [
PosPreAuthPlugin::class,
];
}
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay\Shortcut;
use Yansongda\Pay\Contract\ShortcutInterface;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Plugin\Unipay\OnlineGateway\QueryPlugin;
use Yansongda\Supports\Str;
class QueryShortcut implements ShortcutInterface
{
/**
* @throws InvalidParamsException
*/
public function getPlugins(array $params): array
{
$typeMethod = Str::camel($params['_action'] ?? 'default').'Plugins';
if (method_exists($this, $typeMethod)) {
return $this->{$typeMethod}();
}
throw new InvalidParamsException(Exception::SHORTCUT_MULTI_ACTION_ERROR, "Query action [{$typeMethod}] not supported");
}
protected function defaultPlugins(): array
{
return [
QueryPlugin::class,
];
}
protected function qrCodePlugins(): array
{
return [
\Yansongda\Pay\Plugin\Unipay\QrCode\QueryPlugin::class,
];
}
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay\Shortcut;
use Yansongda\Pay\Contract\ShortcutInterface;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Plugin\Unipay\OnlineGateway\RefundPlugin;
use Yansongda\Supports\Str;
class RefundShortcut implements ShortcutInterface
{
/**
* @throws InvalidParamsException
*/
public function getPlugins(array $params): array
{
$typeMethod = Str::camel($params['_action'] ?? 'default').'Plugins';
if (method_exists($this, $typeMethod)) {
return $this->{$typeMethod}();
}
throw new InvalidParamsException(Exception::SHORTCUT_MULTI_ACTION_ERROR, "Refund action [{$typeMethod}] not supported");
}
protected function defaultPlugins(): array
{
return [
RefundPlugin::class,
];
}
protected function qrCodePlugins(): array
{
return [
\Yansongda\Pay\Plugin\Unipay\QrCode\RefundPlugin::class,
];
}
}

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Unipay\Shortcut;
use Yansongda\Pay\Contract\ShortcutInterface;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Plugin\Unipay\QrCode\ScanFeePlugin;
use Yansongda\Pay\Plugin\Unipay\QrCode\ScanNormalPlugin;
use Yansongda\Pay\Plugin\Unipay\QrCode\ScanPreAuthPlugin;
use Yansongda\Pay\Plugin\Unipay\QrCode\ScanPreOrderPlugin;
use Yansongda\Supports\Str;
class ScanShortcut implements ShortcutInterface
{
/**
* @throws InvalidParamsException
*/
public function getPlugins(array $params): array
{
$typeMethod = Str::camel($params['_action'] ?? 'default').'Plugins';
if (method_exists($this, $typeMethod)) {
return $this->{$typeMethod}();
}
throw new InvalidParamsException(Exception::SHORTCUT_MULTI_ACTION_ERROR, "Scan action [{$typeMethod}] not supported");
}
protected function defaultPlugins(): array
{
return [
ScanNormalPlugin::class,
];
}
protected function preAuthPlugins(): array
{
return [
ScanPreAuthPlugin::class,
];
}
protected function preOrderPlugins(): array
{
return [
ScanPreOrderPlugin::class,
];
}
protected function feePlugins(): array
{
return [
ScanFeePlugin::class,
];
}
}

View File

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

View File

@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat;
use Closure;
use Psr\Http\Message\ServerRequestInterface;
use Yansongda\Pay\Contract\PluginInterface;
use Yansongda\Pay\Direction\NoHttpRequestDirection;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidConfigException;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Exception\InvalidResponseException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Logger;
use Yansongda\Pay\Rocket;
use Yansongda\Supports\Collection;
use function Yansongda\Pay\decrypt_wechat_resource;
use function Yansongda\Pay\verify_wechat_sign;
class CallbackPlugin implements PluginInterface
{
/**
* @throws ContainerException
* @throws InvalidConfigException
* @throws ServiceNotFoundException
* @throws InvalidResponseException
* @throws InvalidParamsException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[wechat][CallbackPlugin] 插件开始装载', ['rocket' => $rocket]);
$this->formatRequestAndParams($rocket);
/* @phpstan-ignore-next-line */
verify_wechat_sign($rocket->getDestinationOrigin(), $rocket->getParams());
$body = json_decode((string) $rocket->getDestination()->getBody(), true);
$rocket->setDirection(NoHttpRequestDirection::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 InvalidParamsException
*/
protected function formatRequestAndParams(Rocket $rocket): void
{
$request = $rocket->getParams()['request'] ?? null;
if (!$request instanceof ServerRequestInterface) {
throw new InvalidParamsException(Exception::REQUEST_NULL_ERROR);
}
$rocket->setDestination(clone $request)
->setDestinationOrigin($request)
->setParams($rocket->getParams()['params'] ?? [])
;
}
}

View File

@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat\Ecommerce\Refund;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Plugin\Wechat\GeneralPlugin;
use Yansongda\Pay\Rocket;
use function Yansongda\Pay\get_wechat_config;
/**
* @see https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_6_1.shtml
*/
class ApplyPlugin extends GeneralPlugin
{
/**
* @throws 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 ContainerException
* @throws ServiceNotFoundException
*/
protected function doSomething(Rocket $rocket): void
{
$config = get_wechat_config($rocket->getParams());
$payload = $rocket->getPayload();
$key = $this->getConfigKey($rocket->getParams());
$wechatId = [
'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''),
'sp_appid' => $payload->get('sp_appid', $config[$key] ?? ''),
];
if (!$payload->has('notify_url')) {
$wechatId['notify_url'] = $config['notify_url'] ?? null;
}
$rocket->mergePayload($wechatId);
}
}

View File

@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat\Ecommerce\Refund;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Plugin\Wechat\GeneralPlugin;
use Yansongda\Pay\Rocket;
use function Yansongda\Pay\get_wechat_config;
/**
* @see https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_6_2.shtml
*/
class QueryPlugin extends GeneralPlugin
{
/**
* @throws InvalidParamsException
*/
protected function getUri(Rocket $rocket): string
{
throw new InvalidParamsException(Exception::NOT_IN_SERVICE_MODE);
}
/**
* @throws ContainerException
* @throws InvalidParamsException
* @throws ServiceNotFoundException
*/
protected function getPartnerUri(Rocket $rocket): string
{
$payload = $rocket->getPayload();
$config = get_wechat_config($rocket->getParams());
$subMchId = $payload->get('sub_mchid', $config['sub_mch_id'] ?? '');
if ($payload->has('refund_id')) {
return 'v3/ecommerce/refunds/id/'.$payload->get('refund_id').'?sub_mchid='.$subMchId;
}
if ($payload->has('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,56 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat\Ecommerce\Refund;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Plugin\Wechat\GeneralPlugin;
use Yansongda\Pay\Rocket;
use function Yansongda\Pay\get_wechat_config;
/**
* @see https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_6_5.shtml
*/
class QueryReturnAdvancePlugin extends GeneralPlugin
{
/**
* @throws InvalidParamsException
*/
protected function getUri(Rocket $rocket): string
{
throw new InvalidParamsException(Exception::NOT_IN_SERVICE_MODE);
}
/**
* @throws ContainerException
* @throws InvalidParamsException
* @throws ServiceNotFoundException
*/
protected function getPartnerUri(Rocket $rocket): string
{
$payload = $rocket->getPayload();
$config = get_wechat_config($rocket->getParams());
$subMchId = $payload->get('sub_mchid', $config['sub_mch_id'] ?? '');
if (!$payload->has('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,56 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat\Ecommerce\Refund;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Plugin\Wechat\GeneralPlugin;
use Yansongda\Pay\Rocket;
use Yansongda\Supports\Collection;
use function Yansongda\Pay\get_wechat_config;
/**
* @see https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_6_4.shtml
*/
class ReturnAdvancePlugin extends GeneralPlugin
{
/**
* @throws InvalidParamsException
*/
protected function getUri(Rocket $rocket): string
{
throw new InvalidParamsException(Exception::NOT_IN_SERVICE_MODE);
}
/**
* @throws InvalidParamsException
*/
protected function getPartnerUri(Rocket $rocket): string
{
$payload = $rocket->getPayload();
if (!$payload->has('refund_id')) {
throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
}
return 'v3/ecommerce/refunds/'.$payload->get('refund_id').'/return-advance';
}
/**
* @throws ContainerException
* @throws ServiceNotFoundException
*/
protected function doSomething(Rocket $rocket): void
{
$config = get_wechat_config($rocket->getParams());
$rocket->setPayload(new Collection([
'sub_mchid' => $rocket->getPayload()->get('sub_mchid', $config['sub_mch_id'] ?? ''),
]));
}
}

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 InvalidParamsException
*/
protected function getUri(Rocket $rocket): string
{
$payload = $rocket->getPayload();
if (!$payload->has('account_type') || !$payload->has('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 InvalidParamsException
*/
protected function getUri(Rocket $rocket): string
{
$payload = $rocket->getPayload();
if (!$payload->has('account_type')) {
throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
}
return 'v3/merchant/fund/balance/'.
$payload->get('account_type');
}
}

View File

@@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat\Fund\Profitsharing;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\InvalidConfigException;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Exception\InvalidResponseException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Pay;
use Yansongda\Pay\Plugin\Wechat\GeneralPlugin;
use Yansongda\Pay\Rocket;
use Yansongda\Pay\Traits\HasWechatEncryption;
use Yansongda\Supports\Collection;
use function Yansongda\Pay\encrypt_wechat_contents;
use function Yansongda\Pay\get_wechat_config;
/**
* @see https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_8.shtml
*/
class AddReceiverPlugin extends GeneralPlugin
{
use HasWechatEncryption;
/**
* @throws ContainerException
* @throws InvalidConfigException
* @throws InvalidParamsException
* @throws InvalidResponseException
* @throws 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(array $config, Collection $payload): array
{
$wechatId = [
'appid' => $config['mp_app_id'] ?? null,
];
if (Pay::MODE_SERVICE === ($config['mode'] ?? null)) {
$wechatId['sub_mchid'] = $payload->get('sub_mchid', $config['sub_mch_id'] ?? '');
}
return $wechatId;
}
/**
* @throws ContainerException
* @throws InvalidParamsException
* @throws ServiceNotFoundException
*/
protected function getEncryptUserName(array $params): string
{
$name = $params['name'] ?? '';
$publicKey = $this->getPublicKey($params, $params['_serial_no'] ?? '');
return encrypt_wechat_contents($name, $publicKey);
}
}

View File

@@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat\Fund\Profitsharing;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\InvalidConfigException;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Exception\InvalidResponseException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Pay;
use Yansongda\Pay\Plugin\Wechat\GeneralPlugin;
use Yansongda\Pay\Rocket;
use Yansongda\Pay\Traits\HasWechatEncryption;
use Yansongda\Supports\Collection;
use function Yansongda\Pay\encrypt_wechat_contents;
use function Yansongda\Pay\get_wechat_config;
/**
* @see https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_1.shtml
*/
class CreatePlugin extends GeneralPlugin
{
use HasWechatEncryption;
/**
* @throws ContainerException
* @throws InvalidConfigException
* @throws InvalidParamsException
* @throws InvalidResponseException
* @throws 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 ContainerException
* @throws ServiceNotFoundException
*/
protected function getWechatExtra(array $params, Collection $payload): array
{
$config = get_wechat_config($params);
$extra = [
'appid' => $config['mp_app_id'] ?? null,
];
if (Pay::MODE_SERVICE === ($config['mode'] ?? null)) {
$extra['sub_mchid'] = $payload->get('sub_mchid', $config['sub_mch_id'] ?? '');
}
return $extra;
}
/**
* @throws ContainerException
* @throws InvalidParamsException
* @throws 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,45 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat\Fund\Profitsharing;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Pay;
use Yansongda\Pay\Plugin\Wechat\GeneralPlugin;
use Yansongda\Pay\Rocket;
use function Yansongda\Pay\get_wechat_config;
/**
* @see https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_9.shtml
*/
class DeleteReceiverPlugin extends GeneralPlugin
{
/**
* @throws ContainerException
* @throws ServiceNotFoundException
*/
protected function doSomething(Rocket $rocket): void
{
$config = get_wechat_config($rocket->getParams());
$wechatId = [
'appid' => $config['mp_app_id'] ?? null,
];
if (Pay::MODE_SERVICE === ($config['mode'] ?? null)) {
$wechatId['sub_mchid'] = $rocket->getPayload()
->get('sub_mchid', $config['sub_mch_id'] ?? '')
;
}
$rocket->mergePayload($wechatId);
}
protected function getUri(Rocket $rocket): string
{
return 'v3/profitsharing/receivers/delete';
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat\Fund\Profitsharing;
use Yansongda\Pay\Direction\OriginResponseDirection;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Plugin\Wechat\GeneralPlugin;
use Yansongda\Pay\Rocket;
/**
* @see https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_12.shtml
*/
class DownloadBillPlugin extends GeneralPlugin
{
/**
* @throws InvalidParamsException
*/
protected function getUri(Rocket $rocket): string
{
$payload = $rocket->getPayload();
if (!$payload->has('download_url')) {
throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
}
return $payload->get('download_url');
}
protected function getMethod(): string
{
return 'GET';
}
protected function doSomething(Rocket $rocket): void
{
$rocket->setDirection(OriginResponseDirection::class);
$rocket->setPayload(null);
}
}

View File

@@ -0,0 +1,42 @@
<?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;
/**
* @see https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_6.shtml
*/
class QueryAmountsPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'GET';
}
protected function doSomething(Rocket $rocket): void
{
$rocket->setPayload(null);
}
/**
* @throws InvalidParamsException
*/
protected function getUri(Rocket $rocket): string
{
$payload = $rocket->getPayload();
if (!$payload->has('transaction_id')) {
throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
}
return 'v3/profitsharing/transactions/'.
$payload->get('transaction_id').
'/amounts';
}
}

View File

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

View File

@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat\Fund\Profitsharing;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Pay;
use Yansongda\Pay\Plugin\Wechat\GeneralPlugin;
use Yansongda\Pay\Rocket;
use function Yansongda\Pay\get_wechat_config;
/**
* @see https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_2.shtml
*/
class QueryPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'GET';
}
protected function doSomething(Rocket $rocket): void
{
$rocket->setPayload(null);
}
/**
* @throws ContainerException
* @throws InvalidParamsException
* @throws ServiceNotFoundException
*/
protected function getUri(Rocket $rocket): string
{
$payload = $rocket->getPayload();
$config = get_wechat_config($rocket->getParams());
if (!$payload->has('out_order_no') || !$payload->has('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['mode'] ?? null)) {
$url .= '&sub_mchid='.$payload->get('sub_mchid', $config['sub_mch_id'] ?? '');
}
return $url;
}
}

View File

@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat\Fund\Profitsharing;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Pay;
use Yansongda\Pay\Plugin\Wechat\GeneralPlugin;
use Yansongda\Pay\Rocket;
use function Yansongda\Pay\get_wechat_config;
/**
* @see https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_4.shtml
*/
class QueryReturnPlugin extends GeneralPlugin
{
protected function getMethod(): string
{
return 'GET';
}
protected function doSomething(Rocket $rocket): void
{
$rocket->setPayload(null);
}
/**
* @throws ContainerException
* @throws InvalidParamsException
* @throws ServiceNotFoundException
*/
protected function getUri(Rocket $rocket): string
{
$payload = $rocket->getPayload();
$config = get_wechat_config($rocket->getParams());
if (!$payload->has('out_return_no') || !$payload->has('out_order_no')) {
throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
}
$url = 'v3/profitsharing/return-orders/'.
$payload->get('out_return_no').
'?out_order_no='.$payload->get('out_order_no');
if (Pay::MODE_SERVICE === ($config['mode'] ?? null)) {
$url .= '&sub_mchid='.$payload->get('sub_mchid', $config['sub_mch_id'] ?? '');
}
return $url;
}
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat\Fund\Profitsharing;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Pay;
use Yansongda\Pay\Plugin\Wechat\GeneralPlugin;
use Yansongda\Pay\Rocket;
use function Yansongda\Pay\get_wechat_config;
/**
* @see https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_3.shtml
*/
class ReturnPlugin extends GeneralPlugin
{
/**
* @throws ContainerException
* @throws ServiceNotFoundException
*/
protected function doSomething(Rocket $rocket): void
{
$config = get_wechat_config($rocket->getParams());
if (Pay::MODE_SERVICE === ($config['mode'] ?? null)) {
$rocket->mergePayload([
'sub_mchid' => $rocket->getPayload()
->get('sub_mchid', $config['sub_mch_id'] ?? ''),
]);
}
}
protected function getUri(Rocket $rocket): string
{
return 'v3/profitsharing/return-orders';
}
}

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat\Fund\Profitsharing;
use Yansongda\Pay\Exception\ContainerException;
use Yansongda\Pay\Exception\ServiceNotFoundException;
use Yansongda\Pay\Pay;
use Yansongda\Pay\Plugin\Wechat\GeneralPlugin;
use Yansongda\Pay\Rocket;
use function Yansongda\Pay\get_wechat_config;
/**
* @see https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_5.shtml
*/
class UnfreezePlugin extends GeneralPlugin
{
/**
* @throws ContainerException
* @throws ServiceNotFoundException
*/
protected function doSomething(Rocket $rocket): void
{
$payload = $rocket->getPayload();
$config = get_wechat_config($rocket->getParams());
if (Pay::MODE_SERVICE === ($config['mode'] ?? null) && !$payload->has('sub_mchid')) {
$rocket->mergePayload([
'sub_mchid' => $config['sub_mch_id'] ?? '',
]);
}
}
protected function getUri(Rocket $rocket): string
{
return 'v3/profitsharing/orders/unfreeze';
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Yansongda\Pay\Plugin\Wechat\Fund\Transfer;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Exception\InvalidParamsException;
use Yansongda\Pay\Plugin\Wechat\GeneralPlugin;
use Yansongda\Pay\Rocket;
/**
* @see https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_3_7.shtml
*/
class CreateBillReceiptPlugin extends GeneralPlugin
{
/**
* @throws InvalidParamsException
*/
protected function doSomething(Rocket $rocket): void
{
$payload = $rocket->getPayload();
if (!$payload->has('out_batch_no')) {
throw new InvalidParamsException(Exception::MISSING_NECESSARY_PARAMS);
}
}
protected function getUri(Rocket $rocket): string
{
return 'v3/transfer/bill-receipt';
}
}

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