海南旅游SAAS
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

277 lines
8.9 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. <?php
  2. namespace App\Http\Controllers\Api;
  3. use App\Common\PayType;
  4. use App\Jobs\BalanceDue;
  5. use App\Jobs\OrderTimeout;
  6. use App\Models\AdminSetting;
  7. use App\Models\Agent;
  8. use App\Models\AgentProduct;
  9. use App\Models\AgentSetting;
  10. use App\Models\Order;
  11. use App\Models\Product;
  12. use App\Models\UserMoneyLog;
  13. use EasyWeChat\Factory;
  14. use EasyWeChat\Kernel\Exceptions\Exception;
  15. use EasyWeChat\Payment\Kernel\Exceptions\InvalidSignException;
  16. use Illuminate\Support\Facades\DB;
  17. use App\Common\OrderStatus;
  18. class WxpayController
  19. {
  20. //微信支付 支付结果通知网址
  21. public function notify()
  22. {
  23. $agent_id = request()->route('agent_id');
  24. // $agent = Agent::find($agent_id);
  25. $setting = AdminSetting::val(['payee_appid', 'payee_mchid', 'payee_mchkey']);
  26. if (!isset($setting['payee_appid'], $setting['payee_mchid'], $setting['payee_mchkey'])) {
  27. return '获取系统配置失败';
  28. }
  29. $config = config('wechat.payment.default');
  30. $config = array_merge($config, [
  31. 'app_id' => $setting['payee_appid'],
  32. 'mch_id' => $setting['payee_mchid'],
  33. 'key' => $setting['payee_mchkey'],
  34. ]);
  35. $app = Factory::payment($config);
  36. try {
  37. $response = $app->handlePaidNotify(function ($message, $fail) use ($agent_id) {
  38. //仅测试用,回调记录
  39. DB::table('pay_debugs')->insert(['agent_id' => $agent_id, 'type' => 1, 'content' => json_encode($message)]);
  40. // $this->log($message);
  41. // 请求成功
  42. if ($message['return_code'] === 'SUCCESS') {
  43. //主要是为了区分定金支付和尾款支付,订单号带有-status后缀,分割后前面才是真正的订单号
  44. $order_no = explode('-', $message['out_trade_no'])[0];
  45. $order = Order::query()
  46. ->where(['order_no' => $order_no])
  47. ->first();
  48. //已经处理过的订单直接返回true
  49. if ($order && in_array($order->status, [OrderStatus::PAID, OrderStatus::PAID_RETAINAGE, OrderStatus::SUCCESS])) {
  50. return true;
  51. }
  52. //判断该微信支付订单号有没有处理过
  53. $exist_log = UserMoneyLog::where([
  54. 'user_id' => $order->user_id,
  55. 'order_id' => $order->id,
  56. 'type' => 1,
  57. 'transaction_id' => $message['transaction_id'],
  58. ])->first();
  59. if ($exist_log) {
  60. return true;
  61. }
  62. // 支付成功
  63. if ($message['result_code'] === 'SUCCESS') {
  64. DB::beginTransaction();
  65. try {
  66. //增加销量,库存在拍下时已经减了
  67. $agent_product = AgentProduct::find($order->agent_product_id);
  68. $agent_product->increment('sale', $order->num);
  69. Product::query()
  70. ->where('id', $order->product_id)
  71. ->increment('sale', $order->num);
  72. $status = $order->status;
  73. $pay_type = $order->pay_type;
  74. $money = $message['total_fee'] / 100;
  75. //定金支付和首付款支付
  76. if (in_array($pay_type, [PayType::DEPOSIT_PAY, PayType::EARNEST_PAY, PayType::DOWN_PAYMENT])) {
  77. if ($status == OrderStatus::UNPAID) {
  78. $order->status = OrderStatus::PAY_EARNEST;
  79. } else if ($status == OrderStatus::PAY_EARNEST) {
  80. $order->status = OrderStatus::PAID_RETAINAGE;
  81. $order->verify_code = uniqid(); //生成核销码
  82. }
  83. } else if ($pay_type == PayType::ONLINE) {
  84. $order->status = OrderStatus::PAID;
  85. $order->verify_code = uniqid(); //生成核销码
  86. }
  87. $order->paid_at = now();
  88. $order->paid_money = DB::raw('`paid_money` + ' . $money);
  89. //如果是已付定金,重新设置超时时间,否则清除超时时间
  90. if ($order->status == OrderStatus::PAY_EARNEST) {
  91. if ($pay_type == PayType::DEPOSIT_PAY || $pay_type == PayType::EARNEST_PAY) { //订金超时
  92. $time = $order->prepay_timeout * 60;
  93. }
  94. if (empty($time)) { //默认订单超时
  95. $time = (AgentSetting::val($agent_id, 'order_timeout') ?? 60) * 60;
  96. }
  97. $order->timeout = date('Y-m-d H:i:s', time() + $time);
  98. //订单超时
  99. OrderTimeout::dispatch($order->order_no,$time);
  100. //尾款通知时间 默认为剩余三小时自动通知
  101. $smsEarnest = env('SMS_EARNEST',60*60*24*3);
  102. //短信通知
  103. if(env('SMS_SWITCH' , '') == true && $time > $smsEarnest) {
  104. //如果剩余时间大于三小时 在订单到期前三小时给用户发短信
  105. if ($time > $smsEarnest) {
  106. BalanceDue::dispatch($order->order_no,$time - $smsEarnest);
  107. }
  108. }
  109. } else {
  110. $order->timeout = null;
  111. }
  112. $order->save();
  113. //资金流水
  114. UserMoneyLog::query()->create([
  115. 'user_id' => $order->user_id,
  116. 'agent_id' => $order->agent_id,
  117. 'money' => $money,
  118. 'order_id' => $order->id,
  119. 'type' => 1,
  120. 'desc' => DB::raw("LEFT('购买产品:{$order->title}', 250)"),
  121. 'transaction_id' => $message['transaction_id'], //微信支付订单号
  122. 'created_at' => now(), //模型没有updated_at,无法自动写入时间
  123. 'out_trade_no' => $message['out_trade_no'] ?? '',
  124. ]);
  125. DB::commit();
  126. return true;
  127. } catch (Exception $e) {
  128. DB::rollBack();
  129. $fail('Unknown error');
  130. }
  131. } // 支付失败
  132. else if ($message['result_code'] === 'FAIL') {
  133. return true;
  134. }
  135. }
  136. // 希望微信重试
  137. $fail('Unknown error 2');
  138. });
  139. } catch (InvalidSignException | Exception $e) {
  140. $this->log($e->getMessage() . $e->getFile() . $e->getLine());
  141. return 'error';
  142. }
  143. return $response;
  144. }
  145. //退款通知
  146. public function refund()
  147. {
  148. $agent_id = request()->route('agent_id');
  149. // $agent = Agent::find($agent_id);
  150. $setting = AdminSetting::val(['payee_appid', 'payee_mchid', 'payee_mchkey']);
  151. if (!isset($setting['payee_appid'], $setting['payee_mchid'], $setting['payee_mchkey'])) {
  152. return '获取系统配置失败';
  153. }
  154. $config = config('wechat.payment.default');
  155. $config = array_merge($config, [
  156. 'app_id' => $setting['payee_appid'],
  157. 'mch_id' => $setting['payee_mchid'],
  158. 'key' => $setting['payee_mchkey'],
  159. ]);
  160. $app = Factory::payment($config);
  161. try {
  162. $response = $app->handleRefundedNotify(function ($message, $reqInfo, $fail) use ($agent_id) {
  163. //仅测试用,回调记录
  164. DB::table('pay_debugs')->insert(['agent_id' => $agent_id, 'type' => 2, 'content' => json_encode($message)]);
  165. // 记录一下本地调试
  166. // $this->log(['message' => $message, 'reqInfo' => $reqInfo], 'refund');
  167. // 请求成功
  168. if ($message['return_code'] === 'SUCCESS') {
  169. //主要是为了区分定金支付和尾款支付,订单号带有-status后缀,分割后前面才是真正的订单号
  170. $order_no = explode('-', $reqInfo['out_trade_no'])[0];
  171. $order = Order::query()
  172. ->where(['order_no' => $order_no])
  173. ->first();
  174. //如果已经处理过则不再处理
  175. if ($order->status == OrderStatus::REFUNDED) {
  176. return true;
  177. }
  178. //如果已经存在相关的退款记录,也不再处理
  179. $exist_log = UserMoneyLog::where([
  180. 'user_id' => $order->user_id,
  181. 'order_id' => $order->id,
  182. 'type' => 2,
  183. 'transaction_id' => $reqInfo['transaction_id'],
  184. ])->first();
  185. if ($exist_log) {
  186. return true;
  187. }
  188. $log = UserMoneyLog::query()
  189. ->where([
  190. 'user_id' => $order->user_id,
  191. 'order_id' => $order->id,
  192. 'type' => 1,
  193. 'transaction_id' => $reqInfo['transaction_id'],
  194. ])->first();
  195. if (!$log) {
  196. $fail('找不到交易信息');
  197. }
  198. // 退款成功
  199. if ($reqInfo['refund_status'] === 'SUCCESS') {
  200. DB::beginTransaction();
  201. try {
  202. //更新订单状态为退款成功
  203. $order->status = OrderStatus::REFUNDED;
  204. $order->save();
  205. //记录日志
  206. UserMoneyLog::create([
  207. 'user_id' => $order->user_id,
  208. 'agent_id' => $order->agent_id,
  209. 'money' => -$reqInfo['settlement_refund_fee'] / 100,
  210. 'order_id' => $order->id,
  211. 'type' => 2,
  212. 'desc' => DB::raw("LEFT('退款:{$order->title}', 250)"),
  213. 'transaction_id' => $reqInfo['transaction_id'],
  214. 'created_at' => now(), //模型没有updated_at,无法自动写入时间
  215. ]);
  216. // 退库存
  217. Product::query()
  218. ->where('id', $order->product_id)
  219. ->increment('stock', $order->num);
  220. DB::commit();
  221. return true;
  222. } catch (\Exception $e) {
  223. DB::rollBack();
  224. $fail('Unknown error');
  225. }
  226. }
  227. }
  228. $fail('Unknown error 2');
  229. });
  230. } catch (Exception $e) {
  231. $this->log($e->getMessage() . $e->getFile() . $e->getLine());
  232. return 'error';
  233. }
  234. return $response;
  235. }
  236. //保存消息,用于调试
  237. private function log($write_data, $type = 'notify')
  238. {
  239. $dir = storage_path('wxpay');
  240. if (!is_dir($dir)) {
  241. mkdir($dir);
  242. }
  243. $filename = $dir . '/' . $type . '_' . date('Y-m-d-H') . '.log';
  244. $data = '[' . date('Y-m-d H:i:s') . ']' . PHP_EOL;
  245. $data .= '[message]: ' . (is_array($write_data) ? json_encode($write_data) : $write_data) . PHP_EOL . PHP_EOL;
  246. file_put_contents($filename, $data, FILE_APPEND);
  247. }
  248. }