国内流行的内容管理系统(CMS)多端全媒体解决方案 https://www.dedebiz.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

184 lines
6.4KB

  1. <?php
  2. namespace WeChat\Contracts;
  3. if (!defined('DEDEINC')) exit ('dedebiz');
  4. use WeChat\Exceptions\InvalidArgumentException;
  5. use WeChat\Exceptions\InvalidResponseException;
  6. /**
  7. * 微信支付基础类
  8. * Class BasicPay
  9. * @package WeChat\Contracts
  10. */
  11. class BasicWePay
  12. {
  13. /**
  14. * 商户配置
  15. * @var DataArray
  16. */
  17. protected $config;
  18. /**
  19. * 当前请求数据
  20. * @var DataArray
  21. */
  22. protected $params;
  23. /**
  24. * 静态缓存
  25. * @var static
  26. */
  27. protected static $cache;
  28. /**
  29. * WeChat constructor.
  30. * @param array $options
  31. */
  32. public function __construct(array $options)
  33. {
  34. if (empty($options['appid'])) {
  35. throw new InvalidArgumentException("Missing Config -- [appid]");
  36. }
  37. if (empty($options['mch_id'])) {
  38. throw new InvalidArgumentException("Missing Config -- [mch_id]");
  39. }
  40. if (empty($options['mch_key'])) {
  41. throw new InvalidArgumentException("Missing Config -- [mch_key]");
  42. }
  43. if (!empty($options['cache_path'])) {
  44. Tools::$cache_path = $options['cache_path'];
  45. }
  46. $this->config = new DataArray($options);
  47. //商户基础参数
  48. $this->params = new DataArray([
  49. 'appid' => $this->config->get('appid'),
  50. 'mch_id' => $this->config->get('mch_id'),
  51. 'nonce_str' => Tools::createNoncestr(),
  52. ]);
  53. //商户参数支持
  54. if ($this->config->get('sub_appid')) {
  55. $this->params->set('sub_appid', $this->config->get('sub_appid'));
  56. }
  57. if ($this->config->get('sub_mch_id')) {
  58. $this->params->set('sub_mch_id', $this->config->get('sub_mch_id'));
  59. }
  60. }
  61. /**
  62. * 静态创建对象
  63. * @param array $config
  64. * @return static
  65. */
  66. public static function instance(array $config)
  67. {
  68. $key = md5(get_called_class().serialize($config));
  69. if (isset(self::$cache[$key])) return self::$cache[$key];
  70. return self::$cache[$key] = new static($config);
  71. }
  72. /**
  73. * 获取微信支付通知
  74. * @return array
  75. * @throws \WeChat\Exceptions\InvalidResponseException
  76. */
  77. public function getNotify()
  78. {
  79. $data = Tools::xml2arr(file_get_contents('php://input'));
  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. * @throws \WeChat\Exceptions\InvalidResponseException
  119. * @throws \WeChat\Exceptions\LocalCacheException
  120. */
  121. public function shortUrl($longUrl)
  122. {
  123. $url = 'https://api.mch.weixin.qq.com/tools/shorturl';
  124. return $this->callPostApi($url, ['long_url' => $longUrl]);
  125. }
  126. /**
  127. * 数组直接转xml数据输出
  128. * @param array $data
  129. * @param bool $isReturn
  130. * @return string
  131. */
  132. public function toXml(array $data, $isReturn = false)
  133. {
  134. $xml = Tools::arr2xml($data);
  135. if ($isReturn) {
  136. return $xml;
  137. }
  138. echo $xml;
  139. }
  140. /**
  141. * 以 Post 请求接口
  142. * @param string $url 请求
  143. * @param array $data 接口参数
  144. * @param bool $isCert 是否需要使用双向证书
  145. * @param string $signType 数据签名类型 MD5|SHA256
  146. * @param bool $needSignType 是否需要传签名类型参数
  147. * @param bool $needNonceStr
  148. * @return array
  149. * @throws \WeChat\Exceptions\InvalidResponseException
  150. * @throws \WeChat\Exceptions\LocalCacheException
  151. */
  152. protected function callPostApi($url, array $data, $isCert = false, $signType = 'HMAC-SHA256', $needSignType = true, $needNonceStr = true)
  153. {
  154. $option = [];
  155. if ($isCert) {
  156. $option['ssl_p12'] = $this->config->get('ssl_p12');
  157. $option['ssl_cer'] = $this->config->get('ssl_cer');
  158. $option['ssl_key'] = $this->config->get('ssl_key');
  159. if (is_string($option['ssl_p12']) && file_exists($option['ssl_p12'])) {
  160. $content = file_get_contents($option['ssl_p12']);
  161. if (openssl_pkcs12_read($content, $certs, $this->config->get('mch_id'))) {
  162. $option['ssl_key'] = Tools::pushFile(md5($certs['pkey']).'.pem', $certs['pkey']);
  163. $option['ssl_cer'] = Tools::pushFile(md5($certs['cert']).'.pem', $certs['cert']);
  164. } else throw new InvalidArgumentException("P12 certificate does not match MCH_ID --- ssl_p12");
  165. }
  166. if (empty($option['ssl_cer']) || !file_exists($option['ssl_cer'])) {
  167. throw new InvalidArgumentException("Missing Config -- ssl_cer", '0');
  168. }
  169. if (empty($option['ssl_key']) || !file_exists($option['ssl_key'])) {
  170. throw new InvalidArgumentException("Missing Config -- ssl_key", '0');
  171. }
  172. }
  173. $params = $this->params->merge($data);
  174. if (!$needNonceStr) unset($params['nonce_str']);
  175. if ($needSignType) $params['sign_type'] = strtoupper($signType);
  176. $params['sign'] = $this->getPaySign($params, $signType);
  177. $result = Tools::xml2arr(Tools::post($url, Tools::arr2xml($params), $option));
  178. if ($result['return_code'] !== 'SUCCESS') {
  179. throw new InvalidResponseException($result['return_msg'], '0');
  180. }
  181. return $result;
  182. }
  183. }
  184. ?>