Skip to content

Commit 2d21acc

Browse files
authored
feat: 支持微信 v2 版本刷卡支付 (#753)
1 parent 04ea3fe commit 2d21acc

23 files changed

+498
-43
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
## TBD-v3.3.0
22

3+
### added
4+
5+
- feat: 支持微信 v2 版本刷卡支付(#753)
6+
37
### deleted
48

59
- delete: 移除废弃的类(#752)
@@ -12,7 +16,8 @@
1216

1317
- change: 所有的 `Find*Plugin` 调整为 `Query*Plugin`(#756)
1418
- change: 插件开始装载日志由 `info` 调整为 `debug`(#755)
15-
- change: ParserInterface 签名由 `?ResponseInterface $response` 调整为 `PackerInterface $packer, ?ResponseInterface $response`(#754)
19+
- change: ParserInterface 签名由 `?ResponseInterface $response` 变更为 `PackerInterface $packer, ?ResponseInterface $response`(#754)
20+
- change: \Yansongda\Pay\Plugin\Wechat\RadarSignPlugin 增加 `__construct(JsonPacker $jsonPacker, XmlPacker $xmlPacker)` 方法(#753)
1621

1722
## v3.2.14
1823

src/Plugin/Wechat/GeneralPlugin.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,17 @@ protected function getHeaders(): array
7474
];
7575
}
7676

77+
protected function getConfigKey(array $params): string
78+
{
79+
$key = ($params['_type'] ?? 'mp').'_app_id';
80+
81+
if ('app_app_id' === $key) {
82+
$key = 'app_id';
83+
}
84+
85+
return $key;
86+
}
87+
7788
abstract protected function doSomething(Rocket $rocket): void;
7889

7990
abstract protected function getUri(Rocket $rocket): string;

src/Plugin/Wechat/GeneralV2Plugin.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yansongda\Pay\Plugin\Wechat;
6+
7+
use function Yansongda\Pay\get_wechat_config;
8+
9+
use Yansongda\Pay\Rocket;
10+
11+
abstract class GeneralV2Plugin extends GeneralPlugin
12+
{
13+
protected function getHeaders(): array
14+
{
15+
return [
16+
'Content-Type' => 'application/xml',
17+
'User-Agent' => 'yansongda/pay-v3',
18+
];
19+
}
20+
21+
/**
22+
* @throws \Yansongda\Pay\Exception\ContainerException
23+
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
24+
*/
25+
protected function doSomething(Rocket $rocket): void
26+
{
27+
$config = get_wechat_config($rocket->getParams());
28+
$configKey = $this->getConfigKey($rocket->getParams());
29+
30+
$rocket->mergeParams(['_version' => 'v2']);
31+
32+
$rocket->mergePayload([
33+
'appid' => $config[$configKey] ?? '',
34+
'mch_id' => $config['mch_id'] ?? '',
35+
]);
36+
}
37+
38+
abstract protected function getUri(Rocket $rocket): string;
39+
}

src/Plugin/Wechat/Pay/Common/PrepayPlugin.php

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,21 +57,12 @@ protected function getWechatId(array $config, Rocket $rocket): array
5757
];
5858

5959
$subAppId = $payload->get('sub_appid', $config['sub_'.$configKey] ?? null);
60+
6061
if (!empty($subAppId)) {
6162
$result['sub_appid'] = $subAppId;
6263
}
6364
}
6465

6566
return $result;
6667
}
67-
68-
protected function getConfigKey(array $params): string
69-
{
70-
$key = ($params['_type'] ?? 'mp').'_app_id';
71-
if ('app_app_id' === $key) {
72-
$key = 'app_id';
73-
}
74-
75-
return $key;
76-
}
7768
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yansongda\Pay\Plugin\Wechat\Pay\Pos;
6+
7+
use Yansongda\Pay\Plugin\Wechat\GeneralV2Plugin;
8+
use Yansongda\Pay\Rocket;
9+
10+
/**
11+
* @see https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10&index=1
12+
*/
13+
class PayPlugin extends GeneralV2Plugin
14+
{
15+
protected function getUri(Rocket $rocket): string
16+
{
17+
return 'pay/micropay';
18+
}
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yansongda\Pay\Plugin\Wechat\Pay\Pos;
6+
7+
use Yansongda\Pay\Plugin\Wechat\GeneralV2Plugin;
8+
use Yansongda\Pay\Rocket;
9+
10+
/**
11+
* @see https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_02
12+
*/
13+
class QueryPlugin extends GeneralV2Plugin
14+
{
15+
protected function getUri(Rocket $rocket): string
16+
{
17+
return 'pay/orderquery';
18+
}
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yansongda\Pay\Plugin\Wechat\Pay\Pos;
6+
7+
use Yansongda\Pay\Plugin\Wechat\GeneralV2Plugin;
8+
use Yansongda\Pay\Rocket;
9+
10+
/**
11+
* @see https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_5
12+
*/
13+
class QueryRefundPlugin extends GeneralV2Plugin
14+
{
15+
protected function getUri(Rocket $rocket): string
16+
{
17+
return 'pay/refundquery';
18+
}
19+
}

src/Plugin/Wechat/RadarSignPlugin.php

Lines changed: 94 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Closure;
88
use GuzzleHttp\Psr7\Utils;
9+
use Psr\Http\Message\RequestInterface;
910
use Yansongda\Pay\Contract\PluginInterface;
1011
use Yansongda\Pay\Exception\Exception;
1112
use Yansongda\Pay\Exception\InvalidConfigException;
@@ -16,12 +17,24 @@
1617
use function Yansongda\Pay\get_wechat_sign;
1718

1819
use Yansongda\Pay\Logger;
20+
use Yansongda\Pay\Packer\JsonPacker;
21+
use Yansongda\Pay\Packer\XmlPacker;
1922
use Yansongda\Pay\Rocket;
2023
use Yansongda\Supports\Collection;
2124
use Yansongda\Supports\Str;
2225

2326
class RadarSignPlugin implements PluginInterface
2427
{
28+
protected JsonPacker $jsonPacker;
29+
30+
protected XmlPacker $xmlPacker;
31+
32+
public function __construct(JsonPacker $jsonPacker, XmlPacker $xmlPacker)
33+
{
34+
$this->jsonPacker = $jsonPacker;
35+
$this->xmlPacker = $xmlPacker;
36+
}
37+
2538
/**
2639
* @throws \Yansongda\Pay\Exception\ContainerException
2740
* @throws \Yansongda\Pay\Exception\InvalidConfigException
@@ -33,11 +46,83 @@ public function assembly(Rocket $rocket, Closure $next): Rocket
3346
{
3447
Logger::debug('[wechat][RadarSignPlugin] 插件开始装载', ['rocket' => $rocket]);
3548

49+
switch ($rocket->getParams()['_version'] ?? 'default') {
50+
case 'v2':
51+
$radar = $this->v2($rocket);
52+
break;
53+
default:
54+
$radar = $this->v3($rocket);
55+
break;
56+
}
57+
58+
$rocket->setRadar($radar);
59+
60+
Logger::info('[wechat][RadarSignPlugin] 插件装载完毕', ['rocket' => $rocket]);
61+
62+
return $next($rocket);
63+
}
64+
65+
/**
66+
* @throws \Yansongda\Pay\Exception\ContainerException
67+
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
68+
* @throws \Yansongda\Pay\Exception\InvalidConfigException
69+
* @throws \Exception
70+
*/
71+
protected function v2(Rocket $rocket): RequestInterface
72+
{
73+
$config = get_wechat_config($rocket->getParams());
74+
75+
$rocket->mergePayload(['nonce_str' => Str::random(32)]);
76+
$rocket->mergePayload([
77+
'sign' => $this->v2GetSign($config['mch_secret_key_v2'] ?? '', $rocket->getPayload()->all()),
78+
]);
79+
80+
return $rocket->getRadar()->withBody(
81+
Utils::streamFor($this->xmlPacker->pack($rocket->getPayload()->all()))
82+
);
83+
}
84+
85+
/**
86+
* @throws \Yansongda\Pay\Exception\InvalidConfigException
87+
*/
88+
protected function v2GetSign(?string $secret, array $payload): string
89+
{
90+
if (empty($secret)) {
91+
throw new InvalidConfigException(Exception::WECHAT_CONFIG_ERROR, 'Missing Wechat Config -- [mch_secret_key_v2]');
92+
}
93+
94+
$string = md5($this->v2PayloadToString($payload).'&key='.$secret);
95+
96+
return strtoupper($string);
97+
}
98+
99+
protected function v2PayloadToString(array $payload): string
100+
{
101+
ksort($payload);
102+
103+
$buff = '';
104+
105+
foreach ($payload as $k => $v) {
106+
$buff .= ('sign' != $k && '' != $v && !is_array($v)) ? $k.'='.$v.'&' : '';
107+
}
108+
109+
return trim($buff, '&');
110+
}
111+
112+
/**
113+
* @throws \Yansongda\Pay\Exception\ContainerException
114+
* @throws \Yansongda\Pay\Exception\InvalidConfigException
115+
* @throws \Yansongda\Pay\Exception\InvalidParamsException
116+
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
117+
* @throws \Exception
118+
*/
119+
protected function v3(Rocket $rocket): RequestInterface
120+
{
36121
$timestamp = time();
37122
$random = Str::random(32);
38-
$body = $this->payloadToString($rocket->getPayload());
39-
$contents = $this->getContents($rocket, $timestamp, $random);
40-
$authorization = $this->getWechatAuthorization($rocket->getParams(), $timestamp, $random, $contents);
123+
$body = $this->v3PayloadToString($rocket->getPayload());
124+
$contents = $this->v3GetContents($rocket, $timestamp, $random);
125+
$authorization = $this->v3GetWechatAuthorization($rocket->getParams(), $timestamp, $random, $contents);
41126
$radar = $rocket->getRadar()->withHeader('Authorization', $authorization);
42127

43128
if (!empty($rocket->getParams()['_serial_no'])) {
@@ -48,19 +133,15 @@ public function assembly(Rocket $rocket, Closure $next): Rocket
48133
$radar = $radar->withBody(Utils::streamFor($body));
49134
}
50135

51-
$rocket->setRadar($radar);
52-
53-
Logger::info('[wechat][RadarSignPlugin] 插件装载完毕', ['rocket' => $rocket]);
54-
55-
return $next($rocket);
136+
return $radar;
56137
}
57138

58139
/**
59140
* @throws \Yansongda\Pay\Exception\ContainerException
60141
* @throws \Yansongda\Pay\Exception\InvalidConfigException
61142
* @throws \Yansongda\Pay\Exception\ServiceNotFoundException
62143
*/
63-
protected function getWechatAuthorization(array $params, int $timestamp, string $random, string $contents): string
144+
protected function v3GetWechatAuthorization(array $params, int $timestamp, string $random, string $contents): string
64145
{
65146
$config = get_wechat_config($params);
66147
$mchPublicCertPath = $config['mch_public_cert_path'] ?? null;
@@ -90,7 +171,7 @@ protected function getWechatAuthorization(array $params, int $timestamp, string
90171
/**
91172
* @throws \Yansongda\Pay\Exception\InvalidParamsException
92173
*/
93-
protected function getContents(Rocket $rocket, int $timestamp, string $random): string
174+
protected function v3GetContents(Rocket $rocket, int $timestamp, string $random): string
94175
{
95176
$request = $rocket->getRadar();
96177

@@ -104,11 +185,11 @@ protected function getContents(Rocket $rocket, int $timestamp, string $random):
104185
$uri->getPath().(empty($uri->getQuery()) ? '' : '?'.$uri->getQuery())."\n".
105186
$timestamp."\n".
106187
$random."\n".
107-
$this->payloadToString($rocket->getPayload())."\n";
188+
$this->v3PayloadToString($rocket->getPayload())."\n";
108189
}
109190

110-
protected function payloadToString(?Collection $payload): string
191+
protected function v3PayloadToString(?Collection $payload): string
111192
{
112-
return (is_null($payload) || 0 === $payload->count()) ? '' : $payload->toJson();
193+
return (is_null($payload) || 0 === $payload->count()) ? '' : $this->jsonPacker->pack($payload->all());
113194
}
114195
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yansongda\Pay\Plugin\Wechat\Shortcut;
6+
7+
use Yansongda\Pay\Contract\ShortcutInterface;
8+
use Yansongda\Pay\Plugin\Wechat\Pay\Pos\PayPlugin;
9+
10+
class PosShortcut implements ShortcutInterface
11+
{
12+
public function getPlugins(array $params): array
13+
{
14+
return [
15+
PayPlugin::class,
16+
];
17+
}
18+
}

tests/FunctionTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use function Yansongda\Pay\decrypt_wechat_resource;
2222
use function Yansongda\Pay\decrypt_wechat_resource_aes_256_gcm;
2323
use function Yansongda\Pay\encrypt_wechat_contents;
24+
use function Yansongda\Pay\from_xml;
2425
use function Yansongda\Pay\get_alipay_config;
2526
use function Yansongda\Pay\get_private_cert;
2627
use function Yansongda\Pay\get_public_cert;
@@ -35,6 +36,7 @@
3536
use function Yansongda\Pay\verify_alipay_sign;
3637
use function Yansongda\Pay\verify_unipay_sign;
3738
use function Yansongda\Pay\verify_wechat_sign;
39+
use function Yansongda\Pay\to_xml;
3840

3941
class FunctionTest extends TestCase
4042
{

0 commit comments

Comments
 (0)