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

253 lines
7.8 KiB

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