Baidu.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. <?php
  2. namespace Ycpay;
  3. class Baidu implements PayInterface
  4. {
  5. private $appKey;
  6. private $payappKey;
  7. private $dealId;
  8. private $rsaPriKeyStr;
  9. private $signFieldsRange = 1;
  10. private $rsaPubKeyStr;
  11. private $refundNotifyUrl = '';
  12. private $notifyUrl = '';
  13. private $appid;
  14. private $applyOrderRefundUrl = 'https://openapi.baidu.com/rest/2.0/smartapp/pay/paymentservice/applyOrderRefund';
  15. protected $findByTpOrderIdUrl = 'https://openapi.baidu.com/rest/2.0/smartapp/pay/paymentservice/findByTpOrderId';
  16. protected $sendMsgUrl = 'https://openapi.baidu.com/rest/2.0/smartapp/template/message/subscribe/send?access_token=';
  17. private $appSecret;
  18. private $isSkipAudit;
  19. private $orderParam;
  20. private $notifyOrder;
  21. public static function init($config)
  22. {
  23. if (empty($config['appid'])) {
  24. throw new \Exception('not empty appid');
  25. }
  26. if (empty($config['appkey'])) {
  27. throw new \Exception('not empty appkey');
  28. }
  29. $class = new self();
  30. $class->appid = $config['appid'];
  31. $class->appKey = $config['appkey'];
  32. $class->payappKey = isset($config['payappKey']) ? $config['payappKey'] : '';
  33. $class->isSkipAudit = isset($config['isSkipAudit']) ? $config['isSkipAudit'] : 0;
  34. $class->dealId = isset($config['dealId']) ? $config['dealId'] : '';
  35. $class->rsaPriKeyStr = isset($config['rsaPriKeyStr']) ? $config['rsaPriKeyStr'] : '';
  36. $class->rsaPubKeyStr = isset($config['rsaPubKeyStr']) ? $config['rsaPubKeyStr'] : '';
  37. $class->appSecret = isset($config['appSecret']) ? $config['appSecret'] : '';
  38. $class->refundNotifyUrl = isset($config['refundNotifyUrl']) ? $config['refundNotifyUrl'] : '';
  39. $class->notifyUrl = isset($config['notifyUrl']) ? $config['notifyUrl'] : '';
  40. return $class;
  41. }
  42. public function getParam()
  43. {
  44. return $this->orderParam;
  45. }
  46. /**
  47. * 获取异步订单信息
  48. */
  49. public function getNotifyOrder()
  50. {
  51. $this->notifyOrder = $_POST;
  52. return $this->notifyOrder;
  53. }
  54. /**
  55. * 设置订单号 金额 描述
  56. * @param string $rder_no 平台订单号
  57. * @param int $money 订单金额
  58. * @param string $title 描述
  59. *
  60. */
  61. public function set($order_no, $money, $title = '')
  62. {
  63. $this->orderParam['totalAmount'] = $money;
  64. $this->orderParam['tpOrderId'] = $order_no;
  65. $this->orderParam['dealId'] = $this->dealId;
  66. $this->orderParam['appKey'] = $this->payappKey;
  67. if ($this->notifyUrl) {
  68. $this->orderParam['notifyUrl'] = $this->notifyUrl;
  69. }
  70. $sign = self::sign($this->orderParam, $this->rsaPriKeyStr);
  71. $this->orderParam['dealTitle'] = $title;
  72. $this->orderParam['rsaSign'] = $sign;
  73. $this->orderParam['signFieldsRange'] = $this->signFieldsRange;
  74. return $this;
  75. }
  76. /**
  77. * 获取token
  78. */
  79. public function getToken()
  80. {
  81. $url = "https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials&client_id=" . $this->appKey . "&client_secret=" . $this->appSecret . "&scope=smartapp_snsapi_base";
  82. return json_decode($this->curl_get($url), true);
  83. }
  84. /**
  85. * 获取openid
  86. * @param string $code
  87. * @return array 成功返回数组 失败为空
  88. */
  89. public function getOpenid($code)
  90. {
  91. $url = "https://spapi.baidu.com/oauth/jscode2sessionkey?code=" . $code . "&client_id=" . $this->appKey . "&sk=" . $this->appSecret;
  92. return json_decode($this->curl_get($url), true);
  93. }
  94. /**
  95. * @desc 异步回调
  96. * @return bool true 验签通过|false 验签不通过
  97. */
  98. public function notifyCheck()
  99. {
  100. return self::checkSign($this->getNotifyOrder(), $this->rsaPubKeyStr);
  101. }
  102. /**
  103. * 申请退款
  104. *
  105. */
  106. public function applyOrderRefund($order)
  107. {
  108. $data = [
  109. 'access_token' => $order['access_token'],
  110. 'bizRefundBatchId' => time(),
  111. 'isSkipAudit' => $this->isSkipAudit,
  112. 'orderId' => $order['orderId'],
  113. 'refundReason' => $order['refundReason'],
  114. 'refundType' => $order['refundType'],
  115. 'tpOrderId' => $order['tpOrderId'],
  116. 'userId' => $order['userId'],
  117. 'pmAppKey' => $this->payappKey,
  118. ];
  119. if ($this->refundNotifyUrl) {
  120. $this->data['refundNotifyUrl'] = $this->refundNotifyUrl;
  121. }
  122. return json_decode($this->curl_post($this->applyOrderRefundUrl, $data), true);
  123. }
  124. /**
  125. * 订单查询
  126. * @param array order 参数组合
  127. * @return array 订单信息
  128. */
  129. public function findOrder($order)
  130. {
  131. if (empty($order)) {
  132. return false;
  133. }
  134. $string = "?access_token=" . $order['access_token'] . "&tpOrderId=" . $order['tpOrderId'] . "&pmAppKey=" . $this->payappKey;
  135. return json_decode($this->curl_get($this->findByTpOrderIdUrl . $string), true);
  136. }
  137. /**
  138. * 发送模版消息
  139. *
  140. * @param [type] $data
  141. * @param [type] $token
  142. * @return void
  143. */
  144. public function sendMsg($data, $token)
  145. {
  146. return json_decode($this->curl_post($this->sendMsgUrl . $token, http_build_query($data)), true);
  147. }
  148. protected static function curl_get($url)
  149. {
  150. $headerArr = array("Content-type:application/x-www-form-urlencoded");
  151. $curl = curl_init();
  152. curl_setopt($curl, CURLOPT_URL, $url);
  153. curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
  154. curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
  155. curl_setopt($curl, CURLOPT_HTTPHEADER, $headerArr);
  156. curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
  157. $output = curl_exec($curl);
  158. curl_close($curl);
  159. return $output;
  160. }
  161. /**
  162. * @desc post 用于退款
  163. */
  164. protected static function curl_post($url, $data = array())
  165. {
  166. $ch = curl_init();
  167. curl_setopt($ch, CURLOPT_URL, $url);
  168. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  169. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  170. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
  171. curl_setopt($ch, CURLOPT_POST, 1);
  172. curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
  173. $output = curl_exec($ch);
  174. curl_close($ch);
  175. return $output;
  176. }
  177. /**
  178. * 解密手机号
  179. *
  180. * @param string $session_key 前端传递的session_key
  181. * @param string $iv 前端传递的iv
  182. * @param string $ciphertext 前端传递的ciphertext
  183. */
  184. public function decryptPhone($session_key, $iv, $ciphertext)
  185. {
  186. $plaintext = self::decrypt($ciphertext, $iv, $this->appKey, $session_key);
  187. return json_decode($plaintext, true);
  188. }
  189. /**
  190. *
  191. * @param string $ciphertext 待解密数据,返回的内容中的data字段
  192. * @param string $iv 加密向量,返回的内容中的iv字段
  193. * @param string $app_key 创建小程序时生成的app_key
  194. * @param string $session_key 登录的code换得的
  195. * @return string | false
  196. */
  197. private static function decrypt($ciphertext, $iv, $app_key, $session_key)
  198. {
  199. $session_key = base64_decode($session_key);
  200. $iv = base64_decode($iv);
  201. $ciphertext = base64_decode($ciphertext);
  202. $plaintext = false;
  203. $plaintext = openssl_decrypt($ciphertext, "AES-192-CBC", $session_key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
  204. if ($plaintext == false) {
  205. return false;
  206. }
  207. // trim pkcs#7 padding
  208. $pad = ord(substr($plaintext, -1));
  209. $pad = ($pad < 1 || $pad > 32) ? 0 : $pad;
  210. $plaintext = substr($plaintext, 0, strlen($plaintext) - $pad);
  211. // trim header
  212. $plaintext = substr($plaintext, 16);
  213. // get content length
  214. $unpack = unpack("Nlen/", substr($plaintext, 0, 4));
  215. // get content
  216. $content = substr($plaintext, 4, $unpack['len']);
  217. // get app_key
  218. $app_key_decode = substr($plaintext, $unpack['len'] + 4);
  219. return $app_key == $app_key_decode ? $content : false;
  220. }
  221. /**
  222. * @desc 使用私钥生成签名字符串
  223. * @param array $assocArr 入参数组
  224. * @param string $rsaPriKeyStr 私钥原始字符串,不含PEM格式前后缀
  225. * @return string 签名结果字符串
  226. * @throws Exception
  227. */
  228. public static function sign(array $assocArr, $rsaPriKeyStr)
  229. {
  230. $sign = '';
  231. if (empty($rsaPriKeyStr) || empty($assocArr)) {
  232. return $sign;
  233. }
  234. if (!function_exists('openssl_pkey_get_private') || !function_exists('openssl_sign')) {
  235. throw new \Exception("openssl扩展不存在");
  236. }
  237. $rsaPriKeyPem = self::convertRSAKeyStr2Pem($rsaPriKeyStr, 1);
  238. $priKey = openssl_pkey_get_private($rsaPriKeyPem);
  239. if (isset($assocArr['sign'])) {
  240. unset($assocArr['sign']);
  241. }
  242. // 参数按字典顺序排序
  243. ksort($assocArr);
  244. $parts = array();
  245. foreach ($assocArr as $k => $v) {
  246. $parts[] = $k . '=' . $v;
  247. }
  248. $str = implode('&', $parts);
  249. openssl_sign($str, $sign, $priKey);
  250. openssl_free_key($priKey);
  251. return base64_encode($sign);
  252. }
  253. /**
  254. * @desc 使用公钥校验签名
  255. * @param array $assocArr 入参数据,签名属性名固定为rsaSign
  256. * @param string $rsaPubKeyStr 公钥原始字符串,不含PEM格式前后缀
  257. * @return bool true 验签通过|false 验签不通过
  258. * @throws Exception
  259. */
  260. public static function checkSign(array $assocArr, $rsaPubKeyStr)
  261. {
  262. if (!isset($assocArr['rsaSign']) || empty($assocArr) || empty($rsaPubKeyStr)) {
  263. return false;
  264. }
  265. if (!function_exists('openssl_pkey_get_public') || !function_exists('openssl_verify')) {
  266. throw new \Exception("openssl扩展不存在");
  267. }
  268. $sign = $assocArr['rsaSign'];
  269. unset($assocArr['rsaSign']);
  270. if (empty($assocArr)) {
  271. return false;
  272. }
  273. // 参数按字典顺序排序
  274. ksort($assocArr);
  275. $parts = array();
  276. foreach ($assocArr as $k => $v) {
  277. $parts[] = $k . '=' . $v;
  278. }
  279. $str = implode('&', $parts);
  280. $sign = base64_decode($sign);
  281. $rsaPubKeyPem = self::convertRSAKeyStr2Pem($rsaPubKeyStr);
  282. $pubKey = openssl_pkey_get_public($rsaPubKeyPem);
  283. $result = (bool) openssl_verify($str, $sign, $pubKey);
  284. return $result;
  285. }
  286. /**
  287. * @desc 将密钥由字符串(不换行)转为PEM格式
  288. * @param string $rsaKeyStr 原始密钥字符串
  289. * @param int $keyType 0 公钥|1 私钥,默认0
  290. * @return string PEM格式密钥
  291. * @throws Exception
  292. */
  293. public static function convertRSAKeyStr2Pem($rsaKeyStr, $keyType = 0)
  294. {
  295. $pemWidth = 64;
  296. $rsaKeyPem = '';
  297. $begin = '-----BEGIN ';
  298. $end = '-----END ';
  299. $key = ' KEY-----';
  300. $type = $keyType ? 'PRIVATE' : 'PUBLIC';
  301. $rsa = $keyType ? 'RSA ' : '';
  302. $keyPrefix = $begin . $rsa . $type . $key;
  303. $keySuffix = $end . $rsa . $type . $key;
  304. $rsaKeyPem .= $keyPrefix . "\n";
  305. $rsaKeyPem .= wordwrap($rsaKeyStr, $pemWidth, "\n", true) . "\n";
  306. $rsaKeyPem .= $keySuffix;
  307. if (!function_exists('openssl_pkey_get_public') || !function_exists('openssl_pkey_get_private')) {
  308. return false;
  309. }
  310. if ($keyType == 0 && false == openssl_pkey_get_public($rsaKeyPem)) {
  311. return false;
  312. }
  313. if ($keyType == 1 && false == openssl_pkey_get_private($rsaKeyPem)) {
  314. return false;
  315. }
  316. return $rsaKeyPem;
  317. }
  318. }