AliPay.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. <?php
  2. namespace app\common\library\alipay;
  3. use Alipay\EasySDK\Kernel\Config;
  4. use Alipay\EasySDK\Kernel\Factory;
  5. use app\api\service\order\paysuccess\type\PayTypeSuccessFactory;
  6. use app\common\enum\order\OrderTypeEnum;
  7. use app\common\enum\order\OrderPayTypeEnum;
  8. use app\common\enum\settings\SettingEnum;
  9. use app\common\exception\BaseException;
  10. use app\common\library\helper;
  11. use app\common\model\app\AppOpen as AppOpenModel;
  12. use app\common\model\settings\Setting;
  13. /**
  14. * 支付宝支付
  15. */
  16. class AliPay
  17. {
  18. // app_id
  19. public $app_id = '';
  20. // 支付宝公钥
  21. public $publicKey = '';
  22. // 应用私钥
  23. public $privateKey = '';
  24. /**
  25. * 构造函数
  26. */
  27. public function __construct($pay_source)
  28. {
  29. $this->init($pay_source, null);
  30. }
  31. public function init($pay_source, $app_id){
  32. if($pay_source == 'app' && $app_id != null){
  33. $config = AppOpenModel::getAppOpenCache($app_id);
  34. $this->app_id = $config['alipay_appid'];
  35. $this->privateKey = $config['alipay_privatekey'];
  36. $this->publicKey = $config['alipay_publickey'];
  37. }else{
  38. // 获取配置
  39. $config = Setting::getItem(SettingEnum::H5ALIPAY, $app_id);
  40. $this->app_id = $config['app_id'];
  41. $this->privateKey = $config['privateKey'];
  42. $this->publicKey = $config['publicKey'];
  43. }
  44. }
  45. private function getOptions($notify_url = '')
  46. {
  47. $options = new Config();
  48. $options->protocol = 'https';
  49. $options->gatewayHost = 'openapi.alipay.com';
  50. $options->signType = 'RSA2';
  51. $options->appId = $this->app_id;
  52. // 为避免私钥随源码泄露,推荐从文件中读取私钥字符串而不是写入源码中
  53. $options->merchantPrivateKey = $this->privateKey;
  54. //注:如果采用非证书模式,则无需赋值上面的三个证书路径,改为赋值如下的支付宝公钥字符串即可
  55. $options->alipayPublicKey = $this->publicKey;
  56. $options->notifyUrl = $notify_url;
  57. return $options;
  58. }
  59. /**
  60. * 统一下单API
  61. */
  62. public function unifiedorder($order_no, $totalFee, $orderType = OrderTypeEnum::MASTER, $pay_source, $multiple)
  63. {
  64. if($pay_source == 'app'){
  65. $notify_url = base_url() . 'index.php/job/notify/alipay_notify?order_type='.$orderType.'&pay_source='.$pay_source.'&ple='.$multiple;
  66. $result = Factory::setOptions($this->getOptions($notify_url))->payment()->app()->pay("订单支付", $order_no, helper::number2($totalFee));
  67. return $result->body;
  68. }else {
  69. //请求参数
  70. $requestConfigs = array(
  71. 'out_trade_no' => $order_no,
  72. 'product_code' => 'QUICK_WAP_WAY',
  73. 'total_amount' => helper::number2($totalFee), //单位 元
  74. 'subject' => '订单支付', //订单标题
  75. );
  76. $commonConfigs = array(
  77. //公共参数
  78. 'app_id' => $this->app_id,
  79. 'method' => 'alipay.trade.wap.pay', //接口名称
  80. 'format' => 'JSON',
  81. 'return_url' => base_url() . 'index.php/job/notify/alipay_return?order_type=' . $orderType . '&pay_source=' . $pay_source . '&ple=' . $multiple,
  82. 'charset' => 'utf-8',
  83. 'sign_type' => 'RSA2',
  84. 'timestamp' => date('Y-m-d H:i:s'),
  85. 'version' => '1.0',
  86. 'notify_url' => base_url() . 'index.php/job/notify/alipay_notify?order_type=' . $orderType . '&pay_source=' . $pay_source . '&ple=' . $multiple,
  87. 'biz_content' => json_encode($requestConfigs, JSON_UNESCAPED_UNICODE),
  88. );
  89. $commonConfigs["sign"] = $this->generateSign($commonConfigs, $commonConfigs['sign_type']);
  90. // 跳h5
  91. return $this->buildRequestForm($commonConfigs);
  92. }
  93. }
  94. /**
  95. * 同步通知
  96. */
  97. public function return()
  98. {
  99. $params = $_GET;
  100. log_write($params);
  101. $order_type = $_GET['order_type'];
  102. $pay_source = $_GET['pay_source'];
  103. $multiple = $_GET['ple'];
  104. $attach = '{"order_type": "' . $order_type . '","pay_source":"' . $pay_source . '","multiple":"' . $multiple . '"}';
  105. // 实例化订单模型
  106. $PaySuccess = PayTypeSuccessFactory::getFactory($_GET['out_trade_no'], json_decode($attach, true));
  107. $app_id = $PaySuccess->isExist(0);
  108. if($app_id == 0){
  109. echo 'error';
  110. exit();
  111. }
  112. $this->init($pay_source, $app_id);
  113. unset($params['order_type']);
  114. unset($params['pay_source']);
  115. unset($params['ple']);
  116. $result = $this->rsaCheck($params, $params['sign_type']);
  117. if ($result === true) {
  118. $query_result = $this->query($params);
  119. if ($query_result['alipay_trade_query_response']['code'] == '10000') {
  120. if ($query_result['alipay_trade_query_response']['trade_status'] == 'TRADE_SUCCESS') {
  121. log_write('支付成功' . $params['out_trade_no'] . ';pay_source:'.$pay_source);
  122. }
  123. // 跳到我的订单
  124. if($order_type == OrderTypeEnum::MASTER){
  125. if($pay_source == 'payH5'){
  126. return base_url() . 'h5/pages/order/myorder';
  127. }else{
  128. return base_url() . 'alipay-h5-app.html';
  129. }
  130. }
  131. if($order_type == OrderTypeEnum::BALANCE){
  132. return base_url() . 'h5/pages/user/my-wallet/my-wallet';
  133. }
  134. }
  135. } else {
  136. log_write('支付失败');
  137. log_write($_GET);
  138. echo 'error';
  139. return false;
  140. }
  141. }
  142. private function query($params)
  143. {
  144. //请求参数
  145. $requestConfigs = array(
  146. 'out_trade_no' => $params['out_trade_no'],
  147. 'trade_no' => $params['trade_no'],
  148. );
  149. $commonConfigs = array(
  150. //公共参数
  151. 'app_id' => $this->app_id,
  152. 'method' => 'alipay.trade.query', //接口名称
  153. 'format' => 'JSON',
  154. 'charset' => 'utf-8',
  155. 'sign_type' => 'RSA2',
  156. 'timestamp' => date('Y-m-d H:i:s'),
  157. 'version' => '1.0',
  158. 'biz_content' => json_encode($requestConfigs),
  159. );
  160. $commonConfigs["sign"] = $this->generateSign($commonConfigs, $commonConfigs['sign_type']);
  161. $result = curlPost('https://openapi.alipay.com/gateway.do?charset=utf-8', $commonConfigs);
  162. return json_decode($result, true);
  163. }
  164. /**
  165. * 支付成功异步通知
  166. */
  167. public function notify()
  168. {
  169. log_write('支付宝回调');
  170. $params = $_POST;
  171. $order_type = $_POST['order_type'];
  172. $pay_source = $_POST['pay_source'];
  173. $multiple = $_POST['ple'];
  174. unset($params['order_type']);
  175. unset($params['pay_source']);
  176. unset($params['ple']);
  177. // 订单支付成功业务处理,兼容微信参数
  178. $attach = '{"order_type": "' . $order_type . '","pay_source":"' . $pay_source . '","multiple":"' . $multiple . '"}';
  179. // 实例化订单模型
  180. $PaySuccess = PayTypeSuccessFactory::getFactory($params['out_trade_no'], json_decode($attach, true));
  181. $app_id = $PaySuccess->isExist(10);
  182. if($app_id == 0){
  183. echo 'error';
  184. exit();
  185. }
  186. $this->init($pay_source, $app_id);
  187. //验证签名
  188. $result = $this->rsaCheck($params, $params['sign_type']);
  189. if ($result === true && $_POST['trade_status'] == 'TRADE_SUCCESS') {
  190. log_write('支付宝回调----验证成功');
  191. $data['attach'] = $attach;
  192. $data['transaction_id'] = $params['trade_no'];
  193. $status = $PaySuccess->onPaySuccess(OrderPayTypeEnum::ALIPAY, $data);
  194. if ($status == false) {
  195. echo 'error';
  196. exit();
  197. }
  198. //程序执行完后必须打印输出“success”(不包含引号)。如果商户反馈给支付宝的字符不是success这7个字符,支付宝服务器会不断重发通知,直到超过24小时22分钟。一般情况下,25小时以内完成8次通知(通知的间隔频率一般是:4m,10m,10m,1h,2h,6h,15h);
  199. echo 'success';
  200. exit();
  201. }
  202. log_write('支付宝回调----验证失败');
  203. echo 'error';
  204. exit();
  205. }
  206. /**
  207. * 申请退款API
  208. */
  209. public function refund($transaction_id, $order_no, $refund_fee)
  210. {
  211. //请求参数
  212. $requestConfigs = array(
  213. 'trade_no' => $transaction_id,
  214. 'out_trade_no' => $order_no,
  215. 'refund_amount' => $refund_fee,
  216. );
  217. $commonConfigs = array(
  218. //公共参数
  219. 'app_id' => $this->app_id,
  220. 'method' => 'alipay.trade.refund', //接口名称
  221. 'format' => 'JSON',
  222. 'charset' => 'utf-8',
  223. 'sign_type' => 'RSA2',
  224. 'timestamp' => date('Y-m-d H:i:s'),
  225. 'version' => '1.0',
  226. 'biz_content' => json_encode($requestConfigs),
  227. );
  228. $commonConfigs["sign"] = $this->generateSign($commonConfigs, $commonConfigs['sign_type']);
  229. $result = curlPost('https://openapi.alipay.com/gateway.do?charset=utf-8', $commonConfigs);
  230. $resultArr = json_decode($result, true);
  231. $result = $resultArr['alipay_trade_refund_response'];
  232. if($result['code'] && $result['code']=='10000'){
  233. return true;
  234. }else{
  235. throw new BaseException(['msg' => 'return_msg: ' . $result['msg'].','.$result['sub_msg']]);
  236. }
  237. }
  238. /**
  239. * 建立请求,以表单HTML形式构造(默认)
  240. * 请求参数数组
  241. * 提交表单HTML文本
  242. */
  243. protected function buildRequestForm($para_temp)
  244. {
  245. $sHtml = "<form id='alipaysubmit' name='alipaysubmit' action='https://openapi.alipay.com/gateway.do?charset=utf-8' method='POST'>";
  246. foreach ($para_temp as $key => $val) {
  247. if (false === $this->checkEmpty($val)) {
  248. $val = str_replace("'", "&apos;", $val);
  249. $sHtml .= "<input type='hidden' name='" . $key . "' value='" . $val . "'/>";
  250. }
  251. }
  252. //submit按钮控件请不要含有name属性
  253. $sHtml = $sHtml . "<input type='submit' value='ok' style='display:none;''></form>";
  254. return $sHtml;
  255. }
  256. public function generateSign($params, $signType = "RSA")
  257. {
  258. return $this->sign($this->getSignContent($params), $signType);
  259. }
  260. protected function sign($data, $signType = "RSA")
  261. {
  262. $res = "-----BEGIN RSA PRIVATE KEY-----\n" .
  263. wordwrap($this->privateKey, 64, "\n", true) .
  264. "\n-----END RSA PRIVATE KEY-----";
  265. ($res) or die('您使用的私钥格式错误,请检查RSA私钥配置');
  266. if ("RSA2" == $signType) {
  267. openssl_sign($data, $sign, $res, version_compare(PHP_VERSION, '5.4.0', '<') ? SHA256 : OPENSSL_ALGO_SHA256); //OPENSSL_ALGO_SHA256是php5.4.8以上版本才支持
  268. } else {
  269. openssl_sign($data, $sign, $res);
  270. }
  271. $sign = base64_encode($sign);
  272. return $sign;
  273. }
  274. public function getSignContent($params)
  275. {
  276. ksort($params);
  277. $stringToBeSigned = "";
  278. $i = 0;
  279. foreach ($params as $k => $v) {
  280. if (false === $this->checkEmpty($v) && "@" != substr($v, 0, 1)) {
  281. // 转换成目标字符集
  282. $v = $this->characet($v, 'utf-8');
  283. if ($i == 0) {
  284. $stringToBeSigned .= "$k" . "=" . "$v";
  285. } else {
  286. $stringToBeSigned .= "&" . "$k" . "=" . "$v";
  287. }
  288. $i++;
  289. }
  290. }
  291. unset ($k, $v);
  292. return $stringToBeSigned;
  293. }
  294. /**
  295. * 转换字符集编码
  296. * @param $data
  297. * @param $targetCharset
  298. * @return string
  299. */
  300. function characet($data, $targetCharset)
  301. {
  302. if (!empty($data)) {
  303. $fileType = 'utf-8';
  304. if (strcasecmp($fileType, $targetCharset) != 0) {
  305. $data = mb_convert_encoding($data, $targetCharset, $fileType);
  306. }
  307. }
  308. return $data;
  309. }
  310. /**
  311. * 校验$value是否非空
  312. * if not set ,return true;
  313. * if is null , return true;
  314. **/
  315. protected function checkEmpty($value)
  316. {
  317. if (!isset($value))
  318. return true;
  319. if ($value === null)
  320. return true;
  321. if (trim($value) === "")
  322. return true;
  323. return false;
  324. }
  325. /**
  326. * 验证签名
  327. **/
  328. public function rsaCheck($params)
  329. {
  330. $sign = $params['sign'];
  331. $signType = $params['sign_type'];
  332. unset($params['sign_type']);
  333. unset($params['sign']);
  334. return $this->verify($this->getSignContent($params), $sign, $signType);
  335. }
  336. function verify($data, $sign, $signType = 'RSA')
  337. {
  338. $res = "-----BEGIN PUBLIC KEY-----\n" .
  339. wordwrap($this->publicKey, 64, "\n", true) .
  340. "\n-----END PUBLIC KEY-----";
  341. ($res) or die('支付宝RSA公钥错误。请检查公钥文件格式是否正确');
  342. //调用openssl内置方法验签,返回bool值
  343. if ("RSA2" == $signType) {
  344. $result = (bool)openssl_verify($data, base64_decode($sign), $res, version_compare(PHP_VERSION, '5.4.0', '<') ? SHA256 : OPENSSL_ALGO_SHA256);
  345. } else {
  346. $result = (bool)openssl_verify($data, base64_decode($sign), $res);
  347. }
  348. return $result;
  349. }
  350. }