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

265 lines
8.4 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) { //订金超时
  90. $time = $agent_product->deposit_timeout * 60;
  91. } else if ($pay_type == PayType::EARNEST_PAY) { //定金超时
  92. $time = $agent_product->earnest_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. } else {
  99. $order->timeout = null;
  100. }
  101. $order->save();
  102. //资金流水
  103. UserMoneyLog::query()->create([
  104. 'user_id' => $order->user_id,
  105. 'agent_id' => $order->agent_id,
  106. 'money' => $money,
  107. 'order_id' => $order->id,
  108. 'type' => 1,
  109. 'desc' => DB::raw("LEFT('购买产品:{$order->title}', 250)"),
  110. 'transaction_id' => $message['transaction_id'], //微信支付订单号
  111. 'created_at' => now(), //模型没有updated_at,无法自动写入时间
  112. 'out_trade_no' => $message['out_trade_no'] ?? '',
  113. ]);
  114. DB::commit();
  115. return true;
  116. } catch (Exception $e) {
  117. DB::rollBack();
  118. $fail('Unknown error');
  119. }
  120. } // 支付失败
  121. else if ($message['result_code'] === 'FAIL') {
  122. return true;
  123. }
  124. }
  125. // 希望微信重试
  126. $fail('Unknown error 2');
  127. });
  128. } catch (InvalidSignException | Exception $e) {
  129. $this->log($e->getMessage() . $e->getFile() . $e->getLine());
  130. return 'error';
  131. }
  132. return $response;
  133. }
  134. //退款通知
  135. public function refund()
  136. {
  137. $agent_id = request()->route('agent_id');
  138. // $agent = Agent::find($agent_id);
  139. $setting = AdminSetting::val(['payee_appid', 'payee_mchid', 'payee_mchkey']);
  140. if (!isset($setting['payee_appid'], $setting['payee_mchid'], $setting['payee_mchkey'])) {
  141. return '获取系统配置失败';
  142. }
  143. $config = config('wechat.payment.default');
  144. $config = array_merge($config, [
  145. 'app_id' => $setting['payee_appid'],
  146. 'mch_id' => $setting['payee_mchid'],
  147. 'key' => $setting['payee_mchkey'],
  148. ]);
  149. $app = Factory::payment($config);
  150. try {
  151. $response = $app->handleRefundedNotify(function ($message, $reqInfo, $fail) use ($agent_id) {
  152. //TODO 仅测试用
  153. DB::table('pay_debugs')->insert(['agent_id' => $agent_id, 'type' => 2, 'content' => json_encode($message)]);
  154. // 记录一下本地调试
  155. $this->log(['message' => $message, 'reqInfo' => $reqInfo], 'refund');
  156. // 请求成功
  157. if ($message['return_code'] === 'SUCCESS') {
  158. //主要是为了区分定金支付和尾款支付,订单号带有-status后缀,分割后前面才是真正的订单号
  159. $order_no = explode('-', $reqInfo['out_trade_no'])[0];
  160. $order = Order::query()
  161. ->where(['order_no' => $order_no])
  162. ->first();
  163. //如果已经处理过则不再处理
  164. if ($order->status == OrderStatus::REFUNDED) {
  165. return true;
  166. }
  167. //如果已经存在相关的退款记录,也不再处理
  168. $exist_log = UserMoneyLog::where([
  169. 'user_id' => $order->user_id,
  170. 'order_id' => $order->id,
  171. 'type' => 2,
  172. 'transaction_id' => $reqInfo['transaction_id'],
  173. ])->first();
  174. if ($exist_log) {
  175. return true;
  176. }
  177. $log = UserMoneyLog::query()
  178. ->where([
  179. 'user_id' => $order->user_id,
  180. 'order_id' => $order->id,
  181. 'type' => 1,
  182. 'transaction_id' => $reqInfo['transaction_id'],
  183. ])->first();
  184. if (!$log) {
  185. $fail('找不到交易信息');
  186. }
  187. // 退款成功
  188. if ($reqInfo['refund_status'] === 'SUCCESS') {
  189. DB::beginTransaction();
  190. try {
  191. //更新订单状态为退款成功
  192. $order->status = OrderStatus::REFUNDED;
  193. $order->save();
  194. //记录日志
  195. UserMoneyLog::create([
  196. 'user_id' => $order->user_id,
  197. 'agent_id' => $order->agent_id,
  198. 'money' => -$reqInfo['settlement_refund_fee'] / 100,
  199. 'order_id' => $order->id,
  200. 'type' => 2,
  201. 'desc' => DB::raw("LEFT('退款:{$order->title}', 250)"),
  202. 'transaction_id' => $reqInfo['transaction_id'],
  203. 'created_at' => now(), //模型没有updated_at,无法自动写入时间
  204. ]);
  205. // 退库存
  206. Product::query()
  207. ->where('id', $order->product_id)
  208. ->increment('stock', $order->num);
  209. DB::commit();
  210. return true;
  211. } catch (\Exception $e) {
  212. DB::rollBack();
  213. $fail('Unknown error');
  214. }
  215. }
  216. }
  217. $fail('Unknown error 2');
  218. });
  219. } catch (Exception $e) {
  220. return 'error';
  221. }
  222. return $response;
  223. }
  224. //保存消息,用于调试
  225. private function log($write_data, $type = 'notify')
  226. {
  227. $dir = storage_path('wxpay');
  228. if (!is_dir($dir)) {
  229. mkdir($dir);
  230. }
  231. $filename = $dir . '/' . $type . '_' . date('Y-m-d-H') . '.log';
  232. $data = '[' . date('Y-m-d H:i:s') . ']' . PHP_EOL;
  233. $data .= '[message]: ' . (is_array($write_data) ? json_encode($write_data) : $write_data) . PHP_EOL . PHP_EOL;
  234. file_put_contents($filename, $data, FILE_APPEND);
  235. }
  236. }