BasicWePay.php 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. <?php
  2. namespace app\extra\weMini;
  3. use GuzzleHttp\Client;
  4. use WeChat\Contracts\DataArray;
  5. use WeChat\Contracts\Tools;
  6. use WeChat\Exceptions\InvalidArgumentException;
  7. use WeChat\Exceptions\InvalidResponseException;
  8. use yzh52521\EasyHttp\Http;
  9. class BasicWePay
  10. {
  11. /**
  12. * 商户配置
  13. * @var DataArray
  14. */
  15. protected $config;
  16. /**
  17. * 当前请求数据
  18. * @var DataArray
  19. */
  20. protected $params;
  21. /**
  22. * 静态缓存
  23. * @var static
  24. */
  25. protected static $cache;
  26. /**
  27. * WeChat constructor.
  28. * @param array $options
  29. */
  30. public function __construct(array $options)
  31. {
  32. if (empty($options['appid'])) {
  33. throw new InvalidArgumentException("Missing Config -- [appid]");
  34. }
  35. if (empty($options['mch_id'])) {
  36. throw new InvalidArgumentException("Missing Config -- [mch_id]");
  37. }
  38. if (empty($options['mch_key'])) {
  39. throw new InvalidArgumentException("Missing Config -- [mch_key]");
  40. }
  41. if (!empty($options['cache_path'])) {
  42. Tools::$cache_path = $options['cache_path'];
  43. }
  44. $this->config = new DataArray($options);
  45. // 商户基础参数
  46. $this->params = new DataArray([
  47. 'appid' => $this->config->get('appid'),
  48. 'mch_id' => $this->config->get('mch_id'),
  49. 'nonce_str' => Tools::createNoncestr(),
  50. ]);
  51. // 商户参数支持
  52. if ($this->config->get('sub_appid')) {
  53. $this->params->set('sub_appid', $this->config->get('sub_appid'));
  54. }
  55. if ($this->config->get('sub_mch_id')) {
  56. $this->params->set('sub_mch_id', $this->config->get('sub_mch_id'));
  57. }
  58. }
  59. /**
  60. * 静态创建对象
  61. * @param array $config
  62. * @return static
  63. */
  64. public static function instance(array $config)
  65. {
  66. $key = md5(get_called_class() . serialize($config));
  67. print_r($key);
  68. if (isset(self::$cache[$key])) return self::$cache[$key];
  69. return self::$cache[$key] = new static($config);
  70. }
  71. /**
  72. * 获取微信支付通知
  73. * @param string|array $xml
  74. * @return array
  75. * @throws \WeChat\Exceptions\InvalidResponseException
  76. */
  77. public function getNotify($xml = '')
  78. {
  79. $data = is_array($xml) ? $xml : Tools::xml2arr(empty($xml) ? Tools::getRawInput() : $xml);
  80. if (isset($data['sign']) && $this->getPaySign($data) === $data['sign']) {
  81. return $data;
  82. }
  83. throw new InvalidResponseException('Invalid Notify.', '0');
  84. }
  85. /**
  86. * 获取微信支付通知回复内容
  87. * @return string
  88. */
  89. public function getNotifySuccessReply()
  90. {
  91. return Tools::arr2xml(['return_code' => 'SUCCESS', 'return_msg' => 'OK']);
  92. }
  93. /**
  94. * 生成支付签名
  95. * @param array $data 参与签名的数据
  96. * @param string $signType 参与签名的类型
  97. * @param string $buff 参与签名字符串前缀
  98. * @return string
  99. */
  100. public function getPaySign(array $data, $signType = 'MD5', $buff = '')
  101. {
  102. ksort($data);
  103. if (isset($data['sign'])) unset($data['sign']);
  104. foreach ($data as $k => $v) {
  105. if ('' === $v || null === $v) continue;
  106. $buff .= "{$k}={$v}&";
  107. }
  108. $buff .= ("key=" . $this->config->get('mch_key'));
  109. if (strtoupper($signType) === 'MD5') {
  110. return strtoupper(md5($buff));
  111. }
  112. return strtoupper(hash_hmac('SHA256', $buff, $this->config->get('mch_key')));
  113. }
  114. /**
  115. * 转换短链接
  116. * @param string $longUrl 需要转换的URL,签名用原串,传输需URLencode
  117. * @return array
  118. */
  119. public function shortUrl($longUrl)
  120. {
  121. $url = 'https://api.mch.weixin.qq.com/tools/shorturl';
  122. return $this->callPostApi($url, ['long_url' => $longUrl]);
  123. }
  124. /**
  125. * 数组直接转xml数据输出
  126. * @param array $data
  127. * @param bool $isReturn
  128. * @return string|void
  129. */
  130. public function toXml(array $data, $isReturn = false)
  131. {
  132. $xml = Tools::arr2xml($data);
  133. if ($isReturn) return $xml;
  134. echo $xml;
  135. }
  136. /**
  137. * 以 Post 请求接口
  138. * @param string $url 请求
  139. * @param array $data 接口参数
  140. * @param bool $isCert 是否需要使用双向证书
  141. * @param string $signType 数据签名类型 MD5|SHA256
  142. * @param bool $needSignType 是否需要传签名类型参数
  143. * @param bool $needNonceStr
  144. * @return array
  145. */
  146. protected function callPostApi($url, array $data, $isCert = false, $signType = 'HMAC-SHA256', $needSignType = true, $needNonceStr = true)
  147. {
  148. $option = [];
  149. if ($isCert) {
  150. $option['ssl_p12'] = $this->config->get('ssl_p12');
  151. $option['ssl_cer'] = $this->config->get('ssl_cer');
  152. $option['ssl_key'] = $this->config->get('ssl_key');
  153. if (is_string($option['ssl_p12']) && file_exists($option['ssl_p12'])) {
  154. $content = file_get_contents($option['ssl_p12']);
  155. if (openssl_pkcs12_read($content, $certs, $this->config->get('mch_id'))) {
  156. // $option['ssl_key'] = Tools::pushFile(md5($certs['pkey']) . '.pem', $certs['pkey']);
  157. // $option['ssl_cer'] = Tools::pushFile(md5($certs['cert']) . '.pem', $certs['cert']);
  158. } else return ['code' => 0 ,"msg" => "P12 certificate does not match MCH_ID --- ssl_p12"];
  159. }
  160. if (empty($option['ssl_cer']) || !file_exists($option['ssl_cer'])) {
  161. return ['code' => 0 ,"msg" => "Missing Config -- ssl_cer"];
  162. }
  163. if (empty($option['ssl_key']) || !file_exists($option['ssl_key'])) {
  164. return ['code' => 0 ,"msg" => "Missing Config -- ssl_key"];
  165. }
  166. }
  167. $params = $this->params->merge($data);
  168. if (!$needNonceStr) unset($params['nonce_str']);
  169. if ($needSignType) $params['sign_type'] = strtoupper($signType);
  170. $params['sign'] = $this->getPaySign($params, $signType);
  171. $client = new Client();
  172. $resp = $client->post($url,[
  173. "header" => $client,
  174. 'body' => Tools::arr2xml($params)
  175. ]);
  176. $result = Tools::xml2arr($resp->getBody());
  177. return $result;
  178. // if ($result['return_code'] !== 'SUCCESS') {
  179. // return $result;
  180. // }
  181. }
  182. }