海南旅游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.

274 lines
8.7 KiB

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