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

238 lines
7.1 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. //支付金额>=订单金额之后生成核销码
  73. if ($order->paid_money + $money >= $order->price) {
  74. $order->verify_code = uniqid(); //生成核销码
  75. }
  76. $order->paid_at = now();
  77. $order->paid_money = DB::raw('`paid_money` + ' . $money);
  78. $order->save();
  79. //增加销量,库存在拍下时已经减了
  80. AgentProduct::query()
  81. ->where('id', $order->agent_product_id)
  82. ->increment('sale', $order->num);
  83. Product::query()
  84. ->where('id', $order->product_id)
  85. ->increment('sale', $order->num);
  86. //资金流水
  87. UserMoneyLog::query()->create([
  88. 'user_id' => $order->user_id,
  89. 'agent_id' => $order->agent_id,
  90. 'money' => -$money,
  91. 'order_id' => $order->id,
  92. 'type' => 1,
  93. 'desc' => DB::raw("LEFT('购买产品:{$order->title}', 250)"),
  94. 'transaction_id' => $message['transaction_id'], //微信支付订单号
  95. 'created_at' => now(), //模型没有updated_at,无法自动写入时间
  96. ]);
  97. DB::commit();
  98. return true;
  99. } catch (Exception $e) {
  100. DB::rollBack();
  101. $fail('Unknown error');
  102. }
  103. } // 支付失败
  104. else if ($message['result_code'] === 'FAIL') {
  105. return true;
  106. }
  107. }
  108. // 希望微信重试
  109. $fail('Unknown error 2');
  110. });
  111. } catch (InvalidSignException | Exception $e) {
  112. $this->log($e->getMessage() . $e->getFile() . $e->getLine());
  113. return 'error';
  114. }
  115. return $response;
  116. }
  117. //退款通知
  118. public function refund()
  119. {
  120. $agent_id = request()->route('agent_id');
  121. $agent = Agent::find($agent_id);
  122. $config = config('wechat.payment.default');
  123. $config = array_merge($config, [
  124. 'app_id' => $agent->appid,
  125. 'mch_id' => $agent->mchid,
  126. 'key' => $agent->mchkey,
  127. ]);
  128. $app = Factory::payment($config);
  129. try {
  130. $response = $app->handleRefundedNotify(function ($message, $reqInfo, $fail) {
  131. // 记录一下本地调试
  132. $this->log(['message' => $message, 'reqInfo' => $reqInfo], 'refund');
  133. // 请求成功
  134. if ($message['return_code'] === 'SUCCESS') {
  135. //主要是为了区分定金支付和尾款支付,订单号带有-status后缀,分割后前面才是真正的订单号
  136. list($order_no, $status) = explode('-', $message['out_trade_no']);
  137. $order = Order::query()
  138. ->where(['order_no' => $order_no])
  139. ->first();
  140. //如果已经处理过则不再处理
  141. if ($order->status == OrderStatus::REFUNDED) {
  142. return true;
  143. }
  144. //如果已经存在相关的退款记录,也不再处理
  145. $exist_log = UserMoneyLog::where([
  146. 'user_id' => $order->user_id,
  147. 'order_id' => $order->id,
  148. 'type' => 2,
  149. 'transaction_id' => $reqInfo['transaction_id'],
  150. ])->first();
  151. if ($exist_log) {
  152. return true;
  153. }
  154. $log = UserMoneyLog::query()
  155. ->where([
  156. 'user_id' => $order->user_id,
  157. 'order_id' => $order->id,
  158. 'type' => 1,
  159. 'transaction_id' => $reqInfo['transaction_id'],
  160. ])->first();
  161. if (!$log) {
  162. $fail('找不到交易信息');
  163. }
  164. // 退款成功
  165. if ($reqInfo['refund_status'] === 'SUCCESS') {
  166. DB::beginTransaction();
  167. try {
  168. //更新订单状态为退款成功
  169. $order->status = OrderStatus::REFUNDED;
  170. $order->save();
  171. //记录日志
  172. UserMoneyLog::create([
  173. 'user_id' => $order->user_id,
  174. 'agent_id' => $order->agent_id,
  175. 'money' => $reqInfo['settlement_refund_fee'] / 100,
  176. 'order_id' => $order->id,
  177. 'type' => 2,
  178. 'desc' => DB::raw("LEFT('退款:{$order->title}', 250)"),
  179. 'transaction_id' => $reqInfo['transaction_id'],
  180. 'created_at' => now(), //模型没有updated_at,无法自动写入时间
  181. ]);
  182. // 退库存
  183. Product::query()
  184. ->where('id', $order->product_id)
  185. ->increment('stock', $order->num);
  186. DB::commit();
  187. return true;
  188. } catch (\Exception $e) {
  189. DB::rollBack();
  190. $fail('Unknown error');
  191. }
  192. }
  193. }
  194. $fail('Unknown error 2');
  195. });
  196. } catch (Exception $e) {
  197. return 'error';
  198. }
  199. return $response;
  200. }
  201. //保存消息,用于调试
  202. private function log($write_data, $type = 'notify')
  203. {
  204. $dir = storage_path('wxpay');
  205. if (!is_dir($dir)) {
  206. mkdir($dir);
  207. }
  208. $filename = $dir . '/' . $type . '_' . date('Y-m-d-H') . '.log';
  209. $data = '[' . date('Y-m-d H:i:s') . ']' . PHP_EOL;
  210. $data .= '[message]: ' . (is_array($write_data) ? json_encode($write_data) : $write_data) . PHP_EOL . PHP_EOL;
  211. file_put_contents($filename, $data, FILE_APPEND);
  212. }
  213. }