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

263 lines
8.3 KiB

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