@@ -6,12 +6,13 @@ WeChatDeveloper 是一个面向 **微信** 与 **支付宝** 的轻量 PHP SDK
66
77## 特性
88
9- - 支持微信公众号 、小程序、微信开放平台 、微信支付 APIv3。
9+ - 支持微信公众平台 、小程序、微信服务平台 、微信支付 APIv3。
1010- 支持支付宝开放平台与支付网关调用。
1111- 统一入口 ` We\Client ` ,按通道创建客户端。
1212- 配置对象实现 ` ConfigInterface ` ,构造时完成基础校验。
1313- 缓存只依赖 ` StoreCacheInterface ` ,可用于单机文件缓存或集群 Redis 适配。
14- - 开放平台授权方 refresh token 通过 ` StoreTokenInterface ` 由业务系统存取。
14+ - 微信服务平台授权方 refresh token 通过 ` StoreTokenInterface ` 由业务系统存取。
15+ - 支持 JSON、原始响应、二进制下载和 multipart 上传等协议层通用能力。
1516- 返回值默认是数组,失败时抛出 ` WechatException ` 或其子类。
1617
1718## 环境要求
@@ -42,9 +43,29 @@ composer require zoujingli/wechat-developer:2.0.x-dev
4243``` bash
4344cd WeChatDeveloper
4445composer install
46+ composer validate --strict
4547composer test
4648```
4749
50+ ## 项目结构
51+
52+ ``` text
53+ src/
54+ ├── Client.php # SDK 根入口与通道客户端工厂
55+ ├── Config/ # 微信、支付宝平台配置对象
56+ ├── Contract/ # 配置、缓存、授权方 Token 存储契约
57+ ├── Exception/ # SDK 异常类型
58+ ├── Platform/
59+ │ ├── Wechat/ # 微信公众平台、小程序、微信服务平台、微信支付 APIv3 客户端
60+ │ │ └── Concerns/ # 微信 JSON、raw/download/upload、Token 注入等协议层复用能力
61+ │ └── Alipay/ # 支付宝开放平台与支付客户端
62+ └── Support/ # 缓存、HTTP、签名、XML、消息/支付解密工具
63+
64+ tests/ # PHPUnit 测试用例,命名空间 We\Tests
65+ ```
66+
67+ 测试入口为根目录 ` tests/ ` ,` phpunit.xml ` 使用 ` tests/bootstrap.php ` 引导 Composer autoload。
68+
4869## 快速开始
4970
5071``` php
@@ -57,20 +78,20 @@ use We\Config\WechatPlatformConfig;
5778
5879$client = new Client();
5980
60- $official = $client->wechatPlatform(new WechatPlatformConfig(
81+ $platform = $client->wechatPlatform(new WechatPlatformConfig(
6182 appid: 'wx_appid',
6283 appSecret: 'app_secret',
6384));
6485
65- $users = $official ->get('cgi-bin/user/get', [
86+ $users = $platform ->get('cgi-bin/user/get', [
6687 'next_openid' => '',
6788]);
6889```
6990
7091` post() ` 、` get() ` 、` call() ` 的 path 与官方文档保持一致,通常不需要前导 ` / ` 。
7192
7293``` php
73- $menu = $official ->post('cgi-bin/menu/create', [
94+ $menu = $platform ->post('cgi-bin/menu/create', [
7495 'button' => [
7596 [
7697 'type' => 'click',
@@ -88,17 +109,17 @@ SDK 不把官方接口包装成大量固定方法,核心约定是“官方文
88109
89110``` php
90111// GET:第二个参数会作为 query string。
91- $result = $official ->get('cgi-bin/user/get', ['next_openid' => '']);
112+ $result = $platform ->get('cgi-bin/user/get', ['next_openid' => '']);
92113
93114// POST:第二个参数默认作为 JSON body。
94- $result = $official ->post('cgi-bin/message/custom/send', [
115+ $result = $platform ->post('cgi-bin/message/custom/send', [
95116 'touser' => 'openid',
96117 'msgtype' => 'text',
97118 'text' => ['content' => 'hello'],
98119]);
99120
100121// call:显式指定 HTTP 方法,并可透传 Guzzle options。
101- $result = $official ->call('cgi-bin/menu/get', [], 'GET');
122+ $result = $platform ->call('cgi-bin/menu/get', [], 'GET');
102123```
103124
104125调用时需要注意:
@@ -112,6 +133,8 @@ $result = $official->call('cgi-bin/menu/get', [], 'GET');
112133| GET query | 用 ` get($path, $query) ` 。 |
113134| 自定义 query + JSON body | ` post($path, $body, ['query' => [...], 'json' => $body]) ` 。 |
114135| 表单提交或原始 body | 透传 Guzzle 的 ` form_params ` 或 ` body ` 。 |
136+ | 二进制/非 JSON 响应 | 用 ` raw() ` 或 ` download() ` 返回 PSR-7 Response。 |
137+ | multipart 上传 | 用 ` upload($path, $multipart, $query) ` ,SDK 会按通道规则附加 token。 |
115138
116139示例:小程序登录接口不需要 access token,应该关闭自动 token:
117140
@@ -124,6 +147,43 @@ $session = $wxapp->get('sns/jscode2session', [
124147], ['with_token' => false]);
125148```
126149
150+ 图片、媒体、文件等非 JSON 响应可直接获取原始响应:
151+
152+ ``` php
153+ $response = $platform->download('cgi-bin/media/get', [
154+ 'media_id' => 'MEDIA_ID',
155+ ]);
156+
157+ $binary = (string) $response->getBody();
158+ ```
159+
160+ 上传媒体或文件时传入 Guzzle multipart 结构:
161+
162+ ``` php
163+ $media = $platform->upload('cgi-bin/media/upload', [
164+ [
165+ 'name' => 'media',
166+ 'contents' => fopen(__DIR__ . '/demo.jpg', 'rb'),
167+ 'filename' => 'demo.jpg',
168+ ],
169+ ], [
170+ 'type' => 'image',
171+ ]);
172+ ```
173+
174+ ### 协议层能力说明
175+
176+ 微信公众平台、小程序、微信服务平台共用 ` InteractsProtocol ` 协议层能力:
177+
178+ | 方法 | 说明 |
179+ | ------| ------|
180+ | ` request() ` | 发送 JSON 风格接口请求并解析为数组。 |
181+ | ` raw() ` | 返回 PSR-7 Response,不解析 JSON,适合图片、媒体、二维码等原始响应。 |
182+ | ` download() ` | 以 GET 方式获取二进制资源,并按通道规则附加 token 或签名。 |
183+ | ` upload() ` | 透传 Guzzle ` multipart ` 上传结构,并解析平台 JSON 响应。 |
184+
185+ 微信支付 APIv3 的 ` raw() ` 与 ` download() ` 会先按官方规则生成 ` Authorization ` 签名,再返回原始响应。支付宝网关请求统一复用公共参数构造、签名和验签逻辑,电脑网站支付跳转地址也使用同一套签名参数。
186+
127187## 配置来源示例
128188
129189生产项目通常从数据库或配置中心读取账号配置,再使用 ` fromArray() ` 构造配置对象:
@@ -146,7 +206,7 @@ $client = new Client(
146206 cacheKeyPrefix: 'my_project_prod',
147207);
148208
149- $official = $client->wechatPlatform(WechatPlatformConfig::fromArray($row));
209+ $platform = $client->wechatPlatform(WechatPlatformConfig::fromArray($row));
150210```
151211
152212` cacheKeyPrefix ` 建议按项目和环境区分,例如 ` mall_prod ` 、` mall_test ` 。` storage_scope ` 建议按租户、账号或业务线区分,避免同一 appid 在不同业务上下文中复用缓存。
@@ -172,14 +232,14 @@ new Client(
172232| 参数 | 说明 |
173233| ------| ------|
174234| ` $cache ` | SDK 运行态缓存,主要保存 access token;不传时默认使用 ` FileCacheStore ` 。 |
175- | ` $authorizers ` | 微信开放平台代调用时 ,读取和回写授权方 refresh token。 |
235+ | ` $authorizers ` | 微信服务平台代调用时 ,读取和回写授权方 refresh token。 |
176236| ` $http ` | 可注入自定义 Guzzle HTTP 客户端,便于统一超时、代理或单元测试。 |
177237| ` $cacheKeyPrefix ` | 缓存键前缀,默认 ` wechat_developer ` ;生产环境建议按项目设置。 |
178238
179239也可以用字符串通道创建客户端:
180240
181241``` php
182- $official = $client->get('wechat.platform', WechatPlatformConfig::fromArray($config));
242+ $platform = $client->get('wechat.platform', WechatPlatformConfig::fromArray($config));
183243```
184244
185245支持的通道:
@@ -289,14 +349,14 @@ $client = new Client(
289349例如:
290350
291351``` text
292- my_app:wechat.platform:wechat:app:wx123:official :access_token
352+ my_app:wechat.platform:wechat:app:wx123:platform :access_token
293353```
294354
295355完整键由 ` CacheKey ` 生成,微信 token 逻辑键由 ` TokenCacheKey ` 生成。
296356
297- ## 微信开放平台授权方 Token
357+ ## 微信服务平台授权方 Token
298358
299- 开放平台代调用授权方接口时 ,SDK 需要读取授权方 refresh token,并在刷新后把新 token 回写业务存储。业务系统实现 ` StoreTokenInterface ` 即可:
359+ 微信服务平台代调用授权方接口时 ,SDK 需要读取授权方 refresh token,并在刷新后把新 token 回写业务存储。业务系统实现 ` StoreTokenInterface ` 即可:
300360
301361``` php
302362use We\Contract\StoreTokenInterface;
@@ -325,21 +385,21 @@ $client = new Client(
325385);
326386```
327387
328- ## 微信公众号
388+ ## 微信公众平台
329389
330390``` php
331391use We\Config\WechatPlatformConfig;
332392
333- $official = $client->wechatPlatform(new WechatPlatformConfig(
393+ $platform = $client->wechatPlatform(new WechatPlatformConfig(
334394 appid: 'wx_appid',
335395 appSecret: 'app_secret',
336396 token: 'message_token',
337397 encodingAesKey: 'encoding_aes_key',
338398));
339399
340- $accessToken = $official ->accessToken();
400+ $accessToken = $platform ->accessToken();
341401
342- $result = $official ->post('cgi-bin/message/custom/send', [
402+ $result = $platform ->post('cgi-bin/message/custom/send', [
343403 'touser' => 'openid',
344404 'msgtype' => 'text',
345405 'text' => ['content' => 'hello'],
@@ -349,7 +409,7 @@ $result = $official->post('cgi-bin/message/custom/send', [
349409安全模式消息解密:
350410
351411``` php
352- $plain = $official ->post('decrypt_message', [
412+ $plain = $platform ->post('decrypt_message', [
353413 'body' => $rawBody,
354414 'msg_signature' => $signature,
355415 'timestamp' => $timestamp,
@@ -385,15 +445,16 @@ $phone = $wxapp->post('wxa/business/getuserphonenumber', [
385445]);
386446```
387447
388- 如果官方接口返回图片、文件等二进制内容,默认 ` post() ` 会按 JSON 响应解析,不适合直接处理;建议注入自定义 Guzzle 客户端或在业务侧扩展专用下载方法 。
448+ 如果官方接口返回图片、文件等二进制内容,使用 ` download() ` 或 ` raw() ` 获取原始 PSR-7 Response; 默认 ` post() ` 仍按 JSON 响应解析。
389449
390- ## 微信开放平台
450+ ## 微信服务平台
391451
392452``` php
393453use We\Config\WechatServiceConfig;
394454
455+ $componentAppid = 'component_appid';
395456$service = $client->wechatService(new WechatServiceConfig(
396- componentAppid: 'component_appid' ,
457+ componentAppid: $componentAppid ,
397458 componentAppSecret: 'component_secret',
398459 componentToken: 'component_token',
399460 componentEncodingAesKey: 'component_encoding_aes_key',
@@ -439,6 +500,14 @@ $refund = $payment->post('v3/refund/domestic/refunds', [
439500]);
440501```
441502
503+ 下载账单等非 JSON 响应时使用已签名的 ` download() ` :
504+
505+ ``` php
506+ $bill = $payment->download('v3/bill/tradebill', [
507+ 'bill_date' => '2026-05-08',
508+ ]);
509+ ```
510+
442511回调验签与解密:
443512
444513``` php
@@ -497,10 +566,10 @@ $page = $payment->post('page', [
497566
498567## 常见业务案例
499568
500- ### 公众号 :创建菜单并发送客服消息
569+ ### 微信公众平台 :创建菜单并发送客服消息
501570
502571``` php
503- $official ->post('cgi-bin/menu/create', [
572+ $platform ->post('cgi-bin/menu/create', [
504573 'button' => [
505574 [
506575 'name' => '服务',
@@ -512,40 +581,39 @@ $official->post('cgi-bin/menu/create', [
512581 ],
513582]);
514583
515- $official ->post('cgi-bin/message/custom/send', [
584+ $platform ->post('cgi-bin/message/custom/send', [
516585 'touser' => 'openid',
517586 'msgtype' => 'text',
518587 'text' => ['content' => '您好,客服消息已发送。'],
519588]);
520589```
521590
522- ### 公众号 :网页授权 URL 与用户资料
591+ ### 微信公众平台 :网页授权 URL 与用户资料
523592
524593``` php
525594$redirectUri = 'https://example.com/oauth/callback';
526- $url = 'https://open.weixin.qq.com/connect/oauth2/authorize?' . http_build_query([
527- 'appid' => 'wx_appid',
595+ $authUrl = $platform->get('connect/oauth2/authorize', [
528596 'redirect_uri' => $redirectUri,
529- 'response_type' => 'code',
530597 'scope' => 'snsapi_userinfo',
531598 'state' => 'state-value',
532- ]) . '#wechat_redirect';
599+ ]);
600+ $url = $authUrl['url'];
533601
534- $oauth = $official ->get('sns/oauth2/access_token', [
602+ $oauth = $platform ->get('sns/oauth2/access_token', [
535603 'appid' => 'wx_appid',
536604 'secret' => 'app_secret',
537605 'code' => $code,
538606 'grant_type' => 'authorization_code',
539607], ['with_token' => false]);
540608
541- $user = $official ->get('sns/userinfo', [
609+ $user = $platform ->get('sns/userinfo', [
542610 'access_token' => $oauth['access_token'],
543611 'openid' => $oauth['openid'],
544612 'lang' => 'zh_CN',
545613], ['with_token' => false]);
546614```
547615
548- ### 开放平台 :授权回调后保存授权方 Token
616+ ### 微信服务平台 :授权回调后保存授权方 Token
549617
550618``` php
551619$componentToken = $service->componentAccessToken($componentVerifyTicket);
@@ -647,7 +715,7 @@ WeChatDeveloper 只处理协议层和 HTTP 编排:
647715- 不提供账号、租户、菜单草稿、订单、授权记录等业务表。
648716- 不托管密钥加密存储。
649717- 不绑定 Hyperf、Laravel、Symfony 等框架。
650- - 不保证覆盖每一个官方接口别名,通用接口通过官方 path 调用 。
718+ - 不保证覆盖每一个官方接口别名;SDK 通过官方 path、JSON、raw/download、multipart upload 等协议层能力支持接口调用 。
651719
652720这种边界使 SDK 更适合作为开源底层包,被后台系统、SaaS 平台或命令行工具组合使用。
653721
0 commit comments