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

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