BasicWePay.php 7.0 KB

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