123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452 |
- <?php
- namespace Yansongda\Pay\Gateways\Alipay;
- use Exception;
- use Yansongda\Pay\Events;
- use Yansongda\Pay\Exceptions\GatewayException;
- use Yansongda\Pay\Exceptions\InvalidArgumentException;
- use Yansongda\Pay\Exceptions\InvalidConfigException;
- use Yansongda\Pay\Exceptions\InvalidSignException;
- use Yansongda\Pay\Gateways\Alipay;
- use Yansongda\Pay\Log;
- use Yansongda\Supports\Arr;
- use Yansongda\Supports\Collection;
- use Yansongda\Supports\Config;
- use Yansongda\Supports\Str;
- use Yansongda\Supports\Traits\HasHttpRequest;
- /**
- * @author yansongda <me@yansongda.cn>
- *
- * @property string app_id alipay app_id
- * @property string ali_public_key
- * @property string private_key
- * @property array http http options
- * @property string mode current mode
- * @property array log log options
- * @property string pid ali pid
- */
- class Support
- {
- use HasHttpRequest;
- /**
- * Alipay 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 = Alipay::URL[$config->get('mode', Alipay::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>
- *
- * @return Support
- */
- public static function create(Config $config)
- {
- if ('cli' === php_sapi_name() || !(self::$instance instanceof self)) {
- self::$instance = new self($config);
- }
- 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 function clear()
- {
- self::$instance = null;
- }
- /**
- * Get Alipay API result.
- *
- * @author yansongda <me@yansongda.cn>
- *
- * @throws GatewayException
- * @throws InvalidConfigException
- * @throws InvalidSignException
- */
- public static function requestApi(array $data): Collection
- {
- Events::dispatch(new Events\ApiRequesting('Alipay', '', self::$instance->getBaseUri(), $data));
- $data = array_filter($data, function ($value) {
- return ('' == $value || is_null($value)) ? false : true;
- });
- $result = json_decode(self::$instance->post('', $data), true);
- Events::dispatch(new Events\ApiRequested('Alipay', '', self::$instance->getBaseUri(), $result));
- return self::processingApiResult($data, $result);
- }
- /**
- * Generate sign.
- *
- * @author yansongda <me@yansongda.cn>
- *
- * @throws InvalidConfigException
- */
- public static function generateSign(array $params): string
- {
- $privateKey = self::$instance->private_key;
- if (is_null($privateKey)) {
- throw new InvalidConfigException('Missing Alipay Config -- [private_key]');
- }
- if (Str::endsWith($privateKey, '.pem')) {
- $privateKey = openssl_pkey_get_private(
- Str::startsWith($privateKey, 'file://') ? $privateKey : 'file://'.$privateKey
- );
- } else {
- $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n".
- wordwrap($privateKey, 64, "\n", true).
- "\n-----END RSA PRIVATE KEY-----";
- }
- openssl_sign(self::getSignContent($params), $sign, $privateKey, OPENSSL_ALGO_SHA256);
- $sign = base64_encode($sign);
- Log::debug('Alipay Generate Sign', [$params, $sign]);
- if (is_resource($privateKey)) {
- openssl_free_key($privateKey);
- }
- return $sign;
- }
- /**
- * Verify sign.
- *
- * @author yansongda <me@yansonga.cn>
- *
- * @param bool $sync
- * @param string|null $sign
- *
- * @throws InvalidConfigException
- */
- public static function verifySign(array $data, $sync = false, $sign = null): bool
- {
- $publicKey = self::$instance->ali_public_key;
- if (is_null($publicKey)) {
- throw new InvalidConfigException('Missing Alipay Config -- [ali_public_key]');
- }
- if (Str::endsWith($publicKey, '.crt')) {
- $publicKey = file_get_contents($publicKey);
- } elseif (Str::endsWith($publicKey, '.pem')) {
- $publicKey = openssl_pkey_get_public(
- Str::startsWith($publicKey, 'file://') ? $publicKey : 'file://'.$publicKey
- );
- } else {
- $publicKey = "-----BEGIN PUBLIC KEY-----\n".
- wordwrap($publicKey, 64, "\n", true).
- "\n-----END PUBLIC KEY-----";
- }
- $sign = $sign ?? $data['sign'];
- $toVerify = $sync ? json_encode($data, JSON_UNESCAPED_UNICODE) : self::getSignContent($data, true);
- $isVerify = 1 === openssl_verify($toVerify, base64_decode($sign), $publicKey, OPENSSL_ALGO_SHA256);
- if (is_resource($publicKey)) {
- openssl_free_key($publicKey);
- }
- return $isVerify;
- }
- /**
- * Get signContent that is to be signed.
- *
- * @author yansongda <me@yansongda.cn>
- *
- * @param bool $verify
- */
- public static function getSignContent(array $data, $verify = false): string
- {
- ksort($data);
- $stringToBeSigned = '';
- foreach ($data as $k => $v) {
- if ($verify && 'sign' != $k && 'sign_type' != $k) {
- $stringToBeSigned .= $k.'='.$v.'&';
- }
- if (!$verify && '' !== $v && !is_null($v) && 'sign' != $k && '@' != substr($v, 0, 1)) {
- $stringToBeSigned .= $k.'='.$v.'&';
- }
- }
- Log::debug('Alipay Generate Sign Content Before Trim', [$data, $stringToBeSigned]);
- return trim($stringToBeSigned, '&');
- }
- /**
- * Convert encoding.
- *
- * @author yansongda <me@yansonga.cn>
- *
- * @param string|array $data
- * @param string $to
- * @param string $from
- */
- public static function encoding($data, $to = 'utf-8', $from = 'gb2312'): array
- {
- return Arr::encoding((array) $data, $to, $from);
- }
- /**
- * 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 Base Uri.
- *
- * @author yansongda <me@yansongda.cn>
- *
- * @return string
- */
- public function getBaseUri()
- {
- return $this->baseUri;
- }
- /**
- * 生成应用证书SN.
- *
- * @author 大冰 https://sbing.vip/archives/2019-new-alipay-php-docking.html
- *
- * @param $certPath
- *
- * @throws /Exception
- */
- public static function getCertSN($certPath): string
- {
- if (!is_file($certPath)) {
- throw new Exception('unknown certPath -- [getCertSN]');
- }
- $x509data = file_get_contents($certPath);
- if (false === $x509data) {
- throw new Exception('Alipay CertSN Error -- [getCertSN]');
- }
- openssl_x509_read($x509data);
- $certdata = openssl_x509_parse($x509data);
- if (empty($certdata)) {
- throw new Exception('Alipay openssl_x509_parse Error -- [getCertSN]');
- }
- $issuer_arr = [];
- foreach ($certdata['issuer'] as $key => $val) {
- $issuer_arr[] = $key.'='.$val;
- }
- $issuer = implode(',', array_reverse($issuer_arr));
- Log::debug('getCertSN:', [$certPath, $issuer, $certdata['serialNumber']]);
- return md5($issuer.$certdata['serialNumber']);
- }
- /**
- * 生成支付宝根证书SN.
- *
- * @author 大冰 https://sbing.vip/archives/2019-new-alipay-php-docking.html
- *
- * @param $certPath
- *
- * @return string
- *
- * @throws /Exception
- */
- public static function getRootCertSN($certPath)
- {
- if (!is_file($certPath)) {
- throw new Exception('unknown certPath -- [getRootCertSN]');
- }
- $x509data = file_get_contents($certPath);
- if (false === $x509data) {
- throw new Exception('Alipay CertSN Error -- [getRootCertSN]');
- }
- $kCertificateEnd = '-----END CERTIFICATE-----';
- $certStrList = explode($kCertificateEnd, $x509data);
- $md5_arr = [];
- foreach ($certStrList as $one) {
- if (!empty(trim($one))) {
- $_x509data = $one.$kCertificateEnd;
- openssl_x509_read($_x509data);
- $_certdata = openssl_x509_parse($_x509data);
- if (in_array($_certdata['signatureTypeSN'], ['RSA-SHA256', 'RSA-SHA1'])) {
- $issuer_arr = [];
- foreach ($_certdata['issuer'] as $key => $val) {
- $issuer_arr[] = $key.'='.$val;
- }
- $_issuer = implode(',', array_reverse($issuer_arr));
- if (0 === strpos($_certdata['serialNumber'], '0x')) {
- $serialNumber = self::bchexdec($_certdata['serialNumber']);
- } else {
- $serialNumber = $_certdata['serialNumber'];
- }
- $md5_arr[] = md5($_issuer.$serialNumber);
- Log::debug('getRootCertSN Sub:', [$certPath, $_issuer, $serialNumber]);
- }
- }
- }
- return implode('_', $md5_arr);
- }
- /**
- * processingApiResult.
- *
- * @author yansongda <me@yansongda.cn>
- *
- * @param $data
- * @param $result
- *
- * @throws GatewayException
- * @throws InvalidConfigException
- * @throws InvalidSignException
- */
- protected static function processingApiResult($data, $result): Collection
- {
- $method = str_replace('.', '_', $data['method']).'_response';
- if (!isset($result['sign']) || '10000' != $result[$method]['code']) {
- throw new GatewayException('Get Alipay API Error:'.$result[$method]['msg'].(isset($result[$method]['sub_code']) ? (' - '.$result[$method]['sub_code']) : ''), $result);
- }
- if (self::verifySign($result[$method], true, $result['sign'])) {
- return new Collection($result[$method]);
- }
- Events::dispatch(new Events\SignFailed('Alipay', '', $result));
- throw new InvalidSignException('Alipay Sign Verify FAILED', $result);
- }
- /**
- * Set Http options.
- *
- * @author yansongda <me@yansongda.cn>
- */
- protected 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;
- }
- /**
- * 0x转高精度数字.
- *
- * @author 大冰 https://sbing.vip/archives/2019-new-alipay-php-docking.html
- *
- * @param $hex
- *
- * @return int|string
- */
- private static function bchexdec($hex)
- {
- $dec = 0;
- $len = strlen($hex);
- for ($i = 1; $i <= $len; ++$i) {
- if (ctype_xdigit($hex[$i - 1])) {
- $dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));
- }
- }
- return str_replace('.00', '', $dec);
- }
- }
|