Full Code of w7corp/easywechat for AI

6.x 19d09d6ef85a cached
543 files
1.2 MB
415.1k tokens
1046 symbols
1 requests
Download .txt
Showing preview only (1,312K chars total). Download the full file or copy to clipboard to get everything.
Repository: w7corp/easywechat
Branch: 6.x
Commit: 19d09d6ef85a
Files: 543
Total size: 1.2 MB

Directory structure:
gitextract_ygm6ii3b/

├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE.md
│   └── workflows/
│       ├── deploy.yml
│       ├── lint.yml
│       └── test.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── composer.json
├── docs/
│   ├── .editorconfig
│   ├── .gitignore
│   ├── .npmrc
│   ├── .prettierrc
│   ├── .vitepress/
│   │   ├── config.ts
│   │   ├── theme/
│   │   │   ├── components/
│   │   │   │   ├── Banner.vue
│   │   │   │   ├── Footer.vue
│   │   │   │   ├── SponsorsAside.vue
│   │   │   │   ├── SponsorsGroup.vue
│   │   │   │   └── VersionTag.vue
│   │   │   ├── index.ts
│   │   │   └── styles/
│   │   │       ├── badges.css
│   │   │       ├── index.css
│   │   │       ├── inline-demo.css
│   │   │       ├── layout.css
│   │   │       ├── options-boxes.css
│   │   │       ├── pages.css
│   │   │       ├── style-guide.css
│   │   │       └── utilities.css
│   │   ├── versions.ts
│   │   └── vitepress+1.6.3.patch
│   ├── README.md
│   ├── env.d.ts
│   ├── package.json
│   ├── postcss.config.js
│   ├── purge-caches
│   ├── src/
│   │   ├── 3.x/
│   │   │   ├── access_token.md
│   │   │   ├── accounts.md
│   │   │   ├── anaylsis.md
│   │   │   ├── broadcast.md
│   │   │   ├── cache.md
│   │   │   ├── card.md
│   │   │   ├── configuration.md
│   │   │   ├── contributing.md
│   │   │   ├── events.md
│   │   │   ├── index.md
│   │   │   ├── installation.md
│   │   │   ├── integration.md
│   │   │   ├── js.md
│   │   │   ├── lucky-money.md
│   │   │   ├── material.md
│   │   │   ├── menu.md
│   │   │   ├── merchant_payment.md
│   │   │   ├── message-transfer.md
│   │   │   ├── messages.md
│   │   │   ├── mini_program.md
│   │   │   ├── miscellaneous.md
│   │   │   ├── notice.md
│   │   │   ├── oauth.md
│   │   │   ├── open_platform.md
│   │   │   ├── overview.md
│   │   │   ├── payment.md
│   │   │   ├── poi.md
│   │   │   ├── qrcode.md
│   │   │   ├── releases.md
│   │   │   ├── reply.md
│   │   │   ├── roadmap.md
│   │   │   ├── semantic.md
│   │   │   ├── server.md
│   │   │   ├── shake-around.md
│   │   │   ├── short-url.md
│   │   │   ├── sidebar.js
│   │   │   ├── staff.md
│   │   │   ├── store.md
│   │   │   ├── troubleshooting.md
│   │   │   ├── tutorial.md
│   │   │   ├── user-group.md
│   │   │   ├── user-tag.md
│   │   │   └── user.md
│   │   ├── 4.x/
│   │   │   ├── basic-services/
│   │   │   │   ├── content_security.md
│   │   │   │   ├── jssdk.md
│   │   │   │   ├── media.md
│   │   │   │   ├── qrcode.md
│   │   │   │   └── url.md
│   │   │   ├── client.md
│   │   │   ├── contributing.md
│   │   │   ├── customize/
│   │   │   │   ├── access_token.md
│   │   │   │   ├── cache.md
│   │   │   │   └── replace-service.md
│   │   │   ├── index.md
│   │   │   ├── installation.md
│   │   │   ├── integration.md
│   │   │   ├── micro-merchant/
│   │   │   │   ├── certficates.md
│   │   │   │   ├── index.md
│   │   │   │   ├── material.md
│   │   │   │   ├── media.md
│   │   │   │   ├── merchant-config.md
│   │   │   │   ├── submit-application.md
│   │   │   │   ├── upgrade.md
│   │   │   │   └── withdraw.md
│   │   │   ├── mini-program/
│   │   │   │   ├── app_code.md
│   │   │   │   ├── auth.md
│   │   │   │   ├── customer_service.md
│   │   │   │   ├── data_cube.md
│   │   │   │   ├── decrypt.md
│   │   │   │   ├── express.md
│   │   │   │   ├── index.md
│   │   │   │   ├── nearby_poi.md
│   │   │   │   ├── plugin.md
│   │   │   │   ├── soter.md
│   │   │   │   ├── subscribe_message.md
│   │   │   │   └── template_message.md
│   │   │   ├── miscellaneous.md
│   │   │   ├── official-account/
│   │   │   │   ├── accounts.md
│   │   │   │   ├── base.md
│   │   │   │   ├── broadcasting.md
│   │   │   │   ├── card.md
│   │   │   │   ├── comment.md
│   │   │   │   ├── configuration.md
│   │   │   │   ├── customer_service.md
│   │   │   │   ├── data_cube.md
│   │   │   │   ├── events.md
│   │   │   │   ├── goods.md
│   │   │   │   ├── index.md
│   │   │   │   ├── material.md
│   │   │   │   ├── menu.md
│   │   │   │   ├── message-transfer.md
│   │   │   │   ├── messages.md
│   │   │   │   ├── oauth.md
│   │   │   │   ├── poi.md
│   │   │   │   ├── reply.md
│   │   │   │   ├── semantic.md
│   │   │   │   ├── server.md
│   │   │   │   ├── shake-around.md
│   │   │   │   ├── store.md
│   │   │   │   ├── template_message.md
│   │   │   │   ├── tutorial.md
│   │   │   │   ├── user-tag.md
│   │   │   │   └── user.md
│   │   │   ├── open-platform/
│   │   │   │   ├── authorizer-delegate.md
│   │   │   │   ├── index.md
│   │   │   │   └── server.md
│   │   │   ├── open-work/
│   │   │   │   ├── index.md
│   │   │   │   ├── provider.md
│   │   │   │   ├── server.md
│   │   │   │   ├── service.md
│   │   │   │   └── work.md
│   │   │   ├── overview.md
│   │   │   ├── payment/
│   │   │   │   ├── bill.md
│   │   │   │   ├── contract.md
│   │   │   │   ├── index.md
│   │   │   │   ├── jssdk.md
│   │   │   │   ├── micropay.md
│   │   │   │   ├── notify.md
│   │   │   │   ├── order.md
│   │   │   │   ├── profit-sharing.md
│   │   │   │   ├── redpack.md
│   │   │   │   ├── refund.md
│   │   │   │   ├── reverse.md
│   │   │   │   ├── scan-pay.md
│   │   │   │   ├── security.md
│   │   │   │   └── transfer.md
│   │   │   ├── sidebar.js
│   │   │   ├── troubleshooting.md
│   │   │   └── wework/
│   │   │       ├── agents.md
│   │   │       ├── contacts.md
│   │   │       ├── external-contact.md
│   │   │       ├── group-robot.md
│   │   │       ├── index.md
│   │   │       ├── invoice.md
│   │   │       ├── media.md
│   │   │       ├── menu.md
│   │   │       ├── message.md
│   │   │       ├── oa.md
│   │   │       ├── oauth.md
│   │   │       └── server.md
│   │   ├── 5.x/
│   │   │   ├── basic-services/
│   │   │   │   ├── content_security.md
│   │   │   │   ├── jssdk.md
│   │   │   │   ├── media.md
│   │   │   │   ├── qrcode.md
│   │   │   │   └── url.md
│   │   │   ├── contributing.md
│   │   │   ├── customize/
│   │   │   │   ├── access_token.md
│   │   │   │   ├── cache.md
│   │   │   │   └── replace-service.md
│   │   │   ├── index.md
│   │   │   ├── installation.md
│   │   │   ├── integration.md
│   │   │   ├── micro-merchant/
│   │   │   │   ├── certficates.md
│   │   │   │   ├── index.md
│   │   │   │   ├── material.md
│   │   │   │   ├── media.md
│   │   │   │   ├── merchant-config.md
│   │   │   │   ├── submit-application.md
│   │   │   │   ├── upgrade.md
│   │   │   │   └── withdraw.md
│   │   │   ├── mini-program/
│   │   │   │   ├── activity_message.md
│   │   │   │   ├── app_code.md
│   │   │   │   ├── auth.md
│   │   │   │   ├── business.md
│   │   │   │   ├── customer_service.md
│   │   │   │   ├── data_cube.md
│   │   │   │   ├── decrypt.md
│   │   │   │   ├── express.md
│   │   │   │   ├── index.md
│   │   │   │   ├── live.md
│   │   │   │   ├── mall.md
│   │   │   │   ├── nearby_poi.md
│   │   │   │   ├── ocr.md
│   │   │   │   ├── phone_number.md
│   │   │   │   ├── plugin.md
│   │   │   │   ├── realtime_log.md
│   │   │   │   ├── risk_control.md
│   │   │   │   ├── safety_control.md
│   │   │   │   ├── search.md
│   │   │   │   ├── shipping.md
│   │   │   │   ├── short_link.md
│   │   │   │   ├── soter.md
│   │   │   │   ├── subscribe_message.md
│   │   │   │   ├── template_message.md
│   │   │   │   ├── union.md
│   │   │   │   ├── url_link.md
│   │   │   │   └── url_scheme.md
│   │   │   ├── miscellaneous.md
│   │   │   ├── official-account/
│   │   │   │   ├── accounts.md
│   │   │   │   ├── base.md
│   │   │   │   ├── broadcasting.md
│   │   │   │   ├── card.md
│   │   │   │   ├── comment.md
│   │   │   │   ├── configuration.md
│   │   │   │   ├── customer_service.md
│   │   │   │   ├── data_cube.md
│   │   │   │   ├── draft.md
│   │   │   │   ├── events.md
│   │   │   │   ├── goods.md
│   │   │   │   ├── index.md
│   │   │   │   ├── material.md
│   │   │   │   ├── menu.md
│   │   │   │   ├── message-transfer.md
│   │   │   │   ├── messages.md
│   │   │   │   ├── oauth.md
│   │   │   │   ├── poi.md
│   │   │   │   ├── reply.md
│   │   │   │   ├── semantic.md
│   │   │   │   ├── server.md
│   │   │   │   ├── shake-around.md
│   │   │   │   ├── store.md
│   │   │   │   ├── template_message.md
│   │   │   │   ├── tutorial.md
│   │   │   │   ├── user-tag.md
│   │   │   │   └── user.md
│   │   │   ├── open-platform/
│   │   │   │   ├── authorizer-delegate.md
│   │   │   │   ├── index.md
│   │   │   │   └── server.md
│   │   │   ├── open-work/
│   │   │   │   ├── index.md
│   │   │   │   ├── provider.md
│   │   │   │   ├── server.md
│   │   │   │   ├── service.md
│   │   │   │   └── work.md
│   │   │   ├── overview.md
│   │   │   ├── payment/
│   │   │   │   ├── bill.md
│   │   │   │   ├── contract.md
│   │   │   │   ├── index.md
│   │   │   │   ├── jssdk.md
│   │   │   │   ├── micropay.md
│   │   │   │   ├── notify.md
│   │   │   │   ├── order.md
│   │   │   │   ├── profit-sharing.md
│   │   │   │   ├── redpack.md
│   │   │   │   ├── refund.md
│   │   │   │   ├── reverse.md
│   │   │   │   ├── scan-pay.md
│   │   │   │   ├── security.md
│   │   │   │   └── transfer.md
│   │   │   ├── sidebar.js
│   │   │   ├── troubleshooting.md
│   │   │   └── wework/
│   │   │       ├── agents.md
│   │   │       ├── calendar.md
│   │   │       ├── chat.md
│   │   │       ├── contacts.md
│   │   │       ├── corp-group.md
│   │   │       ├── external-contact.md
│   │   │       ├── group-robot.md
│   │   │       ├── group-welcome-template.md
│   │   │       ├── index.md
│   │   │       ├── intercept.md
│   │   │       ├── invoice.md
│   │   │       ├── jssdk.md
│   │   │       ├── kf.md
│   │   │       ├── media.md
│   │   │       ├── menu.md
│   │   │       ├── message.md
│   │   │       ├── mini-program.md
│   │   │       ├── mobile.md
│   │   │       ├── msg-audit.md
│   │   │       ├── oa.md
│   │   │       ├── oauth.md
│   │   │       ├── product.md
│   │   │       ├── server.md
│   │   │       ├── to-account.md
│   │   │       └── wedrive.md
│   │   ├── 6.x/
│   │   │   ├── cache.md
│   │   │   ├── client.md
│   │   │   ├── contributing.md
│   │   │   ├── index.md
│   │   │   ├── introduction.md
│   │   │   ├── logging.md
│   │   │   ├── mini-app/
│   │   │   │   ├── config.md
│   │   │   │   ├── examples.md
│   │   │   │   ├── index.md
│   │   │   │   ├── server.md
│   │   │   │   └── utils.md
│   │   │   ├── oauth.md
│   │   │   ├── official-account/
│   │   │   │   ├── config.md
│   │   │   │   ├── examples.md
│   │   │   │   ├── index.md
│   │   │   │   ├── message.md
│   │   │   │   ├── server.md
│   │   │   │   └── utils.md
│   │   │   ├── open-platform/
│   │   │   │   ├── examples.md
│   │   │   │   ├── index.md
│   │   │   │   └── server.md
│   │   │   ├── open-work/
│   │   │   │   ├── examples.md
│   │   │   │   ├── index.md
│   │   │   │   ├── oauth.md
│   │   │   │   └── server.md
│   │   │   ├── pay/
│   │   │   │   ├── examples.md
│   │   │   │   ├── index.md
│   │   │   │   ├── media.md
│   │   │   │   ├── server.md
│   │   │   │   └── utils.md
│   │   │   ├── sidebar.js
│   │   │   ├── troubleshooting.md
│   │   │   └── work/
│   │   │       ├── examples.md
│   │   │       ├── index.md
│   │   │       ├── oauth.md
│   │   │       ├── server.md
│   │   │       └── utils.md
│   │   └── index.md
│   ├── tailwind.config.js
│   └── tsconfig.json
├── phpstan.neon
├── phpunit.xml
├── pint.json
├── src/
│   ├── Kernel/
│   │   ├── Config.php
│   │   ├── Contracts/
│   │   │   ├── AccessToken.php
│   │   │   ├── AccessTokenAwareHttpClient.php
│   │   │   ├── Aes.php
│   │   │   ├── Arrayable.php
│   │   │   ├── Config.php
│   │   │   ├── JsApiTicket.php
│   │   │   ├── Jsonable.php
│   │   │   ├── RefreshableAccessToken.php
│   │   │   ├── RefreshableJsApiTicket.php
│   │   │   └── Server.php
│   │   ├── Encryptor.php
│   │   ├── Exceptions/
│   │   │   ├── BadMethodCallException.php
│   │   │   ├── BadRequestException.php
│   │   │   ├── BadResponseException.php
│   │   │   ├── DecryptException.php
│   │   │   ├── Exception.php
│   │   │   ├── HttpException.php
│   │   │   ├── InvalidArgumentException.php
│   │   │   ├── InvalidConfigException.php
│   │   │   ├── RuntimeException.php
│   │   │   └── ServiceNotFoundException.php
│   │   ├── Form/
│   │   │   ├── File.php
│   │   │   └── Form.php
│   │   ├── HttpClient/
│   │   │   ├── AccessTokenAwareClient.php
│   │   │   ├── AccessTokenExpiredRetryStrategy.php
│   │   │   ├── HttpClientMethods.php
│   │   │   ├── RequestUtil.php
│   │   │   ├── RequestWithPresets.php
│   │   │   ├── Response.php
│   │   │   ├── RetryableClient.php
│   │   │   └── ScopingHttpClient.php
│   │   ├── Message.php
│   │   ├── ServerResponse.php
│   │   ├── Support/
│   │   │   ├── AesCbc.php
│   │   │   ├── AesEcb.php
│   │   │   ├── AesGcm.php
│   │   │   ├── Arr.php
│   │   │   ├── MessageParser.php
│   │   │   ├── Pkcs7.php
│   │   │   ├── PrivateKey.php
│   │   │   ├── PublicKey.php
│   │   │   ├── Str.php
│   │   │   ├── UserAgent.php
│   │   │   └── Xml.php
│   │   └── Traits/
│   │       ├── DecryptJsonMessage.php
│   │       ├── DecryptMessage.php
│   │       ├── DecryptXmlMessage.php
│   │       ├── HasAttributes.php
│   │       ├── InteractWithCache.php
│   │       ├── InteractWithClient.php
│   │       ├── InteractWithConfig.php
│   │       ├── InteractWithHandlers.php
│   │       ├── InteractWithHttpClient.php
│   │       ├── InteractWithServerRequest.php
│   │       ├── MockableHttpClient.php
│   │       ├── RespondJsonMessage.php
│   │       └── RespondXmlMessage.php
│   ├── MiniApp/
│   │   ├── AccessToken.php
│   │   ├── Account.php
│   │   ├── Application.php
│   │   ├── Contracts/
│   │   │   ├── Account.php
│   │   │   └── Application.php
│   │   ├── Decryptor.php
│   │   ├── Server.php
│   │   └── Utils.php
│   ├── OfficialAccount/
│   │   ├── AccessToken.php
│   │   ├── Account.php
│   │   ├── Application.php
│   │   ├── Config.php
│   │   ├── Contracts/
│   │   │   ├── Account.php
│   │   │   └── Application.php
│   │   ├── JsApiTicket.php
│   │   ├── Message.php
│   │   ├── Server.php
│   │   └── Utils.php
│   ├── OpenPlatform/
│   │   ├── Account.php
│   │   ├── Application.php
│   │   ├── Authorization.php
│   │   ├── AuthorizerAccessToken.php
│   │   ├── ComponentAccessToken.php
│   │   ├── Config.php
│   │   ├── Contracts/
│   │   │   ├── Account.php
│   │   │   ├── Application.php
│   │   │   └── VerifyTicket.php
│   │   ├── Message.php
│   │   ├── Server.php
│   │   └── VerifyTicket.php
│   ├── OpenWork/
│   │   ├── Account.php
│   │   ├── Application.php
│   │   ├── Authorization.php
│   │   ├── AuthorizerAccessToken.php
│   │   ├── Config.php
│   │   ├── Contracts/
│   │   │   ├── Account.php
│   │   │   ├── Application.php
│   │   │   └── SuiteTicket.php
│   │   ├── Encryptor.php
│   │   ├── JsApiTicket.php
│   │   ├── Message.php
│   │   ├── ProviderAccessToken.php
│   │   ├── Server.php
│   │   ├── SuiteAccessToken.php
│   │   ├── SuiteEncryptor.php
│   │   └── SuiteTicket.php
│   ├── Pay/
│   │   ├── Application.php
│   │   ├── Client.php
│   │   ├── Config.php
│   │   ├── Contracts/
│   │   │   ├── Application.php
│   │   │   ├── Merchant.php
│   │   │   ├── ResponseValidator.php
│   │   │   └── Validator.php
│   │   ├── Exceptions/
│   │   │   ├── EncryptionFailureException.php
│   │   │   └── InvalidSignatureException.php
│   │   ├── LegacySignature.php
│   │   ├── Merchant.php
│   │   ├── Message.php
│   │   ├── ResponseValidator.php
│   │   ├── Server.php
│   │   ├── Signature.php
│   │   ├── URLSchemeBuilder.php
│   │   ├── Utils.php
│   │   └── Validator.php
│   └── Work/
│       ├── AccessToken.php
│       ├── Account.php
│       ├── Application.php
│       ├── Config.php
│       ├── Contracts/
│       │   ├── Account.php
│       │   └── Application.php
│       ├── Encryptor.php
│       ├── JsApiTicket.php
│       ├── Message.php
│       ├── Server.php
│       └── Utils.php
└── tests/
    ├── Kernel/
    │   ├── EncryptorTest.php
    │   ├── HttpClient/
    │   │   ├── AccessTokenAwareClientTest.php
    │   │   ├── AccessTokenExpiredRetryStrategyTest.php
    │   │   ├── HttpClientMethodsTest.php
    │   │   ├── RequestUtilTest.php
    │   │   ├── RequestWithPresetsTest.php
    │   │   ├── ResponseTest.php
    │   │   └── RetryableClientTest.php
    │   ├── MessageTest.php
    │   ├── ServerResponseTest.php
    │   ├── Support/
    │   │   ├── AesCbcTest.php
    │   │   ├── AesEcbTest.php
    │   │   ├── AesGcmTest.php
    │   │   ├── MessageParserTest.php
    │   │   ├── PrivateKeyTest.php
    │   │   ├── PublicKeyTest.php
    │   │   └── UserAgentTest.php
    │   └── Traits/
    │       ├── DecryptJsonMessageTest.php
    │       ├── DecryptXmlMessageTest.php
    │       ├── InteractWithCacheTest.php
    │       ├── InteractWithClientTest.php
    │       ├── InteractWithConfigTest.php
    │       ├── InteractWithHandlersTest.php
    │       ├── InteractWithHttpClientTest.php
    │       ├── InteractWithServerRequestTest.php
    │       └── RespondXmlMessageTest.php
    ├── MiniApp/
    │   ├── AccessTokenTest.php
    │   ├── ApplicationTest.php
    │   ├── DecryptorTest.php
    │   └── UtilsTest.php
    ├── OfficialAccount/
    │   ├── AccessTokenTest.php
    │   ├── AccountTest.php
    │   ├── ApplicationTest.php
    │   ├── ConfigTest.php
    │   ├── JsApiTicketTest.php
    │   ├── ServerTest.php
    │   └── UtilsTest.php
    ├── OpenPlatform/
    │   ├── AccountTest.php
    │   ├── ApplicationTest.php
    │   ├── AuthorizationTest.php
    │   ├── AuthorizerAccessTokenTest.php
    │   ├── ComponentAccessTokenTest.php
    │   └── ServerTest.php
    ├── Pay/
    │   ├── ApplicationTest.php
    │   ├── ClientTest.php
    │   ├── MerchantTest.php
    │   ├── ServerTest.php
    │   └── UtilsTest.php
    ├── TestCase.php
    ├── Work/
    │   ├── AccessTokenTest.php
    │   ├── AccountTest.php
    │   ├── ApplicationTest.php
    │   ├── ConfigTest.php
    │   ├── JsApiTicketTest.php
    │   ├── ServerTest.php
    │   └── UtilsTest.php
    ├── bootstrap.php
    └── fixtures/
        ├── cert.p12
        ├── cert.pem
        ├── files/
        │   ├── demo_cert.pem
        │   ├── demo_key.pem
        │   ├── empty.file
        │   └── pay_demo.json
        └── private.key

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
* text=auto

/build export-ignore
/tests export-ignore
/docs export-ignore
/.github export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.scrutinizer.yml export-ignore
phpstan.neon export-ignore
phpunit.php export-ignore
phpunit.xml export-ignore
phpunit.xml.dist export-ignore
pint.json export-ignore
.php-cs-fixer.dist.php  export-ignore


================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: [overtrue]


================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
## 我用的环境

* PHP 版本:
* overtrue/wechat 版本:
* 是否使用了框架?框架名称:

## 问题及现象

<!--

描述你的问题现象,报错**贴截图**粘贴或者贴具体信息,提供**必要的代码段**

如果你不提供相关的代码,我不会做任何应答,直接 close,感谢!

请正确使用 Markdown: https://guides.github.com/features/mastering-markdown

-->


<!-- Love wechat? Please consider supporting our collective:
👉  https://opencollective.com/wechat/donate -->


================================================
FILE: .github/workflows/deploy.yml
================================================
# This is a basic workflow to help you get started with Actions

name: Deploy

# Controls when the workflow will run
on:
  # Triggers the workflow on push event but only for the 6.x branch(required the secrets environment)
  push:
    branches: [ 6.x ]
    tags-ignore:
      - '*'
  pull_request:
    branches: [ 6.x ]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  build:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest
    
    env:
      domain: "https://easywechat.com/"
      local_dir: ./docs/.vitepress/dist/
      remote_dir: /
      thread: 10
      region: ap-guangzhou
      bucket: "easywechat-1252049834"
      ignore: "./.git*,*.DS_Store"
      COS_SECRET_ID: ${{ secrets.COS_SECRET_ID }}

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
        with:
          version: 10
          run_install: false
      - uses: actions/setup-node@v4
        with:
          node-version: '>=22.12.0'
          cache: 'pnpm'
          cache-dependency-path: 'docs/pnpm-lock.yaml'
      - name: Install dependencies
        run: |
          cd docs
          pnpm install
      - name: Build
        run: |
          cd docs
          patch -p1 -i .vitepress/vitepress+1.6.3.patch
          pnpm run build
      - name: Install coscmd
        if: env.COS_SECRET_ID
        run: |
          python -m pip install --upgrade pip
          python -m pip install setuptools
          python -m pip install https://github.com/tencentyun/coscmd/archive/refs/heads/master.zip
      - name: Upload to cos
        if: env.COS_SECRET_ID
        run: |
          coscmd config -a "${{secrets.COS_SECRET_ID}}" -s "${{secrets.COS_SECRET_KEY}}" -b $bucket -r $region -m $thread 
          coscmd upload -r -s $local_dir $remote_dir --ignore "$ignore"
      - name: Refresh CDN
        if: env.COS_SECRET_ID
        env:
          COS_SECRET_KEY: ${{secrets.COS_SECRET_KEY}}
        run: ./docs/purge-caches


================================================
FILE: .github/workflows/lint.yml
================================================
name: Lint
on: [push, pull_request]

jobs:
  phpstan:
    name: PHPStan
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@master
    - name: Install Dependencies
      run: composer install --no-progress
    - name: Run PHPStan
      run: ./vendor/bin/phpstan analyse --no-progress

  php_cs_fixer:
    name: PHP-CS-Fxier
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@master
    - name: Install Dependencies
      run: composer install --no-progress
    - name: Run PHP-CS-Fxier
      run: composer check-style


================================================
FILE: .github/workflows/test.yml
================================================
name: Test
on: [push, pull_request]

jobs:
  phpunit:
    name: PHP-${{ matrix.php_version }}-${{ matrix.perfer }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        php_version:
          - 8.0
          - 8.1
          - 8.2
          - 8.3
          - 8.4
          - 8.5
        perfer:
          - stable
          - lowest
    steps:
    - uses: actions/checkout@master
    - name: Install Dependencies
      run: composer update --prefer-dist --no-interaction --no-suggest --prefer-${{ matrix.perfer }}
    - name: Run PHPUnit
      run: ./vendor/bin/phpunit


================================================
FILE: .gitignore
================================================
*.DS_Store
/vendor
sftp-config.json
/*.php
!.php-cs-fixer.dist.php
/.idea
/coverage
/.split
/composer.lock
.php-cs-fixer.cache
.phpunit.result.cache
cghooks.lock
docs/dist/
docs/.vuepress/.temp/
docs/.vuepress/.cache/
.vercel
.phplint-cache


================================================
FILE: CONTRIBUTING.md
================================================
# Contribute

## Introduction

First, thank you for considering contributing to wechat! It's people like you that make the open source community such a great community! 😊

We welcome any type of contribution, not only code. You can help with 
- **QA**: file bug reports, the more details you can give the better (e.g. screenshots with the console open)
- **Marketing**: writing blog posts, howto's, printing stickers, ...
- **Community**: presenting the project at meetups, organizing a dedicated meetup for the local community, ...
- **Code**: take a look at the [open issues](issues). Even if you can't write code, commenting on them, showing that you care about a given issue matters. It helps us triage them.
- **Money**: we welcome financial contributions in full transparency on our [open collective](https://opencollective.com/wechat).

## Your First Contribution

Working on your first Pull Request? You can learn how from this *free* series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github).

## Submitting code

Any code change should be submitted as a pull request. The description should explain what the code does and give steps to execute it. The pull request should also contain tests.

## Code review process

The bigger the pull request, the longer it will take to review and merge. Try to break down large pull requests in smaller chunks that are easier to review and merge.
It is also always helpful to have some context for your pull request. What was the purpose? Why does it matter to you?

## Financial contributions

We also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/wechat).
Anyone can file an expense. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed.

## Questions

If you have any questions, create an [issue](issue) (protip: do a quick search first to see if someone else didn't ask the same question before!).
You can also reach us at hello@wechat.opencollective.com.

## Credits

### Contributors

Thank you to all the people who have already contributed to wechat!
<a href="graphs/contributors"><img src="https://opencollective.com/wechat/contributors.svg?width=890" /></a>


### Backers

Thank you to all our backers! [[Become a backer](https://opencollective.com/wechat#backer)]

<a href="https://opencollective.com/wechat#backers" target="_blank"><img src="https://opencollective.com/wechat/backers.svg?width=890"></a>


### Sponsors

Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/wechat#sponsor))

<a href="https://opencollective.com/wechat/sponsor/0/website" target="_blank"><img src="https://opencollective.com/wechat/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/wechat/sponsor/1/website" target="_blank"><img src="https://opencollective.com/wechat/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/wechat/sponsor/2/website" target="_blank"><img src="https://opencollective.com/wechat/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/wechat/sponsor/3/website" target="_blank"><img src="https://opencollective.com/wechat/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/wechat/sponsor/4/website" target="_blank"><img src="https://opencollective.com/wechat/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/wechat/sponsor/5/website" target="_blank"><img src="https://opencollective.com/wechat/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/wechat/sponsor/6/website" target="_blank"><img src="https://opencollective.com/wechat/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/wechat/sponsor/7/website" target="_blank"><img src="https://opencollective.com/wechat/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/wechat/sponsor/8/website" target="_blank"><img src="https://opencollective.com/wechat/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/wechat/sponsor/9/website" target="_blank"><img src="https://opencollective.com/wechat/sponsor/9/avatar.svg"></a>

<!-- This `CONTRIBUTING.md` is based on @nayafia's template https://github.com/nayafia/contributing-template -->

================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) overtrue <i@overtrue.me>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.



================================================
FILE: README.md
================================================
# [EasyWeChat](https://easywechat.com)

📦 一个 PHP 微信开发 SDK,开源 SaaS 平台提供商 [微擎](https://www.w7.cc/) 旗下开源产品。

[![Test Status](https://github.com/w7corp/easywechat/workflows/Test/badge.svg)](https://github.com/w7corp/easywechat/actions)
[![Lint Status](https://github.com/w7corp/easywechat/workflows/Lint/badge.svg)](https://github.com/w7corp/easywechat/actions)
[![Latest Stable Version](https://poser.pugx.org/w7corp/easywechat/v/stable.svg)](https://packagist.org/packages/w7corp/easywechat)
[![Latest Unstable Version](https://poser.pugx.org/w7corp/easywechat/v/unstable.svg)](https://packagist.org/packages/w7corp/easywechat)
[![Total Downloads](https://poser.pugx.org/w7corp/easywechat/downloads)](https://packagist.org/packages/w7corp/easywechat)
[![License](https://poser.pugx.org/w7corp/easywechat/license)](https://packagist.org/packages/w7corp/easywechat)

## 环境需求

- PHP >= 8.0.2
- [Composer](https://getcomposer.org/) >= 2.0

## 安装

```bash
composer require w7corp/easywechat
```

## 使用示例

基本使用(以公众号服务端为例):

```php
<?php

use EasyWeChat\OfficialAccount\Application;

$config = [
    'app_id' => 'wx3cf0f39249eb0exxx',
    'secret' => 'f1c242f4f28f735d4687abb469072xxx',
    'aes_key' => 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG',
    'token' => 'easywechat',
];

$app = new Application($config);

$server = $app->getServer();

$server->with(fn() => "您好!EasyWeChat!");

$response = $server->serve();
```

## 文档和链接

[官网](https://easywechat.com) · [讨论](https://github.com/w7corp/easywechat/discussions) · [更新策略](https://github.com/w7corp/easywechat/security/policy)

## :heart: 支持我

如果你喜欢我的项目并想支持它,[点击这里 :heart:](https://github.com/sponsors/overtrue)


## 可爱的贡献者们

<a href="https://github.com/w7corp/easywechat/graphs/contributors"><img src="https://opencollective.com/wechat/contributors.svg?width=890" /></a>

## License

MIT


================================================
FILE: SECURITY.md
================================================
# 更新策略 Security Policy

## 支持的版本 Supported Versions

| Version | 状态          |
| ------- | ------------------ |
| 6.x   | ✅ 维护中|
| 5.x   | 🚨 仅安全修复,不建议旧项目升级至 6.x |
| 4.x  | ❌ 不再维护,建议升级至 5.x(仅 OAuth 方法有变化) |
| 3.x   | ❌ 不再维护               |
| 2.x   | ❌ 不再维护               |
| 1.x   | ❌ 不再维护               |

## 漏洞报告 Reporting a Vulnerability

如果你发现了安全漏洞,请发邮件给我 `anzhengchao@gmail.com`,或者提 issue。


================================================
FILE: composer.json
================================================
{
  "name": "w7corp/easywechat",
  "description": "微信SDK",
  "keywords": [
    "easywechat",
    "wechat",
    "weixin",
    "weixin-sdk",
    "sdk"
  ],
  "license": "MIT",
  "authors": [
    {
      "name": "overtrue",
      "email": "anzhengchao@gmail.com"
    }
  ],
  "require": {
    "php": ">=8.0.2",
    "ext-fileinfo": "*",
    "ext-openssl": "*",
    "ext-simplexml": "*",
    "ext-libxml": "*",
    "ext-curl": "*",
    "nyholm/psr7": "^1.5",
    "nyholm/psr7-server": "^1.0",
    "overtrue/socialite": "^3.5.4|^4.0.1",
    "psr/simple-cache": "^1.0|^2.0|^3.0",
    "psr/http-client": "^1.0",
    "symfony/cache": "^5.4|^6.0|^7.0",
    "symfony/http-foundation": "^5.4|^6.0|^7.0",
    "symfony/psr-http-message-bridge": "^2.1.2|^6.4.0|^7.1",
    "symfony/http-client": "^5.4|^6.0|^7.0",
    "symfony/mime": "^5.4|^6.0|^7.0",
    "symfony/polyfill-php81": "^1.25",
    "thenorthmemory/xml": "^1.0"
  },
  "require-dev": {
    "mikey179/vfsstream": "^1.6",
    "mockery/mockery": "^1.4.4",
    "phpstan/phpstan": "^1.0 | ^2",
    "phpunit/phpunit": "^9.5",
    "symfony/var-dumper": "^5.2|^6|^7",
    "jetbrains/phpstorm-attributes": "^1.0",
    "laravel/pint": "^1.2"
  },
  "autoload": {
    "psr-4": {
      "EasyWeChat\\": "src/"
    }
  },
  "autoload-dev": {
    "psr-4": {
      "EasyWeChat\\Tests\\": "tests/"
    }
  },
  "scripts": {
    "post-merge": "composer install",
    "phpstan": "phpstan analyse --memory-limit=-1",
    "check-style": "vendor/bin/pint --test",
    "fix-style": "vendor/bin/pint",
    "test": "phpunit --colors"
  },
  "conflict": {
    "overtrue/wechat": "*"
  },
  "config": {
    "allow-plugins": {
      "composer/package-versions-deprecated": true,
      "php-http/discovery": true
    }
  }
}


================================================
FILE: docs/.editorconfig
================================================
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false


================================================
FILE: docs/.gitignore
================================================
node_modules/
package-lock.json
yarn-error.log
.vitepress/dist/
.vitepress/cache/


================================================
FILE: docs/.npmrc
================================================
auto-install-peers=true


================================================
FILE: docs/.prettierrc
================================================
{
  "semi": false,
  "singleQuote": true,
  "trailingComma": "none",
  "printWidth": 75
}


================================================
FILE: docs/.vitepress/config.ts
================================================
import path from 'path'
import versions from './versions'

const latest = versions[0]

const nav = [
  {
    text: '首页',
    link: '/'
  },
  {
    text: '文档',
    activeMatch: `^/([0-9]\.x)/`,
    items: versions.map((version) => ({
      text: version,
      link: `/${version}/`
    }))
  },
  {
    text: '视频',
    link: 'https://wiki.w7.cc/college/collectiondetail/3'
  },
  {
    text: '讨论',
    link: 'https://github.com/w7corp/easywechat/discussions'
  },
  {
    text: '赞助',
    link: 'https://github.com/sponsors/overtrue'
  },
  {
    text: '蓝P站',
    link: 'https://wechatpay.im/'
  },
]

export const sidebar = versions.reduce(
  (sidebars, version) => ({
    ...sidebars,
    [`/${version}/`]: require(path.join(
      __dirname,
      `../src/${version}/sidebar`
    ))
  }),
  {}
)

export default {
  lang: 'zh-CN',
  title: 'EasyWeChat',
  description: '一个 PHP 微信开发 SDK',
  srcDir: 'src',
  srcExclude: [],
  scrollOffset: 'header',
  metaChunk: true,

  head: [
    ['link', { rel: 'icon', href: '/favicon.svg' }],
    ['meta', { name: 'twitter:site', content: '@easywechat' }],
    ['meta', { name: 'twitter:card', content: 'summary' }],
    [
      'meta',
      {
        name: 'twitter:image',
        content: 'https://easywechat.com/logo.svg'
      }
    ],
    // google analytics, without tracing dev
    ...(process?.argv?.[2] === 'dev' ? [] : [
      [
        'script',
        { async: '', src: 'https://www.googletagmanager.com/gtag/js?id=G-ZVHYZEP1SR' }
      ],
      [
        'script',
        {},
        `window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);}
        gtag('js', new Date());
        gtag('config', 'G-ZVHYZEP1SR');`
      ],
    ]),
    // end google analytics
  ],

  markdown: {
    codeCopyButtonTitle: '复制',
    lineNumbers: true,
  },

  themeConfig: {
    nav,
    sidebar,

    logo: '/logo-icon.svg',

    algolia: {
      placeholder: '搜索文档',
      translations: {
        button: {
          buttonText: '搜索文档',
          buttonAriaLabel: '搜索文档'
        },
        modal: {
          searchBox: {
            resetButtonTitle: '清除查询条件',
            resetButtonAriaLabel: '清除查询条件',
            cancelButtonText: '取消',
            cancelButtonAriaLabel: '取消'
          },
          startScreen: {
            recentSearchesTitle: '搜索历史',
            noRecentSearchesText: '没有搜索历史',
            saveRecentSearchButtonTitle: '保存至搜索历史',
            removeRecentSearchButtonTitle: '从搜索历史中移除',
            favoriteSearchesTitle: '收藏',
            removeFavoriteSearchButtonTitle: '从收藏中移除'
          },
          errorScreen: {
            titleText: '无法获取结果',
            helpText: '你可能需要检查你的网络连接'
          },
          footer: {
            selectText: '选择',
            navigateText: '切换',
            closeText: '关闭',
            searchByText: '搜索提供者'
          },
          noResultsScreen: {
            noResultsText: '无法找到相关结果',
            suggestedQueryText: '你可以尝试查询',
            reportMissingResultsText: '你认为该查询应该有结果?',
            reportMissingResultsLinkText: '点击反馈'
          },
        },
      },
      indexName: 'easywechat',
      appId: 'X3KJL5SQXD',
      apiKey: '5c5ba71b35c48411f245bef4c695fc36'
      // searchParameters: {
      //   facetFilters: ['version:v3']
      // }
    },

    returnToTopLabel: '回到顶部',
    sidebarMenuLabel: '菜单',
    darkModeSwitchLabel: '主题模式',
    lightModeSwitchTitle: '浅色模式',
    darkModeSwitchTitle: '深色模式',

    outline: {
      level: [2, 3],
      label: '页面导航',
    },

    docFooter: {
      prev: '上一页',
      next: '下一页'
    },

    notFound: {
      title: '未找到',
      quote: '您所访问的页面未找到,或者已失效',
      linkLabel: '返回首页',
      linkText: '返回首页',
    },

    // carbonAds: {
    //   code: '',
    //   placement: ''
    // },

    socialLinks: [
      { icon: 'github', link: 'https://github.com/w7corp/easywechat' },
      { icon: 'twitter', link: 'https://twitter.com/overtrue666' },
      {
        icon: {
          svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0,0,256,256">
                <g transform="translate(-7.68,-7.68) scale(1.06,1.06)"><g fill="currentColor" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(4,4)"><path d="M46.846,7.021c-1.357,-0.069 -2.725,0.03 -4.063,0.315c-1.24,0.266 -2.061,1.506 -1.797,2.756c0.264,1.25 1.494,2.077 2.735,1.811c3.819,-0.817 7.979,0.345 10.782,3.465c2.793,3.13 3.545,7.441 2.344,11.182c-0.391,1.221 0.273,2.52 1.484,2.914c1.201,0.394 2.5,-0.285 2.891,-1.496c1.68,-5.266 0.664,-11.27 -3.281,-15.67c-2.96,-3.298 -7.013,-5.08 -11.095,-5.277zM26.142,13.951c-4.19,0.453 -10.04,3.74 -15.235,8.977c-5.655,5.708 -8.907,11.782 -8.907,17.008c0,10.001 12.706,16.064 25.158,16.064c16.319,0 27.189,-9.577 27.189,-17.166c0,-4.587 -3.819,-7.195 -7.266,-8.268c-0.85,-0.256 -1.455,-0.374 -1.016,-1.496c0.957,-2.431 1.113,-4.567 0.078,-6.063c-1.943,-2.805 -7.334,-2.658 -13.438,-0.079c0,0 -1.895,0.906 -1.406,-0.63c0.938,-3.041 0.762,-5.611 -0.703,-7.087c-1.036,-1.044 -2.55,-1.467 -4.454,-1.26zM46.455,15.211c-0.664,-0.03 -1.299,0.02 -1.953,0.157c-1.074,0.226 -1.797,1.358 -1.563,2.441c0.234,1.073 1.279,1.732 2.344,1.496c1.279,-0.276 2.735,0.138 3.672,1.181c0.938,1.043 1.182,2.451 0.781,3.701c-0.332,1.043 0.215,2.175 1.25,2.52c1.035,0.335 2.168,-0.217 2.5,-1.26c0.82,-2.559 0.283,-5.492 -1.641,-7.638c-1.434,-1.604 -3.397,-2.51 -5.39,-2.598zM28.486,28.518c8.301,0.295 14.981,4.488 15.548,10.237c0.645,6.575 -6.866,12.717 -16.798,13.701c-9.932,0.984 -18.575,-3.582 -19.22,-10.157c-0.645,-6.575 6.944,-12.717 16.876,-13.701c1.24,-0.129 2.412,-0.119 3.594,-0.08zM24.579,33.4c-3.594,0.345 -6.973,2.441 -8.516,5.591c-2.09,4.282 -0.088,9.075 4.688,10.63c4.951,1.604 10.782,-0.886 12.813,-5.512c2.002,-4.518 -0.498,-9.124 -5.391,-10.394c-1.181,-0.305 -2.392,-0.433 -3.594,-0.315zM26.845,38.913c0.156,0 0.244,0.02 0.391,0.079c0.606,0.226 0.879,0.896 0.547,1.496c-0.352,0.6 -1.113,0.876 -1.719,0.63c-0.596,-0.246 -0.801,-0.906 -0.469,-1.496c0.264,-0.444 0.781,-0.7 1.25,-0.709zM22.235,40.33c0.42,0.01 0.869,0.069 1.25,0.236c1.553,0.669 2.041,2.461 1.094,4.016c-0.957,1.545 -2.979,2.284 -4.531,1.575c-1.524,-0.699 -1.973,-2.51 -1.016,-4.016c0.713,-1.122 1.953,-1.831 3.203,-1.811z"></path></g></g></g>
                </svg>`
        },
        ariaLabel: 'Weibo',
        link: 'https://weibo.com/44294631'
      }
    ],

    editLink: {
      pattern:
        'https://github.com/w7corp/EasyWeChat/edit/6.x/docs/src/:path',
      text: '帮助我们改善此页面!'
    },

    license: {
      text: 'MIT License',
      link: 'https://opensource.org/licenses/MIT'
    },
    copyright: `Copyright © 2013-${new Date().getFullYear()} 微擎 <a class="ml-4" href="https://beian.miit.gov.cn/" target="_blank">皖ICP备19002904号-6</a>`
  },

  vite: {
    define: {
      __VUE_OPTIONS_API__: false
    },
    optimizeDeps: {
      include: ['gsap', 'dynamics.js'],
      exclude: []
    },
    // @ts-ignore
    ssr: {
      external: []
    },
    server: {
      host: true,
      fs: {
        // for when developing with locally linked theme
        allow: ['../..']
      }
    },
    json: {
      stringify: true
    }
  },

  vue: {
    reactivityTransform: true
  }
}


================================================
FILE: docs/.vitepress/theme/components/Banner.vue
================================================
<script setup>
/**
 * Adding a new banner:
 * 1. uncomment the banner slot in ../index.ts
 * 2. uncomment and update BANNER_ID in ../../inlined-scripts/restorePreferences.ts
 * 3. update --vt-banner-height if necessary
 */

let open = $ref(true)

/**
 * Call this if the banner is dismissible
 */
function dismiss() {
  open = false
  document.documentElement.classList.add('banner-dismissed')
  localStorage.setItem(`vue-docs-banner-${__VUE_BANNER_ID__}`, 'true')
}
</script>

<template>
  <div class="banner" v-if="open"></div>
</template>

<style>
html:not(.banner-dismissed) {
  --vt-banner-height: 24px;
}
</style>

<style scoped>
.banner {
  position: fixed;
  z-index: var(--vp-z-index-banner);
  box-sizing: border-box;
  top: 0;
  left: 0;
  right: 0;
  height: var(--vt-banner-height);
  line-height: var(--vt-banner-height);
  text-align: center;
  font-size: 12px;
  font-weight: 600;
  color: #fff;
  background-color: var(--vt-c-green);
}

.banner-dismissed .banner {
  display: none;
}

a {
  text-decoration: underline;
}
</style>


================================================
FILE: docs/.vitepress/theme/components/Footer.vue
================================================
<script lang="ts" setup>
import { useData } from 'vitepress';

const { theme } = useData()
</script>

<template>
  <div class="text-center border-t dark:border-black leading-loose py-6 text-xs">
    <p v-if="theme.license" class="license">
      Released under the
      <a class="link" :href="theme.license.link" no-icon>
        {{ theme.license.text }} </a>.
    </p>
    <p v-if="theme.copyright" class="copyright" v-html="theme.copyright"></p>
  </div>
</template>


================================================
FILE: docs/.vitepress/theme/components/SponsorsAside.vue
================================================
<script setup lang="ts">
import SponsorsGroup from './SponsorsGroup.vue'
import { useData } from 'vitepress'
const { frontmatter } = useData()
</script>

<template>
  <div v-if="frontmatter.sponsors !== false">
    <a class="sponsors-aside-text">Sponsors</a>
    <SponsorsGroup tier="special" />
    <SponsorsGroup tier="platinum" />
  </div>
</template>

<style>
a.sponsors-aside-text {
  color: var(--vt-c-text-3);
  display: block;
  margin: 3em 0 1em;
  font-weight: 700;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
}
</style>


================================================
FILE: docs/.vitepress/theme/components/SponsorsGroup.vue
================================================
<script setup lang="ts">
import { defineProps, onMounted, onUnmounted, ref } from 'vue';
interface Sponsor {
  url: string
  img: string
  name: string
}

interface SponsorData {
  special: Sponsor[]
  platinum: Sponsor[]
  platinum_china: Sponsor[]
  gold: Sponsor[]
  silver: Sponsor[]
  bronze: Sponsor[]
}

// shared data across instances so we load only once
let data = ref<SponsorData>()
let pending = false

const { tier, placement = 'aside' } = defineProps<{
  tier: keyof SponsorData
  placement?: 'aside' | 'page' | 'landing'
}>()

let container = ref<HTMLElement>()
let visible = ref(false)

onMounted(async () => {
  // only render when entering view
  const observer = new IntersectionObserver(
    (entries) => {
      if (entries[0].isIntersecting) {
        visible = true
        observer.disconnect()
      }
    },
    { rootMargin: '0px 0px 300px 0px' }
  )
  observer.observe(container)
  onUnmounted(() => observer.disconnect())

  // load data
  if (!pending) {
    pending = true
    // data = await (await fetch(`${base}/data.json`)).json()
  }
})
</script>

<template>
  <div ref="container" class="sponsor-container" :class="[tier.startsWith('plat') ? 'platinum' : tier, placement]">
    <template v-if="data && visible">
      <a v-for="{ url, img, name } of data[tier]" class="sponsor-item" :href="url" target="_blank" rel="sponsored noopener">
        <picture v-if="img.endsWith('png')">
          <source type="image/avif" :srcset="`${base}/images/${img.replace(/\.png$/, '.avif')}`" />
          <img :src="`${base}/images/${img}`" :alt="name" />
        </picture>
        <img v-else :src="`${base}/images/${img}`" :alt="name" />
      </a>
    </template>
    <a v-if="placement !== 'page' && tier !== 'special'" href="https://github.com/sponsors/overtrue" class="sponsor-item action">Your logo</a>
  </div>
</template>

<style scoped>
.sponsor-container {
  --max-width: 100%;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--max-width), 1fr));
  column-gap: 4px;
}

.sponsor-container.platinum {
  --max-width: 240px;
}

.sponsor-container.gold {
  --max-width: 180px;
}

.sponsor-container.silver {
  --max-width: 140px;
}

.sponsor-item {
  margin: 2px 0;
  background-color: var(--vt-c-white-soft);
  display: flex;
  justify-content: space-around;
  align-items: center;
  border-radius: 2px;
  transition: background-color 0.2s ease;
  height: calc(var(--max-width) / 2 - 6px);
}

.sponsor-item.action {
  font-size: 11px;
  color: var(--vt-c-text-3);
}

.sponsor-item img {
  max-width: calc(var(--max-width) - 30px);
  max-height: calc(var(--max-width) / 2 - 20px);
}

.special .sponsor-item {
  height: 160px;
}

.special .sponsor-item img {
  max-width: 300px;
  max-height: 150px;
}

/* dark mode */
.dark .aside .sponsor-item,
.dark .landing .sponsor-item {
  background-color: var(--vt-c-bg-soft);
}

.aside .sponsor-item img,
.landing .sponsor-item img {
  transition: filter 0.2s ease;
}

.dark .aside .sponsor-item img,
.dark .landing .sponsor-item img {
  filter: grayscale(1) invert(1);
}

.dark .aside .sponsor-item:hover,
.dark .landing .sponsor-item:hover {
  color: var(--vt-c-indigo);
  background-color: var(--vt-c-white-mute);
}

.dark .sponsor-item:hover img {
  filter: none;
}

/* aside mode (on content pages) */
.sponsor-container.platinum.aside {
  --max-width: 110px;
  column-gap: 1px;
}

.aside .sponsor-item {
  margin: 1px 0;
}

.aside .special .sponsor-item {
  width: 100%;
  height: 60px;
}

.aside .special .sponsor-item img {
  width: 120px;
}

.aside .platinum .sponsor-item {
  width: 111px;
  height: 50px;
}

.aside .platinum .sponsor-item img {
  max-width: 88px;
}

/* narrow, aside will be hidden under this state so it's mutually exclusive */
@media (max-width: 720px) {
  .sponsor-container.platinum {
    --max-width: 180px;
  }

  .sponsor-container.gold {
    --max-width: 140px;
  }

  .sponsor-container.silver {
    --max-width: 120px;
  }
}

@media (max-width: 480px) {
  .sponsor-container.platinum {
    --max-width: 150px;
  }

  .sponsor-container.gold {
    --max-width: 120px;
  }

  .sponsor-container.silver {
    --max-width: 100px;
  }
}
</style>


================================================
FILE: docs/.vitepress/theme/components/VersionTag.vue
================================================
<template>
  <sup
    class="bg-green-500 text-xs text-white px-2 py-1 rounded-lg align-top rounded-bl-none"
    title="该特性需要更新到此版本可用"
    ><slot
  /></sup>
</template>


================================================
FILE: docs/.vitepress/theme/index.ts
================================================
import './styles/index.css'
import { h, App } from 'vue'
import SponsorsAside from './components/SponsorsAside.vue'
import VersionTag from './components/VersionTag.vue'
import Footer from './components/Footer.vue'
import DefaultTheme from 'vitepress/theme'

export default Object.assign({
  ...DefaultTheme,
  Layout: () => {
    // @ts-ignore
    return h(DefaultTheme.Layout, null, {
      // banner: () => h(Banner),
      'aside-mid': () => h(SponsorsAside),
      'layout-bottom': () => h(Footer)
    })
  },
  enhanceApp({ app }: { app: App }) {
    app.component('version-tag', VersionTag)
  }
})


================================================
FILE: docs/.vitepress/theme/styles/badges.css
================================================
.vt-badge.wip:before {
  content: 'WIP';
}

.vt-badge.ts {
  background-color: #3178c6;
}
.vt-badge.ts:before {
  content: 'TS';
}

.vt-badge.dev-only,
.vt-badge.experimental {
  color: var(--vt-c-text-light-1);
  background-color: var(--vt-c-yellow);
}

.vt-badge.dev-only:before {
  content: 'Dev only';
}

.vt-badge.experimental:before {
  content: 'Experimental';
}

.vt-badge[data-text]:before {
  content: attr(data-text);
}


================================================
FILE: docs/.vitepress/theme/styles/index.css
================================================
@import './layout.css';
@import './pages.css';
@import './badges.css';
@import './options-boxes.css';
@import './inline-demo.css';
@import './utilities.css';
@import './style-guide.css';

@tailwind base;
@tailwind components;
@tailwind utilities;


================================================
FILE: docs/.vitepress/theme/styles/inline-demo.css
================================================
.vt-doc a[href^="https://sfc.vuejs.org"]:before
{
  content: '▶';
  width: 20px;
  height: 20px;
  display: inline-block;
  border-radius: 10px;
  vertical-align: middle;
  position: relative;
  top: -2px;
  color: var(--vt-c-green);
  border: 2px solid var(--vt-c-green);
  margin-right: 8px;
  margin-left: 4px;
  line-height: 15px;
  padding-left: 4.5px;
  font-size: 11px;
}

.demo {
  padding: 22px 24px;
  border-radius: 8px;
  box-shadow: var(--vt-shadow-2);
  margin-bottom: 1.2em;
  transition: background-color 0.5s ease;
}

.dark .demo {
  background-color: var(--vt-c-bg-soft);
}

.demo p {
  margin: 0;
}

.demo button {
  background-color: var(--vt-c-bg-mute);
  transition: background-color 0.5s;
  padding: 5px 12px;
  border: 1px solid var(--vt-c-divider);
  border-radius: 8px;
  font-size: 0.9em;
  font-weight: 600;
}

.demo button + button {
  margin-left: 1em;
}

.demo input,
.demo textarea,
.demo select {
  border: 1px solid var(--vt-c-divider);
  border-radius: 4px;
  padding: 0.2em 0.6em;
  margin-top: 10px;
  background: transparent;
  transition: background-color 0.5s;
}

.dark .demo select {
  background: var(--vt-c-bg-soft);
}

.dark .demo select option {
  background: transparent;
}

.demo input:not([type]):focus,
.demo textarea:focus,
.demo select:focus {
  outline: 1px solid blue;
}

.demo select {
  /* this was set by normalize.css */
  -webkit-appearance: listbox;
}

.demo label {
  margin: 0 1em 0 0.4em;
}

.demo select[multiple] {
  width: 100px;
}

.demo h1 {
  margin: 10px 0 0;
}


================================================
FILE: docs/.vitepress/theme/styles/layout.css
================================================
.VPContent,
.VPContent .VPContentPage,
.VPContent .VPContentPage main,
.VPContent .VPContentPage main>div,
.VPContent .VPContentPage main>div>div {
  @apply flex-1 flex flex-col;
}

.dark .im-home {
  --im-gradient-p1: #8356dc;
  --im-gradient-p3: #5908a6;
  --im-gradient-p2: #044f1e;
  --im-gradient-p4: #49bcb7;
}

.im-home {
  --im-gradient-p1: #ecd8ff;
  --im-gradient-p2: #e8fca7;
  --im-gradient-p3: #dafbe1;
  --im-gradient-p4: #ffd8b5;
  background: linear-gradient(-40deg, var(--im-gradient-p1), var(--im-gradient-p2), var(--im-gradient-p3), var(--im-gradient-p4));
  background-size: 120% 120%;
}

.im-home .top {
  --vp-nav-bg-color: transparent;
  --vp-c-bg: transparent;
  --vp-c-gutter: transparent;
  --vp-c-bg-alt: transparent;
  --vp-c-divider: transparent;
}

.im-home .VPContent {
  justify-content: center;
}

.im-home .bash-composer:before {
  content: "$";
  letter-spacing: .35rem;
  opacity: 1;
  animation: im-blink 1s ease infinite;
}

@keyframes im-blink {
  0% {
    opacity: 1;
  }

  50% {
    opacity: 0;
  }

  100% {
    opacity: 8;
  }
}

.im-home .border-t {
  border-top-color: transparent;
}

.dark .im-home .dark\:border-black {
  --tw-border-opacity: 0;
}

.DocSearch-Container {
  backdrop-filter: blur(10px);
}

.VPContent.has-sidebar .edit-info:not(:has(>.last-updated)) {
  justify-content: flex-end;
  text-align: right;
}

.vp-code-group .tabs::-webkit-scrollbar,
.vp-code-group .tabs::-webkit-scrollbar-track,
.vp-doc [class*="language-"] pre::-webkit-scrollbar,
.vp-doc [class*="language-"] pre::-webkit-scrollbar-track,
.VPSidebar::-webkit-scrollbar,
.VPSidebar::-webkit-scrollbar-track {
  background-color: initial
}

.vp-code-group .tabs::-webkit-scrollbar-thumb:active,
.vp-code-group .tabs::-webkit-scrollbar-thumb:hover,
.vp-doc [class*="language-"] pre::-webkit-scrollbar-thumb:active,
.vp-doc [class*="language-"] pre::-webkit-scrollbar-thumb:hover,
.VPSidebar::-webkit-scrollbar-thumb:active,
.VPSidebar::-webkit-scrollbar-thumb:hover {
  background-color: var(--vp-c-text-1) !important
}

.vp-code-group .tabs:active::-webkit-scrollbar-thumb,
.vp-code-group .tabs:focus-within::-webkit-scrollbar-thumb,
.vp-code-group .tabs:focus::-webkit-scrollbar-thumb,
.vp-code-group .tabs:hover::-webkit-scrollbar-thumb,
.vp-code-group .tabs:active::-webkit-scrollbar-thumb,
.vp-doc [class*="language-"] pre:focus-within::-webkit-scrollbar-thumb,
.vp-doc [class*="language-"] pre:focus::-webkit-scrollbar-thumb,
.vp-doc [class*="language-"] pre:hover::-webkit-scrollbar-thumb,
.VPSidebar:active::-webkit-scrollbar-thumb,
.VPSidebar:focus-within::-webkit-scrollbar-thumb,
.VPSidebar:focus::-webkit-scrollbar-thumb,
.VPSidebar:hover::-webkit-scrollbar-thumb {
  background-clip: content-box;
  background-color: var(--vp-c-text-3);
  border: 4px solid #0000;
  border-radius: 10px
}

.custom-block:not(.im-badges)[class^="im-"] {
  display: flex;
  align-items: center;
}

.custom-block:not(.im-badges)[class^="im-"] .custom-block-title {
  font-size: calc(var(--vp-custom-block-font-size, 14px) * 1.3);
  letter-spacing: .25em;
}

.custom-block.info.im-badges {
  background-color: inherit;
  margin: 0;
  padding: 0;
}

.custom-block.info.im-badges .custom-block-title {
  display: none;
}

.custom-block.info.im-badges p:not(.custom-block-title) {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
}

.custom-block.info.im-badges p:not(.custom-block-title) img {
  margin: 0 .25em .25em 0;
}


================================================
FILE: docs/.vitepress/theme/styles/options-boxes.css
================================================
.next-steps {
  margin-top: 3rem;
}

.next-steps .vt-box {
  border: 1px solid var(--vt-c-bg-soft);
}

.next-steps .vt-box:hover {
  border-color: var(--vt-c-green-light);
  transition: border-color 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
}

.vt-doc .next-steps-link {
  font-size: 20px;
  line-height: 1.4;
  letter-spacing: -0.02em;
  margin-bottom: 0.75em;
  display: block;
  color: var(--vt-c-green);
}

.vt-doc .next-steps-caption {
  margin-bottom: 0;
  color: var(--vt-c-text-2);
  transition: color 0.5s;
}


================================================
FILE: docs/.vitepress/theme/styles/pages.css
================================================
/* always show anchors on /api/ and /style-guide/ pages */
.vt-doc.api h2 .header-anchor,
.vt-doc.style-guide h2 .header-anchor {
  opacity: 1;
}

.vt-doc.sponsor h3 {
  text-align: center;
  padding-bottom: 1em;
  border-bottom: 1px solid var(--vt-c-divider-light);
}

.vt-doc.sponsor h3 .header-anchor {
  display: none;
}

.custom-block.details.bg-transparent {
  background-color: transparent
}


================================================
FILE: docs/.vitepress/theme/styles/style-guide.css
================================================
.style-example {
  border-radius: 8px 8px 12px 12px;
  margin: 1.6em 0;
  padding: 1.6em 1.6em 0.1px;
  position: relative;
  border: 1px solid transparent;
  transition: background-color 0.25s ease, border-color 0.25s ease;
}

.vt-doc .style-example h3 {
  margin: 0;
  font-size: 1.1em;
}

.style-example-bad {
  background: #f7e8e8;
}
.dark .style-example-bad {
  background: transparent;
  border-color: var(--vt-c-red);
}

.style-example-bad h3 {
  color: var(--vt-c-red);
}

.style-example-good {
  background: #ecfaf7;
}
.dark .style-example-good {
  background: transparent;
  border-color: var(--vt-c-green);
}

.style-example-good h3 {
  color: var(--vt-c-green);
}

.details summary {
  font-weight: bold !important;
}

.style-verb {
  font-size: 0.6em;
  display: inline-block;
  border-radius: 6px;
  font-size: 0.65em;
  line-height: 1;
  font-weight: 600;
  padding: 0.35em 0.4em 0.3em;
  position: relative;
  top: -0.15em;
  margin-right: 0.5em;
  color: var(--vt-c-bg);
  transition: color 0.5s;
  background-color: var(--vt-c-brand);
}

.style-verb.avoid {
  background-color: var(--vt-c-red);
}


================================================
FILE: docs/.vitepress/theme/styles/utilities.css
================================================
.nowrap {
  white-space: nowrap;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  border: 0;
}


================================================
FILE: docs/.vitepress/versions.ts
================================================
export default ["6.x", "5.x", "4.x", "3.x"];


================================================
FILE: docs/.vitepress/vitepress+1.6.3.patch
================================================
diff --git a/node_modules/vitepress/dist/node/chunk-Zsoi3j4v.js b/node_modules/vitepress/dist/node/chunk-Zsoi3j4v.js
index 8cfe39e..f37e255 100644
--- a/node_modules/vitepress/dist/node/chunk-Zsoi3j4v.js
+++ b/node_modules/vitepress/dist/node/chunk-Zsoi3j4v.js
@@ -35149,16 +35149,17 @@ function createContainer(klass, defaultTitle, md) {
     {
       render(tokens, idx, _options, env) {
         const token = tokens[idx];
-        const info = token.info.trim().slice(klass.length).trim();
-        const attrs = md.renderer.renderAttrs(token);
         if (token.nesting === 1) {
+          token.attrJoin("class", `${klass} custom-block`);
+          const attrs = md.renderer.renderAttrs(token);
+          const info = token.info.trim().slice(klass.length).trim();
           const title = md.renderInline(info || defaultTitle, {
             references: env.references
           });
           if (klass === "details")
-            return `<details class="${klass} custom-block"${attrs}><summary>${title}</summary>
+            return `<details ${attrs}><summary>${title}</summary>
 `;
-          return `<div class="${klass} custom-block"${attrs}><p class="custom-block-title">${title}</p>
+          return `<div ${attrs}><p class="custom-block-title">${title}</p>
 `;
         } else return klass === "details" ? `</details>
 ` : `</div>
@@ -35731,8 +35732,12 @@ async function createMarkdownRenderer(srcDir, options = {}, base = "/", logger =
     { target: "_blank", rel: "noreferrer", ...options.externalLinks },
     base
   ).use(lineNumberPlugin, options.lineNumbers);
+  const orgi = md.renderer.rules.table_open;
   md.renderer.rules.table_open = function(tokens, idx, options2, env, self) {
-    return '<table tabindex="0">\n';
+    tokens[idx].attrGet("tabindex") ?? tokens[idx].attrJoin("tabindex", "0");
+    return orgi
+      ? orgi(tokens, idx, options2, env, self)
+      : self.renderToken(tokens, idx, options2);
   };
   if (options.gfmAlerts !== false) {
     md.use(gitHubAlertsPlugin);


================================================
FILE: docs/README.md
================================================
# easywechat.com

## Contributing

This site is built with [VitePress](https://github.com/vuejs/vitepress) and depends on [@vue/theme](https://github.com/vuejs/vue-theme). Site content is written in Markdown format located in `src`. For simple edits, you can directly edit the file on GitHub and generate a Pull Request.

For local development, [pnpm](https://pnpm.io/) is preferred as package manager:

```bash
pnpm i
pnpm run dev
```

## Working on the content

- See VitePress docs on supported [Markdown Extensions](https://vitepress.vuejs.org/guide/markdown.html) and the ability to [use Vue syntax inside markdown](https://vitepress.vuejs.org/guide/using-vue.html).

## Working on the theme

If changes need to made for the theme, check out the [instructions for developing the theme alongside the docs](https://github.com/vuejs/vue-theme#developing-with-real-content).


================================================
FILE: docs/env.d.ts
================================================
/// <reference types="vitepress/client" />
/// <reference types="vue/macros-global" />

declare module '@overtrue/easywechat-theme/config' {
  import { UserConfig } from 'vitepress'
  const config: () => Promise<UserConfig>
  export default config
}

declare module '@overtrue/easywechat-theme/highlight' {
  const createHighlighter: () => Promise<(input: string) => string>
  export default createHighlighter
}


================================================
FILE: docs/package.json
================================================
{
  "private": true,
  "scripts": {
    "dev": "vitepress dev",
    "build": "vitepress build",
    "serve": "vitepress serve",
    "purge-caches": "./purge-caches",
    "preinstall": "npx only-allow pnpm"
  },
  "dependencies": {
    "autoprefixer": "^10.4.21",
    "clipboard": "^2.0.11",
    "dynamics.js": "^1.1.5",
    "gsap": "^3.13.0",
    "tailwindcss": "^3.4.18"
  },
  "devDependencies": {
    "@types/markdown-it": "^14.1.2",
    "@types/node": "^22.18.12",
    "postcss": "^8.5.6",
    "postcss-import": "^16.1.1",
    "tenyun": "^0.14.0",
    "vitepress": "1.6.3"
  },
  "packageManager": "pnpm@9.12.3+sha512.cce0f9de9c5a7c95bef944169cc5dfe8741abfb145078c0d508b868056848a87c81e626246cb60967cbd7fd29a6c062ef73ff840d96b3c86c40ac92cf4a813ee"
}


================================================
FILE: docs/postcss.config.js
================================================
module.exports = {
  plugins: [
    require('postcss-import'),
    require("tailwindcss")("./tailwind.config.js"),
    require("autoprefixer"),
    require('postcss-nested')
  ]
}

================================================
FILE: docs/purge-caches
================================================
#!/usr/bin/env node

const { default: TenYun } = require('tenyun');

const tc = new TenYun(process.env.COS_SECRET_ID ?? '', process.env.COS_SECRET_KEY ?? '');

tc.cdn.PurgePathCache({
    Paths: [process.env.domain ?? ''],
    FlushType: 'flush',
})
.then(({ data }) => {
    console.log(data);

    return tc.cdn.DescribePurgeTasks({
        TaskId: data.TaskId,
        PurgeType: 'path',
    });
})
.then(({ data }) => console.log(data));


================================================
FILE: docs/src/3.x/access_token.md
================================================
# Access Token


SDK 中有一个 [Access Token](https://github.com/overtrue/wechat/blob/master/src/Core/AccessToken.php) 对象,它是一个全局使用的东西,请把它与 OAuth 中的 code 换取的 Access Token 区别开。

我们一个 SDK 应用在初始化以后,你可以在任何时机从应用中拿到该配置下的 Access Token 实例:

```php
use EasyWeChat\Foundation\Application;

$options = [
    //...
];

$app = new Application($options);

// 获取 access token 实例
$accessToken = $app->access_token; // EasyWeChat\Core\AccessToken 实例
$token = $accessToken->getToken(); // token 字符串
$token = $accessToken->getToken(true); // 强制重新从微信服务器获取 token.
```

## 修改 `$app` 的 Access Token

```php
$app['access_token']->setToken($newAccessToken, $expires);
```

例如:

```php
$app['access_token']->setToken('ccfdec35bd7ba359f6101c2da321d675');
// 或者指定过期时间
$app['access_token']->setToken('ccfdec35bd7ba359f6101c2da321d675', 3600);  // 单位:秒
```

## 设置 AccessToken 的缓存

你也可以自定义 token 的缓存方式,把一个实现了 `Doctrine\Common\Cache\Cache` 缓存接口的实例作为 AccessToken 构造函数的第三个参数传入即可:

本项目使用 [doctrine/cache](https://github.com/doctrine/cache) 来完成缓存工作,它支持几乎目前所有的缓存引擎。

以 Redis 为例:

```php

use Doctrine\Common\Cache\RedisCache; // RedisCache 实例了 `Doctrine\Common\Cache\Cache` 接口

$cache = new RedisCache();

// 创建 redis 实例
$redis = new Redis();
$redis->connect('redis_host', 6379);

$cache->setRedis($redis);

$app->access_token->setCache($cache);
```



================================================
FILE: docs/src/3.x/accounts.md
================================================
# 账号接入


如果你想使用本项目接入多个公众号,在本程序中,您可以为每个帐号都设置一个id,此id对应了该帐号的appid、token等信息。
如下表

| id | appId | secret | 其它... |
| --- | --- | --- | --- |
| 1 | `wx3cf0f39249eb0e60` | `f28f735d4f1c242f4687abb469072a29` | ... |
| 2 | `wx49eb0e63cf0f39s2` | `8f735d4687abb469f1c2422a29f4f207` | ... |
| N | `wx5cfeb0e60f392490` | `35f8f27d46f1c242f487a9072a29bb46` | ... |

在微信公众平台的设置中,您可以将您帐号中平台的 `url` 设置为 `您的网址/?id=xxx`,如:

```
http://www.example.com/wechat?id=1
```

而在程序入口处,根据 `id` 查找对应帐号的 `appid` 和 其它信息, 传入 'Overtrue\Wechat\Server',完成初始化。


================================================
FILE: docs/src/3.x/anaylsis.md
================================================
# 数据统计与分析


通过数据接口,开发者可以获取与公众平台官网统计模块类似但更灵活的数据,还可根据需要进行高级处理。

> 1. 接口侧的公众号数据的数据库中仅存储了 **2014年12月1日之后**的数据,将查询不到在此之前的日期,即使有查到,也是不可信的脏数据;
> 2. 请开发者在调用接口获取数据后,将数据保存在自身数据库中,即加快下次用户的访问速度,也降低了微信侧接口调用的不必要损耗。
> 3. 额外注意,获取图文群发每日数据接口的结果中,只有**中间页阅读人数+原文页阅读人数+分享转发人数+分享转发次数+收藏次数 >=3** 的结果才会得到统计,过小的阅读量的图文消息无法统计。

### 获取实例

```php
<?php

use EasyWeChat\Foundation\Application;

//...

$app = new Application($options);
$stats = $app->stats;
```

## API

    $from   example: `2014-02-13` 获取数据的起始日期
    $to     example: `2014-02-18` 获取数据的结束日期,`$to`允许设置的最大值为昨日

    `$from` 和 `$to` 的差值需小于 “最大时间跨度”(比如最大时间跨度为 1 时,`$from` 和 `$to` 的差值只能为 0,才能小于 1 ),否则会报错

+ `array userSummary($from, $to)` 获取用户增减数据, 最大时间跨度:**7**;
+ `array userCumulate($from, $to)` 获取累计用户数据, 最大时间跨度:**7**;
+ `array articleSummary($from, $to)` 获取图文群发每日数据, 最大时间跨度:**1**;
+ `array articleTotal($from, $to)` 获取图文群发总数据, 最大时间跨度:**1**;
+ `array userReadSummary($from, $to)` 获取图文统计数据, 最大时间跨度:**3**;
+ `array userReadHourly($from, $to)` 获取图文统计分时数据, 最大时间跨度:**1**;
+ `array userShareSummary($from, $to)` 获取图文分享转发数据, 最大时间跨度:**7**;
+ `array userShareHourly($from, $to)` 获取图文分享转发分时数据, 最大时间跨度:**1**;
+ `array upstreamMessageSummary($from, $to)` 获取消息发送概况数据, 最大时间跨度:**7**;
+ `array upstreamMessageHourly($from, $to)` 获取消息发送分时数据, 最大时间跨度:**1**;
+ `array upstreamMessageWeekly($from, $to)` 获取消息发送周数据, 最大时间跨度:**30**;
+ `array upstreamMessageMonthly($from, $to)` 获取消息发送月数据, 最大时间跨度:**30**;
+ `array upstreamMessageDistSummary($from, $to)` 获取消息发送分布数据, 最大时间跨度:**15**;
+ `array upstreamMessageDistWeekly($from, $to)` 获取消息发送分布周数据, 最大时间跨度:**30**;
+ `array upstreamMessageDistMonthly($from, $to)` 获取消息发送分布月数据, 最大时间跨度:**30**;
+ `array interfaceSummary($from, $to)` 获取接口分析数据, 最大时间跨度:**30**;
+ `array interfaceSummaryHourly($from, $to)` 获取接口分析分时数据, 最大时间跨度:**1**;

example:

```php
$userSummary = $stats->userSummary('2014-12-07', '2014-12-08');

var_dump($userSummary);
//
//[
//    {
//        "ref_date": "2014-12-07",
//        "user_source": 0,
//        "new_user": 0,
//        "cancel_user": 0
//    }
//    //后续还有ref_date在begin_date和end_date之间的数据
// ]

```

更多详细内容与协议说明,请查看微信官方文档:http://mp.weixin.qq.com/wiki/ **数据统计** 章节


================================================
FILE: docs/src/3.x/broadcast.md
================================================
# 群发


微信的群发消息接口有各种乱七八糟的注意事项及限制,具体请阅读微信官方文档:http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html

## 获取实例

```php
<?php
use EasyWeChat\Foundation\Application;
// ...
$app = new Application($options);

$broadcast = $app->broadcast;

```

## API

> 注意:

    下面提到的 `$messageType` 、`$message` 可以是:

    - `$messageType = Broadcast::MSG_TYPE_NEWS;` 图文消息类型,所对应的 `$message` 为 media_id
    - `$messageType = Broadcast::MSG_TYPE_TEXT;` 文本消息类型,所对应的 `$message` 为一个文本字符串
    - `$messageType = Broadcast::MSG_TYPE_VOICE;` 语音消息类型,所对应的 `$message` 为 media_id
    - `$messageType = Broadcast::MSG_TYPE_IMAGE;` 图片消息类型,所对应的 `$message` 为 media_id
    - `$messageType = Broadcast::MSG_TYPE_CARD;` 卡券消息类型,所对应的 `$message` 为 card_id
    - `$messageType = Broadcast::MSG_TYPE_VIDEO;` 视频消息为两种情况:
        - 视频消息类型,群发视频消息给**组或预览群发视频消息**给用户时所对应的 `$message` 为`media_id`
        - 群发视频消息**给指定用户**时所对应的 `$message` 为一个数组 `['MEDIA_ID', 'TITLE', 'DESCRIPTION']`


### 群发消息给所有粉丝

```php
$broadcast->send($messageType, $message);

// 别名方式
$broadcast->sendText("大家好!欢迎使用 EasyWeChat。");
$broadcast->sendNews($mediaId);
$broadcast->sendVoice($mediaId);
$broadcast->sendImage($mediaId);
//视频:
// - 群发给组用户,或者预览群发视频时 $message 为 media_id
// - 群发给指定用户时为数组:[$media_Id, $title, $description]
$broadcast->sendVideo($message);
$broadcast->sendCard($cardId);
```

### 群发消息给指定组

```php
$broadcast->send($messageType, $message, $groupId);

// 别名方式
$broadcast->sendText($text, $groupId);
$broadcast->sendNews($mediaId, $groupId);
$broadcast->sendVoice($mediaId, $groupId);
$broadcast->sendImage($mediaId, $groupId);
$broadcast->sendVideo($message, $groupId);
$broadcast->sendCard($cardId, $groupId);
```

### 群发消息给指定用户

至少两个用户的openid,必须是数组。

```php
$broadcast->send($messageType, $message, [$openId1, $openId2]);

// 别名方式
$broadcast->sendText($text, [$openId1, $openId2]);
$broadcast->sendNews($mediaId, [$openId1, $openId2]);
$broadcast->sendVoice($mediaId, [$openId1, $openId2]);
$broadcast->sendImage($mediaId, [$openId1, $openId2]);
$broadcast->sendVideo($message, [$openId1, $openId2]);
$broadcast->sendCard($cardId, [$openId1, $openId2]);
```

### 发送预览群发消息给指定的 `openId` 用户

```php
$broadcast->preview($messageType, $message, $openId);

// 别名方式
$broadcast->previewText($text, $openId);
$broadcast->previewNews($mediaId, $openId);
$broadcast->previewVoice($mediaId, $openId);
$broadcast->previewImage($mediaId, $openId);
$broadcast->previewVideo($message, $openId);
$broadcast->previewCard($cardId, $openId);
```

### 发送预览群发消息给指定的微信号用户

```php
$broadcast->previewByName($messageType, $message, $wxname);

// 别名方式
$broadcast->previewTextByName($text, $wxname);
$broadcast->previewNewsByName($mediaId, $wxname);
$broadcast->previewVoiceByName($mediaId, $wxname);
$broadcast->previewImageByName($mediaId, $wxname);
$broadcast->previewVideoByName($message, $wxname);
$broadcast->previewCardByName($cardId, $wxname);
```

### 删除群发消息

```php
$broadcast->delete($msgId);
```

### 查询群发消息发送状态

```php
$broadcast->status($msgId);
```

有关群发信息的更多细节请参考微信官方文档:http://mp.weixin.qq.com/wiki/


================================================
FILE: docs/src/3.x/cache.md
================================================
# 缓存


本项目使用 [doctrine/cache](https://github.com/doctrine/cache) 来完成缓存工作,它支持基本目前所有的缓存引擎。

在我们的 SDK 中的所有缓存默认使用文件缓存,缓存路径取决于 PHP 的临时目录,如果你需要自定义缓存,那么你需要做如下的事情:

你可以参考[doctrine/cache官方文档](http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/caching.html)来替换掉应用中默认的缓存配置:

> 以 redis 为例
> 请先安装 redis 拓展:https://github.com/phpredis/phpredis

```php

use Doctrine\Common\Cache\RedisCache;

$cacheDriver = new RedisCache();

// 创建 redis 实例
$redis = new Redis();
$redis->connect('redis_host', 6379);

$cacheDriver->setRedis($redis);

$options = [
    'debug'  => false,
    'app_id' => $wechatInfo['app_id'],
    'secret' => $wechatInfo['app_secret'],
    'token'  => $wechatInfo['token'],
    'aes_key' => $wechatInfo['aes_key'], // 可选
    'cache'   => $cacheDriver,
];

$wechatApp = new Application($options);
```

### Laravel 中使用

在 Laravel 中框架使用 [predis/predis](https://github.com/nrk/predis),那么我们就得使用 `Doctrine\Common\Cache\PredisCache`:

```php

use Doctrine\Common\Cache\PredisCache;

$predis = app('redis')->connection();// connection($name), $name 默认为 `default`
$cacheDriver = new PredisCache($predis);

$app->cache = $cacheDriver;
```

> 上面提到的 `app('redis')->connection($name)`, 这里的 `$name` 是 laravel 项目中配置文件 `database.php` 中 `redis` 配置名 `default`:https://github.com/laravel/laravel/blob/master/config/database.php#L118
> 如果你使用的其它连接,对应传名称就好了。
> 如果你在使用Laravel 5.4,应将`$predis = app('redis')->connection();`修改为:`$predis = app('redis')->connection()->client();`

## 使用自定义的缓存方式

如果你发现 doctrine 提供的几十种缓存方式都满足不了你的需求的话,那么你可以自己建立一个类来完成缓存操作,前提这个类得实现接口:[Doctrine\Common\Cache\Cache](https://github.com/doctrine/cache/blob/master/lib/Doctrine/Common/Cache/Cache.php)

该接口有以下方法需要实现:

```php
   public function fetch($id);    // 读取缓存
   public function contains($id);  // 检查是否存在缓存
   public function save($id, $data, $lifeTime = 0);   // 设置缓存
   public function delete($id);  // 删除缓存
   public function getStats(); // 获取状态
```

下面为一个示例:

```php
<?php

use Doctrine\Common\Cache\Cache as CacheInterface;

class MyCacheDriver implements CacheInterface
{
    public function fetch($id)
    {
        // 你自己从你想实现的存储方式读取并返回
    }

    public function contains($id)
    {
        // 同理 返回存在与否 bool 值
    }

    public function save($id, $data, $lifeTime = 0)
    {
        // 用你的方式存储该缓存内容即可
    }

    public function delete($id)
    {
        // 删除并返回 bool 值
    }

    public function getStats()
    {
        // 这个你可以不用实现,返回 null 即可
    }
}
```

然后实例化你的缓存类并在 EasyWeChat 里使用它:

```php
$myCacheDriver = new MyCacheDriver();

$config = [
    //...
    'cache'   => $myCacheDriver,
];

$wechatApp = new Application($options);
```

OK,这样就完成了自定义缓存的操作。


================================================
FILE: docs/src/3.x/card.md
================================================
# 卡券
-

> Version `>=3.1.2`

## 获取实例

```php
<?php
use EasyWeChat\Foundation\Application;

// ...

$app = new Application($options);

$card = $app->card;
```


## API列表

### 获取卡券颜色

```php
$card->getColors();
```

example:

```php
$result = $card->getColors();
```



### 创建卡券

创建卡券接口是微信卡券的基础接口,用于创建一类新的卡券,获取card_id,创建成功并通过审核后,商家可以通过文档提供的其他接口将卡券下发给用户,每次成功领取,库存数量相应扣除。

```php
$card->create($cardType, $baseInfo, $especial);
```

- `cardType` string - 是要添加卡券的类型
- `baseInfo` array  - 为卡券的基本数据
- `especial` array  - 是扩展字段

example:

```php
<?php

	$cardType = 'GROUPON';

    $baseInfo = [
        'logo_url' => 'http://mmbiz.qpic.cn/mmbiz/2aJY6aCPatSeibYAyy7yct9zJXL9WsNVL4JdkTbBr184gNWS6nibcA75Hia9CqxicsqjYiaw2xuxYZiaibkmORS2oovdg/0',
        'brand_name' => '测试商户造梦空间',
        'code_type' => 'CODE_TYPE_QRCODE',
        'title' => '测试',
        'sub_title' => '测试副标题',
        'color' => 'Color010',
        'notice' => '测试使用时请出示此券',
        'service_phone' => '15311931577',
        'description' => "测试不可与其他优惠同享\n如需团购券发票,请在消费时向商户提出\n店内均可使用,仅限堂食",

        'date_info' => [
          'type' => 'DATE_TYPE_FIX_TERM',
          'fixed_term' => 90, //表示自领取后多少天内有效,不支持填写0
          'fixed_begin_term' => 0, //表示自领取后多少天开始生效,领取后当天生效填写0。
        ],

        'sku' => [
          'quantity' => '0', //自定义code时设置库存为0
        ],

        'location_id_list' => ['461907340'],  //获取门店位置poi_id,具备线下门店的商户为必填

        'get_limit' => 1,
        'use_custom_code' => true, //自定义code时必须为true
        'get_custom_code_mode' => 'GET_CUSTOM_CODE_MODE_DEPOSIT',  //自定义code时设置
        'bind_openid' => false,
        'can_share' => true,
        'can_give_friend' => false,
        'center_title' => '顶部居中按钮',
        'center_sub_title' => '按钮下方的wording',
        'center_url' => 'http://www.qq.com',
        'custom_url_name' => '立即使用',
        'custom_url' => 'http://www.qq.com',
        'custom_url_sub_title' => '6个汉字tips',
        'promotion_url_name' => '更多优惠',
        'promotion_url' => 'http://www.qq.com',
        'source' => '造梦空间',
      ];

    $especial = [
      'deal_detail' => 'deal_detail',
    ];

    $result = $card->create($cardType, $baseInfo, $especial);
```



### 创建二维码

开发者可调用该接口生成一张卡券二维码供用户扫码后添加卡券到卡包。

自定义Code码的卡券调用接口时,POST数据中需指定code,非自定义code不需指定,指定openid同理。指定后的二维码只能被用户扫描领取一次。

```php
$card->QRCode($cards);
```

- `cards` array - 卡券相关信息

example:

```php
//领取单张卡券
$cards = [
    'action_name' => 'QR_CARD',
    'expire_seconds' => 1800,
    'action_info' => [
      'card' => [
        'card_id' => 'pdkJ9uFS2WWCFfbbEfsAzrzizVyY',
        'is_unique_code' => false,
        'outer_id' => 1,
      ],
    ],
  ];

$result = $card->QRCode($cards);
```

```php
//领取多张卡券
$cards = [
    'action_name' => 'QR_MULTIPLE_CARD',
    'action_info' => [
      'multiple_card' => [
        'card_list' => [
          ['card_id' => 'pdkJ9uFS2WWCFfbbEfsAzrzizVyY'],
        ],
      ],
    ],
  ];

$result = $card->QRCode($cardList);
```

请求成功返回值示例:

```php
array(4) {
  ["ticket"]=>
  string(96) "gQHa7joAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xLzdrUFlQMHJsV3Zvanc5a2NzV1N5AAIEJUVyVwMEAKd2AA=="
  ["expire_seconds"]=>
  int(7776000)
  ["url"]=>
  string(43) "http://weixin.qq.com/q/7kPYP0rlWvojw9kcsWSy"
  ["show_qrcode_url"]=>
  string(151) "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=gQHa7joAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xLzdrUFlQMHJsV3Zvanc5a2NzV1N5AAIEJUVyVwMEAKd2AA%3D%3D"
}
```

成功返回值列表说明:

|       参数名       | 描述                                       |
| :-------------: | :--------------------------------------- |
|     ticket      | 获取的二维码ticket,凭借此ticket调用[通过ticket换取二维码接口](http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1443433542&token=&lang=zh_CN)可以在有效时间内换取二维码。 |
| expire_seconds  | 二维码的有效时间                                 |
|       url       | 二维码图片解析后的地址,开发者可根据该地址自行生成需要的二维码图片        |
| show_qrcode_url | 二维码显示地址,点击后跳转二维码页面                       |



### ticket 换取二维码图片

获取二维码 ticket 后,开发者可用 ticket 换取二维码图片。

```php
$card->showQRCode($ticket);
```

- `ticket` string  - 获取的二维码 ticket,凭借此 ticket 可以在有效时间内换取二维码。

example:

```php
$ticket = 'gQFF8DoAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xL01VTzN0T0hsS1BwUlBBYUszbVN5AAIEughxVwMEAKd2AA==';
$result = $card->showQRCode($ticket);
```


### ticket 换取二维码链接

```php
$card->getQRCodeUrl($ticket);  //获取的二维码ticket
```

example:

```php
$ticket = 'gQFF8DoAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xL01VTzN0T0hsS1BwUlBBYUszbVN5AAIEughxVwMEAKd2AA==';
$card->getQRCodeUrl($ticket);
```

### JSAPI 卡券批量下发到用户

微信卡券:JSAPI 卡券

```php
$cards = [
    ['card_id' => 'pdkJ9uLRSbnB3UFEjZAgUxAJrjeY', 'outer_id' => 2],
    ['card_id' => 'pdkJ9uJ37aU-tyRj4_grs8S45k1c', 'outer_id' => 3],
];
$json = $card->jsConfigForAssign($cards); // 返回 json 格式
```

返回 json,在模板里的用法:

```html
wx.addCard({
    cardList: <?= $json ?>, // 需要打开的卡券列表
    success: function (res) {
        var cardList = res.cardList; // 添加的卡券列表信息
    }
});
```

### 创建货架接口

开发者需调用该接口创建货架链接,用于卡券投放。创建货架时需填写投放路径的场景字段。

```php
$card->createLandingPage($banner, $pageTitle, $canShare, $scene, $cards);
```

- `banner` string -页面的 banner 图;
- `pageTitle` string - 页面的 title
- `canShare` bool - 页面是不是可以分享,true 或 false
- `scene`  string - 投放页面的场景值,具体值请参考下面的 example
- `cards`  array - 卡券列表,每个元素有两个字段

example:

```php
$banner     = 'http://mmbiz.qpic.cn/mmbiz/iaL1LJM1mF9aRKPZJkmG8xXhiaHqkKSVMMWeN3hLut7X7hicFN';
$pageTitle = '惠城优惠大派送';
$canShare  = true;

//SCENE_NEAR_BY          附近
//SCENE_MENU             自定义菜单
//SCENE_QRCODE             二维码
//SCENE_ARTICLE             公众号文章
//SCENE_H5                 h5页面
//SCENE_IVR                 自动回复
//SCENE_CARD_CUSTOM_CELL 卡券自定义cell
$scene = 'SCENE_NEAR_BY';

$cardList = [
    ['card_id' => 'pdkJ9uLRSbnB3UFEjZAgUxAJrjeY', 'thumb_url' => 'http://test.digilinx.cn/wxApi/Uploads/test.png'],
    ['card_id' => 'pdkJ9uJ37aU-tyRj4_grs8S45k1c', 'thumb_url' => 'http://test.digilinx.cn/wxApi/Uploads/aa.jpg'],
];

$result = $card->createLandingPage($banner, $pageTitle, $canShare, $scene, $cardList);
```



### 导入code接口

在自定义code卡券成功创建并且通过审核后,必须将自定义code按照与发券方的约定数量调用导入code接口导入微信后台。

```php
$card->deposit($card_id, $code);
```

- `cardId` string - 要导入code的卡券ID
- `code` string - 要导入微信卡券后台的自定义 code,最多100个

example:

```php
$cardId = 'pdkJ9uLCEF_HSKO7JdQOUcZ-PUzo';
$code    = ['11111', '22222', '33333'];

$result = $card->deposit($cardId, $code);
```



### 查询导入code数目

```php
$card->getDepositedCount($cardId);  //要导入code的卡券ID
```

example:

```php
$cardId = 'pdkJ9uLCEF_HSKO7JdQOUcZ-PUzo';

$result = $card->getDepositedCount($cardId);
```



### 核查code接口

为了避免出现导入差错,强烈建议开发者在查询完code数目的时候核查code接口校验code导入微信后台的情况。

```php
$card->checkCode($cardId, $code);
```

example:

```php
$cardId = 'pdkJ9uLCEF_HSKO7JdQOUcZ-PUzo';

$code = ['807732265476', '22222', '33333'];

$result = $card->checkCode($cardId, $code);
```



### 图文消息群发卡券

特别注意:目前该接口仅支持填入非自定义code的卡券,自定义code的卡券需先进行code导入后调用。

```php
$card->getHtml($cardId);
```

example:

```php
$cardId = 'pdkJ9uLCEF_HSKO7JdQOUcZ-PUzo';

$result = $card->getHtml($cardId);
```



### 设置测试白名单

同时支持“openid”、“username”两种字段设置白名单,总数上限为10个。

```php
$card->setTestWhitelist($openids); // 使用 openid
$card->setTestWhitelistByUsername($usernames); // 使用 username
```

- `openids` array - 测试的openid列表
- `usernames` array  - 测试的微信号列表

example:

```php
// by openid
$openids   = [$openId, $openId2, $openid3...];
$result = $card->setTestWhitelist($openids);

// by username
$usernames = ['tianye0327', 'iovertrue'];
$result = $card->setTestWhitelistByUsername($usernames);
```

### 查询Code接口

```php
$card->getCode($code, $checkConsume, $cardId);
```

- checkConsume  是否校验code核销状态,true和false

example:

```php
$code          = '736052543512';
$checkConsume = true;
$cardId       = 'pdkJ9uDgnm0pKfrTb1yV0dFMO_Gk';

$result = $card->getCode($code, $checkConsume, $cardId);
```



### 核销Code接口

```php
$card->consume($code);

// 或者指定 cardId

$card->consume($code, $cardId);
```

example:

```php
$cardId = 'pdkJ9uDmhkLj6l5bm3cq9iteQBck';
$code    = '789248558333';

$result = $card->consume($code);

//或

$result = $card->consume($code, $cardId);
```



### Code解码接口

```php
$card->decryptCode($encryptedCode);
```

example:

```php
$encryptedCode = 'XXIzTtMqCxwOaawoE91+VJdsFmv7b8g0VZIZkqf4GWA60Fzpc8ksZ/5ZZ0DVkXdE';

$result = $card->decryptCode($encryptedCode);
```



### 获取用户已领取卡券接口

用于获取用户卡包里的,属于该appid下所有**可用卡券,包括正常状态和未生效状态**。

```php
$card->getUserCards($openid, $cardId);
```

example:

```php
$openid  = 'odkJ9uDUz26RY-7DN1mxkznfo9xU';
$cardId = ''; //卡券ID。不填写时默认查询当前appid下的卡券。

$result = $card->getUserCards($openid, $cardId);
```



### 查看卡券详情

开发者可以调用该接口查询某个card_id的创建信息、审核状态以及库存数量。

```php
$card->getCard($cardId);
```

example:

```php
$cardId = 'pdkJ9uLRSbnB3UFEjZAgUxAJrjeY';

$result = $card->getCard($cardId);
```



### 批量查询卡列表

```php
$card->lists($offset, $count, $statusList);
```

- `offset` int - 查询卡列表的起始偏移量,从0开始
- `count` int - 需要查询的卡片的数量
- `statusList` -  支持开发者拉出指定状态的卡券列表,详见example

example:

```php
$offset      = 0;
$count       = 10;

//CARD_STATUS_NOT_VERIFY,待审核;
//CARD_STATUS_VERIFY_FAIL,审核失败;
//CARD_STATUS_VERIFY_OK,通过审核;
//CARD_STATUS_USER_DELETE,卡券被商户删除;
//CARD_STATUS_DISPATCH,在公众平台投放过的卡券;
$statusList = 'CARD_STATUS_VERIFY_OK';

$result = $card->lists($offset, $count, $statusList);
```



### 更改卡券信息接口

支持更新所有卡券类型的部分通用字段及特殊卡券中特定字段的信息。

```php
$card->update($cardId, $type, $baseInfo);
```

- `type` string - 卡券类型

example:

```php
$cardId = 'pdkJ9uCzKWebwgNjxosee0ZuO3Os';

$type = 'groupon';

$baseInfo = [
    'logo_url' => 'http://mmbiz.qpic.cn/mmbiz/2aJY6aCPatSeibYAyy7yct9zJXL9WsNVL4JdkTbBr184gNWS6nibcA75Hia9CqxicsqjYiaw2xuxYZiaibkmORS2oovdg/0',
    'center_title' => '顶部居中按钮',
    'center_sub_title' => '按钮下方的wording',
    'center_url' => 'http://www.baidu.com',
    'custom_url_name' => '立即使用',
    'custom_url' => 'http://www.qq.com',
    'custom_url_sub_title' => '6个汉字tips',
    'promotion_url_name' => '更多优惠',
    'promotion_url' => 'http://www.qq.com',
];

$result = $card->update($cardId, $type, $baseInfo);
```



### 设置微信买单接口

```php
$card->setPayCell($cardId, $isOpen);
```

- `isOpen` string - 是否开启买单功能,填 true/false,不填默认 true

example:

```php
$cardId = 'pdkJ9uH7u11R-Tu1kilbaW_zDFow';
$isOpen = true;

$result = $card->setPayCell($cardId, $isOpen);
```



### 修改库存接口

```php
$card->increaseStock($cardId, $amount); // 增加库存
$card->reductStock($cardId, $amount); // 减少库存
```

- `cardId` string - 卡券 ID
- `amount` int - 修改多少库存

example:

```php
$cardId = 'pdkJ9uLRSbnB3UFEjZAgUxAJrjeY';

$result = $card->increaseStock($cardId, 100);
```


### 更改Code接口

```php
$card->updateCode($code, $newCode, $cardId);
```

- `newCode` string - 变更后的有效Code码

example:

```php
$code     = '148246271394';
$newCode = '659266965266';
$cardId  = '';

$result = $card->updateCode($code, $newCode, $cardId);
```



### 删除卡券接口

```php
$card->delete($cardId);
```

example:

```php
$cardId = 'pdkJ9uItT7iUpBp4GjZp8Cae0Vig';

$result = $card->delete($cardId);
```



### 设置卡券失效

```php
$card->disable($code, $cardId);
```

example:

```php
$code    = '736052543512';
$cardId = '';

$result = $card->disable($code, $cardId);
```



### 会员卡接口激活

```php
$result = $card->activate($info);
```

- `info` - 需要激活的会员卡信息

example:

```php
$activate = [
      'membership_number'        => '357898858', //会员卡编号,由开发者填入,作为序列号显示在用户的卡包里。可与Code码保持等值。
      'code'                     => '916679873278', //创建会员卡时获取的初始code。
      'activate_begin_time'      => '1397577600', //激活后的有效起始时间。若不填写默认以创建时的 data_info 为准。Unix时间戳格式
      'activate_end_time'        => '1422724261', //激活后的有效截至时间。若不填写默认以创建时的 data_info 为准。Unix时间戳格式。
      'init_bonus'               => '持白金会员卡到店消费,可享8折优惠。', //初始积分,不填为0。
      'init_balance'             => '持白金会员卡到店消费,可享8折优惠。', //初始余额,不填为0。
      'init_custom_field_value1' => '白银', //创建时字段custom_field1定义类型的初始值,限制为4个汉字,12字节。
      'init_custom_field_value2' => '9折', //创建时字段custom_field2定义类型的初始值,限制为4个汉字,12字节。
      'init_custom_field_value3' => '200', //创建时字段custom_field3定义类型的初始值,限制为4个汉字,12字节。
];

$result = $card->activate($activate);
```



### 设置开卡字段接口

```php
$card->activateUserForm($cardId, $requiredForm, $optionalForm);
```

- `requiredForm` array - 会员卡激活时的必填选项
- `optionalForm` array - 会员卡激活时的选填项

example:

```php
$cardId = 'pdkJ9uJYAyfLXsUCwI2LdH2Pn1AU';

$requiredForm = [
    'required_form' => [
        'common_field_id_list' => [
            'USER_FORM_INFO_FLAG_MOBILE',
            'USER_FORM_INFO_FLAG_LOCATION',
            'USER_FORM_INFO_FLAG_BIRTHDAY',
        ],
        'custom_field_list' => [
            '喜欢的食物',
        ],
    ],
];

$optionalForm = [
    'optional_form' => [
        'common_field_id_list' => [
            'USER_FORM_INFO_FLAG_EMAIL',
        ],
        'custom_field_list' => [
            '喜欢的食物',
        ],
    ],
];

$result = $card->activateUserForm($cardId, $requiredForm, $optionalForm);
```



### 拉取会员信息接口

```php
$card->getMemberCardUser($cardId, $code);
```

example:

```php
$cardId = 'pbLatjtZ7v1BG_ZnTjbW85GYc_E8';
$code    = '916679873278';

$result = $card->getMemberCardUser($cardId, $code);
```



### 更新会员信息

```php
$card->updateMemberCardUser($updateUser);
```

- `updateUser` array - 可以更新的会员信息

example:

```php
$updateUser = [
    'code'                => '916679873278', //卡券Code码。
    'card_id'             => 'pbLatjtZ7v1BG_ZnTjbW85GYc_E8', //卡券ID。
    'record_bonus'        => '消费30元,获得3积分', //商家自定义积分消耗记录,不超过14个汉字。
    'bonus'               => '100', //需要设置的积分全量值,传入的数值会直接显示,如果同时传入add_bonus和bonus,则前者无效。
    'balance'             => '持白金会员卡到店消费,可享8折优惠。', //需要设置的余额全量值,传入的数值会直接显示,如果同时传入add_balance和balance,则前者无效。
    'record_balance'      => '持白金会员卡到店消费,可享8折优惠。', //商家自定义金额消耗记录,不超过14个汉字。
    'custom_field_value1' => '100', //创建时字段custom_field1定义类型的最新数值,限制为4个汉字,12字节。
    'custom_field_value2' => '200', //创建时字段custom_field2定义类型的最新数值,限制为4个汉字,12字节。
    'custom_field_value3' => '300', //创建时字段custom_field3定义类型的最新数值,限制为4个汉字,12字节。
];

$result = $card->updateMemberCardUser($updateUser);
```



### 添加子商户

```php
$card->craeteSubMerchant($brandName, $logoUrl, $protocol, $endTime, $primaryCategoryId, $secondaryCategoryId, $agreementMediaId, $operatorMediaId, $appId); 
```

- `brand_name` string - 子商户名称(12个汉字内),该名称将在制券时填入并显示在卡券页面上
- `logo_url`  string - 子商户 logo,可通过上传 logo 接口获取。该 logo 将在制券时填入并显示在卡券页面上
- `protocol`  string - 授权函ID,即通过上传临时素材接口上传授权函后获得的 meida_id
- `primary_category_id`  int - 一级类目id,可以通过本文档中接口查询
- `secondary_category_id` int - 二级类目id,可以通过本文档中接口查询
- `agreement_media_id`  string - 营业执照或个体工商户营业执照彩照或扫描件
- `operator_media_id`  string - 营业执照内登记的经营者身份证彩照或扫描件
- `app_id`  string - 子商户的公众号 app_id,配置后子商户卡券券面上的 app_id 为该 app_id, app_id 须经过认证

example:

```php
$info = [
    'brand_name' => 'overtrue',
    'logo_url' => 'http://mmbiz.qpic.cn/mmbiz/iaL1LJM1mF9aRKPZJkmG8xXhiaHqkKSVMMWeN3hLut7X7hicFNjakmxibMLGWpXrEXB33367o7zHN0CwngnQY7zb7g/0',
    'protocol' => 'qIqwTfzAdJ_1-VJFT0fIV53DSY4sZY2WyhkzZzbV498Qgdp-K5HJtZihbHLS0Ys0',
    'end_time' => '1438990559',
    'primary_category_id' => 1,
    'secondary_category_id' => 101,
    'agreement_media_id' => '',
    'operator_media_id' => '',
    'app_id' => '',
];

$result = $card->createSubMerchant($info);
```

### 更新子商户

```php
$card->updateSubMerchant($merchantId, $info);
```

- `$merchantId` int - 子商户 ID
- `$info` array - 参数与创建子商户参数一样

example:

```php
$info = [
  //...
];
$result = $card->updateSubMerchant('12', $info);
```

### 卡券开放类目查询接口

```php
$card->getCategories();
```

example:

```php
$result = $card->getCategories();
```

关于卡券接口的使用请参阅官方文档:http://mp.weixin.qq.com/wiki/


================================================
FILE: docs/src/3.x/configuration.md
================================================
# 配置


在前面我们已经讲过,初始化 SDK 的时候方法就是创建一个 `EasyWeChat\Foundation\Application` 实例:

```php
use EasyWeChat\Foundation\Application;

$options = [
   // ...
];

$app = new Application($options);

/**
* 如果想要在Application实例化完成之后, 修改某一个options的值,
* 比如服务商+子商户支付回调场景, 所有子商户订单支付信息都是通过同一个服务商的$option 配置进来的,
* 当oauth在微信端验证完成之后, 可以通过动态设置merchant_id来区分具体是哪个子商户
*/
$app['config']->set('oauth.callback','wechat/oauthcallback/'. $sub_merchant_id->id);
```

那么配置的具体选项有哪些,下面是一个完整的列表:

```php
<?php

return [
    /**
     * Debug 模式,bool 值:true/false
     *
     * 当值为 false 时,所有的日志都不会记录
     */
    'debug'  => true,

    /**
     * 账号基本信息,请从微信公众平台/开放平台获取
     */
    'app_id'  => 'your-app-id',         // AppID
    'secret'  => 'your-app-secret',     // AppSecret
    'token'   => 'your-token',          // Token
    'aes_key' => '',                    // EncodingAESKey,安全模式与兼容模式下请一定要填写!!!

    /**
     * 日志配置
     *
     * level: 日志级别, 可选为:
     *         debug/info/notice/warning/error/critical/alert/emergency
     * permission:日志文件权限(可选),默认为null(若为null值,monolog会取0644)
     * file:日志文件位置(绝对路径!!!),要求可写权限
     */
    'log' => [
        'level'      => 'debug',
        'permission' => 0777,
        'file'       => '/tmp/easywechat.log',
    ],

    /**
     * OAuth 配置
     *
     * scopes:公众平台(snsapi_userinfo / snsapi_base),开放平台:snsapi_login
     * callback:OAuth授权完成后的回调页地址
     */
    'oauth' => [
        'scopes'   => ['snsapi_userinfo'],
        'callback' => '/examples/oauth_callback.php',
    ],

    /**
     * 微信支付
     */
    'payment' => [
        'merchant_id'        => 'your-mch-id',
        'key'                => 'key-for-signature',
        'cert_path'          => 'path/to/your/cert.pem', // XXX: 绝对路径!!!!
        'key_path'           => 'path/to/your/key',      // XXX: 绝对路径!!!!
        // 'device_info'     => '013467007045764',
        // 'sub_app_id'      => '',
        // 'sub_merchant_id' => '',
        // ...
    ],

    /**
     * Guzzle 全局设置
     *
     * 更多请参考: http://docs.guzzlephp.org/en/latest/request-options.html
     */
    'guzzle' => [
        'timeout' => 3.0, // 超时时间(秒)
        //'verify' => false, // 关掉 SSL 认证(强烈不建议!!!)
    ],
];
```

> :heart: 安全模式下请一定要填写 `aes_key`

## 日志文件

配置文件里的`/tmp/...`是绝对路径

如果在 windows 下,去把它改成`C:\foo\bar`的形式,
如果是 Linux ,你已经懂了……

如果需要按日独立存储,可以配置成`'file'  => storage_path('/tmp/easywechat/easywechat_'.date('Ymd').'.log'),`

其它同理……



================================================
FILE: docs/src/3.x/contributing.md
================================================
# 贡献代码

## 开发

我们欢迎广大开发者贡献大家的智慧,让我们共同让它变得更完美.

### 开始之前

请严格遵循以下代码标准:

- [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md).
- 使用 4 个空格作为缩进。

### 流程

1. Fork [overtrue/wechat](https://github.com/overtrue/wechat) 到本地.
2. 创建新的分支:

   ```bash
   git checkout -b new_feature
   ```

3. 编写代码。
4. Push 到你的分支:

   ```bash
   git push origin new_feature
   ```

5. 创建 Pull Request 并描述你完成的功能或者做出的修改。

> 注意:注释请使用英文

## 更新文档

我们的文档也是开源的,源代码在 [w7corp/EasyWeChat/docs](https://github.com/w7corp/easywechat/tree/master/docs)。

### 流程

1. Fork [w7corp/EasyWeChat](https://github.com/w7corp/EasyWeChat)
2. Clone 到你的电脑:

   ```bash
   git clone https://github.com/[你的账号]/EasyWeChat
   cd docs
   ```

3. 创建新的分支,编辑文档
4. Push 到你的分支。
5. 创建 Pull Request 并描述你完成的功能或者做出的修改。

## 报告 Bug

当你在使用过程中遇到问题,请查阅 [疑难解答](troubleshooting.html) 或者在这里提问 [GitHub](https://github.com/overtrue/wechat/issues). 如果还是不能解决你的问题,请到 GitHub 联系我们。

[overtrue/wechat]: https://github.com/overtrue/wechat


================================================
FILE: docs/src/3.x/events.md
================================================
# 事件


> 注意:3.0 起,所有服务端的入口(**消息与事件**)都已经合并为一个方法来处理:`setMessageHandler()`

### 在服务端接收用户端产生的事件

```php
<?php
use EasyWeChat\Foundation\Application;

// ...

$app = new Application($options);

$server = $app->server;

$server->setMessageHandler(function($message){
    // 注意,这里的 $message 不仅仅是用户发来的消息,也可能是事件
    // 当 $message->MsgType 为 event 时为事件
    if ($message->MsgType == 'event') {
        # code...
        switch ($message->Event) {
            case 'subscribe':
                # code...
                break;

            default:
                # code...
                break;
        }
    }
});

$response = $server->serve();

$response->send(); // Laravel 里请使用:return $response;
```

> 注意:`$response` 是一个对象,不要直接 echo.

更多请参考:[服务端](server.html)

关于事件类型请参考微信官方文档:http://mp.weixin.qq.com/wiki/


================================================
FILE: docs/src/3.x/index.md
================================================
> 👋🏼 您当前浏览的文档为 3.x,其它版本的文档请参考:[6.x](/6.x/)、[5.x](/5.x/)、[4.x](/4.x/)

# EasyWeChat

EasyWeChat 是一个开源的 [微信](http://www.wechat.com) 非官方 SDK。安装非常简单,因为它是一个标准的 [Composer](https://getcomposer.org/) 包,这意味着任何满足下列安装条件的 PHP 项目支持 Composer 都可以使用它。

## 环境要求

- PHP >= 5.5.9
- [PHP cURL 扩展](http://php.net/manual/en/book.curl.php)
- [PHP OpenSSL 扩展](http://php.net/manual/en/book.openssl.php)
- [PHP fileinfo 拓展](http://php.net/manual/en/book.fileinfo.php)

# 参与贡献

1. fork 当前库到你的名下
2. 切换到你想要修改的分支,`zh-cn` 或者 `en`
3. 在你的本地修改完成审阅过后提交到你的仓库的对应分支
4. 提交 PR 并描述你的修改,等待合并

# License

MIT


================================================
FILE: docs/src/3.x/installation.md
================================================
# 安装


## 环境要求

- PHP >= 5.5.9
- [PHP cURL 扩展](http://php.net/manual/en/book.curl.php)
- [PHP OpenSSL 扩展](http://php.net/manual/en/book.openssl.php)
- [PHP fileinfo 拓展](http://php.net/manual/en/book.fileinfo.php) 素材管理模块需要用到


Laravel 5 拓展包: [overtrue/laravel-wechat](https://github.com/overtrue/laravel-wechat)

## 安装

使用 [composer](http://getcomposer.org/):

```shell
$ composer require overtrue/wechat:~3.1 -vvv
```

================================================
FILE: docs/src/3.x/integration.md
================================================
# 在框架中使用

EasyWeChat 是一个通用的 Composer 包,所以不需要对框架单独做修改,只要支持 Composer 就能直接使用,当然了,为了更方便的使用,我们收集了以下框架单独提供的拓展包:

## Laravel

- [overtrue/laravel-wechat](https://github.com/overtrue/laravel-wechat)


## Symfony

- [lilocon/WechatBundle](https://github.com/lilocon/WechatBundle)

## Yii

- [max-wen/yii2-easy-wechat](https://github.com/max-wen/yii2-easy-wechat)

## CI

TODO

## Phalcon

TODO

... more



================================================
FILE: docs/src/3.x/js.md
================================================
# JSSDK


## 获取实例

```php
<?php
use EasyWeChat\Foundation\Application;
//...
$app = new Application($options);

$js = $app->js;
```

## API

- `$js->config(array $APIs, $debug = false, $beta = false, $json = true);` 获取JSSDK的配置数组,默认返回 JSON 字符串,当 `$json` 为 `false` 时返回数组,你可以直接使用到网页中。
- `$js->setUrl($url)` 设置当前URL,如果不想用默认读取的URL,可以使用此方法手动设置,通常不需要。

example:

我们可以生成js配置文件:

```js
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" charset="utf-8">
    wx.config(<?php echo $js->config(array('onMenuShareQQ', 'onMenuShareWeibo'), true) ?>);
</script>
```
结果如下:

```js
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" charset="utf-8">
wx.config({
    debug: true,
    appId: 'wx3cf0f39249eb0e60',
    timestamp: 1430009304,
    nonceStr: 'qey94m021ik',
    signature: '4F76593A4245644FAE4E1BC940F6422A0C3EC03E',
    jsApiList: ['onMenuShareQQ', 'onMenuShareWeibo']
});
</script>
```

更多 JSSDK 的使用请参考 [微信官方文档](http://mp.weixin.qq.com/wiki/) 中 **JSSDK章节**

================================================
FILE: docs/src/3.x/lucky-money.md
================================================
# 红包


你在阅读本文之前确认你已经仔细阅读了:[微信支付 | 现金红包文档 ](https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_1)。

## 配置

与支付接口一样,红包接口也需要配置如下参数,需要特别注意的是,红包相关的全部接口**都需要使用 SSL 证书**,因此** cert_path 以及 cert_key 必须正确配置**。

```php
<?php

use EasyWeChat\Foundation\Application;

$options = [
    // payment
    'payment' => [
        'merchant_id'        => 'your-mch-id',
        'key'                => 'key-for-signature',
        'cert_path'          => 'path/to/your/cert.pem',
        'key_path'           => 'path/to/your/key',
        // ...
    ],
];

$app = new Application($options);

$luckyMoney = $app->lucky_money;
```

## 发送红包

微信的现金红包分为**普通红包**和**裂变红包**两类。SDK 中对其分别进行了封装,同时也提供了一个统一的调用方法。

**默认情况下,通过接口发送的红包金额应该在200元以内,但可以通过在调用发送接口时传递场景 ID (scene_id)来发送特定场景的红包,不同场景红包可以由商户自己登录商户平台设置最大金额。scene_id 的可选值及对应含义可参阅微信支付官方文档。**

### 通用发送接口

```php
<?php

$luckyMoneyData = [
    'mch_billno'       => 'xy123456',
    'send_name'        => '测试红包',
    're_openid'        => 'oxTWIuGaIt6gTKsQRLau2M0yL16E',
    'total_num'        => 1,  //普通红包固定为1,裂变红包不小于3
    'total_amount'     => 100,  //单位为分,普通红包不小于100,裂变红包不小于300
    'wishing'          => '祝福语',
    'client_ip'        => '192.168.0.1',  //可不传,不传则由 SDK 取当前客户端 IP
    'act_name'         => '测试活动',
    'remark'           => '测试备注',
    // ...
];

$result = $luckyMoney->send($luckyMoneyData, \EasyWeChat\Payment\LuckyMoney\API::TYPE_NORMAL);
或
$result = $luckyMoney->send($luckyMoneyData, \EasyWeChat\Payment\LuckyMoney\API::TYPE_GROUP);

```

> 不同类型红包所传参数有所差别,请参考官方文档中参数列表。


### 发送普通红包接口

```php
<?php

$luckyMoneyData = [
    'mch_billno'       => 'xy123456',
    'send_name'        => '测试红包',
    're_openid'        => 'oxTWIuGaIt6gTKsQRLau2M0yL16E',
    'total_num'        => 1,  //固定为1,可不传
    'total_amount'     => 100,  //单位为分,不小于100
    'wishing'          => '祝福语',
    'client_ip'        => '192.168.0.1',  //可不传,不传则由 SDK 取当前客户端 IP
    'act_name'         => '测试活动',
    'remark'           => '测试备注',
    // ...
];

$result = $luckyMoney->sendNormal($luckyMoneyData);

```

### 发送裂变红包接口

```php
<?php

$luckyMoneyData = [
    'mch_billno'       => 'xy123456',
    'send_name'        => '测试红包',
    're_openid'        => 'oxTWIuGaIt6gTKsQRLau2M0yL16E',
    'total_num'        => 3,  //不小于3
    'total_amount'     => 300,  //单位为分,不小于300
    'wishing'          => '祝福语',
    'act_name'         => '测试活动',
    'remark'           => '测试备注',
    'amt_type'         => 'ALL_RAND',  //可不传
    // ...
];

$result = $luckyMoney->sendGroup($luckyMoneyData);

```

## 红包预下单接口

红包预下单接口是为摇一摇红包接口配合使用的,在开发摇一摇周边的摇红包相关功能时,需要调用本接口获取红包单号。详情参见[官方文档](http://mp.weixin.qq.com/wiki/7/0ddd50ed2421b99fedd071281c074aab.html#.E7.BA.A2.E5.8C.85.E9.A2.84.E4.B8.8B.E5.8D.95.E6.8E.A5.E5.8F.A3)


```php
<?php

$luckyMoneyData = [
    'hb_type'          => 'NORMAL',  //NORMAL 或 GROUP
    'mch_billno'       => 'xy123456',
    'send_name'        => '测试红包',
    're_openid'        => 'oxTWIuGaIt6gTKsQRLau2M0yL16E',
    'total_num'        => 1,  //普通红包固定为1,裂变红包不小于3
    'total_amount'     => 100,  //单位为分,普通红包不小于100,裂变红包不小于300
    'wishing'          => '祝福语',
    'client_ip'        => '192.168.0.1',  //可不传,不传则由 SDK 取当前客户端 IP
    'act_name'         => '测试活动',
    'remark'           => '测试备注',
    'amt_type'         => 'ALL_RAND',
    // ...
];

$result = $luckyMoney->prepare($luckyMoneyData);

```

## 查询红包信息

用于商户对已发放的红包进行查询红包的具体信息以及领取情况 ,普通红包和裂变包均使用这一接口进行查询。

```php
$mchBillNo = "商户系统内部的订单号(mch_billno)";
$luckyMoney->query($mchBillNo);
```


================================================
FILE: docs/src/3.x/material.md
================================================
# 素材管理


在微信里的图片,音乐,视频等等都需要先上传到微信服务器作为素材才可以在消息中使用。

> 请注意:

>     1. 限制:
>       - 图片(image): 1M,支持 bmp/png/jpeg/jpg/gif 格式
>       - 语音(voice):2M,播放长度不超过 60s,支持 mp3/wma/wav/amr 格式
>       - 视频(video):10MB,支持MP4格式
>       - 缩略图(thumb):64KB,支持JPG格式

>     2. `media_id` 是可复用的;
>     3. 素材分为 `临时素材` 与 `永久素材`, 临时素材媒体文件在后台保存时间为3天,即 3 天后 `media_id` 失效;
>     4. 新增的永久素材也可以在公众平台官网素材管理模块中看到;
>     5. 永久素材的数量是有上限的,请谨慎新增。图文消息素材和图片素材的上限为5000,其他类型为1000;

## 获取实例

```php
<?php
use EasyWeChat\Foundation\Application;

$app = new Application($options);

// 永久素材
$material = $app->material;
// 临时素材
$temporary = $app->material_temporary;
```

## 永久素材 API:

### 上传图片:

> 注意:微信图片上传服务有敏感检测系统,图片内容如果含有敏感内容,如色情,商品推广,虚假信息等,上传可能失败。

```php
$result = $material->uploadImage("/path/to/your/image.jpg");  // 请使用绝对路径写法!除非你正确的理解了相对路径(好多人是没理解对的)!
var_dump($result);
// {
//    "media_id":MEDIA_ID,
//    "url":URL
// }
```

> `url` 只有上传图片素材有返回值。

### 上传声音

语音**大小不超过 5M**,**长度不超过 60 秒**,支持 `mp3/wma/wav/amr` 格式。

```php
$result = $material->uploadVoice("/path/to/your/voice.mp3"); // 请使用绝对路径写法!除非你正确的理解了相对路径(好多人是没理解对的)!
$mediaId = $result->media_id;
// {
//    "media_id":MEDIA_ID,
// }
```

### 上传视频

```php
$result = $material->uploadVideo("/path/to/your/video.mp4", "视频标题", "视频描述"); // 请使用绝对路径写法!除非你正确的理解了相对路径(好多人是没理解对的)!
$mediaId = $result->media_id;
// {
//    "media_id":MEDIA_ID,
// }
```

### 上传缩略图

用于视频封面或者音乐封面。

```php
$result = $material->uploadThumb("/path/to/your/thumb.jpg"); // 请使用绝对路径写法!除非你正确的理解了相对路径(好多人是没理解对的)!
$mediaId = $result->media_id;
// {
//    "media_id":MEDIA_ID,
// }
```

### 上传永久图文消息

图文消息没有临时一说。

```php
use EasyWeChat\Message\Article;
// 上传单篇图文
$article = new Article([
    'title' => 'xxx',
    'thumb_media_id' => $mediaId,
    //...
  ]);
$material->uploadArticle($article);

// 或者多篇图文
$material->uploadArticle([$article, $article2, ...]);
```

### 修改永久图文消息

有三个参数:

- `$mediaId` 要更新的文章的 `mediaId`
- `$article` 文章内容,`Article` 实例或者 全字段数组
- `$index` 要更新的文章在图文消息中的位置(多图文消息时,此字段才有意义,单图片忽略此参数),第一篇为 0;

```php
$result = $material->updateArticle($mediaId, new Article(...));
$mediaId = $result->media_id;

// or

$result = $material->updateArticle($mediaId, [
    'title'          => 'xxx',
    'thumb_media_id' => 'xxx',
    // ...
  ]);

// 指定更新多图文中的第 2 篇
$result = $material->updateArticle($mediaId, new Article(...), 1); // 第 2 篇
```


### 上传永久文章内容图片

> 注意:微信图片上传服务有敏感检测系统,图片内容如果含有敏感内容,如色情,商品推广,虚假信息等,上传可能失败。

返回值中 url 就是上传图片的 URL,可用于后续群发中,放置到图文消息中。

```php
$result = $material->uploadArticleImage($path);
$url = $result->url;
//{
//    "url":  "http://mmbiz.qpic.cn/mmbiz/gLO17UPS6FS2xsypf378iaNhWacZ1G1UplZYWEYfwvuU6Ont96b1roYsCNFwaRrSaKTPCUdBK9DgEHicsKwWCBRQ/0"
//}
```

### 获取永久素材

```php
$resource = $material->get($mediaId);
```

如果请求的素材为图文消息,则响应如下:

```
{
 "news_item": [
       {
       "title":TITLE,
       "thumb_media_id"::THUMB_MEDIA_ID,
       "show_cover_pic":SHOW_COVER_PIC(0/1),
       "author":AUTHOR,
       "digest":DIGEST,
       "content":CONTENT,
       "url":URL,
       "content_source_url":CONTENT_SOURCE_URL
       },
       //多图文消息有多篇文章
    ]
  }
```

如果返回的是视频消息素材,则内容如下:

```
{
  "title":TITLE,
  "description":DESCRIPTION,
  "down_url":DOWN_URL,
}
```

其他类型的素材消息,则响应的直接为素材的内容,开发者可以自行保存为文件。例如

```
$image = $material->get($mediaId);
file_put_contents('/foo/abc.jpg', $image);
```

### 获取永久素材列表

参考:[微信公众平台开发者文档:获取永久素材列表](http://mp.weixin.qq.com/wiki/12/2108cd7aafff7f388f41f37efa710204.html)

- `$type`   素材的类型,图片(`image`)、视频(`video`)、语音 (`voice`)、图文(`news`)
- `$offset` 从全部素材的该偏移位置开始返回,可选,默认 `0`,0 表示从第一个素材 返回
- `$count`  返回素材的数量,可选,默认 `20`, 取值在 1 到 20 之间

```php
$material->lists($type, $offset, $count);
```

example:

```
$lists = $material->lists('image', 0, 10);
```

图片、语音、视频 等类型的返回如下

```
{
   "total_count": TOTAL_COUNT,
   "item_count": ITEM_COUNT,
   "item": [{
       "media_id": MEDIA_ID,
       "name": NAME,
       "update_time": UPDATE_TIME,
       "url":URL
   },
   //可能会有多个素材
   ]
}
```

永久图文消息素材列表的响应如下:

```
{
   "total_count": TOTAL_COUNT,
   "item_count": ITEM_COUNT,
   "item": [{
       "media_id": MEDIA_ID,
       "content": {
           "news_item": [{
               "title": TITLE,
               "thumb_media_id": THUMB_MEDIA_ID,
               "show_cover_pic": SHOW_COVER_PIC(0 / 1),
               "author": AUTHOR,
               "digest": DIGEST,
               "content": CONTENT,
               "url": URL,
               "content_source_url": CONTETN_SOURCE_URL
           },
           //多图文消息会在此处有多篇文章
           ]
        },
        "update_time": UPDATE_TIME
    },
    //可能有多个图文消息item结构
  ]
}
```


### 获取素材计数

```php
$stats = $material->stats();

// {
//   "voice_count":COUNT,
//   "video_count":COUNT,
//   "image_count":COUNT,
//   "news_count":COUNT
// }
```

### 删除永久素材;

```php
$material->delete($mediaId);
```


## 临时素材 API

上传的临时多媒体文件有格式和大小限制,如下:

- 图片(image): 1M,支持 `JPG` 格式
- 语音(voice):2M,播放长度不超过 `60s`,支持 `AMR\MP3` 格式
- 视频(video):10MB,支持 `MP4` 格式
- 缩略图(thumb):64KB,支持 `JPG` 格式

### 上传图片

> 注意:微信图片上传服务有敏感检测系统,图片内容如果含有敏感内容,如色情,商品推广,虚假信息等,上传可能失败。

```php
$temporary->uploadImage($path);
```

### 上传声音

```php
$temporary->uploadVoice($path);
```

### 上传视频

```php
$temporary->uploadVideo($path, $title, $description);
```

### 上传缩略图

用于视频封面或者音乐封面。

```php
$temporary->uploadThumb($path);
```

### 获取临时素材内容

比如图片、视频、声音等二进制流内容。

```php
$content = $temporary->getStream($mediaId);
file_put_contents('/tmp/abc.jpg', $content);// 请使用绝对路径写法!除非你正确的理解了相对路径(好多人是没理解对的)!
```

### 下载临时素材到本地

其实就是上一个 API 的封装。

```php
$temporary->download($mediaId, "/tmp/", "abc.jpg");
```

参数说明:

  - `$directory` 为目标目录,
  - `$filename` 为新的文件名,可以为空,默认使用 `$mediaId` 作为文件名。


更多请参考 [微信官方文档](http://mp.weixin.qq.com/wiki) `素材管理` 章节

================================================
FILE: docs/src/3.x/menu.md
================================================
# 自定义菜单


3.0 的菜单组件有所简化,相比 2.x 版本变化如下:

- 去除 `MenuItem` 类,创建菜单直接使用数组不再支持 `callback` 与 `MenuItem` 类似的繁杂的方式
- `set()` 方法与 `addConditional()` 合并为一个方法 `add()`
- `get()` 改名为 `all()`
- `delete()` 与 `deleteById()` 合并为 `destroy()`
- 所有 API 的返回值(非调用失败情况)均为官方文档原样返回(Collection形式),不再取返回值中部分 `key` 返回。
  > 例如原来的 `get()` 方法,官方返回的数组为: `{ menu: [...]}`,SDK 取了其中的 `menu` 内容作为返回值,在 3.0 后将直接整体返回。

## 获取菜单模块实例

```php
<?php
use EasyWeChat\Foundation\Application;

// ...

$app = new Application($options);

$menu = $app->menu;
```

## API 列表

### 读取(查询)已设置菜单

微信的菜单读取有两个不同的方式:

一种叫 **[查询菜单](http://mp.weixin.qq.com/wiki/5/f287d1a5b78a35a8884326312ac3e4ed.html)**,在 SDK 中以 `all()` 方法来调用:

```php
$menus = $menu->all();
```

另外一种叫 **[获取自定义菜单](http://mp.weixin.qq.com/wiki/14/293d0cb8de95e916d1216a33fcb81fd6.html)**,使用 `current()` 方法来调用:

```php
$menus = $menu->current();
```

### 添加菜单

#### 添加普通菜单

```php
$buttons = [
    [
        "type" => "click",
        "name" => "今日歌曲",
        "key"  => "V1001_TODAY_MUSIC"
    ],
    [
        "name"       => "菜单",
        "sub_button" => [
            [
                "type" => "view",
                "name" => "搜索",
                "url"  => "http://www.soso.com/"
            ],
            [
                "type" => "view",
                "name" => "视频",
                "url"  => "http://v.qq.com/"
            ],
            [
                "type" => "click",
                "name" => "赞一下我们",
                "key" => "V1001_GOOD"
            ],
        ],
    ],
];
$menu->add($buttons);
```

以上将会创建一个普通菜单。

#### 添加个性化菜单

与创建普通菜单不同的是,需要在 `add()` 方法中将个性化匹配规则作为第二个参数传进去:

```php
$buttons = [
    // ...
];
$matchRule = [
    "tag_id" => "2",
    "sex" => "1",
    "country" => "中国",
    "province" => "广东",
    "city" => "广州",
    "client_platform_type" => "2",
    "language" => "zh_CN"
];
$menu->add($buttons, $matchRule);
```

### 删除菜单

有两种删除方式,一种是**全部删除**,另外一种是**根据菜单 ID 来删除**(删除个性化菜单时用,ID 从查询接口获取):

```php
$menu->destroy(); // 全部
$menu->destroy($menuId);
```

### 测试个性化菜单

```php
$menus = $menu->test($userId);
```

> `$userId` 可以是粉丝的 OpenID,也可以是粉丝的微信号。

返回 `$menus` 与指定的 `$userId` 匹配的菜单项。

更多关于微信自定义菜单 API 请参考: http://mp.weixin.qq.com/wiki `自定义菜单` 章节。


================================================
FILE: docs/src/3.x/merchant_payment.md
================================================
# 企业支付


你在阅读本文之前确认你已经仔细阅读了:[微信支付 | 企业付款文档 ](https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_1)。

## 配置

与其他支付接口一样,企业支付接口也需要配置如下参数,需要特别注意的是,企业支付相关的全部接口 **都需要使用 SSL 证书**,因此 **cert_path 以及 cert_key 必须正确配置**。

```php
<?php

use EasyWeChat\Foundation\Application;

$options = [
    'app_id' => 'your-app-id',
    // payment
    'payment' => [
        'merchant_id'        => 'your-mch-id',
        'key'                => 'key-for-signature',
        'cert_path'          => 'path/to/your/cert.pem',
        'key_path'           => 'path/to/your/key',
        // ...
    ],
];

$app = new Application($options);

$merchantPay = $app->merchant_pay;
```

## 企业付款

企业付款使用的余额跟微信支付的收款并非同一账户,请注意充值。

### 发送接口

```php
<?php

$merchantPayData = [
        'partner_trade_no' => str_random(16), //随机字符串作为订单号,跟红包和支付一个概念。
        'openid' => $openid, //收款人的openid
        'check_name' => 'NO_CHECK',  //文档中有三种校验实名的方法 NO_CHECK OPTION_CHECK FORCE_CHECK
        're_user_name'=>'张三',     //OPTION_CHECK FORCE_CHECK 校验实名的时候必须提交
        'amount' => 100,  //单位为分
        'desc' => '企业付款',
        'spbill_create_ip' => '192.168.0.1',  //发起交易的IP地址
    ];
$result = $merchantPay->send($merchantPayData);

```

> 更多参数请参考官方文档中参数列表。

## 查询付款信息

用于商户对已发放的企业支付进行查询企业支付的具体信息。

```php
$partnerTradeNo = "商户系统内部的订单号(partner_trade_no)";
$merchantPay->query($partnerTradeNo);
```


================================================
FILE: docs/src/3.x/message-transfer.md
================================================
# 多客服消息转发



多客服的消息转发绝对是超级的简单,转发的消息类型为 `transfer`:

```php


  // 转发收到的消息给客服
  $server->setMessageHandler(function($message) {
      return new \EasyWeChat\Message\Transfer();
  });

  $result = $server->serve();

  echo $result;
```

当然,你也可以指定转发给某一个客服:

```php
$server->setMessageHandler(function($message) {
    $transfer = new \EasyWeChat\Message\Transfer();
    $transfer->account($account);// 或者 $transfer->to($account);

    return $transfer;
});
```

更多请参考 [微信官方文档](http://mp.weixin.qq.com/wiki/) **多客服消息转发** 章节

================================================
FILE: docs/src/3.x/messages.md
================================================
# 消息


我把微信的 API 里的所有“消息”都按类型抽象出来了,也就是说,你不用区分它是回复消息还是主动推送消息,免去了你去手动拼装微信那帮 SB 那么恶心的 XML 以及乱七八糟命名不统一的 JSON 了,我替你承受这份苦,不要问是谁,我是雷锋他弟弟,雷管。

在阅读以下内容时请忽略是**接收消息**还是**回复消息**,后面我会给你讲它们的区别。

## 消息类型

消息分为以下几种:`文本`、`图片`、`视频`、`声音`、`链接`、`坐标`、`图文`、`文章` 和一种特殊的 `原始消息`。

另外还有一种特殊的消息类型:**素材消息**,用于群发或者客服时发送已有素材用。

> 注意:回复消息与客服消息里的图文类型为:**图文**,群发与素材中的图文为**文章**

所有的消息类都在 `EasyWeChat\Message` 这个命名空间下, 下面我们来分开讲解:

### 文本消息

属性列表:

```
- content 文本内容
```

```php
<?php

use EasyWeChat\Message\Text;

$text = new Text(['content' => '您好!overtrue。']);

// or
$text = new Text();
$text->content = '您好!overtrue。';

// or
$text = new Text();
$text->setAttribute('content', '您好!overtrue。');
```

### 图片消息

属性列表:

```
- media_id 媒体资源 ID
```

```php
<?php

use EasyWeChat\Message\Image;

$text = new Image(['media_id' => $mediaId]);

// or
$text = new Image();
$text->media_id = $mediaId; // or $text->mediaId = $media;

// or
$text = new Image();
$text->setAttribute('media_id', $mediaId);
```


### 视频消息

属性列表:

```
- title 标题
- description 描述
- media_id 媒体资源 ID
- thumb_media_id 封面资源 ID
```

```php
<?php

use EasyWeChat\Message\Video;

$video = new Video([
        'title' => $title,
        'media_id' => $mediaId,
        'description' => '...',
        // ...
    ]);

// or
$video = new Video();
$video->media_id = $mediaId; // or $video->mediaId = $media;
$video->description = 'video description...'; // or $video->description = $description;
// ...

// or
$video = new Video();
$video->setAttribute('media_id', $mediaId);
// ...
```

### 声音消息

属性列表:

```
- media_id 媒体资源 ID
```

```php
<?php

use EasyWeChat\Message\Voice;

$voice = new Voice(['media_id' => $mediaId]);

// or
$voice = new Voice();
$voice->media_id = $mediaId; // or $voice->mediaId = $media;

// or
$voice = new Voice();
$voice->setAttribute('media_id', $mediaId);
```

### 链接消息

> 微信目前不支持回复链接消息

### 坐标消息

> 微信目前不支持回复坐标消息

### 图文消息

属性列表:

```
- title 标题
- description 描述
- image 图片链接
- url 链接 URL
```

```php
<?php
use EasyWeChat\Message\News;

$news = new News([
        'title'       => $title,
        'description' => '...',
        'url'         => $url,
        'image'       => $image,
        // ...
    ]);

// or
$news = new News();
$news->title = 'EasyWeChat';
$news->description = '微信 SDK ...';
// ...

```

### 文章消息

属性列表:

```
- title 标题
- author 作者
- content 具体内容
- thumb_media_id 图文消息的封面图片素材id(必须是永久mediaID)
- digest 图文消息的摘要,仅有单图文消息才有摘要,多图文此处为空
- source_url 来源 URL
- show_cover 是否显示封面,0 为 false,即不显示,1 为 true,即显示
```

```php
<?php
use EasyWeChat\Message\Article;

$article = new Article([
        'title'   => 'EasyWeChat',
        'author'  => 'overtrue',
        'content' => 'EasyWeChat 是一个开源的微信 SDK,它... ...',
        // ...
    ]);

// or
$article = new Article();
$article->title   = 'EasyWeChat';
$article->author  = 'overtrue';
$article->content = '微信 SDK ...';
// ...
```


### 素材消息

素材消息用于群发与客服消息时使用。

属性就一个:`media_id`。

在构造时有两个参数:

- `$type` 素材类型,目前只支持:`mpnews`、 `mpvideo`、`voice`、`image` 等。
- `$mediaId` 素材 ID,从接口查询或者上传后得到。


```php
use EasyWeChat\Message\Material;

$material = new Material('mpnews', $mediaId);
```

以上呢,是所有微信支持的基本消息类型。

> 需要注意的是,你不需要关心微信的消息字段叫啥,因为这里我们使用了更标准的命名,然后最终在中间做了转换,所以你不需要关注。

### 原始消息

原始消息是一种特殊的消息,它的场景是:**你不想使用其它消息类型,你想自己手动拼消息**。比如,回复消息时,你想自己拼 XML,那么你就直接用它就可以了:

```php
use EasyWeChat\Message\Raw;

$message = new Raw('<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[image]]></MsgType>
<Image>
<MediaId><![CDATA[media_id]]></MediaId>
</Image>
</xml>');
```

比如,你要用于客服消息(客服消息是JSON结构):

```php
use EasyWeChat\Message\Raw;

$message = new Raw('{
    "touser":"OPENID",
    "msgtype":"text",
    "text":
    {
         "content":"Hello World"
    }
}');
```

总之,就是直接写微信接口要求的格式内容就好,此类型消息在 SDK 中不存在转换行为,所以请注意不要写错格式。

## 在 SDK 中使用消息

### 在服务端回复消息

在 [服务端](server.html) 一节中,我们讲了回复消息的写法:

```php
// ... 前面部分省略
$app = new Application($options);
$server = $app->server;

$server->setMessageHandler(function ($message) {
    return "您好!欢迎关注我!";
});

$server->serve()->send();
```

上面 `return` 了一句普通的文本内容,这里只是为了方便大家,实际上最后会有一个隐式转换为 `Text` 类型的动作。

如果你要回复其它类型的消息,就需要返回一个具体的实例了,比如回复一个图片类型的消息:

```php
use EasyWeChat\Message\Image;
// ...
$server->setMessageHandler(function ($message) {
    return new Image(['media_id' => '........']);
});
// ...
```

#### 回复多图文消息

多图文消息其实就是单图文消息的一个数组而已了:

```php
use EasyWeChat\Message\News;

// ...
$server->setMessageHandler(function ($message) {
    $news1 = new News(...);
    $news2 = new News(...);
    $news3 = new News(...);
    $news4 = new News(...);

    return [$news1, $news2, $news3, $news4];
});
// ...
```


### 作为客服消息发送

在客服消息里的使用也一样,都是直接传入消息实例即可:

```php
use EasyWeChat\Message\Text;

$message = new Text(['content' => 'Hello world!']);

$result = $app->staff->message($message)->to($openId)->send();
//...
```

#### 发送多图文消息

多图文消息其实就是单图文消息的一个数组而已了:

```php
$news1 = new News(...);
$news2 = new News(...);
$news3 = new News(...);
$news4 = new News(...);

$app->staff->message([$news1, $news2, $news3, $news4])->to($openId)->send();
```

### 群发消息

请参考:[群发消息](broadcast.html)

## 消息转发给客服系统

参见:[多客服消息转发](message-transfer.html)


================================================
FILE: docs/src/3.x/mini_program.md
================================================
title: 小程序
---

## 实例化

```php
<?php
use EasyWeChat\Foundation\Application;

$options = [
    // ...
    'mini_program' => [
        'app_id'   => 'component-app-id',
        'secret'   => 'component-app-secret',
        'token'    => 'component-token',
        'aes_key'  => 'component-aes-key'
        ],
    // ...
    ];

$app = new Application($options);
$miniProgram = $app->mini_program;
```

## 登录

### 通过 Code 换取 SessionKey

```php
// 3.2 版本
$miniProgram->user->getSessionKey($code);
// 3.3 版本
$miniProgram->sns->getSessionKey($code);
```

## 加密数据解密

```php
$miniProgram->encryptor->decryptData($sessionKey, $iv, $encryptData);
```

## 数据分析

### API

- `summaryTrend($from, $to)` 概况趋势,限定查询1天数据,即 `$from` 要与 `$to` 相同;
- `dailyVisitTrend($from, $to)` 访问日趋势,限定查询1天数据,即 `$from` 要与 `$to` 相同;
- `weeklyVisitTrend($from, $to)` 访问周趋势, `$from` 为周一日期, `$to` 为周日日期;
- `monthlyVisitTrend($from, $to)` 访问月趋势, `$from` 为月初日期, `$to` 为月末日期;
- `visitDistribution($from, $to)` 访问分布,限定查询1天数据,即 `$from` 要与 `$to` 相同;
- `dailyRetainInfo($from, $to)` 访问日留存,限定查询1天数据,即 `$from` 要与 `$to` 相同;
- `weeklyRetainInfo($from, $to)` 访问周留存, `$from` 为周一日期, `$to` 为周日日期;
- `montylyRetainInfo($from, $to)` 访问月留存, `$from` 为月初日期, `$to` 为月末日期;
- `visitPage($from, $to)` 访问页面,限定查询1天数据,即 `$from` 要与 `$to` 相同;

### 代码示例

```php
$miniProgram->stats->summaryTrend('20170313', '20170313');
```


================================================
FILE: docs/src/3.x/miscellaneous.md
================================================
# 其它


### 其它

================================================
FILE: docs/src/3.x/notice.md
================================================
# 模板消息

模板消息仅用于公众号向用户发送重要的服务通知,只能用于符合其要求的服务场景中,如信用卡刷卡通知,商品购买成功通知等。不支持广告等营销类消息以及其它所有可能对用户造成骚扰的消息。

## 获取实例

```php
<?php
use EasyWeChat\Foundation\Application;
// ...
$app = new Application($options);

$notice = $app->notice;
```

### API

- `boolean setIndustry($industryId1, $industryId2)` 修改账号所属行业;
- `array getIndustry()` 返回所有支持的行业列表,用于做下拉选择行业可视化更新;
- `string  addTemplate($shortId)` 添加模板并获取模板 ID;
- `collection send($message)` 发送模板消息, 返回消息 ID;
- `array  getPrivateTemplates()` 获取所有模板列表;
- `array  deletePrivateTemplate($templateId)` 删除指定 ID 的模板。

非链接调用方法:

```php
$messageId = $notice->send([
        'touser' => 'user-openid',
        'template_id' => 'template-id',
        'url' => 'xxxxx',
        'data' => [
            //...
        ],
    ]);
```

链式调用方法:

    设置模板ID:template / templateId / uses
    设置接收者openId: to / receiver
    设置详情链接:url / link / linkTo
    设置模板数据:data / with

    以上方法都支持 `withXXX` 与 `andXXX` 形式链式调用

```php
$messageId = $notice->to($userOpenId)->uses($templateId)->andUrl($url)->data($data)->send();
// 或者
$messageId = $notice->to($userOpenId)->url($url)->template($templateId)->andData($data)->send();
// 或者
$messageId = $notice->withTo($userOpenId)->withUrl($url)->withTemplate($templateId)->withData($data)->send();
// 或者
$messageId = $notice->to($userOpenId)->url($url)->withTemplateId($templateId)->send();
// ... ...
```

## 示例:

### 模板

```
{{ first.DATA }}

商品明细:

名称:{{ name.DATA }}
价格:{{ price.DATA }}

{{ remark.DATA }}
```

发送模板消息:

```php
$userId = 'OPENID';
$templateId = 'ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY';
$url = 'http://overtrue.me';
$data = array(
         "first"  => "恭喜你购买成功!",
         "name"   => "巧克力",
         "price"  => "39.8元",
         "remark" => "欢迎再次购买!",
        );

$result = $notice->uses($templateId)->withUrl($url)->andData($data)->andReceiver($userId)->send();
var_dump($result);

// {
//      "errcode":0,
//      "errmsg":"ok",
//      "msgid":200228332
//  }
```

## 模板数据

为了方便大家开发,我们拓展支持以下格式的模板数据,其它格式的数据可能会导致接口调用失败:

- 所有数据项颜色一样的(这是方便的一种方式):

  ```php
  $data = array(
      "first"    => "恭喜你购买成功!",
      "keynote1" => "巧克力",
      "keynote2" => "39.8元",
      "keynote3" => "2014年9月16日",
      "remark"   => "欢迎再次购买!",
  );
  ```

  默认颜色为'#173177', 你可以通过 `defaultColor($color)` 来修改

- 独立设置每个模板项颜色的:

  - 简便型:

    ```php
    $data = array(
        "first"    => array("恭喜你购买成功!", '#555555'),
        "keynote1" => array("巧克力", "#336699"),
        "keynote2" => array("39.8元", "#FF0000"),
        "keynote3" => array("2014年9月16日", "#888888"),
        "remark"   => array("欢迎再次购买!", "#5599FF"),
    );
    ```

  - 复杂型(也是微信官方唯一支持的方式,估计没有人想这么用):

    ```php
    $data = array(
        "first"    => array("value" => "恭喜你购买成功!", "color" => '#555555'),
        "keynote1" => array("value" => "巧克力", "color" => "#336699"),
        "keynote2" => array("value" => "39.8元","color" => "#FF0000"),
        "keynote3" => array("value" => "2014年9月16日", "color" => "#888888"),
        "remark"   => array("value" => "欢迎再次购买!", "color" => "#5599FF"),
    );
    ```

关于模板消息的使用请参考 [微信官方文档](http://mp.weixin.qq.com/wiki/)


================================================
FILE: docs/src/3.x/oauth.md
================================================
# 网页授权

## 关于 OAuth2.0

OAuth 是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是 2.0 版。

```

     +--------+                               +---------------+
     |        |--(A)- Authorization Request ->|   Resource    |
     |        |                               |     Owner     |
     |        |<-(B)-- Authorization Grant ---|               |
     |        |                               +---------------+
     |        |
     |        |                               +---------------+
     |        |--(C)-- Authorization Grant -->| Authorization |
     | Client |                               |     Server    |
     |        |<-(D)----- Access Token -------|               |
     |        |                               +---------------+
     |        |
     |        |                               +---------------+
     |        |--(E)----- Access Token ------>|    Resource   |
     |        |                               |     Server    |
     |        |<-(F)--- Protected Resource ---|               |
     +--------+                               +---------------+
                      OAuth 授权流程
```

> 摘自:[RFC 6749](https://datatracker.ietf.org/doc/rfc6749/?include_text=1)

步骤解释:

    (A)用户打开客户端以后,客户端要求用户给予授权。
    (B)用户同意给予客户端授权。
    (C)客户端使用上一步获得的授权,向认证服务器申请令牌。
    (D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
    (E)客户端使用令牌,向资源服务器申请获取资源。
    (F)资源服务器确认令牌无误,同意向客户端开放资源。

关于 OAuth 协议我们就简单了解到这里,如果还有不熟悉的同学,请 [Google 相关资料](https://www.google.com.hk/?gws_rd=ssl#safe=strict&q=OAuth2)

## 微信 OAuth

在微信里的 OAuth 其实有两种:[公众平台网页授权获取用户信息](http://mp.weixin.qq.com/wiki/9/01f711493b5a02f24b04365ac5d8fd95.html)、[开放平台网页登录](https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN)。

它们的区别有两处,授权地址不同,`scope` 不同。

- **公众平台网页授权获取用户信息**

  **授权 URL**: `https://open.weixin.qq.com/connect/oauth2/authorize`
  **Scopes**: `snsapi_base` 与 `snsapi_userinfo`

- **开放平台网页登录**

  **授权 URL**: `https://open.weixin.qq.com/connect/qrconnect`
  **Scopes**: `snsapi_login`

他们的逻辑都一样:

1. 用户尝试访问一个我们的业务页面,例如: `/user/profile`
2. 如果用户已经登录,则正常显示该页面
3. 系统检查当前访问的用户并未登录(从 session 或者其它方式检查),则跳转到**跳转到微信授权服务器**(上面的两种中一种**授权 URL**),并告知微信授权服务器我的**回调 URL(redirect_uri=callback.php)**,此时用户看到蓝色的授权确认页面(`scope` 为 `snsapi_base` 时不显示)
4. 用户点击确定完成授权,浏览器跳转到**回调 URL**: `callback.php` 并带上 `code`: `?code=CODE&state=STATE`。
5. 在 `callback.php` 中得到 `code` 后,通过 `code` 再次向微信服务器请求得到 **网页授权 access_token** 与 `openid`
6. 你可以选择拿 `openid` 去请求 API 得到用户信息(可选)
7. 将用户信息写入 SESSION。
8. 跳转到第 3 步写入的 `target_url` 页面(`/user/profile`)。

> 看懵了?没事,使用 SDK,你不用管这么多。:smile:
>
> 注意,上面的第 3 步:redirect_uri=callback.php 实际上我们会在 `callback.php` 后面还会带上授权目标页面 `user/profile`,所以完整的 `redirect_uri` 应该是下面的这样的 PHP 去拼出来:`'redirect_uri='.urlencode('callback.php?target=user/profile')`
> 结果:redirect_uri=callback.php%3Ftarget%3Duser%2Fprofile

## 逻辑组成

从上面我们所描述的授权流程来看,我们至少有 3 个页面:

1. **业务页面**,也就是需要授权才能访问的页面。
2. **发起授权页**,此页面其实可以省略,可以做成一个中间件,全局检查未登录就发起授权。
3. **授权回调页**,接收用户授权后的状态,并获取用户信息,写入用户会话状态(SESSION)。

## 开始之前

在开始之前请一定要记住,先登录公众号后台,找到**边栏 “开发”** 模块下的 **“接口权限”**,点击 **“网页授权获取用户基本信息”** 后面的修改,添加你的网页授权域名。

> 如果你的授权地址为:`http://www.abc.com/xxxxx`,那么请填写 `www.abc.com`,也就是说请填写与网址匹配的域名,前者如果填写 `abc.com` 是通过不了的。

## SDK 中 OAuth 模块的 API

在 SDK 中,我们使用名称为 `oauth` 的模块来完成授权服务,我们主要用到以下两个 API:

### 发起授权

```php
$response = $app->oauth->scopes(['snsapi_userinfo'])
                          ->redirect();
```

当你的应用是分布式架构且没有会话保持的情况下,你需要自行设置请求对象以实现会话共享。比如在 [Laravel](http://laravel.com) 框架中支持 Session 储存在 Redis 中,那么需要这样:

```php
$response = $app->oauth->scopes(['snsapi_userinfo'])
                          ->setRequest($request)
                          ->redirect();

//回调后获取user时也要设置$request对象
//$user = $app->oauth->setRequest($request)->user();
```

它的返回值 `$response` 是一个 [Symfony\Component\HttpFoundation\RedirectResponse](http://api.symfony.com/3.0/Symfony/Component/HttpFoundation/RedirectResponse.html) 实例。

你可以选择在框架中做一些正确的响应,比如在 [Laravel](http://laravel.com) 框架中控制器方法是要求返回响应值的,那么你就直接:

```php
return $response;
```

在有的框架 (比如 yii2) 中是直接 `echo` 或者 `$this->display()` 这种的时候,你就直接:

```php
$response->send(); // Laravel 里请使用:return $response;
```

### 获取已授权用户

```php
$user = $app->oauth->user();
// $user 可以用的方法:
// $user->getId();  // 对应微信的 OPENID
// $user->getNickname(); // 对应微信的 nickname
// $user->getName(); // 对应微信的 nickname
// $user->getAvatar(); // 头像网址
// $user->getOriginal(); // 原始API返回的结果
// $user->getToken(); // access_token, 比如用于地址共享时使用
```

返回的 `$user` 是 [Overtrue\Socialite\User](https://github.com/overtrue/socialite/blob/master/src/User.php) 对象,你可以从该对象拿到[更多的信息](https://github.com/overtrue/socialite#user-interface)。

> :pray: 注意:`$user` 里没有 `openid`, `$user->id` 便是 `openid`.
> 如果你想拿微信返回给你的原样的全部信息,请使用:$user->getOriginal();

当 `scope` 为 `snsapi_base` 时 `$oauth->user();` 对象里只有 `id`,没有其它信息。

## 网页授权实例

我们这里来用原生 PHP 写法举个例子,`oauth_callback` 是我们的授权回调 URL (未 urlencode 编码的 URL), `user/profile` 是我们需要授权才能访问的页面,它的 PHP 代码如下:

```php
// http://easywechat.com/user/profile
<?php

use EasyWeChat\Foundation\Application;

$config = [
  // ...
  'oauth' => [
      'scopes'   => ['snsapi_userinfo'],
      'callback' => '/oauth_callback',
  ],
  // ..
];

$app = new Application($config);
$oauth = $app->oauth;

// 未登录
if (empty($_SESSION['wechat_user'])) {

  $_SESSION['target_url'] = 'user/profile';

  return $oauth->redirect();
  // 这里不一定是return,如果你的框架action不是返回内容的话你就得使用
  // $oauth->redirect()->send();
}

// 已经登录过
$user = $_SESSION['wechat_user'];

// ...

```

授权回调页:

```php
// http://easywechat.com/oauth_callback
<?php

use EasyWeChat\Foundation\Application;

$config = [
  // ...
];

$app = new Application($config);
$oauth = $app->oauth;

// 获取 OAuth 授权结果用户信息
$user = $oauth->user();

$_SESSION['wechat_user'] = $user->toArray();

$targetUrl = empty($_SESSION['target_url']) ? '/' : $_SESSION['target_url'];

header('location:'. $targetUrl); // 跳转到 user/profile
```

上面的例子呢都是基于 `$_SESSION` 来保持会话的,在微信客户端中,你可以结合 COOKIE 来存储,但是有效期平台不一样时间也不一样,好像 Android 的失效会快一些,不过基本也够用了。

更多关于微信网页授权 API 请参考: http://mp.weixin.qq.com/wiki/
更多开放平台网页登录请参考:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN


================================================
FILE: docs/src/3.x/open_platform.md
================================================
# 微信开放平台


### 实例化

```php
<?php
use EasyWeChat\Foundation\Application;

$options = [
    // ...
    'open_platform' => [
        'app_id'   => 'component-app-id',
        'secret'   => 'component-app-secret',
        'token'    => 'component-token',
        'aes_key'  => 'component-aes-key'
        ],
    // ...
    ];

$app = new Application($options);
$openPlatform = $app->open_platform;
```

### 监听微信服务器推送事件

公众号第三方平台推送的有四个事件:授权成功(`authorized`),授权更新(`updateauthorized`),授权取消(`unauthorized`),以及 `component_verify_ticket`。

本 SDK 默认处理方式为:

- `authorized` / `updateauthorized`: 获取授权方(Authorizer)的所有信息,并缓存 `authorizer_access_token` 和 `authorizer_refresh_token`,授权方的信息则需要开发者手动处理。
- `unauthorized`: 删除 `authorizer_access_token` 和 `authorizer_refresh_token` 的缓存。
- `component_verify_ticket`: 缓存 `component_veirfy_ticket`。

当然也允许自定义处理这些事件,不过以上默认处理仍然会先执行,为的是帮助开发者免去缓存的困扰。

```php
// 默认处理方式
$openPlatform->server->serve();

// 自定义处理
$openPlatform->server->setMessageHandler(function($event) {
    // 事件类型常量定义在 \EasyWeChat\OpenPlatform\Guard 类里
    switch ($event->InfoType) {
        case 'authorized':
            // ...
        case 'unauthorized':
            // ...
        case 'updateauthorized':
            // ...
        case 'component_verify_ticket':
            // ...
    }
});
$openPlatform->server->serve();

// 或者
$openPlatform->server->listen(function ($event) {
    switch ($event->InfoType) {
        // ...
    }
});
```

#### 授权成功,授权更新

这两个事件下,SDK 默认抓取了所有授权方所有的信息,并缓存 `authorizer_access_token` 和 `authorizer_refresh_token`,授权方的信息为原微信 API 的返回结果,由开发者自行处理,比如保存到数据库。

```php
// 自定义处理
// 其中 $event 变量里有微信推送事件本身的信息,也有授权方所有的信息。
$openPlatform->server->setMessageHandler(function($event) {
    // 事件类型常量定义在 \EasyWeChat\OpenPlatform\Guard 类里
    switch ($event->InfoType) {
        case 'authorized':
            // 授权信息,主要是 token 和授权域
            $info1 = $event->authorization_info;
            // 授权方信息,就是授权方公众号的信息了
            $info2 = $event->authorizer_info;
    }
});
```

目前 SDK 对这两个事件的处理方式没有区别。

#### 授权取消

SDK 默认处理:删除 `authorizer_access_token` 和 `authorizer_refresh_token` 的缓存。开发者可以自行处理数据库删除授权方信息等操作。

#### 推送 component_verify_ticket

在公众号第三方平台创建审核通过后,微信服务器会向其“授权事件接收URL”每隔10分钟定时推送 `component_verify_ticket`。SDK 内部已实现缓存 `component_veirfy_ticket`,无需开发者另行缓存。

注:需要在URL路由中写上触发代码,并且注册路由后需要等待微信服务器推送 `component_verify_ticket`,才能进行后续操作,否则报"Component verify ticket does not exists."

### 调用 API

#### 设置授权方的 App Id

开发者必须设置授权方来调用 API。

```php
$openPlatform = new Application($options)->open_platform;

// 加载授权方信息,比如 $authorizer = Authorizer::find($id);
$authorizerAppId = $authorizer->app_id;
$authorizerRefreshToken = $authorizer->refresh_token;

$app = $openPlatform->createAuthorizerApplication($authorizerAppId, $authorizerRefreshToken);
// 然后调用方法和普通调用一致。
// ...
```

### 授权 API

#### 获取预授权网址

```php
// 直接跳转
$response = $openPlatform->pre_auth->redirect('https://domain.com/callback');

// 获取跳转的链接
$response->getTargetUrl();
```

用户授权后会带上 `code` 跳转到 `redirect` 指定的链接。

#### 使用授权码换取公众号的接口调用凭据和授权信息

```php
// 使用授权码换取公众号的接口调用凭据和授权信息
// Optional: $authorizationCode 不传值时会自动获取 URL 中 auth_code 值
$openPlatform->getAuthorizationInfo($authorizationCode = null);
```

#### 获取授权方的公众号帐号基本信息

```php
$openPlatform->getAuthorizerInfo($authorizerAppId);
```

#### 获取授权方的选项设置信息

```php
$openPlatform->getAuthorizerOption($authorizerAppId, $optionName);
```

#### 设置授权方的选项信息

```php
$openPlatform->setAuthorizerOption($authorizerAppId, $optionName, $optionValue);
```


================================================
FILE: docs/src/3.x/overview.md
================================================
# EasyWeChat

## EasyWeChat 是什么?

EasyWeChat 是一个开源的 [微信](http://www.wechat.com) 非官方 SDK。

EasyWeChat 的安装非常简单,因为它是一个标准的 [Composer](https://getcomposer.org/) 包,这意味着任何满足下列安装条件的 PHP 项目支持 Composer 都可以使用它。

### 环境需求

- PHP >= 5.5.9 (其实你不必惊讶,PHP 7 的时代了)
- [PHP cURL 扩展](http://php.net/manual/en/book.curl.php)
- [PHP OpenSSL 扩展](http://php.net/manual/en/book.openssl.php)
- [PHP fileinfo 拓展](http://php.net/manual/en/book.fileinfo.php) 素材管理模块需要用到

### 加入我们

<a target="_blank" href="http://shang.qq.com/wpa/qunwpa?idkey=b4dcf3ec51a7e8c3c3a746cf450ce59895e5c4ec4fbcb0f80c2cd97c3c6e63e9"><img border="0" src="http://pub.idqqimg.com/wpa/images/group.png" alt="EasyWeChat SDK 交流群" title="EasyWeChat SDK 交流群"></a> ID: 319502940

> 为了避免广告及不看文档用户,加群需要付费,所以请使用 能支持群费的客户端。
> 另外:付费加群不代表我们有责任在群里回答你的问题,所以请认真阅读微信官方文档与 SDK 使用文档再使用,否则提的低级问题不会有人理你
> 不喜勿加,谢谢!
> 除非你发现了明确的 Bug,否则不要在群里 @ 我,否则直接 T 人(当然了,不退群费):pray:

你有以下两种方式加入到我们中来,为广大开发者提供更优质的免费开源的服务:

- **贡献代码**:我们 3.0 的代码都在 [overtrue/wechat](https://github.com/overtrue/wechat) ,你可以提交 PR 到任何一个项目,当然,前提是代码质量必须是 OK 的。
- **翻译或补充文档**:我们的文档在:[w7corp/EasyWeChat/docs](https://github.com/w7corp/easywechat/tree/master/docs),你可以选择补充文档或者参与英文文档的翻译,目前有 `zh-cn` 与 `en` 两个分支,你可以提交对应的 PR 到目标分支参与翻译工作。

### 开始之前

本 SDK 不是一个全新再造的东西,所以我不会从 0 开始教会你开发微信,你完全有必要在使用本 SDK 前做好以下工作:

- 具备 PHP 基础知识,不要连闭包是啥都不明白,可以参考我在知乎的回答: [想要开发自己的 PHP 框架需要那些知识储备?](http://www.zhihu.com/question/26635323/answer/33812516)
- 熟悉 PHP 常见的知识:自动加载、composer 的使用、JSON 处理、Curl 的使用等;
- **仔细阅读并看懂** (不是**看过**,是**看明白+看完** :exclamation:) [微信官方文档](http://mp.weixin.qq.com/wiki/13/80a1a25adbc46faf2716774c423b3151.html) [微信开放平台文档](https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419318292&token=&lang=zh_CN);
- 明白微信接口的组成,自有服务器、微信服务器、公众号(还有其它各种号)、测试号、以及通信原理(交互过程);
- 了解基本的 HTTP 协议,Header 头、请求方式(GET\POST\PUT\PATCH\DELETE)等;
- 基本的 Debug 技能,查看 php 日志,nginx 日志等。

如果你不具备这些知识,请不要使用,因为用起来会比较痛苦。

另外你有必要看一下以下的链接:

- https://phphub.org/topics/535
- http://laravel-china.github.io/php-the-right-way/

如果你在群里问以下类似的问题,这真的是你没有做好上面的工作:

- "为啥我的不行啊,请问服务器日志怎么看啊?"
- "请问这是什么原因啊?[结果/报错截图]"
- "请问这个 SDK 怎么用啊?"
- "谁能告诉我这个 SDK 是怎么安装的啊?"
- "怎么接收用户发的消息啊?"
- "为啥我的报这个错啊:Class XXXX not found..."
- ...

我们专门针对一些容易出现的通用问题已经做了汇总: [疑难解答](troubleshooting) ,如果你在问题疑难解答没找到你出现的问题,那么可以在这里提问 [GitHub](https://github.com/overtrue/wechat/issues),提问请描述清楚你用的版本,你的做法是什么,不然别人没法帮你。

最后,请 **不要在 QQ 单独找我提问**,除非你是发现了明显的 bug。有问题先审查代码,看文档, 再 google,然后 去群里发个问题,带上你的代码,重现流程,大家有空的会帮忙你解答。谢谢合作!:pray:

### 打赏支持

这是一个开源的项目,我们没有收费服务,你如果觉得你从中获益,简化了你的开发工作,你可以 [打赏](https://github.com/sponsors/overtrue) 来支持我们。


================================================
FILE: docs/src/3.x/payment.md
================================================
# 支付

你在阅读本文之前确认你已经仔细阅读了:[微信支付 | 商户平台开发文档](https://pay.weixin.qq.com/wiki/doc/api/index.html)。

网友贡献的教程:[小能手马闯 set 发布在 Laravel-China 的文章《基于 Laravel5.1 LTS 版的微信开发》](https://laravel-china.org/topics/3146)

## 配置

配置在前面的例子中已经提到过了,支付的相关配置如下:

```php
<?php

use EasyWeChat\Foundation\Application;

$options = [
    // 前面的appid什么的也得保留哦
    'app_id' => 'xxxx',
    // ...

    // payment
    'payment' => [
        'merchant_id'        => 'your-mch-id',
        'key'                => 'key-for-signature',
        'cert_path'          => 'path/to/your/cert.pem', // XXX: 绝对路径!!!!
        'key_path'           => 'path/to/your/key',      // XXX: 绝对路径!!!!
        'notify_url'         => '默认的订单回调地址',       // 你也可以在下单时单独设置来想覆盖它
        // 'device_info'     => '013467007045764',
        // 'sub_app_id'      => '',
        // 'sub_merchant_id' => '',
        // ...
    ],
];

$app = new Application($options);

$payment = $app->payment;
```

## 创建订单

### 正常模式

```php
<?php

use EasyWeChat\Payment\Order;

$attributes = [
    'trade_type'       => 'JSAPI', // JSAPI,NATIVE,APP...
    'body'             => 'iPad mini 16G 白色',
    'detail'           => 'iPad mini 16G 白色',
    'out_trade_no'     => '1217752501201407033233368018',
    'total_fee'        => 5388, // 单位:分
    'notify_url'       => 'http://easywechat.com/order-notify', // 支付结果通知网址,如果不设置则会使用配置里的默认地址
    'openid'           => '当前用户的 openid', // trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识,
    // ...
];

$order = new Order($attributes);

```

### 子服务商模式

```php
<?php

use EasyWeChat\Payment\Order;

$attributes = [
    'trade_type'       => 'JSAPI', // JSAPI,NATIVE,APP...
    'body'             => 'iPad mini 16G 白色',
    'detail'           => 'iPad mini 16G 白色',
    'out_trade_no'     => '1217752501201407033233368018',
    'total_fee'        => 5388, // 单位:分
    'notify_url'       => 'http://easywechat.com/order-notify', // 支付结果通知网址,如果不设置则会使用配置里的默认地址
    'sub_openid'        => '当前用户的 openid', // 如果传入sub_openid, 请在实例化Application时, 同时传入$sub_app_id, $sub_merchant_id
    // ...
];

$order = new Order($attributes);

```

通知 url 必须为直接可访问的 url,不能携带参数。示例:notify_url:“https://pay.weixin.qq.com/wxpay/pay.action”

## 下单接口

### 刷卡支付

[官方文档](https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10)

```php
$result = $payment->pay($order);
```

> 也许你需要生成二维码图片,参考页面底部相关的包推荐

## 统一下单

[公众号支付](https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1)、[扫码支付](https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_1)、[APP 支付](https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=9_1) 都统一使用此接口完成订单的创建。

```php
$result = $payment->prepare($order);
if ($result->return_code == 'SUCCESS' && $result->result_code == 'SUCCESS'){
    $prepayId = $result->prepay_id;
}
```

## 支付结果通知

在用户成功支付后,微信服务器会向该 **订单中设置的回调 URL** 发起一个 POST 请求,请求的内容为一个 XML。里面包含了所有的详细信息,具体请参考:
[支付结果通用通知](https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7)

在本 SDK 中处理回调真的再简单不过了,请求验证你就不用管了,SDK 已经为你做好了,你只需要关注业务即可:

```php
$response = $app->payment->handleNotify(function($notify, $successful){
    // 你的逻辑
    return true; // 或者错误消息
});

$response->send(); // Laravel 里请使用:return $response;
```

这里需要注意的有几个点:

1. `handleNotify` 只接收一个 [`callable`](http://php.net/manual/zh/language.types.callable.php) 参数,通常用一个匿名函数即可。
2. 该匿名函数接收两个参数,这两个参数分别为:

   - `$notify` 为封装了通知信息的 `EasyWeChat\Support\Collection` 对象,前面已经讲过这里就不赘述了,你可以以对象或者数组形式来读取通知内容,比如:`$notify->total_fee` 或者 `$notify['total_fee']`。
   - `$successful` 这个参数其实就是判断 **用户是否付款成功了**(result_code == 'SUCCESS')

3. 该函数返回值就是告诉微信 **“我是否处理完成”**,如果你返回一个 `false` 或者一个具体的错误消息,那么微信会在稍后再次继续通知你,直到你明确的告诉它:“我已经处理完成了”,在函数里 `return true;` 代表处理完成。

4. `handleNotify` 返回值 `$response` 是一个 Response 对象,如果你要直接输出,使用 `$response->send()`, 在一些框架里不是输出而是返回:`return $response`。

通常我们的处理逻辑大概是下面这样(**以下只是伪代码**):

```php
$response = $app->payment->handleNotify(function($notify, $successful){
    // 使用通知里的 "微信支付订单号" 或者 "商户订单号" 去自己的数据库找到订单
    $order = 查询订单($notify->out_trade_no);

    if (!$order) { // 如果订单不存在
        return 'Order not exist.'; // 告诉微信,我已经处理完了,订单没找到,别再通知我了
    }

    // 如果订单存在
    // 检查订单是否已经更新过支付状态
    if ($order->paid_at) { // 假设订单字段“支付时间”不为空代表已经支付
        return true; // 已经支付成功了就不再更新了
    }

    // 用户是否支付成功
    if ($successful) {
        // 不是已经支付状态则修改为已经支付状态
        $order->paid_at = time(); // 更新支付时间为当前时间
        $order->status = 'paid';
    } else { // 用户支付失败
        $order->status = 'paid_fail';
    }

    $order->save(); // 保存订单

    return true; // 返回处理完成
});

return $response;
```

> 注意:请把 “支付成功与否” 与 “是否处理完成” 分开,它俩没有必然关系。
> 比如:微信通知你用户支付完成,但是支付失败了(result_code 为 'FAIL'),你应该**更新你的订单为支付失败**,但是要**告诉微信处理完成**。

## 撤销订单 API

目前只有 **刷卡支付** 有此功能。

> 调用支付接口后请勿立即调用撤销订单 API,建议支付后至少 15s 后再调用撤销订单接口。

```php
$orderNo = "商户系统内部的订单号(out_trade_no)";
$payment->reverse($orderNo);
```

或者:

```php

$orderNo = "微信的订单号(transaction_id)";
$payment->reverseByTransactionId($orderNo);
```

## 查询订单

该接口提供所有微信支付订单的查询,商户可以通过该接口主动查询订单状态,完成下一步的业务逻辑。

需要调用查询接口的情况:

- 当商户后台、网络、服务器等出现异常,商户系统最终未接收到支付通知;
- 调用支付接口后,返回系统错误或未知交易状态情况;
- 调用被扫支付 API,返回 USERPAYING 的状态;
- 调用关单或撤销接口 API 之前,需确认支付状态;

```php
$orderNo = "商户系统内部的订单号(out_trade_no)";
$payment->query($orderNo);
```

或者:

```php

$orderNo = "微信的订单号(transaction_id)";
$payment->queryByTransactionId($orderNo);
```

## 关闭订单

> 注意:订单生成后不能马上调用关单接口,最短调用时间间隔为 5 分钟。

```php
$orderNo = "商户系统内部的订单号(out_trade_no)";
$payment->close($orderNo);
```

## 申请退款

当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,微信支付将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家帐号上。

注意:

> 1、交易时间超过一年的订单无法提交退款;
> 2、微信支付退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。一笔退款失败后重新提交,要采用原来的退款单号。总退款金额不能超过用户实际支付金额。

```php
$payment->refund(订单号,退款单号,总金额,退款金额,操作员,退款单号类型(out_trade_no/transaction_id),退款账户(REFUND_SOURCE_UNSETTLED_FUNDS/REFUND_SOURCE_RECHARGE_FUNDS))
```

参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4

例子:

```php
# 1. 使用商户订单号退款
$result = $payment->refund($orderNo, $refundNo, 100); // 总金额 100 退款 100,操作员:商户号
// or
$result = $payment->refund($orderNo, $refundNo, 100, 80); // 总金额 100, 退款 80,操作员:商户号
// or
$result = $payment->refund($orderNo, $refundNo, 100, 80, 1900000109); // 总金额 100, 退款 80,操作员:1900000109
// or
$result = $payment->refund($orderNo, $refundNo, 100, 80, 1900000109, 'out_trade_no'); // 总金额 100, 退款 80,操作员:1900000109, 退款单号:使用商户订单号退款
// or
$result = $payment->refund($orderNo, $refundNo, 100, 80, 1900000109, 'out_trade_no', 'REFUND_SOURCE_RECHARGE_FUNDS'); // 总金额 100, 退款 80,操作员:1900000109, 退款单号:使用商户订单号退款, 退款账户:可用余额退款

# 2. 使用 TransactionId 退款
$result = $payment->refundByTransactionId($transactionId, $refundNo, 100); // 总金额 100 退款 100,操作员:商户号
// or
$result = $payment->refundByTransactionId($transactionId, $refundNo, 100, 80); // 总金额 100, 退款 80,操作员:商户号
// or
$result = $payment->refundByTransactionId($transactionId, $refundNo, 100, 80, 1900000109); // 总金额 100, 退款 80,操作员:1900000109
// or
$result = $payment->refundByTransactionId($transactionId, $refundNo, 100, 80, 1900000109, 'REFUND_SOURCE_RECHARGE_FUNDS'); // 总金额 100, 退款 80,操作员:1900000109,退款账户:可用余额退款
```

> $refundNo 为商户退款单号,自己生成用于自己识别即可。

## 查询退款

提交退款申请后,通过调用该接口查询退款状态。退款有一定延时,用零钱支付的退款 20 分钟内到账,银行卡支付的退款 3 个工作日后重新查询退款状态。

```php
$result = $payment->queryRefund($outTradeNo);
// or
$result = $payment->queryRefundByTransactionId($transactionId);
// or
$result = $payment->queryRefundByRefundNo($outRefundNo);
// or
$result = $payment->queryRefundByRefundId($refundId);
```

## 下载对账单

```php
$bill = $payment->downloadBill('20140603')->getContents(); // type: ALL
// or
$bill = $payment->downloadBill('20140603', 'SUCCESS')->getContents(); // type: SUCCESS
// bill 为 csv 格式的内容

// 保存为文件
file_put_contents('YOUR/PATH/TO/bill-20140603.csv', $bill);
```

第二个参数为类型:

- **ALL**:返回当日所有订单信息(默认值)
- **SUCCESS**:返回当日成功支付的订单
- **REFUND**:返回当日退款订单
- **REVOKED**:已撤销的订单

## 测速上报

```php
$payment->report($api, $timeConsuming, $resultCode, $returnCode);
// or
$payment->report($api, $timeConsuming, $resultCode, $returnCode, [
        'err_code'     => 'xxxx',
        'err_code_des' => '...',
        'out_trade_no' => '...',
        'user_ip'      => '...',
    ]);
```

## 转换短链接

```php
$shortUrl = $payment->urlShorten('http://easywechat.com');
```

## 授权码查询 OPENID 接口

```php
$response = $payment->authCodeToOpenId($authCode);
$response->openid;
```

## 生成支付 JS 配置

有两种发起支付的方式:[WeixinJSBridge](https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6), [JSSDK](https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN)

1. WeixinJSBridge:

   ```php
   $json = $payment->configForPayment($prepayId); // 返回 json 字符串,如果想返回数组,传第二个参数 false
   ```

   javascript:

   ```js
   ...
   WeixinJSBridge.invoke(
          'getBrandWCPayRequest', <?= $json ?>,
          function(res){
              if(res.err_msg == "get_brand_wcpay_request:ok" ) {
                   // 使用以上方式判断前端返回,微信团队郑重提示:
                   // res.err_msg将在用户支付成功后返回
                   // ok,但并不保证它绝对可靠。
              }
          }
      );
   ...
   ```

2. JSSDK:

   ```php
   $config = $payment->configForJSSDKPayment($prepayId); // 返回数组
   ```

   javascript:

   ```js
   wx.chooseWXPay({
       timestamp: <?= $config['timestamp'] ?>,
       nonceStr: '<?= $config['nonceStr'] ?>',
       package: '<?= $config['package'] ?>',
       signType: '<?= $config['signType'] ?>',
       paySign: '<?= $config['paySign'] ?>', // 支付签名
       success: function (res) {
           // 支付成功后的回调函数
       }
   });
   ```

## 生成共享收货地址 JS 配置

1. 发起 OAuth 授权:

```php
use EasyWeChat\Support\Url as UrlHelper;

// 检查当前不是微信 oauth 的回调,则跳过去授权
// 注意,授权回调地址为当前页
if (empty($_GET['code'])) {
    $currentUrl = UrlHelper::current(); // 获取当前页 URL
    $response = $app->oauth->scopes(['snsapi_base'])->redirect($currentUrl);

    return $response; // or echo $response;

}
// 授权回来
$oauthUser = $app->oauth->user();
$token = $oauthUser->getAccessToken();
$configForPickAddress = $payment->configForShareAddress($token);

// 拿着这个生成好的配置 $configForPickAddress 去订单页(或者直接显示订单页)写 js 调用了
// ...
```

## 生成 APP 支付配置

```php
$config = $payment->configForAppPayment($prepayId);
```

`$config` 为数组格式,你可以用 API 返回给客户端

# 二维码生成工具推荐

你也许需要生成二维码,那么以下这些供参考:

- https://github.com/endroid/QrCode
- https://github.com/Bacon/BaconQrCode
- https://github.com/SimpleSoftwareIO/simple-qrcode (Bacon/BaconQrCode 的 Laravel 版本)
- https://github.com/aferrandini/PHPQRCode


================================================
FILE: docs/src/3.x/poi.md
================================================
# 门店

## 获取实例

```php
<?php
use EasyWeChat\Foundation\Application;

// ...

$app = new Application($options);

$poi = $app->poi;
```

## 创建门店

用 POI 接口新建门店时所使用的图片 url 必须为微信自己域名的 url,因此需要先用上传图片接 口上传图片并获取 url,再创建门店。上传的图片限制文件大小限制 1MB,支持 JPG 格式,图片接口请参考:[TODO](/)

```php
$poi->create($baseInfo);
```

- `$baseInfo` 为门店的基本信息数组

example:

```php
<?php

$info = array(
         "sid"             => "33788392",
         "business_name"   => "麦当劳",
         "branch_name"     => "艺苑路店",
         "province"        => "广东省",
         "city"            => "广州市",
         "district"        => "海珠区",
         "address"         => "艺苑路 11 号",
         "telephone"       => "020-12345678",
         "categories"      => array("美食,快餐小吃"),
         "offset_type"     => 1,
         "longitude"       => 115.32375,
         "latitude"        => 25.097486,
         "photo_list"      => array(
                               array("photo_url" => "https://easywechat.com"),
                               array("photo_url" => "https://easywechat.com"),
                             ),
         "recommend"       => "麦辣鸡腿堡套餐,麦乐鸡,全家桶",
         "special"         => "免费 wifi,外卖服务",
         "introduction"    => "麦当劳是全球大型跨国连锁餐厅,1940 年创立于美国,在世界上大约拥有 3  万间分店。主要售卖汉堡包,以及薯条、炸鸡、汽水、冰品、沙拉、水果等 快餐食品",
         "open_time"       => "8:00-20:00",
         "avg_price"       => 35,
    );

$result = $poi->create($info); // true or exception
```

> 注意:新创建的门店在审核通过后,会以事件形式推送给商户填写的回调 URL

## 获取指定门店信息

```php
$poi->get($poiId);
```

- `$poiId` 为门店 ID

example:

```php
$info = $poi->get(271262077);
var_dump($info->business_name); // 麦当劳
var_dump($info->introduction); // 麦当劳是全球大型跨国连锁餐厅...
var_dump($info->toArray());// array('business_name' => '麦当劳', 'branch_name' => '艺苑路店', ...);
```

## 获取门店列表

```php
$poi->lists($begin, $limit);// begin:0, limit:10
```

- `$begin` 就是查询起点,`MySQL` 里的 `offset`;
- `$limit` 查询条数,同 `MySQL` 里的 `limit`;

> 两参数均可选

example:

```php
$pois = $poi->lists(0, 2);// 取2条记录
//
//[
//  {
//    "sid": "100",
//    "poi_id": "271864249",
//    "business_name": "麦当劳",
//    "branch_name": "艺苑路店",
//    "address": "艺苑路 11 号",
//    "available_state": 3
//  },
//  {
//    "sid": "101",
//    "business_name": "麦当劳",
//    "branch_name": "赤岗路店",
//    "address": "赤岗路 102 号",
//    "available_state": 4
//  }
//]
```

## 修改门店信息

商户可以通过该接口,修改门店的服务信息,包括:图片列表、营业时间、推荐、特色服务、简 介、人均价格、电话 7 个字段。目前基础字段包括(名称、坐标、地址等不可修改)。

```php
$poi->update($poiId, $data);
```

- `$poiId` 为门店 ID
- `$data` 需要更新的部分数据,**若有填写内容则为覆盖更新,若无内容则视为不 修改,维持原有内容。photo_list 字段为全列表覆盖,若需要增加图片,需将之前图片同样放入 list 中,在其后增加新增图片。如:已有 A、B、C 三张图片,又要增加 D、E 两张图,则需要调 用该接口,photo_list 传入 A、B、C、D、E 五张图片的链接。**

example:

```php
$data = array(
         "telephone" => "020-12345678",
         "recommend" => "麦辣鸡腿堡套餐,麦乐鸡,全家桶",
         //...
        );

$res = $poi->update(271262077, $data); //true or exception
```

## 删除门店

```php
$poi->delete($poiId);
```

example:

```php
$poi->delete(271262077);// true or exception
```

## 错误码

- `invalid categories` 分类不合法,必须严格按照附表的分类填写
- `invalid photo url` 图片 url 不合法,必须使用接口 1 的图片上传 接口所获取的 url
- `poi audit state must be approved` 门店状态必须未审核通过
- `invalid poiid` poi_id 不正确
- `invalid args` 参数不正确,请检查 json 字段
- `system error` 系统错误,请稍后重试


================================================
FILE: docs/src/3.x/qrcode.md
================================================
# 二维码


目前有2种类型的二维码:

1. 临时二维码,是有过期时间的,最长可以设置为在二维码生成后的**30天**后过期,但能够生成较多数量。临时二维码主要用于帐号绑定等不要求二维码永久保存的业务场景
2. 永久二维码,是无过期时间的,但数量较少(目前为最多10万个)。永久二维码主要用于适用于帐号绑定、用户来源统计等场景。

## 获取实例

```php
<?php
use EasyWeChat\Foundation\Application;

// ...

$app = new Application($options);

$qrcode = $app->qrcode;
```


## API

+ `Bag temporary($sceneId, $expireSeconds = null)` 创建临时二维码;
+ `Bag forever($sceneValue)` 创建永久二维码
+ `Bag card(array $card)` 创建卡券二维码
+ `string url($ticket)` 获取二维码网址,用法: `<img src="<?php $qrcode->url($qrTicket); ?>">`;

### 创建临时二维码

```php
$result = $qrcode->temporary(56, 6 * 24 * 3600);

$ticket = $result->ticket;// 或者 $result['ticket']
$expireSeconds = $result->expire_seconds; // 有效秒数
$url = $result->url; // 二维码图片解析后的地址,开发者可根据该地址自行生成需要的二维码图片
```

### 创建永久二维码

```php
$result = $qrcode->forever(56);// 或者 $qrcode->forever("foo");

$ticket = $result->ticket; // 或者 $result['ticket']
$url = $result->url;
```

### 获取二维码网址

```php
$url = $qrcode->url($ticket);
```

### 创建卡券二维码

```php
$qrcode->card($card);
```

### 获取二维码内容

```php
$url = $qrcode->url($ticket);

$content = file_get_contents($url); // 得到二进制图片内容

file_put_contents(__DIR__ . '/code.jpg', $content); // 写入文件
```


================================================
FILE: docs/src/3.x/releases.md
================================================
# 升级日志


## 3.0

- 新的架构
- 重写代码
- 更低的耦合
- 更规范的代码
- 更友好的调试支持
- 更完善的文档

================================================
FILE: docs/src/3.x/reply.md
================================================
# 自动回复


## 获取实例

```php
<?php
use EasyWeChat\Foundation\Application;

// ...

$app = new Application($options);

$reply = $app->reply;
```

## 获取当前设置的回复规则

```php
$reply->current();
```

================================================
FILE: docs/src/3.x/roadmap.md
================================================
# 路线图

## 3.1

- 微信小店
- 新的卡券
- 设备管理

## 3.0

- 全新的架构,更清晰的模块拆分
- Debug 优化,便于快速定位问题
- 统一返回微信 API 原值,以 Collection 类装载,便于便捷操作
- 大量模块重写
- 全新的支付模块
- 多类型缓存系统
- 新的命名空间:EasyWeChat
- 专属网站与更清晰与文档:http://easywechat.com

## 2.1

- 新增支付与红包

## 2.0

- 仅问题修复,不再增加新功能;

## 1.0

- 不推荐使用。
- 已停止维护。


================================================
FILE: docs/src/3.x/semantic.md
================================================
# 语义理解


微信开放平台语义理解接口调用(http请求)简单方便,用户无需掌握语义理解及相关技术,只需根据自己的产品特点,选择相应的服务即可搭建一套智能语义服务。

## 获取实例

```php
<?php

// ... 前面部分省略

$app = new Application($options);

$semantic = $app->semantic;
```

## API

+ `query($keyword, $categories, $other)` 语义理解:

  + `$keyword` 为关键字
  + `$categories` 需要使用的服务类型,数组或者多个用 “,” 隔开字符吕,不能为空;
  + `$other` 为其它属性:
    + `latitude`  `float`  纬度坐标,与经度同时传入;与城市二选一传入
    + `longitude`  `float`  经度坐标,与纬度同时传入;与城市二选一传入
    + `city`   `string`  城市名称,与经纬度二选一传入
    + `region` `string`  区域名称,在城市存在的情况下可省;与经纬度二选一传入
    + `uid`  `string` 用户唯一id(非开发者id),用户区分公众号下的不同用户(建议填入用户openid),如果为空,则无法使用上下文理解功能。appid和uid同时存在的情况下,才可以使用上下文理解功能。

> 注:单类别意图比较明确,识别的覆盖率比较大,所以如果只要使用特定某个类别,建议将category只设置为该类别。

example:

```php
$result = $semantic->query('查一下明天从北京到上海的南航机票', "flight,hotel", array('city' => '北京', 'uid' => '123456'));
// 查询参数:
// {
//    "query":"查一下明天从北京到上海的南航机票",
//    "city":"北京",
//    "category": "flight,hotel",
//    "appid":"wxaaaaaaaaaaaaaaaa",
//    "uid":"123456"
// }
```
返回值示例:

```json
{
    "errcode":0,
    "query":"查一下明天从北京到上海的南航机票",
    "type":"flight",
    "semantic":{
        "details":{
            "start_loc":{
                "type":"LOC_CITY",
                "city":"北京市",
                "city_simple":"北京",
                "loc_ori":"北京"
                },
            "end_loc": {
                "type":"LOC_CITY",
                "city":"上海市",
                "city_simple":"上海",
                "loc_ori":"上海"
              },
            "start_date": {
                "type":"DT_ORI",
                "date":"2014-03-05",
                "date_ori":"明天"
              },
           "airline":"中国南方航空公司"
        },
    "intent":"SEARCH"
}
```

更多详细内容与协议说明,请查看 [微信官方文档](http://mp.weixin.qq.com/wiki/)

================================================
FILE: docs/src/3.x/server.md
================================================
# 服务端


我们在入门小教程一节以服务端为例讲解了一个基本的消息的处理,这里就不再讲服务器验证的流程了,请直接参考前面的入门实例即可。

服务端的作用呢,在整个微信开发中主要是负责 **[接收用户发送过来的消息](http://mp.weixin.qq.com/wiki/10/79502792eef98d6e0c6e1739da387346.html)**,还有 **[用户触发的一系列事件](http://mp.weixin.qq.com/wiki/2/5baf56ce4947d35003b86a9805634b1e.html)**。

首先我们得厘清一下消息与事件的回复,当你收到用户消息后(消息由微信服务器推送到你的服务器),在你对消息进行一些处理后,不管是选择回复一个消息还是什么不都回给用户,你也应该给微信服务器一个 “答复”,如果是选择回复一条消息,就直接返回一个消息xml就好,如果选择不作任何回复,你也得回复一个空字符串或者字符串 `SUCCESS`(不然用户就会看到 `该公众号暂时无法提供服务`)。

## 基本使用

在 SDK 中呢,使用 `setMessageHandler(callable $callback)` 来设置消息处理函数:

```php
<?php
use EasyWeChat\Foundation\Application;

// ...

$app = new Application($options);

// 从项目实例中得到服务端应用实例。
$server = $app->server;

$server->setMessageHandler(function ($message) {
    // $message->FromUserName // 用户的 openid
    // $message->MsgType // 消息类型:event, text....
    return "您好!欢迎关注我!";
});

$response = $server->serve();

$response->send(); // Laravel 里请使用:return $response;
```

这里我们使用 `setMessageHandler` 传入了一个 **闭包([Closure](http://php.net/manual/en/class.closure.php))**,该闭包接收一个参数 `$message` 为消息对象(Collection),这里需要注意的时,与 2.0 不同,2.0 当中我们对消息与事件做了区分,还对消息进行了分类(按 MsgType)。在 3.0 后,**所有的消息包括事件都会使用 `setMessageHandler` 来处理**,也就是说你可能需要在里面进行一些判断,例如:

```php
$server->setMessageHandler(function ($message) {
    switch ($message->MsgType) {
        case 'event':
            return '收到事件消息';
            break;
        case 'text':
            return '收到文字消息';
            break;
        case 'image':
            return '收到图片消息';
            break;
        case 'voice':
            return '收到语音消息';
            break;
        case 'video':
            return '收到视频消息';
            break;
        case 'location':
            return '收到坐标消息';
            break;
        case 'link':
            return '收到链接消息';
            break;
        // ... 其它消息
        default:
            return '收到其它消息';
            break;
    }

    // ...
});
```

当然,因为这里 `setMessageHandler` 接收一个 [`callable`](http://php.net/manual/zh/language.types.callable.php) 的参数,所以你不一定要传入一个 Closure 闭包,你可以选择传入一个函数名,一个 `[$class, $method]` 或者 `Foo::bar` 这样的类型。

> :heart: 注意,默认没有验证是否为微信的请求,部署上线建议关掉 debug 模式。

某些情况,我们需要直接使用 `$message` 参数,那么怎么在 `setMessageHandler` 闭包外调用呢?

```php
    $message = $server->getMessage();
```
> 注意:`$message` 是一个数组类型的数据,使用的时候这样使用:`$message['ToUserName']`

## 请求消息的属性

当你接收到用户发来的消息时,可能会提取消息中的相关属性,那么请参考:

请求消息基本属性(以下所有消息都有的基本属性):

    $message->ToUserName    接收方帐号(该公众号 ID)
    $message->FromUserName  发送方帐号(OpenID, 代表用户的唯一标识)
    $message->CreateTime    消息创建时间(时间戳)
    $message->MsgId         消息 ID(64位整型)

### 文本:

    $message->MsgType  text
    $message->Content  文本消息内容

### 图片:

    $message->MsgType  image
    $message->PicUrl   图片链接

### 语音:

    $message->MsgType        voice
    $message->MediaId        语音消息媒体id,可以调用多媒体文件下载接口拉取数据。
    $message->Format         语音格式,如 amr,speex 等
    $message->Recognition * 开通语音识别后才有

    > 请注意,开通语音识别后,用户每次发送语音给公众号时,微信会在推送的语音消息XML数据包中,增加一个 `Recongnition` 字段

### 视频:

    $message->MsgType       video
    $message->MediaId       视频消息媒体id,可以调用多媒体文件下载接口拉取数据。
    $message->ThumbMediaId  视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据。

### 小视频:

    $message->MsgType     shortvideo
    $message->MediaId     视频消息媒体id,可以调用多媒体文件下载接口拉取数据。
    $message->ThumbMediaId    视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据。

### 事件:

    $message->MsgType     event
    $message->Event       事件类型 (如:subscribe(订阅)、unsubscribe(取消订阅) ..., CLICK 等)

    # 扫描带参数二维码事件
    $message->EventKey    事件KEY值,比如:qrscene_123123,qrscene_为前缀,后面为二维码的参数值
    $message->Ticket      二维码的 ticket,可用来换取二维码图片

    # 上报地理位置事件
    $message->Latitude    23.137466   地理位置纬度
    $message->Longitude   113.352425  地理位置经度
    $message->Precision   119.385040  地理位置精度

    # 自定义菜单事件
    $message->EventKey    事件KEY值,与自定义菜单接口中KEY值对应,如:CUSTOM_KEY_001, www.qq.com

### 地理位置:

    $message->MsgType     location
    $message->Location_X  地理位置纬度
    $message->Location_Y  地理位置经度
    $message->Scale       地图缩放大小
    $message->Label       地理位置信息

### 链接:

    $message->MsgType      link
    $message->Title        消息标题
    $message->Description  消息描述
    $message->Url          消息链接

## 回复消息

回复的消息可以为 `null`,此时 SDK 会返回给微信一个 "SUCCESS",你也可以回复一个普通字符串,比如:`欢迎关注 overtrue.`,此时 SDK 会对它进行一个封装,产生一个 [`EasyWeChat\Message\Text`](https://github.com/EasyWeChat/message/blob/master/src/Text.php) 类型的消息并在最后的 `$server->serve();` 时生成对应的消息 XML 格式。

如果你想返回一个自己手动拼的原生 XML 格式消息,请返回一个 [`EasyWeChat\Message\Raw`](https://github.com/EasyWeChat/message/blob/master/src/Raw.php) 实例即可。

## 消息转发给客服系统

参见:[多客服消息转发](message-transfer.html)

关于消息的使用,请参考 [`消息`](messages.html) 章节。


================================================
FILE: docs/src/3.x/shake-around.md
================================================
# 摇一摇周边


摇一摇周边是微信在线下的全新功能, 为线下商户提供近距离连接用户的能力, 并支持线下商户向周边用户提供个性化营销、互动及信息推荐等服务。

## 获取实例

```php
<?php
use EasyWeChat\Foundation\Application;
// ...
$app = new Application($options);

$shakearound = $app->shakearound;

```

## API

> 特别提醒:
1、下述所有的接口调用的方法参数都要严格按照方法参数前的类型传入相应类型的实参,否则可能会得到非预期的结果。
2、涉及需要传入设备id($deviceIdentifier)的参数时,该参数是一个以 `device_id` 或包含 `uuid` `major` `minor` 为key的关联数组。
3、涉及需要传入设备id列表($deviceIdentifiers)的参数时,该参数是一个二维数组,第一层为索引类型,第二层为关联类型($deviceIdentifier)。

```php
// 参数$deviceIdentifier的实参形式:
['device_id' => 10097]
// 或
[
    'uuid' => 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
    'major' => 10001,
    'minor' => 12102,
]
// 参数$deviceIdentifiers的实参形式:
[
    ['device_id' => 10097],
    ['device_id' => 10098],
]
// 或
[
    [
        'uuid' => 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
        'major' => 10001,
        'minor' => 12102,
    ],
    [
        'uuid' => 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
        'major' => 10001,
        'minor' => 12103,
    ]
]
```

### 开通摇一摇周边

> 提示:
若不是做 [公众号第三方平台](https://open.weixin.qq.com/cgi-bin/frame?t=home/wx_plugin_tmpl&lang=zh_CN) 开发,建议直接在微信管理后台申请开通摇一摇周边功能。

#### 申请开通

申请开通摇一摇周边功能。成功提交申请请求后,工作人员会在三个工作日内完成审核。若审核不通过,可以重新提交申请请求。若是审核中,请耐心等待工作人员审核,在审核中状态不能再提交申请请求。

方法

> $shakearound->register(string $name, string $tel, string $email, string $industryId, array $certUrls [, $reason = ''])

参数

> $name 联系人姓名,不超过20汉字或40个英文字母
$tel 联系人电话
$email 联系人邮箱
$industryId 平台定义的行业代号,具体请查看链接 [行业代号](http://3gimg.qq.com/shake_nearby/Qualificationdocuments.html)
$certUrls 相关资质文件的图片url,图片需先上传至微信侧服务器,用“素材管理-上传图片素材”接口上传图片,返回的图片URL再配置在此处;当不需要资质文件时,请传入空数组
$reason 可选,申请理由,不超过250汉字或500个英文字母

> 注意:
1、相关资质文件的图片是使用本页面下方的素材管理的接口上传的,切勿和另一个 [素材管理](material.html) 接口混淆。
2、行业代码请务必传入**字符串**类型的实参,否则以数字0开头的行业代码将会被当成八进制数处理(将转换为十进制数),这可能不是期望的。

示例

```php
$result = $shakearound->register('zhang_san', '13512345678', 'weixin123@qq.com', '0118', [], 'test');

/* 返回结果
{
   "data": {

   },
   "errcode": 0,
   "errmsg": "success."
}
*/
var_dump($result->data) // 空数组
var_dump($result->errcode) // 0
var_dump($result->errmsg) // success.
```

#### 查询审核状态

查询已经提交的开通摇一摇周边功能申请的审核状态。在申请提交后,工作人员会在三个工作日内完成审核。

方法

> $shakearound->getStatus()

参数

> 无

示例

```php
$result = $shakearound->getStatus();

/* 返回结果
{
    "data": {
        "apply_time": 1432026025,
        "audit_comment": "test",
        "audit_status": 1,
        "audit_time": 0
    },
    "errcode": 0,
    "errmsg": "success."
}
*/
var_dump($result->data['audit_comment']) // test
```

#### 获取摇一摇的设备及用户信息

获取设备信息,包括UUID、major、minor,以及距离、openID等信息。

方法

> $shakearound->getShakeInfo(string $ticket [, int $needPoi = null])

参数

> $ticket 摇周边业务的ticket,可在摇到的URL中得到,ticket生效时间为30分钟,每一次摇都会重新生成新的ticket
$needPoi 可选,是否需要返回门店poi_id,传1则返回,否则不返回

示例

```php
$result = $shakearound->getShakeInfo('6ab3d8465166598a5f4e8c1b44f44645', 1);

/* 返回结果
{
   "data": {
       "page_id ": 14211,
       "beacon_info": {
           "distance": 55.00620700469034,
           "major": 10001,
           "minor": 19007,
           "uuid": "FDA50693-A4E2-4FB1-AFCF-C6EB07647825"
       },
       "openid": "oVDmXjp7y8aG2AlBuRpMZTb1-cmA",
       "poi_id":1234
   },
   "errcode": 0,
   "errmsg": "success."
}
*/
var_dump($result->data['page_id']) // 14211
var_dump($result->data['beacon_info']['distance']) // 55.00620700469034
```

### 设备管理

#### 申请设备ID

申请配置设备所需的UUID、Major、Minor。申请成功后返回批次ID,可用返回的批次ID通过“查询设备ID申请状态”接口查询目前申请的审核状态。
一个公众账号最多可申请100000个设备ID,如需申请的设备ID数超过最大限额,请邮件至zhoubian@tencent.com,邮件格式如下:

> 标题:申请提升设备ID额度
内容:
1、公众账号名称及appid(wx开头的字符串,在mp平台可查看)
2、用途
3、预估需要多少设备ID

方法

> $shakearound->device()->apply(int $quantity, string $reason [, string $comment = '' [, int $poiId = null]])

参数

> $quantity 申请的设备ID的数量,单次新增设备超过500个,需走人工审核流程
$reason 申请理由,不超过100个汉字或200个英文字母
$comment 可选,备注,不超过15个汉字或30个英文字母
$poiId 可选,设备关联的门店ID,关联门店后,在门店1KM的范围内有优先摇出信息的机会

示例

```php
$result = $shakearound->device()->apply(3, '测试', '测试专用', 1234);

/* 返回结果
{
    "data": {
        "apply_id": 123,
        "audit_status": 1,
        "audit_comment": "审核中"
    },
    "errcode": 0,
    "errmsg": "success."
}
*/
var_dump($result->data['apply_id']) // 123
```

#### 查询设备ID申请审核状态

查询设备ID申请的审核状态。若单次申请的设备ID数量小于等于500个,系统会进行快速审核;若单次申请的设备ID数量大于500个,则在三个工作日内完成审核。

方法

> $shakearound->device()->getStatus(int $applyId)

参数

> $applyId 批次ID,申请设备ID时所返回的批次ID

示例

```php
$result = $shakearound->device()->getStatus(123);

/* 返回结果
{
    "data": {
        "apply_time": 1432026025,
        "audit_comment": "test",
        "audit_status": 1,
        "audit_time": 0
    },
    "errcode": 0,
    "errmsg": "success."
}
*/
var_dump($result->data['audit_status']) // 1
```

#### 编辑设备信息

> 仅能修改设备的备注信息。

方法

> $shakearound->device()->update(array $deviceIdentifier, string $comment)

参数

> $deviceIdentifier 设备id,设备编号device_id或UUID、major、minor的关联数组,若二者都填,则以设备编号为优先
$comment 设备的备注信息,不超过15个汉字或30个英文字母

示例

```php
$result = $shakearound->device()->update(['device_id' => 10011], 'test');
// 或
$result = $shakearound->device()->update(['uuid' => 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
                                          'major' => 1002,
                                          'minor' => 1223,
], 'test');

/* 返回结果
{
    "data": {
    },
    "errcode": 0,
    "errmsg": "success."
}
*/
var_dump($result->errcode) // 0
```

#### 配置设备与门店/其他公众账号门店的关联关系

关联本公众账号门店时,支持创建门店后直接关联在设备上,无需为审核通过状态,摇周边后台自动更新门店的最新信息和状态。
关联其他公众账号门店时,支持设备关联其他公众账号的门店,门店需为审核通过状态。

> 因为第三方门店不归属本公众账号,所以未保存到设备详情中,查询设备列表接口与获取摇周边的设备及用户信息接口不会返回第三方门店。

方法

> $shakearound->device()->bindLocation(array $deviceIdentifier, $poiId [, $type = 1 [, $poiAppid = null]])

参数

> $deviceIdentifier 设备id,设备编号device_id或UUID、major、minor的关联数组,若二者都填,则以设备编号为优先
$poiId 设备关联的门店ID,关联门店后,在门店1KM的范围内有优先摇出信息的机会。当值为0时,将清除设备已关联的门店ID
$type 可选,为1时,关联的门店和设备归属于同一公众账号;为2时,关联的门店为其他公众账号的门店
$poiAppid 可选,当$type为1时该参数为必填

示例

```php
// 关联本公众账号门店
$result = $shakearound->device()->bindLocation(['device_id' => 10011], 1231);
// 或
$result = $shakearound->device()->bindLocation(['uuid' => 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
                                                'major' => 1002,
                                                'minor' => 1223,
], 1231);

// 关联其他公众账号门店
// wxappid为关联门店所归属的公众账号的APPID
$result = $shakearound->device()->bindLocation(['device_id' => 10011], 1231, 2, 'wxappid');
// 或
$result = $shakearound->device()->bindLocation(['uuid' => 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
                                                'major' => 1002,
                                                'minor' => 1223,
], 1231, 2, 'wxappid');

/* 返回结果
{
    "data": {
    },
    "errcode": 0,
    "errmsg": "success."
}
*/
var_dump($result->errcode) // 0
```

#### 查询设备列表

查询已有的设备ID、UUID、Major、Minor、激活状态、备注信息、关联门店、关联页面等信息。

##### 根据设备id批量取回设备数据

方法

> $shakearound->device()->fetchByIds(array $deviceIdentifiers)

参数

> $deviceIdentifiers 设备id列表

示例

```php
$result = $shakearound->device()->fetchByIds([
                                                ['device_id' => 10097],
                                                ['device_id' => 10098],
]);
// 或
$result = $shakearound->device()->fetchByIds([
                                                ['uuid' => 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
                                                 'major' => 10001,
                                                 'minor' => 12102,],
                                                ['uuid' => 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
                                                 'major' => 10001,
                                                 'minor' => 12103,]
]);

/* 返回结果
{
    "data": {
        "devices": [
            {
                "comment": "",
                "device_id": 10097,
                "major": 10001,
                "minor": 12102,
                "status": 1,
                "last_active_time":1437276018,
                "poi_id": 0,
                "uuid": "FDA50693-A4E2-4FB1-AFCF-C6EB07647825"
            },
            {
                "comment": "",
                "device_id": 10098,
                "major": 10001,
                "minor": 12103,
                "status": 1,
                "last_active_time":1437276018,
                "poi_appid":"wxe3813f5d8c546fc7"
                "poi_id": 123,
                "uuid": "FDA50693-A4E2-4FB1-AFCF-C6EB07647825"
            }
        ],
        "total_count": 151
    },
    "errcode": 0,
    "errmsg": "success."
}
*/
var_dump($result->data['devices'][0][device_id]) // 10097
var_dump($result->data['total_count']) // 151
```

##### 分页批量取回设备数据

方法

> $shakearound->device()->pagination(int $lastSeen, int $count)

参数

> $lastSeen 前一次查询列表末尾的设备编号device_id,第一次查询last_seen为0
$count 待查询的设备数量,不能超过50个

示例

```php
$result = $shakearound->device()->pagination(10097, 3);

// 返回结果同上
```

##### 根据申请时的批次ID分页批量取回设备数据

方法

> $shakearound->device()->fetchByApplyId(int $applyId, int $lastSeen, int $count)

参数

> $applyId 批次ID,申请设备ID时所返回的批次ID
$lastSeen 前一次查询列表末尾的设备编号device_id,第一次查询lastSeen为0
$count 待查询的设备数量,不能超过50个

示例

```php
$result = $shakearound->device()->fetchByApplyId(1231, 10097, 3);

// 返回结果同上
```

### 页面管理

#### 新增页面

新增摇一摇出来的页面信息,包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。其中,图片必须为用素材管理接口上传至微信侧服务器后返回的链接。

> 注意:
图片是使用本页面下方的素材管理的接口上传的,切勿和另一个 [素材管理](material.html) 接口混淆。

方法

> $shakearound->page()->add(string $title, string $description, strig $pageUrl, string $iconUrl [, string $comment = ''])

参数

> $title 在摇一摇页面展示的主标题,不超过6个汉字或12个英文字母
$description 在摇一摇页面展示的副标题,不超过7个汉字或14个英文字母
$pageUrl 点击进去的超链接
$iconUrl 在摇一摇页面展示的图片。图片需先上传至微信侧服务器,用“素材管理-上传图片素材”接口上传图片,返回的图片URL再配置在此处
$comment 可选,页面的备注信息,不超过15个汉字或30个英文字母

示例

```php
$result = $shakearound->page()->add('主标题', '副标题', 'https://zb.weixin.qq.com', 'http://3gimg.qq.com/shake_nearby/dy/icon', 'test');

/* 返回结果
{
   "data": {
       "page_id": 28840
   }
   "errcode": 0,
   "errmsg": "success."
}
*/
var_dump($result->data['page_id']) // 28840
```

#### 编辑页面信息

编辑摇一摇出来的页面信息,包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。

方法

> $shakearound->page()->update(int $pageId, string $title, string $description, string $pageUrl, string $iconUrl [, string $comment = ''])

参数

> $pageId 摇周边页面唯一ID
$title 在摇一摇页面展示的主标题,不超过6个汉字或12个英文字母
$description 在摇一摇页面展示的副标题,不超过7个汉字或14个英文字母
$pageUrl 点击进去的超链接
$iconUrl 在摇一摇页面展示的图片。图片需先上传至微信侧服务器,用“素材管理-上传图片素材”接口上传图片,返回的图片URL再配置在此处
$comment 可选,页面的备注信息,不超过15个汉字或30个英文字母

示例

```php
$result = $shakearound->page()->add(28840, '主标题', '副标题', 'https://zb.weixin.qq.com', 'http://3gimg.qq.com/shake_nearby/dy/icon', 'test');

/* 返回结果
{
    "data": {
    },
    "errcode": 0,
    "errmsg": "success."
}
*/
var_dump($result->errcode) // 0
```

#### 查询页面列表

查询已有的页面,包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。

##### 根据页面id批量取回页面数据

方法

> $shakearound->page()->fetchByIds(array $pageIds)

参数

> $pageIds 页面的id列表,索引数组

示例

```php
$result = $shakearound->page()->fetchByIds([28840, 28842]);

/* 返回结果
{
   "data": {
       "pages": [
           {
               "comment": "just for test",
               "description": "test",
               "icon_url": "https://www.baidu.com/img/bd_logo1",
               "page_id": 28840,
               "page_url": "http://xw.qq.com/testapi1",
               "title": "测试1"
           },
           {
               "comment": "just for test",
               "description": "test",
               "icon_url": "https://www.baidu.com/img/bd_logo1",
               "page_id": 28842,
               "page_url": "http://xw.qq.com/testapi2",
               "title": "测试2"
           }
       ],
       "total_count": 2
   },
   "errcode": 0,
   "errmsg": "success."
}
*/
var_dump($result->data['pages'][0]['title']) // 测试1
var_dump($result->data['total_count']) // 2
```

##### 分页批量取回页面数据

方法

> $shakearound->page()->pagination(int $begin, int $count)

参数

> $begin 页面列表的起始索引值
$count 待查询的页面数量,不能超过50个

示例

```php
$result = $shakearound->page()->pagination(0,2);

// 返回结果同上
```

#### 删除页面

删除已有的页面,包括在摇一摇页面出现的主标题、副标题、图片和点击进去的超链接。

> 注意:
只有页面与设备没有关联关系时,才可被删除。

方法

> $shakearound->page()->delete(int $pageId)

参数

> $pageId 页面的id

示例

```php
$result = $shakearound->page()->delete(34567);

/* 返回结果
{
    "data": {
    },
    "errcode": 0,
    "errmsg": "success."
}
*/
var_dump($result->errcode) // 0
```

### 素材管理

上传在摇一摇功能中需使用到的图片素材,素材保存在微信侧服务器上。图片格式限定为:jpg,jpeg,png,gif。
若图片为在摇一摇页面展示的图片,则其素材为 `icon` 类型的图片,图片大小建议 `120px*120 px` ,限制不超过 `200 px *200 px` ,图片需为 `正方形` 。
若图片为申请开通摇一摇周边功能需要上传的资质文件图片,则其素材为 `license` 类型的图片,图片的文件大小不超过 `2MB` ,尺寸不限,形状不限。

方法

> $shakearound->material()->uploadImage(string $path [, string $type = 'icon'])

参数

> $path 图片所在路径
$type 可选,值为icon或license

示例

```php
$result = $shakearound->material()->uploadImage(__DIR__ . '/stubs/image.jpg');

/* 返回结果
{
    "data": {
        "pic_url": http://shp.qpic.cn/wechat_shakearound_pic/0/1428377032e9dd2797018cad79186e03e8c5aec8dc/120"
    },
    "errcode": 0,
    "errmsg": "success."
}
*/
var_dump($result->data['pic_url']) // http://shp.qpic.cn/wechat_shakearound_pic/0/1428377032e9dd2797018cad79186e03e8c5aec8dc/120
```

### 管理设备与页面的关系

通过接口申请的设备ID,需先配置页面,若未配置页面,则摇不出页面信息。

#### 配置设备与页面的关联关系

配置完成后,在此设备的信号范围内,即可摇出关联的页面信息。
若设备配置多个页面,则随机出现页面信息。一个设备最多可配置30个关联页面。

> 注意:
1、配置时传入该设备需要关联的页面的id列表,该设备原有的关联关系将被直接清除。
2、页面的id列表允许为空(**传入空数组**),当页面的id列表为空时则会清除该设备的所有关联关系。

方法

> $shakearound->relation()->bindPage(array $deviceIdentifier, array $pageIds)

参数

> $deviceIdentifier 设备id,设备编号device_id或UUID、major、minor的关联数组,若二者都填,则以设备编号为优先
$pageIds 页面的id列表,索引数组

示例

```php
$result = $shakearound->relation()->bindPage(['device_id' => 10011], [12345, 23456, 334567]);
// 或
$result = $shakearound->relation()->bindPage(['uuid' => 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
                                              'major' => 1002,
                                              'minor' => 1223,
], [12345, 23456, 334567]);

/* 返回结果
{
    "data": {
    },
    "errcode": 0,
    "errmsg": "success."
}
*/
var_dump($result->errcode) // 0
```

#### 查询设备与页面的关联关系

##### 查询指定设备所关联的页面

根据设备ID或完整的UUID、Major、Minor查询该设备所关联的所有页面信息

方法

> $shakearound->relation()->getPageByDeviceId(array $deviceIdentifier [, boolean $raw = false])

> 注意:
该方法默认对返回的数据进行处理后返回一个包含页面id的索引数组。若要返回和 `getDeviceByPageId` 方法类似的数据,请传入 `true` 作为第二个参数。

参数

> $deviceIdentifier 设备id,设备编号device_id或UUID、major、minor的关联数组,若二者都填,则以设备编号为优先
$raw 可选,当为true时,返回值和getDeviceByPageId方法类似,否则返回页面的id列表(索引数组,无关联时为空数组)

示例

```php
$result = $shakearound->relation()->getPageByDeviceId(['device_id' => 10011]);
// 或
$result = $shakearound->relation()->getPageByDeviceId(['uuid' => 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
                                                       'major' => 1002,
                                                       'minor' => 1223,
]);

// 返回结果
var_dump($result) // [50054,50055]
```

##### 查询指定页面所关联的设备

指定页面ID分页查询该页面所关联的所有的设备信息

方法

> $shakearound->relation()->getDeviceByPageId(int $pageId, int $begin, int $count)

参数

> $pageId 指定的页面id
$begin 关联关系列表的起始索引值
$count 待查询的关联关系数量,不能超过50个

示例

```php
$result = $shakearound->relation()->getDeviceByPageId(50054, 0, 3);

/* 返回结果
{
  "data": {
      "relations": [
          {
              "device_id": 797994,
              "major": 10001,
              "minor": 10023,
              "page_id": 50054,
              "uuid": "FDA50693-A4E2-4FB1-AFCF-C6EB07647825"
          },
          {
              "device_id": 797995,
              "major": 10001,
              "minor": 10024,
              "page_id": 50054,
              "uuid": "FDA50693-A4E2-4FB1-AFCF-C6EB07647825"
          }
      ],
      "total_count": 2
  },
  "errcode": 0,
  "errmsg": "success."
}
*/
var_dump($result->data['relations'][0]['device_id']) // 797994
var_dump($result->data['total_count']) // 2
```

### 摇一摇数据统计

> 此接口无法获取当天的数据,最早只能获取前一天的数据。
由于系统在凌晨处理前一天的数据,太早调用此接口可能获取不到数据,建议在早上8:00之后调用此接口。

#### 以设备为维度的数据统计

查询单个设备进行摇周边操作的人数、次数,点击摇周边消息的人数、次数。

> 注意:
查询的最长时间跨度为30天。只能查询最近90天的数据。

方法

> $shakearound->stats()->deviceSummary(array $deviceIdentifier, int $beginDate, int $endDate)

参数

> $deviceIdentifier 设备id,设备编号device_id或UUID、major、minor的关联数组,若二者都填,则以设备编号为优先
$beginDate 起始日期时间戳,最长时间跨度为30天,单位为秒
$endDate 结束日期时间戳,最长时间跨度为30天,单位为秒

示例

```php
$result = $shakearound->stats()->deviceSummary(['device_id' => 10011], 1425052800, 1425139200);
// 或
$result = $shakearound->stats()->deviceSummary(['uuid' => 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
                                                'major' => 1002,
                                                'minor' => 1223,
], 1425052800, 1425139200);

/* 返回结果
{
   "data": [
       {
           "click_pv": 0,
           "click_uv": 0,
           "ftime": 1425052800,
           "shake_pv": 0,
           "shake_uv": 0
       },
       {
           "click_pv": 0,
           "click_uv": 0,
           "ftime": 1425139200,
           "shake_pv": 0,
           "shake_uv": 0
       }
   ],
   "errcode": 0,
   "errmsg": "success."
}
*/
var_dump($result->data[0]['ftime']) // 1425052800
```

#### 批量查询设备统计数据

查询指定时间商家帐号下的每个设备进行摇周边操作的人数、次数,点击摇周边消息的人数、次数。

> 只能查询最近90天内的数据,且一次只能查询一天。

> 注意:
对于摇周边人数、摇周边次数、点击摇周边消息的人数、点击摇周边消息的次数都为0的设备,不在结果列表中返回。

方法

> $shakearound->stats()->batchDeviceSummary(int $timestamp, int $pageIndex)

参数

> $timestamp 指定查询日期时间戳,单位为秒
$pageIndex 指定查询的结果页序号,返回结果按摇周边人数降序排序,每50条记录为一页

示例

```php
$result = $shakearound->stats()->batchDeviceSummary(1435075200, 1);

/* 返回结果
{
    "data": {
        "devices": [
            {
                "device_id": 10097,
                "major": 10001,
                "minor": 12102,
                "uuid": "FDA50693-A4E2-4FB1-AFCF-C6EB07647825"
                "shake_pv": 1
                "shake_uv": 2
                "click_pv": 3
                "click_uv": 4
            },
            {
                "device_id": 10098,
                "major": 10001,
                "minor": 12103,
                "uuid": "FDA50693-A4E2-4FB1-AFCF-C6EB07647825"
                "shake_pv": 1
                "shake_uv": 2
                "click_pv": 3
                "click_uv": 4
            }
        ],
    },
    "date":1435075200
    "total_count": 151
    "page_index":1
    "errcode": 0,
    "errmsg": "success."
}
*/
var_dump($result->data['devices'][0]['device_id']) // 10097
var_dump($result->total_count) // 151
```

#### 以页面为维度的数据统计

查询单个页面通过摇周边摇出来的人数、次数,点击摇周边页面的人数、次数

> 注意:
查询的最长时间跨度为30天。只能查询最近90天的数据。

方法

> $shakearound->stats()->pageSummary(int $pageId, int $beginDate, int $endDate);

参数

> $pageId 指定页面的页面ID
$beginDate 起始日期时间戳,最长时间跨度为30天,单位为秒
$endDate 结束日期时间戳,最长时间跨度为30天,单位为秒

示例

```php
$result = $shakearound->stats()->pageSummary(12345, 1425052800, 1425139200);

/* 返回结果
{
   "data": [
       {
           "click_pv": 0,
           "click_uv": 0,
           "ftime": 1425052800,
           "shake_pv": 0,
           "shake_uv": 0
       },
       {
           "click_pv": 0,
           "click_uv": 0,
           "ftime": 1425139200,
           "shake_pv": 0,
           "shake_uv": 0
       }
   ],
   "errcode": 0,
   "errmsg": "success."
}
*/
var_dump($result->data[1]['ftime']) // 1425139200
```
#### 批量查询页面统计数据

查询指定时间商家帐号下的每个页面进行摇周边操作的人数、次数,点击摇周边消息的人数、次数。

> 注意:
对于摇周边人数、摇周边次数、点击摇周边消息的人数、点击摇周边消息的次数都为0的页面,不在结果列表中返回。

方法

> $shakearound->stats()->batchPageSummary(int $timestamp, int $pageIndex);

参数

> $timestamp 指定查询日期时间戳,单位为秒
$pageIndex 指定查询的结果页序号,返回结果按摇周边人数降序排序,每50条记录为一页

示例

```php
$result = $shakearound->stats()->batchPageSummary(1435075200, 1);

/* 返回结果
{
    "data": {
        "pages": [
            {
                "page_id":1234
                "click_pv": 1,
                "click_uv": 3,
                "shake_pv": 0,
                "shake_uv": 0
            },
            {
                "page_id":5678
                "click_pv": 1,
                "click_uv": 2,
                "shake_pv": 0,
                "shake_uv": 0
            },
        ],
    },
    "date":1435075200
    "total_count": 151
    "page_index":1
    "errcode": 0,
    "errmsg": "success."
}
*/
var_dump($result->data['pages'][0]['click_uv']) // 3
var_dump($result->total_count) // 151
```

### 设备分组管理

调用H5页面获取设备信息 JS API接口,需要先把设备分组,微信客户端只会返回已在分组中的设备信息。

#### 新增分组

新建设备分组,每个帐号下最多只有1000个分组。

方法

> $shakearound->group()->add(string $name)

参数

> $name 分组名称,不超过100汉字或200个英文字母

示例

```php
$result = $shakearound->group()->add('test');

/* 返回结果
{
  "data": {
      "group_id" : 123,
      "group_name" : "test"
  },
  "errcode": 0,
  "errmsg": "success."
}
*/
var_dump($result->data['group_id']) // 123
var_dump($result->data['group_name']) // test
```

#### 编辑分组信息

编辑设备分组信息,目前只能修改分组名。

方法

> $shakearound->group()->update(int $groupId, string $name)

参数

> $groupId 分组唯一标识,全局唯一
$name 分组名称,不超过100汉字或200个英文字母

示例

```php
$result = $shakearound->group()->update(123, 'newName');

/* 返回结果
{
    "data": {
    },
    "errcode": 0,
    "errmsg": "success."
}
*/
var_dump($result->errcode) // 0
```

#### 删除分组

删除设备分组,若分组中还存在设备,则不能删除成功。需把设备移除以后,才能删除。

> 在执行删除前,最好先使用 `getDetails` 方法查询分组详情,若分组内有设备,先使用 `removeDevice` 方法移除。

方法

> $shakearound->group()->delete(int $groupId)

参数

> $groupId 分组唯一标识,全局唯一

示例

```php
$result = $shakearound->group()->delete(123);

/* 返回结果
{
    "data": {
    },
    "errcode": 0,
    "errmsg": "success."
}
*/
var_dump($result->errcode) // 0
```

#### 查询分组列表

查询账号下所有的分组。

方法

> $shakearound->group()->lists(int $begin, int $count)

参数

> $begin 分组列表的起始索引值
$count 待查询的分组数量,不能超过1000个

示例

```php
$result = $shakearound->group()->lists(0, 2);

/* 返回结果
{
    "data": {
        "groups":[
            {
                "group_id" : 123,
                "group_name" : "test1"
            },
            {
                "group_id" : 124,
                "group_name" : "test2"
            }
        ],
        "total_count": 100
    },
    "errcode": 0,
    "errmsg": "success."
}
*/
var_dump($result->data['groups'][1]['group_name']) // test2
var_dump($result->data['total_count']) // 100
```

#### 查询分组详情

查询分组详情,包括分组名,分组id,分组里的设备列表。

方法

> $shakearound->group()->getDetails(int $groupId, int $begin, int $count)

参数

> $groupId 分组唯一标识,全局唯一
$begin 分组里设备的起始索引值
$count 待查询的分组里设备的数量,不能超过1000个

示例

```php
$result = $shakearound->group()->getDetails(123, 0, 2);

/* 返回结果
{
    "data": {
        "group_id" : 123,
        "group_name" : "test",
        "total_count": 100,
        "devices" :[
            {
                "device_id" : 123456,
                "uuid" : "FDA50693-A4E2-4FB1-AFCF-C6EB07647825",
                "major" : 10001,
                "minor" : 10001,
                "comment" : "test device1",
                "poi_id" : 12345,
            },
            {
                "device_id" : 123457,
                "uuid" : "FDA50693-A4E2-4FB1-AFCF-C6EB07647825",
                "major" : 10001,
                "minor" : 10002,
                "comment" : "test device2",
                "poi_id" : 12345,
            }
        ]
    },
    "errcode": 0,
    "errmsg": "success."
}
*/
var_dump($result->data['devices'][0]['comment']) // test device1
var_dump($result->data['total_count']) // 100
```

#### 添加设备到分组

添加设备到分组,每个分组能够持有的设备上限为10000,并且每次添加操作的添加上限为1000。

> 只有在摇周边申请的设备才能添加到分组。

方法

> $shakearound->group()->addDevice(int $groupId, array $deviceIdentifiers)

参数

> $groupId 分组唯一标识,全局唯一
$deviceIdentifiers 设备id列表

示例

```php
$result = $shakearound->group()->addDevice(123, [
                                                    ['device_id' => 10097],
                                                    ['device_id' => 10098],
]);
// 或
$result = $shakearound->group()->addDevice(123, [
                                                    ['uuid' => 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
                                                    'major' => 10001,
                                                    'minor' => 12102,],
                                                    ['uuid' => 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
                                                    'major' => 10001,
                                                    'minor' => 12103,]
]);

/* 返回结果
{
    "data": {
    },
    "errcode": 0,
    "errmsg": "success."
}
*/
var_dump($result->errcode) // 0
```

#### 从分组中移除设备

从分组中移除设备,每次删除操作的上限为1000。

方法

> $shakearound->group()->removeDevice(int $groupId, array $deviceIdentifiers)

参数

> $groupId 分组唯一标识,全局唯一
$deviceIdentifiers 设备id列表

示例

```php
$result = $shakearound->group()->removeDevice(123, [
                                                    ['device_id' => 10097],
                                                    ['device_id' => 10098],
]);
// 或
$result = $shakearound->group()->removeDevice(123, [
                                                    ['uuid' => 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
                                                    'major' => 10001,
                                                    'minor' => 12102,],
                                                    ['uuid' => 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825',
                                                    'major' => 10001,
                                                    'minor' => 12103,]
]);

/* 返回结果
{
    "data": {
    },
    "errcode": 0,
    "errmsg": "success."
}
*/
var_dump($result->errcode) // 0
```

### 摇一摇红包

微信官方目前暂停了摇红包接口,该接口可能会有所调整,故而暂时不提供该接口的封装。

> 官方公告详情请至: [关于摇红包接口暂停的公告](https://zb.weixin.qq.com/nearby/announce.xhtml?announceId=10047)

### 摇一摇事件通知

用户进入摇一摇界面,在“周边”页卡下摇一摇时,微信会把这个事件推送到开发者填写的URL(登录公众平台进入开发者中心设置)。推送内容包含摇一摇时“周边”页卡展示出来的页面所对应的设备信息,以及附近最多五个属于该公众账号的设备的信息。当摇出列表时,此事件不推送。

> 摇一摇事件的事件类型:ShakearoundUserShake
关于事件的处理请移步: [事件](events.html)

### 摇一摇周边错误码

> 摇周边错误码请移步: [错误码](https://mp.weixin.qq.com/wiki?action=doc&id=mp1443448163&t=0.17525333335674986)

有关摇一摇周边接口信息的更多细节请参考微信官方文档相应条目: [微信官方文档](http://mp.weixin.qq.com/wiki/)


================================================
FILE: docs/src/3.x/short-url.md
================================================
# 短网址服务


主要使用场景: 开发者用于生成二维码的原链接(商品、支付二维码等)太长导致扫码速度和成功率下降,将原长链接通过此接口转成短链接再生成二维码将大大提升扫码速度和成功率。

## 获取实例

```php
<?php
use EasyWeChat\Foundation\Application;
// ...
$app = new Application($options);

$url = $app->url;
```

## API

+ `shorten($url)` 长链接转短链接

example:

```php
$shortUrl = $url->shorten('http://overtrue.me/open-source');
//
```

微信官方文档:http://mp.weixin.qq.com/wiki/


================================================
FILE: docs/src/3.x/sidebar.js
================================================
exports = module.exports = [
  {
    text: '开始使用',
    items: [
      { text: '概述', link: '/3.x/overview.html' },
      { text: '安装', link: '/3.x/installation.html' },
      { text: '小教程', link: '/3.x/tutorial.html' },
      { text: '配置', link: '/3.x/configuration.html' },
      { text: '在框架中使用', link: '/3.x/integration.html' },
      { text: '常见问题汇总', link: '/3.x/troubleshooting.html' }
    ]
  },
  {
    text: '基本使用',
    items: [
      { text: '服务端', link: '/3.x/server.html' },
      { text: '消息', link: '/3.x/messages.html' },
      { text: '多客服消息转发', link: '/3.x/message-transfer.html' },
      { text: '事件', link: '/3.x/events.html' },
      { text: '群发消息', link: '/3.x/broadcast.html' },
      { text: '模板消息', link: '/3.x/notice.html' },
      { text: '用户', link: '/3.x/user.html' },
      { text: '用户标签', link: '/3.x/user-tag.html' },
      { text: '用户组', link: '/3.x/user-group.html' },
      { text: '网页授权', link: '/3.x/oauth.html' },
      { text: '素材管理', link: '/3.x/material.html' },
      { text: '菜单', link: '/3.x/menu.html' },
      { text: 'JSSDK', link: '/3.x/js.html' },
      { text: '支付', link: '/3.x/payment.html' },
      { text: '企业支付', link: '/3.x/merchant_payment.html' },
      { text: '红包', link: '/3.x/lucky-money.html' },
      { text: '卡券', link: '/3.x/card.html' },
      { text: '小店', link: '/3.x/store.html' },
      { text: '门店', link: '/3.x/poi.html' },
      { text: '客服', link: '/3.x/staff.html' },
      { text: '数据统计与分析', link: '/3.x/anaylsis.html' },
      { text: '二维码', link: '/3.x/qrcode.html' },
      { text: '短网址', link: '/3.x/short-url.html' },
      { text: '小程序', link: '/3.x/mini_program.html' },
      { text: '语义理解', link: '/3.x/semantic.html' },
      { text: '自动回复', link: '/3.x/reply.html' },
      { text: '开放平台', link: '/3.x/open_platform.html' }
    ]
  },
  {
    text: '自定义',
    items: [
      { text: 'Access Token', link: '/3.x/access_token.html' },
      { text: '缓存', link: '/3.x/cache.html' }
    ]
  },
  {
    text: '其他',
    items: [
      { text: '问题解答', link: '/3.x/troubleshooting.html' },
      { text: '贡献', link: '/3.x/contributing.html' },
      { text: '更新日志', link: '/3.x/releases.html' },
      { text: '路线图', link: '/3.x/roadmap.html' }
    ]
  }
]


================================================
FILE: docs/src/3.x/staff.md
================================================
# 客服


> 2016.06.28 已经更新为新版多客服 API
> 请更新到 3.1 版本: composer require "overtrue/wechat:~3.1"

微信的客服才能发送消息或者群发消息,而且还有时效限制,真恶心的说。。。

## 客服管理

```php
<?php
use EasyWeChat\Foundation\Application;
// ...
$app = new Application($options);

$staff = $app->staff; // 客服管理
```

## API

### 获取所有客服账号列表

```php
$staff->lists();
```

### 获取所有在线的客服账号列表

```php
$staff->onlines();
```

### 添加客服帐号

```php
$staff->create('foo@test', '客服1');
```

### 修改客服帐号

```php
$staff->update('foo@test', '客服1');
```

### 删除客服帐号

```php
$staff->delete('foo@test');
```

### 设置客服帐号的头像

```php
$staff->avatar('foo@test', $avatarPath); // $avatarPath 为本地图片路径,非 URL
```

### 获取客服聊天记录 `NEW`

```php
$staff->records($startTime, $endTime, $pageIndex, $pageSize);

// example: $records = $staff->records('2015-06-07', '2015-06-21', 1, 20);
```

### 主动发送消息给用户

```php
$staff->message($message)->to($openId)->send();
```

> `$message` 为消息对象,请参考:[消息](messages.html)

### 指定客服发送消息

```php
$staff->message($message)->by('account@test')->to($openId)->send();
```
> `$message` 为消息对象,请参考:[消息](messages.html)

## 客服会话控制

> 客服会话为新版 API 功能

```php
<?php
use EasyWeChat\Foundation\Application;
// ...
$app = new Application($options);

$session = $app->staff_session; // 客服会话管理
```

## 创建会话

```php
$session->create('test1@test', 'OPENID');
```

### 关闭会话

```php
$session->close('test1@test', 'OPENID');
```

### 获取客户会话状态

```php
$session->get('OPENID');
```

### 获取客服会话列表

```php
$session->lists('test1@test');
```

### 获取未接入会话列表

```php
$session->waiters();
```


关于更多客服接口信息请参考微信官方文档:http://mp.weixin.qq.com/wiki


================================================
FILE: docs/src/3.x/store.md
================================================
# 门店


TODO

================================================
FILE: docs/src/3.x/troubleshooting.md
================================================
# 疑难解答


在微信公众平台开发的道路上,遍布着各种大大小小的坑,有的人掉坑里,几经折腾又爬出来了,然后拍拍屁股走人。然而坑还在那里,还会继续有后来人掉进去……

这,是我们不愿看到的。

所以在这里,我们将陆续将微信开发中可能遇到的各种疑难问题进行汇总,并给出对应的解决办法。一般情况下,这些问题都可以对号入座,轻松地解决。但也不排除特殊情况,这时候你遇到的问题与文中某一个症状一致,但文中所给的解决方案并不凑效,这种情况下就需要发挥你自己的智慧,去……折腾了……

我们期待这一版块为各位的开发带来便利,同时也希望各位本着开源、分享的精神对其进行补充和完善,将各种坑一一填小、填平,让微信开发变得不那么痛苦,甚至,变成一件快乐的事……

# 一些服务器基本设施问题:

- 时区不对, 使用命令 `date` 可以在服务器上查看当前时间,如果发现时区不对则需要修改时区:[Setting The Correct Timezone In CentOS And Ubuntu Servers With NTP](https://www.liberiangeek.net/2013/02/setting-the-correct-timezone-in-centos-and-ubuntu-servers-with-ntp/)
    - ...


## curl: (60) SSL certificate problem: unable to get local issuer certificate

这是 SSL 证书问题所致,在使用 SDK 调用微信支付等相关的操作时可能会遇到报 “SSL certificate problem: unable to get local issuer certificate” 的错误。

微信公众平台提供的文档中建议对部分较敏感的操作接口使用 https 协议进行访问,例如微信支付和红包等接口中涉及到操作商户资金的一些操作。
wechat SDK 遵循了官方建议,所以在调用这些接口时,除了按照官方文档设置操作证书文件外,还需要保证服务器正确安装了 CA 证书。

1. 下载 CA 证书

  你可以从 http://curl.haxx.se/ca/cacert.pem 下载 或者 使用[微信官方提供的证书](https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=4_3)中的 CA 证书 `rootca.pem` 也是同样的效果。

2. 在 `php.ini` 中配置 CA 证书

  只需要将上面下载好的 CA 证书放置到您的服务器上某个位置,然后修改 `php.ini` 的 `curl.cainfo` 为该路径(**绝对路径!**),重启 `php-fpm` 服务即可。

  ```
  curl.cainfo = /path/to/downloaded/cacert.pem
  ```
  > 注意证书文件**路径为绝对路径**!以自己实际情况为准。

  其它修改 HTTP 类源文件的方式是不允许的。

## cURL error 56: SSLRead() return error -9806

目前在 OSX 下,发现使用 HomeBrew 装的 PHP 7.0 有这个问题,解决方案是重新 brew 安装 PHP:

```shell
$ brew install homebrew/php/php70 --with-homebrew-openssl --with-homebrew-curl --without-snmp -vvv
```

验证:

```shell
$ php -i | grep 'OpenSSL support'

OpenSSL support => enabled
OpenSSL support => enabled
```


## 支付失败!当前页面的 URL 未注册

这是由于微信支付授权目录未正确配置引起的。此时开发者应该登录微信公众平台,进入**【微信支付】->【开发设置】**进行设置。

1. 公众号可添加3个支付授权目录,满足不同应用使用同一个公众号进行支付的业务需求。

2. 正确的**【支付授权目录】**应以 `http://` 或 `https://` 开头,并以正斜杠 `/` 结尾,授权目录所包含的域名**必须经过 ICP 备案**。

3. 支付授权目录需**细化至二级或三级目录**。

4. 所有**实际调起微信支付请求的页面都必须要所配置的支付授权目录之下**。

5. 在开发过程中,也可以使用测试授权目录进行开发测试,此时还**应该将参与测试的个人微信号添加到测试白名单中**,否则将出现对应的错误提示……

> 配置前请先理解**页面**、**目录**、**URL **以及**域名**等几个基本概念,并对自己所使用的框架的路由机制有一个大致了解。这样你才会知道自己正在配置的参数是个啥玩意儿,有什么卵用…… :smile:


## redirect_url 参数错误

这是由于程序使用了**网页授权**而公众号没有正确配置**【网页授权域名】**所致。此时你需要登录[微信公众平台](https://mp.weixin.qq.com/),在【开发】->【接口权限】页面找到**网页授权获取用户基本信息**进行配置并保存。

1. 网页授权域名应该为通过 ICP 备案的有效域名,否则保存时无法通过安全监测。

2. 网页授权域名即程序完成授权获得授权  code 后跳转到的页面的域名,一般情况下为你的业务域名。

3. 网页授权域名配置成功后会立即生效。

4. 公众号的网页授权域名只可配置一个,请合理规划你的业务,否则你会发现……授权域名不够用哈。


## [JSAPI] config: invalid url domain
在使用 JS-SDK 进行开发时,每个页面都需要调用 wx.config() 方法配置 JSPAI 参数。如果没有正确配置 **JSAPI 安全域名**并且开启了调试模式,此时就报此错误。遇到这个问题时,开发者需要登录微信公众平台,进入【公众号设置】->【功能设置】页面,将项目所使用的域名添加至 **【JSAPI 安全域名】**列表中。

1. 一个公众号同时最多可绑定**三个**安全域名,并且这些域名必须为通过 **ICP 备案**的**一级或一级以上**的有效域名。

2. JSAPI 安全域名每个月**限修改三次**,修改任何一个都算,所以,请谨慎操作。

3. 如果需要使用 JSAPI 调起支付功能,则支付目录必须也在所配置的**安全域名之下**,并且需要将支付目录添加至**支付授权目录**。

## token验证失败、向公众号发送消息无任何反应

相信对接公众号一般是微信开发者进行开发过程中最先进行的工作,而在这看似简单的配置操作中,也可能会掉坑里。
最常见的两种情况就如下:

1. 确认你 “**启用**” 了开发模式, token 验证通过不代表启用,保存后也不代表启用。看到红色 “**停用**” 才真正的是启用了。

2. 配置好URL(服务器地址)以及Token(令牌)后,点击保存时提示**token验证失败**,出现这种情况的原因有多种,其中之一便是网络不稳定,所以**可尝试多次保存**,若始终无法通过再排查其它可能因素。

3. 配置保存成功之后,向公众号发送消息无任何反应,自己的消息处理程序也没有被调用的记录(无对应日志)。这种情况下如果你尝试**反复停用和启用服务器配置**,可能突然间惊奇地了现,问题莫名其妙的解决了。

4. 使用在线调试工具的消息接口,http://mp.weixin.qq.com/debug/, 只要返回绿色的“**请求成功**”,就代表你的代码没有问题,请**重复上面第3项**再测试。

5. **如果你在用什么本地开发工具,或者什么 ngrok 代理到本机这样的开发方式,那么失败就很正常了,微信服务器到你机器的网络延迟太大(还是用服务器开发吧)。**

> 请开发者理解服务器 TOKEN 验证原理(官方文档有说明)并谨记服务器验证时使用 GET 方式访问,而公众平台向你的服务器发送消息/数据则使用 POST 方式,所以服务器验证成功之后,在某些启用了 CSRF 验证的框架里,接收消息时可能还会遇到 CSRF 相关的问题,请根据自己项目实际情况进行排查。
> 另外有的朋友的 Laravel 里使用了 laravel-debugbar,这个组件的原理是在页面输出时在后面添加 HTML 来实现的,所以它会改变我们返回给微信的内容,此时要么卸载,要么禁用掉它。


## Maximum function nesting level of '100' reached, aborting!

在使用了 Xdebug 的环境下可能出现这个问题。这是由于 Xdebug 限制函数嵌套的最大层级数(默认为100),当嵌套次数达到该值便会触发 Xdebug 跳出嵌套并报此错误。

为避免这个问题,**可以将 Xdebug 的 max_nesting_level 参数适当设置大一些**,通常设置为200就可以了(当然可根据自己实际情况设置为更大的值)。

如下,修改 php.ini 配置文件后,重启 Apache 或 php-fpm 服务即可。

```
xdebug.max_nesting_level=200
```


================================================
FILE: docs/src/3.x/tutorial.md
================================================
# 快速开始

在我们已经安装完成后,即可很快的开始使用它了,当然你还是有必要明白 PHP 基本知识,如命名空间等,我这里就不赘述了。

我们以完成服务器端验证与接收响应用户发送的消息为例来演示,首先你有必要了解一下微信交互的运行流程:

```
                                     +-----------------+                       +---------------+
    +----------+                     |                 |    POST/GET/PUT       |               |
    |          | ------------------> |                 | ------------------->  |               |
    |   user   |                     |  wechat server  |                       |  your server  |
    |          | < - - - - - - - - - |                 |                       |               |
    +----------+                     |                 | <- - - - - - - - - -  |               |
                                     +-----------------+                       +---------------+
```

那么我们要做的就是图中 **微信服务器把用户消息转到我们的自有服务器(虚线返回部分)** 后的处理过程。

## 服务端验证

在微信接入开始有一个 “服务器验证” 的过程,这一步呢,其实就是微信服务器向我们服务器发起一个请求(上图实线部分),传了一个名称为 `echostr` 的字符串过来,我们只需要原样返回就好了。

你也知道,微信后台只能填写一个服务器地址,所以 **服务器验证** 与 **消息的接收与回复**,都在这一个链接内完成交互。

考虑到这些,我已经把验证这一步给封装到 SDK 里了,你可以完全忽略这一步。

下面我们来配置一个基本的服务端,这里假设我们自己的服务器域名叫 `easywechat.com`,我们在服务器上准备这么一个文件`server.php`:

// server.php

```php
<?php

include __DIR__ . '/vendor/autoload.php'; // 引入 composer 入口文件

use EasyWeChat\Foundation\Application;

$options = [
    'debug'  => true,
    'app_id' => 'your-app-id',
    'secret' => 'you-secret',
    'token'  => 'easywechat',


    // 'aes_key' => null, // 可选

    'log' => [
        'level' => 'debug',
        'file'  => '/tmp/easywechat.log', // XXX: 绝对路径!!!!
    ],

    //...
];

$app = new Application($options);

$response = $app->server->serve();

// 将响应输出
$response->send(); // Laravel 里请使用:return $response;

```

> :heart: 安全模式下请一定要填写 `aes_key`

一个服务端带验证功能的代码已经完成,当然没有对消息做处理,别着急,后面我们再讲。

我们先来分析上面的代码:

```php
<?php

// 这行代码是引入 `composer` 的入口文件,这样我们的类才能正常加载。
include __DIR__ . '/vendor/autoload.php';

// 引入我们的主项目的入口类。
use EasyWeChat\Foundation\Application;

// 一些配置
$options = [...];

// 使用配置来初始化一个项目。
$app = new Application($options);

$response = $app->server->serve();

// 将响应输出
$response->send(); // Laravel 里请使用:return $response;
```

最后这一行我有必要详细讲一下:

> 1.  我们的 `$app->server->serve()` 就是执行服务端业务了,那么它的返回值呢,是一个 `Symfony\Component\HttpFoundation\Response` 实例。
> 2.  我这里是直接调用了它的 `send()` 方法,它就是直接输出了,我们在一些框架就不能直接输出了,那你就直接拿到 Response 实例后做相应的操作即可,比如 Laravel 里你就可以直接 `return $app->server->serve();`

OK, 有了上面的代码,那么请你按 **[微信官方的接入指引](http://mp.weixin.qq.com/wiki/17/2d4265491f12608cd170a95559800f2d.html)** 操作,并相应修改上面的 `$options` 的配置。

> URL 就是我们的 `http://easywechat.com/server.php`,这里我是举例哦,你可不要填写我的域名。

这样,点击提交验证就 OK 了。

> :heart: 请一定要将微信后台的开发者模式 “**启用**” !!!!!!看到红色 “**停用**” 才真正的是启用了。

## 接收 & 回复用户消息

那服务端验证通过了,我们就来试一下接收消息吧。

> 在刚刚上面代码最后一行 `$app->server->serve()->send();` 前面,我们调用 `$app->server` 的 `setMessageHandler()` 方法来注册一个消息处理函数,这里用到了 **[PHP 闭包](http://php.net/manual/zh/functions.anonymous.php)** 的知识,如果你不熟悉赶紧补课去。

```php
// ...

$server->setMessageHandler(function ($message) {
    return "您好!欢迎关注我!";
});

$response = $app->server->serve();

// 将响应输出
$response->send(); // Laravel 里请使用:return $response;

```

> 注意:send() 方法里已经包含 echo 了,请不要再加 echo 在前面。

好吧,打开你的微信客户端,向你的公众号发送任意一条消息,你应该会收到回复:`您好!欢迎关注我!`。

> 没有收到回复?看到了“你的公众号暂时无法提供服务” ?, 好,那检查一下你的日志吧,日志在哪儿?我们的配置里写了日志路径了(`'/tmp/easywechat.log'`)。 没有这个文件?看看权限哦。

一个基本的服务端验证就完成了。

## 总结

1. 所有的服务都通过主入口 `EasyWeChat\Foundation\Application` 类来获取:

```php
$app = new Application($options);

// services...
$server = $app->server;
$user   = $app->user;
$oauth  = $app->oauth;

// ... js/menu/staff/material/qrcode/notice/stats...

```

2. 所有的 API 返回值均为 [`EasyWeChat\Support\Collection`](https://github.com/EasyWeChat/support/blob/master/src/Collection.php) 类,这个类是个什么东西呢?

它实现了一些 **[PHP 预定义接口](http://php.net/manual/zh/reserved.interfaces.php)**,比如:[`ArrayAccess`](http://php.net/manual/zh/class.arrayaccess.php)、[`Serializable`](http://php.net/manual/zh/class.serializable.php) 等。

有啥好处呢?它让我们操作起返回值来更方便,比如:

```php
$userService = $app->user; // 用户API

$user = $userService->get($openId);

// $user 便是一个 EasyWeChat\Support\Collection 实例
$user['nickname'];
$user->nickname;
$user->get('nickname');

//...
```

还有这些方便的操作:检查是否存在某个属性 `$user->has('email')`、元素个数 `$user->count()`,还有返回数组 `$user->toArray()` ,生成 JSON `$user->toJSON()` 等。

## 最后

希望你在使用本 SDK 的时候能忘记微信官方给你的痛苦,同时如果你发现 SDK 的不足,欢迎提交 PR 或者给我[提建议 & 报告问题](https://github.com/overtrue/wechat/issues)。

祝你生活愉快!


================================================
FILE: docs/src/3.x/user-group.md
================================================
# 用户组


用户组的使用就非常简单了,基本的增删改查。

## 获取实例

```php
<?php
use EasyWeChat\Foundation\Application;

// ...

$app = new Application($options);

$group = $app->user_group; // $user['user_group']
```

## API

### 获取所有分组

```php
$group->lists();
```

example:

```php
$groups = $group->lists();

// {
//     "groups": [
//         {
//             "id": 0,
//             "name": "未分组",
//             "count": 72596
//         },
//         {
//             "id": 1,
//             "name": "黑名单",
//             "count": 36
//         },
//         ...
//     ]
// }

var_dump($groups->groups[0]['name']) // “未分组”
```

### 创建分组

```php
$group->create($name);
```

example:

```php
$group->create($name);
```

### 修改分组信息

```php
$group->update($groupId, $name);
```

example:

```php
$group->update($groupId, "新的组名");
```

### 删除分组

```php
$group->delete($groupId);
```

example:

```php
$group->delete($groupId);
```

### 移动单个用户到指定分组

```php
$group->moveUser($openId, $groupId);
```

example:

```php
$group->moveUser($openId, $groupId);
```

### 批量移动用户到指定分组

```php
$group->moveUsers(array $openIds, $groupId);
```

example:

```php
$openIds = [$openId1, $openId2, $openId3 ...];
$group->moveUsers($openIds, $groupId);
```

关于用户管理请参考微信官方文档:http://mp.weixin.qq.com/wiki/ `用户管理` 章节。


================================================
FILE: docs/src/3.x/user-tag.md
================================================
# 用户标签


用户标签的使用就非常简单了,基本的增删改查。

## 获取实例

```php
<?php
use EasyWeChat\Foundation\Application;

// ...

$app = new Application($options);

$tag = $app->user_tag; // $user['user_tag']
```

## API

### 获取所有标签

```php
$tag->lists();
```

example:

```php
$tags = $tag->lists();

// {
//     "tags": [
//         {
//             "id": 0,
//             "name": "标签1",
//             "count": 72596
//         },
//         {
//             "id": 1,
//             "name": "标签2",
//             "count": 36
//         },
//         ...
//     ]
// }

var_dump($tags->tags[0]['name']) // “标签1”
```

### 创建标签

```php
$tag->create($name);
```

example:

```php
$tag->create('测试标签');
```

### 修改标签信息

```php
$tag->update($tagId, $name);
```

example:

```php
$tag->update(12, "新的名称");
```

### 删除标签

```php
$tag->delete($tagId);
```

example:

```php
$tag->delete($tagId);
```

### 获取指定 openid 用户身上的标签

```php
$userTags = $tag->userTags($openId);
//
// {
//     "tagid_list":["标签1","标签2"]
// }
```

### 获取标签下粉丝列表

```php
$tag->usersOfTag($tagId, $nextOpenId = '');
// $nextOpenId:第一个拉取的OPENID,不填默认从头开始拉取

// {
//   "count":2,//这次获取的粉丝数量
//   "data":{//粉丝列表
//      "openid":[
//          "ocYxcuAEy30bX0NXmGn4ypqx3tI0",
//          "ocYxcuBt0mRugKZ7tGAHPnUaOW7Y"
//      ]
//   },
//   "next_openid":"ocYxcuBt0mRugKZ7tGAHPnUaOW7Y"//拉取列表最后一个用户的openid
// }
```

### 批量为用户打标签

```php
$openIds = [$openId1, $openId2, ...];
$tag->batchTagUsers($openIds, $tagId);
```


### 批量为用户取消标签

```php
$openIds = [$openId1, $openId2, ...];
$tag->batchUntagUsers($openIds, $tagId);
```

关于用户管理请参考微信官方文档:http://mp.weixin.qq.com/wiki/ `用户管理` 章节。


================================================
FILE: docs/src/3.x/user.md
================================================
# 用户


用户信息的获取是微信开发中比较常用的一个功能了,以下所有的用户信息的获取与更新,都是**基于微信的 `openid` 的,并且是已关注当前账号的**,其它情况可能无法正常使用。

## 获取实例

```php
<?php
use EasyWeChat\Foundation\Application;

// ...

$app = new Application($options);

$userService = $app->user;
```

## API 列表

### 获取用户信息

```php
$userService->get($openId);
$userService->batchGet($openIds);
```

获取单个:

```php
$user = $userService->get($openId);

echo $user->nickname; // or $user['nickname']
```

获取多个:

```php
$users = $userService->batchGet([$openId1, $openId2, ...]);
```

### 获取用户列表

```php
$userService->lists($nextOpenId = null);  // $nextOpenId 可选
```

 example:

 ```php
 $users = $userService->lists();

 // result
 {
  "total": 2,
  "count": 2,
  "data": {
    "openid": [
      "",
      "OPENID1",
      "OPENID2"
    ]
  },
  "next_openid": "NEXT_OPENID"
}

$users->total; // 2
 ```

### 修改用户备注

```php
$userService->remark($openId, $remark); // 成功返回boolean
```

example:

```php
$userService->remark($openId, "僵尸粉");
```

### 获取用户所属用户组ID

```php
$userService->group($openId);
```

example:

```php
$userGroupId = $userService->group($openId);
```

## 其它

- [用户标签](user-tag.html)
- [用户分组](user-group.html)

关于用户管理请参考微信官方文档:http://mp.weixin.qq.com/wiki/ `用户管理` 章节。

================================================
FILE: docs/src/4.x/basic-services/content_security.md
================================================
# 内容安全接口

## 文本安全内容检测

用于校验一段文本是否含有违法内容。

### 频率限制

单个appid调用上限为2000次/分钟,1,000,000次/天

### 调用示例

```php
// 传入要检测的文本内容,长度不超过500K字节
$content = '你好';

$result = $app->content_security->checkText($content);

// 正常返回 0
{
    "errcode": "0",
    "errmsg": "ok"
}

//当 $content 内含有敏感信息,则返回 87014
{
    "errcode": 87014,
    "errmsg": "risky content"
}
```

## 图片安全内容检测

用于校验一张图片是否含有敏感信息。如涉黄、涉及敏感人脸(通常是政治人物)。

### 频率限制

单个appid调用上限为1000次/分钟,100,000次/天

### 调用示例

```php
// 所传参数为要检测的图片文件的绝对路径,图片格式支持PNG、JPEG、JPG、GIF, 像素不超过 750 x 1334,同时文件大小以不超过 300K 为宜,否则可能报错
$result = $app->content_security->checkImage('/path/to/the/image');

// 正常返回 0
{
    "errcode": "0",
    "errmsg": "ok"
}

// 当图片文件内含有敏感内容,则返回 87014
{
    "errcode": 87014,
    "errmsg": "risky content"
}
```

## 重要说明

目前上述两个接口仅支持在小程序中使用,示例中的 `$app` 表示小程序实例,即:

```php
use EasyWeChat\Factory;

$config = [
    'app_id' => 'wx3cf0f39249eb0exx',
    'secret' => 'f1c242f4f28f735d4687abb469072axx',

    // 下面为可选项
    // 指定 API 调用返回结果的类型:array(default)/collection/object/raw/自定义类名
    'response_type' => 'array',

    'log' => [
        'level' => 'debug',
        'file' => __DIR__.'/wechat.log',
    ],
];

$app = Factory::miniProgram($config);
```


================================================
FILE: docs/src/4.x/basic-services/jssdk.md
================================================
# JSSDK

微信 JSSDK 官方文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115

## API

#### 获取JSSDK的配置数组

```php
$app->jssdk->buildConfig(array $APIs, $debug = false, $beta = false, $json = true);
```

默认返回 JSON 字符串,当 `$json` 为 `false` 时返回数组,你可以直接使用到网页中。

#### 设置当前URL

```php
$app->jssdk->setUrl($url)
```
如果不想用默认读取的URL,可以使用此方法手动设置,通常不需要。


#### 示例

我们可以生成js配置文件:

```js
<script src="https://res.wx.qq.com/open/js/jweixin-1.4.0.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" charset="utf-8">
    wx.config(<?php echo $app->jssdk->buildConfig(array('updateAppMessageShareData', 'updateTimelineShareData'), true) ?>);
</script>
```
结果如下:


```js
<script src="https://res.wx.qq.com/open/js/jweixin-1.4.0.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" charset="utf-8">
wx.config({
    debug: true, // 请在上线前删除它
    appId: 'wx3cf0f39249eb0e60',
    timestamp: 1430009304,
    nonceStr: 'qey94m021ik',
    signature: '4F76593A4245644FAE4E1BC940F6422A0C3EC03E',
    jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData']
});
</script>
```



================================================
FILE: docs/src/4.x/basic-services/media.md
================================================
# 临时素材

上传的临时多媒体文件有格式和大小限制,如下:

> - 图片(image): 2M,支持 `JPG` 格式
> - 语音(voice):2M,播放长度不超过 `60s`,支持 `AMR\MP3` 格式
> - 视频(video):10MB,支持 `MP4` 格式
> - 缩略图(thumb):64KB,支持 `JPG` 格式

## 上传图片

> 注意:微信图片上传服务有敏感检测系统,图片内容如果含有敏感内容,如色情,商品推广,虚假信息等,上传可能失败。

```php
$app->media->uploadImage($path);
```

## 上传声音

```php
$app->media->uploadVoice($path);
```

## 上传视频

```php
$app->media->uploadVideo($path, $title, $description);
```

## 上传缩略图

用于视频封面或者音乐封面。

```php
$app->media->uploadThumb($path);
```

## 上传群发视频

上传视频获取 `media_id` 用以创建群发消息用。

```php
$app->media->uploadVideoForBroadcasting($path, $title, $description);

//{
//  "media_id": "rF4UdIMfYK3efUfyoddYRMU50zMiRmmt_l0kszupYh_SzrcW5Gaheq05p_lHuOTQ",
//  "title": "TITLE",
//  "description": "Description"
//}
```

## 创建群发消息

不要与上面 **上传群发视频** 搞混了,上面一个是上传视频得到 `media_id`,这个是使用该 `media_id` 加标题描述 **创建一条消息素材** 用来发送给用户。详情参见:[消息群发](../official-account/broadcasting.md)

```php
$app->media->createVideoForBroadcasting($mediaId, $title, $description);

//{
//  "type":"video",
//  "media_id":"IhdaAQXuvJtGzwwc0abfXnzeezfO0NgPK6AQYShD8RQYMTtfzbLdBIQkQziv2XJc",
//  "created_at":1398848981
//}
```

## 获取临时素材内容

比如图片、语音等二进制流内容,响应为 `EasyWeChat\Kernel\Http\StreamResponse` 实例。

```php
$stream = $app->media->get($mediaId);

if ($stream instanceof \EasyWeChat\Kernel\Http\StreamResponse) {
  // 以内容 md5 为文件名存到本地
  $stream->save('保存目录');

  // 自定义文件名,不需要带后缀
  $stream->saveAs('保存目录', '文件名');
}
```

## 获取 JSSDK 上传的高清语音

```php
$stream = $app->media->getJssdkMedia($mediaId);
$stream->saveAs('保存目录', 'custom-name.speex');
```


================================================
FILE: docs/src/4.x/basic-services/qrcode.md
================================================
# 二维码

目前有 2 种类型的二维码:

1. 临时二维码,是有过期时间的,最长可以设置为在二维码生成后的 **30天**后过期,但能够生成较多数量。临时二维码主要用于帐号绑定等不要求二维码永久保存的业务场景
2. 永久二维码,是无过期时间的,但数量较少(目前为最多10万个)。永久二维码主要用于适用于帐号绑定、用户来源统计等场景。

## 创建临时二维码

```php
$result = $app->qrcode->temporary('foo', 6 * 24 * 3600);

// Array
// (
//     [ticket] => gQFD8TwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyTmFjVTRWU3ViUE8xR1N4ajFwMWsAAgS2uItZAwQA6QcA
//     [expire_seconds] => 518400
//     [url] => http://weixin.qq.com/q/02NacU4VSubPO1GSxj1p1k
// )
```

## 创建永久二维码

```php
$result = $app->qrcode->forever(56);// 或者 $app->qrcode->forever("foo");
// Array
// (
//     [ticket] => gQFD8TwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyTmFjVTRWU3ViUE8xR1N4ajFwMWsAAgS2uItZAwQA6QcA
//     [url] => http://weixin.qq.com/q/02NacU4VSubPO1GSxj1p1k
// )
```

## 获取二维码网址

```php
$url = $app->qrcode->url($ticket);
// https://api.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET
```

## 获取二维码内容

```php
$url = $app->qrcode->url($ticket);

$content = file_get_contents($url); // 得到二进制图片内容

file_put_contents(__DIR__ . '/code.jpg', $content); // 写入文件
```


================================================
FILE: docs/src/4.x/basic-services/url.md
================================================
# 短网址服务

主要使用场景: 开发者用于生成二维码的原链接(商品、支付二维码等)太长导致扫码速度和成功率下降,将原长链接通过此接口转成短链接再生成二维码将大大提升扫码速度和成功率。

## 长链接转短链接

```php
$shortUrl = $app->url->shorten('https://easywechat.com');
//
(
    [errcode] => 0
    [errmsg] => ok
    [short_url] => https://w.url.cn/s/Aq7jWrd
)
```

================================================
FILE: docs/src/4.x/client.md
================================================
# API 调用

该方法将 API 交由开发者自行调用,微信有部分新的接口4.x并未全部兼容支持,可以使用该方案去自行封装接口:

例如URL Link接口

```php

$response = $app->httpPostJson('wxa/generate_urllink',[
    'path' => 'pages/index/index',
    'is_expire' => true,
    'expire_type' => 1,
    'expire_interval' => 1
]);
```

## 语法说明

```php
httpGet(string $uri, array $query = [])
httpPostJson(string $uri, array $data = [], array $query = [])
```



### GET

```php
$response = $app->httpGet('/cgi-bin/user/list', [
    'next_openid' => 'OPENID1',
]);
```

### POST

```php
$response = $app->httpPostJson('/cgi-bin/user/info/updateremark', [
    "openid" => "oDF3iY9ffA-hqb2vVvbr7qxf6A0Q",
    "remark" => "pangzi"
]);
```





================================================
FILE: docs/src/4.x/contributing.md
================================================
# 贡献代码

## 开发

我们欢迎广大开发者贡献大家的智慧,让我们共同让它变得更完美.

### 开始之前

请严格遵循以下代码标准:

> - [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md).
> - 使用 4 个空格作为缩进。

### 流程

1. Fork [overtrue/wechat](https://github.com/overtrue/wechat) 到本地.
2. 创建新的分支:

```shell
    $ git checkout -b new_feature
```

3. 编写代码。
4. Push 到你的分支:

```shell
    $ git push origin new_feature
```

5. 创建 Pull Request 并描述你完成的功能或者做出的修改。

> 注意:注释请使用英文

## 更新文档

我们的文档也是开源的,源代码在 [w7corp/EasyWeChat/docs](https://github.com/w7corp/easywechat/tree/master/docs)。

### 流程

1. Fork [w7corp/EasyWeChat](https://github.com/w7corp/easywechat)
2. Clone 到你的电脑:

```shell
    $ git clone https://github.com/<username>/site.git
    $ cd docs
```

3. 创建新的分支,编辑文档
4. Push 到你的分支。
5. 创建 Pull Request 并描述你完成的功能或者做出的修改。

## 报告 Bug

当你在使用过程中遇到问题,请查阅 [疑难解答](troubleshooting.html) 或者在这里提问 [GitHub](https://github.com/overtrue/wechat/issues). 如果还是不能解决你的问题,请到 GitHub 联系我们。

[overtrue/wechat]: https://github.com/overtrue/wechat


================================================
FILE: docs/src/4.x/customize/access_token.md
================================================
# Access Token


我们一个 SDK 应用在初始化以后,你可以在任何时机从应用中拿到该配置下的 Access Token 实例:

```php
use EasyWeChat\Factory;

$config = [
    //...
];

$app = Factory::officialAccount($config);

// 获取 access token 实例
$accessToken = $app->access_token;
$token = $accessToken->getToken(); // token 数组  token['access_token'] 字符串
$token = $accessToken->getToken(true); // 强制重新从微信服务器获取 token.
```

## 修改 `$app` 的 Access Token

```php
$app['access_token']->setToken($newAccessToken, 7200);
```

例如:

```php
$app['access_token']->setToken('ccfdec35bd7ba359f6101c2da321d675');
// 或者指定过期时间
$app['access_token']->setToken('ccfdec35bd7ba359f6101c2da321d675', 3600);  // 单位:秒
```


================================================
FILE: docs/src/4.x/customize/cache.md
================================================
# 缓存


本项目使用 [symfony/cache](https://github.com/symfony/cache) 来完成缓存工作,它支持基本目前所有的缓存引擎。

在我们的 SDK 中的所有缓存默认使用文件缓存,缓存路径取决于 PHP 的临时目录,如果你需要自定义缓存,那么你需要做如下的事情:

你可以参考[symfony/cache官方文档](https://symfony.com/doc/current/components/cache.html) 来替换掉应用中默认的缓存配置:


## 以 redis 为例


### Symfony 4.3 + 

> 请先安装 redis 拓展:`composer require predis/predis`

```php

use Symfony\Component\Cache\Adapter\RedisAdapter;

// 创建 redis 实例
$client = new \Predis\Client('tcp://10.0.0.1:6379');

// 创建缓存实例
$cache = new RedisAdapter($client);

// 替换应用中的缓存
$app->rebind('cache', $cache);
```

### Symfony 3.4 + 

> 请先安装 redis 拓展:https://github.com/phpredis/phpredis

```php

use Symfony\Component\Cache\Simple\RedisCache;

// 创建 redis 实例
$redis = new Redis();
$redis->connect('redis_host', 6379);

// 创建缓存实例
$cache = new RedisCache($redis);

// 替换应用中的缓存
$app->rebind('cache', $cache);
```


### Laravel 中使用

在 Laravel 中框架使用 [predis/predis](https://github.com/nrk/predis):

### Symfony 4.3 + 

> 请先安装 redis 拓展:`composer require predis/predis`

```php

use Symfony\Component\Cache\Adapter\RedisAdapter;

// 创建缓存实例
$cache = new RedisAdapter(app('redis')->connection()->client());
$app->rebind('cache', $cache);

```

### Symfony 3.4 + 

```php

use Symfony\Component\Cache\Simple\RedisCache;

$predis = app('redis')->connection()->client(); // connection($name), $name 默认为 `default`
$cache = new RedisCache($predis);

$app->rebind('cache', $cache);
```

> 上面提到的 `app('redis')->connection($name)`, 这里的 `$name` 是 laravel 项目中配置文件 `database.php` 中 `redis` 配置名 `default`:https://github.com/laravel/laravel/blob/master/config/database.php#L118
> 如果你使用的其它连接,对应传名称就好了。

## 使用自定义的缓存方式

如果你发现 symfony 提供的十几种缓存方式都满足不了你的需求的话,那么你可以自己建立一个类来完成缓存操作,前提这个类得实现接口:[PSR-16](http://www.php-fig.org/psr/psr-16/)

该接口有以下方法需要实现:

```php
   public function get($key, $default = null);
   public function set($key, $value, $ttl = null);
   public function delete($key);
   public function clear();
   public function getMultiple($keys, $default = null);
   public function setMultiple($values, $ttl = null);
   public function deleteMultiple($keys);
   public function has($key);
```

下面为一个示例:

```php
<?php

use Psr\SimpleCache\CacheInterface;

class MyCustomCache implements CacheInterface
{
    public function get($key, $default = null)
    {
        // your code
    }

    public function set($key, $value, $ttl = null)
    {
        // your code
    }

    public function delete($key)
    {
        // your code
    }

    public function clear()
    {
        // your code
    }

    public function getMultiple($keys, $default = null)
    {
        // your code
    }

    public function setMultiple($values, $ttl = null)
    {
        // your code
    }

    public function deleteMultiple($keys)
    {
        // your code
    }

    public function has($key)
    {
        // your code
    }
}
```

然后实例化你的缓存类并在 EasyWeChat 里使用它:

```php
$app->rebind('cache', new MyCustomCache());
```

OK,这样就完成了自定义缓存的操作。


================================================
FILE: docs/src/4.x/customize/replace-service.md
================================================
# 自定义服务模块

由于使用了容器模式来组织各模块的实例,意味着你可以比较容易的替换掉已经有的服务,以公众号服务为例:

```php

<...>

$app = Factory::officialAccount($config);

$app->rebind('request', new MyCustomRequest(...)); 
```

这里的 `request` 为 SDK 内部服务名称。


================================================
FILE: docs/src/4.x/index.md
================================================
> 👋🏼 您当前浏览的文档为 4.x,其它版本的文档请参考:[6.x](/6.x/)、[5.x](/5.x/)、[3.x](/3.x/)

# EasyWeChat

EasyWeChat 是一个开源的 [微信](http://www.wechat.com) 非官方 SDK。安装非常简单,因为它是一个标准的 [Composer](https://getcomposer.org/) 包,这意味着任何满足下列安装条件的 PHP 项目支持 Composer 都可以使用它。

## 环境要求

> - PHP >= 7.0
> - [PHP cURL 扩展](http://php.net/manual/en/book.curl.php)
> - [PHP OpenSSL 扩展](http://php.net/manual/en/book.openssl.php)
> - [PHP SimpleXML 扩展](http://php.net/manual/en/book.simplexml.php)
> - [PHP fileinfo 拓展](http://php.net/manual/en/book.fileinfo.php)

Laravel 5 拓展包: [overtrue/laravel-wechat](https://github.com/overtrue/laravel-wechat)

# 参与贡献

1. fork 当前库到你的名下
2. 选择你想要修改的语言版本,`zh-CN` 或者 `en`
3. 在你的本地修改完成审阅过后提交到你的仓库
4. 提交 PR 并描述你的修改,等待合并

# License

MIT


================================================
FILE: docs/src/4.x/installation.md
================================================
# 安装


## 环境要求

> - PHP >= 7.0
> - [PHP cURL 扩展](http://php.net/manual/en/book.curl.php)
> - [PHP OpenSSL 扩展](http://php.net/manual/en/book.openssl.php)
> - [PHP SimpleXML 扩展](http://php.net/manual/en/book.simplexml.php)
> - [PHP fileinfo 拓展](http://php.net/manual/en/book.fileinfo.php)

Laravel 5 拓展包: [overtrue/laravel-wechat](https://github.com/overtrue/laravel-wechat)

## 安装

使用 [composer](http://getcomposer.org/):

```shell
$ composer require overtrue/wechat:~4.0 -vvv
```

================================================
FILE: docs/src/4.x/integration.md
================================================
# 在框架中使用

EasyWeChat 是一个通用的 Composer 包,所以不需要对框架单独做修改,只要支持 Composer 就能直接使用,当然了,为了更方便的使用,我们收集了以下框架单独提供的拓展包:

## Laravel

>  - [overtrue/laravel-wechat](https://github.com/overtrue/laravel-wechat)


## Symfony

>  - [lilocon/WechatBundle](https://github.com/lilocon/WechatBundle)

## Yii

> - [jianyan74/yii2-easy-wechat](https://github.com/jianyan74/yii2-easy-wechat) 适用于 EasyWeChat 4.x 
> - [max-wen/yii2-easy-wechat](https://github.com/max-wen/yii2-easy-wechat) 适用于 EasyWeChat 3.x 

## ThinkPHP

>  - [naixiaoxin/think-wechat](https://github.com/qiqizjl/think-wechat) 适用于 EasyWeChat 4.x
>  - [zyan/think-wechat](https://github.com/aa24615/think-wechat) 适用于 EasyWeChat 4.x/5.x


## CI

TODO

## Phalcon

TODO

... more



================================================
FILE: docs/src/4.x/micro-merchant/certficates.md
================================================
# 获取平台证书
调用获取平台证书接口之前,请前往微信支付商户平台升级API证书,升级后才可成功调用本接口。

```php
// 获取到证书后可以做缓存处理,无需每次重新获取
$response = $app->certficates->get(bool $returnRaw = false);

// 获取到平台证书后,可以直接使用 setCertificate 方法把证书配置追加到配置项里面去
$app->setCertificate(string $certificate, string $serialNo);
```
> $returnRaw 不填默认为false时,请确保你的PHP已安装了sodium扩展    
> 返回值:固定array格式的解密后的证书信息

> $returnRaw 传入true时     
> 返回值:Response对象`$response->getBody()->getContents();`获取到微信返回xml原始数据


================================================
FILE: docs/src/4.x/micro-merchant/index.md
================================================
# 小微商户

你在阅读本文之前确认你已经仔细阅读了:[微信小微商户专属接口文档](https://pay.weixin.qq.com/wiki/doc/api/xiaowei.php?chapter=19_2)。

PS: ⚠️ 因系统升级,腾讯暂时关闭了小微商户接口,恢复时间未定。调用提交申请接口会提示「PARAM_ERROR」,详细说明可参见[微信开放平台相关帖子](https://developers.weixin.qq.com/community/develop/doc/0000a0ffc9ce28bd4bc9999ba5b800)


## 配置

小微商户整体接口调用方式相对于其他微信接口略有不同,配置时请勿填错,相关配置如下:

```php
use EasyWeChat\Factory;

$config = [
    // 必要配置
    'mch_id'           => 'your-mch-id', // 服务商的商户号
    'key'              => 'key-for-signature', // API 密钥
    'apiv3_key'        => 'APIv3-key-for-signature', // APIv3 密钥
    // API 证书路径(登录商户平台下载 API 证书)
    'cert_path'        => 'path/to/your/cert.pem', // XXX: 绝对路径!!!!
    'key_path'         => 'path/to/your/key', // XXX: 绝对路径!!!!
    // 以下两项配置在获取证书接口时可为空,在调用入驻接口前请先调用获取证书接口获取以下两项配置,如果获取过证书可以直接在这里配置,也可参照本文档获取平台证书章节中示例
    // 'serial_no'     => '获取证书接口获取到的平台证书序列号',
    // 'certificate'   => '获取证书接口获取到的证书内容'
    
    // 以下为可选项
    // 指定 API 调用返回结果的类型:array(default)/collection/object/raw/自定义类名
    'response_type' => 'array'
    'appid'            => 'wx931386123456789e' // 服务商的公众账号 ID
];

$app = Factory::microMerchant($config);

```


`$app` 在所有相关小微商户的文档都是指 `Factory::microMerchant` 得到的实例,就不在每个页面单独写了。

## 使用时值得注意的地方:
1、小微商户所有接口中以下列出参数 `version`, `mch_id`, `nonce_str`, `sign`, `sign_type`, `cert_sn` 可不用传入。

2、所有敏感信息无需手动加密,sdk会在调用接口前自动完成加密

3、在调用入驻等需要敏感信息加密的接口前请先调用获取证书接口然后把配置填入配置项

4、入驻成功获取到子商户号后需帮助子商户调用配置修改等接口可以先调用以下方法,方便调用修改等接口时无需再次传入子商户号
```php
// $subMchId 为子商户号
// $appid    服务商的公众账号 ID
$app->setSubMchId(string $subMchId, string $appId = '');
```


================================================
FILE: docs/src/4.x/micro-merchant/material.md
================================================
# 商户信息修改
## 修改结算银行卡

```php
$response = $app->material->setSettlementCard([
    // 'sub_mch_id' => '1230000109',
    'account_number' => '银行卡号',
    'bank_name' => '开户银行全称(含支行)',
    'account_bank' => '开户银行',
    'ba
Download .txt
gitextract_ygm6ii3b/

├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE.md
│   └── workflows/
│       ├── deploy.yml
│       ├── lint.yml
│       └── test.yml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── composer.json
├── docs/
│   ├── .editorconfig
│   ├── .gitignore
│   ├── .npmrc
│   ├── .prettierrc
│   ├── .vitepress/
│   │   ├── config.ts
│   │   ├── theme/
│   │   │   ├── components/
│   │   │   │   ├── Banner.vue
│   │   │   │   ├── Footer.vue
│   │   │   │   ├── SponsorsAside.vue
│   │   │   │   ├── SponsorsGroup.vue
│   │   │   │   └── VersionTag.vue
│   │   │   ├── index.ts
│   │   │   └── styles/
│   │   │       ├── badges.css
│   │   │       ├── index.css
│   │   │       ├── inline-demo.css
│   │   │       ├── layout.css
│   │   │       ├── options-boxes.css
│   │   │       ├── pages.css
│   │   │       ├── style-guide.css
│   │   │       └── utilities.css
│   │   ├── versions.ts
│   │   └── vitepress+1.6.3.patch
│   ├── README.md
│   ├── env.d.ts
│   ├── package.json
│   ├── postcss.config.js
│   ├── purge-caches
│   ├── src/
│   │   ├── 3.x/
│   │   │   ├── access_token.md
│   │   │   ├── accounts.md
│   │   │   ├── anaylsis.md
│   │   │   ├── broadcast.md
│   │   │   ├── cache.md
│   │   │   ├── card.md
│   │   │   ├── configuration.md
│   │   │   ├── contributing.md
│   │   │   ├── events.md
│   │   │   ├── index.md
│   │   │   ├── installation.md
│   │   │   ├── integration.md
│   │   │   ├── js.md
│   │   │   ├── lucky-money.md
│   │   │   ├── material.md
│   │   │   ├── menu.md
│   │   │   ├── merchant_payment.md
│   │   │   ├── message-transfer.md
│   │   │   ├── messages.md
│   │   │   ├── mini_program.md
│   │   │   ├── miscellaneous.md
│   │   │   ├── notice.md
│   │   │   ├── oauth.md
│   │   │   ├── open_platform.md
│   │   │   ├── overview.md
│   │   │   ├── payment.md
│   │   │   ├── poi.md
│   │   │   ├── qrcode.md
│   │   │   ├── releases.md
│   │   │   ├── reply.md
│   │   │   ├── roadmap.md
│   │   │   ├── semantic.md
│   │   │   ├── server.md
│   │   │   ├── shake-around.md
│   │   │   ├── short-url.md
│   │   │   ├── sidebar.js
│   │   │   ├── staff.md
│   │   │   ├── store.md
│   │   │   ├── troubleshooting.md
│   │   │   ├── tutorial.md
│   │   │   ├── user-group.md
│   │   │   ├── user-tag.md
│   │   │   └── user.md
│   │   ├── 4.x/
│   │   │   ├── basic-services/
│   │   │   │   ├── content_security.md
│   │   │   │   ├── jssdk.md
│   │   │   │   ├── media.md
│   │   │   │   ├── qrcode.md
│   │   │   │   └── url.md
│   │   │   ├── client.md
│   │   │   ├── contributing.md
│   │   │   ├── customize/
│   │   │   │   ├── access_token.md
│   │   │   │   ├── cache.md
│   │   │   │   └── replace-service.md
│   │   │   ├── index.md
│   │   │   ├── installation.md
│   │   │   ├── integration.md
│   │   │   ├── micro-merchant/
│   │   │   │   ├── certficates.md
│   │   │   │   ├── index.md
│   │   │   │   ├── material.md
│   │   │   │   ├── media.md
│   │   │   │   ├── merchant-config.md
│   │   │   │   ├── submit-application.md
│   │   │   │   ├── upgrade.md
│   │   │   │   └── withdraw.md
│   │   │   ├── mini-program/
│   │   │   │   ├── app_code.md
│   │   │   │   ├── auth.md
│   │   │   │   ├── customer_service.md
│   │   │   │   ├── data_cube.md
│   │   │   │   ├── decrypt.md
│   │   │   │   ├── express.md
│   │   │   │   ├── index.md
│   │   │   │   ├── nearby_poi.md
│   │   │   │   ├── plugin.md
│   │   │   │   ├── soter.md
│   │   │   │   ├── subscribe_message.md
│   │   │   │   └── template_message.md
│   │   │   ├── miscellaneous.md
│   │   │   ├── official-account/
│   │   │   │   ├── accounts.md
│   │   │   │   ├── base.md
│   │   │   │   ├── broadcasting.md
│   │   │   │   ├── card.md
│   │   │   │   ├── comment.md
│   │   │   │   ├── configuration.md
│   │   │   │   ├── customer_service.md
│   │   │   │   ├── data_cube.md
│   │   │   │   ├── events.md
│   │   │   │   ├── goods.md
│   │   │   │   ├── index.md
│   │   │   │   ├── material.md
│   │   │   │   ├── menu.md
│   │   │   │   ├── message-transfer.md
│   │   │   │   ├── messages.md
│   │   │   │   ├── oauth.md
│   │   │   │   ├── poi.md
│   │   │   │   ├── reply.md
│   │   │   │   ├── semantic.md
│   │   │   │   ├── server.md
│   │   │   │   ├── shake-around.md
│   │   │   │   ├── store.md
│   │   │   │   ├── template_message.md
│   │   │   │   ├── tutorial.md
│   │   │   │   ├── user-tag.md
│   │   │   │   └── user.md
│   │   │   ├── open-platform/
│   │   │   │   ├── authorizer-delegate.md
│   │   │   │   ├── index.md
│   │   │   │   └── server.md
│   │   │   ├── open-work/
│   │   │   │   ├── index.md
│   │   │   │   ├── provider.md
│   │   │   │   ├── server.md
│   │   │   │   ├── service.md
│   │   │   │   └── work.md
│   │   │   ├── overview.md
│   │   │   ├── payment/
│   │   │   │   ├── bill.md
│   │   │   │   ├── contract.md
│   │   │   │   ├── index.md
│   │   │   │   ├── jssdk.md
│   │   │   │   ├── micropay.md
│   │   │   │   ├── notify.md
│   │   │   │   ├── order.md
│   │   │   │   ├── profit-sharing.md
│   │   │   │   ├── redpack.md
│   │   │   │   ├── refund.md
│   │   │   │   ├── reverse.md
│   │   │   │   ├── scan-pay.md
│   │   │   │   ├── security.md
│   │   │   │   └── transfer.md
│   │   │   ├── sidebar.js
│   │   │   ├── troubleshooting.md
│   │   │   └── wework/
│   │   │       ├── agents.md
│   │   │       ├── contacts.md
│   │   │       ├── external-contact.md
│   │   │       ├── group-robot.md
│   │   │       ├── index.md
│   │   │       ├── invoice.md
│   │   │       ├── media.md
│   │   │       ├── menu.md
│   │   │       ├── message.md
│   │   │       ├── oa.md
│   │   │       ├── oauth.md
│   │   │       └── server.md
│   │   ├── 5.x/
│   │   │   ├── basic-services/
│   │   │   │   ├── content_security.md
│   │   │   │   ├── jssdk.md
│   │   │   │   ├── media.md
│   │   │   │   ├── qrcode.md
│   │   │   │   └── url.md
│   │   │   ├── contributing.md
│   │   │   ├── customize/
│   │   │   │   ├── access_token.md
│   │   │   │   ├── cache.md
│   │   │   │   └── replace-service.md
│   │   │   ├── index.md
│   │   │   ├── installation.md
│   │   │   ├── integration.md
│   │   │   ├── micro-merchant/
│   │   │   │   ├── certficates.md
│   │   │   │   ├── index.md
│   │   │   │   ├── material.md
│   │   │   │   ├── media.md
│   │   │   │   ├── merchant-config.md
│   │   │   │   ├── submit-application.md
│   │   │   │   ├── upgrade.md
│   │   │   │   └── withdraw.md
│   │   │   ├── mini-program/
│   │   │   │   ├── activity_message.md
│   │   │   │   ├── app_code.md
│   │   │   │   ├── auth.md
│   │   │   │   ├── business.md
│   │   │   │   ├── customer_service.md
│   │   │   │   ├── data_cube.md
│   │   │   │   ├── decrypt.md
│   │   │   │   ├── express.md
│   │   │   │   ├── index.md
│   │   │   │   ├── live.md
│   │   │   │   ├── mall.md
│   │   │   │   ├── nearby_poi.md
│   │   │   │   ├── ocr.md
│   │   │   │   ├── phone_number.md
│   │   │   │   ├── plugin.md
│   │   │   │   ├── realtime_log.md
│   │   │   │   ├── risk_control.md
│   │   │   │   ├── safety_control.md
│   │   │   │   ├── search.md
│   │   │   │   ├── shipping.md
│   │   │   │   ├── short_link.md
│   │   │   │   ├── soter.md
│   │   │   │   ├── subscribe_message.md
│   │   │   │   ├── template_message.md
│   │   │   │   ├── union.md
│   │   │   │   ├── url_link.md
│   │   │   │   └── url_scheme.md
│   │   │   ├── miscellaneous.md
│   │   │   ├── official-account/
│   │   │   │   ├── accounts.md
│   │   │   │   ├── base.md
│   │   │   │   ├── broadcasting.md
│   │   │   │   ├── card.md
│   │   │   │   ├── comment.md
│   │   │   │   ├── configuration.md
│   │   │   │   ├── customer_service.md
│   │   │   │   ├── data_cube.md
│   │   │   │   ├── draft.md
│   │   │   │   ├── events.md
│   │   │   │   ├── goods.md
│   │   │   │   ├── index.md
│   │   │   │   ├── material.md
│   │   │   │   ├── menu.md
│   │   │   │   ├── message-transfer.md
│   │   │   │   ├── messages.md
│   │   │   │   ├── oauth.md
│   │   │   │   ├── poi.md
│   │   │   │   ├── reply.md
│   │   │   │   ├── semantic.md
│   │   │   │   ├── server.md
│   │   │   │   ├── shake-around.md
│   │   │   │   ├── store.md
│   │   │   │   ├── template_message.md
│   │   │   │   ├── tutorial.md
│   │   │   │   ├── user-tag.md
│   │   │   │   └── user.md
│   │   │   ├── open-platform/
│   │   │   │   ├── authorizer-delegate.md
│   │   │   │   ├── index.md
│   │   │   │   └── server.md
│   │   │   ├── open-work/
│   │   │   │   ├── index.md
│   │   │   │   ├── provider.md
│   │   │   │   ├── server.md
│   │   │   │   ├── service.md
│   │   │   │   └── work.md
│   │   │   ├── overview.md
│   │   │   ├── payment/
│   │   │   │   ├── bill.md
│   │   │   │   ├── contract.md
│   │   │   │   ├── index.md
│   │   │   │   ├── jssdk.md
│   │   │   │   ├── micropay.md
│   │   │   │   ├── notify.md
│   │   │   │   ├── order.md
│   │   │   │   ├── profit-sharing.md
│   │   │   │   ├── redpack.md
│   │   │   │   ├── refund.md
│   │   │   │   ├── reverse.md
│   │   │   │   ├── scan-pay.md
│   │   │   │   ├── security.md
│   │   │   │   └── transfer.md
│   │   │   ├── sidebar.js
│   │   │   ├── troubleshooting.md
│   │   │   └── wework/
│   │   │       ├── agents.md
│   │   │       ├── calendar.md
│   │   │       ├── chat.md
│   │   │       ├── contacts.md
│   │   │       ├── corp-group.md
│   │   │       ├── external-contact.md
│   │   │       ├── group-robot.md
│   │   │       ├── group-welcome-template.md
│   │   │       ├── index.md
│   │   │       ├── intercept.md
│   │   │       ├── invoice.md
│   │   │       ├── jssdk.md
│   │   │       ├── kf.md
│   │   │       ├── media.md
│   │   │       ├── menu.md
│   │   │       ├── message.md
│   │   │       ├── mini-program.md
│   │   │       ├── mobile.md
│   │   │       ├── msg-audit.md
│   │   │       ├── oa.md
│   │   │       ├── oauth.md
│   │   │       ├── product.md
│   │   │       ├── server.md
│   │   │       ├── to-account.md
│   │   │       └── wedrive.md
│   │   ├── 6.x/
│   │   │   ├── cache.md
│   │   │   ├── client.md
│   │   │   ├── contributing.md
│   │   │   ├── index.md
│   │   │   ├── introduction.md
│   │   │   ├── logging.md
│   │   │   ├── mini-app/
│   │   │   │   ├── config.md
│   │   │   │   ├── examples.md
│   │   │   │   ├── index.md
│   │   │   │   ├── server.md
│   │   │   │   └── utils.md
│   │   │   ├── oauth.md
│   │   │   ├── official-account/
│   │   │   │   ├── config.md
│   │   │   │   ├── examples.md
│   │   │   │   ├── index.md
│   │   │   │   ├── message.md
│   │   │   │   ├── server.md
│   │   │   │   └── utils.md
│   │   │   ├── open-platform/
│   │   │   │   ├── examples.md
│   │   │   │   ├── index.md
│   │   │   │   └── server.md
│   │   │   ├── open-work/
│   │   │   │   ├── examples.md
│   │   │   │   ├── index.md
│   │   │   │   ├── oauth.md
│   │   │   │   └── server.md
│   │   │   ├── pay/
│   │   │   │   ├── examples.md
│   │   │   │   ├── index.md
│   │   │   │   ├── media.md
│   │   │   │   ├── server.md
│   │   │   │   └── utils.md
│   │   │   ├── sidebar.js
│   │   │   ├── troubleshooting.md
│   │   │   └── work/
│   │   │       ├── examples.md
│   │   │       ├── index.md
│   │   │       ├── oauth.md
│   │   │       ├── server.md
│   │   │       └── utils.md
│   │   └── index.md
│   ├── tailwind.config.js
│   └── tsconfig.json
├── phpstan.neon
├── phpunit.xml
├── pint.json
├── src/
│   ├── Kernel/
│   │   ├── Config.php
│   │   ├── Contracts/
│   │   │   ├── AccessToken.php
│   │   │   ├── AccessTokenAwareHttpClient.php
│   │   │   ├── Aes.php
│   │   │   ├── Arrayable.php
│   │   │   ├── Config.php
│   │   │   ├── JsApiTicket.php
│   │   │   ├── Jsonable.php
│   │   │   ├── RefreshableAccessToken.php
│   │   │   ├── RefreshableJsApiTicket.php
│   │   │   └── Server.php
│   │   ├── Encryptor.php
│   │   ├── Exceptions/
│   │   │   ├── BadMethodCallException.php
│   │   │   ├── BadRequestException.php
│   │   │   ├── BadResponseException.php
│   │   │   ├── DecryptException.php
│   │   │   ├── Exception.php
│   │   │   ├── HttpException.php
│   │   │   ├── InvalidArgumentException.php
│   │   │   ├── InvalidConfigException.php
│   │   │   ├── RuntimeException.php
│   │   │   └── ServiceNotFoundException.php
│   │   ├── Form/
│   │   │   ├── File.php
│   │   │   └── Form.php
│   │   ├── HttpClient/
│   │   │   ├── AccessTokenAwareClient.php
│   │   │   ├── AccessTokenExpiredRetryStrategy.php
│   │   │   ├── HttpClientMethods.php
│   │   │   ├── RequestUtil.php
│   │   │   ├── RequestWithPresets.php
│   │   │   ├── Response.php
│   │   │   ├── RetryableClient.php
│   │   │   └── ScopingHttpClient.php
│   │   ├── Message.php
│   │   ├── ServerResponse.php
│   │   ├── Support/
│   │   │   ├── AesCbc.php
│   │   │   ├── AesEcb.php
│   │   │   ├── AesGcm.php
│   │   │   ├── Arr.php
│   │   │   ├── MessageParser.php
│   │   │   ├── Pkcs7.php
│   │   │   ├── PrivateKey.php
│   │   │   ├── PublicKey.php
│   │   │   ├── Str.php
│   │   │   ├── UserAgent.php
│   │   │   └── Xml.php
│   │   └── Traits/
│   │       ├── DecryptJsonMessage.php
│   │       ├── DecryptMessage.php
│   │       ├── DecryptXmlMessage.php
│   │       ├── HasAttributes.php
│   │       ├── InteractWithCache.php
│   │       ├── InteractWithClient.php
│   │       ├── InteractWithConfig.php
│   │       ├── InteractWithHandlers.php
│   │       ├── InteractWithHttpClient.php
│   │       ├── InteractWithServerRequest.php
│   │       ├── MockableHttpClient.php
│   │       ├── RespondJsonMessage.php
│   │       └── RespondXmlMessage.php
│   ├── MiniApp/
│   │   ├── AccessToken.php
│   │   ├── Account.php
│   │   ├── Application.php
│   │   ├── Contracts/
│   │   │   ├── Account.php
│   │   │   └── Application.php
│   │   ├── Decryptor.php
│   │   ├── Server.php
│   │   └── Utils.php
│   ├── OfficialAccount/
│   │   ├── AccessToken.php
│   │   ├── Account.php
│   │   ├── Application.php
│   │   ├── Config.php
│   │   ├── Contracts/
│   │   │   ├── Account.php
│   │   │   └── Application.php
│   │   ├── JsApiTicket.php
│   │   ├── Message.php
│   │   ├── Server.php
│   │   └── Utils.php
│   ├── OpenPlatform/
│   │   ├── Account.php
│   │   ├── Application.php
│   │   ├── Authorization.php
│   │   ├── AuthorizerAccessToken.php
│   │   ├── ComponentAccessToken.php
│   │   ├── Config.php
│   │   ├── Contracts/
│   │   │   ├── Account.php
│   │   │   ├── Application.php
│   │   │   └── VerifyTicket.php
│   │   ├── Message.php
│   │   ├── Server.php
│   │   └── VerifyTicket.php
│   ├── OpenWork/
│   │   ├── Account.php
│   │   ├── Application.php
│   │   ├── Authorization.php
│   │   ├── AuthorizerAccessToken.php
│   │   ├── Config.php
│   │   ├── Contracts/
│   │   │   ├── Account.php
│   │   │   ├── Application.php
│   │   │   └── SuiteTicket.php
│   │   ├── Encryptor.php
│   │   ├── JsApiTicket.php
│   │   ├── Message.php
│   │   ├── ProviderAccessToken.php
│   │   ├── Server.php
│   │   ├── SuiteAccessToken.php
│   │   ├── SuiteEncryptor.php
│   │   └── SuiteTicket.php
│   ├── Pay/
│   │   ├── Application.php
│   │   ├── Client.php
│   │   ├── Config.php
│   │   ├── Contracts/
│   │   │   ├── Application.php
│   │   │   ├── Merchant.php
│   │   │   ├── ResponseValidator.php
│   │   │   └── Validator.php
│   │   ├── Exceptions/
│   │   │   ├── EncryptionFailureException.php
│   │   │   └── InvalidSignatureException.php
│   │   ├── LegacySignature.php
│   │   ├── Merchant.php
│   │   ├── Message.php
│   │   ├── ResponseValidator.php
│   │   ├── Server.php
│   │   ├── Signature.php
│   │   ├── URLSchemeBuilder.php
│   │   ├── Utils.php
│   │   └── Validator.php
│   └── Work/
│       ├── AccessToken.php
│       ├── Account.php
│       ├── Application.php
│       ├── Config.php
│       ├── Contracts/
│       │   ├── Account.php
│       │   └── Application.php
│       ├── Encryptor.php
│       ├── JsApiTicket.php
│       ├── Message.php
│       ├── Server.php
│       └── Utils.php
└── tests/
    ├── Kernel/
    │   ├── EncryptorTest.php
    │   ├── HttpClient/
    │   │   ├── AccessTokenAwareClientTest.php
    │   │   ├── AccessTokenExpiredRetryStrategyTest.php
    │   │   ├── HttpClientMethodsTest.php
    │   │   ├── RequestUtilTest.php
    │   │   ├── RequestWithPresetsTest.php
    │   │   ├── ResponseTest.php
    │   │   └── RetryableClientTest.php
    │   ├── MessageTest.php
    │   ├── ServerResponseTest.php
    │   ├── Support/
    │   │   ├── AesCbcTest.php
    │   │   ├── AesEcbTest.php
    │   │   ├── AesGcmTest.php
    │   │   ├── MessageParserTest.php
    │   │   ├── PrivateKeyTest.php
    │   │   ├── PublicKeyTest.php
    │   │   └── UserAgentTest.php
    │   └── Traits/
    │       ├── DecryptJsonMessageTest.php
    │       ├── DecryptXmlMessageTest.php
    │       ├── InteractWithCacheTest.php
    │       ├── InteractWithClientTest.php
    │       ├── InteractWithConfigTest.php
    │       ├── InteractWithHandlersTest.php
    │       ├── InteractWithHttpClientTest.php
    │       ├── InteractWithServerRequestTest.php
    │       └── RespondXmlMessageTest.php
    ├── MiniApp/
    │   ├── AccessTokenTest.php
    │   ├── ApplicationTest.php
    │   ├── DecryptorTest.php
    │   └── UtilsTest.php
    ├── OfficialAccount/
    │   ├── AccessTokenTest.php
    │   ├── AccountTest.php
    │   ├── ApplicationTest.php
    │   ├── ConfigTest.php
    │   ├── JsApiTicketTest.php
    │   ├── ServerTest.php
    │   └── UtilsTest.php
    ├── OpenPlatform/
    │   ├── AccountTest.php
    │   ├── ApplicationTest.php
    │   ├── AuthorizationTest.php
    │   ├── AuthorizerAccessTokenTest.php
    │   ├── ComponentAccessTokenTest.php
    │   └── ServerTest.php
    ├── Pay/
    │   ├── ApplicationTest.php
    │   ├── ClientTest.php
    │   ├── MerchantTest.php
    │   ├── ServerTest.php
    │   └── UtilsTest.php
    ├── TestCase.php
    ├── Work/
    │   ├── AccessTokenTest.php
    │   ├── AccountTest.php
    │   ├── ApplicationTest.php
    │   ├── ConfigTest.php
    │   ├── JsApiTicketTest.php
    │   ├── ServerTest.php
    │   └── UtilsTest.php
    ├── bootstrap.php
    └── fixtures/
        ├── cert.p12
        ├── cert.pem
        ├── files/
        │   ├── demo_cert.pem
        │   ├── demo_key.pem
        │   ├── empty.file
        │   └── pay_demo.json
        └── private.key
Download .txt
SYMBOL INDEX (1046 symbols across 190 files)

FILE: docs/.vitepress/theme/index.ts
  method enhanceApp (line 18) | enhanceApp({ app }: { app: App }) {

FILE: src/Kernel/Config.php
  class Config (line 17) | class Config implements ArrayAccess, ConfigInterface
    method __construct (line 29) | public function __construct(
    method has (line 35) | #[\JetBrains\PhpStorm\Pure]
    method get (line 44) | #[\JetBrains\PhpStorm\Pure]
    method getMany (line 58) | #[\JetBrains\PhpStorm\Pure]
    method set (line 74) | public function set(string $key, mixed $value = null): void
    method all (line 82) | public function all(): array
    method offsetExists (line 87) | #[\JetBrains\PhpStorm\Pure]
    method offsetGet (line 93) | #[\JetBrains\PhpStorm\Pure]
    method offsetSet (line 99) | public function offsetSet(mixed $offset, mixed $value): void
    method offsetUnset (line 104) | public function offsetUnset(mixed $offset): void
    method checkMissingKeys (line 112) | public function checkMissingKeys(): bool

FILE: src/Kernel/Contracts/AccessToken.php
  type AccessToken (line 7) | interface AccessToken
    method getToken (line 9) | public function getToken(): string;
    method toQuery (line 14) | public function toQuery(): array;

FILE: src/Kernel/Contracts/AccessTokenAwareHttpClient.php
  type AccessTokenAwareHttpClient (line 10) | interface AccessTokenAwareHttpClient extends HttpClientInterface
    method withAccessToken (line 12) | public function withAccessToken(AccessTokenInterface $accessToken): st...

FILE: src/Kernel/Contracts/Aes.php
  type Aes (line 5) | interface Aes
    method encrypt (line 7) | public static function encrypt(string $plaintext, string $key, ?string...
    method decrypt (line 9) | public static function decrypt(string $ciphertext, string $key, ?strin...

FILE: src/Kernel/Contracts/Arrayable.php
  type Arrayable (line 7) | interface Arrayable
    method toArray (line 12) | public function toArray(): array;

FILE: src/Kernel/Contracts/Config.php
  type Config (line 12) | interface Config extends ArrayAccess
    method all (line 17) | public function all(): array;
    method has (line 19) | public function has(string $key): bool;
    method set (line 21) | public function set(string $key, mixed $value = null): void;
    method get (line 26) | public function get(array|string $key, mixed $default = null): mixed;

FILE: src/Kernel/Contracts/JsApiTicket.php
  type JsApiTicket (line 7) | interface JsApiTicket
    method getTicket (line 9) | public function getTicket(): string;
    method configSignature (line 14) | public function configSignature(string $url, string $nonce, int $times...

FILE: src/Kernel/Contracts/Jsonable.php
  type Jsonable (line 7) | interface Jsonable
    method toJson (line 9) | public function toJson(): string|false;

FILE: src/Kernel/Contracts/RefreshableAccessToken.php
  type RefreshableAccessToken (line 5) | interface RefreshableAccessToken extends AccessToken
    method refresh (line 7) | public function refresh(): string;

FILE: src/Kernel/Contracts/RefreshableJsApiTicket.php
  type RefreshableJsApiTicket (line 5) | interface RefreshableJsApiTicket extends JsApiTicket
    method refreshTicket (line 7) | public function refreshTicket(): string;

FILE: src/Kernel/Contracts/Server.php
  type Server (line 12) | interface Server
    method serve (line 14) | public function serve(): ResponseInterface;

FILE: src/Kernel/Encryptor.php
  class Encryptor (line 36) | class Encryptor
    method __construct (line 75) | public function __construct(string $appId, string $token, string $aesK...
    method getToken (line 83) | public function getToken(): string
    method encrypt (line 92) | public function encrypt(string $plaintext, ?string $nonce = null, int|...
    method encryptAsXml (line 99) | public function encryptAsXml(string $plaintext, ?string $nonce = null,...
    method encryptAsJson (line 113) | public function encryptAsJson(string $plaintext, ?string $nonce = null...
    method encryptAsArray (line 136) | public function encryptAsArray(string $plaintext, ?string $nonce = nul...
    method createSignature (line 167) | public function createSignature(mixed ...$attributes): string
    method decrypt (line 177) | public function decrypt(string $ciphertext, string $msgSignature, stri...

FILE: src/Kernel/Exceptions/BadMethodCallException.php
  class BadMethodCallException (line 5) | class BadMethodCallException extends Exception

FILE: src/Kernel/Exceptions/BadRequestException.php
  class BadRequestException (line 7) | class BadRequestException extends Exception

FILE: src/Kernel/Exceptions/BadResponseException.php
  class BadResponseException (line 7) | class BadResponseException extends Exception

FILE: src/Kernel/Exceptions/DecryptException.php
  class DecryptException (line 7) | class DecryptException extends Exception

FILE: src/Kernel/Exceptions/Exception.php
  class Exception (line 9) | class Exception extends BaseException

FILE: src/Kernel/Exceptions/HttpException.php
  class HttpException (line 9) | class HttpException extends Exception
    method __construct (line 16) | public function __construct(string $message, ?ResponseInterface $respo...

FILE: src/Kernel/Exceptions/InvalidArgumentException.php
  class InvalidArgumentException (line 7) | class InvalidArgumentException extends Exception

FILE: src/Kernel/Exceptions/InvalidConfigException.php
  class InvalidConfigException (line 7) | class InvalidConfigException extends Exception

FILE: src/Kernel/Exceptions/RuntimeException.php
  class RuntimeException (line 7) | class RuntimeException extends Exception

FILE: src/Kernel/Exceptions/ServiceNotFoundException.php
  class ServiceNotFoundException (line 7) | class ServiceNotFoundException extends Exception

FILE: src/Kernel/Form/File.php
  class File (line 18) | class File extends DataPart
    method from (line 23) | public static function from(
    method fromContents (line 39) | public static function fromContents(
    method withContents (line 71) | public static function withContents(

FILE: src/Kernel/Form/Form.php
  class Form (line 8) | class Form
    method __construct (line 13) | public function __construct(protected array $fields)
    method create (line 20) | public static function create(array $fields): Form
    method toArray (line 28) | #[\JetBrains\PhpStorm\ArrayShape(['headers' => 'array', 'body' => 'str...
    method toOptions (line 37) | #[\JetBrains\PhpStorm\ArrayShape(['headers' => 'array', 'body' => 'str...

FILE: src/Kernel/HttpClient/AccessTokenAwareClient.php
  class AccessTokenAwareClient (line 25) | class AccessTokenAwareClient implements AccessTokenAwareHttpClientInterface
    method __construct (line 33) | public function __construct(
    method withAccessToken (line 42) | public function withAccessToken(AccessTokenInterface $accessToken): st...
    method request (line 52) | public function request(string $method, string $url, array $options = ...
    method __call (line 70) | public function __call(string $name, array $arguments): mixed
    method createMockClient (line 79) | public static function createMockClient(MockHttpClient $mockHttpClient...

FILE: src/Kernel/HttpClient/AccessTokenExpiredRetryStrategy.php
  class AccessTokenExpiredRetryStrategy (line 12) | class AccessTokenExpiredRetryStrategy extends GenericRetryStrategy
    method withAccessToken (line 18) | public function withAccessToken(AccessTokenInterface $accessToken): st...
    method decideUsing (line 25) | public function decideUsing(Closure $decider): static
    method shouldRetry (line 32) | public function shouldRetry(

FILE: src/Kernel/HttpClient/HttpClientMethods.php
  type HttpClientMethods (line 8) | trait HttpClientMethods
    method get (line 15) | public function get(string $url, array $options = []): Response|Respon...
    method post (line 25) | public function post(string $url, array $options = []): Response|Respo...
    method postJson (line 33) | public function postJson(string $url, array $data = [], array $options...
    method postXml (line 45) | public function postXml(string $url, array $data = [], array $options ...
    method patch (line 63) | public function patch(string $url, array $options = []): Response|Resp...
    method patchJson (line 71) | public function patchJson(string $url, array $options = []): Response|...
    method put (line 83) | public function put(string $url, array $options = []): Response|Respon...
    method delete (line 93) | public function delete(string $url, array $options = []): Response|Res...

FILE: src/Kernel/HttpClient/RequestUtil.php
  class RequestUtil (line 24) | class RequestUtil
    method mergeDefaultRetryOptions (line 30) | #[\JetBrains\PhpStorm\ArrayShape([
    method formatDefaultOptions (line 54) | public static function formatDefaultOptions(array $options): array
    method formatOptions (line 71) | public static function formatOptions(array $options, string $method): ...
    method formatBody (line 111) | public static function formatBody(array $options): array
    method createDefaultServerRequest (line 156) | public static function createDefaultServerRequest(): ServerRequestInte...

FILE: src/Kernel/HttpClient/RequestWithPresets.php
  type RequestWithPresets (line 20) | trait RequestWithPresets
    method setPresets (line 40) | public function setPresets(array $presets): static
    method withHeader (line 47) | public function withHeader(string $key, string $value): static
    method withHeaders (line 54) | public function withHeaders(array $headers): static
    method with (line 66) | public function with(string|array $key, mixed $value = null): static
    method withFile (line 90) | public function withFile(string $pathOrContents, string $formName = 'f...
    method withFileContents (line 112) | public function withFileContents(string $contents, string $formName = ...
    method withFiles (line 120) | public function withFiles(array $files): static
    method mergeThenResetPrepends (line 132) | public function mergeThenResetPrepends(array $options, string $method ...
    method handleMagicWithCall (line 161) | public function handleMagicWithCall(string $method, mixed $value = nul...

FILE: src/Kernel/HttpClient/Response.php
  class Response (line 39) | class Response implements Arrayable, ArrayAccess, Jsonable, ResponseInte...
    method __construct (line 41) | public function __construct(
    method throw (line 48) | public function throw(bool $throw = true): static
    method throwOnFailure (line 55) | public function throwOnFailure(): static
    method quietly (line 60) | public function quietly(): static
    method judgeFailureUsing (line 65) | public function judgeFailureUsing(callable $callback): static
    method isSuccessful (line 72) | public function isSuccessful(): bool
    method isFailed (line 77) | public function isFailed(): bool
    method toArray (line 93) | public function toArray(?bool $throw = null): array
    method toJson (line 116) | public function toJson(?bool $throw = null): string|false
    method toStream (line 126) | public function toStream(?bool $throw = null)
    method toDataUrl (line 142) | public function toDataUrl(): string
    method toPsrResponse (line 147) | public function toPsrResponse(?ResponseFactoryInterface $responseFacto...
    method saveAs (line 188) | public function saveAs(string $filename): string
    method offsetExists (line 203) | public function offsetExists(mixed $offset): bool
    method offsetGet (line 208) | public function offsetGet(mixed $offset): mixed
    method offsetSet (line 216) | public function offsetSet(mixed $offset, mixed $value): void
    method offsetUnset (line 224) | public function offsetUnset(mixed $offset): void
    method __call (line 232) | public function __call(string $name, array $arguments): mixed
    method getStatusCode (line 237) | public function getStatusCode(): int
    method getHeaders (line 242) | public function getHeaders(?bool $throw = null): array
    method getContent (line 247) | public function getContent(?bool $throw = null): string
    method cancel (line 252) | public function cancel(): void
    method getInfo (line 257) | public function getInfo(?string $type = null): mixed
    method __toString (line 262) | public function __toString(): string
    method hasHeader (line 267) | public function hasHeader(string $name, ?bool $throw = null): bool
    method getHeader (line 275) | public function getHeader(string $name, ?bool $throw = null): array
    method getHeaderLine (line 283) | public function getHeaderLine(string $name, ?bool $throw = null): string
    method is (line 291) | public function is(string $type): bool

FILE: src/Kernel/HttpClient/RetryableClient.php
  type RetryableClient (line 10) | trait RetryableClient
    method retry (line 15) | public function retry(array $config = []): static
    method retryUsing (line 36) | public function retryUsing(

FILE: src/Kernel/HttpClient/ScopingHttpClient.php
  class ScopingHttpClient (line 15) | class ScopingHttpClient implements HttpClientInterface, LoggerAwareInter...
    method __construct (line 23) | public function __construct(HttpClientInterface $client, array $defaul...
    method request (line 29) | public function request(string $method, string $url, array $options = ...
    method stream (line 41) | public function stream(ResponseInterface|iterable $responses, ?float $...
    method reset (line 49) | public function reset()
    method setLogger (line 56) | public function setLogger(LoggerInterface $logger): void

FILE: src/Kernel/Message.php
  class Message (line 22) | abstract class Message implements \JsonSerializable, ArrayAccess, Jsonable
    method __construct (line 29) | final public function __construct(array $attributes = [], protected ?s...
    method createFromRequest (line 37) | public static function createFromRequest(ServerRequestInterface $reque...
    method createFromStringContent (line 45) | public static function createFromStringContent(string $originContent):...
    method getOriginalContents (line 52) | public function getOriginalContents(): string
    method __toString (line 57) | public function __toString()

FILE: src/Kernel/ServerResponse.php
  class ServerResponse (line 20) | class ServerResponse implements ResponseInterface
    method __construct (line 22) | public function __construct(protected ResponseInterface $response)
    method make (line 27) | public static function make(ResponseInterface $response): ServerResponse
    method getProtocolVersion (line 36) | public function getProtocolVersion(): string
    method withProtocolVersion (line 41) | public function withProtocolVersion($version): ServerResponse|Response...
    method getHeaders (line 46) | public function getHeaders(): array
    method hasHeader (line 51) | public function hasHeader($name): bool
    method getHeader (line 56) | public function getHeader($name): array
    method getHeaderLine (line 61) | public function getHeaderLine($name): string
    method withHeader (line 66) | public function withHeader($name, $value): ServerResponse|ResponseInte...
    method withAddedHeader (line 71) | public function withAddedHeader($name, $value): ServerResponse|Respons...
    method withoutHeader (line 76) | public function withoutHeader($name): ServerResponse|ResponseInterface
    method getBody (line 81) | public function getBody(): StreamInterface
    method withBody (line 86) | public function withBody(StreamInterface $body): ServerResponse|Respon...
    method getStatusCode (line 91) | public function getStatusCode(): int
    method withStatus (line 96) | public function withStatus($code, $reasonPhrase = ''): ServerResponse|...
    method getReasonPhrase (line 103) | public function getReasonPhrase(): string
    method send (line 111) | public function send(): static
    method sendHeaders (line 127) | public function sendHeaders(): static
    method sendContent (line 156) | public function sendContent(): static
    method closeOutputBuffers (line 172) | public static function closeOutputBuffers(int $targetLevel, bool $flus...
    method __toString (line 187) | public function __toString(): string

FILE: src/Kernel/Support/AesCbc.php
  class AesCbc (line 14) | class AesCbc implements Aes
    method encrypt (line 19) | public static function encrypt(string $plaintext, string $key, ?string...
    method decrypt (line 33) | public static function decrypt(string $ciphertext, string $key, ?strin...

FILE: src/Kernel/Support/AesEcb.php
  class AesEcb (line 14) | class AesEcb implements Aes
    method encrypt (line 19) | public static function encrypt(string $plaintext, string $key, ?string...
    method decrypt (line 33) | public static function decrypt(string $ciphertext, string $key, ?strin...

FILE: src/Kernel/Support/AesGcm.php
  class AesGcm (line 16) | class AesGcm implements Aes
    method encrypt (line 23) | public static function encrypt(string $plaintext, string $key, ?string...
    method decrypt (line 46) | public static function decrypt(string $ciphertext, string $key, ?strin...

FILE: src/Kernel/Support/Arr.php
  class Arr (line 9) | class Arr
    method get (line 11) | #[\JetBrains\PhpStorm\Pure]
    method exists (line 40) | public static function exists(array $array, string|int $key): bool
    method set (line 49) | public static function set(array &$array, string|int|null $key, mixed ...
    method dot (line 79) | public static function dot(array $array, string $prepend = ''): array
    method has (line 98) | #[\JetBrains\PhpStorm\Pure]

FILE: src/Kernel/Support/MessageParser.php
  class MessageParser (line 9) | class MessageParser
    method parse (line 19) | public static function parse(string $content): array

FILE: src/Kernel/Support/Pkcs7.php
  class Pkcs7 (line 7) | class Pkcs7
    method padding (line 12) | public static function padding(string $contents, int $blockSize): string
    method unpadding (line 23) | public static function unpadding(string $contents, int $blockSize): st...

FILE: src/Kernel/Support/PrivateKey.php
  class PrivateKey (line 9) | class PrivateKey
    method __construct (line 11) | public function __construct(protected string $key, protected ?string $...
    method getKey (line 18) | public function getKey(): string
    method getPassphrase (line 27) | public function getPassphrase(): ?string
    method __toString (line 32) | #[\JetBrains\PhpStorm\Pure]

FILE: src/Kernel/Support/PublicKey.php
  class PublicKey (line 13) | class PublicKey
    method __construct (line 15) | public function __construct(public string $certificate)
    method getSerialNo (line 25) | public function getSerialNo(): string
    method __toString (line 36) | public function __toString(): string

FILE: src/Kernel/Support/Str.php
  class Str (line 16) | class Str
    method random (line 23) | public static function random(int $length = 16): string
    method snakeCase (line 39) | public static function snakeCase(string $string): string

FILE: src/Kernel/Support/UserAgent.php
  class UserAgent (line 20) | class UserAgent
    method create (line 25) | public static function create(array $appends = []): string

FILE: src/Kernel/Support/Xml.php
  class Xml (line 9) | class Xml
    method parse (line 11) | public static function parse(string $xml): ?array
    method build (line 16) | public static function build(array $data): string

FILE: src/Kernel/Traits/DecryptJsonMessage.php
  type DecryptJsonMessage (line 8) | trait DecryptJsonMessage

FILE: src/Kernel/Traits/DecryptMessage.php
  type DecryptMessage (line 11) | trait DecryptMessage
    method decryptMessage (line 19) | public function decryptMessage(
    method validateSignature (line 53) | protected function validateSignature(

FILE: src/Kernel/Traits/DecryptXmlMessage.php
  type DecryptXmlMessage (line 8) | trait DecryptXmlMessage

FILE: src/Kernel/Traits/HasAttributes.php
  type HasAttributes (line 11) | trait HasAttributes
    method __construct (line 21) | public function __construct(array $attributes)
    method toArray (line 29) | public function toArray(): array
    method toJson (line 34) | public function toJson(): string|false
    method has (line 39) | public function has(string $key): bool
    method merge (line 47) | public function merge(array $attributes): static
    method jsonSerialize (line 57) | public function jsonSerialize(): array
    method __set (line 62) | public function __set(string $attribute, mixed $value): void
    method __get (line 67) | public function __get(string $attribute): mixed
    method offsetExists (line 72) | public function offsetExists(mixed $offset): bool
    method offsetGet (line 78) | public function offsetGet(mixed $offset): mixed
    method offsetSet (line 83) | public function offsetSet(mixed $offset, mixed $value): void
    method offsetUnset (line 92) | public function offsetUnset(mixed $offset): void

FILE: src/Kernel/Traits/InteractWithCache.php
  type InteractWithCache (line 11) | trait InteractWithCache
    method getCacheLifetime (line 19) | public function getCacheLifetime(): int
    method setCacheLifetime (line 24) | public function setCacheLifetime(int $cacheLifetime): void
    method getCacheNamespace (line 29) | public function getCacheNamespace(): string
    method setCacheNamespace (line 34) | public function setCacheNamespace(string $cacheNamespace): void
    method setCache (line 39) | public function setCache(CacheInterface $cache): static
    method getCache (line 46) | public function getCache(): CacheInterface

FILE: src/Kernel/Traits/InteractWithClient.php
  type InteractWithClient (line 9) | trait InteractWithClient
    method getClient (line 13) | public function getClient(): AccessTokenAwareClient
    method setClient (line 22) | public function setClient(AccessTokenAwareClient $client): static
    method createClient (line 29) | abstract public function createClient(): AccessTokenAwareClient;

FILE: src/Kernel/Traits/InteractWithConfig.php
  type InteractWithConfig (line 12) | trait InteractWithConfig
    method __construct (line 19) | public function __construct(array|ConfigInterface $config)
    method getConfig (line 24) | public function getConfig(): ConfigInterface
    method setConfig (line 29) | public function setConfig(ConfigInterface $config): static

FILE: src/Kernel/Traits/InteractWithHandlers.php
  type InteractWithHandlers (line 21) | trait InteractWithHandlers
    method getHandlers (line 31) | public function getHandlers(): array
    method with (line 36) | public function with(callable|string $handler): static
    method withHandler (line 44) | public function withHandler(callable|string $handler): static
    method createHandlerItem (line 54) | #[\JetBrains\PhpStorm\ArrayShape(['hash' => 'string', 'handler' => 'ca...
    method getHandlerHash (line 66) | protected function getHandlerHash(callable|array|string $handler): string
    method makeClosure (line 82) | protected function makeClosure(callable|string $handler): callable
    method prepend (line 100) | public function prepend(callable|string $handler): static
    method prependHandler (line 105) | public function prependHandler(callable|string $handler): static
    method without (line 115) | public function without(callable|string $handler): static
    method withoutHandler (line 123) | public function withoutHandler(callable|string $handler): static
    method indexOf (line 134) | public function indexOf(callable|string $handler): int
    method when (line 145) | public function when(mixed $value, callable|string $handler): static
    method handle (line 158) | public function handle(mixed $result, mixed $payload = null): mixed
    method has (line 169) | public function has(callable|string $handler): bool

FILE: src/Kernel/Traits/InteractWithHttpClient.php
  type InteractWithHttpClient (line 17) | trait InteractWithHttpClient
    method getHttpClient (line 21) | public function getHttpClient(): HttpClientInterface
    method setHttpClient (line 30) | public function setHttpClient(HttpClientInterface $httpClient): static
    method createHttpClient (line 42) | protected function createHttpClient(): HttpClientInterface
    method getHttpClientDefaultOptions (line 61) | protected function getHttpClientDefaultOptions(): array

FILE: src/Kernel/Traits/InteractWithServerRequest.php
  type InteractWithServerRequest (line 13) | trait InteractWithServerRequest
    method getRequest (line 17) | public function getRequest(): ServerRequestInterface
    method setRequest (line 26) | public function setRequest(ServerRequestInterface $request): static
    method setRequestFromSymfonyRequest (line 33) | public function setRequestFromSymfonyRequest(Request $symfonyRequest):...

FILE: src/Kernel/Traits/MockableHttpClient.php
  type MockableHttpClient (line 11) | trait MockableHttpClient
    method createMockClient (line 13) | public static function createMockClient(MockHttpClient $mockHttpClient...
    method mock (line 21) | public static function mock(

FILE: src/Kernel/Traits/RespondJsonMessage.php
  type RespondJsonMessage (line 11) | trait RespondJsonMessage
    method transformJsonToReply (line 13) | public function transformJsonToReply(mixed $response, Message $message...
    method normalizeJsonResponse (line 28) | protected function normalizeJsonResponse(mixed $response): array
    method createJsonResponse (line 50) | protected function createJsonResponse(array $attributes, ?Encryptor $e...

FILE: src/Kernel/Traits/RespondXmlMessage.php
  type RespondXmlMessage (line 19) | trait RespondXmlMessage
    method transformToReply (line 25) | public function transformToReply(mixed $response, Message $message, ?E...
    method normalizeResponse (line 51) | protected function normalizeResponse(mixed $response): array
    method createXmlResponse (line 82) | protected function createXmlResponse(array $attributes, ?Encryptor $en...

FILE: src/MiniApp/AccessToken.php
  class AccessToken (line 7) | class AccessToken extends \EasyWeChat\OfficialAccount\AccessToken

FILE: src/MiniApp/Account.php
  class Account (line 9) | class Account extends \EasyWeChat\OfficialAccount\Account implements Acc...

FILE: src/MiniApp/Application.php
  class Application (line 33) | class Application implements ApplicationInterface
    method getAccount (line 50) | public function getAccount(): AccountInterface
    method setAccount (line 64) | public function setAccount(AccountInterface $account): static
    method getEncryptor (line 74) | public function getEncryptor(): Encryptor
    method setEncryptor (line 95) | public function setEncryptor(Encryptor $encryptor): static
    method getServer (line 102) | public function getServer(): Server|ServerInterface
    method setServer (line 114) | public function setServer(ServerInterface $server): static
    method getAccessToken (line 121) | public function getAccessToken(): AccessTokenInterface
    method setAccessToken (line 136) | public function setAccessToken(AccessTokenInterface $accessToken): static
    method getUtils (line 143) | #[\JetBrains\PhpStorm\Pure]
    method createClient (line 149) | public function createClient(): AccessTokenAwareClient
    method getRetryStrategy (line 171) | public function getRetryStrategy(): AccessTokenExpiredRetryStrategy
    method getHttpClientDefaultOptions (line 186) | protected function getHttpClientDefaultOptions(): array

FILE: src/MiniApp/Contracts/Account.php
  type Account (line 7) | interface Account
    method getAppId (line 9) | public function getAppId(): string;
    method getSecret (line 11) | public function getSecret(): string;
    method getToken (line 13) | public function getToken(): ?string;
    method getAesKey (line 15) | public function getAesKey(): ?string;

FILE: src/MiniApp/Contracts/Application.php
  type Application (line 16) | interface Application
    method getAccount (line 18) | public function getAccount(): Account;
    method getEncryptor (line 20) | public function getEncryptor(): Encryptor;
    method getServer (line 22) | public function getServer(): Server;
    method getRequest (line 24) | public function getRequest(): ServerRequestInterface;
    method getClient (line 26) | public function getClient(): AccessTokenAwareClient;
    method getHttpClient (line 28) | public function getHttpClient(): HttpClientInterface;
    method getConfig (line 30) | public function getConfig(): Config;
    method getAccessToken (line 32) | public function getAccessToken(): AccessToken;
    method getCache (line 34) | public function getCache(): CacheInterface;

FILE: src/MiniApp/Decryptor.php
  class Decryptor (line 16) | class Decryptor
    method decrypt (line 23) | public static function decrypt(string $sessionKey, string $iv, string ...

FILE: src/MiniApp/Server.php
  class Server (line 7) | class Server extends \EasyWeChat\OfficialAccount\Server

FILE: src/MiniApp/Utils.php
  class Utils (line 7) | class Utils
    method __construct (line 9) | public function __construct(protected Application $app)
    method codeToSession (line 16) | public function codeToSession(string $code): array
    method decryptSession (line 34) | public function decryptSession(string $sessionKey, string $iv, string ...
    method getPhoneNumber (line 42) | public function getPhoneNumber(string $code): array

FILE: src/OfficialAccount/AccessToken.php
  class AccessToken (line 20) | class AccessToken implements RefreshableAccessTokenInterface
    method __construct (line 28) | public function __construct(
    method getKey (line 40) | public function getKey(): string
    method setKey (line 45) | public function setKey(string $key): static
    method getToken (line 52) | public function getToken(): string
    method toQuery (line 66) | #[\JetBrains\PhpStorm\ArrayShape(['access_token' => 'string'])]
    method refresh (line 72) | public function refresh(): string
    method getStableAccessToken (line 80) | public function getStableAccessToken(bool $force_refresh = false): string
    method getAccessToken (line 107) | public function getAccessToken(): string

FILE: src/OfficialAccount/Account.php
  class Account (line 10) | class Account implements AccountInterface
    method __construct (line 12) | public function __construct(
    method getAppId (line 20) | public function getAppId(): string
    method getSecret (line 28) | public function getSecret(): string
    method getToken (line 37) | public function getToken(): ?string
    method getAesKey (line 42) | public function getAesKey(): ?string

FILE: src/OfficialAccount/Application.php
  class Application (line 37) | class Application implements ApplicationInterface
    method getAccount (line 58) | public function getAccount(): AccountInterface
    method setAccount (line 72) | public function setAccount(AccountInterface $account): static
    method getEncryptor (line 82) | public function getEncryptor(): Encryptor
    method setEncryptor (line 103) | public function setEncryptor(Encryptor $encryptor): static
    method getServer (line 110) | public function getServer(): Server|ServerInterface
    method setServer (line 122) | public function setServer(ServerInterface $server): static
    method getAccessToken (line 129) | public function getAccessToken(): AccessTokenInterface|RefreshableAcce...
    method setAccessToken (line 144) | public function setAccessToken(AccessTokenInterface|RefreshableAccessT...
    method setOAuthFactory (line 151) | public function setOAuthFactory(callable $factory): static
    method getOAuth (line 161) | public function getOAuth(): SocialiteProviderInterface
    method getTicket (line 185) | public function getTicket(): JsApiTicketInterface|RefreshableJsApiTick...
    method setTicket (line 200) | public function setTicket(JsApiTicketInterface|RefreshableJsApiTicketI...
    method getUtils (line 207) | #[\JetBrains\PhpStorm\Pure]
    method createClient (line 213) | public function createClient(): AccessTokenAwareClient
    method getRetryStrategy (line 233) | public function getRetryStrategy(): AccessTokenExpiredRetryStrategy
    method getHttpClientDefaultOptions (line 248) | protected function getHttpClientDefaultOptions(): array

FILE: src/OfficialAccount/Config.php
  class Config (line 7) | class Config extends \EasyWeChat\Kernel\Config

FILE: src/OfficialAccount/Contracts/Account.php
  type Account (line 7) | interface Account
    method getAppId (line 9) | public function getAppId(): string;
    method getSecret (line 11) | public function getSecret(): string;
    method getToken (line 13) | public function getToken(): ?string;
    method getAesKey (line 15) | public function getAesKey(): ?string;

FILE: src/OfficialAccount/Contracts/Application.php
  type Application (line 17) | interface Application
    method getAccount (line 19) | public function getAccount(): Account;
    method getEncryptor (line 21) | public function getEncryptor(): Encryptor;
    method getServer (line 23) | public function getServer(): Server;
    method getRequest (line 25) | public function getRequest(): ServerRequestInterface;
    method getClient (line 27) | public function getClient(): AccessTokenAwareClient;
    method getHttpClient (line 29) | public function getHttpClient(): HttpClientInterface;
    method getConfig (line 31) | public function getConfig(): Config;
    method getAccessToken (line 33) | public function getAccessToken(): AccessToken;
    method getCache (line 35) | public function getCache(): CacheInterface;
    method getOAuth (line 37) | public function getOAuth(): ProviderInterface;
    method setOAuthFactory (line 39) | public function setOAuthFactory(callable $factory): static;

FILE: src/OfficialAccount/JsApiTicket.php
  class JsApiTicket (line 12) | class JsApiTicket extends AccessToken implements RefreshableJsApiTicketI...
    method getTicket (line 14) | public function getTicket(): string
    method refreshTicket (line 29) | public function refreshTicket(): string
    method configSignature (line 46) | #[\JetBrains\PhpStorm\ArrayShape([
    method getKey (line 70) | public function getKey(): string

FILE: src/OfficialAccount/Message.php
  class Message (line 11) | class Message extends \EasyWeChat\Kernel\Message

FILE: src/OfficialAccount/Server.php
  class Server (line 20) | class Server implements ServerInterface
    method __construct (line 27) | public function __construct(
    method serve (line 34) | public function serve(): ResponseInterface
    method addMessageListener (line 59) | public function addMessageListener(string $type, callable|string $hand...
    method addEventListener (line 71) | public function addEventListener(string $event, callable|string $handl...
    method decryptRequestMessage (line 88) | protected function decryptRequestMessage(array $query): Closure
    method getRequestMessage (line 101) | public function getRequestMessage(?ServerRequestInterface $request = n...
    method getDecryptedMessage (line 106) | public function getDecryptedMessage(?ServerRequestInterface $request =...
    method decryptIncomingMessage (line 122) | protected function decryptIncomingMessage(\EasyWeChat\Kernel\Message $...

FILE: src/OfficialAccount/Utils.php
  class Utils (line 9) | class Utils
    method __construct (line 11) | public function __construct(protected Application $app)
    method buildJsSdkConfig (line 20) | public function buildJsSdkConfig(

FILE: src/OpenPlatform/Account.php
  class Account (line 9) | class Account implements AccountInterface
    method __construct (line 11) | public function __construct(
    method getAppId (line 19) | public function getAppId(): string
    method getSecret (line 24) | public function getSecret(): string
    method getToken (line 29) | public function getToken(): string
    method getAesKey (line 34) | public function getAesKey(): string

FILE: src/OpenPlatform/Application.php
  class Application (line 35) | class Application implements ApplicationInterface
    method getAccount (line 54) | public function getAccount(): AccountInterface
    method setAccount (line 68) | public function setAccount(AccountInterface $account): static
    method getVerifyTicket (line 75) | public function getVerifyTicket(): VerifyTicketInterface
    method setVerifyTicket (line 87) | public function setVerifyTicket(VerifyTicketInterface $verifyTicket): ...
    method getEncryptor (line 94) | public function getEncryptor(): Encryptor
    method setEncryptor (line 108) | public function setEncryptor(Encryptor $encryptor): static
    method getServer (line 115) | public function getServer(): Server|ServerInterface
    method setServer (line 138) | public function setServer(ServerInterface $server): static
    method getAccessToken (line 145) | public function getAccessToken(): AccessTokenInterface
    method getComponentAccessToken (line 150) | public function getComponentAccessToken(): AccessTokenInterface
    method setComponentAccessToken (line 165) | public function setComponentAccessToken(AccessTokenInterface $componen...
    method getAuthorization (line 175) | public function getAuthorization(string $authorizationCode): Authoriza...
    method refreshAuthorizerToken (line 201) | public function refreshAuthorizerToken(string $authorizerAppId, string...
    method createPreAuthorizationCode (line 228) | public function createPreAuthorizationCode(): array
    method createPreAuthorizationUrl (line 250) | public function createPreAuthorizationUrl(string $callbackUrl, array|s...
    method getOAuth (line 272) | public function getOAuth(): SocialiteProviderInterface
    method getOfficialAccountWithRefreshToken (line 283) | public function getOfficialAccountWithRefreshToken(
    method getOfficialAccountWithAccessToken (line 295) | public function getOfficialAccountWithAccessToken(
    method getOfficialAccount (line 303) | public function getOfficialAccount(
    method getMiniAppWithRefreshToken (line 329) | public function getMiniAppWithRefreshToken(
    method getMiniAppWithAccessToken (line 341) | public function getMiniAppWithAccessToken(
    method getMiniApp (line 349) | public function getMiniApp(AuthorizerAccessToken $authorizerAccessToke...
    method createAuthorizerOAuthFactory (line 370) | protected function createAuthorizerOAuthFactory(string $authorizerAppI...
    method createClient (line 386) | public function createClient(): AccessTokenAwareClient
    method getAuthorizerAccessToken (line 396) | public function getAuthorizerAccessToken(string $appId, string $refres...
    method getHttpClientDefaultOptions (line 415) | protected function getHttpClientDefaultOptions(): array

FILE: src/OpenPlatform/Authorization.php
  class Authorization (line 15) | class Authorization implements Arrayable, ArrayAccess, Jsonable
    method getAppId (line 19) | public function getAppId(): string
    method getAccessToken (line 25) | #[\JetBrains\PhpStorm\Pure]
    method getRefreshToken (line 37) | public function getRefreshToken(): string

FILE: src/OpenPlatform/AuthorizerAccessToken.php
  class AuthorizerAccessToken (line 10) | class AuthorizerAccessToken implements AccessToken, Stringable
    method __construct (line 12) | public function __construct(protected string $appId, protected string ...
    method getAppId (line 16) | public function getAppId(): string
    method getToken (line 21) | public function getToken(): string
    method __toString (line 26) | public function __toString()
    method toQuery (line 34) | #[\JetBrains\PhpStorm\Pure]

FILE: src/OpenPlatform/ComponentAccessToken.php
  class ComponentAccessToken (line 20) | class ComponentAccessToken implements RefreshableAccessTokenInterface
    method __construct (line 26) | public function __construct(
    method getKey (line 38) | public function getKey(): string
    method setKey (line 43) | public function setKey(string $key): static
    method getToken (line 50) | public function getToken(): string
    method toQuery (line 61) | #[\JetBrains\PhpStorm\ArrayShape(['component_access_token' => 'string'])]
    method refresh (line 70) | public function refresh(): string

FILE: src/OpenPlatform/Config.php
  class Config (line 7) | class Config extends \EasyWeChat\Kernel\Config

FILE: src/OpenPlatform/Contracts/Account.php
  type Account (line 7) | interface Account
    method getAppId (line 9) | public function getAppId(): string;
    method getSecret (line 11) | public function getSecret(): string;
    method getToken (line 13) | public function getToken(): string;
    method getAesKey (line 15) | public function getAesKey(): string;

FILE: src/OpenPlatform/Contracts/Application.php
  type Application (line 20) | interface Application
    method getAccount (line 22) | public function getAccount(): Account;
    method getEncryptor (line 24) | public function getEncryptor(): Encryptor;
    method getServer (line 26) | public function getServer(): Server;
    method getRequest (line 28) | public function getRequest(): ServerRequestInterface;
    method getClient (line 30) | public function getClient(): AccessTokenAwareClient;
    method getHttpClient (line 32) | public function getHttpClient(): HttpClientInterface;
    method getConfig (line 34) | public function getConfig(): Config;
    method getComponentAccessToken (line 36) | public function getComponentAccessToken(): AccessToken;
    method getCache (line 38) | public function getCache(): CacheInterface;
    method getOAuth (line 40) | public function getOAuth(): ProviderInterface;
    method getMiniApp (line 45) | public function getMiniApp(AuthorizerAccessToken $authorizerAccessToke...
    method getOfficialAccount (line 50) | public function getOfficialAccount(

FILE: src/OpenPlatform/Contracts/VerifyTicket.php
  type VerifyTicket (line 7) | interface VerifyTicket
    method getTicket (line 9) | public function getTicket(): string;
    method setTicket (line 11) | public function setTicket(string $ticket): static;

FILE: src/OpenPlatform/Message.php
  class Message (line 11) | class Message extends \EasyWeChat\Kernel\Message

FILE: src/OpenPlatform/Server.php
  class Server (line 21) | class Server implements ServerInterface
    method __construct (line 30) | public function __construct(
    method serve (line 37) | public function serve(): ResponseInterface
    method handleAuthorized (line 56) | public function handleAuthorized(callable $handler): static
    method handleUnauthorized (line 65) | public function handleUnauthorized(callable $handler): static
    method handleAuthorizeUpdated (line 74) | public function handleAuthorizeUpdated(callable $handler): static
    method withDefaultVerifyTicketHandler (line 83) | public function withDefaultVerifyTicketHandler(callable $handler): void
    method handleVerifyTicketRefreshed (line 89) | public function handleVerifyTicketRefreshed(callable $handler): static
    method decryptRequestMessage (line 102) | protected function decryptRequestMessage(): Closure
    method getRequestMessage (line 119) | public function getRequestMessage(?ServerRequestInterface $request = n...
    method getDecryptedMessage (line 124) | public function getDecryptedMessage(?ServerRequestInterface $request =...

FILE: src/OpenPlatform/VerifyTicket.php
  class VerifyTicket (line 16) | class VerifyTicket implements VerifyTicketInterface
    method __construct (line 20) | public function __construct(
    method getKey (line 28) | public function getKey(): string
    method setKey (line 33) | public function setKey(string $key): static
    method setTicket (line 40) | public function setTicket(string $ticket): static
    method getTicket (line 50) | public function getTicket(): string

FILE: src/OpenWork/Account.php
  class Account (line 9) | class Account implements AccountInterface
    method __construct (line 11) | public function __construct(
    method getCorpId (line 21) | public function getCorpId(): string
    method getProviderSecret (line 26) | public function getProviderSecret(): string
    method getSuiteId (line 31) | public function getSuiteId(): string
    method getSuiteSecret (line 36) | public function getSuiteSecret(): string
    method getToken (line 41) | public function getToken(): string
    method getAesKey (line 46) | public function getAesKey(): string

FILE: src/OpenWork/Application.php
  class Application (line 26) | class Application implements ApplicationInterface
    method getAccount (line 51) | public function getAccount(): AccountInterface
    method setAccount (line 67) | public function setAccount(AccountInterface $account): static
    method getEncryptor (line 74) | public function getEncryptor(): Encryptor
    method setEncryptor (line 87) | public function setEncryptor(Encryptor $encryptor): static
    method getSuiteEncryptor (line 94) | public function getSuiteEncryptor(): SuiteEncryptor
    method setSuiteEncryptor (line 107) | public function setSuiteEncryptor(SuiteEncryptor $encryptor): static
    method getServer (line 114) | public function getServer(): Server|ServerInterface
    method setServer (line 135) | public function setServer(ServerInterface $server): static
    method getProviderAccessToken (line 142) | public function getProviderAccessToken(): AccessTokenInterface
    method setProviderAccessToken (line 156) | public function setProviderAccessToken(AccessTokenInterface $accessTok...
    method getSuiteAccessToken (line 163) | public function getSuiteAccessToken(): AccessTokenInterface
    method setSuiteAccessToken (line 178) | public function setSuiteAccessToken(AccessTokenInterface $accessToken)...
    method getSuiteTicket (line 185) | public function getSuiteTicket(): SuiteTicketInterface
    method setSuiteTicket (line 197) | public function setSuiteTicket(SuiteTicketInterface $suiteTicket): Sui...
    method getAuthorization (line 207) | public function getAuthorization(
    method getAuthorizerAccessToken (line 231) | public function getAuthorizerAccessToken(
    method createClient (line 247) | public function createClient(): AccessTokenAwareClient
    method getAuthorizerClient (line 257) | public function getAuthorizerClient(string $corpId, string $permanentC...
    method getJsApiTicket (line 267) | public function getJsApiTicket(string $corpId, string $permanentCode, ...
    method getOAuth (line 276) | public function getOAuth(
    method getCorpOAuth (line 291) | public function getCorpOAuth(
    method getHttpClientDefaultOptions (line 309) | protected function getHttpClientDefaultOptions(): array

FILE: src/OpenWork/Authorization.php
  class Authorization (line 15) | class Authorization implements Arrayable, ArrayAccess, Jsonable
    method getAppId (line 19) | public function getAppId(): string

FILE: src/OpenWork/AuthorizerAccessToken.php
  class AuthorizerAccessToken (line 17) | class AuthorizerAccessToken implements RefreshableAccessToken, Stringable
    method __construct (line 23) | public function __construct(
    method getCorpId (line 35) | public function getCorpId(): string
    method getToken (line 40) | public function getToken(): string
    method __toString (line 55) | public function __toString()
    method toQuery (line 60) | #[\JetBrains\PhpStorm\ArrayShape(['access_token' => 'string'])]
    method getKey (line 66) | public function getKey(): string
    method setKey (line 71) | public function setKey(string $key): static
    method refresh (line 81) | public function refresh(): string

FILE: src/OpenWork/Config.php
  class Config (line 7) | class Config extends \EasyWeChat\Kernel\Config

FILE: src/OpenWork/Contracts/Account.php
  type Account (line 7) | interface Account
    method getCorpId (line 9) | public function getCorpId(): string;
    method getProviderSecret (line 11) | public function getProviderSecret(): string;
    method getSuiteId (line 13) | public function getSuiteId(): string;
    method getSuiteSecret (line 15) | public function getSuiteSecret(): string;
    method getToken (line 17) | public function getToken(): string;
    method getAesKey (line 19) | public function getAesKey(): string;

FILE: src/OpenWork/Contracts/Application.php
  type Application (line 16) | interface Application
    method getAccount (line 18) | public function getAccount(): Account;
    method getEncryptor (line 20) | public function getEncryptor(): Encryptor;
    method getSuiteEncryptor (line 22) | public function getSuiteEncryptor(): Encryptor;
    method getServer (line 24) | public function getServer(): Server;
    method getRequest (line 26) | public function getRequest(): ServerRequestInterface;
    method getClient (line 28) | public function getClient(): AccessTokenAwareClient;
    method getHttpClient (line 30) | public function getHttpClient(): HttpClientInterface;
    method getConfig (line 32) | public function getConfig(): Config;
    method getProviderAccessToken (line 34) | public function getProviderAccessToken(): AccessToken;
    method getCache (line 36) | public function getCache(): CacheInterface;

FILE: src/OpenWork/Contracts/SuiteTicket.php
  type SuiteTicket (line 7) | interface SuiteTicket
    method getTicket (line 9) | public function getTicket(): string;
    method setTicket (line 11) | public function setTicket(string $ticket): static;

FILE: src/OpenWork/Encryptor.php
  class Encryptor (line 5) | class Encryptor extends \EasyWeChat\Kernel\Encryptor
    method __construct (line 7) | #[\JetBrains\PhpStorm\Pure]

FILE: src/OpenWork/JsApiTicket.php
  class JsApiTicket (line 18) | class JsApiTicket
    method __construct (line 24) | public function __construct(
    method createConfigSignature (line 37) | public function createConfigSignature(string $nonce, int $timestamp, s...
    method getTicketSignature (line 51) | public function getTicketSignature(string $ticket, string $nonce, int ...
    method getTicket (line 59) | public function getTicket(): string
    method setKey (line 79) | public function setKey(string $key): static
    method getKey (line 86) | public function getKey(): string
    method createAgentConfigSignature (line 91) | public function createAgentConfigSignature(int $agentId, string $nonce...
    method getAgentTicket (line 106) | public function getAgentTicket(int $agentId): string
    method getAgentKey (line 126) | public function getAgentKey(int $agentId): string

FILE: src/OpenWork/Message.php
  class Message (line 15) | class Message extends \EasyWeChat\Kernel\Message

FILE: src/OpenWork/ProviderAccessToken.php
  class ProviderAccessToken (line 19) | class ProviderAccessToken implements RefreshableAccessTokenInterface
    method __construct (line 25) | public function __construct(
    method getKey (line 36) | public function getKey(): string
    method setKey (line 41) | public function setKey(string $key): static
    method getToken (line 48) | public function getToken(): string
    method toQuery (line 62) | #[\JetBrains\PhpStorm\ArrayShape(['provider_access_token' => 'string'])]
    method refresh (line 71) | public function refresh(): string

FILE: src/OpenWork/Server.php
  class Server (line 24) | class Server implements ServerInterface
    method __construct (line 33) | public function __construct(
    method serve (line 46) | public function serve(): ResponseInterface
    method withDefaultSuiteTicketHandler (line 74) | public function withDefaultSuiteTicketHandler(callable $handler): void
    method handleSuiteTicketRefreshed (line 80) | public function handleSuiteTicketRefreshed(callable $handler): static
    method handleAuthCreated (line 93) | public function handleAuthCreated(callable $handler): static
    method handleAuthChanged (line 102) | public function handleAuthChanged(callable $handler): static
    method handleAuthCancelled (line 111) | public function handleAuthCancelled(callable $handler): static
    method handleUserCreated (line 120) | public function handleUserCreated(callable $handler): static
    method handleUserUpdated (line 132) | public function handleUserUpdated(callable $handler): static
    method handleUserDeleted (line 144) | public function handleUserDeleted(callable $handler): static
    method handlePartyCreated (line 156) | public function handlePartyCreated(callable $handler): static
    method handlePartyUpdated (line 168) | public function handlePartyUpdated(callable $handler): static
    method handlePartyDeleted (line 180) | public function handlePartyDeleted(callable $handler): static
    method handleUserTagUpdated (line 192) | public function handleUserTagUpdated(callable $handler): static
    method handleShareAgentChanged (line 204) | public function handleShareAgentChanged(callable $handler): static
    method handleResetPermanentCode (line 213) | public function handleResetPermanentCode(callable $handler): static
    method handleChangeAppAdmin (line 222) | public function handleChangeAppAdmin(callable $handler): static
    method decryptRequestMessage (line 234) | protected function decryptRequestMessage(): Closure
    method getRequestMessage (line 254) | public function getRequestMessage(?ServerRequestInterface $request = n...
    method getDecryptedMessage (line 263) | public function getDecryptedMessage(?ServerRequestInterface $request =...

FILE: src/OpenWork/SuiteAccessToken.php
  class SuiteAccessToken (line 22) | class SuiteAccessToken implements RefreshableAccessTokenInterface
    method __construct (line 28) | public function __construct(
    method getKey (line 41) | public function getKey(): string
    method setKey (line 46) | public function setKey(string $key): static
    method getToken (line 53) | public function getToken(): string
    method toQuery (line 67) | #[\JetBrains\PhpStorm\ArrayShape(['suite_access_token' => 'string'])]
    method refresh (line 76) | public function refresh(): string

FILE: src/OpenWork/SuiteEncryptor.php
  class SuiteEncryptor (line 7) | class SuiteEncryptor extends Encryptor
    method __construct (line 9) | #[\JetBrains\PhpStorm\Pure]

FILE: src/OpenWork/SuiteTicket.php
  class SuiteTicket (line 16) | class SuiteTicket implements SuiteTicketInterface
    method __construct (line 20) | public function __construct(
    method getKey (line 28) | public function getKey(): string
    method setKey (line 33) | public function setKey(string $key): static
    method setTicket (line 40) | public function setTicket(string $ticket): static
    method getTicket (line 50) | public function getTicket(): string

FILE: src/Pay/Application.php
  class Application (line 18) | class Application implements \EasyWeChat\Pay\Contracts\Application
    method getUtils (line 33) | public function getUtils(): Utils
    method getMerchant (line 38) | public function getMerchant(): Merchant
    method getValidator (line 54) | public function getValidator(): ValidatorInterface
    method setValidator (line 63) | public function setValidator(ValidatorInterface $validator): static
    method getServer (line 70) | public function getServer(): Server|ServerInterface
    method setServer (line 82) | public function setServer(ServerInterface $server): static
    method setConfig (line 89) | public function setConfig(ConfigInterface $config): static
    method getConfig (line 96) | public function getConfig(): ConfigInterface
    method getClient (line 101) | public function getClient(): Client|HttpClientInterface
    method setClient (line 110) | public function setClient(HttpClientInterface $client): static

FILE: src/Pay/Client.php
  class Client (line 47) | class Client implements HttpClientInterface
    method __construct (line 90) | public function __construct(
    method request (line 112) | public function request(string $method, string $url, array $options = ...
    method isV3Request (line 178) | protected function isV3Request(string $url): bool
    method withSerialHeader (line 194) | public function withSerialHeader(?string $serial = null): static
    method __call (line 210) | public function __call(string $name, array $arguments): mixed
    method uploadMedia (line 219) | public function uploadMedia(string $uri, string $pathOrContents, ?arra...
    method createSignature (line 247) | protected function createSignature(string $method, string $url, array ...
    method attachLegacySignature (line 258) | protected function attachLegacySignature(array $body): array
    method createMockClient (line 263) | public static function createMockClient(MockHttpClient $mockHttpClient...

FILE: src/Pay/Config.php
  class Config (line 7) | class Config extends \EasyWeChat\Kernel\Config

FILE: src/Pay/Contracts/Application.php
  type Application (line 10) | interface Application
    method getMerchant (line 12) | public function getMerchant(): Merchant;
    method getConfig (line 14) | public function getConfig(): Config;
    method getHttpClient (line 16) | public function getHttpClient(): HttpClientInterface;
    method getClient (line 18) | public function getClient(): HttpClientInterface;

FILE: src/Pay/Contracts/Merchant.php
  type Merchant (line 10) | interface Merchant
    method getMerchantId (line 12) | public function getMerchantId(): int;
    method getPrivateKey (line 14) | public function getPrivateKey(): PrivateKey;
    method getSecretKey (line 16) | public function getSecretKey(): string;
    method getV2SecretKey (line 18) | public function getV2SecretKey(): ?string;
    method getCertificate (line 20) | public function getCertificate(): PublicKey;
    method getPlatformCert (line 22) | public function getPlatformCert(string $serial): ?PublicKey;
    method getPlatformCerts (line 27) | public function getPlatformCerts(): array;

FILE: src/Pay/Contracts/ResponseValidator.php
  type ResponseValidator (line 10) | interface ResponseValidator
    method validate (line 12) | public function validate(ResponseInterface|Response $response): void;

FILE: src/Pay/Contracts/Validator.php
  type Validator (line 9) | interface Validator
    method validate (line 11) | public function validate(MessageInterface $message): void;

FILE: src/Pay/Exceptions/EncryptionFailureException.php
  class EncryptionFailureException (line 7) | class EncryptionFailureException extends RuntimeException

FILE: src/Pay/Exceptions/InvalidSignatureException.php
  class InvalidSignatureException (line 7) | class InvalidSignatureException extends RuntimeException

FILE: src/Pay/LegacySignature.php
  class LegacySignature (line 19) | class LegacySignature
    method __construct (line 21) | public function __construct(protected MerchantInterface $merchant)
    method sign (line 32) | public function sign(array $params): array

FILE: src/Pay/Merchant.php
  class Merchant (line 16) | class Merchant implements MerchantInterface
    method __construct (line 26) | public function __construct(
    method getMerchantId (line 37) | public function getMerchantId(): int
    method getPrivateKey (line 42) | public function getPrivateKey(): PrivateKey
    method getCertificate (line 47) | public function getCertificate(): PublicKey
    method getSecretKey (line 52) | public function getSecretKey(): string
    method getV2SecretKey (line 57) | public function getV2SecretKey(): ?string
    method getPlatformCert (line 62) | public function getPlatformCert(string $serial): ?PublicKey
    method getPlatformCerts (line 67) | public function getPlatformCerts(): array
    method normalizePlatformCerts (line 78) | protected function normalizePlatformCerts(array $platformCerts): array

FILE: src/Pay/Message.php
  class Message (line 14) | class Message extends \EasyWeChat\Kernel\Message
    method getOriginalAttributes (line 19) | public function getOriginalAttributes(): array
    method getEventType (line 29) | public function getEventType(): ?string

FILE: src/Pay/ResponseValidator.php
  class ResponseValidator (line 12) | class ResponseValidator implements \EasyWeChat\Pay\Contracts\ResponseVal...
    method __construct (line 14) | public function __construct(protected MerchantInterface $merchant)
    method validate (line 21) | public function validate(PsrResponse|HttpClientResponse $response): void

FILE: src/Pay/Server.php
  class Server (line 29) | class Server implements ServerInterface
    method __construct (line 34) | public function __construct(
    method serve (line 41) | public function serve(): ResponseInterface
    method handlePaid (line 120) | public function handlePaid(callable $handler): static
    method handleRefunded (line 185) | public function handleRefunded(callable $handler): static
    method getRequestMessage (line 198) | public function getRequestMessage(?ServerRequestInterface $request = n...
    method decodeXmlMessage (line 214) | protected function decodeXmlMessage(string $contents): array
    method decodeJsonMessage (line 256) | protected function decodeJsonMessage(string $contents): array
    method getDecryptedMessage (line 285) | public function getDecryptedMessage(?ServerRequestInterface $request =...

FILE: src/Pay/Signature.php
  class Signature (line 23) | class Signature
    method __construct (line 25) | public function __construct(protected MerchantInterface $merchant)
    method createHeader (line 32) | public function createHeader(string $method, string $url, array $optio...

FILE: src/Pay/URLSchemeBuilder.php
  class URLSchemeBuilder (line 12) | class URLSchemeBuilder
    method __construct (line 14) | public function __construct(protected MerchantInterface $merchant)
    method forProduct (line 18) | public function forProduct(string|int $productId, string $appId): string
    method forCodeUrl (line 33) | public function forCodeUrl(string $codeUrl): string

FILE: src/Pay/Utils.php
  class Utils (line 20) | class Utils
    method __construct (line 22) | public function __construct(protected MerchantInterface $merchant)
    method buildBridgeConfig (line 29) | #[\JetBrains\PhpStorm\ArrayShape([
    method buildSdkConfig (line 68) | #[\JetBrains\PhpStorm\ArrayShape([
    method buildMiniAppConfig (line 91) | #[\JetBrains\PhpStorm\ArrayShape([
    method buildAppConfig (line 107) | #[\JetBrains\PhpStorm\ArrayShape([
    method createSignature (line 137) | protected function createSignature(string $message): string
    method encryptWithRsaPublicKey (line 155) | public function encryptWithRsaPublicKey(string $plaintext, ?string $se...
    method createV2Signature (line 176) | public function createV2Signature(array $params): string

FILE: src/Pay/Validator.php
  class Validator (line 12) | class Validator implements \EasyWeChat\Pay\Contracts\Validator
    method __construct (line 24) | public function __construct(protected MerchantInterface $merchant)
    method validate (line 32) | public function validate(MessageInterface $message): void

FILE: src/Work/AccessToken.php
  class AccessToken (line 22) | class AccessToken implements RefreshableAccessToken
    method __construct (line 28) | public function __construct(
    method getKey (line 39) | public function getKey(): string
    method setKey (line 44) | public function setKey(string $key): static
    method getToken (line 51) | public function getToken(): string
    method toQuery (line 65) | #[\JetBrains\PhpStorm\ArrayShape(['access_token' => 'string'])]
    method refresh (line 74) | public function refresh(): string

FILE: src/Work/Account.php
  class Account (line 9) | class Account implements AccountInterface
    method __construct (line 11) | public function __construct(
    method getCorpId (line 19) | public function getCorpId(): string
    method getSecret (line 24) | public function getSecret(): string
    method getToken (line 29) | public function getToken(): string
    method getAesKey (line 34) | public function getAesKey(): string

FILE: src/Work/Application.php
  class Application (line 24) | class Application implements ApplicationInterface
    method getAccount (line 43) | public function getAccount(): AccountInterface
    method setAccount (line 57) | public function setAccount(AccountInterface $account): static
    method getEncryptor (line 64) | public function getEncryptor(): Encryptor
    method setEncryptor (line 77) | public function setEncryptor(Encryptor $encryptor): static
    method getServer (line 84) | public function getServer(string $messageType = 'xml'): Server|ServerI...
    method setServer (line 97) | public function setServer(ServerInterface $server): static
    method getAccessToken (line 104) | public function getAccessToken(): AccessTokenInterface
    method setAccessToken (line 118) | public function setAccessToken(AccessTokenInterface $accessToken): static
    method getUtils (line 125) | public function getUtils(): Utils
    method createClient (line 130) | public function createClient(): AccessTokenAwareClient
    method getOAuth (line 140) | public function getOAuth(): SocialiteProviderInterface
    method getTicket (line 160) | public function getTicket(): JsApiTicket
    method setTicket (line 173) | public function setTicket(JsApiTicket $ticket): static
    method getHttpClientDefaultOptions (line 183) | protected function getHttpClientDefaultOptions(): array

FILE: src/Work/Config.php
  class Config (line 7) | class Config extends \EasyWeChat\Kernel\Config

FILE: src/Work/Contracts/Account.php
  type Account (line 7) | interface Account
    method getCorpId (line 9) | public function getCorpId(): string;
    method getSecret (line 11) | public function getSecret(): string;
    method getToken (line 13) | public function getToken(): string;
    method getAesKey (line 15) | public function getAesKey(): string;

FILE: src/Work/Contracts/Application.php
  type Application (line 16) | interface Application
    method getAccount (line 18) | public function getAccount(): Account;
    method getEncryptor (line 20) | public function getEncryptor(): Encryptor;
    method getServer (line 22) | public function getServer(): Server;
    method getRequest (line 24) | public function getRequest(): ServerRequestInterface;
    method getClient (line 26) | public function getClient(): AccessTokenAwareClient;
    method getHttpClient (line 28) | public function getHttpClient(): HttpClientInterface;
    method getConfig (line 30) | public function getConfig(): Config;
    method getAccessToken (line 32) | public function getAccessToken(): AccessToken;
    method getCache (line 34) | public function getCache(): CacheInterface;

FILE: src/Work/Encryptor.php
  class Encryptor (line 5) | class Encryptor extends \EasyWeChat\Kernel\Encryptor
    method __construct (line 7) | #[\JetBrains\PhpStorm\Pure]

FILE: src/Work/JsApiTicket.php
  class JsApiTicket (line 18) | class JsApiTicket
    method __construct (line 24) | public function __construct(
    method createConfigSignature (line 37) | #[\JetBrains\PhpStorm\ArrayShape([
    method getTicketSignature (line 55) | public function getTicketSignature(string $ticket, string $nonce, int ...
    method getTicket (line 63) | public function getTicket(): string
    method setKey (line 83) | public function setKey(string $key): static
    method getKey (line 90) | public function getKey(): string
    method createAgentConfigSignature (line 98) | #[\JetBrains\PhpStorm\ArrayShape([
    method getAgentTicket (line 121) | public function getAgentTicket(int $agentId): string
    method getAgentKey (line 142) | public function getAgentKey(int $agentId): string

FILE: src/Work/Message.php
  class Message (line 13) | class Message extends \EasyWeChat\Kernel\Message

FILE: src/Work/Server.php
  class Server (line 20) | class Server implements ServerInterface
    method __construct (line 28) | public function __construct(
    method serve (line 36) | public function serve(): ResponseInterface
    method handleContactChanged (line 66) | public function handleContactChanged(callable $handler): static
    method handleUserTagUpdated (line 75) | public function handleUserTagUpdated(callable $handler): static
    method handleUserCreated (line 87) | public function handleUserCreated(callable $handler): static
    method handleUserUpdated (line 99) | public function handleUserUpdated(callable $handler): static
    method handleUserDeleted (line 111) | public function handleUserDeleted(callable $handler): static
    method handlePartyCreated (line 123) | public function handlePartyCreated(callable $handler): static
    method handlePartyUpdated (line 135) | public function handlePartyUpdated(callable $handler): static
    method handlePartyDeleted (line 147) | public function handlePartyDeleted(callable $handler): static
    method handleBatchJobsFinished (line 159) | public function handleBatchJobsFinished(callable $handler): static
    method addMessageListener (line 168) | public function addMessageListener(string $type, callable $handler): s...
    method addEventListener (line 179) | public function addEventListener(string $event, callable $handler): st...
    method validateUrl (line 190) | protected function validateUrl(): Closure
    method decryptRequestMessage (line 205) | protected function decryptRequestMessage(): Closure
    method getRequestMessage (line 222) | public function getRequestMessage(?ServerRequestInterface $request = n...
    method getDecryptedMessage (line 227) | public function getDecryptedMessage(?ServerRequestInterface $request =...

FILE: src/Work/Utils.php
  class Utils (line 9) | class Utils
    method __construct (line 11) | public function __construct(protected Application $app)
    method buildJsSdkConfig (line 20) | public function buildJsSdkConfig(
    method buildJsSdkAgentConfig (line 38) | public function buildJsSdkAgentConfig(

FILE: tests/Kernel/EncryptorTest.php
  class EncryptorTest (line 9) | class EncryptorTest extends TestCase
    method getEncryptor (line 11) | public function getEncryptor()
    method test_decrypt (line 16) | public function test_decrypt()
    method test_decrypt_with_error_signature (line 28) | public function test_decrypt_with_error_signature()
    method test_decrypt_with_error_received_id (line 36) | public function test_decrypt_with_error_received_id()
    method test_encrypt_and_decrypt (line 45) | public function test_encrypt_and_decrypt()
    method test_get_token (line 69) | public function test_get_token()

FILE: tests/Kernel/HttpClient/AccessTokenAwareClientTest.php
  class AccessTokenAwareClientTest (line 11) | class AccessTokenAwareClientTest extends TestCase
    method test_full_uri_call (line 13) | public function test_full_uri_call()
    method test_shortcuts_call (line 30) | public function test_shortcuts_call()
    method test_it_will_auto_wrap_body (line 45) | public function test_it_will_auto_wrap_body()
    method test_it_will_apply_access_token_to_query (line 90) | public function test_it_will_apply_access_token_to_query()
    method test_it_will_merge_presets (line 112) | public function test_it_will_merge_presets()

FILE: tests/Kernel/HttpClient/AccessTokenExpiredRetryStrategyTest.php
  class AccessTokenExpiredRetryStrategyTest (line 13) | class AccessTokenExpiredRetryStrategyTest extends TestCase
    method test_it_will_passthru_to_parent_retry_strategy (line 15) | public function test_it_will_passthru_to_parent_retry_strategy()
    method test_it_will_refresh_access_token_when_token_is_refreshable (line 28) | public function test_it_will_refresh_access_token_when_token_is_refres...
    method getContext (line 69) | private function getContext($retryCount, $method, $url, $statusCode): ...

FILE: tests/Kernel/HttpClient/HttpClientMethodsTest.php
  class HttpClientMethodsTest (line 11) | class HttpClientMethodsTest extends TestCase
    method test_get (line 13) | public function test_get()
    method test_post (line 24) | public function test_post()
    method test_post_json (line 44) | public function test_post_json()
    method test_post_xml (line 61) | public function test_post_xml()
    method test_put (line 98) | public function test_put()
    method test_patch (line 108) | public function test_patch()
    method test_patch_json (line 118) | public function test_patch_json()
    method test_delete (line 129) | public function test_delete()
  class DummyHttpClient (line 139) | class DummyHttpClient
    method request (line 143) | public function request($method, $url, $options = []): ResponseInterface

FILE: tests/Kernel/HttpClient/RequestUtilTest.php
  class RequestUtilTest (line 10) | class RequestUtilTest extends TestCase
    method test_merge_default_retry_options (line 12) | public function test_merge_default_retry_options()
    method test_format_default_options (line 30) | public function test_format_default_options()
    method test_format_options (line 50) | public function test_format_options()
    method test_format_xml_body (line 124) | public function test_format_xml_body()
    method test_format_json_body (line 147) | public function test_format_json_body()

FILE: tests/Kernel/HttpClient/RequestWithPresetsTest.php
  class RequestWithPresetsTest (line 8) | class RequestWithPresetsTest extends TestCase
    method test_it_can_with_key_value (line 10) | public function test_it_can_with_key_value()
    method test_it_can_with_key_of_presets (line 46) | public function test_it_can_with_key_of_presets()
    method test_it_can_with_use_magic_call (line 68) | public function test_it_can_with_use_magic_call()
    method test_it_can_merge_to_options (line 96) | public function test_it_can_merge_to_options()
    method test_it_can_with_headers (line 207) | public function test_it_can_with_headers()
  class DummyClassForRequestWithPresetsTest (line 227) | class DummyClassForRequestWithPresetsTest
    method getPrependsParts (line 231) | public function getPrependsParts(): array
    method getPrependsHeaders (line 236) | public function getPrependsHeaders(): array
    method __call (line 241) | public function __call(string $name, array $arguments)

FILE: tests/Kernel/HttpClient/ResponseTest.php
  class ResponseTest (line 12) | class ResponseTest extends TestCase
    method test_it_will_throw_if_body_is_empty (line 14) | public function test_it_will_throw_if_body_is_empty()
    method test_it_can_decode_xml (line 24) | public function test_it_can_decode_xml()
    method test_it_support_array_access (line 39) | public function test_it_support_array_access()
    method test_it_support_to_json (line 56) | public function test_it_support_to_json()
    method test_it_can_get_headers (line 69) | public function test_it_can_get_headers()
    method test_it_can_save_content_to_files (line 85) | public function test_it_can_save_content_to_files()
    method test_it_can_transform_to_data_url (line 113) | public function test_it_can_transform_to_data_url()
    method test_it_can_judge_failure_with_custom_callback (line 126) | public function test_it_can_judge_failure_with_custom_callback()
    method test_it_can_has_global_throw_settings (line 167) | public function test_it_can_has_global_throw_settings()

FILE: tests/Kernel/HttpClient/RetryableClientTest.php
  class RetryableClientTest (line 15) | class RetryableClientTest extends TestCase
    method test_it_can_retry_with_default_config (line 17) | public function test_it_can_retry_with_default_config()
    method test_it_can_retry_with_custom_strategy (line 42) | public function test_it_can_retry_with_custom_strategy()
  class DummyClientForRetryableClientTest (line 73) | class DummyClientForRetryableClientTest implements HttpClientInterface
    method __construct (line 78) | public function __construct($response = null)
    method getClient (line 83) | public function getClient(): HttpClientInterface
    method __call (line 88) | public function __call(string $name, array $arguments)

FILE: tests/Kernel/MessageTest.php
  class MessageTest (line 8) | class MessageTest extends TestCase
    method test_message (line 10) | public function test_message()
    method test_message_can_be_encode_as_json (line 17) | public function test_message_can_be_encode_as_json()

FILE: tests/Kernel/ServerResponseTest.php
  class ServerResponseTest (line 9) | class ServerResponseTest extends TestCase
    method test_to_string (line 11) | public function test_to_string()
    method test_to_string_without_headers (line 21) | public function test_to_string_without_headers()
    method test_it_can_send_response (line 30) | public function test_it_can_send_response()

FILE: tests/Kernel/Support/AesCbcTest.php
  class AesCbcTest (line 10) | class AesCbcTest extends TestCase
    method test_it_can_encrypt_and_decrypt (line 12) | public function test_it_can_encrypt_and_decrypt()

FILE: tests/Kernel/Support/AesEcbTest.php
  class AesEcbTest (line 11) | class AesEcbTest extends TestCase
    method test_it_can_encrypt_and_decrypt (line 13) | public function test_it_can_encrypt_and_decrypt()

FILE: tests/Kernel/Support/AesGcmTest.php
  class AesGcmTest (line 10) | class AesGcmTest extends TestCase
    method test_it_can_encrypt_and_decrypt (line 12) | public function test_it_can_encrypt_and_decrypt()

FILE: tests/Kernel/Support/MessageParserTest.php
  class MessageParserTest (line 11) | class MessageParserTest extends TestCase
    method test_it_can_parse_json_content (line 13) | public function test_it_can_parse_json_content()
    method test_it_can_parse_json_with_whitespace (line 23) | public function test_it_can_parse_json_with_whitespace()
    method test_it_falls_back_to_xml_when_json_decode_fails (line 32) | public function test_it_falls_back_to_xml_when_json_decode_fails()
    method test_it_falls_back_to_xml_when_json_is_not_array (line 42) | public function test_it_falls_back_to_xml_when_json_is_not_array()
    method test_it_falls_back_to_xml_when_json_is_empty_array (line 50) | public function test_it_falls_back_to_xml_when_json_is_empty_array()
    method test_it_falls_back_to_xml_when_json_is_not_array_but_xml_is_valid (line 58) | public function test_it_falls_back_to_xml_when_json_is_not_array_but_x...
    method test_it_throws_exception_when_both_json_and_xml_fail (line 69) | public function test_it_throws_exception_when_both_json_and_xml_fail()
    method test_it_prioritizes_json_over_xml (line 77) | public function test_it_prioritizes_json_over_xml()
    method test_it_can_parse_xml_with_whitespace (line 88) | public function test_it_can_parse_xml_with_whitespace()

FILE: tests/Kernel/Support/PrivateKeyTest.php
  class PrivateKeyTest (line 8) | class PrivateKeyTest extends TestCase
    method test_create_from_contents (line 10) | public function test_create_from_contents()
    method test_create_from_path (line 19) | public function test_create_from_path()

FILE: tests/Kernel/Support/PublicKeyTest.php
  class PublicKeyTest (line 8) | class PublicKeyTest extends TestCase
    method test_create_from_contents (line 10) | public function test_create_from_contents()
    method test_create_from_path (line 18) | public function test_create_from_path()

FILE: tests/Kernel/Support/UserAgentTest.php
  class UserAgentTest (line 11) | class UserAgentTest extends TestCase
    method test_it_can_generate_user_agent (line 13) | public function test_it_can_generate_user_agent()

FILE: tests/Kernel/Traits/DecryptJsonMessageTest.php
  class DecryptJsonMessageTest (line 11) | class DecryptJsonMessageTest extends TestCase
    method test_it_can_decrypt_json_message (line 15) | public function test_it_can_decrypt_json_message(): void
  class JsonDummyMessage (line 66) | class JsonDummyMessage extends Message

FILE: tests/Kernel/Traits/DecryptXmlMessageTest.php
  class DecryptXmlMessageTest (line 11) | class DecryptXmlMessageTest extends TestCase
    method test_it_can_decrypt_message (line 15) | public function test_it_can_decrypt_message()
  class DummyMessage (line 40) | class DummyMessage extends Message

FILE: tests/Kernel/Traits/InteractWithCacheTest.php
  class InteractWithCacheTest (line 10) | class InteractWithCacheTest extends TestCase
    method test_get_and_set_cache (line 12) | public function test_get_and_set_cache()
  class DummyClassForInteractWithCacheTest (line 26) | class DummyClassForInteractWithCacheTest

FILE: tests/Kernel/Traits/InteractWithClientTest.php
  class InteractWithClientTest (line 9) | class InteractWithClientTest extends TestCase
    method test_get_and_set_client (line 11) | public function test_get_and_set_client()
  class DummyClassForInteractWithClientTest (line 25) | class DummyClassForInteractWithClientTest
    method createClient (line 29) | public function createClient(): AccessTokenAwareClient

FILE: tests/Kernel/Traits/InteractWithConfigTest.php
  class InteractWithConfigTest (line 9) | class InteractWithConfigTest extends TestCase
    method test_get_and_set_config (line 11) | public function test_get_and_set_config()
  class DummyClassForInteractWithConfigTest (line 25) | class DummyClassForInteractWithConfigTest

FILE: tests/Kernel/Traits/InteractWithHandlersTest.php
  class InteractWithHandlersTest (line 10) | class InteractWithHandlersTest extends TestCase
    method test_it_has_callable_handlers (line 15) | public function test_it_has_callable_handlers()
    method test_it_has_closure_handlers (line 55) | public function test_it_has_closure_handlers()
    method test_it_has_class_based_handlers (line 73) | public function test_it_has_class_based_handlers()
    method test_it_will_run_by_sort (line 91) | public function test_it_will_run_by_sort()
    method test_it_can_push_with_conditions (line 121) | public function test_it_can_push_with_conditions()
    method test_it_can_handle_with_chain_handles (line 145) | public function test_it_can_handle_with_chain_handles()
    method test_it_can_handle_with_default_value (line 179) | public function test_it_can_handle_with_default_value()
    method test_it_can_prepend_handlers (line 215) | public function test_it_can_prepend_handlers()
  class DummyClassBasedHandler (line 247) | class DummyClassBasedHandler
    method __invoke (line 249) | public function __invoke($payload, \Closure $next)

FILE: tests/Kernel/Traits/InteractWithHttpClientTest.php
  class InteractWithHttpClientTest (line 10) | class InteractWithHttpClientTest extends TestCase
    method test_get_and_set_http_client (line 12) | public function test_get_and_set_http_client()
  class DummyClassForInteractWithHttpClientTest (line 26) | class DummyClassForInteractWithHttpClientTest

FILE: tests/Kernel/Traits/InteractWithServerRequestTest.php
  class InteractWithServerRequestTest (line 9) | class InteractWithServerRequestTest extends TestCase
    method test_get_and_set_request (line 11) | public function test_get_and_set_request()
    method test_it_can_set_request_from_symfony_request (line 24) | public function test_it_can_set_request_from_symfony_request()
  class DummyClassForInteractWithServerRequestTest (line 38) | class DummyClassForInteractWithServerRequestTest

FILE: tests/Kernel/Traits/RespondXmlMessageTest.php
  class RespondXmlMessageTest (line 10) | class RespondXmlMessageTest extends TestCase
    method test_it_will_return_success_response (line 14) | public function test_it_will_return_success_response()
    method test_it_will_handle_array_response (line 22) | public function test_it_will_handle_array_response()
    method test_it_will_handle_string_response (line 40) | public function test_it_will_handle_string_response()
    method test_it_will_throw_when_response_type_error (line 56) | public function test_it_will_throw_when_response_type_error()

FILE: tests/MiniApp/AccessTokenTest.php
  class AccessTokenTest (line 8) | class AccessTokenTest extends TestCase
    method test_it_will_use_mini_app_cache_prefix (line 10) | public function test_it_will_use_mini_app_cache_prefix()

FILE: tests/MiniApp/ApplicationTest.php
  class ApplicationTest (line 20) | class ApplicationTest extends TestCase
    method test_get_and_set_account (line 22) | public function test_get_and_set_account()
    method test_get_and_set_encryptor (line 42) | public function test_get_and_set_encryptor()
    method test_get_and_set_server (line 62) | public function test_get_and_set_server()
    method test_get_and_set_access_token (line 82) | public function test_get_and_set_access_token()
    method test_get_utils (line 101) | public function test_get_utils()

FILE: tests/MiniApp/DecryptorTest.php
  class DecryptorTest (line 9) | class DecryptorTest extends TestCase
    method test_it_can_decrypt_message (line 11) | public function test_it_can_decrypt_message()
    method test_it_will_throw_exception_when_payload_is_invalid (line 37) | public function test_it_will_throw_exception_when_payload_is_invalid()

FILE: tests/MiniApp/UtilsTest.php
  class UtilsTest (line 12) | class UtilsTest extends TestCase
    method test_code_to_session (line 14) | public function test_code_to_session()
    method test_decrypt_session (line 41) | public function test_decrypt_session()
    method test_get_phone_number (line 91) | public function test_get_phone_number()
    method test_get_phone_number_with_error (line 130) | public function test_get_phone_number_with_error()

FILE: tests/OfficialAccount/AccessTokenTest.php
  class AccessTokenTest (line 13) | class AccessTokenTest extends TestCase
    method test_get_token_from_http_request (line 15) | public function test_get_token_from_http_request()
    method test_get_token_from_cache (line 47) | public function test_get_token_from_cache()
    method test_set_key (line 70) | public function test_set_key()

FILE: tests/OfficialAccount/AccountTest.php
  class AccountTest (line 12) | class AccountTest extends TestCase
    method test_application_can_create_account_instance (line 17) | public function test_application_can_create_account_instance()
    method test_set_account_to_application (line 33) | public function test_set_account_to_application()
    method test_get_account_app_id (line 72) | public function test_get_account_app_id()
    method test_get_account_secret (line 91) | public function test_get_account_secret()
    method test_get_account_token (line 110) | public function test_get_account_token()
    method test_get_account_aes_key (line 129) | public function test_get_account_aes_key()

FILE: tests/OfficialAccount/ApplicationTest.php
  class ApplicationTest (line 22) | class ApplicationTest extends TestCase
    method test_get_and_set_account (line 24) | public function test_get_and_set_account()
    method test_get_and_set_encryptor (line 44) | public function test_get_and_set_encryptor()
    method test_get_and_set_server (line 64) | public function test_get_and_set_server()
    method test_get_and_set_access_token (line 84) | public function test_get_and_set_access_token()
    method test_get_client_without_http_config (line 104) | public function test_get_client_without_http_config()
    method test_get_and_set_ticket (line 131) | public function test_get_and_set_ticket()
    method test_get_utils (line 150) | public function test_get_utils()

FILE: tests/OfficialAccount/ConfigTest.php
  class ConfigTest (line 13) | class ConfigTest extends TestCase
    method test_application_created_can_get_config (line 18) | public function test_application_created_can_get_config()
    method test_set_config_to_application (line 35) | public function test_set_config_to_application()
    method test_init_config_can_check_missing_keys (line 71) | public function test_init_config_can_check_missing_keys()

FILE: tests/OfficialAccount/JsApiTicketTest.php
  class JsApiTicketTest (line 12) | class JsApiTicketTest extends TestCase
    method test_get_key (line 14) | public function test_get_key()
    method test_get_ticket (line 29) | public function test_get_ticket()
    method test_config_signature (line 59) | public function test_config_signature()

FILE: tests/OfficialAccount/ServerTest.php
  class ServerTest (line 13) | class ServerTest extends TestCase
    method test_it_will_handle_validation_request (line 15) | public function test_it_will_handle_validation_request()
    method test_it_will_response_success_without_handlers (line 25) | public function test_it_will_response_success_without_handlers()
    method test_it_will_respond_from_message_handlers (line 43) | public function test_it_will_respond_from_message_handlers()
    method test_it_will_respond_from_event_handlers (line 77) | public function test_it_will_respond_from_event_handlers()
    method test_it_can_decrypt_json_mode_messages (line 112) | public function test_it_can_decrypt_json_mode_messages()

FILE: tests/OfficialAccount/UtilsTest.php
  class UtilsTest (line 10) | class UtilsTest extends TestCase
    method test_build_js_sdk_config (line 12) | public function test_build_js_sdk_config()

FILE: tests/OpenPlatform/AccountTest.php
  class AccountTest (line 10) | class AccountTest extends TestCase
    method test_application_can_create_account_instance (line 12) | public function test_application_can_create_account_instance()
    method test_set_account_to_application (line 29) | public function test_set_account_to_application()
    method test_get_account_app_id (line 63) | public function test_get_account_app_id()
    method test_get_account_secret (line 82) | public function test_get_account_secret()
    method test_get_account_token (line 101) | public function test_get_account_token()
    method test_get_account_aes_key (line 120) | public function test_get_account_aes_key()

FILE: tests/OpenPlatform/ApplicationTest.php
  class ApplicationTest (line 29) | class ApplicationTest extends TestCase
    method test_get_and_set_account (line 31) | public function test_get_and_set_account()
    method test_get_and_set_encryptor (line 50) | public function test_get_and_set_encryptor()
    method test_get_and_set_server (line 72) | public function test_get_and_set_server()
    method test_get_and_set_component_access_token (line 93) | public function test_get_and_set_component_access_token()
    method test_get_and_set_verify_ticket (line 109) | public function test_get_and_set_verify_ticket()
    method test_get_authorization (line 126) | public function test_get_authorization()
    method test_get_authorization_exception (line 174) | public function test_get_authorization_exception()
    method test_refresh_authorizer_token (line 214) | public function test_refresh_authorizer_token()
    method test_refresh_authorizer_token_exception (line 267) | public function test_refresh_authorizer_token_exception()
    method test_get_oauth (line 310) | public function test_get_oauth()
    method test_get_official_account (line 322) | public function test_get_official_account()
    method test_get_mini_app (line 339) | public function test_get_mini_app()

FILE: tests/OpenPlatform/AuthorizationTest.php
  class AuthorizationTest (line 9) | class AuthorizationTest extends TestCase
    method test_get_app_id (line 11) | public function test_get_app_id()
    method test_get_access_token (line 22) | public function test_get_access_token()
    method test_get_refresh_token (line 36) | public function test_get_refresh_token()

FILE: tests/OpenPlatform/AuthorizerAccessTokenTest.php
  class AuthorizerAccessTokenTest (line 8) | class AuthorizerAccessTokenTest extends TestCase
    method test_get_app_id_and_token (line 10) | public function test_get_app_id_and_token()

FILE: tests/OpenPlatform/ComponentAccessTokenTest.php
  class ComponentAccessTokenTest (line 12) | class ComponentAccessTokenTest extends TestCase
    method test_set_and_get_cache_key (line 14) | public function test_set_and_get_cache_key()
    method test_get_token_from_cache (line 24) | public function test_get_token_from_cache()
    method test_get_token_from_server (line 38) | public function test_get_token_from_server()

FILE: tests/OpenPlatform/ServerTest.php
  class ServerTest (line 9) | class ServerTest extends TestCase
    method test_it_will_handle_authorized_event (line 11) | public function test_it_will_handle_authorized_event()
    method test_it_will_handle_unauthorized_event (line 37) | public function test_it_will_handle_unauthorized_event()
    method test_it_will_handle_authorize_updated_event (line 60) | public function test_it_will_handle_authorize_updated_event()
    method test_it_will_handle_verify_ticket_refresh_event (line 86) | public function test_it_will_handle_verify_ticket_refresh_event()

FILE: tests/Pay/ApplicationTest.php
  class ApplicationTest (line 14) | class ApplicationTest extends TestCase
    method test_get_merchant (line 16) | public function test_get_merchant()
    method test_get_client (line 32) | public function test_get_client()
    method test_get_server (line 48) | public function test_get_server()
    method test_get_and_set_validator (line 64) | public function test_get_and_set_validator()

FILE: tests/Pay/ClientTest.php
  class ClientTest (line 12) | class ClientTest extends TestCase
    method test_v3_request (line 14) | public function test_v3_request()
    method test_v2_request_with_array (line 32) | public function test_v2_request_with_array()
    method test_v2_request_without_body (line 52) | public function test_v2_request_without_body()
    method test_v2_request_with_xml_option (line 68) | public function test_v2_request_with_xml_option()
    method test_v2_request_with_xml_string (line 84) | public function test_v2_request_with_xml_string()
    method test_v2_request_with_xml_string_as_body (line 113) | public function test_v2_request_with_xml_string_as_body()
    method test_v2_request_appauth_getaccesstoken (line 129) | public function test_v2_request_appauth_getaccesstoken()
    method test_v3_upload_media (line 146) | public function test_v3_upload_media()
    method test_v3_with_serial_header (line 176) | public function test_v3_with_serial_header()

FILE: tests/Pay/MerchantTest.php
  class MerchantTest (line 12) | class MerchantTest extends TestCase
    method test_construct (line 14) | public function test_construct()

FILE: tests/Pay/ServerTest.php
  class ServerTest (line 24) | class ServerTest extends TestCase
    method test_it_will_handle_validation_request (line 26) | public function test_it_will_handle_validation_request()
    method test_legacy_encryped_by_aesecb_refund_request (line 47) | public function test_legacy_encryped_by_aesecb_refund_request()
    method test_legacy_encryped_by_aesgcm_notification_request (line 93) | public function test_legacy_encryped_by_aesgcm_notification_request()

FILE: tests/Pay/UtilsTest.php
  class UtilsTest (line 15) | class UtilsTest extends TestCase
    method test_create_v2_signature (line 39) | public function test_create_v2_signature()
    method test_encrypt_with_rsa_public_key (line 60) | public function test_encrypt_with_rsa_public_key()

FILE: tests/TestCase.php
  class TestCase (line 12) | class TestCase extends BaseTestCase
    method tearDown (line 17) | protected function tearDown(): void
    method createEncryptedXmlMessageRequest (line 26) | public function createEncryptedXmlMessageRequest($plainMessageXml, Enc...

FILE: tests/Work/AccessTokenTest.php
  class AccessTokenTest (line 13) | class AccessTokenTest extends TestCase
    method test_get_token_from_http_request (line 15) | public function test_get_token_from_http_request()
    method test_get_token_from_cache (line 46) | public function test_get_token_from_cache()
    method test_set_key (line 69) | public function test_set_key()

FILE: tests/Work/AccountTest.php
  class AccountTest (line 12) | class AccountTest extends TestCase
    method test_application_created_can_get_account (line 17) | public function test_application_created_can_get_account()
    method test_set_account_to_application (line 35) | public function test_set_account_to_application()
    method test_get_account_corp_id (line 69) | public function test_get_account_corp_id()
    method test_get_account_secret (line 88) | public function test_get_account_secret()
    method test_get_account_token (line 107) | public function test_get_account_token()
    method test_get_account_aes_key (line 126) | public function test_get_account_aes_key()

FILE: tests/Work/ApplicationTest.php
  class ApplicationTest (line 28) | class ApplicationTest extends TestCase
    method test_get_and_set_account (line 30) | public function test_get_and_set_account()
    method test_get_and_set_encryptor (line 51) | public function test_get_and_set_encryptor()
    method test_get_and_set_request (line 71) | public function test_get_and_set_request()
    method test_get_and_set_server (line 91) | public function test_get_and_set_server()
    method test_get_and_set_client (line 114) | public function test_get_and_set_client()
    method test_get_and_set_http_client (line 134) | public function test_get_and_set_http_client()
    method test_get_and_set_access_token (line 154) | public function test_get_and_set_access_token()
    method test_get_and_set_cache (line 174) | public function test_get_and_set_cache()
    method test_get_and_set_config (line 194) | public function test_get_and_set_config()
    method test_get_and_set_ticket (line 221) | public function test_get_and_set_ticket()
    method test_get_utils (line 241) | public function test_get_utils()
    method test_get_oauth (line 256) | public function test_get_oauth()

FILE: tests/Work/ConfigTest.php
  class ConfigTest (line 13) | class ConfigTest extends TestCase
    method test_application_created_can_get_config (line 18) | public function test_application_created_can_get_config()
    method test_set_config_to_application (line 35) | public function test_set_config_to_application()
    method test_init_config_can_check_missing_keys (line 72) | public function test_init_config_can_check_missing_keys()

FILE: tests/Work/JsApiTicketTest.php
  class JsApiTicketTest (line 11) | class JsApiTicketTest extends TestCase
    method test_get_key (line 13) | public function test_get_key()
    method test_get_agent_key (line 28) | public function test_get_agent_key()
    method test_get_ticket (line 43) | public function test_get_ticket()
    method test_get_agent_ticket (line 73) | public function test_get_agent_ticket()
    method test_config_signature (line 104) | public function test_config_signature()
    method test_agent_config_signature (line 129) | public function test_agent_config_signature()
    method test_get_ticket_signature (line 155) | public function test_get_ticket_signature()

FILE: tests/Work/ServerTest.php
  class ServerTest (line 13) | class ServerTest extends TestCase
    method test_it_will_handle_validation_request (line 17) | public function test_it_will_handle_validation_request()
    method test_it_will_validate_message (line 40) | public function test_it_will_validate_message()
    method test_it_will_response_success_without_handlers (line 68) | public function test_it_will_response_success_without_handlers()
    method test_it_will_respond_from_message_handlers (line 96) | public function test_it_will_respond_from_message_handlers()
    method test_it_will_respond_from_event_handlers (line 142) | public function test_it_will_respond_from_event_handlers()

FILE: tests/Work/UtilsTest.php
  class UtilsTest (line 10) | class UtilsTest extends TestCase
    method test_build_js_sdk_config (line 12) | public function test_build_js_sdk_config()
    method test_build_js_sdk_agent_config (line 47) | public function test_build_js_sdk_agent_config()
Condensed preview — 543 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,314K chars).
[
  {
    "path": ".gitattributes",
    "chars": 355,
    "preview": "* text=auto\n\n/build export-ignore\n/tests export-ignore\n/docs export-ignore\n/.github export-ignore\n.gitattributes export-"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 66,
    "preview": "# These are supported funding model platforms\n\ngithub: [overtrue]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "chars": 338,
    "preview": "## 我用的环境\n\n* PHP 版本:\n* overtrue/wechat 版本:\n* 是否使用了框架?框架名称:\n\n## 问题及现象\n\n<!--\n\n描述你的问题现象,报错**贴截图**粘贴或者贴具体信息,提供**必要的代码段**\n\n如果你"
  },
  {
    "path": ".github/workflows/deploy.yml",
    "chars": 2318,
    "preview": "# This is a basic workflow to help you get started with Actions\n\nname: Deploy\n\n# Controls when the workflow will run\non:"
  },
  {
    "path": ".github/workflows/lint.yml",
    "chars": 549,
    "preview": "name: Lint\non: [push, pull_request]\n\njobs:\n  phpstan:\n    name: PHPStan\n    runs-on: ubuntu-latest\n    steps:\n    - uses"
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 601,
    "preview": "name: Test\non: [push, pull_request]\n\njobs:\n  phpunit:\n    name: PHP-${{ matrix.php_version }}-${{ matrix.perfer }}\n    r"
  },
  {
    "path": ".gitignore",
    "chars": 241,
    "preview": "*.DS_Store\n/vendor\nsftp-config.json\n/*.php\n!.php-cs-fixer.dist.php\n/.idea\n/coverage\n/.split\n/composer.lock\n.php-cs-fixer"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 4445,
    "preview": "# Contribute\n\n## Introduction\n\nFirst, thank you for considering contributing to wechat! It's people like you that make t"
  },
  {
    "path": "LICENSE",
    "chars": 1087,
    "preview": "The MIT License (MIT)\n\nCopyright (c) overtrue <i@overtrue.me>\n\nPermission is hereby granted, free of charge, to any pers"
  },
  {
    "path": "README.md",
    "chars": 1837,
    "preview": "# [EasyWeChat](https://easywechat.com)\n\n📦 一个 PHP 微信开发 SDK,开源 SaaS 平台提供商 [微擎](https://www.w7.cc/) 旗下开源产品。\n\n[![Test Status"
  },
  {
    "path": "SECURITY.md",
    "chars": 394,
    "preview": "# 更新策略 Security Policy\n\n## 支持的版本 Supported Versions\n\n| Version | 状态          |\n| ------- | ------------------ |\n| 6.x   "
  },
  {
    "path": "composer.json",
    "chars": 1742,
    "preview": "{\n  \"name\": \"w7corp/easywechat\",\n  \"description\": \"微信SDK\",\n  \"keywords\": [\n    \"easywechat\",\n    \"wechat\",\n    \"weixin\","
  },
  {
    "path": "docs/.editorconfig",
    "chars": 175,
    "preview": "[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whit"
  },
  {
    "path": "docs/.gitignore",
    "chars": 82,
    "preview": "node_modules/\npackage-lock.json\nyarn-error.log\n.vitepress/dist/\n.vitepress/cache/\n"
  },
  {
    "path": "docs/.npmrc",
    "chars": 24,
    "preview": "auto-install-peers=true\n"
  },
  {
    "path": "docs/.prettierrc",
    "chars": 90,
    "preview": "{\n  \"semi\": false,\n  \"singleQuote\": true,\n  \"trailingComma\": \"none\",\n  \"printWidth\": 75\n}\n"
  },
  {
    "path": "docs/.vitepress/config.ts",
    "chars": 7395,
    "preview": "import path from 'path'\nimport versions from './versions'\n\nconst latest = versions[0]\n\nconst nav = [\n  {\n    text: '首页',"
  },
  {
    "path": "docs/.vitepress/theme/components/Banner.vue",
    "chars": 1047,
    "preview": "<script setup>\n/**\n * Adding a new banner:\n * 1. uncomment the banner slot in ../index.ts\n * 2. uncomment and update BAN"
  },
  {
    "path": "docs/.vitepress/theme/components/Footer.vue",
    "chars": 470,
    "preview": "<script lang=\"ts\" setup>\nimport { useData } from 'vitepress';\n\nconst { theme } = useData()\n</script>\n\n<template>\n  <div "
  },
  {
    "path": "docs/.vitepress/theme/components/SponsorsAside.vue",
    "chars": 560,
    "preview": "<script setup lang=\"ts\">\nimport SponsorsGroup from './SponsorsGroup.vue'\nimport { useData } from 'vitepress'\nconst { fro"
  },
  {
    "path": "docs/.vitepress/theme/components/SponsorsGroup.vue",
    "chars": 4175,
    "preview": "<script setup lang=\"ts\">\nimport { defineProps, onMounted, onUnmounted, ref } from 'vue';\ninterface Sponsor {\n  url: stri"
  },
  {
    "path": "docs/.vitepress/theme/components/VersionTag.vue",
    "chars": 169,
    "preview": "<template>\n  <sup\n    class=\"bg-green-500 text-xs text-white px-2 py-1 rounded-lg align-top rounded-bl-none\"\n    title=\""
  },
  {
    "path": "docs/.vitepress/theme/index.ts",
    "chars": 604,
    "preview": "import './styles/index.css'\nimport { h, App } from 'vue'\nimport SponsorsAside from './components/SponsorsAside.vue'\nimpo"
  },
  {
    "path": "docs/.vitepress/theme/styles/badges.css",
    "chars": 431,
    "preview": ".vt-badge.wip:before {\n  content: 'WIP';\n}\n\n.vt-badge.ts {\n  background-color: #3178c6;\n}\n.vt-badge.ts:before {\n  conten"
  },
  {
    "path": "docs/.vitepress/theme/styles/index.css",
    "chars": 247,
    "preview": "@import './layout.css';\n@import './pages.css';\n@import './badges.css';\n@import './options-boxes.css';\n@import './inline-"
  },
  {
    "path": "docs/.vitepress/theme/styles/inline-demo.css",
    "chars": 1531,
    "preview": ".vt-doc a[href^=\"https://sfc.vuejs.org\"]:before\n{\n  content: '▶';\n  width: 20px;\n  height: 20px;\n  display: inline-block"
  },
  {
    "path": "docs/.vitepress/theme/styles/layout.css",
    "chars": 3449,
    "preview": ".VPContent,\n.VPContent .VPContentPage,\n.VPContent .VPContentPage main,\n.VPContent .VPContentPage main>div,\n.VPContent .V"
  },
  {
    "path": "docs/.vitepress/theme/styles/options-boxes.css",
    "chars": 515,
    "preview": ".next-steps {\n  margin-top: 3rem;\n}\n\n.next-steps .vt-box {\n  border: 1px solid var(--vt-c-bg-soft);\n}\n\n.next-steps .vt-b"
  },
  {
    "path": "docs/.vitepress/theme/styles/pages.css",
    "chars": 399,
    "preview": "/* always show anchors on /api/ and /style-guide/ pages */\n.vt-doc.api h2 .header-anchor,\n.vt-doc.style-guide h2 .header"
  },
  {
    "path": "docs/.vitepress/theme/styles/style-guide.css",
    "chars": 1115,
    "preview": ".style-example {\n  border-radius: 8px 8px 12px 12px;\n  margin: 1.6em 0;\n  padding: 1.6em 1.6em 0.1px;\n  position: relati"
  },
  {
    "path": "docs/.vitepress/theme/styles/utilities.css",
    "chars": 189,
    "preview": ".nowrap {\n  white-space: nowrap;\n}\n\n.sr-only {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  padding: 0;\n  margin"
  },
  {
    "path": "docs/.vitepress/versions.ts",
    "chars": 45,
    "preview": "export default [\"6.x\", \"5.x\", \"4.x\", \"3.x\"];\n"
  },
  {
    "path": "docs/.vitepress/vitepress+1.6.3.patch",
    "chars": 2022,
    "preview": "diff --git a/node_modules/vitepress/dist/node/chunk-Zsoi3j4v.js b/node_modules/vitepress/dist/node/chunk-Zsoi3j4v.js\nind"
  },
  {
    "path": "docs/README.md",
    "chars": 876,
    "preview": "# easywechat.com\n\n## Contributing\n\nThis site is built with [VitePress](https://github.com/vuejs/vitepress) and depends o"
  },
  {
    "path": "docs/env.d.ts",
    "chars": 412,
    "preview": "/// <reference types=\"vitepress/client\" />\n/// <reference types=\"vue/macros-global\" />\n\ndeclare module '@overtrue/easywe"
  },
  {
    "path": "docs/package.json",
    "chars": 754,
    "preview": "{\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"vitepress dev\",\n    \"build\": \"vitepress build\",\n    \"serve\": \"vitepress "
  },
  {
    "path": "docs/postcss.config.js",
    "chars": 179,
    "preview": "module.exports = {\n  plugins: [\n    require('postcss-import'),\n    require(\"tailwindcss\")(\"./tailwind.config.js\"),\n    r"
  },
  {
    "path": "docs/purge-caches",
    "chars": 442,
    "preview": "#!/usr/bin/env node\n\nconst { default: TenYun } = require('tenyun');\n\nconst tc = new TenYun(process.env.COS_SECRET_ID ?? "
  },
  {
    "path": "docs/src/3.x/access_token.md",
    "chars": 1308,
    "preview": "# Access Token\n\n\nSDK 中有一个 [Access Token](https://github.com/overtrue/wechat/blob/master/src/Core/AccessToken.php) 对象,它是一"
  },
  {
    "path": "docs/src/3.x/accounts.md",
    "chars": 526,
    "preview": "# 账号接入\n\n\n如果你想使用本项目接入多个公众号,在本程序中,您可以为每个帐号都设置一个id,此id对应了该帐号的appid、token等信息。\n如下表\n\n| id | appId | secret | 其它... |\n| --- | -"
  },
  {
    "path": "docs/src/3.x/anaylsis.md",
    "chars": 2147,
    "preview": "# 数据统计与分析\n\n\n通过数据接口,开发者可以获取与公众平台官网统计模块类似但更灵活的数据,还可根据需要进行高级处理。\n\n> 1. 接口侧的公众号数据的数据库中仅存储了 **2014年12月1日之后**的数据,将查询不到在此之前的日期,即"
  },
  {
    "path": "docs/src/3.x/broadcast.md",
    "chars": 3044,
    "preview": "# 群发\n\n\n微信的群发消息接口有各种乱七八糟的注意事项及限制,具体请阅读微信官方文档:http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html\n\n## 获取"
  },
  {
    "path": "docs/src/3.x/cache.md",
    "chars": 2652,
    "preview": "# 缓存\n\n\n本项目使用 [doctrine/cache](https://github.com/doctrine/cache) 来完成缓存工作,它支持基本目前所有的缓存引擎。\n\n在我们的 SDK 中的所有缓存默认使用文件缓存,缓存路径取决"
  },
  {
    "path": "docs/src/3.x/card.md",
    "chars": 15466,
    "preview": "# 卡券\n-\n\n> Version `>=3.1.2`\n\n## 获取实例\n\n```php\n<?php\nuse EasyWeChat\\Foundation\\Application;\n\n// ...\n\n$app = new Applicatio"
  },
  {
    "path": "docs/src/3.x/configuration.md",
    "chars": 2390,
    "preview": "# 配置\n\n\n在前面我们已经讲过,初始化 SDK 的时候方法就是创建一个 `EasyWeChat\\Foundation\\Application` 实例:\n\n```php\nuse EasyWeChat\\Foundation\\Applicati"
  },
  {
    "path": "docs/src/3.x/contributing.md",
    "chars": 1002,
    "preview": "# 贡献代码\n\n## 开发\n\n我们欢迎广大开发者贡献大家的智慧,让我们共同让它变得更完美.\n\n### 开始之前\n\n请严格遵循以下代码标准:\n\n- [PSR-2](https://github.com/php-fig/fig-standard"
  },
  {
    "path": "docs/src/3.x/events.md",
    "chars": 804,
    "preview": "# 事件\n\n\n> 注意:3.0 起,所有服务端的入口(**消息与事件**)都已经合并为一个方法来处理:`setMessageHandler()`\n\n### 在服务端接收用户端产生的事件\n\n```php\n<?php\nuse EasyWeCha"
  },
  {
    "path": "docs/src/3.x/index.md",
    "chars": 567,
    "preview": "> 👋🏼 您当前浏览的文档为 3.x,其它版本的文档请参考:[6.x](/6.x/)、[5.x](/5.x/)、[4.x](/4.x/)\n\n# EasyWeChat\n\nEasyWeChat 是一个开源的 [微信](http://www.we"
  },
  {
    "path": "docs/src/3.x/installation.md",
    "chars": 417,
    "preview": "# 安装\n\n\n## 环境要求\n\n- PHP >= 5.5.9\n- [PHP cURL 扩展](http://php.net/manual/en/book.curl.php)\n- [PHP OpenSSL 扩展](http://php.net"
  },
  {
    "path": "docs/src/3.x/integration.md",
    "chars": 396,
    "preview": "# 在框架中使用\n\nEasyWeChat 是一个通用的 Composer 包,所以不需要对框架单独做修改,只要支持 Composer 就能直接使用,当然了,为了更方便的使用,我们收集了以下框架单独提供的拓展包:\n\n## Laravel\n\n-"
  },
  {
    "path": "docs/src/3.x/js.md",
    "chars": 1128,
    "preview": "# JSSDK\n\n\n## 获取实例\n\n```php\n<?php\nuse EasyWeChat\\Foundation\\Application;\n//...\n$app = new Application($options);\n\n$js = $a"
  },
  {
    "path": "docs/src/3.x/lucky-money.md",
    "chars": 3481,
    "preview": "# 红包\n\n\n你在阅读本文之前确认你已经仔细阅读了:[微信支付 | 现金红包文档 ](https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_1)。\n\n"
  },
  {
    "path": "docs/src/3.x/material.md",
    "chars": 5691,
    "preview": "# 素材管理\n\n\n在微信里的图片,音乐,视频等等都需要先上传到微信服务器作为素材才可以在消息中使用。\n\n> 请注意:\n\n>     1. 限制:\n>       - 图片(image): 1M,支持 bmp/png/jpeg/jpg/gif"
  },
  {
    "path": "docs/src/3.x/menu.md",
    "chars": 2200,
    "preview": "# 自定义菜单\n\n\n3.0 的菜单组件有所简化,相比 2.x 版本变化如下:\n\n- 去除 `MenuItem` 类,创建菜单直接使用数组不再支持 `callback` 与 `MenuItem` 类似的繁杂的方式\n- `set()` 方法与 "
  },
  {
    "path": "docs/src/3.x/merchant_payment.md",
    "chars": 1366,
    "preview": "# 企业支付\n\n\n你在阅读本文之前确认你已经仔细阅读了:[微信支付 | 企业付款文档 ](https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_1)。\n\n##"
  },
  {
    "path": "docs/src/3.x/message-transfer.md",
    "chars": 518,
    "preview": "# 多客服消息转发\n\n\n\n多客服的消息转发绝对是超级的简单,转发的消息类型为 `transfer`:\n\n```php\n\n\n  // 转发收到的消息给客服\n  $server->setMessageHandler(function($mess"
  },
  {
    "path": "docs/src/3.x/messages.md",
    "chars": 5175,
    "preview": "# 消息\n\n\n我把微信的 API 里的所有“消息”都按类型抽象出来了,也就是说,你不用区分它是回复消息还是主动推送消息,免去了你去手动拼装微信那帮 SB 那么恶心的 XML 以及乱七八糟命名不统一的 JSON 了,我替你承受这份苦,不要问是"
  },
  {
    "path": "docs/src/3.x/mini_program.md",
    "chars": 1355,
    "preview": "title: 小程序\n---\n\n## 实例化\n\n```php\n<?php\nuse EasyWeChat\\Foundation\\Application;\n\n$options = [\n    // ...\n    'mini_program' "
  },
  {
    "path": "docs/src/3.x/miscellaneous.md",
    "chars": 13,
    "preview": "# 其它\n\n\n### 其它"
  },
  {
    "path": "docs/src/3.x/notice.md",
    "chars": 3099,
    "preview": "# 模板消息\n\n模板消息仅用于公众号向用户发送重要的服务通知,只能用于符合其要求的服务场景中,如信用卡刷卡通知,商品购买成功通知等。不支持广告等营销类消息以及其它所有可能对用户造成骚扰的消息。\n\n## 获取实例\n\n```php\n<?php\n"
  },
  {
    "path": "docs/src/3.x/oauth.md",
    "chars": 6184,
    "preview": "# 网页授权\n\n## 关于 OAuth2.0\n\nOAuth 是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是 2.0 版。\n\n```\n\n     +--------+              "
  },
  {
    "path": "docs/src/3.x/open_platform.md",
    "chars": 3469,
    "preview": "# 微信开放平台\n\n\n### 实例化\n\n```php\n<?php\nuse EasyWeChat\\Foundation\\Application;\n\n$options = [\n    // ...\n    'open_platform' => "
  },
  {
    "path": "docs/src/3.x/overview.md",
    "chars": 2582,
    "preview": "# EasyWeChat\n\n## EasyWeChat 是什么?\n\nEasyWeChat 是一个开源的 [微信](http://www.wechat.com) 非官方 SDK。\n\nEasyWeChat 的安装非常简单,因为它是一个标准的 ["
  },
  {
    "path": "docs/src/3.x/payment.md",
    "chars": 10304,
    "preview": "# 支付\n\n你在阅读本文之前确认你已经仔细阅读了:[微信支付 | 商户平台开发文档](https://pay.weixin.qq.com/wiki/doc/api/index.html)。\n\n网友贡献的教程:[小能手马闯 set 发布在 L"
  },
  {
    "path": "docs/src/3.x/poi.md",
    "chars": 3216,
    "preview": "# 门店\n\n## 获取实例\n\n```php\n<?php\nuse EasyWeChat\\Foundation\\Application;\n\n// ...\n\n$app = new Application($options);\n\n$poi = $a"
  },
  {
    "path": "docs/src/3.x/qrcode.md",
    "chars": 1188,
    "preview": "# 二维码\n\n\n目前有2种类型的二维码:\n\n1. 临时二维码,是有过期时间的,最长可以设置为在二维码生成后的**30天**后过期,但能够生成较多数量。临时二维码主要用于帐号绑定等不要求二维码永久保存的业务场景\n2. 永久二维码,是无过期时间"
  },
  {
    "path": "docs/src/3.x/releases.md",
    "chars": 67,
    "preview": "# 升级日志\n\n\n## 3.0\n\n- 新的架构\n- 重写代码\n- 更低的耦合\n- 更规范的代码\n- 更友好的调试支持\n- 更完善的文档"
  },
  {
    "path": "docs/src/3.x/reply.md",
    "chars": 186,
    "preview": "# 自动回复\n\n\n## 获取实例\n\n```php\n<?php\nuse EasyWeChat\\Foundation\\Application;\n\n// ...\n\n$app = new Application($options);\n\n$reply"
  },
  {
    "path": "docs/src/3.x/roadmap.md",
    "chars": 279,
    "preview": "# 路线图\n\n## 3.1\n\n- 微信小店\n- 新的卡券\n- 设备管理\n\n## 3.0\n\n- 全新的架构,更清晰的模块拆分\n- Debug 优化,便于快速定位问题\n- 统一返回微信 API 原值,以 Collection 类装载,便于便捷操"
  },
  {
    "path": "docs/src/3.x/semantic.md",
    "chars": 1747,
    "preview": "# 语义理解\n\n\n微信开放平台语义理解接口调用(http请求)简单方便,用户无需掌握语义理解及相关技术,只需根据自己的产品特点,选择相应的服务即可搭建一套智能语义服务。\n\n## 获取实例\n\n```php\n<?php\n\n// ... 前面部分"
  },
  {
    "path": "docs/src/3.x/server.md",
    "chars": 4569,
    "preview": "# 服务端\n\n\n我们在入门小教程一节以服务端为例讲解了一个基本的消息的处理,这里就不再讲服务器验证的流程了,请直接参考前面的入门实例即可。\n\n服务端的作用呢,在整个微信开发中主要是负责 **[接收用户发送过来的消息](http://mp.w"
  },
  {
    "path": "docs/src/3.x/shake-around.md",
    "chars": 25599,
    "preview": "# 摇一摇周边\n\n\n摇一摇周边是微信在线下的全新功能, 为线下商户提供近距离连接用户的能力, 并支持线下商户向周边用户提供个性化营销、互动及信息推荐等服务。\n\n## 获取实例\n\n```php\n<?php\nuse EasyWeChat\\Fou"
  },
  {
    "path": "docs/src/3.x/short-url.md",
    "chars": 379,
    "preview": "# 短网址服务\n\n\n主要使用场景: 开发者用于生成二维码的原链接(商品、支付二维码等)太长导致扫码速度和成功率下降,将原长链接通过此接口转成短链接再生成二维码将大大提升扫码速度和成功率。\n\n## 获取实例\n\n```php\n<?php\nuse"
  },
  {
    "path": "docs/src/3.x/sidebar.js",
    "chars": 2235,
    "preview": "exports = module.exports = [\n  {\n    text: '开始使用',\n    items: [\n      { text: '概述', link: '/3.x/overview.html' },\n      "
  },
  {
    "path": "docs/src/3.x/staff.md",
    "chars": 1564,
    "preview": "# 客服\n\n\n> 2016.06.28 已经更新为新版多客服 API\n> 请更新到 3.1 版本: composer require \"overtrue/wechat:~3.1\"\n\n微信的客服才能发送消息或者群发消息,而且还有时效限制,真恶"
  },
  {
    "path": "docs/src/3.x/store.md",
    "chars": 11,
    "preview": "# 门店\n\n\nTODO"
  },
  {
    "path": "docs/src/3.x/troubleshooting.md",
    "chars": 3982,
    "preview": "# 疑难解答\n\n\n在微信公众平台开发的道路上,遍布着各种大大小小的坑,有的人掉坑里,几经折腾又爬出来了,然后拍拍屁股走人。然而坑还在那里,还会继续有后来人掉进去……\n\n这,是我们不愿看到的。\n\n所以在这里,我们将陆续将微信开发中可能遇到的各"
  },
  {
    "path": "docs/src/3.x/tutorial.md",
    "chars": 4405,
    "preview": "# 快速开始\n\n在我们已经安装完成后,即可很快的开始使用它了,当然你还是有必要明白 PHP 基本知识,如命名空间等,我这里就不赘述了。\n\n我们以完成服务器端验证与接收响应用户发送的消息为例来演示,首先你有必要了解一下微信交互的运行流程:\n\n"
  },
  {
    "path": "docs/src/3.x/user-group.md",
    "chars": 1272,
    "preview": "# 用户组\n\n\n用户组的使用就非常简单了,基本的增删改查。\n\n## 获取实例\n\n```php\n<?php\nuse EasyWeChat\\Foundation\\Application;\n\n// ...\n\n$app = new Applicat"
  },
  {
    "path": "docs/src/3.x/user-tag.md",
    "chars": 1618,
    "preview": "# 用户标签\n\n\n用户标签的使用就非常简单了,基本的增删改查。\n\n## 获取实例\n\n```php\n<?php\nuse EasyWeChat\\Foundation\\Application;\n\n// ...\n\n$app = new Applic"
  },
  {
    "path": "docs/src/3.x/user.md",
    "chars": 1212,
    "preview": "# 用户\n\n\n用户信息的获取是微信开发中比较常用的一个功能了,以下所有的用户信息的获取与更新,都是**基于微信的 `openid` 的,并且是已关注当前账号的**,其它情况可能无法正常使用。\n\n## 获取实例\n\n```php\n<?php\nu"
  },
  {
    "path": "docs/src/4.x/basic-services/content_security.md",
    "chars": 1199,
    "preview": "# 内容安全接口\n\n## 文本安全内容检测\n\n用于校验一段文本是否含有违法内容。\n\n### 频率限制\n\n单个appid调用上限为2000次/分钟,1,000,000次/天\n\n### 调用示例\n\n```php\n// 传入要检测的文本内容,长度"
  },
  {
    "path": "docs/src/4.x/basic-services/jssdk.md",
    "chars": 1131,
    "preview": "# JSSDK\n\n微信 JSSDK 官方文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115\n\n## API\n\n#### 获取JSSDK的配置数组\n\n```"
  },
  {
    "path": "docs/src/4.x/basic-services/media.md",
    "chars": 1553,
    "preview": "# 临时素材\n\n上传的临时多媒体文件有格式和大小限制,如下:\n\n> - 图片(image): 2M,支持 `JPG` 格式\n> - 语音(voice):2M,播放长度不超过 `60s`,支持 `AMR\\MP3` 格式\n> - 视频(vide"
  },
  {
    "path": "docs/src/4.x/basic-services/qrcode.md",
    "chars": 1066,
    "preview": "# 二维码\n\n目前有 2 种类型的二维码:\n\n1. 临时二维码,是有过期时间的,最长可以设置为在二维码生成后的 **30天**后过期,但能够生成较多数量。临时二维码主要用于帐号绑定等不要求二维码永久保存的业务场景\n2. 永久二维码,是无过期"
  },
  {
    "path": "docs/src/4.x/basic-services/url.md",
    "chars": 265,
    "preview": "# 短网址服务\n\n主要使用场景: 开发者用于生成二维码的原链接(商品、支付二维码等)太长导致扫码速度和成功率下降,将原长链接通过此接口转成短链接再生成二维码将大大提升扫码速度和成功率。\n\n## 长链接转短链接\n\n```php\n$shortU"
  },
  {
    "path": "docs/src/4.x/client.md",
    "chars": 667,
    "preview": "# API 调用\n\n该方法将 API 交由开发者自行调用,微信有部分新的接口4.x并未全部兼容支持,可以使用该方案去自行封装接口:\n\n例如URL Link接口\n\n```php\n\n$response = $app->httpPostJson("
  },
  {
    "path": "docs/src/4.x/contributing.md",
    "chars": 1005,
    "preview": "# 贡献代码\n\n## 开发\n\n我们欢迎广大开发者贡献大家的智慧,让我们共同让它变得更完美.\n\n### 开始之前\n\n请严格遵循以下代码标准:\n\n> - [PSR-2](https://github.com/php-fig/fig-standa"
  },
  {
    "path": "docs/src/4.x/customize/access_token.md",
    "chars": 647,
    "preview": "# Access Token\n\n\n我们一个 SDK 应用在初始化以后,你可以在任何时机从应用中拿到该配置下的 Access Token 实例:\n\n```php\nuse EasyWeChat\\Factory;\n\n$config = [\n   "
  },
  {
    "path": "docs/src/4.x/customize/cache.md",
    "chars": 2958,
    "preview": "# 缓存\n\n\n本项目使用 [symfony/cache](https://github.com/symfony/cache) 来完成缓存工作,它支持基本目前所有的缓存引擎。\n\n在我们的 SDK 中的所有缓存默认使用文件缓存,缓存路径取决于 "
  },
  {
    "path": "docs/src/4.x/customize/replace-service.md",
    "chars": 205,
    "preview": "# 自定义服务模块\n\n由于使用了容器模式来组织各模块的实例,意味着你可以比较容易的替换掉已经有的服务,以公众号服务为例:\n\n```php\n\n<...>\n\n$app = Factory::officialAccount($config);\n\n"
  },
  {
    "path": "docs/src/4.x/index.md",
    "chars": 723,
    "preview": "> 👋🏼 您当前浏览的文档为 4.x,其它版本的文档请参考:[6.x](/6.x/)、[5.x](/5.x/)、[3.x](/3.x/)\n\n# EasyWeChat\n\nEasyWeChat 是一个开源的 [微信](http://www.we"
  },
  {
    "path": "docs/src/4.x/installation.md",
    "chars": 479,
    "preview": "# 安装\n\n\n## 环境要求\n\n> - PHP >= 7.0\n> - [PHP cURL 扩展](http://php.net/manual/en/book.curl.php)\n> - [PHP OpenSSL 扩展](http://php"
  },
  {
    "path": "docs/src/4.x/integration.md",
    "chars": 719,
    "preview": "# 在框架中使用\n\nEasyWeChat 是一个通用的 Composer 包,所以不需要对框架单独做修改,只要支持 Composer 就能直接使用,当然了,为了更方便的使用,我们收集了以下框架单独提供的拓展包:\n\n## Laravel\n\n>"
  },
  {
    "path": "docs/src/4.x/micro-merchant/certficates.md",
    "chars": 438,
    "preview": "# 获取平台证书\n调用获取平台证书接口之前,请前往微信支付商户平台升级API证书,升级后才可成功调用本接口。\n\n```php\n// 获取到证书后可以做缓存处理,无需每次重新获取\n$response = $app->certficates->"
  },
  {
    "path": "docs/src/4.x/micro-merchant/index.md",
    "chars": 1550,
    "preview": "# 小微商户\n\n你在阅读本文之前确认你已经仔细阅读了:[微信小微商户专属接口文档](https://pay.weixin.qq.com/wiki/doc/api/xiaowei.php?chapter=19_2)。\n\nPS: ⚠️ 因系统升"
  },
  {
    "path": "docs/src/4.x/micro-merchant/material.md",
    "chars": 492,
    "preview": "# 商户信息修改\n## 修改结算银行卡\n\n```php\n$response = $app->material->setSettlementCard([\n    // 'sub_mch_id' => '1230000109',\n    'ac"
  },
  {
    "path": "docs/src/4.x/micro-merchant/media.md",
    "chars": 121,
    "preview": "# 图片上传\n上传证件照片。支持 jpeg、jpg、bmp、png 格式,图片大小不超过2M。\n\n```php\n// $path string 图片路径\n$response = $app->media->upload($path);\n```"
  },
  {
    "path": "docs/src/4.x/micro-merchant/merchant-config.md",
    "chars": 698,
    "preview": "# 小微商户配置\n\n## 关注功能配置\n\n```php\n$response = $app->merchantConfig->setFollowConfig(string $subAppId, string $subscribeAppId, "
  },
  {
    "path": "docs/src/4.x/micro-merchant/submit-application.md",
    "chars": 577,
    "preview": "# 商户入驻\n## 申请入驻\n\n使用申请入驻接口提交你的小微商户资料。\n\n```php\n$result = $app->submitApplication([\n    'business_code' => '123456', // 业务申请"
  },
  {
    "path": "docs/src/4.x/micro-merchant/upgrade.md",
    "chars": 548,
    "preview": "# 商户升级\n## 提交升级申请单\n\n使用“提交升级申请单”接口为小微商户发起升级流程,根据商户实际情况可升级为个体户、企业、党政、机关及事业单位、其他组织。。\n\n```php\n$result = $app->upgrade([\n    '"
  },
  {
    "path": "docs/src/4.x/micro-merchant/withdraw.md",
    "chars": 242,
    "preview": "# 提现相关\n\n## 查询提现状态\n\n```php\n$response = $app->withdraw->queryWithdrawalStatus($date, $subMchId = '');\n```\n## 重新发起提现\n\n```ph"
  },
  {
    "path": "docs/src/4.x/mini-program/app_code.md",
    "chars": 2252,
    "preview": "# 小程序码\n\n## 获取小程序码\n\n### 接口A: 适用于需要的码数量较少的业务场景\n\nAPI:\n\n```\n$app->app_code->get(string $path, array $optional = []);\n```\n\n其中"
  },
  {
    "path": "docs/src/4.x/mini-program/auth.md",
    "chars": 90,
    "preview": "# 微信登录\n\n## 根据 jsCode 获取用户 session 信息\n\nAPI:\n\n```php\n$app->auth->session(string $code);\n```\n"
  },
  {
    "path": "docs/src/4.x/mini-program/customer_service.md",
    "chars": 85,
    "preview": "# 客服消息\n\n## 获取实例\n\n```php\n$service = $app->customer_service;\n```\n\n> 使用方法详看公众号-客服消息章节。\n\n"
  },
  {
    "path": "docs/src/4.x/mini-program/data_cube.md",
    "chars": 678,
    "preview": "# 数据统计与分析\n\n获取小程序概况趋势:\n\n```php\n$app->data_cube->summaryTrend('20170313', '20170313')\n```\n开始日期与结束日期的格式为 yyyymmdd。\n\n## API\n"
  },
  {
    "path": "docs/src/4.x/mini-program/decrypt.md",
    "chars": 135,
    "preview": "# 微信小程序消息解密\n\n## 比如获取电话等功能,信息是加密的,需要解密。\n\nAPI:\n\n```php\n$decryptedData = $app->encryptor->decryptData($session, $iv, $encry"
  },
  {
    "path": "docs/src/4.x/mini-program/express.md",
    "chars": 2068,
    "preview": "# 物流助手 电子面单\n\n## 获取支持的快递公司列表\n\n> https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/express/by-business"
  },
  {
    "path": "docs/src/4.x/mini-program/index.md",
    "chars": 459,
    "preview": "# 小程序\n\n\n```php\nuse EasyWeChat\\Factory;\n\n$config = [\n    'app_id' => 'wx3cf0f39249eb0exx',\n    'secret' => 'f1c242f4f28f7"
  },
  {
    "path": "docs/src/4.x/mini-program/nearby_poi.md",
    "chars": 2609,
    "preview": "# 附近的小程序\n\n> 微信文档:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/nearby-poi/nearbyPoi.add.html\n\n##"
  },
  {
    "path": "docs/src/4.x/mini-program/plugin.md",
    "chars": 652,
    "preview": "# 插件管理\n\n> 微信文档:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/plugin-management/pluginManager.app"
  },
  {
    "path": "docs/src/4.x/mini-program/soter.md",
    "chars": 644,
    "preview": "# 生物认证\n\n## 生物认证秘钥签名验证\n\n> https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/soter/soter.verifySignatu"
  },
  {
    "path": "docs/src/4.x/mini-program/subscribe_message.md",
    "chars": 1597,
    "preview": "# 订阅消息\n\n> 微信文档:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage."
  },
  {
    "path": "docs/src/4.x/mini-program/template_message.md",
    "chars": 653,
    "preview": "# 模板消息\n\n## 获取小程序模板库标题列表\n\n```\n$app->template_message->list($offset, $count);\n```\n\n## 获取模板库某个模板标题下关键词库\n\n```\n$app->template"
  },
  {
    "path": "docs/src/4.x/miscellaneous.md",
    "chars": 13,
    "preview": "# 其它\n\n\n### 其它"
  },
  {
    "path": "docs/src/4.x/official-account/accounts.md",
    "chars": 619,
    "preview": "# 多账号接入\n\n如果你想使用本项目接入多个公众号,在本程序中,您可以为每个帐号都设置一个 id,此 id 对应了该帐号的 appid、token 等信息。\n如下表\n\n| id   | appId                | secr"
  },
  {
    "path": "docs/src/4.x/official-account/base.md",
    "chars": 141,
    "preview": "# 基础接口\n\n## 清理接口调用次数\n\n> 此接口官方有每月调用限制,不可随意调用\n\n```php\n$app->base->clearQuota();\n```\n\n## 获取微信服务器 IP (或IP段)\n\n```php\n$app->bas"
  },
  {
    "path": "docs/src/4.x/official-account/broadcasting.md",
    "chars": 2594,
    "preview": "# 群发\n\n微信的群发消息接口有各种乱七八糟的注意事项及限制,具体请阅读微信官方文档。\n\n## 发送消息\n\n以下所有方法均有第二个参数 `$to` 用于指定接收对象:\n\n>  - 当 `$to` 为整型时为标签 id\n>  - 当 `$to"
  },
  {
    "path": "docs/src/4.x/official-account/card.md",
    "chars": 13335,
    "preview": "# 卡券\n\n-\n\n## 获取实例\n\n```php\n$card = $app->card;\n```\n\n## 通用功能\n\n### 获取卡券颜色\n\n```php\n$card->colors();\n```\n\n### 卡券开放类目查询\n\n```php"
  },
  {
    "path": "docs/src/4.x/official-account/comment.md",
    "chars": 737,
    "preview": "# 评论数据管理\n\n\n\n## 打开已群发文章评论\n\n```php\n$app->comment->open($msgId, $index = null);\n```\n\n## 关闭已群发文章评论\n\n```php\n$app->comment->cl"
  },
  {
    "path": "docs/src/4.x/official-account/configuration.md",
    "chars": 3832,
    "preview": "# 配置\n\n常用的配置参数会比较少,因为除非你有特别的定制,否则基本上默认值就可以了:\n\n```php\nuse EasyWeChat\\Factory;\n\n$config = [\n    'app_id' => 'wx3cf0f39249eb"
  },
  {
    "path": "docs/src/4.x/official-account/customer_service.md",
    "chars": 1943,
    "preview": "# 客服\n\n使用客服系统可以向用户发送消息以及群发消息,客服的管理等功能。\n\n## 客服管理\n\n### 获取所有客服\n\n```php\n$app->customer_service->list();\n```\n\n### 获取所有在线的客服\n\n`"
  },
  {
    "path": "docs/src/4.x/official-account/data_cube.md",
    "chars": 2505,
    "preview": "# 数据统计与分析\n\n通过数据接口,开发者可以获取与公众平台官网统计模块类似但更灵活的数据,还可根据需要进行高级处理。\n\n>\n> 1. 接口侧的公众号数据的数据库中仅存储了 **2014年12月1日之后**的数据,将查询不到在此之前的日期,"
  },
  {
    "path": "docs/src/4.x/official-account/events.md",
    "chars": 80,
    "preview": "# 事件\n\n\n\n更多请参考:[服务端](server.html)\n\n关于事件类型请参考微信官方文档:http://mp.weixin.qq.com/wiki/\n"
  },
  {
    "path": "docs/src/4.x/official-account/goods.md",
    "chars": 2781,
    "preview": "# 返佣商品\n\n> 微信文档:https://mp.weixin.qq.com/cgi-bin/announce?action=getannouncement&key=11533749572M9ODP&version=1&lang=zh_C"
  },
  {
    "path": "docs/src/4.x/official-account/index.md",
    "chars": 398,
    "preview": "## 公众号\n\n公众号的各模块相对比较统一,用法如下:\n\n\n```php\nuse EasyWeChat\\Factory;\n\n$config = [\n    'app_id' => 'wx3cf0f39249eb0exx',\n    'sec"
  },
  {
    "path": "docs/src/4.x/official-account/material.md",
    "chars": 4229,
    "preview": "# 素材管理\n\n在微信里的图片,音乐,视频等等都需要先上传到微信服务器作为素材才可以在消息中使用。\n\n### 上传图片\n\n> 注意:微信图片上传服务有敏感检测系统,图片内容如果含有敏感内容,如色情,商品推广,虚假信息等,上传可能失败。\n\n`"
  },
  {
    "path": "docs/src/4.x/official-account/menu.md",
    "chars": 1420,
    "preview": "# 自定义菜单\n\n## 读取(查询)已设置菜单\n\n\n```php\n$list = $app->menu->list();\n```\n\n## 获取当前菜单\n\n```php\n$current = $app->menu->current();\n``"
  },
  {
    "path": "docs/src/4.x/official-account/message-transfer.md",
    "chars": 375,
    "preview": "# 多客服消息转发\n\n多客服的消息转发绝对是超级的简单,转发的消息类型为 `transfer`:\n\n```php\n\nuse EasyWeChat\\Kernel\\Messages\\Transfer;\n\n// 转发收到的消息给客服\n$app->"
  },
  {
    "path": "docs/src/4.x/official-account/messages.md",
    "chars": 4883,
    "preview": "# 消息\n\n我把微信的 API 里的所有“消息”都按类型抽象出来了,也就是说,你不用区分它是回复消息还是主动推送消息,免去了你去手动拼装微信的 XML 以及乱七八糟命名不统一的 JSON 了。\n\n在阅读以下内容时请忽略是 **接收消息** "
  },
  {
    "path": "docs/src/4.x/official-account/oauth.md",
    "chars": 5250,
    "preview": "# 网页授权\n\n## 关于 OAuth2.0\n\nOAuth 是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是 2.0 版。\n\n<img src=\"https://user-images.gith"
  },
  {
    "path": "docs/src/4.x/official-account/poi.md",
    "chars": 2712,
    "preview": "# 门店\n\n## 创建门店\n\n用 POI 接口新建门店时所使用的图片 url 必须为微信自己域名的 url,因此需要先用上传图片接 口上传图片并获取 url,再创建门店。上传的图片限制文件大小限制 1MB,支持 JPG 格式,图片接口请参考"
  },
  {
    "path": "docs/src/4.x/official-account/reply.md",
    "chars": 63,
    "preview": "# 自动回复\n\n## 获取当前设置的回复规则\n\n```php\n$app->auto_reply->current();\n```"
  },
  {
    "path": "docs/src/4.x/official-account/semantic.md",
    "chars": 1529,
    "preview": "# 语义理解\n\n> 貌似此接口已经下线,调用无正确返回值\n\n+ `query($keyword, $categories, $optional = [])` 语义理解:\n\n  + `$keyword` 为关键字\n  + `$categori"
  },
  {
    "path": "docs/src/4.x/official-account/server.md",
    "chars": 5234,
    "preview": "# 服务端\n\n我们在入门小教程一节以服务端为例讲解了一个基本的消息的处理,这里就不再讲服务器验证的流程了,请直接参考前面的入门实例即可。\n\n服务端的作用呢,在整个微信开发中主要是负责 **[接收用户发送过来的消息](http://mp.we"
  },
  {
    "path": "docs/src/4.x/official-account/shake-around.md",
    "chars": 19325,
    "preview": "# 摇一摇周边\n\n\n摇一摇周边是微信在线下的全新功能, 为线下商户提供近距离连接用户的能力, 并支持线下商户向周边用户提供个性化营销、互动及信息推荐等服务。\n\n## 获取实例\n\n```php\n$shakearound = $app->sha"
  },
  {
    "path": "docs/src/4.x/official-account/store.md",
    "chars": 3400,
    "preview": "# 门店小程序\n\n## 拉取门店小程序类目\n\n```php\n$app->store->categories();\n```\n\n## 创建门店小程序\n\n> 说明:创建门店小程序提交后需要公众号管理员确认通过后才可进行审核。如果主管理员 24 小"
  },
  {
    "path": "docs/src/4.x/official-account/template_message.md",
    "chars": 1549,
    "preview": "# 模板消息\n\n模板消息仅用于公众号向用户发送重要的服务通知,只能用于符合其要求的服务场景中,如信用卡刷卡通知,商品购买成功通知等。不支持广告等营销类消息以及其它所有可能对用户造成骚扰的消息。\n\n## 修改账号所属行业\n\n```php\n$a"
  },
  {
    "path": "docs/src/4.x/official-account/tutorial.md",
    "chars": 3613,
    "preview": "# 快速开始\n\n在我们已经安装完成后,即可很快的开始使用它了,当然你还是有必要明白 PHP 基本知识,如命名空间等,我这里就不赘述了。\n\n我们以完成服务器端验证与接收响应用户发送的消息为例来演示,首先你有必要了解一下微信交互的运行流程:\n\n"
  },
  {
    "path": "docs/src/4.x/official-account/user-tag.md",
    "chars": 1366,
    "preview": "# 用户标签\n\n## 获取所有标签\n\n```php\n$app->user_tag->list();\n```\n\n示例:\n\n```php\n$tags = $app->user_tag->list();\n\n// {\n//     \"tags\": "
  },
  {
    "path": "docs/src/4.x/official-account/user.md",
    "chars": 2051,
    "preview": "# 用户\n\n用户信息的获取是微信开发中比较常用的一个功能了,以下所有的用户信息的获取与更新,都是**基于微信的 `openid` 的,并且是已关注当前账号的**,其它情况可能无法正常使用。\n\n## 获取用户信息\n\n获取单个:\n\n```php"
  },
  {
    "path": "docs/src/4.x/open-platform/authorizer-delegate.md",
    "chars": 1864,
    "preview": "# 代授权方实现业务\n\n> 授权方已经把公众号、小程序授权给你的开放平台第三方平台了,接下来的代授权方实现业务只需一行代码即可获得授权方实例。\n\n## 实例化\n\n```php\nuse EasyWeChat\\Factory;\n\n$config"
  },
  {
    "path": "docs/src/4.x/open-platform/index.md",
    "chars": 1265,
    "preview": "# 微信开放平台第三方平台\n\n此页涉及接口信息与说明请参见:[授权流程技术说明 - 官方文档](https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resour"
  },
  {
    "path": "docs/src/4.x/open-platform/server.md",
    "chars": 1490,
    "preview": "# 服务端\n\n## 第三方平台推送事件\n\n公众号第三方平台推送的有四个事件:\n\n> 如已经授权的公众号、小程序再次进行授权,而未修改已授权的权限的话,是没有相关事件推送的。\n\n​\t授权成功 `authorized`\n\n​\t授权更新 `upd"
  },
  {
    "path": "docs/src/4.x/open-work/index.md",
    "chars": 605,
    "preview": "# 企业微信第三方服务商\n\n## 实例化\n\n```php\n<?php\nuse EasyWeChat\\Factory;\n\n$config = [\n     'corp_id'              => '服务商的corpid',\n   "
  },
  {
    "path": "docs/src/4.x/open-work/provider.md",
    "chars": 1411,
    "preview": "# 服务商相关接口\n\n## 单点登录\n\n\n### 获取从第三方单点登录连接\n\n```php\n$app->provider->getLoginUrl(string $redirectUri = '', string $userType = '"
  },
  {
    "path": "docs/src/4.x/open-work/server.md",
    "chars": 1432,
    "preview": "# 服务端\n\n## 企业微信第三方回调协议\n\nSDK 默认会处理事件 `suite_ticket` ,并会缓存 `suite_ticket`\n\n> 需要注意的是:授权成功、变更授权、取消授权通知时间的响应必须在 1000ms 内完成,以保证"
  },
  {
    "path": "docs/src/4.x/open-work/service.md",
    "chars": 933,
    "preview": "# 第三方应用接口\n\n\n## 获取预授权码\n\n```php\n$app->corp->getPreAuthCode();\n```\n\n## 设置授权配置\n\n```php\n$app->corp->setSession(string $preAut"
  },
  {
    "path": "docs/src/4.x/open-work/work.md",
    "chars": 111,
    "preview": "# 企业\n\n\n### 获取授权企业的相关信息\n\n\n```php\n\n$work = $app->work('授权企业的corp_id','授权企业的永久授权码');\n\n```\n\n然后就可以像企业微信一样 获取相关的数据信息 "
  },
  {
    "path": "docs/src/4.x/overview.md",
    "chars": 2646,
    "preview": "# EasyWeChat\n\nEasyWeChat 是一个开源的 [微信](http://www.wechat.com) 非官方 SDK。\n\nEasyWeChat 的安装非常简单,因为它是一个标准的 [Composer](https://ge"
  },
  {
    "path": "docs/src/4.x/payment/bill.md",
    "chars": 417,
    "preview": "# 对账单\n\n## 下载对账单\n\n> 调用参数正确会返回一个 `EasyWeChat\\Kernel\\Http\\StreamResponse` 对象,否则会返回相应错误信息\n\nExample:\n\n```php\n$bill = $app->bi"
  },
  {
    "path": "docs/src/4.x/payment/contract.md",
    "chars": 663,
    "preview": "# 签约\n\n## 公众号签约\n\n> 参数 `appid`, `version`, `timestamp`, `sign` 可不用传入\n\n```php\n$result = $app->contract->web([\n    'mch_id' "
  },
  {
    "path": "docs/src/4.x/payment/index.md",
    "chars": 1502,
    "preview": "# 支付\n\n你在阅读本文之前确认你已经仔细阅读了:[微信支付 | 商户平台开发文档](https://pay.weixin.qq.com/wiki/doc/api/index.html)。\n\n## 配置\n\n配置在前面的例子中已经提到过了,支"
  },
  {
    "path": "docs/src/4.x/payment/jssdk.md",
    "chars": 2960,
    "preview": "# JSSDK\n\nJSSDK 模块用于生成调起微信支付以及共享收货地址的调用所需的配置参数。\n\n## 配置\n\n```php\nuse EasyWeChat\\Factory;\n\n$config = [\n    // 前面的appid什么的也得保"
  },
  {
    "path": "docs/src/4.x/payment/micropay.md",
    "chars": 505,
    "preview": "# 付款码支付\n\n## 配置\n\n> 请务必先熟悉流程:<https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=5_1>\n\n\n```php\n$result = $app->pa"
  },
  {
    "path": "docs/src/4.x/payment/notify.md",
    "chars": 2921,
    "preview": "# 通知\n\n## 支付结果通知\n\n在用户成功支付后,微信服务器会向该 **订单中设置的回调 URL** 发起一个 POST 请求,请求的内容为一个 XML。里面包含了所有的详细信息,具体请参考:[支付结果通知](https://pay.we"
  },
  {
    "path": "docs/src/4.x/payment/order.md",
    "chars": 2949,
    "preview": "# 订单\n\n## 统一下单\n\n没错,什么 H5 支付,公众号支付,扫码支付,支付中签约,全部都是用这个接口下单。\n\n> 参数 `appid`, `mch_id`, `nonce_str`, `sign`, `sign_type` 可不用传入"
  },
  {
    "path": "docs/src/4.x/payment/profit-sharing.md",
    "chars": 2785,
    "preview": "# 分账\n> 官方文档 https://pay.weixin.qq.com/wiki/doc/api/allocation.php?chapter=27_1&index=1\n\n```php\nuse EasyWeChat\\Factory;\n$"
  },
  {
    "path": "docs/src/4.x/payment/redpack.md",
    "chars": 2523,
    "preview": "# 红包\n\n\n在阅读本文之前确认你已经仔细阅读了:[微信支付 | 现金红包文档 ](https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_1)。\n\n#"
  },
  {
    "path": "docs/src/4.x/payment/refund.md",
    "chars": 1361,
    "preview": "# 退款\n\n## 申请退款\n\n当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,微信支付将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家帐号上。\n\n注意:\n\n> 1、交易"
  },
  {
    "path": "docs/src/4.x/payment/reverse.md",
    "chars": 249,
    "preview": "# 撤销订单\n\n目前只有 **刷卡支付** 有此功能。\n\n> 调用支付接口后请勿立即调用撤销订单API,建议支付后至少15s后再调用撤销订单接口。\n\n## 通过内部订单号撤销订单\n\n```php\n$app->reverse->byOutTr"
  },
  {
    "path": "docs/src/4.x/payment/scan-pay.md",
    "chars": 1906,
    "preview": "## 扫码支付\n\n### 模式一:先生成产品二维码,扫码下单后支付\n\n> 请务必先熟悉流程:<https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_4>\n\n#### 生成产品"
  },
  {
    "path": "docs/src/4.x/payment/security.md",
    "chars": 1190,
    "preview": "# 安全与风控\n\n> EasyWeChat 4.0.7+\n\n## 获取 RSA 公钥\n\n```php\n$result = $app->security->getPublicKey();\n\n// 存成文件\n\nfile_put_contents"
  },
  {
    "path": "docs/src/4.x/payment/transfer.md",
    "chars": 2223,
    "preview": "# 企业付款\n\n> EasyWeChat 4.0.7+\n\n该模块需要用到双向证书,请参考:https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=4_3\n\n## 企业"
  },
  {
    "path": "docs/src/4.x/sidebar.js",
    "chars": 7101,
    "preview": "exports = module.exports = [\n  {\n    text: '开始使用',\n    collapsible: true,\n    items: [\n      { text: '概述', link: '/4.x/o"
  },
  {
    "path": "docs/src/4.x/troubleshooting.md",
    "chars": 4991,
    "preview": "# 疑难解答\n\n在微信公众平台开发的道路上,遍布着各种大大小小的坑,有的人掉坑里,几经折腾又爬出来了,然后拍拍屁股走人。然而坑还在那里,还会继续有后来人掉进去……\n\n这,是我们不愿看到的。\n\n所以在这里,我们将陆续将微信开发中可能遇到的各种"
  },
  {
    "path": "docs/src/4.x/wework/agents.md",
    "chars": 432,
    "preview": "# 应用管理\n\n> 企业微信在 17 年 11 月对 API 进行了大量的改动,应用管理部分已经没啥用了\n\n应用管理是企业微信中比较特别的地方,因为它的使用是不基于应用的,或者说基于任何一个应用都能访问这些 API,所以在用法上是直接调用 "
  },
  {
    "path": "docs/src/4.x/wework/contacts.md",
    "chars": 2995,
    "preview": "# 通讯录\n\n```php\n$config = [\n    'corp_id' => 'xxxxxxxxxxxxxxxxx',\n    'secret'   => 'xxxxxxxxxx', // 通讯录的 secret\n    //..."
  },
  {
    "path": "docs/src/4.x/wework/external-contact.md",
    "chars": 4495,
    "preview": "# 外部联系人管理\n\n## 获取实例\n\n```php\n$config = [\n    'corp_id' => 'xxxxxxxxxxxxxxxxx',\n    'secret'   => 'xxxxxxxxxx',\n    ...\n];\n"
  },
  {
    "path": "docs/src/4.x/wework/group-robot.md",
    "chars": 2480,
    "preview": "# 群机器人\n\n## 使用说明\n\n使用前必须先在群组里面添加机器人,然后将 `Webhook 地址` 中的 `key` 取出来,作为示例中 `$groupKey` 的值。\n\n> Webhook 地址示例:<https://qyapi.wei"
  },
  {
    "path": "docs/src/4.x/wework/index.md",
    "chars": 481,
    "preview": "## 企业微信\n\n企业微信的使用与公众号以及其它几个应用的使用方式都是一致的,使用 `\\EasyWeChat\\Factory::work($config)` 来初始化:\n\n```php\n$config = [\n    'corp_id' ="
  },
  {
    "path": "docs/src/4.x/wework/invoice.md",
    "chars": 1391,
    "preview": "# 电子发票\n\n```php\n$config = [\n    'corp_id' => 'xxxxxxxxxxxxxxxxx',\n    'secret'   => 'xxxxxxxxxx',\n    //...\n];\n\n$app = Fa"
  },
  {
    "path": "docs/src/4.x/wework/media.md",
    "chars": 841,
    "preview": "# 临时素材\n\n它的使用是不基于应用的,或者说基于任何一个应用都能访问这些 API,所以在用法上是直接调用 work 实例的 `media` 属性:\n\n**上传的媒体文件限制:**\n\n所有文件size必须大于5个字节\n\n>  - 图片(im"
  },
  {
    "path": "docs/src/4.x/wework/menu.md",
    "chars": 647,
    "preview": "# 自定义菜单\n\n自定义菜单是指为单个应用设置自定义菜单功能,所以在使用时请注意调用正确的应用实例。\n\n```php\n$config = [\n    'corp_id' => 'xxxxxxxxxxxxxxxxx',\n    'secret"
  },
  {
    "path": "docs/src/4.x/wework/message.md",
    "chars": 565,
    "preview": "# 消息\n\n## 主动发送消息\n\n```php\nuse EasyWeChat\\Kernel\\Messages\\TextCard;\n\n\n// 获取 Messenger 实例\n$messenger = $app->messenger;\n\n// "
  },
  {
    "path": "docs/src/4.x/wework/oa.md",
    "chars": 861,
    "preview": "# OA\n\n```php\n$config = [\n    'corp_id' => 'xxxxxxxxxxxxxxxxx',\n    'secret'   => 'xxxxxxxxxx',\n    //...\n];\n\n$app = Fact"
  },
  {
    "path": "docs/src/4.x/wework/oauth.md",
    "chars": 821,
    "preview": "# OAuth\n\n> 此文档为企业微信内部应用开发的网页授权\n\n[企业微信官方文档](https://work.weixin.qq.com/api/doc#90000/90135/91020)\n\n创建实例:\n\n```php\n$config "
  },
  {
    "path": "docs/src/4.x/wework/server.md",
    "chars": 564,
    "preview": "## 服务端\n\n我们在企业微信应用开启接收消息的功能,将设置页面的 token 与 aes key 配置到 agents 下对应的应用内:\n\n```php\n$config = [\n    'corp_id' => 'xxxxxxxxxxxx"
  },
  {
    "path": "docs/src/5.x/basic-services/content_security.md",
    "chars": 1199,
    "preview": "# 内容安全接口\n\n## 文本安全内容检测\n\n用于校验一段文本是否含有违法内容。\n\n### 频率限制\n\n单个appid调用上限为2000次/分钟,1,000,000次/天\n\n### 调用示例\n\n```php\n// 传入要检测的文本内容,长度"
  },
  {
    "path": "docs/src/5.x/basic-services/jssdk.md",
    "chars": 1156,
    "preview": "# JSSDK\n\n微信 JSSDK 官方文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115\n\n## API\n\n#### 获取JSSDK的配置数组\n\n```"
  },
  {
    "path": "docs/src/5.x/basic-services/media.md",
    "chars": 1553,
    "preview": "# 临时素材\n\n上传的临时多媒体文件有格式和大小限制,如下:\n\n> - 图片(image): 2M,支持 `JPG` 格式\n> - 语音(voice):2M,播放长度不超过 `60s`,支持 `AMR\\MP3` 格式\n> - 视频(vide"
  },
  {
    "path": "docs/src/5.x/basic-services/qrcode.md",
    "chars": 1066,
    "preview": "# 二维码\n\n目前有 2 种类型的二维码:\n\n1. 临时二维码,是有过期时间的,最长可以设置为在二维码生成后的 **30天**后过期,但能够生成较多数量。临时二维码主要用于帐号绑定等不要求二维码永久保存的业务场景\n2. 永久二维码,是无过期"
  },
  {
    "path": "docs/src/5.x/basic-services/url.md",
    "chars": 265,
    "preview": "# 短网址服务\n\n主要使用场景: 开发者用于生成二维码的原链接(商品、支付二维码等)太长导致扫码速度和成功率下降,将原长链接通过此接口转成短链接再生成二维码将大大提升扫码速度和成功率。\n\n## 长链接转短链接\n\n```php\n$shortU"
  },
  {
    "path": "docs/src/5.x/contributing.md",
    "chars": 1005,
    "preview": "# 贡献代码\n\n## 开发\n\n我们欢迎广大开发者贡献大家的智慧,让我们共同让它变得更完美.\n\n### 开始之前\n\n请严格遵循以下代码标准:\n\n> - [PSR-2](https://github.com/php-fig/fig-standa"
  },
  {
    "path": "docs/src/5.x/customize/access_token.md",
    "chars": 647,
    "preview": "# Access Token\n\n\n我们一个 SDK 应用在初始化以后,你可以在任何时机从应用中拿到该配置下的 Access Token 实例:\n\n```php\nuse EasyWeChat\\Factory;\n\n$config = [\n   "
  },
  {
    "path": "docs/src/5.x/customize/cache.md",
    "chars": 2958,
    "preview": "# 缓存\n\n\n本项目使用 [symfony/cache](https://github.com/symfony/cache) 来完成缓存工作,它支持基本目前所有的缓存引擎。\n\n在我们的 SDK 中的所有缓存默认使用文件缓存,缓存路径取决于 "
  },
  {
    "path": "docs/src/5.x/customize/replace-service.md",
    "chars": 205,
    "preview": "# 自定义服务模块\n\n由于使用了容器模式来组织各模块的实例,意味着你可以比较容易的替换掉已经有的服务,以公众号服务为例:\n\n```php\n\n<...>\n\n$app = Factory::officialAccount($config);\n\n"
  },
  {
    "path": "docs/src/5.x/index.md",
    "chars": 723,
    "preview": "> 👋🏼 您当前浏览的文档为 5.x,其它版本的文档请参考:[6.x](/6.x/)、[4.x](/4.x/)、[3.x](/3.x/)\n\n# EasyWeChat\n\nEasyWeChat 是一个开源的 [微信](http://www.we"
  },
  {
    "path": "docs/src/5.x/installation.md",
    "chars": 479,
    "preview": "# 安装\n\n\n## 环境要求\n\n> - PHP >= 7.4\n> - [PHP cURL 扩展](http://php.net/manual/en/book.curl.php)\n> - [PHP OpenSSL 扩展](http://php"
  },
  {
    "path": "docs/src/5.x/integration.md",
    "chars": 718,
    "preview": "# 在框架中使用\n\nEasyWeChat 是一个通用的 Composer 包,所以不需要对框架单独做修改,只要支持 Composer 就能直接使用,当然了,为了更方便的使用,我们收集了以下框架单独提供的拓展包:\n\n## Laravel\n\n>"
  },
  {
    "path": "docs/src/5.x/micro-merchant/certficates.md",
    "chars": 438,
    "preview": "# 获取平台证书\n调用获取平台证书接口之前,请前往微信支付商户平台升级API证书,升级后才可成功调用本接口。\n\n```php\n// 获取到证书后可以做缓存处理,无需每次重新获取\n$response = $app->certficates->"
  },
  {
    "path": "docs/src/5.x/micro-merchant/index.md",
    "chars": 1382,
    "preview": "# 小微商户\n\n你在阅读本文之前确认你已经仔细阅读了:[微信小微商户专属接口文档](https://pay.weixin.qq.com/wiki/doc/api/xiaowei.php?chapter=19_2)。\n\n## 配置\n\n小微商户"
  },
  {
    "path": "docs/src/5.x/micro-merchant/material.md",
    "chars": 492,
    "preview": "# 商户信息修改\n## 修改结算银行卡\n\n```php\n$response = $app->material->setSettlementCard([\n    // 'sub_mch_id' => '1230000109',\n    'ac"
  },
  {
    "path": "docs/src/5.x/micro-merchant/media.md",
    "chars": 121,
    "preview": "# 图片上传\n上传证件照片。支持 jpeg、jpg、bmp、png 格式,图片大小不超过2M。\n\n```php\n// $path string 图片路径\n$response = $app->media->upload($path);\n```"
  },
  {
    "path": "docs/src/5.x/micro-merchant/merchant-config.md",
    "chars": 698,
    "preview": "# 小微商户配置\n\n## 关注功能配置\n\n```php\n$response = $app->merchantConfig->setFollowConfig(string $subAppId, string $subscribeAppId, "
  },
  {
    "path": "docs/src/5.x/micro-merchant/submit-application.md",
    "chars": 577,
    "preview": "# 商户入驻\n## 申请入驻\n\n使用申请入驻接口提交你的小微商户资料。\n\n```php\n$result = $app->submitApplication([\n    'business_code' => '123456', // 业务申请"
  },
  {
    "path": "docs/src/5.x/micro-merchant/upgrade.md",
    "chars": 548,
    "preview": "# 商户升级\n## 提交升级申请单\n\n使用“提交升级申请单”接口为小微商户发起升级流程,根据商户实际情况可升级为个体户、企业、党政、机关及事业单位、其他组织。。\n\n```php\n$result = $app->upgrade([\n    '"
  },
  {
    "path": "docs/src/5.x/micro-merchant/withdraw.md",
    "chars": 242,
    "preview": "# 提现相关\n\n## 查询提现状态\n\n```php\n$response = $app->withdraw->queryWithdrawalStatus($date, $subMchId = '');\n```\n## 重新发起提现\n\n```ph"
  },
  {
    "path": "docs/src/5.x/mini-program/activity_message.md",
    "chars": 6096,
    "preview": "# 动态消息\n\n小程序动态消息功能允许开发者创建可变更的消息内容,在特定条件下更新消息显示内容。\n\n## 获取实例\n\n```php\n$activityMessage = $app->activity_message;\n```\n\n## 基础功"
  },
  {
    "path": "docs/src/5.x/mini-program/app_code.md",
    "chars": 2252,
    "preview": "# 小程序码\n\n## 获取小程序码\n\n### 接口A: 适用于需要的码数量较少的业务场景\n\nAPI:\n\n```\n$app->app_code->get(string $path, array $optional = []);\n```\n\n其中"
  }
]

// ... and 343 more files (download for full content)

About this extraction

This page contains the full source code of the w7corp/easywechat GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 543 files (1.2 MB), approximately 415.1k tokens, and a symbol index with 1046 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!