commit 1ed53bf65b29199ecb3732bef43d28f0a0b4f08f Author: 李二狗 Date: Sat Jan 11 14:06:19 2025 +0800 设置供应商余额结算 diff --git a/app/Http/Controllers/Member/OrderController.php b/app/Http/Controllers/Member/OrderController.php new file mode 100644 index 0000000..06bede6 --- /dev/null +++ b/app/Http/Controllers/Member/OrderController.php @@ -0,0 +1,804 @@ +toValidator([ + 'cart_ids' => 'nullable|string', + 'drug_ids' => 'required|string', + ], [], [ + 'cart_ids' => '购物车商品', + 'drug_ids' => '商品', + ]); + + $cart_ids = string_to_array($request->input('cart_ids')); + $drug_ids = string_to_array($request->input('drug_ids')); + $numbers = string_to_array($request->input('numbers')); + + $drugs = Drug::with([ + 'drugstoreDrug' => function (Relation $relation) { + $relation->select(['drug_id', 'price']) + ->where('drugstore_id', $this->getDrugstoreId()); + }, + ]) + ->select(['id', 'drug_name', 'usage', 'specification', 'image']) + ->whereHasIn('drugStoreRelation', function (Builder $builder) { + $builder->where('store_id', $this->getStoreId()); + }) + ->whereIn('id', $drug_ids) + ->get() + ->keyBy('id'); + + $carts = []; + if (count($cart_ids)) { + $carts = Cart::with([ + 'drug' => function (Relation $relation) { + $relation->select(['id']); + } + ]) + ->whereHasIn('drug') + ->where('store_id', $this->getStoreId()) + ->where('user_id', $this->userId()) + ->whereIn('id', $cart_ids) + ->get() + ->keyBy('drug.id'); + } + + $data = []; + $fee = 0; + $new_drug_ids = $new_cart_ids = []; + foreach ($drug_ids as $k => $drug_id) { + if (!isset($drugs[$drug_id])) { + json_error('商品信息已更新,请重新下单'); + } + $drug = $drugs[$drug_id]; + if (!$drug['drugstoreDrug']) { + json_error('商品信息已更新,请重新下单'); + } + $new_drug_ids[] = $drug_id; + $drug['qty'] = $numbers[$k] ?? 1; + if (isset($carts[$drug_id])) { + $new_cart_ids[] = $carts[$drug_id]['id']; + $drug['qty'] = $carts[$drug_id]['qty']; + } + $fee += $drug['drugstoreDrug']['price'] * $drug['qty']; + $data[] = $drug; + } + + $store = $this->getStore(); + $expressFeeInfo = ExpressFee::getDefaultExpressFee(); + if (!$expressFeeInfo) { + json_error('运费信息错误'); + } + $isFree = $fee >= $expressFeeInfo->free_express_fee; + $express_fee = $isFree ? 0 : $expressFeeInfo->base_express_fee; + $fee_total = $isFree ? $fee : ($expressFeeInfo->base_express_fee + $fee); + + json_success([ + 'store_name' => $store->name, + 'fee' => $fee, + 'is_free' => $isFree, + 'express_fee' => $express_fee, + 'fee_total' => $fee_total, + 'data' => $data, + 'drug_ids' => $new_drug_ids, + 'cart_ids' => $new_cart_ids, + ]); + } + + /** + * 提交订单 + * + * @param Request $request + * @return void + * @throws \Throwable + */ + public function submit(Request $request) + { + $this->toValidator([ + 'address_id' => 'required|integer', + 'cart_ids' => 'nullable|string', + 'drug_ids' => 'required|string', + 'numbers' => 'required|string', + ], [ + 'address_id' => '请选择地址', + ], [ + 'cart_ids' => '购物车商品', + 'drug_ids' => '商品', + 'numbers' => '商品数量', + ]); + + $cart_ids = string_to_array($request->input('cart_ids')); + $drug_ids = string_to_array($request->input('drug_ids')); + $numbers = string_to_array($request->input('numbers')); + $address_id = $request->input('address_id'); + $remark = $request->input('remark'); + + if (count($drug_ids) != count($numbers)) { + json_error('商品数量不正确'); + } + + $address = Address::where('user_id', $this->userId()) + ->where('id', $address_id) + ->first(); + if (!$address) { + json_error('用户地址获取失败'); + } + $expressFeeInfo = ExpressFee::getDefaultExpressFee(); + if (!$expressFeeInfo) { + json_error('运费信息错误'); + } + + $drugs = Drug::with([ + 'drugStoreRelation' => function (Relation $relation) { + $relation->where('store_id', $this->getStoreId()); + }, + 'drugstoreDrug' => function (Relation $relation) { + $relation->where('drugstore_id', $this->getDrugstoreId()); + }, + ]) + ->whereHasIn('drugStoreRelation', function (Builder $builder) { + $builder->where('store_id', $this->getStoreId()); + }) + ->whereIn('id', $drug_ids) + ->get(); + /* @var Drug $drug */ + foreach ($drugs as $k => $drug) { + if (!$drug->drugstoreDrug) { + if (!$drug['drugstoreDrug']) { + json_error('商品信息已更新,请重新下单'); + } + if ($drug->drugstoreDrug->stock < $numbers[$k]) { + json_error("{$drug->drug_name}库存不足"); + } + } + } + + $order_no = Carbon::now()->format('YmdHis') . rand(10000, 99999) . rand(10000, 99999); + DB::beginTransaction(); + try { + $productOrder = ProductOrder::create([ + 'store_id' => $this->getStoreId(), + 'drugstore_id' => $this->getDrugstoreId(), + 'user_id' => $this->userId(), + 'order_no' => $order_no, + 'order_type' => ProductOrder::ORDER_TYPE_SHOP, + 'type' => ProductOrder::TYPE_WECHAT, + 'is_pay' => ProductOrder::IS_PAY_FALSE, + 'status' => ProductOrder::STATUS_UNPAID, + 'pay_method' => ProductOrder::PAY_METHOD_ONLINE, + 'remark' => $remark, + 'address_id' => $address->id, + 'express_name' => $address->name, + 'express_mobile' => $address->mobile, + 'express_region' => $address->region, + 'express_address' => $address->detail_address, + ]); + + $productOrder->logs()->create([ + 'content' => '下单成功' + ]); + + $fee = 0; + /* @var Drug $drug */ + foreach ($drugs as $k => $drug) { + $fee += $drug->drugstoreDrug->price * $numbers[$k]; + $productOrder->items()->create([ + 'drug_id' => $drug->id, + 'drug_image' => $drug->image, + 'number' => $numbers[$k], + 'price' => $drug->drugstoreDrug->price, + 'drug_name' => $drug->drug_name, + 'small_info' => $drug->small_info, + ]); + $drug->drugstoreDrug->stock -= $numbers[$k]; + $drug->drugstoreDrug->frozen_number += $numbers[$k]; + $drug->drugstoreDrug->save(); + } + + $isFree = $fee >= $expressFeeInfo->free_express_fee; + $express_fee = $isFree ? 0 : $expressFeeInfo->base_express_fee; + $fee_total = $isFree ? $fee : ($expressFeeInfo->base_express_fee + $fee); + + $productOrder->fill([ + 'items_price' => $fee, + 'total_pay_price' => $fee_total, + 'trans_expenses' => $express_fee, + 'free_ship' => $isFree ? ProductOrder::FREE_SHIP_TRUE : ProductOrder::FREE_SHIP_FALSE, + ])->update(); + if (count($cart_ids)) { + Cart::where('store_id', $this->getStoreId()) + ->where('user_id', $this->userId()) + ->whereIn('id', $cart_ids) + ->delete(); + } + + // // 同步数据 + // $productOrder->refresh(); + // $productOrder->load(['items']); + // Artisan::call('data:sync', [ + // '--type' => 'product_order_sync', + // '--data' => $productOrder->toArray(), + // ]); + + ProductOrderCancelJob::dispatch($productOrder->id)->delay(now()->addMinutes(15)); + + DB::commit(); + } catch (Exception $exception) { + DB::rollBack(); + log_s('下单错误:' . var_export($request->all(), true) . $exception->getMessage() . $exception->getTraceAsString(), 'order', 'error'); + json_error('下单错误,请重试'); + } + + json_success('下单成功,请支付', [ + 'order_id' => $productOrder->id, + ]); + } + + /** + * 订单列表 + * + * @return void + */ + public function lists(Request $request) + { + $this->toValidator([ + 'type' => 'nullable|string', + ], [], [ + 'type' => '类型', + ]); + $type = $request->input('type'); + $keyword = $request->input('keyword'); + $lists = ProductOrder::with([ + 'store:id,name', + 'items', + 'prescription:id,prescription_type', + ]) + ->select([ + 'id', 'store_id', 'order_no', 'p_id', 'order_type', 'is_pay', 'total_pay_price', 'items_price', 'status', + 'trans_expenses', 'cancel_status', + ]) + ->when($type, function (Builder $builder) use ($type) { + if ($type == 'unpaid') { + $builder->where('status', ProductOrder::STATUS_UNPAID); + } elseif ($type == 'wait_send') { + $builder->where('status', ProductOrder::STATUS_WAIT_SEND); + } elseif ($type == 'wait_accept') { + $builder->where('status', ProductOrder::STATUS_WAIT_ACCEPT); + } elseif ($type == 'finished') { + $builder->whereIn('status', [ + ProductOrder::STATUS_WAIT_COMMENT, + ProductOrder::STATUS_REFUND, + ProductOrder::STATUS_REFUNDING, + ProductOrder::STATUS_ACCEPTED, + ProductOrder::STATUS_CONFIRM, + ProductOrder::STATUS_CANCEL, + ]); + } + }) + ->when($keyword, function (Builder $builder) use ($keyword) { + $builder->whereHasIn('items', fn(Builder $builder) => $builder->where('drug_name', 'like', "%$keyword%")); + }) + ->where('store_id', $this->getStoreId()) + ->where('user_id', $this->userId()) + ->latest() + ->paginate(request('per_page')); + + $lists->getCollection()->each(function (ProductOrder $productOrder) { + $productOrder->append(['status_string', 'cancel_status_string']); + if ($productOrder->prescription) { + $productOrder->prescription->append(['prescription_type_string']); + } + }); + + json_page($lists); + } + + /** + * 订单详情 + * + * @param Request $request + * @return void. + */ + public function detail(Request $request) + { + $this->toValidator([ + 'order_id' => 'required|integer', + ], [], [ + 'order_id' => '订单号', + ]); + $order_id = $request->input('order_id'); + + /* @var ProductOrder $productOrder */ + $productOrder = ProductOrder::with([ + 'items', + 'prescription:id,content,prescription_type', + ]) + ->where('id', $order_id) + ->where('store_id', $this->getStoreId()) + ->where('user_id', $this->userId()) + ->first(); + if (!$productOrder) { + json_error('订单信息不存在'); + } + if ($productOrder->express_no_id && $productOrder->status == ProductOrder::STATUS_WAIT_ACCEPT) { + Artisan::call('express', [ + 'type' => 'update', + '--id' => $productOrder->express_no_id, + ]); + $productOrder->load(['expressNo.detail']); + } + + if ( + $productOrder->prescription + && in_array($productOrder->prescription->prescription_type, [Prescription::CHINESE_MEDICINES_DECOCTION_PIECE, Prescription::DISPENSING_GRANULES] + )) { + json_success(new ProductOrderDetailResource($productOrder)); + } else { + json_success(new ProductOrderDetailResource($productOrder)); + } + + } + + /** + * 物流详情 + * + * @param Request $request + * @return void. + */ + public function expressDetail(Request $request) + { + $this->toValidator([ + 'order_id' => 'required|integer', + ], [], [ + 'order_id' => '订单号', + ]); + $order_id = $request->input('order_id'); + + $productOrder = ProductOrder::where('id', $order_id) + ->where('store_id', $this->getStoreId()) + ->where('user_id', $this->userId()) + ->first(); + if (!$productOrder) { + json_error('订单信息不存在'); + } + if (!$productOrder->express_no_id) { + json_error('无物流信息'); + } + Artisan::call('express', [ + 'type' => 'update', + '--id' => $productOrder->express_no_id, + ]); + $productOrder->load(['expressNo.details']); + if (!$productOrder->expressNo) { + json_error('无物流信息'); + } + + json_success([ + 'address' => $productOrder->only([ + 'express_name', + 'express_mobile', + 'express_region', + 'express_address', + 'express_no_id', + ]), + 'express' => $productOrder->expressNo, + ]); + } + + /** + * 获取支付 + * + * @return void + */ + public function getPay(Request $request) + { + $this->toValidator([ + 'order_id' => 'required|integer', + ], [], [ + 'order_id' => '订单号', + ]); + $order_id = $request->input('order_id'); + + $productOrder = ProductOrder::where('id', $order_id) + ->where('store_id', $this->getStoreId()) + ->where('user_id', $this->userId()) + ->first(); + if (!$productOrder) { + json_error('订单信息不存在'); + } + if (!$productOrder->express_name) { + json_error('请选择收货地址'); + } + if ($productOrder->is_pay == ProductOrder::IS_PAY_TRUE) { + json_error('订单已支付'); + } + if ($productOrder->status != ProductOrder::STATUS_UNPAID) { + json_error('支付失败,订单状态为' . $productOrder->status_string); + } + + /* @var Bill $bill */ + $bill = $productOrder->bills()->create([ + 'user_id' => 1, + 'openid' => $this->user()->openid, + 'title' => $this->getStore()->name . "订单支付", + 'pay_way' => Bill::PAY_WAY_WECHAT, + 'pay_amount' => $productOrder->total_pay_price, + ]); + + // 开发测试用 + if (app()->isLocal() && file_exists(base_path('./../dev.ini'))) { + $order = [ + 'out_trade_no' => $bill->pay_no, + 'description' => $bill->title, + 'amount' => [ + 'total' => $bill->pay_amount * 100, + ], + ]; + $result = Pay::wechat()->scan($order); + if (isset($result['code'])) { + json_error($result['message'] ?? ''); + } + json_success($result->toArray()); + } + + $order = [ + 'out_trade_no' => $bill->pay_no, + 'description' => $bill->title, + 'amount' => [ + 'total' => $bill->pay_amount * 100, + ], + 'payer' => [ + 'openid' => $bill->openid, + ] + ]; + $result = Pay::wechat()->mini($order); + + if (isset($result['code'])) { + json_error($result['message'] ?? ''); + } + + json_success($result->toArray()); + } + + /** + * 取消订单 + * + * @param Request $request + * @return void + * @throws \Throwable + */ + public function cancel(Request $request) + { + $this->toValidator([ + 'order_id' => 'required|integer', + ], [], [ + 'order_id' => '订单号', + ]); + $order_id = $request->input('order_id'); + + $productOrder = ProductOrder::where('id', $order_id) + ->where('store_id', $this->getStoreId()) + ->where('user_id', $this->userId()) + ->first(); + if (!$productOrder) { + json_error('订单信息不存在'); + } + if ($productOrder->is_pay == ProductOrder::IS_PAY_TRUE) { + json_error('订单已支付'); + } + if ($productOrder->status != ProductOrder::STATUS_UNPAID) { + json_error('取消失败,订单状态为' . $productOrder->status_string); + } + + DB::beginTransaction(); + try { + $productOrder->status = ProductOrder::STATUS_CANCEL; + $productOrder->cancel_status = ProductOrder::CANCEL_STATUS_FALSE; + $productOrder->cancel_time = time(); + $productOrder->cancel_remark = '用户主动取消'; + $productOrder->save(); + + $productOrder->logs()->create([ + 'content' => '未支付用户取消订单' + ]); + + $this->createNotice($productOrder, '您的订单已取消'); + + $productOrder->loadMissing(['items.drugstoreDrug' => fn(Relation $relation) => $relation->where('drugstore_id', $productOrder->drugstore_id)]); + foreach ($productOrder->items as $item) { + if ($item->drugstoreDrug) { + $item->drugstoreDrug->stock += $item->number; + $item->drugstoreDrug->frozen_number -= $item->number; + $item->drugstoreDrug->save(); + } + } + + // // 同步数据 + // $productOrder->refresh(); + // $productOrder->load(['items']); + // Artisan::call('data:sync', [ + // '--type' => 'product_order_sync', + // '--data' => $productOrder->toArray(), + // ]); + + if ($productOrder->is_online == ProductOrder::IS_ONLINE_TRUE && $productOrder->sync_order_no) { + // 同步数据(老) + Artisan::call('data:sync-old', [ + '--type' => 'syncOrder', + '--data' => json_encode([ + 'order_no' => $productOrder->sync_order_no, + 'status' => 2, // 1已支付 2已取消 3已退款 + 'time' => Carbon::createFromTimestamp($productOrder->cancel_time)->toDateTimeString(), + ]), + ]); + } + + DB::commit(); + } catch (Exception $exception) { + DB::rollBack(); + log_s('取消错误:' . var_export($request->all(), true) . $exception->getMessage() . $exception->getTraceAsString(), 'order', 'error'); + json_error('取消错误,请重试'); + } + + json_success('取消成功'); + } + + /** + * 确认收货 + * + * @param Request $request + * @return void + * @throws \Throwable + */ + public function confirm(Request $request) + { + $this->toValidator([ + 'order_id' => 'required|integer', + ], [], [ + 'order_id' => '订单号', + ]); + $order_id = $request->input('order_id'); + + /* @var ProductOrder $productOrder */ + $productOrder = ProductOrder::where('id', $order_id) + ->where('store_id', $this->getStoreId()) + ->where('user_id', $this->userId()) + ->first(); + if (!$productOrder) { + json_error('订单信息不存在'); + } + if ($productOrder->status != ProductOrder::STATUS_WAIT_ACCEPT) { + json_error('确认收货失败,订单状态为' . $productOrder->status_string); + } + + DB::beginTransaction(); + try { + $productOrder->status = ProductOrder::STATUS_CONFIRM; + $productOrder->save(); + + $productOrder->logs()->create([ + 'content' => '用户确认收货' + ]); + + $this->createNotice($productOrder, '您的订单已确认收货'); + + $productOrder->loadMissing(['items.drugstoreDrug' => fn(Relation $relation) => $relation->where('drugstore_id', $productOrder->drugstore_id)]); + foreach ($productOrder->items as $item) { + if ($item->drugstoreDrug) { + $item->drugstoreDrug->frozen_number -= $item->number; + $item->drugstoreDrug->save(); + } + } + + + // // 同步数据 + // $productOrder->refresh(); + // $productOrder->load(['items']); + // Artisan::call('data:sync', [ + // '--type' => 'product_order_sync', + // '--data' => $productOrder->toArray(), + // ]); + + if ($productOrder->order_type === 5) { + $client = new \GuzzleHttp\Client(); + $response = $client->request('POST', 'https://api.xiaokang88.com/open-api/client-confirm', [ + 'json' => [ + 'order_id' => $order_id, + ] + ]); + $body = json_decode($response->getBody()->getContents(), true); + if ($body['code'] != 0) { + exit($body['message']); + } + } + DB::commit(); + } catch (Exception $exception) { + DB::rollBack(); + log_s('确认收货错误:' . var_export($request->all(), true) . $exception->getMessage() . $exception->getTraceAsString(), 'order', 'error'); + json_error('确认收货错误,请重试'); + } + + json_success('确认收货成功'); + } + + /** + * 退款基础信息 + * + * @return void + */ + public function refundBase(Request $request) + { + $this->toValidator([ + 'order_id' => 'required|integer', + ], [], [ + 'order_id' => '订单号', + ]); + $order_id = $request->input('order_id'); + + $productOrder = ProductOrder::where('id', $order_id) + ->where('store_id', $this->getStoreId()) + ->where('user_id', $this->userId()) + ->first(); + if (!$productOrder) { + json_error('订单信息不存在'); + } + if (!in_array($productOrder->status, [ + ProductOrder::STATUS_WAIT_SEND, + ProductOrder::STATUS_WAIT_ACCEPT, + ProductOrder::STATUS_WAIT_COMMENT, + ProductOrder::STATUS_ACCEPTED, + ProductOrder::STATUS_CONFIRM, + ])) { + json_error('申请失败,订单状态为' . $productOrder->status_string); + } + + $reasons = pluck_to_array(ProductOrderRefund::REASON); + $refundTypes = pluck_to_array(ProductOrderRefund::REFUND_TYPE); + + json_success([ + 'total_pay_price' => $productOrder->total_pay_price, + 'trans_expenses' => $productOrder->trans_expenses, + 'reasons' => $reasons, + 'refund_types' => $refundTypes, + ]); + } + + /** + * 申请退款 + * + * @param Request $request + * @return void + * @throws \Throwable + */ + public function refundSubmit(Request $request) + { + $this->toValidator([ + 'refund_type' => 'required|integer', + 'reason_id' => 'required|integer', + 'order_id' => 'required|integer', + ], [], [ + 'refund_type' => '退款类型', + 'reason_id' => '退款原因', + 'order_id' => '订单号', + ]); + $order_id = $request->input('order_id'); + $refund_type = $request->input('refund_type'); + $reason_id = $request->input('reason_id'); + $image = $request->input('image'); + + /* @var ProductOrder $productOrder */ + $productOrder = ProductOrder::where('id', $order_id) + ->where('store_id', $this->getStoreId()) + ->where('user_id', $this->userId()) + ->first(); + if (!$productOrder) { + json_error('订单信息不存在'); + } + if (!in_array($productOrder->status, [ + ProductOrder::STATUS_WAIT_SEND, + ProductOrder::STATUS_WAIT_ACCEPT, + ProductOrder::STATUS_WAIT_COMMENT, + ProductOrder::STATUS_ACCEPTED, + ProductOrder::STATUS_CONFIRM, + ])) { + json_error('申请失败,订单状态为' . $productOrder->status_string); + } + if ($productOrder->is_pay != ProductOrder::IS_PAY_TRUE) { + json_error('申请失败,订单尚未支付'); + } + if ($productOrder->cancel_status == ProductOrder::CANCEL_STATUS_TRUE) { + json_error('申请失败,订单已取消'); + } + if (!in_array($productOrder->refund_status, [ + ProductOrder::REFUND_STATUS_NORMAL, + ProductOrder::REFUND_STATUS_CANCEL, + ])) { + json_error('申请失败,订单退款状态为' . $productOrder->refund_status_string); + } + if (in_array($productOrder->status, [ + ProductOrder::STATUS_WAIT_ACCEPT, + ProductOrder::STATUS_WAIT_COMMENT, + ProductOrder::STATUS_ACCEPTED, + ProductOrder::STATUS_CONFIRM, + ])) { + if (now()->diffInDays($productOrder->created_at) > 30) { + json_error('申请失败,已超过最大申请时效'); + } + } + + DB::beginTransaction(); + $refundResult = []; + try { + $beforeStatus = $productOrder->status; + $productOrder->status = ProductOrder::STATUS_REFUNDING; + $productOrder->refund_status = $beforeStatus == ProductOrder::STATUS_WAIT_SEND ? ProductOrder::REFUND_STATUS_AGREE : ProductOrder::REFUND_STATUS_APPLYING; + $productOrder->save(); + + $productOrder->logs()->create([ + 'content' => '用户申请退款' + ]); + + /* @var ProductOrderRefund $orderRefund */ + $orderRefund = $productOrder->refunds()->create([ + 'user_id' => $productOrder->user_id, + 'order_id' => $productOrder->id, + 'reason' => ProductOrderRefund::REASON[$reason_id], + 'refund_images' => $image, + 'refund_type' => ProductOrder::STATUS_WAIT_SEND ? ProductOrderRefund::REFUND_TYPE_REFUND_ONLY : ProductOrderRefund::REFUND_TYPE[$refund_type], + 'refund_no' => ProductOrderRefund::getNewNumber('refund_no'), + 'refund_price' => $productOrder->total_pay_price, + 'is_refund' => ProductOrderRefund::IS_REFUND_FALSE, + 'status' => $beforeStatus == ProductOrder::STATUS_WAIT_SEND ? ProductOrderRefund::STATUS_PASS : ProductOrderRefund::STATUS_APPLY, + ]); + + if ($beforeStatus == ProductOrder::STATUS_WAIT_SEND) { + $refundResult = $productOrder->toRefund($productOrder->total_pay_price, $orderRefund->refund_no); + } + + DB::commit(); + } catch (Exception $exception) { + DB::rollBack(); + log_s('申请错误:' . var_export($request->all(), true) . $exception->getMessage() . $exception->getTraceAsString(), 'order', 'error'); + json_error('申请错误,请重试'); + } + + if (count($refundResult)) { + response()->json($refundResult)->throwResponse(); + } + json_success('申请成功'); + } +}