AliPayService.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2016~2020 https://www.crmeb.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  8. // +----------------------------------------------------------------------
  9. // | Author: CRMEB Team <admin@crmeb.com>
  10. // +----------------------------------------------------------------------
  11. namespace crmeb\services;
  12. use crmeb\utils\Hook;
  13. use think\facade\Log;
  14. use think\facade\Route as Url;
  15. use Alipay\EasySDK\Kernel\Config;
  16. use Alipay\EasySDK\Kernel\Factory;
  17. use crmeb\exceptions\PayException;
  18. use app\services\pay\PayNotifyServices;
  19. use Alipay\EasySDK\Kernel\Util\ResponseChecker;
  20. use AlibabaCloud\Tea\Tea;
  21. /**
  22. * Class AliPayService
  23. * @package crmeb\services
  24. */
  25. class AliPayService
  26. {
  27. /**
  28. * 配置
  29. * @var array
  30. */
  31. protected $config = [
  32. 'appId' => '',
  33. 'merchantPrivateKey' => '',//应用私钥
  34. 'alipayPublicKey' => '',//支付宝公钥
  35. 'notifyUrl' => '',//可设置异步通知接收服务地址
  36. 'encryptKey' => '',//可设置AES密钥,调用AES加解密相关接口时需要(可选)
  37. ];
  38. /**
  39. * @var ResponseChecker
  40. */
  41. protected $response;
  42. /**
  43. * 付款码
  44. * @var string
  45. */
  46. protected $authCode;
  47. /**
  48. * @var static
  49. */
  50. protected static $instance;
  51. /**
  52. * AliPayService constructor.
  53. * @param array $config
  54. */
  55. protected function __construct(array $config = [])
  56. {
  57. if (!$config) {
  58. $alipay = SystemConfigService::more(['ali_pay_appid', 'alipay_merchant_private_key', 'alipay_public_key', 'site_url']);
  59. $config = [
  60. 'appId' => $alipay['ali_pay_appid'] ?? '',
  61. 'merchantPrivateKey' => $alipay['alipay_merchant_private_key'] ?? '',
  62. 'alipayPublicKey' => $alipay['alipay_public_key'] ?? '',
  63. 'notifyUrl' => ($alipay['site_url'] ?? '') . Url::buildUrl('/api/pay/notify/alipay'),
  64. ];
  65. }
  66. $this->config = array_merge($this->config, $config);
  67. $this->initialize();
  68. $this->response = new ResponseChecker();
  69. }
  70. /**
  71. * 实例化
  72. * @param array $config
  73. * @return static
  74. */
  75. public static function instance(array $config = [])
  76. {
  77. if (is_null(self::$instance)) {
  78. self::$instance = new static($config);
  79. }
  80. return self::$instance;
  81. }
  82. /**
  83. * 是否是支付宝付款码
  84. * @param string $authCode
  85. * @return bool
  86. */
  87. public static function isAliPayAuthCode(string $authCode)
  88. {
  89. return preg_match('/^[0-9]{16,24}$/', $authCode) && in_array(substr($authCode, 0, 2), ['25', '26', '27', '28', '29', '30']);
  90. }
  91. /**
  92. * 初始化
  93. */
  94. protected function initialize()
  95. {
  96. Factory::setOptions($this->getOptions());
  97. }
  98. /**
  99. * 设置配置
  100. * @return Config
  101. */
  102. protected function getOptions()
  103. {
  104. $options = new Config();
  105. $options->protocol = 'https';
  106. $options->gatewayHost = 'openapi.alipay.com';
  107. $options->signType = 'RSA2';
  108. $options->appId = $this->config['appId'];
  109. // 为避免私钥随源码泄露,推荐从文件中读取私钥字符串而不是写入源码中
  110. $options->merchantPrivateKey = $this->config['merchantPrivateKey'];
  111. //注:如果采用非证书模式,则无需赋值上面的三个证书路径,改为赋值如下的支付宝公钥字符串即可
  112. $options->alipayPublicKey = $this->config['alipayPublicKey'];
  113. //可设置异步通知接收服务地址(可选)
  114. $options->notifyUrl = $this->config['notifyUrl'];
  115. //可设置AES密钥,调用AES加解密相关接口时需要(可选)
  116. if ($this->config['encryptKey']) {
  117. $options->encryptKey = $this->config['encryptKey'];
  118. }
  119. Tea::config(['verify' => false]);
  120. return $options;
  121. }
  122. /**
  123. * 付款码付款
  124. * @param string $authCode
  125. * @param string $title
  126. * @param string $orderId
  127. * @param string $totalAmount
  128. * @param string $passbackParams
  129. * @return array
  130. */
  131. public function microPay(string $authCode, string $title, string $orderId, string $totalAmount, string $passbackParams)
  132. {
  133. $title = trim($title);
  134. try {
  135. $result = Factory::payment()->faceToFace()->optional('passback_params', $passbackParams)->pay($title, $orderId, $totalAmount, $authCode);
  136. if ($this->response->success($result)) {
  137. $response = $result->toMap();
  138. return [
  139. 'paid' => $response['code'] === '10000' ? 1 : 0,
  140. 'message' => $response['sub_msg'] ?? '支付成功',
  141. 'payInfo' => $response
  142. ];
  143. } else {
  144. throw new PayException('失败原因:' . $result->msg . ',' . $result->subMsg);
  145. }
  146. } catch (\Exception $e) {
  147. throw new PayException($e->getMessage());
  148. }
  149. }
  150. /**
  151. * 创建订单
  152. * @param string $title 商品名称
  153. * @param string $orderId 订单号
  154. * @param string $totalAmount 支付金额
  155. * @param string $passbackParams 备注
  156. * @param string $quitUrl 同步跳转地址
  157. * @param string $siteUrl
  158. * @return \Alipay\EasySDK\Payment\Wap\Models\AlipayTradeWapPayResponse
  159. */
  160. public function create(string $title, string $orderId, string $totalAmount, string $passbackParams, string $quitUrl = '', string $siteUrl = '', bool $isCode = false)
  161. {
  162. $title = trim($title);
  163. try {
  164. if ($isCode) {
  165. //二维码支付
  166. $result = Factory::payment()->faceToFace()->optional('passback_params', $passbackParams)->precreate($title, $orderId, $totalAmount);
  167. } else if (request()->isApp()) {
  168. //app支付
  169. $result = Factory::payment()->app()->optional('passback_params', $passbackParams)->pay($title, $orderId, $totalAmount);
  170. } else {
  171. //h5支付
  172. $result = Factory::payment()->wap()->optional('passback_params', $passbackParams)->pay($title, $orderId, $totalAmount, $quitUrl, $siteUrl);
  173. }
  174. if ($this->response->success($result)) {
  175. return isset($result->body) ? $result->body : $result;
  176. } else {
  177. throw new PayException('失败原因:' . $result->msg . ',' . $result->subMsg);
  178. }
  179. } catch (\Exception $e) {
  180. throw new PayException($e->getMessage());
  181. }
  182. }
  183. /**
  184. * 订单退款
  185. * @param string $outTradeNo 订单号
  186. * @param string $totalAmount 退款金额
  187. * @param string $refund_id 退款单号
  188. * @return \Alipay\EasySDK\Payment\Common\Models\AlipayTradeRefundResponse
  189. */
  190. public function refund(string $outTradeNo, string $totalAmount, string $refund_id)
  191. {
  192. try {
  193. $result = Factory::payment()->common()->refund($outTradeNo, $totalAmount, $refund_id);
  194. if ($this->response->success($result)) {
  195. return $result;
  196. } else {
  197. throw new PayException('失败原因:' . $result->msg . ',' . $result->subMsg);
  198. }
  199. } catch (\Exception $e) {
  200. throw new PayException($e->getMessage());
  201. }
  202. }
  203. /**
  204. * 查询交易退款单号信息
  205. * @param string $outTradeNo
  206. * @param string $outRequestNo
  207. * @return \Alipay\EasySDK\Payment\Common\Models\AlipayTradeFastpayRefundQueryResponse
  208. */
  209. public function queryRefund(string $outTradeNo, string $outRequestNo)
  210. {
  211. try {
  212. $result = Factory::payment()->common()->queryRefund($outTradeNo, $outRequestNo);
  213. if ($this->response->success($result)) {
  214. return $result;
  215. } else {
  216. throw new PayException('失败原因:' . $result->msg . ',' . $result->subMsg);
  217. }
  218. } catch (\Exception $e) {
  219. throw new PayException($e->getMessage());
  220. }
  221. }
  222. /**
  223. * 支付异步回调
  224. * @return string
  225. */
  226. public static function handleNotify()
  227. {
  228. return self::instance()->notify(function ($notify) {
  229. if (isset($notify->out_trade_no)) {
  230. if (isset($notify->attach) && $notify->attach) {
  231. if (($count = strpos($notify->out_trade_no, '_')) !== false) {
  232. $notify->trade_no = $notify->out_trade_no;
  233. $notify->out_trade_no = substr($notify->out_trade_no, $count + 1);
  234. }
  235. return (new Hook(PayNotifyServices::class, 'aliyun'))->listen($notify->attach, $notify->out_trade_no, $notify->trade_no);
  236. }
  237. return false;
  238. }
  239. });
  240. }
  241. /**
  242. * 异步回调
  243. * @param callable $notifyFn
  244. * @return string
  245. */
  246. public function notify(callable $notifyFn)
  247. {
  248. app()->request->filter(['trim']);
  249. $paramInfo = app()->request->postMore([
  250. ['gmt_create', ''],
  251. ['charset', ''],
  252. ['seller_email', ''],
  253. ['subject', ''],
  254. ['sign', ''],
  255. ['buyer_id', ''],
  256. ['invoice_amount', ''],
  257. ['notify_id', ''],
  258. ['fund_bill_list', ''],
  259. ['notify_type', ''],
  260. ['trade_status', ''],
  261. ['receipt_amount', ''],
  262. ['buyer_pay_amount', ''],
  263. ['app_id', ''],
  264. ['seller_id', ''],
  265. ['sign_type', ''],
  266. ['gmt_payment', ''],
  267. ['notify_time', ''],
  268. ['passback_params', ''],
  269. ['version', ''],
  270. ['out_trade_no', ''],
  271. ['total_amount', ''],
  272. ['trade_no', ''],
  273. ['auth_app_id', ''],
  274. ['buyer_logon_id', ''],
  275. ['point_amount', ''],
  276. ], false, false);
  277. //商户订单号
  278. $postOrder['out_trade_no'] = $paramInfo['out_trade_no'] ?? '';
  279. //支付宝交易号
  280. $postOrder['trade_no'] = $paramInfo['trade_no'] ?? '';
  281. //交易状态
  282. $postOrder['trade_status'] = $paramInfo['trade_status'] ?? '';
  283. //备注
  284. $postOrder['attach'] = isset($paramInfo['passback_params']) ? urldecode($paramInfo['passback_params']) : '';
  285. if (in_array($postOrder['trade_status'], ['TRADE_SUCCESS', 'TRADE_FINISHED']) && $this->verifyNotify($paramInfo)) {
  286. try {
  287. if ($notifyFn((object)$postOrder)) {
  288. return 'success';
  289. }
  290. } catch (\Exception $e) {
  291. Log::error('支付宝异步会回调成功,执行函数错误。错误单号:' . $postOrder['out_trade_no']);
  292. }
  293. }
  294. return 'fail';
  295. }
  296. /**
  297. * 验签
  298. * @return bool
  299. */
  300. protected function verifyNotify(array $param)
  301. {
  302. try {
  303. return Factory::payment()->common()->verifyNotify($param);
  304. } catch (\Exception $e) {
  305. Log::error('支付宝回调成功,验签发生错误,错误原因:' . $e->getMessage());
  306. }
  307. return false;
  308. }
  309. }