AlipayController.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * Author: 芸众商城 www.yunzshop.com
  5. * Date: 24/03/2017
  6. * Time: 01:07
  7. */
  8. namespace app\payment\controllers;
  9. use app\backend\modules\refund\services\RefundOperationService;
  10. use app\common\events\finance\AlipayWithdrawEvent;
  11. use app\common\events\order\AfterOrderPaidRedirectEvent;
  12. use app\common\helpers\Url;
  13. use app\common\models\Order;
  14. use app\common\models\OrderPay;
  15. use app\common\models\PayOrder;
  16. use app\common\models\PayRefundOrder;
  17. use app\common\models\PayType;
  18. use app\common\models\PayWithdrawOrder;
  19. use app\common\models\refund\RefundApply;
  20. use app\common\modules\alipay\models\AlipayPayOrder;
  21. use app\common\services\alipay\f2fpay\model\AlipayConfig;
  22. use app\common\services\finance\Withdraw;
  23. use app\common\services\Pay;
  24. use app\payment\PaymentController;
  25. use app\common\models\OrderGoods;
  26. use Yunshop\StoreCashier\common\models\StoreOrder;
  27. class AlipayController extends PaymentController
  28. {
  29. private $sign_type = ['MD5' => '支付宝', 'RSA' => '支付宝APP', 'RSA2' => '支付宝APP2.0'];
  30. private $total_fee = ['MD5' => 'total_fee', 'RSA' => 'total_fee', 'RSA2' => 'total_amount'];
  31. private $pay_type_id = 2;
  32. //商城支付宝app支付异步通知
  33. public function notifyUrl()
  34. {
  35. $this->log($_POST, '支付宝支付2.0');
  36. $this->pay_type_id = 10;
  37. $verify_result = $this->get_RSA_SignResult($_POST);
  38. \Log::debug(sprintf('支付回调验证结果[%d]', intval($verify_result)));
  39. if ($verify_result) {
  40. if ($_POST['trade_status'] == 'TRADE_SUCCESS') {
  41. if (strpos($_POST['out_trade_no'], '_') !== false) {
  42. $out_trade_no = substr($_POST['out_trade_no'], strpos($_POST['out_trade_no'], '_') + 1);
  43. } else {
  44. $out_trade_no = $_POST['out_trade_no'];
  45. }
  46. $data = [
  47. 'total_fee' => $_POST['total_amount'],
  48. 'out_trade_no' => $out_trade_no,
  49. 'trade_no' => $_POST['trade_no'],
  50. 'unit' => 'yuan',
  51. 'pay_type' => $this->sign_type[$_POST['sign_type']],
  52. 'pay_type_id' => $this->pay_type_id
  53. ];
  54. $this->payResutl($data);
  55. }
  56. echo "success";
  57. } else {
  58. echo "fail";
  59. }
  60. }
  61. //商城支付宝app支付异步通知
  62. public function newNotifyUrl()
  63. {
  64. $this->log($_POST, '支付宝支付2.0');
  65. $this->pay_type_id = 10;
  66. $verify_result = $this->get_RSA_SignResult($_POST);
  67. \Log::debug(sprintf('支付回调验证结果[%d]', intval($verify_result)));
  68. if ($verify_result) {
  69. if ($_POST['trade_status'] == 'TRADE_SUCCESS') {
  70. if (strpos($_POST['out_trade_no'], '_') !== false) {
  71. $out_trade_no = substr($_POST['out_trade_no'], strpos($_POST['out_trade_no'], '_') + 1);
  72. } else {
  73. $out_trade_no = $_POST['out_trade_no'];
  74. }
  75. $data = [
  76. 'total_fee' => $_POST['total_amount'],
  77. 'out_trade_no' => $out_trade_no,
  78. 'trade_no' => $_POST['trade_no'],
  79. 'unit' => 'yuan',
  80. 'pay_type' => $this->sign_type[$_POST['sign_type']],
  81. 'pay_type_id' => $this->pay_type_id
  82. ];
  83. $this->payResutl($data);
  84. }
  85. echo "success";
  86. } else {
  87. echo "fail";
  88. }
  89. }
  90. //标准支付宝支付
  91. public function preNotifyUrl()
  92. {
  93. $this->log($_POST, '支付宝支付2.0');
  94. $verify_result = $this->get_RSA2_SignResult($_POST);
  95. \Log::debug(sprintf('支付回调验证结果[%d]', intval($verify_result)));
  96. if ($verify_result) {
  97. if ($_POST['trade_status'] == 'TRADE_SUCCESS') {
  98. if (strpos($_POST['out_trade_no'], '_') !== false) {
  99. $out_trade_no = substr($_POST['out_trade_no'], strpos($_POST['out_trade_no'], '_') + 1);
  100. } else {
  101. $out_trade_no = $_POST['out_trade_no'];
  102. }
  103. $data = [
  104. 'total_fee' => $_POST['total_amount'],
  105. 'out_trade_no' => $out_trade_no,
  106. 'trade_no' => $_POST['trade_no'],
  107. 'unit' => 'yuan',
  108. 'pay_type' => $this->sign_type[$_POST['sign_type']],
  109. 'pay_type_id' => $this->pay_type_id
  110. ];
  111. $this->payResutl($data);
  112. }
  113. echo "success";
  114. } else {
  115. echo "fail";
  116. }
  117. }
  118. public function returnUrl()
  119. {
  120. if (isset($_GET['alipayresult']) && !empty($_GET['alipayresult'])) {
  121. $alipayresult = json_decode($_GET['alipayresult'], true);
  122. if (strpos($alipayresult['alipay_trade_app_pay_response']['out_trade_no'], '_') !== false) {
  123. $data = explode('_', $alipayresult['alipay_trade_app_pay_response']['out_trade_no']);
  124. $out_trade_no = $data[1];
  125. \YunShop::app()->uniacid = $data[0];
  126. } else {
  127. $out_trade_no = $alipayresult['alipay_trade_app_pay_response']['out_trade_no'];
  128. }
  129. \Log::debug('=========支付宝APP支付2.0==========:', $alipayresult['alipay_trade_app_pay_response']);
  130. } elseif (strpos($_GET['out_trade_no'], '_') !== false) {
  131. $data = explode('_', $_GET['out_trade_no']);
  132. if (count($data) == 2) {
  133. $out_trade_no = $data[1];
  134. \YunShop::app()->uniacid = $data[0];
  135. \Log::debug('=============商城支付宝APP支付2.0==========:', $data);
  136. } else {
  137. $out_trade_no = $data[0];
  138. \YunShop::app()->uniacid = $data[1];
  139. \Log::debug('========支付宝JSAPI支付(服务商)=====:', $data);
  140. }
  141. } else {
  142. $out_trade_no = $this->substr_var($_GET['out_trade_no']);
  143. }
  144. $orderPay = OrderPay::where('pay_sn', $out_trade_no)->first();
  145. if (!is_null($orderPay)) {
  146. $orders = Order::whereIn('id', $orderPay->order_ids)->get();
  147. event($event = new AfterOrderPaidRedirectEvent($orders, $orderPay->id));
  148. }
  149. $trade = \Setting::get('shop.trade');
  150. //这里做支付后跳转,需要取到支付流水号
  151. $redirect = Url::absoluteApp('home');
  152. if (!is_null($trade) && isset($trade['redirect_url']) && !empty($trade['redirect_url'])) {
  153. $redirect = $trade['redirect_url'];
  154. preg_match("/^(http:\/\/)?([^\/]+)/i", $trade['redirect_url'], $matches);
  155. $host = $matches[2];
  156. // 从主机名中取得后面两段
  157. preg_match("/[^\.\/]+\.[^\.\/]+$/", $host, $matches);
  158. if ($matches) {//判断域名是否一致
  159. $redirect = $trade['redirect_url'] . '&outtradeno=' . $out_trade_no;
  160. }
  161. }
  162. if (isset($event)) {
  163. $redirect = $event->getData()['redirect'] ?: $redirect;
  164. }
  165. redirect($redirect)->send();
  166. }
  167. public function jsapiNotifyUrl()
  168. {
  169. $this->log($_POST, '支付宝支付2.0');
  170. if ($_POST['sign_type'] == 'MD5') {
  171. $verify_result = $this->getSignResult();
  172. } else {
  173. $verify_result = $this->get_Jsapi_RSA2_SignResult($_POST);
  174. }
  175. \Log::debug(sprintf('支付回调验证结果[%d]', intval($verify_result)));
  176. $verify_result = true;
  177. if ($verify_result) {
  178. if ($_POST['trade_status'] == 'TRADE_SUCCESS') {
  179. if ($_POST['sign_type'] == 'RSA2') {
  180. if (strpos($_POST['out_trade_no'], '_') !== false) {
  181. $trade_no = explode('_', $_POST['out_trade_no']);
  182. $out_trade_no = $trade_no[0];
  183. } else {
  184. $out_trade_no = $_POST['out_trade_no'];
  185. }
  186. } else {
  187. $out_trade_no = $_POST['out_trade_no'];
  188. }
  189. $total_key = $this->total_fee[$_POST['sign_type']];
  190. $data = [
  191. 'total_fee' => $_POST[$total_key],
  192. 'out_trade_no' => $out_trade_no,
  193. 'trade_no' => $_POST['trade_no'],
  194. 'unit' => 'yuan',
  195. 'pay_type' => $this->sign_type[$_POST['sign_type']],
  196. 'pay_type_id' => PayType::ALIPAY_JSAPI_PAY
  197. ];
  198. $this->alipayPayResult($data, $trade_no[2]);
  199. $this->payResutl($data);
  200. }
  201. echo "success";
  202. } else {
  203. echo "fail";
  204. }
  205. }
  206. /**
  207. * 门店POS分账功能
  208. * @param $result
  209. * @param $royalty
  210. */
  211. public function alipayPayResult($result, $royalty)
  212. {
  213. $orderPay = OrderPay::where('pay_sn', $result['out_trade_no'])->first();
  214. $order = $orderPay->orders->first();
  215. $store_order = StoreOrder::where('order_id', $order->id)->first();
  216. $store_id = $store_order->store_id;
  217. request()->offsetSet('store_id', $store_id);
  218. $data = [
  219. 'uniacid' => \Yunshop::app()->uniacid,
  220. 'order_id' => $order->id,
  221. 'order_sn' => $order->order_sn,
  222. 'member_id' => $order->uid,
  223. 'account_id' => request()->store_id,
  224. 'pay_sn' => $result['out_trade_no'],
  225. 'trade_no' => $result['trade_no'],
  226. 'total_amount' => $result['total_fee'],
  227. 'royalty' => $royalty,
  228. ];
  229. AlipayPayOrder::create($data);
  230. }
  231. //判断返回的数据是否是json格式
  232. protected function is_json($string)
  233. {
  234. json_decode($string);
  235. return (json_last_error() == JSON_ERROR_NONE);
  236. }
  237. public function refundNotifyUrl()
  238. {
  239. \Log::debug('支付宝退款回调');
  240. $this->refundLog($_POST, '支付宝退款');
  241. $verify_result = $this->getSignResult();
  242. \Log::debug(sprintf('支付回调验证结果[%d]', intval($verify_result)));
  243. if ($verify_result) {
  244. if ($_POST['success_num'] >= 1) {
  245. $plits = explode('^', $_POST['result_details']);
  246. if ($plits[2] == 'SUCCESS') {
  247. $data = [
  248. 'total_fee' => $plits[1],
  249. 'trade_no' => $plits[0],
  250. 'unit' => 'yuan',
  251. 'pay_type' => '支付宝',
  252. 'batch_no' => $_POST['batch_no']
  253. ];
  254. $this->refundResutl($data);
  255. }
  256. }
  257. echo "success";
  258. } else {
  259. echo "fail";
  260. }
  261. }
  262. public function withdrawNotifyUrl()
  263. {
  264. $data = [];
  265. \Log::debug('支付宝提现回调');
  266. $this->withdrawLog($_POST, '支付宝提现');
  267. $verify_result = $this->getSignResult();
  268. \Log::debug(sprintf('支付回调验证结果[%d]', intval($verify_result)));
  269. if ($verify_result) {
  270. if ($_POST['success_details']) {
  271. $post_success_details = explode('|', rtrim($_POST['success_details'], '|'));
  272. foreach ($post_success_details as $success_details) {
  273. $plits = explode('^', $success_details);
  274. if ($plits[4] == 'S') {
  275. $data[] = [
  276. 'total_fee' => $plits[3],
  277. 'trade_no' => $plits[0],
  278. 'unit' => 'yuan',
  279. 'pay_type' => '支付宝'
  280. ];
  281. }
  282. }
  283. $this->withdrawResutl($data);
  284. } elseif ($_POST['fail_details']) {
  285. $post_fail_details = explode('|', rtrim($_POST['fail_details'], '|'));
  286. foreach ($post_fail_details as $fail_details) {
  287. $plits = explode('^', $fail_details);
  288. if ($plits[4] == 'F') {
  289. $data[] = [
  290. 'total_fee' => $plits[3],
  291. 'trade_no' => $plits[0],
  292. ];
  293. }
  294. }
  295. $this->withdrawFailResutl($data);
  296. }
  297. echo "success";
  298. } else {
  299. echo "fail";
  300. }
  301. }
  302. /**
  303. * 签名验证
  304. *
  305. * @return bool
  306. */
  307. public function getSignResult()
  308. {
  309. \Log::debug(sprintf('Uniacid[%d]', \YunShop::app()->uniacid));
  310. $key = \Setting::get('alipay-web.key');
  311. \Log::debug(sprintf('$key %s', $key));
  312. $alipay = app('alipay.web');
  313. $alipay->setSignType('MD5');
  314. $alipay->setKey($key);
  315. return $alipay->verify();
  316. }
  317. /**
  318. * app签名验证
  319. *
  320. * @return bool
  321. */
  322. public function get_RSA_SignResult($params)
  323. {
  324. $sign = $params['sign'];
  325. $params['sign_type'] = null;
  326. $params['sign'] = null;
  327. return $this->verify($this->getSignContent($params), $sign);
  328. }
  329. /**
  330. * app2.0签名验证
  331. *
  332. * @return bool
  333. */
  334. public function get_RSA2_SignResult($params)
  335. {
  336. $sign = $params['sign'];
  337. $params['sign_type'] = null;
  338. $params['sign'] = null;
  339. return $this->verify2($this->getSignContent($params), $sign);
  340. }
  341. /**
  342. * app2.0签名验证
  343. *
  344. * @return bool
  345. */
  346. public function get_Jsapi_RSA2_SignResult($params)
  347. {
  348. $sign = $params['sign'];
  349. $params['sign_type'] = null;
  350. $params['sign'] = null;
  351. return $this->verify3($this->getSignContent($params), $sign);
  352. }
  353. /**
  354. * 通过支付宝公钥验证回调信息
  355. *
  356. * @param $data
  357. * @param $sign
  358. * @return bool
  359. */
  360. function verify($data, $sign)
  361. {
  362. $alipay_sign_public = \Setting::get('shop_app.pay.alipay_sign_public');
  363. //如果isnewalipay为1,则为rsa2支付类型
  364. $isnewalipay = \Setting::get('shop_app.pay.newalipay');
  365. if (!$this->checkEmpty($alipay_sign_public)) {
  366. $res = "-----BEGIN PUBLIC KEY-----\n" .
  367. wordwrap($alipay_sign_public, 64, "\n", true) .
  368. "\n-----END PUBLIC KEY-----";
  369. }
  370. ($res) or die('支付宝RSA公钥错误。请检查公钥文件格式是否正确');
  371. //调用openssl内置方法验签,返回bool值
  372. $result = (bool)openssl_verify($data, base64_decode($sign), $res, OPENSSL_ALGO_SHA256);
  373. openssl_free_key($res);
  374. return $result;
  375. }
  376. /**
  377. * 通过支付宝公钥验证回调信息
  378. *
  379. * @param $data
  380. * @param $sign
  381. * @return bool
  382. */
  383. function verify2($data, $sign)
  384. {
  385. $set = \Setting::get('shop.pay');
  386. $alipay_sign_public = decrypt($set['rsa_public_key']);
  387. //如果isnewalipay为1,则为rsa2支付类型
  388. if (!$this->checkEmpty($alipay_sign_public)) {
  389. $res = "-----BEGIN PUBLIC KEY-----\n" .
  390. wordwrap($alipay_sign_public, 64, "\n", true) .
  391. "\n-----END PUBLIC KEY-----";
  392. }
  393. ($res) or die('支付宝RSA公钥错误。请检查公钥文件格式是否正确');
  394. //调用openssl内置方法验签,返回bool值
  395. $result = (bool)openssl_verify($data, base64_decode($sign), $res, OPENSSL_ALGO_SHA256);
  396. openssl_free_key($res);
  397. return $result;
  398. }
  399. /**
  400. * 通过支付宝公钥验证回调信息
  401. *
  402. * @param $data
  403. * @param $sign
  404. * @return bool
  405. */
  406. function verify3($data, $sign)
  407. {
  408. $res = '';
  409. $set = \Setting::get('shop.alipay_set');
  410. $alipay_sign_public = $set['alipay_public_key'];
  411. //如果isnewalipay为1,则为rsa2支付类型
  412. if (!$this->checkEmpty($alipay_sign_public)) {
  413. $res = "-----BEGIN PUBLIC KEY-----\n" .
  414. wordwrap($alipay_sign_public, 64, "\n", true) .
  415. "\n-----END PUBLIC KEY-----";
  416. }
  417. ($res) or die('支付宝RSA公钥错误。请检查公钥文件格式是否正确');
  418. //调用openssl内置方法验签,返回bool值
  419. $result = (bool)openssl_verify($data, base64_decode($sign), $res, OPENSSL_ALGO_SHA256);
  420. if (!$this->checkEmpty($alipay_sign_public)) {
  421. //释放资源
  422. openssl_free_key($res);
  423. }
  424. return $result;
  425. }
  426. /**
  427. * 验证数组重组
  428. *
  429. * @param $params
  430. * @return string
  431. */
  432. public function getSignContent($params)
  433. {
  434. ksort($params);
  435. $stringToBeSigned = "";
  436. $i = 0;
  437. foreach ($params as $k => $v) {
  438. if (false === $this->checkEmpty($v) && "@" != substr($v, 0, 1)) {
  439. // 转换成目标字符集
  440. $v = $this->characet($v, 'UTF-8');
  441. if ($i == 0) {
  442. $stringToBeSigned .= "$k" . "=" . "$v";
  443. } else {
  444. $stringToBeSigned .= "&" . "$k" . "=" . "$v";
  445. }
  446. $i++;
  447. }
  448. }
  449. unset ($k, $v);
  450. return $stringToBeSigned;
  451. }
  452. /**
  453. * 校验$value是否非空
  454. * if not set ,return true;
  455. * if is null , return true;
  456. **/
  457. protected function checkEmpty($value)
  458. {
  459. if (!isset($value))
  460. return true;
  461. if ($value === null)
  462. return true;
  463. if (trim($value) === "")
  464. return true;
  465. return false;
  466. }
  467. /**
  468. * 转换字符集编码
  469. * @param $data
  470. * @param $targetCharset
  471. * @return string
  472. */
  473. function characet($data, $targetCharset)
  474. {
  475. if (!empty($data)) {
  476. $fileType = $this->fileCharset;
  477. if (strcasecmp($fileType, $targetCharset) != 0) {
  478. $data = mb_convert_encoding($data, $targetCharset, $fileType);
  479. // $data = iconv($fileType, $targetCharset.'//IGNORE', $data);
  480. }
  481. }
  482. return $data;
  483. }
  484. /**
  485. * 响应日志
  486. *
  487. * @param $post
  488. */
  489. public function log($post, $desc)
  490. {
  491. //访问记录
  492. Pay::payAccessLog();
  493. //保存响应数据
  494. Pay::payResponseDataLog($post['out_trade_no'], $desc, json_encode($post));
  495. }
  496. public function refundLog($post, $desc)
  497. {
  498. //访问记录
  499. Pay::payAccessLog();
  500. //保存响应数据
  501. Pay::payResponseDataLog(0, $desc, json_encode($post));
  502. }
  503. public function withdrawLog($post, $desc)
  504. {
  505. //访问记录
  506. Pay::payAccessLog();
  507. //保存响应数据
  508. Pay::payResponseDataLog($post['batch_no'], $desc, json_encode($post));
  509. }
  510. /**
  511. * 支付宝退款回调操作
  512. *
  513. * @param $data
  514. */
  515. public function refundResutl($data)
  516. {
  517. \Log::debug('退款操作', 'refund.succeeded');
  518. $pay_order = PayOrder::getPayOrderInfoByTradeNo($data['trade_no'])->first();
  519. if (!$pay_order) {
  520. return \Log::error('未找到退款订单支付信息', $data);
  521. }
  522. $pay_refund_model = PayRefundOrder::getOrderInfo($pay_order->out_order_no);
  523. if (!$pay_refund_model) {
  524. return \Log::error('退款订单支付信息保存失败', $data);
  525. }
  526. $pay_refund_model->status = 2;
  527. $pay_refund_model->trade_no = $pay_refund_model->trade_no;
  528. $pay_refund_model->type = $data['pay_type'];
  529. $pay_refund_model->save();
  530. $refundApply = RefundApply::where('alipay_batch_sn', $data['batch_no'])->first();
  531. if (!isset($refundApply)) {
  532. return \Log::error('订单退款信息不存在', $data);
  533. }
  534. if (!(bccomp($refundApply->price, $data['total_fee'], 2) == 0)) {
  535. return \Log::error("订单退款金额错误(订单金额:{$refundApply->price}|退款金额:{$data['total_fee']})|比较结果:" . bccomp($refundApply->price, $data['total_fee'], 2) . ")");
  536. }
  537. \Log::debug('订单退款(退款申请id:' . $refundApply->id . ',订单id:' . $refundApply->order_id . ')');
  538. RefundOperationService::refundComplete(['id' => $refundApply->id]);
  539. }
  540. /**
  541. * 支付宝提现回调操作
  542. *
  543. * @param $data
  544. */
  545. public function withdrawResutl($params)
  546. {
  547. if (!empty($params)) {
  548. foreach ($params as $data) {
  549. $pay_refund_model = PayWithdrawOrder::getOrderInfo($data['trade_no']);
  550. if ($pay_refund_model) {
  551. $pay_refund_model->status = 2;
  552. $pay_refund_model->trade_no = $data['trade_no'];
  553. $pay_refund_model->save();
  554. }
  555. \Log::debug('提现操作', 'withdraw.succeeded');
  556. if (bccomp($pay_refund_model->price, $data['total_fee'], 2) == 0) {
  557. Withdraw::paySuccess($data['trade_no']);
  558. event(new AlipayWithdrawEvent($data['trade_no']));
  559. }
  560. }
  561. }
  562. }
  563. public function withdrawFailResutl($params)
  564. {
  565. $trade_no = [];
  566. if (!empty($params)) {
  567. foreach ($params as $data) {
  568. $pay_refund_model = PayWithdrawOrder::getOrderInfo($data['trade_no']);
  569. if ($pay_refund_model) {
  570. \Log::debug('提现操作', 'withdraw.failed');
  571. if (bccomp($pay_refund_model->price, $data['total_fee'], 2) == 0) {
  572. Withdraw::payFail($data['trade_no']);
  573. }
  574. }
  575. }
  576. }
  577. }
  578. }