RentBikeController.php 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119
  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: Mead
  5. * Date: 2019/11/6
  6. * Time: 8:53 PM.
  7. */
  8. namespace App\Http\Controllers\V1;
  9. use App\Handlers\BikeControl;
  10. use App\Handlers\BikeHandler;
  11. use App\Handlers\BikeStatusInfoSyncHandler;
  12. use App\Handlers\ConvertHandler;
  13. use App\Http\Requests\RetryBikeRequest;
  14. use App\Jobs\CloseRentOrderJob;
  15. use App\Models\Area;
  16. use App\Models\AreaSetting;
  17. use App\Models\Bike;
  18. use App\Models\RentOrder;
  19. use App\Models\RentOrderBikeOperate;
  20. use App\Models\User;
  21. use App\Models\WalletLog;
  22. use App\Repositories\AreaRepository;
  23. use App\Repositories\AreaSettingRepository;
  24. use App\Repositories\BikeRepository;
  25. use App\Repositories\LocationLogRepository;
  26. use App\Repositories\OrderRepository;
  27. use App\Repositories\RentOrderBikeOperateRepository;
  28. use App\Repositories\RentOrderRepository;
  29. use App\Repositories\UserRepository;
  30. use App\Transformers\RentOrderTransformer;
  31. use App\Transformers\RentUseOrderTransformer;
  32. use Carbon\Carbon;
  33. use Dingo\Api\Http\Request;
  34. use Illuminate\Support\Facades\Log;
  35. use function EasyWeChat\Kernel\Support\generate_sign;
  36. use Illuminate\Support\Facades\Cache;
  37. use Illuminate\Support\Facades\DB;
  38. use Illuminate\Support\Facades\Redis;
  39. use Symfony\Component\HttpKernel\Exception\HttpException;
  40. /**
  41. * 日租车模块
  42. * Class RentBikeController
  43. * @package App\Http\Controllers\V1
  44. */
  45. class RentBikeController extends BaseController
  46. {
  47. /**
  48. * 租车下单
  49. * User: Mead.
  50. */
  51. public function storeOrder(Request $request, BikeRepository $bikeRepository, RentOrderRepository $rentOrderRepository, OrderRepository $orderRepository, AreaSettingRepository $areaSettingRepository, UserRepository $userRepository)
  52. {
  53. try {
  54. $type = $request->get('type');
  55. $bike_no = $request->get('bike_no');
  56. $area_id = $request->get('area_id');
  57. $lat = $request->get('lat');
  58. $lng = $request->get('lng');
  59. $cache_key = "OPEN_RENT_BIKE_ORDER_{$bike_no}";
  60. if (Cache::has($cache_key)) {
  61. return $this->errorNoValidation('您提交的太频繁了,请一会再提交!');
  62. }
  63. Cache::put($cache_key, 1, Carbon::now()->addSeconds(5));
  64. $bike = $bikeRepository->byNoIsCanRentBikeGetModel($bike_no);
  65. if (!$bike) {
  66. return $this->errorNoValidation('该车暂不能用');
  67. }
  68. //以车的区域为主
  69. if ($bike->put_area_id !== $area_id) {
  70. $area_id = $bike->put_area_id;
  71. }
  72. // 判断用户押金,授权,认证,手机号状态是否正常
  73. $user = $this->user;
  74. if (User::DEPOSIT_NO === (int)$user->is_deposit) {
  75. return $this->errorNoValidation('请您先交纳押金');
  76. }
  77. if ((int)$user->deposit_type === User::DEPOSIT_CARD && (int)$user->is_deposit === User::DEPOSIT_OK) {
  78. // 押金类型为免押金卡 判断是否过期
  79. if (!$userRepository->isDepositCardExpired($user->id)) {
  80. return $this->errorNoValidation('免押金卡已到期,请您先交纳押金');
  81. }
  82. }
  83. if (User::BIND_MOBILE_NO === (int)$user->is_bind_mobile) {
  84. return $this->errorNoValidation('请先绑定您的手机号');
  85. }
  86. if (User::CARD_NO === (int)$user->is_card_certified) {
  87. return $this->errorNoValidation('请您先完善实名认证');
  88. }
  89. if (User::RIDE_BIKE_AGE_NO === (int)$user->is_match_ride_age) {
  90. return $this->errorNoValidation('未成年人禁止骑车');
  91. }
  92. $setting = $areaSettingRepository->byAreaId($area_id);
  93. if (!$setting->is_open_day_rent) {
  94. return $this->errorNoValidation('该区域租车咱不开放,敬请期待!');
  95. }
  96. // 判断用户是否有为支付的订单
  97. if ($orderRepository->byUserIdCheckIsExistRideOrder($user->id)) {
  98. return $this->errorNoValidation('您有未完成的订单,请先处理');
  99. }
  100. if ($rentOrderRepository->byUserIdCheckIsExistRideOrder($user->id)) {
  101. return $this->errorNoValidation('您有未完成的租车订单,请先处理');
  102. }
  103. // 判断用户是否在车的距离范围内
  104. $options = ['SORT' => 'ASC'];
  105. $redis = Redis::connection();
  106. $nearby_bikes = $redis->georadius(Bike::REDIS_BIKE_LOCATION_TAG, $lng, $lat, 1, 'km', $options);
  107. if (!in_array($bike->bike_no, $nearby_bikes)) {
  108. return $this->errorNoValidation('小主,咱俩有点远!');
  109. }
  110. $user_id = $this->user->id;
  111. $cache_key = "RIDE_RENT_ORDER_{$user_id}";
  112. if (Cache::has($cache_key)) {
  113. return $this->errorNoValidation('您提交的太频繁了,请一会再提交!');
  114. }
  115. Cache::put($cache_key, 1, Carbon::now()->addSeconds(3));
  116. //不同类型处理价格(暂不用)
  117. // 下单
  118. $rent_money = $setting->day_rent_money;
  119. $users = User::find($user['id']);
  120. $settingConfig = [
  121. 'rent_money' => $setting->day_rent_money,
  122. 'rent_time' => $setting->day_rent_hours,
  123. 'over_rent_time_money' => $setting->per_hours_day_rent_timeout_money,
  124. 'over_rent_time_max_money' => $setting->day_rent_capping_money,
  125. ];
  126. $data = [
  127. 'user_id' => $user['id'],
  128. 'no' => RentOrder::makeNo(),
  129. 'type' => RentOrder::TYPE_DAY_RENT,
  130. 'bike_no' => $bike_no,
  131. 'bike_id' => $bike->id,
  132. 'area_id' => $area_id,
  133. 'phone_detail' => $users->userPhoneDetail->detail ?? '',
  134. 'start_use_bike_location' => [
  135. 'latitude' => $lat,
  136. 'longitude' => $lng,
  137. ],
  138. 'rent_money' => $rent_money,
  139. 'rent_preferential_money' => 0.00,
  140. 'rent_total_money' => $rent_money,
  141. 'status' => RentOrder::STATUS_WAIT_PAY_RENT_MONEY,
  142. 'setting' => $settingConfig,
  143. ];
  144. $order = RentOrder::create($data);
  145. // $this->dispatch(new CloseRentOrderJob($order, Carbon::now()->addMinutes(10)));
  146. // 更新用户的区域id
  147. if (!$user->register_area_id) {
  148. $user->register_area_id = $area_id;
  149. $user->save();
  150. }
  151. $order->status = RentOrder::STATUS_RENT_BIKE;
  152. $order->start_use_bike_time = now();
  153. $order->return_end_bike_time = Carbon::now()->addHours($settingConfig['rent_time'])->toDateTimeString();
  154. $order->save();
  155. $bike->is_rent = Bike::RENT_YES;
  156. $bike->is_riding = Bike::RIDING_YES;
  157. $bike->save();
  158. //同步redis
  159. (new BikeStatusInfoSyncHandler())->toBikeRideStatus(BikeStatusInfoSyncHandler::ROLE_USER, $bike->bike_no, [
  160. 'id' => $order->id,
  161. 'bike_id' => $order->bike_id,
  162. 'area_id' => $order->area_id,
  163. 'is_rent' => 1,
  164. ]);
  165. $params['no'] = $order->no;
  166. return $this->response->array($params);
  167. } catch (\Exception $exception) {
  168. return $this->errorNoValidation($exception->getMessage());
  169. }
  170. }
  171. public function useOrder(Request $request, RentOrderRepository $rentOrderRepository)
  172. {
  173. try {
  174. $no = $request->get('no');
  175. $order = $rentOrderRepository->byNo($no);
  176. return $this->response->item($order, RentUseOrderTransformer::class);
  177. } catch (\Exception $exception) {
  178. }
  179. }
  180. /**
  181. * @param Request $request
  182. * @param RentOrderRepository $rentOrderRepository
  183. *
  184. * @return \Dingo\Api\Http\Response
  185. * User: Mead
  186. */
  187. public function show(Request $request, RentOrderRepository $rentOrderRepository)
  188. {
  189. try {
  190. $no = $request->get('no');
  191. $order = $rentOrderRepository->byNo($no);
  192. return $this->response->item($order, new RentOrderTransformer());
  193. } catch (\Exception $exception) {
  194. }
  195. }
  196. /**
  197. * 结束租车
  198. * User: Mead.
  199. */
  200. public function closeOrder(Request $request, RentOrderRepository $rentOrderRepository, BikeRepository $bikeRepository, LocationLogRepository $locationLogRepository, AreaSettingRepository $areaSettingRepository)
  201. {
  202. try {
  203. $bike_no = $request->get('bike_no');
  204. $order_no = $request->get('order_no');
  205. $lat = $request->get('lat');
  206. $lng = $request->get('lng');
  207. $order = $rentOrderRepository->byNoGetRideOrder($order_no);
  208. if (!$order) {
  209. return $this->errorNoValidation('订单不存在或订单已结算');
  210. }
  211. if (Bike::RIDING_YES === (int)$order->bike_is_riding) {
  212. return $this->errorNoValidation('请先关锁再还车');
  213. }
  214. if ($rentOrderRepository->checkUserMoreCloseOrder($this->user->id)) {
  215. return $this->errorNoValidation('您今天太频繁操作车辆');
  216. }
  217. $user_id = $this->user->id;
  218. $cache_key = "RIDE_CLOSE_RENT_ORDER_{$user_id}";
  219. if (Cache::has($cache_key)) {
  220. return $this->errorNoValidation('您提交的太频繁了,请一会再提交!');
  221. }
  222. Cache::put($cache_key, 1, Carbon::now()->addSeconds(3));
  223. //获取车的最后位置
  224. $location = $locationLogRepository->byBikeNoGetLastLocation($order->bike_no);
  225. if ($location['lat'] <= 0) {
  226. $location['lat'] = $order->end_use_bike_location['latitude'];
  227. $location['lng'] = $order->end_use_bike_location['longitude'];
  228. if ($location['lat'] <= 0) {
  229. $location['lat'] = $lat;
  230. $location['lng'] = $lng;
  231. }
  232. }
  233. // 更新车的信息
  234. $bikeModel = $bikeRepository->byIdGetModel($order->bike_id);
  235. $bikeModel->is_riding = Bike::RIDING_NO;
  236. $bikeModel->is_rent = Bike::RENT_NO;
  237. $bikeModel->save();
  238. //更新redis
  239. (new BikeStatusInfoSyncHandler())->toBikeWaitRideStatus($order->bike_no, $location['lng'], $location['lat']);
  240. // 判断是否经常这样操作(未做)
  241. $second = Carbon::now()->diffInSeconds(Carbon::parse($order->start_use_bike_time));
  242. if ($second <= AreaSetting::CLOSE_BIKE_TIME) {
  243. // 关闭订单
  244. $order->status = RentOrder::STATUS_CLOSE_ORDER;
  245. $order->end_use_bike_location = [
  246. 'latitude' => $lat,
  247. 'longitude' => $lng,
  248. ];
  249. $order->end_use_bike_time = now();
  250. $order->rent_money = 0;
  251. $order->save();
  252. //退钱
  253. // 记录临时关锁
  254. return $this->response->item($order, RentOrderTransformer::class);
  255. }
  256. //检查是否在禁停区
  257. $BikeHandler = new BikeHandler();
  258. $is_ban_stop_bike = $BikeHandler->byLocationCheckIsInBanStopParking($location['lat'], $location['lng'], $order->area_id);
  259. if ($is_ban_stop_bike) {
  260. return $this->errorNoValidation('禁停区域内禁止停车!');
  261. }
  262. // 结束订单
  263. $setting = $order->setting;
  264. $money = 0.00;
  265. $over_hours = 0;
  266. //是否需要收取超出费用
  267. $is_over_time = (strtotime($order->return_end_bike_time) < time());
  268. if ($is_over_time) {
  269. //超出时间
  270. $over_hours = ceil(Carbon::now()->diffInMinutes(Carbon::parse($order->return_end_bike_time)) / 60);
  271. $hours = ceil(Carbon::now()->diffInMinutes(Carbon::parse($order->start_use_bike_time)) / 60);
  272. if ($hours > 24) {
  273. //超过1天
  274. $day = ceil($hours / 24);
  275. $end_hours = $hours % 24;
  276. //日封顶租金*天
  277. $money = bcmul($setting['over_rent_time_max_money'], ($day - 1), 2);
  278. // $money = bcadd($money, $setting['rent_money'], 2);
  279. if ($end_hours > $setting['rent_time']) {
  280. // 超过一天 又超过8小时
  281. // (超时费 + 日封顶租金*天)
  282. $money = bcadd(bcmul(($end_hours - $setting['rent_time']), $setting['over_rent_time_money'], 2), $money, 2);
  283. }
  284. } else {
  285. // 不超过1天 但是超过8小时
  286. $money = bcmul($over_hours, $setting['over_rent_time_money'], 2);
  287. // (超时费 + 基础租金) 是否大于日封顶租金
  288. $total_money = bcadd($money, $setting['rent_money'], 2);
  289. if ($total_money > $setting['over_rent_time_max_money']) {
  290. $money = bcsub($setting['over_rent_time_max_money'], $setting['rent_money'], 2);
  291. }
  292. }
  293. }
  294. //计算骑行距离
  295. $order->use_bike_distance_length = bcdiv($location['mileage'], 1000, 2);
  296. // 日租结算
  297. $order->time_money = $money;
  298. $order->distance_money = 0.00;
  299. $order->preferential_money = 0.00;
  300. $order->over_hours = $over_hours;
  301. // 租金 + 超时费
  302. $order->total_money = bcadd($money, $order->rent_money, 2);
  303. $order->pay_money = $order->total_money;
  304. //判断是否收取调度费
  305. if ($order->dispatch_money > 0) {
  306. $order->total_money = bcadd($order->total_money, $order->dispatch_money, 2);
  307. }
  308. if ($order->total_money > 0) {
  309. $order->status = RentOrder::STATUS_CLOSE_RENT_BIKE;
  310. } else {
  311. $order->status = RentOrder::STATUS_COMPLETE_ORDER;
  312. }
  313. //计算用车时间 (分)
  314. $order->use_bike_time_length = ceil($second / 60);
  315. // 车辆最后位置 (防止定位失败的情况)
  316. if (empty($order->end_use_bike_location)) {
  317. $order->end_use_bike_location = [
  318. 'latitude' => $lat,
  319. 'longitude' => $lng,
  320. ];
  321. }
  322. $order->end_use_bike_time = Carbon::now();
  323. $order->order_total_money = $order->total_money;
  324. $order->save();
  325. // 删除redis订单
  326. if (RentOrder::STATUS_COMPLETE_ORDER === (int)$order->status) {
  327. (new BikeStatusInfoSyncHandler())->toBikeWaitRideStatus($order->bike_no, $location['lng'], $location['lat']);
  328. }
  329. return $this->response->item($order, RentOrderTransformer::class);
  330. } catch (HttpException $exception) {
  331. return $this->errorNoValidation($exception->getMessage(), $exception->getStatusCode());
  332. }
  333. }
  334. public function payShow(Request $request, RentOrderRepository $rentOrderRepository, RentOrderBikeOperateRepository $rentOrderBikeOperateRepository)
  335. {
  336. try {
  337. $order_no = $request->get('order_no');
  338. $order = $rentOrderRepository->byNoAndUserId($order_no, $this->user->id);
  339. if (!$order) {
  340. return $this->errorNoValidation('订单不存在!');
  341. }
  342. // 检查用户余额是否够
  343. $is_user_wallet = true;
  344. if ($order->pay_money > $this->user->wallet_money) {
  345. // 余额不够
  346. $is_user_wallet = false;
  347. }
  348. if (bccomp($order->total_money, 0) === 0) {
  349. $order->pay_type = RentOrder::PAY_STATUS_OK;
  350. $order->status = RentOrder::STATUS_CLOSE_ORDER;
  351. }
  352. $order->save();
  353. // 检查是否系统自动锁车
  354. $is_system_off_lock = $rentOrderBikeOperateRepository->checkLowPowerOffLock($order->id);
  355. $is_coupon = false;
  356. $userCoupons = [];
  357. $ridingCard = [];
  358. return $this->response->array([
  359. 'order' => $order->append(['use_bike_time_length_text', 'use_bike_distance_length_text', 'end_use_bike_time_timestamp'])->toArray(),
  360. 'orders' => [
  361. 'id' => $order->id,
  362. 'no' => $order->no,
  363. 'bike_no' => $order->bike_no,
  364. 'use_bike_time_length_text' => $order->use_bike_time_length_text,
  365. 'use_bike_distance_length_text' => $order->use_bike_distance_length_text,
  366. 'end_use_bike_time_timestamp' => $order->end_use_bike_time_timestamp,
  367. 'pay_status' => $order->pay_status,
  368. 'pay_money' => $order->pay_money,
  369. 'time_money' => $order->time_money, // 时长费
  370. 'rent_money' => $order->rent_money, // 租费
  371. 'dispatch_money' => $order->dispatch_money, // 调度费
  372. 'distance_money' => $order->distance_money, // 里程费用
  373. 'total_money' => $order->total_money,// 加调度费的总金额
  374. 'order_total_money' => bcadd($order->rent_money, $order->time_money, 2), // 不加调度费的总金额
  375. 'order_wait_pay_money' => empty($userCoupons) ? $order->total_money : $userCoupons['order_wait_pay_money'], // 总待支付
  376. 'total_preferential_money' => empty($userCoupons) ? $order->preferential_money : $userCoupons['total_preferential_money'], // 总优惠
  377. ],
  378. 'wallet_pay_status' => $is_user_wallet,
  379. 'wallet_money' => $this->user->wallet_money,
  380. 'user_coupon' => [
  381. 'is_coupon' => $is_coupon,
  382. 'coupon_preferential_money' => empty($userCoupons) ? '0.00' : $userCoupons['coupon_preferential_money'], // 优惠券优惠的金额
  383. 'coupon_user_bags_id' => empty($userCoupons) ? 0 : $userCoupons['id'],
  384. ],
  385. 'user_card' => [
  386. 'is_card' => empty($ridingCard) ? false : true,
  387. 'card' => $ridingCard,
  388. 'card_preferential_money' => $order->preferential_money, // 没支付之前优惠金额就是 骑行卡优惠的金额
  389. ],
  390. 'system_off_lock' => [
  391. 'is_system_off_lock' => empty($is_system_off_lock) ? false : true,
  392. 'system_off_lock_text' => '电量过低,系统自动还车,敬请谅解,如有疑问请致电客服'
  393. ],
  394. ]);
  395. } catch (\Exception $exception) {
  396. return $this->errorException($exception->getMessage());
  397. }
  398. }
  399. public function pay(Request $request, RentOrderRepository $rentOrderRepository)
  400. {
  401. try {
  402. $pay_type = $request->get('pay_type');
  403. $order_no = $request->get('order_no');
  404. $order = $rentOrderRepository->byNo($order_no);
  405. if (!$order) {
  406. return $this->errorBadRequest('订单不存在');
  407. }
  408. if (RentOrder::STATUS_COMPLETE_ORDER === (int)$order->status) {
  409. return $this->errorNoValidation('订单已完成');
  410. }
  411. if (RentOrder::STATUS_CLOSE_ORDER === (int)$order->status) {
  412. return $this->errorNoValidation('订单已关闭');
  413. }
  414. if (RentOrder::PAY_STATUS_OK === (int)$order->pay_status) {
  415. return $this->errorNoValidation('订单已支付');
  416. }
  417. if (RentOrder::STATUS_RENT_BIKE === (int)$order->status) {
  418. return $this->errorNoValidation('请先结束租车订单,再支付');
  419. }
  420. $user = $this->user;
  421. if ($order->user_id !== $user->id) {
  422. return $this->errorNoValidation('非法操作');
  423. }
  424. $response = '';
  425. $user_id = $user['id'];
  426. $cache_key = "PAY_RENT_ORDER_{$user_id}";
  427. if (Cache::has($cache_key)) {
  428. return $this->errorNoValidation('您提交的太频繁了,请一会再提交!');
  429. }
  430. Cache::put($cache_key, 1, Carbon::now()->addSeconds(5));
  431. switch ($pay_type) {
  432. case RentOrder::PAY_TYPE_ACCOUNT:
  433. //余额支付
  434. if (bccomp($order->total_money, $this->user->wallet_money) === 1) {
  435. // 余额不够
  436. return $this->errorNoValidation('用户余额不够');
  437. }
  438. DB::transaction(function () use ($order, $user) {
  439. //添加钱包记录
  440. WalletLog::log(WalletLog::OPERATE_TYPE_SUB, $order->total_money, $user->id, WalletLog::TYPE_SUB_WALLET_RENT_ORDER_MONEY, $order->area_id, $order->id, RentOrder::class);
  441. //修改订单记录
  442. $order->pay_status = RentOrder::PAY_STATUS_OK;
  443. $order->pay_money = $order->total_money;
  444. $order->pay_time = now();
  445. $order->pay_type = RentOrder::PAY_TYPE_ACCOUNT;
  446. $order->status = RentOrder::STATUS_COMPLETE_ORDER;
  447. $order->order_total_money = $order->total_money;
  448. $order->save();
  449. });
  450. $response = [
  451. 'pay_order_status' => true,
  452. ];
  453. break;
  454. case RentOrder::PAY_TYPE_WECHAT:
  455. //微信支付
  456. $payment = app('wechat.payment'); // 微信支付
  457. $username = $user->truename;
  458. $auth = $user->auth;
  459. $order->over_no = RentOrder::makeOverNo();
  460. $order->save();
  461. $result = $payment->order->unify([
  462. 'body' => "[{$username}]支付租车费用",
  463. 'out_trade_no' => $order->no,
  464. 'trade_type' => 'JSAPI', // 必须为JSAPI
  465. 'openid' => $auth['credential'], // 这里的openid为付款人的openid
  466. 'total_fee' => wechat_fee($order->total_money), // 总价
  467. 'attach' => RentOrder::NO_TAG,
  468. 'notify_url' => config('app.url') . '/api/payments/wechat-rent-notify',
  469. ]);
  470. if ($result['return_code'] === 'FAIL') return $this->errorNoValidation('下单失败');
  471. // 如果成功生成统一下单的订单,那么进行二次签名
  472. if ($result['result_code'] === 'FAIL') {
  473. //判断是否重复下单
  474. if ($result['err_code'] === 'INVALID_REQUEST') {
  475. $order->no = RentOrder::makeNo();
  476. $order->save();
  477. $result = $payment->order->unify([
  478. 'body' => "[{$username}]支付租车费用",
  479. 'out_trade_no' => $order->no,
  480. 'trade_type' => 'JSAPI', // 必须为JSAPI
  481. 'openid' => $auth['credential'], // 这里的openid为付款人的openid
  482. 'total_fee' => wechat_fee($order->total_money), // 总价
  483. 'attach' => RentOrder::NO_TAG,
  484. 'notify_url' => config('app.url') . '/api/payments/wechat-rent-notify',
  485. ]);
  486. } elseif ($result['err_code'] === 'ORDERPAID') {
  487. $order->pay_status = RentOrder::PAY_STATUS_OK;
  488. $order->pay_time = now();
  489. $order->pay_type = RentOrder::PAY_TYPE_WECHAT;
  490. $order->pay_money = $order->total_money;
  491. $order->order_total_money = $order->pay_money;
  492. $order->status = RentOrder::STATUS_COMPLETE_ORDER;
  493. $order->save();
  494. $order->pay_rent_over_order_callback();
  495. return $this->errorNoValidation('订单已支付,请勿重复支付!');
  496. } else {
  497. return $this->errorNoValidation('下单失败');
  498. }
  499. }
  500. if ('SUCCESS' === $result['return_code'] && 'SUCCESS' === $result['result_code']) {
  501. // 二次签名的参数必须与下面相同
  502. $params = [
  503. 'appId' => $auth['identifier'],
  504. 'timeStamp' => time(),
  505. 'nonceStr' => $result['nonce_str'],
  506. 'package' => 'prepay_id=' . $result['prepay_id'],
  507. 'signType' => 'MD5',
  508. ];
  509. // config('wechat.payment.default.key')为商户的key
  510. $params['paySign'] = generate_sign($params, config('wechat.payment.default.key'));
  511. $response = $params;
  512. } else {
  513. return $this->errorNoValidation('下单失败');
  514. }
  515. break;
  516. default:
  517. return $this->errorBadRequest('支付类型不对');
  518. break;
  519. }
  520. $response['order_no'] = $order->no;
  521. return $this->response->array($response);
  522. } catch (\Exception $exception) {
  523. return $this->errorNoValidation($exception->getMessage());
  524. }
  525. }
  526. /**
  527. * 开锁
  528. * User: Mead.
  529. */
  530. public function openBike(Request $request, RentOrderRepository $rentOrderRepository, BikeRepository $bikeRepository)
  531. {
  532. try {
  533. $order_no = $request->get('order_no');
  534. $lat = $request->get('lat');
  535. $lng = $request->get('lng');
  536. $order = $rentOrderRepository->byNoGetRideOrder($order_no);
  537. if (!$order) {
  538. return $this->errorNoValidation('订单不存在');
  539. }
  540. if ($order->user_id !== $this->user->id) {
  541. return $this->errorNoValidation('非法操作');
  542. }
  543. $user_id = $this->user['id'];
  544. $cache_key = "RENT_ORDER_OPEN_BIKE_{$user_id}";
  545. if (Cache::has($cache_key)) {
  546. return $this->errorNoValidation('您提交的太频繁了,请一会再提交!');
  547. }
  548. Cache::put($cache_key, 1, Carbon::now()->addSeconds(3));
  549. $box_no = $bikeRepository->byIdGetBoxNo($order->bike_id);
  550. BikeControl::openLock($box_no);
  551. (new BikeStatusInfoSyncHandler())->toBikeRentRideStatus($order->bike_no);
  552. // 记录日志信息
  553. RentOrderBikeOperate::log($order->id, RentOrderBikeOperate::TYPE_OPEN_BIKE, $order->bike_id, $this->user->id, $lat, $lng);
  554. $order->bike_is_riding = Bike::RIDING_YES;
  555. $order->save();
  556. //增加次数
  557. Cache::remember('increment_use_bike', 1, function () use ($order) {
  558. return RentOrder::where('id', $order->id)->increment('use_bike_count');
  559. });
  560. return $this->success();
  561. } catch (\Exception $exception) {
  562. return $this->errorNoValidation($exception->getMessage());
  563. }
  564. }
  565. /**
  566. * 关锁
  567. * User: Mead.
  568. */
  569. public function closeBike(Request $request, RentOrderRepository $rentOrderRepository, BikeRepository $bikeRepository, LocationLogRepository $locationLogRepository)
  570. {
  571. try {
  572. $order_no = $request->get('order_no');
  573. $lat = $request->get('lat');
  574. $lng = $request->get('lng');
  575. $order = $rentOrderRepository->byNoGetRideOrder($order_no);
  576. if (!$order) {
  577. return $this->errorNoValidation('订单不存在');
  578. }
  579. if ($order->user_id !== $this->user->id) {
  580. return $this->errorNoValidation('非法操作');
  581. }
  582. $user_id = $this->user['id'];
  583. $cache_key = "RENT_ORDER_CLOSE_BIKE_{$user_id}";
  584. if (Cache::has($cache_key)) {
  585. return $this->errorNoValidation('您提交的太频繁了,请一会再提交!');
  586. }
  587. Cache::put($cache_key, 1, Carbon::now()->addSeconds(3));
  588. $location = $locationLogRepository->byBikeNoGetLastLocation($order->bike_no);
  589. if ($location['lat'] <= 0) {
  590. $location['lat'] = $lat;
  591. $location['lng'] = $lng;
  592. }
  593. $order->end_use_bike_location = [
  594. 'latitude' => $location['lat'],
  595. 'longitude' => $location['lng'],
  596. ];
  597. $order->bike_is_riding = Bike::RIDING_NO;
  598. $order->save();
  599. //最后骑行时间
  600. $bikeModel = $bikeRepository->byIdGetModel($order->bike_id);
  601. $bikeModel->last_use_bike_end_time = date('Y-m-d H:i:s');
  602. $bikeModel->save();
  603. $box_no = $bikeRepository->byIdGetBoxNo($order->bike_id);
  604. BikeControl::closeLock($box_no);
  605. // 记录日志信息
  606. RentOrderBikeOperate::log($order->id, RentOrderBikeOperate::TYPE_CLONE_BIKE, $order->bike_id, $this->user->id, $lat, $lng);
  607. (new BikeStatusInfoSyncHandler())->toBikeRentWaitRideStatus($order->bike_no);
  608. return $this->success();
  609. } catch (\Exception $exception) {
  610. return $this->errorNoValidation($exception->getMessage());
  611. }
  612. }
  613. /**
  614. * 日租订单列表页.
  615. *
  616. * @param Request $request
  617. * @param RentOrder $rentOrder
  618. * User: Mead
  619. */
  620. public function orders(Request $request, RentOrder $rentOrder)
  621. {
  622. try {
  623. $query = $rentOrder->query();
  624. $query->where('pay_status', RentOrder::PAY_STATUS_OK);
  625. if ($year = $request->get('year', date('Y'))) {
  626. $query->whereYear('created_at', $year);
  627. }
  628. if ($month = $request->get('month', date('m'))) {
  629. $query->whereMonth('created_at', $month);
  630. }
  631. $orders = $query->where('user_id', $this->user->id)->orderBy('id', 'desc')->paginate();
  632. $orders->appends($request->only(['year', 'month']))->withPath(config('app.url') . '/api/rent/orders');
  633. return $this->response->paginator($orders, RentOrderTransformer::class);
  634. } catch (\Exception $exception) {
  635. return $this->errorException($exception->getMessage());
  636. }
  637. }
  638. /**
  639. * 检查是否在还车点
  640. * User: Mead.
  641. */
  642. public function checkBikeIsInStopParking(Request $request, BikeRepository $bikeRepository, RentOrderRepository $rentOrderRepository, AreaSettingRepository $areaSettingRepository, LocationLogRepository $locationLogRepository, AreaRepository $areaRepository)
  643. {
  644. $bike_no = $request->get('bike_no');
  645. $order_no = $request->get('order_no');
  646. $user_lat = $request->get('lat');
  647. $user_lng = $request->get('lng');
  648. try {
  649. $order = $rentOrderRepository->byNo($order_no);
  650. if (!$order) {
  651. return $this->errorBadRequest('非法请求');
  652. }
  653. $user_id = $this->user['id'];
  654. $cache_key = "RENT_ORDER_CHECK_BIKE_IS_PARK_{$user_id}";
  655. if (Cache::has($cache_key)) {
  656. return $this->errorNoValidation('您提交的太频繁了,请一会再提交!');
  657. }
  658. Cache::put($cache_key, 1, Carbon::now()->addSeconds(3));
  659. $box_no = $bikeRepository->byNoGetBoxNO($order->bike_no);
  660. BikeControl::nowBikeLocation($box_no);
  661. $setting = $areaSettingRepository->byAreaId($order->area_id);
  662. $location = $locationLogRepository->byBikeNoGetLastLocation($order->bike_no);
  663. if ($location['lat'] <= 0) {
  664. $location['lat'] = $order->end_use_bike_location['latitude'];
  665. $location['lng'] = $order->end_use_bike_location['longitude'];
  666. if ($location['lat'] <= 0) {
  667. $location['lat'] = $user_lat;
  668. $location['lng'] = $user_lng;
  669. }
  670. }
  671. //检查是否在骑行区域
  672. $area = $areaRepository->byIdGetModelRedis($order->area_id);
  673. if (!$area) {
  674. $area = Area::where('id', $order->area_id)->first();
  675. }
  676. $is_out_area = $this->isOutArea($location['lat'], $location['lng'], $area);
  677. if (!$is_out_area) {
  678. return $this->errorNoValidation('超出骑行区域,暂不能还车');
  679. }
  680. //检查是否在禁停区
  681. $BikeHandler = new BikeHandler();
  682. $is_ban_stop_bike = $BikeHandler->byLocationCheckIsInBanStopParking($location['lat'], $location['lng'], $order->area_id);
  683. if ($is_ban_stop_bike) {
  684. return $this->errorNoValidation('禁停区域内禁止停车!');
  685. }
  686. // //运动中不能锁车
  687. // if ($location['speed']) {
  688. // BikeControl::nowBikeLocation($box_no);
  689. //
  690. // return $this->errorNoValidation('运动中不能关锁', 450);
  691. // }
  692. //判断是否全区域内可停
  693. if ($areaSettingRepository->byAreaIdGetIsWholeAreaHuanche($order->area_id) === AreaSetting::WHOLE_AREA_HUANCHE_OK) {
  694. return [
  695. 'is_dispatch' => true,
  696. 'is_stop_bike' => true,
  697. 'dispatch_money' => 0,
  698. ];
  699. }
  700. // 判断是否在停车点
  701. $BikeHandler = new BikeHandler();
  702. $is_huanche = $BikeHandler->byLocationCheckIsInStopParking($location['lat'], $location['lng'], $order->area_id);
  703. if (!$is_huanche['status']) {
  704. // 不在还车点
  705. $dispatch_money = $BikeHandler->byDistanceGetDistanceMoney($is_huanche['distance'], $setting);
  706. RentOrder::where('id', $order->id)->update([
  707. 'dispatch_money' => $dispatch_money,
  708. ]);
  709. return [
  710. 'is_dispatch' => false,
  711. 'is_stop_bike' => false,
  712. 'dispatch_money' => $dispatch_money,
  713. ];
  714. }
  715. return [
  716. 'is_dispatch' => true,
  717. 'is_stop_bike' => true,
  718. 'dispatch_money' => 0,
  719. ];
  720. } catch (HttpException $exception) {
  721. return $this->errorNoValidation($exception->getMessage(), $exception->getStatusCode());
  722. }
  723. }
  724. /**
  725. * 判断是否在骑行区.
  726. *
  727. * @param $lat
  728. * @param $lng
  729. * @param $box_no
  730. * User: Mead
  731. */
  732. private function isOutArea($lat, $lng, $area)
  733. {
  734. try {
  735. $location = [
  736. 'latitude' => $lat,
  737. 'longitude' => $lng,
  738. ];
  739. $fences = $area['area_fence'];
  740. $centre = $area['area_centre'];
  741. $radius = $area['area_radius'];
  742. // 判断是否在骑行区域
  743. $ConvertHandler = (new ConvertHandler());
  744. $is_out_area = $ConvertHandler->is_point_in_polygon($location, $fences);
  745. return $is_out_area;
  746. } catch (\Exception $exception) {
  747. return $this->errorNoValidation($exception->getMessage());
  748. }
  749. }
  750. /**
  751. * 寻铃
  752. * @param Request $request
  753. * @param OrderRepository $orderRepository
  754. * @param BikeRepository $bikeRepository
  755. * User: Fx
  756. */
  757. public function retryBell(RetryBikeRequest $request, RentOrderRepository $rentOrderRepository, BikeRepository $bikeRepository)
  758. {
  759. try {
  760. $order_no = $request->get('order_no');
  761. $bike_no = $request->get('bike_no');
  762. $order = $rentOrderRepository->checkUserIsRetryOpenLock($order_no, $bike_no, $this->user->id);
  763. if (!$order) return $this->errorNoValidation('没有此订单');
  764. $box_no = $bikeRepository->byNoGetBoxNO($bike_no);
  765. $re = BikeControl::bellBike($box_no);
  766. return $this->response->array([
  767. 'is_ok' => $re
  768. ]);
  769. } catch (\Exception $exception) {
  770. return $this->errorNoValidation($exception->getMessage());
  771. }
  772. }
  773. /**
  774. * 检查是否可以结束订单
  775. * @param Request $request
  776. * @param BikeRepository $bikeRepository
  777. * @param RentOrderRepository $rentOrderRepository
  778. * @param LocationLogRepository $locationLogRepository
  779. * @param AreaRepository $areaRepository
  780. * @return array|void
  781. * Author: Mead
  782. */
  783. public function checkBikeIsRidingArea(Request $request, BikeRepository $bikeRepository, RentOrderRepository $rentOrderRepository, LocationLogRepository $locationLogRepository, AreaRepository $areaRepository)
  784. {
  785. $bike_no = $request->get('bike_no');
  786. $order_no = $request->get('order_no');
  787. $user_lat = $request->get('lat');
  788. $user_lng = $request->get('lng');
  789. try {
  790. $order = $rentOrderRepository->byNo($order_no);
  791. if (!$order) {
  792. return $this->errorBadRequest('非法请求');
  793. }
  794. $second = Carbon::now()->diffInSeconds(Carbon::parse($order->start_use_bike_time));
  795. if ($second < AreaSetting::CLOSE_BIKE_TIME) {
  796. //小于60秒直接锁车
  797. return [
  798. 'is_close_order' => true,
  799. ];
  800. }
  801. $user_id = $this->user['id'];
  802. $cache_key = "RENT_ORDER_CHECK_BIKE_IS_RIDING_PARK_{$user_id}";
  803. if (Cache::has($cache_key)) {
  804. return $this->errorNoValidation('您提交的太频繁了,请一会再提交!');
  805. }
  806. Cache::put($cache_key, 1, Carbon::now()->addSeconds(3));
  807. $box_no = $bikeRepository->byNoGetBoxNO($order->bike_no);
  808. BikeControl::nowBikeLocation($box_no);
  809. $location = $locationLogRepository->byBikeNoGetLastLocation($order->bike_no);
  810. if ($location['lat'] <= 0) {
  811. $location['lat'] = $order->end_use_bike_location['latitude'];
  812. $location['lng'] = $order->end_use_bike_location['longitude'];
  813. if ($location['lat'] <= 0) {
  814. $location['lat'] = $user_lat;
  815. $location['lng'] = $user_lng;
  816. }
  817. }
  818. //检查是否在骑行区域
  819. $area = $areaRepository->byIdGetModelRedis($order->area_id);
  820. if (!$area) {
  821. $area = Area::where('id', $order->area_id)->first();
  822. }
  823. $is_out_area = $this->isOutArea($location['lat'], $location['lng'], $area);
  824. if (!$is_out_area) {
  825. return $this->errorNoValidation('超出骑行区域,暂不能还车');
  826. }
  827. //检查是否在禁停区
  828. $BikeHandler = new BikeHandler();
  829. $is_ban_stop_bike = $BikeHandler->byLocationCheckIsInBanStopParking($location['lat'], $location['lng'], $order->area_id);
  830. if ($is_ban_stop_bike) {
  831. return $this->errorNoValidation('禁停区域内禁止停车!');
  832. }
  833. return [
  834. 'is_close_order' => false,
  835. ];
  836. } catch (HttpException $exception) {
  837. return $this->errorNoValidation($exception->getMessage(), $exception->getStatusCode());
  838. }
  839. }
  840. /**
  841. * Author: Mead
  842. */
  843. public function expectRentOrderMoney(Request $request, BikeRepository $bikeRepository, RentOrderRepository $rentOrderRepository, AreaSettingRepository $areaSettingRepository, LocationLogRepository $locationLogRepository, AreaRepository $areaRepository)
  844. {
  845. $bike_no = $request->get('bike_no');
  846. $order_no = $request->get('order_no');
  847. $user_lat = $request->get('lat');
  848. $user_lng = $request->get('lng');
  849. try {
  850. $order = $rentOrderRepository->byNo($order_no);
  851. if (!$order) {
  852. return $this->errorBadRequest('非法请求');
  853. }
  854. $second = Carbon::now()->diffInSeconds(Carbon::parse($order->start_use_bike_time));
  855. if ($second < AreaSetting::CLOSE_BIKE_TIME) {
  856. //小于60秒直接锁车
  857. return [
  858. 'dispatch_money' => 0,
  859. 'time_money' => 0,
  860. 'rent_total_money' => 0,
  861. 'total_money' => 0,
  862. ];
  863. }
  864. $user_id = $this->user['id'];
  865. $cache_key = "RENT_ORDER_EXPECT_MONEY_{$user_id}";
  866. if (Cache::has($cache_key)) {
  867. return $this->errorNoValidation('您提交的太频繁了,请一会再提交!');
  868. }
  869. Cache::put($cache_key, 1, Carbon::now()->addSeconds(3));
  870. $box_no = $bikeRepository->byNoGetBoxNO($order->bike_no);
  871. BikeControl::nowBikeLocation($box_no);
  872. $setting = $areaSettingRepository->byAreaId($order->area_id);
  873. $money = 0.00;
  874. $over_hours = 0;
  875. //是否需要收取超出费用
  876. $is_over_time = (strtotime($order->return_end_bike_time) < time());
  877. if ($is_over_time) {
  878. //超出时间
  879. $over_hours = ceil(Carbon::now()->diffInMinutes(Carbon::parse($order->return_end_bike_time)) / 60);
  880. $hours = ceil(Carbon::now()->diffInMinutes(Carbon::parse($order->start_use_bike_time)) / 60);
  881. if ($hours > 24) {
  882. //超过1天
  883. $day = ceil($hours / 24);
  884. $end_hours = $hours % 24;
  885. //日封顶租金*天
  886. $money = bcmul($setting['over_rent_time_max_money'], ($day - 1), 2);
  887. // $money = bcadd($money, $setting['rent_money'], 2);
  888. if ($end_hours > $setting['rent_time']) {
  889. // 超过一天 又超过8小时
  890. // (超时费 + 日封顶租金*天)
  891. $money = bcadd(bcmul(($end_hours - $setting['rent_time']), $setting['over_rent_time_money'], 2), $money, 2);
  892. }
  893. } else {
  894. // 不超过1天 但是超过8小时
  895. $money = bcmul($over_hours, $setting['over_rent_time_money'], 2);
  896. // (超时费 + 基础租金) 是否大于日封顶租金
  897. $total_money = bcadd($money, $setting['rent_money'], 2);
  898. if ($total_money > $setting['over_rent_time_max_money']) {
  899. $money = bcsub($setting['over_rent_time_max_money'], $setting['rent_money'], 2);
  900. }
  901. }
  902. }
  903. $location = $locationLogRepository->byBikeNoGetLastLocation($order->bike_no);
  904. //计算骑行距离
  905. // $order->use_bike_distance_length = bcdiv($location['mileage'], 1000, 2);
  906. if ($location['lat'] <= 0) {
  907. $location['lat'] = $order->end_use_bike_location['latitude'];
  908. $location['lng'] = $order->end_use_bike_location['longitude'];
  909. if ($location['lat'] <= 0) {
  910. $location['lat'] = $user_lat;
  911. $location['lng'] = $user_lng;
  912. }
  913. }
  914. $money = floatval($money);
  915. $total_money = floatval(bcadd($money, $order->rent_total_money, 2));
  916. $rent_total_money = floatval($order->rent_total_money);
  917. //判断是否全区域内可停
  918. if ($areaSettingRepository->byAreaIdGetIsWholeAreaHuanche($order->area_id) === AreaSetting::WHOLE_AREA_HUANCHE_OK) {
  919. return [
  920. 'dispatch_money' => 0,
  921. 'time_money' => $money,
  922. 'rent_total_money' => $rent_total_money,
  923. 'total_money' => $total_money,
  924. ];
  925. }
  926. // 判断是否在停车点
  927. $BikeHandler = new BikeHandler();
  928. $is_huanche = $BikeHandler->byLocationCheckIsInStopParking($location['lat'], $location['lng'], $order->area_id);
  929. if (!$is_huanche['status']) {
  930. // 不在还车点
  931. $dispatch_money = $BikeHandler->byDistanceGetDistanceMoney($is_huanche['distance'], $setting);
  932. RentOrder::where('id', $order->id)->update([
  933. 'dispatch_money' => $dispatch_money,
  934. ]);
  935. return [
  936. 'dispatch_money' => $dispatch_money,
  937. 'time_money' => $money,
  938. 'rent_total_money' => $rent_total_money,
  939. 'total_money' => floatval(bcadd($total_money, $dispatch_money, 2)),
  940. ];
  941. }
  942. return [
  943. 'dispatch_money' => 0,
  944. 'time_money' => $money,
  945. 'rent_total_money' => $rent_total_money,
  946. 'total_money' => $total_money,
  947. ];
  948. } catch (HttpException $exception) {
  949. return $this->errorNoValidation($exception->getMessage(), $exception->getStatusCode());
  950. }
  951. }
  952. }