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

233 lines
6.9 KiB

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