Skip to content

Commit

Permalink
feat: 支持最新版微信商户转账 (#1058)
Browse files Browse the repository at this point in the history
  • Loading branch information
yansongda authored Jan 31, 2025
1 parent ab7eb6c commit 522f6cb
Show file tree
Hide file tree
Showing 35 changed files with 1,605 additions and 442 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
php-version: '8.4'
extensions: openssl
coverage: xdebug
- name: Install Dependencies
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/style.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
- 8.1
- 8.2
- 8.3
- 8.4
steps:
- name: Checkout Code
uses: actions/checkout@v4
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/tester.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
- 8.1
- 8.2
- 8.3
- 8.4
steps:
- name: Checkout Code
uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion src/Functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ function get_wechat_public_key(array $config, string $serialNo): string
$publicKey = $config['wechat_public_cert_path'][$serialNo] ?? null;

if (empty($publicKey)) {
throw new InvalidParamsException(Exception::PARAMS_WECHAT_SERIAL_NOT_FOUND, '参数异常: 微信公钥序列号为找到 -'.$serialNo);
throw new InvalidParamsException(Exception::PARAMS_WECHAT_SERIAL_NOT_FOUND, '参数异常: 微信公钥序列号未找到 - '.$serialNo);
}

return $publicKey;
Expand Down
54 changes: 54 additions & 0 deletions src/Plugin/Wechat/V3/Marketing/MchTransfer/CancelPlugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace Yansongda\Pay\Plugin\Wechat\V3\Marketing\MchTransfer;

use Closure;
use Yansongda\Artful\Contract\PluginInterface;
use Yansongda\Artful\Exception\ContainerException;
use Yansongda\Artful\Exception\InvalidParamsException;
use Yansongda\Artful\Exception\ServiceNotFoundException;
use Yansongda\Artful\Logger;
use Yansongda\Artful\Rocket;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Pay;

use function Yansongda\Pay\get_provider_config;

/**
* @see https://pay.weixin.qq.com/doc/v3/merchant/4012716458
*/
class CancelPlugin implements PluginInterface
{
/**
* @throws InvalidParamsException
* @throws ContainerException
* @throws ServiceNotFoundException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[Wechat][Marketing][MchTransfer][CancelPlugin] 插件开始装载', ['rocket' => $rocket]);

$config = get_provider_config('wechat', $rocket->getParams());
$payload = $rocket->getPayload();
$outBillNo = $payload?->get('out_bill_no') ?? null;

if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 撤销转账,只支持普通商户模式,当前配置为服务商模式');
}

if (empty($outBillNo)) {
throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 撤销转账,参数缺少 `out_bill_no`');
}

$rocket->setPayload([
'_method' => 'POST',
'_url' => 'v3/fund-app/mch-transfer/transfer-bills/out-bill-no/'.$outBillNo.'/cancel',
]);

Logger::info('[Wechat][Marketing][MchTransfer][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]);

return $next($rocket);
}
}
102 changes: 102 additions & 0 deletions src/Plugin/Wechat/V3/Marketing/MchTransfer/CreatePlugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

declare(strict_types=1);

namespace Yansongda\Pay\Plugin\Wechat\V3\Marketing\MchTransfer;

use Closure;
use Yansongda\Artful\Contract\PluginInterface;
use Yansongda\Artful\Exception\ContainerException;
use Yansongda\Artful\Exception\InvalidConfigException;
use Yansongda\Artful\Exception\InvalidParamsException;
use Yansongda\Artful\Exception\ServiceNotFoundException;
use Yansongda\Artful\Logger;
use Yansongda\Artful\Rocket;
use Yansongda\Pay\Exception\DecryptException;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Pay;
use Yansongda\Supports\Collection;

use function Yansongda\Pay\encrypt_wechat_contents;
use function Yansongda\Pay\get_provider_config;
use function Yansongda\Pay\get_wechat_public_key;
use function Yansongda\Pay\get_wechat_serial_no;
use function Yansongda\Pay\get_wechat_type_key;

/**
* @see https://pay.weixin.qq.com/doc/v3/merchant/4012716434
*/
class CreatePlugin implements PluginInterface
{
/**
* @throws ContainerException
* @throws DecryptException
* @throws InvalidConfigException
* @throws InvalidParamsException
* @throws ServiceNotFoundException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
Logger::debug('[Wechat][Marketing][MchTransfer][CreatePlugin] 插件开始装载', ['rocket' => $rocket]);

$params = $rocket->getParams();
$payload = $rocket->getPayload();
$config = get_provider_config('wechat', $params);

if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: 发起商家转账,只支持普通商户模式,当前配置为服务商模式');
}

if (is_null($payload) || $payload->isEmpty()) {
throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 发起商家转账参数,参数缺失');
}

$rocket->mergePayload(array_merge(
[
'_method' => 'POST',
'_url' => 'v3/fund-app/mch-transfer/transfer-bills',
'appid' => $payload->get('appid', $config[get_wechat_type_key($params)] ?? ''),
],
$this->normal($params, $payload)
));

Logger::info('[Wechat][Marketing][MchTransfer][CreatePlugin] 插件装载完毕', ['rocket' => $rocket]);

return $next($rocket);
}

/**
* @throws ContainerException
* @throws InvalidParamsException
* @throws ServiceNotFoundException
* @throws DecryptException
* @throws InvalidConfigException
*/
protected function normal(array $params, Collection $payload): array
{
if (!$payload->has('user_name')) {
return [];
}

return $this->encryptSensitiveData($params, $payload);
}

/**
* @throws ContainerException
* @throws DecryptException
* @throws InvalidConfigException
* @throws InvalidParamsException
* @throws ServiceNotFoundException
*/
protected function encryptSensitiveData(array $params, Collection $payload): array
{
$data['_serial_no'] = get_wechat_serial_no($params);

$config = get_provider_config('wechat', $params);
$publicKey = get_wechat_public_key($config, $data['_serial_no']);

$data['user_name'] = encrypt_wechat_contents($payload->get('user_name'), $publicKey);

return $data;
}
}
77 changes: 77 additions & 0 deletions src/Plugin/Wechat/V3/Marketing/MchTransfer/InvokeAndroidPlugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

declare(strict_types=1);

namespace Yansongda\Pay\Plugin\Wechat\V3\Marketing\MchTransfer;

use Closure;
use Yansongda\Artful\Contract\PluginInterface;
use Yansongda\Artful\Exception\ContainerException;
use Yansongda\Artful\Exception\InvalidParamsException;
use Yansongda\Artful\Exception\InvalidResponseException;
use Yansongda\Artful\Exception\ServiceNotFoundException;
use Yansongda\Artful\Logger;
use Yansongda\Artful\Rocket;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Pay;
use Yansongda\Supports\Collection;
use Yansongda\Supports\Config;

use function Yansongda\Pay\get_provider_config;
use function Yansongda\Pay\get_wechat_type_key;

/**
* @see https://pay.weixin.qq.com/doc/v3/merchant/4012719576
*/
class InvokeAndroidPlugin implements PluginInterface
{
/**
* @throws ContainerException
* @throws InvalidResponseException
* @throws ServiceNotFoundException
* @throws InvalidParamsException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
/* @var Rocket $rocket */
$rocket = $next($rocket);

Logger::debug('[Wechat][V3][Marketing][MchTransfer][InvokeAndroidPlugin] 插件开始装载', ['rocket' => $rocket]);

$config = get_provider_config('wechat', $rocket->getParams());
$destination = $rocket->getDestination();
$packageInfo = $destination?->get('package_info');

if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: Android调起用户确认收款,只支持普通商户模式,当前配置为服务商模式');
}

if (is_null($packageInfo)) {
Logger::error('[Wechat][V3][Marketing][MchTransfer][InvokeAndroidPlugin] Android调起用户确认收款失败:响应缺少 `package_info` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);

throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('fail_reason') ?? 'Android调起用户确认收款失败:响应缺少 `package_info` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
}

$params = $rocket->getParams();
$config = get_provider_config('wechat', $params);
$payload = $rocket->getPayload();

$rocket->setDestination($this->getInvokeConfig($payload, $params, $config, $packageInfo));

Logger::info('[Wechat][V3][Marketing][MchTransfer][InvokeAndroidPlugin] 插件装载完毕', ['rocket' => $rocket]);

return $rocket;
}

protected function getInvokeConfig(?Collection $payload, array $params, array $config, string $packageInfo): Config
{
return new Config([
'businessType' => 'requestMerchantTransfer',
'query' => http_build_query([
'appId' => $payload?->get('_invoke_appId') ?? $config[get_wechat_type_key($params)] ?? '',
'mchId' => $payload?->get('_invoke_mchId') ?? $config['mch_id'] ?? '',
'package' => $packageInfo,
]),
]);
}
}
77 changes: 77 additions & 0 deletions src/Plugin/Wechat/V3/Marketing/MchTransfer/InvokeIosPlugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

declare(strict_types=1);

namespace Yansongda\Pay\Plugin\Wechat\V3\Marketing\MchTransfer;

use Closure;
use Yansongda\Artful\Contract\PluginInterface;
use Yansongda\Artful\Exception\ContainerException;
use Yansongda\Artful\Exception\InvalidParamsException;
use Yansongda\Artful\Exception\InvalidResponseException;
use Yansongda\Artful\Exception\ServiceNotFoundException;
use Yansongda\Artful\Logger;
use Yansongda\Artful\Rocket;
use Yansongda\Pay\Exception\Exception;
use Yansongda\Pay\Pay;
use Yansongda\Supports\Collection;
use Yansongda\Supports\Config;

use function Yansongda\Pay\get_provider_config;
use function Yansongda\Pay\get_wechat_type_key;

/**
* @see https://pay.weixin.qq.com/doc/v3/merchant/4012719578
*/
class InvokeIosPlugin implements PluginInterface
{
/**
* @throws ContainerException
* @throws InvalidResponseException
* @throws ServiceNotFoundException
* @throws InvalidParamsException
*/
public function assembly(Rocket $rocket, Closure $next): Rocket
{
/* @var Rocket $rocket */
$rocket = $next($rocket);

Logger::debug('[Wechat][V3][Marketing][MchTransfer][InvokeIosPlugin] 插件开始装载', ['rocket' => $rocket]);

$config = get_provider_config('wechat', $rocket->getParams());
$destination = $rocket->getDestination();
$packageInfo = $destination?->get('package_info');

if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) {
throw new InvalidParamsException(Exception::PARAMS_PLUGIN_ONLY_SUPPORT_NORMAL_MODE, '参数异常: iOS调起用户确认收款,只支持普通商户模式,当前配置为服务商模式');
}

if (is_null($packageInfo)) {
Logger::error('[Wechat][V3][Marketing][MchTransfer][InvokeIosPlugin] iOS调起用户确认收款失败:响应缺少 `package_info` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);

throw new InvalidResponseException(Exception::RESPONSE_MISSING_NECESSARY_PARAMS, $destination?->get('fail_reason') ?? 'iOS调起用户确认收款失败:响应缺少 `package_info` 参数,请自行检查参数是否符合微信要求', $destination?->all() ?? null);
}

$params = $rocket->getParams();
$config = get_provider_config('wechat', $params);
$payload = $rocket->getPayload();

$rocket->setDestination($this->getInvokeConfig($payload, $params, $config, $packageInfo));

Logger::info('[Wechat][V3][Marketing][MchTransfer][InvokeIosPlugin] 插件装载完毕', ['rocket' => $rocket]);

return $rocket;
}

protected function getInvokeConfig(?Collection $payload, array $params, array $config, string $packageInfo): Config
{
return new Config([
'businessType' => 'requestMerchantTransfer',
'query' => http_build_query([
'appId' => $payload?->get('_invoke_appId') ?? $config[get_wechat_type_key($params)] ?? '',
'mchId' => $payload?->get('_invoke_mchId') ?? $config['mch_id'] ?? '',
'package' => $packageInfo,
]),
]);
}
}
Loading

0 comments on commit 522f6cb

Please sign in to comment.