RentBikeController.php 46 KB

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