RentBikeController.php 46 KB

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