Pay.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. <?php
  2. namespace addons\shopro\controller;
  3. use addons\epay\library\Service;
  4. use fast\Random;
  5. use think\addons\Controller;
  6. use addons\shopro\exception\Exception;
  7. use addons\shopro\model\Order;
  8. use addons\shopro\model\User;
  9. use addons\shopro\model\TradeOrder;
  10. use think\Db;
  11. use think\Log;
  12. class Pay extends Base
  13. {
  14. protected $noNeedLogin = ['prepay', 'notifyx', 'notifyr', 'alipay'];
  15. protected $noNeedRight = ['*'];
  16. /**
  17. * 支付宝网页支付
  18. */
  19. public function alipay()
  20. {
  21. $order_sn = $this->request->get('order_sn');
  22. list($order, $prepay_type) = $this->getOrderInstance($order_sn);
  23. $order = $order->where('order_sn', $order_sn)->find();
  24. try {
  25. if (!$order) {
  26. throw new \Exception("订单不存在");
  27. }
  28. if ($order->status > 0) {
  29. throw new \Exception("订单已支付");
  30. }
  31. if ($order->status < 0) {
  32. throw new \Exception("订单已失效");
  33. }
  34. $notify_url = $this->request->root(true) . '/addons/shopro/pay/notifyx/payment/alipay/platform/H5';
  35. $pay = new \addons\shopro\library\PayService('alipay', 'url', $notify_url);
  36. $order_data = [
  37. 'order_id' => $order->id,
  38. 'out_trade_no' => $order->order_sn,
  39. 'total_fee' => $order->total_fee,
  40. 'subject' => '商城订单支付',
  41. ];
  42. $result = $pay->create($order_data);
  43. $result = $result->getContent();
  44. echo $result;
  45. } catch (\Exception $e) {
  46. echo $e->getMessage();
  47. }
  48. // $this->assign('result', $result);
  49. // return $this->view->fetch();
  50. }
  51. /**
  52. * 拉起支付
  53. */
  54. public function prepay()
  55. {
  56. checkEnv('yansongda');
  57. $order_sn = $this->request->post('order_sn');
  58. $payment = $this->request->post('payment');
  59. $openid = $this->request->post('openid', '');
  60. $platform = request()->header('platform');
  61. list($order, $prepay_type) = $this->getOrderInstance($order_sn);
  62. $order = $order->nopay()->where('order_sn', $order_sn)->find();
  63. if (!$order) {
  64. $this->error("订单不存在");
  65. }
  66. if (!$payment || !in_array($payment, ['wechat', 'alipay', 'wallet'])) {
  67. $this->error("支付类型不能为空");
  68. }
  69. if ($payment == 'wallet' && $prepay_type == 'order') {
  70. // 余额支付
  71. $this->walletPay($order, $payment, $platform);
  72. }
  73. $order_data = [
  74. 'order_id' => $order->id,
  75. 'out_trade_no' => $order->order_sn,
  76. 'total_fee' => $order->total_fee,
  77. ];
  78. // 微信公众号,小程序支付,必须有 openid
  79. if ($payment == 'wechat') {
  80. if (in_array($platform, ['wxOfficialAccount', 'wxMiniProgram'])) {
  81. if (isset($openid) && $openid) {
  82. // 如果传的有 openid
  83. $order_data['openid'] = $openid;
  84. } else {
  85. // 没有 openid 默认拿下单人的 openid
  86. $oauth = \addons\shopro\model\UserOauth::where([
  87. 'user_id' => $order->user_id,
  88. 'provider' => 'Wechat',
  89. 'platform' => $platform
  90. ])->find();
  91. $order_data['openid'] = $oauth ? $oauth->openid : '';
  92. }
  93. if (empty($order_data['openid'])) {
  94. // 缺少 openid
  95. return $this->success('缺少 openid', 'no_openid');
  96. }
  97. }
  98. $order_data['body'] = '商城订单支付';
  99. } else {
  100. $order_data['subject'] = '商城订单支付';
  101. }
  102. $notify_url = $this->request->root(true) . '/addons/shopro/pay/notifyx/payment/' . $payment . '/platform/' . $platform;
  103. $pay = new \addons\shopro\library\PayService($payment, $platform, $notify_url);
  104. try {
  105. $result = $pay->create($order_data);
  106. } catch (\Yansongda\Pay\Exceptions\Exception $e) {
  107. $this->error("支付配置错误:" . $e->getMessage());
  108. }
  109. if ($platform == 'App') {
  110. $result = $result->getContent();
  111. }
  112. if ($platform == 'H5' && $payment == 'wechat') {
  113. $result = $result->getContent();
  114. }
  115. return $this->success('获取预付款成功', [
  116. 'pay_data' => $result,
  117. 'pay_action' => $pay->method,
  118. ]);
  119. }
  120. // 余额支付
  121. public function walletPay ($order, $type, $method) {
  122. $order = Db::transaction(function () use ($order, $type, $method) {
  123. // 重新加锁读,防止连点问题
  124. $order = Order::nopay()->where('order_sn', $order->order_sn)->lock(true)->find();
  125. if (!$order) {
  126. $this->error("订单已支付");
  127. }
  128. $total_fee = $order->total_fee;
  129. // 扣除余额
  130. $user = User::info();
  131. if (is_null($user)) {
  132. // 没有登录,请登录
  133. $this->error(__('Please login first'), null, 401);
  134. }
  135. User::money(-$total_fee, $user->id, 'wallet_pay', $order->id, '',[
  136. 'order_id' => $order->id,
  137. 'order_sn' => $order->order_sn,
  138. ]);
  139. // 支付后流程
  140. $notify = [
  141. 'order_sn' => $order['order_sn'],
  142. 'transaction_id' => '',
  143. 'notify_time' => date('Y-m-d H:i:s'),
  144. 'buyer_email' => $user->id,
  145. 'pay_fee' => $order->total_fee,
  146. 'pay_type' => 'wallet' // 支付方式
  147. ];
  148. $notify['payment_json'] = json_encode($notify);
  149. $order->paymentProcess($order, $notify);
  150. return $order;
  151. });
  152. $this->success('支付成功', $order);
  153. }
  154. /**
  155. * 支付成功回调
  156. */
  157. public function notifyx()
  158. {
  159. Log::write('notifyx-comein:');
  160. $payment = $this->request->param('payment');
  161. $platform = $this->request->param('platform');
  162. $pay = new \addons\shopro\library\PayService($payment, $platform);
  163. $result = $pay->notify(function ($data, $pay) use ($payment, $platform) {
  164. Log::write('notifyx-result:'. json_encode($data));
  165. // { // 微信回调参数
  166. // "appid":"wx39cd0799d4567dd0",
  167. // "bank_type":"OTHERS",
  168. // "cash_fee":"1",
  169. // "fee_type":"CNY",
  170. // "is_subscribe":"N",
  171. // "mch_id":"1481069012",
  172. // "nonce_str":"dPpcZ6AzCDU8daNC",
  173. // "openid":"oD9ko4x7QMDQPZEuN8V5jtZjie3g",
  174. // "out_trade_no":"202010240834057843002700",
  175. // "result_code":"SUCCESS",
  176. // "return_code":"SUCCESS",
  177. // "sign":"3103B6D06F13D2B3959C5B3F7F1FD61C",
  178. // "time_end":"20200407102424",
  179. // "total_fee":"1",
  180. // "trade_type":"JSAPI",
  181. // "transaction_id":"4200000479202004070804485027"
  182. // }
  183. // { // 支付宝支付成功回调参数
  184. // "gmt_create":"2020-04-10 19:15:00",
  185. // "charset":"utf-8",
  186. // "seller_email":"xptech@qq.com",
  187. // "subject":"\u5546\u57ce\u8ba2\u5355\u652f\u4ed8",
  188. // "sign":"AOiYZ1a2mEMOuIbHFCi6V6A0LJ97UMiHsCWgNdSU9dlzKFl15Ts8b0mL\/tN+Hhskl+94S3OUiNTBD3dD0Kv923SqaTWxNdj533PCdo2GDKsZIZgKbavnOvaccSKUdmQRE9KtmePPq9V9lFzEQvdUkKq1M8KAWO5K9LTy2iT2y5CUynpiu\/04GVzsTL9PqY+LDwqj6K+w7MgceWm1BWaFWg27AXIRw7wvsFckr3k9GGajgE2fufhoCYGYtGFbhGOp6ExtqS5RXBuPODOyRhBLpD8mwpOX38Oy0X+R4YQIrOi02dhqwPpvw79YjnvgXY3qZEQ66EdUsrT7EBdcPHK0Gw==",
  189. // "buyer_id":"2088902485164146",
  190. // "invoice_amount":"0.01",
  191. // "notify_id":"2020041000222191501064141414240102",
  192. // "fund_bill_list":"[{\"amount\":\"0.01\",\"fundChannel\":\"PCREDIT\"}]",
  193. // "notify_type":"trade_status_sync",
  194. // "trade_status":"TRADE_SUCCESS",
  195. // "receipt_amount":"0.01",
  196. // "buyer_pay_amount":"0.01",
  197. // "app_id":"2021001114666742",
  198. // "sign_type":"RSA2",
  199. // "seller_id":"2088721922277739",
  200. // "gmt_payment":"2020-04-10 19:15:00",
  201. // "notify_time":"2020-04-10 19:15:01",
  202. // "version":"1.0",
  203. // "out_trade_no":"202007144778322770017000",
  204. // "total_amount":"0.01",
  205. // "trade_no":"2020041022001464141443020240",
  206. // "auth_app_id":"2021001114666742",
  207. // "buyer_logon_id":"157***@163.com",
  208. // "point_amount":"0.00"
  209. // }
  210. // { // 支付宝退款成功(交易关闭)回调参数
  211. // "gmt_create": "2020-08-15 14:48:32",
  212. // "charset": "utf-8",
  213. // "seller_email": "xptech@qq.com",
  214. // "gmt_payment": "2020-08-15 14:48:32",
  215. // "notify_time": "2020-08-15 16:11:45",
  216. // "subject": "商城订单支付",
  217. // "gmt_refund": "2020-08-15 16:11:45.140",
  218. // "sign": "b6ArkgzLIRteRL9FMGC6i\/jf6VwFYQbaGDGRx002W+pdmN5q9+O4edZ3ALF74fYaijSl9ksNr0dKdvanu3uYxBTcd\/GIS4N1CWzmCOv6pzgx5rO\/YvGoHLM3Yop0GKKuMxmnNsZ6jhYKEY7SYD3Y0L6PU9ZMdHV7yIiVj+zZmbKzUgK9MPDCEXs+nzpNAiSM8GTqYRSUvKobAK68hswG2k1QIcqr5z+ZmVYa\/nHHkoC9qXt5zwyGi4P+2eOsr6V2PjA3x8qqe7TN5aI1DeoZD5KqHPYYaYF17J2q6YPlgl3WUl1RhE7H86bivB1fIuYEv\/3+JR74WN\/o7krGw1RPHg==",
  219. // "out_biz_no": "R202004114414846255015300",
  220. // "buyer_id": "2088902485164146",
  221. // "version": "1.0",
  222. // "notify_id": "2020081500222161145064141453349793",
  223. // "notify_type": "trade_status_sync",
  224. // "out_trade_no": "202002460317545607015300",
  225. // "total_amount": "0.01",
  226. // "trade_status": "TRADE_CLOSED",
  227. // "refund_fee": "0.01",
  228. // "trade_no": "2020081522001464141438570535",
  229. // "auth_app_id": "2021001114666742",
  230. // "buyer_logon_id": "157***@163.com",
  231. // "gmt_close": "2020-08-15 16:11:45",
  232. // "app_id": "2021001114666742",
  233. // "sign_type": "RSA2",
  234. // "seller_id": "2088721922277739"
  235. // }
  236. try {
  237. $out_trade_no = $data['out_trade_no'];
  238. $out_refund_no = $data['out_biz_no'] ?? '';
  239. list($order, $prepay_type) = $this->getOrderInstance($out_trade_no);
  240. // 判断是否是支付宝退款(支付宝退款成功会通知该接口)
  241. if ($payment == 'alipay' // 支付宝支付
  242. && $data['notify_type'] == 'trade_status_sync' // 同步交易状态
  243. && $data['trade_status'] == 'TRADE_CLOSED' // 交易关闭
  244. && $out_refund_no // 退款单号
  245. ) {
  246. // 退款回调
  247. if ($prepay_type == 'order') {
  248. // 退款回调
  249. $this->refundFinish($out_trade_no, $out_refund_no);
  250. } else {
  251. // 其他订单如果支持退款,逻辑这里补充
  252. }
  253. return $pay->success()->send();
  254. }
  255. // 判断支付宝微信是否是支付成功状态,如果不是,直接返回响应
  256. if ($payment == 'alipay' && $data['trade_status'] != 'TRADE_SUCCESS') {
  257. // 不是交易成功的通知,直接返回成功
  258. return $pay->success()->send();
  259. }
  260. if ($payment == 'wechat' && ($data['result_code'] != 'SUCCESS' || $data['return_code'] != 'SUCCESS')) {
  261. // 微信交易未成功,返回 false,让微信再次通知
  262. return false;
  263. }
  264. // 支付成功流程
  265. $pay_fee = $payment == 'alipay' ? $data['total_amount'] : $data['total_fee'] / 100;
  266. //你可以在此编写订单逻辑
  267. $order = $order->where('order_sn', $out_trade_no)->find();
  268. if (!$order || $order->status > 0) {
  269. // 订单不存在,或者订单已支付
  270. return $pay->success()->send();
  271. }
  272. Db::transaction(function () use ($order, $data, $payment, $platform, $pay_fee, $prepay_type) {
  273. $notify = [
  274. 'order_sn' => $data['out_trade_no'],
  275. 'transaction_id' => $payment == 'alipay' ? $data['trade_no'] : $data['transaction_id'],
  276. 'notify_time' => date('Y-m-d H:i:s', strtotime($data['time_end'])),
  277. 'buyer_email' => $payment == 'alipay' ? $data['buyer_logon_id'] : $data['openid'],
  278. 'payment_json' => json_encode($data->all()),
  279. 'pay_fee' => $pay_fee,
  280. 'pay_type' => $payment // 支付方式
  281. ];
  282. $order->paymentProcess($order, $notify);
  283. });
  284. return $pay->success()->send();
  285. } catch (\Exception $e) {
  286. Log::write('notifyx-error:' . json_encode($e->getMessage()));
  287. }
  288. });
  289. return $result;
  290. }
  291. /**
  292. * 退款成功回调
  293. */
  294. public function notifyr()
  295. {
  296. Log::write('notifyreturn-comein:');
  297. $payment = $this->request->param('payment');
  298. $platform = $this->request->param('platform');
  299. $pay = new \addons\shopro\library\PayService($payment, $platform);
  300. $result = $pay->notifyRefund(function ($data, $pay) use ($payment, $platform) {
  301. Log::write('notifyr-result:' . json_encode($data));
  302. try {
  303. $out_refund_no = $data['out_refund_no'];
  304. $out_trade_no = $data['out_trade_no'];
  305. // 退款
  306. $this->refundFinish($out_trade_no, $out_refund_no);
  307. return $pay->success()->send();
  308. } catch (\Exception $e) {
  309. Log::write('notifyreturn-error:' . json_encode($e->getMessage()));
  310. }
  311. });
  312. return $result;
  313. }
  314. private function refundFinish($out_trade_no, $out_refund_no) {
  315. $order = Order::where('order_sn', $out_trade_no)->find();
  316. $refundLog = \app\admin\model\shopro\order\RefundLog::where('refund_sn', $out_refund_no)->find();
  317. if (!$order || !$refundLog || $refundLog->status != 0) {
  318. // 订单不存在,或者订单已退款
  319. return true;
  320. }
  321. $item = \app\admin\model\shopro\order\OrderItem::where('id', $refundLog->order_item_id)->find();
  322. Db::transaction(function () use ($order, $item, $refundLog) {
  323. \app\admin\model\shopro\order\Order::refundFinish($order, $item, $refundLog);
  324. });
  325. return true;
  326. }
  327. /**
  328. * 根据订单号获取订单实例
  329. *
  330. * @param [type] $order_sn
  331. * @return void
  332. */
  333. private function getOrderInstance($order_sn)
  334. {
  335. $prepay_type = 'order';
  336. if (strpos($order_sn, 'TO') === 0) {
  337. // 充值订单
  338. $prepay_type = 'recharge';
  339. $order = new TradeOrder();
  340. } else {
  341. // 订单
  342. $order = new Order();
  343. }
  344. return [$order, $prepay_type];
  345. }
  346. }