123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449 |
- <?php
- namespace Yansongda\Pay\Gateways\Wechat;
- use Exception;
- use Yansongda\Pay\Events;
- use Yansongda\Pay\Exceptions\BusinessException;
- use Yansongda\Pay\Exceptions\GatewayException;
- use Yansongda\Pay\Exceptions\InvalidArgumentException;
- use Yansongda\Pay\Exceptions\InvalidSignException;
- use Yansongda\Pay\Gateways\Wechat;
- use Yansongda\Pay\Log;
- use Yansongda\Supports\Collection;
- use Yansongda\Supports\Config;
- use Yansongda\Supports\Str;
- use Yansongda\Supports\Traits\HasHttpRequest;
- /**
- * @author yansongda <me@yansongda.cn>
- *
- * @property string appid
- * @property string app_id
- * @property string miniapp_id
- * @property string sub_appid
- * @property string sub_app_id
- * @property string sub_miniapp_id
- * @property string mch_id
- * @property string sub_mch_id
- * @property string key
- * @property string return_url
- * @property string cert_client
- * @property string cert_key
- * @property array log
- * @property array http
- * @property string mode
- */
- class Support
- {
- use HasHttpRequest;
- /**
- * Wechat gateway.
- *
- * @var string
- */
- protected $baseUri;
- /**
- * Config.
- *
- * @var Config
- */
- protected $config;
- /**
- * Instance.
- *
- * @var Support
- */
- private static $instance;
- /**
- * Bootstrap.
- *
- * @author yansongda <me@yansongda.cn>
- */
- private function __construct(Config $config)
- {
- $this->baseUri = Wechat::URL[$config->get('mode', Wechat::MODE_NORMAL)];
- $this->config = $config;
- $this->setHttpOptions();
- }
- /**
- * __get.
- *
- * @author yansongda <me@yansongda.cn>
- *
- * @param $key
- *
- * @return mixed|Config|null
- */
- public function __get($key)
- {
- return $this->getConfig($key);
- }
- /**
- * create.
- *
- * @author yansongda <me@yansongda.cn>
- *
- * @throws GatewayException
- * @throws InvalidArgumentException
- * @throws InvalidSignException
- *
- * @return Support
- */
- public static function create(Config $config)
- {
- if ('cli' === php_sapi_name() || !(self::$instance instanceof self)) {
- self::$instance = new self($config);
- self::setDevKey();
- }
- return self::$instance;
- }
- /**
- * getInstance.
- *
- * @author yansongda <me@yansongda.cn>
- *
- * @throws InvalidArgumentException
- *
- * @return Support
- */
- public static function getInstance()
- {
- if (is_null(self::$instance)) {
- throw new InvalidArgumentException('You Should [Create] First Before Using');
- }
- return self::$instance;
- }
- /**
- * clear.
- *
- * @author yansongda <me@yansongda.cn>
- */
- public static function clear()
- {
- self::$instance = null;
- }
- /**
- * Request wechat api.
- *
- * @author yansongda <me@yansongda.cn>
- *
- * @param string $endpoint
- * @param array $data
- * @param bool $cert
- *
- * @throws GatewayException
- * @throws InvalidArgumentException
- * @throws InvalidSignException
- */
- public static function requestApi($endpoint, $data, $cert = false): Collection
- {
- Events::dispatch(new Events\ApiRequesting('Wechat', '', self::$instance->getBaseUri().$endpoint, $data));
- $result = self::$instance->post(
- $endpoint,
- self::toXml($data),
- $cert ? [
- 'cert' => self::$instance->cert_client,
- 'ssl_key' => self::$instance->cert_key,
- ] : []
- );
- $result = is_array($result) ? $result : self::fromXml($result);
- Events::dispatch(new Events\ApiRequested('Wechat', '', self::$instance->getBaseUri().$endpoint, $result));
- return self::processingApiResult($endpoint, $result);
- }
- /**
- * Filter payload.
- *
- * @author yansongda <me@yansongda.cn>
- *
- * @param array $payload
- * @param array|string $params
- * @param bool $preserve_notify_url
- *
- * @throws InvalidArgumentException
- */
- public static function filterPayload($payload, $params, $preserve_notify_url = false): array
- {
- $type = self::getTypeName($params['type'] ?? '');
- $payload = array_merge(
- $payload,
- is_array($params) ? $params : ['out_trade_no' => $params]
- );
- $payload['appid'] = self::$instance->getConfig($type, '');
- if (Wechat::MODE_SERVICE === self::$instance->getConfig('mode', Wechat::MODE_NORMAL)) {
- $payload['sub_appid'] = self::$instance->getConfig('sub_'.$type, '');
- }
- unset($payload['trade_type'], $payload['type']);
- if (!$preserve_notify_url) {
- unset($payload['notify_url']);
- }
- $payload['sign'] = self::generateSign($payload);
- return $payload;
- }
- /**
- * Generate wechat sign.
- *
- * @author yansongda <me@yansongda.cn>
- *
- * @param array $data
- *
- * @throws InvalidArgumentException
- */
- public static function generateSign($data): string
- {
- $key = self::$instance->key;
- if (is_null($key)) {
- throw new InvalidArgumentException('Missing Wechat Config -- [key]');
- }
- ksort($data);
- $string = md5(self::getSignContent($data).'&key='.$key);
- Log::debug('Wechat Generate Sign Before UPPER', [$data, $string]);
- return strtoupper($string);
- }
- /**
- * Generate sign content.
- *
- * @author yansongda <me@yansongda.cn>
- *
- * @param array $data
- */
- public static function getSignContent($data): string
- {
- $buff = '';
- foreach ($data as $k => $v) {
- $buff .= ('sign' != $k && '' != $v && !is_array($v)) ? $k.'='.$v.'&' : '';
- }
- Log::debug('Wechat Generate Sign Content Before Trim', [$data, $buff]);
- return trim($buff, '&');
- }
- /**
- * Decrypt refund contents.
- *
- * @author yansongda <me@yansongda.cn>
- *
- * @param string $contents
- */
- public static function decryptRefundContents($contents): string
- {
- return openssl_decrypt(
- base64_decode($contents),
- 'AES-256-ECB',
- md5(self::$instance->key),
- OPENSSL_RAW_DATA
- );
- }
- /**
- * Convert array to xml.
- *
- * @author yansongda <me@yansongda.cn>
- *
- * @param array $data
- *
- * @throws InvalidArgumentException
- */
- public static function toXml($data): string
- {
- if (!is_array($data) || count($data) <= 0) {
- throw new InvalidArgumentException('Convert To Xml Error! Invalid Array!');
- }
- $xml = '<xml>';
- foreach ($data as $key => $val) {
- $xml .= is_numeric($val) ? '<'.$key.'>'.$val.'</'.$key.'>' :
- '<'.$key.'><![CDATA['.$val.']]></'.$key.'>';
- }
- $xml .= '</xml>';
- return $xml;
- }
- /**
- * Convert xml to array.
- *
- * @author yansongda <me@yansongda.cn>
- *
- * @param string $xml
- *
- * @throws InvalidArgumentException
- */
- public static function fromXml($xml): array
- {
- if (!$xml) {
- throw new InvalidArgumentException('Convert To Array Error! Invalid Xml!');
- }
- libxml_disable_entity_loader(true);
- return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA), JSON_UNESCAPED_UNICODE), true);
- }
- /**
- * Get service config.
- *
- * @author yansongda <me@yansongda.cn>
- *
- * @param string|null $key
- * @param mixed|null $default
- *
- * @return mixed|null
- */
- public function getConfig($key = null, $default = null)
- {
- if (is_null($key)) {
- return $this->config->all();
- }
- if ($this->config->has($key)) {
- return $this->config[$key];
- }
- return $default;
- }
- /**
- * Get app id according to param type.
- *
- * @author yansongda <me@yansongda.cn>
- *
- * @param string $type
- */
- public static function getTypeName($type = ''): string
- {
- switch ($type) {
- case '':
- $type = 'app_id';
- break;
- case 'app':
- $type = 'appid';
- break;
- default:
- $type = $type.'_id';
- }
- return $type;
- }
- /**
- * Get Base Uri.
- *
- * @author yansongda <me@yansongda.cn>
- *
- * @return string
- */
- public function getBaseUri()
- {
- return $this->baseUri;
- }
- /**
- * processingApiResult.
- *
- * @author yansongda <me@yansongda.cn>
- *
- * @param $endpoint
- *
- * @throws GatewayException
- * @throws InvalidArgumentException
- * @throws InvalidSignException
- *
- * @return Collection
- */
- protected static function processingApiResult($endpoint, array $result)
- {
- if (!isset($result['return_code']) || 'SUCCESS' != $result['return_code']) {
- throw new GatewayException('Get Wechat API Error:'.($result['return_msg'] ?? $result['retmsg'] ?? ''), $result);
- }
- if (isset($result['result_code']) && 'SUCCESS' != $result['result_code']) {
- throw new BusinessException('Wechat Business Error: '.$result['err_code'].' - '.$result['err_code_des'], $result);
- }
- if ('pay/getsignkey' === $endpoint ||
- false !== strpos($endpoint, 'mmpaymkttransfers') ||
- self::generateSign($result) === $result['sign']) {
- return new Collection($result);
- }
- Events::dispatch(new Events\SignFailed('Wechat', '', $result));
- throw new InvalidSignException('Wechat Sign Verify FAILED', $result);
- }
- /**
- * setDevKey.
- *
- * @author yansongda <me@yansongda.cn>
- *
- * @throws GatewayException
- * @throws InvalidArgumentException
- * @throws InvalidSignException
- * @throws Exception
- *
- * @return Support
- */
- private static function setDevKey()
- {
- if (Wechat::MODE_DEV == self::$instance->mode) {
- $data = [
- 'mch_id' => self::$instance->mch_id,
- 'nonce_str' => Str::random(),
- ];
- $data['sign'] = self::generateSign($data);
- $result = self::requestApi('pay/getsignkey', $data);
- self::$instance->config->set('key', $result['sandbox_signkey']);
- }
- return self::$instance;
- }
- /**
- * Set Http options.
- *
- * @author yansongda <me@yansongda.cn>
- */
- private function setHttpOptions(): self
- {
- if ($this->config->has('http') && is_array($this->config->get('http'))) {
- $this->config->forget('http.base_uri');
- $this->httpOptions = $this->config->get('http');
- }
- return $this;
- }
- }
|