AliPay.php 13 KB

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