diff --git a/CHANGELOG.md b/CHANGELOG.md index 50d2bdfec..e892edd2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ -## 3.7.7 +## v3.7.8 + +### added + +- feat: 新增 v3 付款码服务商模式(#1010) + +## v3.7.7 ### added diff --git a/src/Plugin/Wechat/V3/Pay/Pos/CancelPlugin.php b/src/Plugin/Wechat/V3/Pay/Pos/CancelPlugin.php index 94cd3cad0..2da3ac3e1 100644 --- a/src/Plugin/Wechat/V3/Pay/Pos/CancelPlugin.php +++ b/src/Plugin/Wechat/V3/Pay/Pos/CancelPlugin.php @@ -13,12 +13,15 @@ use Yansongda\Artful\Logger; use Yansongda\Artful\Rocket; use Yansongda\Pay\Exception\Exception; +use Yansongda\Pay\Pay; +use Yansongda\Supports\Collection; use function Yansongda\Pay\get_provider_config; use function Yansongda\Pay\get_wechat_type_key; /** * @see https://pay.weixin.qq.com/docs/merchant/apis/code-payment-v3/direct/reverse.html + * @see https://pay.weixin.qq.com/docs/partner/apis/partner-code-payment-v3/partner/partner-reverse.html */ class CancelPlugin implements PluginInterface { @@ -41,12 +44,17 @@ public function assembly(Rocket $rocket, Closure $next): Rocket throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 付款码支付撤销订单,参数缺少 `out_trade_no`'); } + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $params, $config); + } + $rocket->setPayload(array_merge( [ '_method' => 'POST', '_url' => 'v3/pay/transactions/out-trade-no/'.$outTradeNo.'/reverse', + '_service_url' => 'v3/pay/partner/transactions/out-trade-no/'.$outTradeNo.'/reverse', ], - $this->normal($params, $config) + $data ?? $this->normal($params, $config) )); Logger::info('[Wechat][V3][Pay][Pos][CancelPlugin] 插件装载完毕', ['rocket' => $rocket]); @@ -61,4 +69,15 @@ protected function normal(array $params, array $config): array 'mchid' => $config['mch_id'] ?? '', ]; } + + protected function service(Collection $payload, array $params, array $config): array + { + $configKey = get_wechat_type_key($params); + + return [ + 'sp_appid' => $config[$configKey] ?? '', + 'sp_mchid' => $config['mch_id'] ?? '', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]; + } } diff --git a/src/Plugin/Wechat/V3/Pay/Pos/PayPlugin.php b/src/Plugin/Wechat/V3/Pay/Pos/PayPlugin.php index c77ca0f05..c97f7cb28 100644 --- a/src/Plugin/Wechat/V3/Pay/Pos/PayPlugin.php +++ b/src/Plugin/Wechat/V3/Pay/Pos/PayPlugin.php @@ -8,15 +8,20 @@ use Throwable; 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 Yansongda\Supports\Collection; use function Yansongda\Pay\get_provider_config; use function Yansongda\Pay\get_wechat_type_key; /** * @see https://pay.weixin.qq.com/docs/merchant/apis/code-payment-v3/direct/code-pay.html + * @see https://pay.weixin.qq.com/docs/partner/apis/partner-code-payment-v3/partner/partner-code-pay.html */ class PayPlugin implements PluginInterface { @@ -29,15 +34,25 @@ public function assembly(Rocket $rocket, Closure $next): Rocket { Logger::debug('[Wechat][V3][Pay][Pos][PayPlugin] 插件开始装载', ['rocket' => $rocket]); + $payload = $rocket->getPayload(); $params = $rocket->getParams(); $config = get_provider_config('wechat', $params); + if (is_null($payload)) { + throw new InvalidParamsException(Exception::PARAMS_NECESSARY_PARAMS_MISSING, '参数异常: 付款码支付,参数为空'); + } + + if (Pay::MODE_SERVICE === ($config['mode'] ?? Pay::MODE_NORMAL)) { + $data = $this->service($payload, $params, $config); + } + $rocket->mergePayload(array_merge( [ '_method' => 'POST', '_url' => 'v3/pay/transactions/codepay', + '_service_url' => 'v3/pay/partner/transactions/codepay', ], - $this->normal($params, $config) + $data ?? $this->normal($params, $config) )); Logger::info('[Wechat][V3][Pay][Pos][PayPlugin] 插件装载完毕', ['rocket' => $rocket]); @@ -52,4 +67,15 @@ protected function normal(array $params, array $config): array 'mchid' => $config['mch_id'] ?? '', ]; } + + protected function service(Collection $payload, array $params, array $config): array + { + $configKey = get_wechat_type_key($params); + + return [ + 'sp_appid' => $config[$configKey] ?? '', + 'sp_mchid' => $config['mch_id'] ?? '', + 'sub_mchid' => $payload->get('sub_mchid', $config['sub_mch_id'] ?? ''), + ]; + } } diff --git a/tests/Plugin/Wechat/V3/Pay/Pos/CancelPluginTest.php b/tests/Plugin/Wechat/V3/Pay/Pos/CancelPluginTest.php index ad4378d5d..8a39e4614 100644 --- a/tests/Plugin/Wechat/V3/Pay/Pos/CancelPluginTest.php +++ b/tests/Plugin/Wechat/V3/Pay/Pos/CancelPluginTest.php @@ -20,6 +20,20 @@ protected function setUp(): void $this->plugin = new CancelPlugin(); } + public function testMissingOut() + { + $rocket = new Rocket(); + $rocket->setPayload(new Collection([ + 'aaa' => 'aaa' + ])); + + self::expectException(InvalidParamsException::class); + self::expectExceptionCode(Exception::PARAMS_NECESSARY_PARAMS_MISSING); + self::expectExceptionMessage('参数异常: 付款码支付撤销订单,参数缺少 `out_trade_no`'); + + $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + } + public function testNormal() { $rocket = new Rocket(); @@ -31,25 +45,46 @@ public function testNormal() $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); $payload = $result->getPayload(); - self::assertEqualsCanonicalizing([ - '_method' => 'POST', - '_url' => 'v3/pay/transactions/out-trade-no/111/reverse', - 'appid' => 'wx55955316af4ef13', - 'mchid' => '1600314069' - ], $payload->all()); + self::assertEquals('POST', $payload->get('_method')); + self::assertEquals('v3/pay/transactions/out-trade-no/111/reverse', $payload->get('_url')); + self::assertEquals('wx55955316af4ef13', $payload->get('appid')); + self::assertEquals('1600314069', $payload->get('mchid')); } - public function testMissingOut() + public function testService() { $rocket = new Rocket(); - $rocket->setPayload(new Collection([ + $rocket->setParams(['_config' => 'service_provider'])->setPayload(new Collection([ + "out_trade_no" => "111", 'aaa' => 'aaa' ])); - self::expectException(InvalidParamsException::class); - self::expectExceptionCode(Exception::PARAMS_NECESSARY_PARAMS_MISSING); - self::expectExceptionMessage('参数异常: 付款码支付撤销订单,参数缺少 `out_trade_no`'); + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + $payload = $result->getPayload(); - $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + self::assertEquals('POST', $payload->get('_method')); + self::assertEquals('v3/pay/partner/transactions/out-trade-no/111/reverse', $payload->get('_service_url')); + self::assertEquals('wx55955316af4ef13', $payload->get('sp_appid')); + self::assertEquals('1600314069', $payload->get('sp_mchid')); + self::assertEquals('1600314070', $payload->get('sub_mchid')); + } + + public function testServiceParams() + { + $rocket = new Rocket(); + $rocket->setParams(['_config' => 'service_provider'])->setPayload(new Collection([ + 'sub_mchid' => '1222', + "out_trade_no" => "111", + 'aaa' => 'aaa' + ])); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + $payload = $result->getPayload(); + + self::assertEquals('POST', $payload->get('_method')); + self::assertEquals('v3/pay/partner/transactions/out-trade-no/111/reverse', $payload->get('_service_url')); + self::assertEquals('wx55955316af4ef13', $payload->get('sp_appid')); + self::assertEquals('1600314069', $payload->get('sp_mchid')); + self::assertEquals('1222', $payload->get('sub_mchid')); } } diff --git a/tests/Plugin/Wechat/V3/Pay/Pos/PayPluginTest.php b/tests/Plugin/Wechat/V3/Pay/Pos/PayPluginTest.php index f26b9280d..acaf5ee73 100644 --- a/tests/Plugin/Wechat/V3/Pay/Pos/PayPluginTest.php +++ b/tests/Plugin/Wechat/V3/Pay/Pos/PayPluginTest.php @@ -3,6 +3,8 @@ namespace Yansongda\Pay\Tests\Plugin\Wechat\V3\Pay\Pos; use Yansongda\Artful\Contract\PackerInterface; +use Yansongda\Artful\Exception\InvalidParamsException; +use Yansongda\Pay\Exception\Exception; use Yansongda\Pay\Plugin\Wechat\V3\Pay\Pos\PayPlugin; use Yansongda\Artful\Rocket; use Yansongda\Pay\Tests\TestCase; @@ -19,6 +21,17 @@ protected function setUp(): void $this->plugin = new PayPlugin(); } + public function testEmptyPayload() + { + $rocket = new Rocket(); + + self::expectException(InvalidParamsException::class); + self::expectExceptionCode(Exception::PARAMS_NECESSARY_PARAMS_MISSING); + self::expectExceptionMessage('参数异常: 付款码支付,参数为空'); + + $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + } + public function testNormal() { $rocket = new Rocket(); @@ -50,4 +63,71 @@ public function testNormal() self::assertEquals(1, $payload->get('amount')['total']); self::assertEquals('5678', $payload->get('scene_info')['id']); } + + public function testService() + { + $rocket = new Rocket(); + $rocket->setParams(['_config' => 'service_provider'])->setPayload(new Collection([ + 'description' => 'test', + "out_trade_no" => "111", + 'payer' => [ + 'auth_code' => '1234' + ], + 'amount' => [ + 'total' => 1, + ], + 'scene_info' => [ + 'id' => '5678' + ], + ])); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + $payload = $result->getPayload(); + + self::assertEquals(PackerInterface::class, $result->getPacker()); + self::assertEquals('v3/pay/partner/transactions/codepay', $payload->get('_service_url')); + self::assertEquals('POST', $payload->get('_method')); + self::assertEquals('wx55955316af4ef13', $payload->get('sp_appid')); + self::assertEquals('1600314069', $payload->get('sp_mchid')); + self::assertEquals('1600314070', $payload->get('sub_mchid')); + self::assertEquals('111', $payload->get('out_trade_no')); + self::assertEquals('test', $payload->get('description')); + self::assertEquals('1234', $payload->get('payer')['auth_code']); + self::assertEquals(1, $payload->get('amount')['total']); + self::assertEquals('5678', $payload->get('scene_info')['id']); + } + + public function testServiceParams() + { + $rocket = new Rocket(); + $rocket->setParams(['_config' => 'service_provider'])->setPayload(new Collection([ + 'sub_mchid' => 'aaaa', + 'description' => 'test', + "out_trade_no" => "111", + 'payer' => [ + 'auth_code' => '1234' + ], + 'amount' => [ + 'total' => 1, + ], + 'scene_info' => [ + 'id' => '5678' + ], + ])); + + $result = $this->plugin->assembly($rocket, function ($rocket) { return $rocket; }); + $payload = $result->getPayload(); + + self::assertEquals(PackerInterface::class, $result->getPacker()); + self::assertEquals('v3/pay/partner/transactions/codepay', $payload->get('_service_url')); + self::assertEquals('POST', $payload->get('_method')); + self::assertEquals('wx55955316af4ef13', $payload->get('sp_appid')); + self::assertEquals('1600314069', $payload->get('sp_mchid')); + self::assertEquals('aaaa', $payload->get('sub_mchid')); + self::assertEquals('111', $payload->get('out_trade_no')); + self::assertEquals('test', $payload->get('description')); + self::assertEquals('1234', $payload->get('payer')['auth_code']); + self::assertEquals(1, $payload->get('amount')['total']); + self::assertEquals('5678', $payload->get('scene_info')['id']); + } }