From 90a80a57671a056a0113eb8dd240b455669b9e88 Mon Sep 17 00:00:00 2001 From: Rmiku <46063139+Rmiku@users.noreply.github.com> Date: Mon, 6 Jan 2025 18:20:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0HisHttpClient?= =?UTF-8?q?=EF=BC=8C=E6=B7=BB=E5=8A=A0=E6=8C=82=E5=8F=B7=EF=BC=8C=E9=97=A8?= =?UTF-8?q?=E8=AF=8A=EF=BC=8C=E5=9B=9E=E8=B0=83=E6=A8=A1=E5=9D=97=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=95=B0=E6=8D=AE=E5=BA=93=E6=96=87=E4=BB=B6?= =?UTF-8?q?=EF=BC=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Dictionary/Order/PayType.php | 15 + .../Controllers/Notify/NotifyController.php | 94 +++++ .../Outpatient/PaymentController.php | 47 +++ .../Outpatient/PendingController.php | 64 ++++ .../Outpatient/RecordController.php | 2 +- .../Registration/RegisterController.php | 40 +++ app/Http/Logics/Dictionary/ItemLogic.php | 10 +- app/Http/Logics/Notify/NotifyLogic.php | 338 ++++++++++++++++++ app/Http/Logics/Outpatient/PaymentLogic.php | 237 ++++++++++++ app/Http/Logics/Outpatient/PendingLogic.php | 104 ++++++ app/Http/Logics/Outpatient/RecordLogic.php | 10 +- app/Http/Logics/Registration/RecordLogic.php | 12 +- .../Logics/Registration/RegisterLogic.php | 250 +++++++++++++ .../Logics/Registration/ScheduleLogic.php | 10 +- .../Requests/Registration/RegisterRequest.php | 66 ++++ .../Pending/PendingDetailsResource.php | 58 +++ .../Pending/PendingListsResource.php | 50 +++ app/Models/Order.php | 3 +- app/Providers/AppServiceProvider.php | 12 +- app/Services/HisHttp/Client.php | 51 ++- app/Utils/Helpers.php | 21 +- .../HisHttpClient/ClientMockHttpTransfer.php | 37 +- .../HisSoapClient/ClientMockSoapTransfer.php | 3 +- app/Utils/Transfer/HttpTransferAbstract.php | 4 +- config/logging.php | 27 ++ ...0715_create_registration_records_table.php | 2 +- ...reate_outpatient_payment_records_table.php | 2 +- .../src/Cores/Exceptions/RuntimeException.php | 2 - routes/api.php | 11 + 29 files changed, 1517 insertions(+), 65 deletions(-) create mode 100644 app/Http/Controllers/Notify/NotifyController.php create mode 100644 app/Http/Controllers/Outpatient/PaymentController.php create mode 100644 app/Http/Controllers/Outpatient/PendingController.php create mode 100644 app/Http/Logics/Notify/NotifyLogic.php create mode 100644 app/Http/Logics/Outpatient/PaymentLogic.php create mode 100644 app/Http/Logics/Outpatient/PendingLogic.php create mode 100644 app/Http/Logics/Registration/RegisterLogic.php create mode 100644 app/Http/Requests/Registration/RegisterRequest.php create mode 100644 app/Http/Resources/Outpatient/Pending/PendingDetailsResource.php create mode 100644 app/Http/Resources/Outpatient/Pending/PendingListsResource.php diff --git a/app/Dictionary/Order/PayType.php b/app/Dictionary/Order/PayType.php index 03d1b88..378c423 100644 --- a/app/Dictionary/Order/PayType.php +++ b/app/Dictionary/Order/PayType.php @@ -47,4 +47,19 @@ enum PayType: int self::MEDICAL_INSURANCE_PAY => 'YB', }; } + + /** + * His System Pay Code + * @return string + */ + public function hisCode(): string + { + return match ($this) { + self::AGGREGATION_PAY => '12', + self::UNION_PAY => '12', + self::WECHAT_PAY => '88', + self::ALI_PAY => '89', + self::MEDICAL_INSURANCE_PAY => '3', + }; + } } diff --git a/app/Http/Controllers/Notify/NotifyController.php b/app/Http/Controllers/Notify/NotifyController.php new file mode 100644 index 0000000..ea959d7 --- /dev/null +++ b/app/Http/Controllers/Notify/NotifyController.php @@ -0,0 +1,94 @@ +notify_logic = new NotifyLogic(); + $this->setChannel('notify'); + } + + /** + * @return ResponseInterface + * @throws InvalidArgumentException + * @throws RuntimeException + * @throws ReflectionException + * @throws Throwable + */ + public function notify(): ResponseInterface + { + $app = getWeChatMiniProgramPaymentApp(); + $server = $app->getServer(); + + $server->handlePaid(function (Message $message, \Closure $next) use ($app) { + // $message->out_trade_no 获取商户订单号 + // $message->payer['openid'] 获取支付者 openid + // 🚨🚨🚨 注意:推送信息不一定靠谱哈,请务必验证 + // 建议是拿订单号调用微信支付查询接口, + $this->info('接收回调消息', $message->toArray()); + try{ + // 验证通过,业务处理 + $app->getValidator()->validate($app->getRequest()); + + // 查询订单 + $response = $app->getClient()->postXml(V2Api::QUERY_ORDER->value, [ + 'body' => [ + 'appid' => config('wechat.payment.app_id'), + 'mch_id' => config('wechat.payment.mch_id'), + 'out_trade_no' => $message->out_trade_no + ] + ]); + if ($response->isFailed()) { + // 查询失败 + $this->warning('订单查询失败', [$message->out_trade_no]); + return $next($message); + } + + // 不成功 + if ( + !isset($response['return_code'], $response['result_code']) || + $response["return_code"] !== "SUCCESS" || + $response["result_code"] !== "SUCCESS" + ) { + $this->warning('订单查询支付失败', [$message->out_trade_no, $response]); + return $next($message); + } + + // 业务处理 + $result = $this->notify_logic->notifyHandle($message); + + $this->info('接受回调消息结果', ['result' => $result]); + } catch(\Exception $e){ + // 验证失败 + $err_msg = '订单验证签名失败:'. $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine(); + $this->error($err_msg, [$message->toArray()]); + } + + return $next($message); + }); + + return $server->serve(); + } +} diff --git a/app/Http/Controllers/Outpatient/PaymentController.php b/app/Http/Controllers/Outpatient/PaymentController.php new file mode 100644 index 0000000..5be5409 --- /dev/null +++ b/app/Http/Controllers/Outpatient/PaymentController.php @@ -0,0 +1,47 @@ +payment_logic = new PaymentLogic(); + } + + /** + * 缴费 + * @param Request $request + * @param string $patient_id + * @param string $serial_no + * @return JsonResponse + * @throws GeneralException + */ + public function payment(Request $request, string $patient_id, string $serial_no): JsonResponse + { + $validated = $request->validate([ + 'prescription_ids' => 'required', + 'reg_id' => 'required', + ], [ + 'prescription_ids.required' => '请选择要缴纳的处方', + 'reg_id.required' => '请选择要缴纳的处方', + ]); + + $response = $this->payment_logic->payment($patient_id, $serial_no, $validated['prescription_ids'], $validated['reg_id']); + + return jsonResponse(Response::HTTP_OK, 'success', PendingListsResource::make($response)->toArray()); + } +} diff --git a/app/Http/Controllers/Outpatient/PendingController.php b/app/Http/Controllers/Outpatient/PendingController.php new file mode 100644 index 0000000..3986e89 --- /dev/null +++ b/app/Http/Controllers/Outpatient/PendingController.php @@ -0,0 +1,64 @@ +pending_logic = new PendingLogic(); + } + + /** + * 获取缴费记录列表 + * @param Request $request + * @param string $patient_id + * @return JsonResponse + * @throws GeneralException + */ + public function lists(Request $request, string $patient_id): JsonResponse + { + $response = $this->pending_logic->getLists($patient_id); + + return jsonResponse(Response::HTTP_OK, 'success', PendingListsResource::make($response)->toArray()); + } + + /** + * 获取待缴费详情 + * @param Request $request + * @param string $patient_id + * @param string $serial_no + * @return JsonResponse + * @throws GeneralException + */ + public function details(Request $request, string $patient_id, string $serial_no): JsonResponse + { + $validated = $request->validate([ + 'prescription_ids' => 'required', + 'reg_id' => 'required', + ], [ + 'prescription_ids.required' => '请选择要缴纳的处方', + 'reg_id.required' => '请选择要缴纳的处方', + ]); + + $response = $this->pending_logic->getDetails($patient_id, $serial_no, $validated['prescription_ids'], $validated['reg_id']); + + return jsonResponse(Response::HTTP_OK, 'success.', PendingDetailsResource::make($response)->toArray()); + } +} diff --git a/app/Http/Controllers/Outpatient/RecordController.php b/app/Http/Controllers/Outpatient/RecordController.php index 3f8c379..e245418 100644 --- a/app/Http/Controllers/Outpatient/RecordController.php +++ b/app/Http/Controllers/Outpatient/RecordController.php @@ -47,7 +47,7 @@ class RecordController } /** - * 退号 + * 获取缴费记录详情 * @param Request $request * @param string $patient_id * @param string $serial_no diff --git a/app/Http/Controllers/Registration/RegisterController.php b/app/Http/Controllers/Registration/RegisterController.php index b3d9bbc..8c1cf25 100644 --- a/app/Http/Controllers/Registration/RegisterController.php +++ b/app/Http/Controllers/Registration/RegisterController.php @@ -1 +1,41 @@ register_logic = new RegisterLogic(); + } + + /** + * 获取挂号记录列表 + * @param RegisterRequest $request + * @param string $patient_id + * @return JsonResponse + * @throws GeneralException + */ + public function register(RegisterRequest $request, string $patient_id): JsonResponse + { + $validated = $request->safe()->only(['date', 'dept_id', 'doctor_id', 'reg_id']); + + $response = $this->register_logic->register($patient_id, $validated['date'], $validated['dept_id'], $validated['doctor_id'], $validated['reg_id']); + + return jsonResponse(Response::HTTP_OK, 'success', RecordListsResource::make($response)->toArray()); + } +} diff --git a/app/Http/Logics/Dictionary/ItemLogic.php b/app/Http/Logics/Dictionary/ItemLogic.php index aaea387..7e68628 100644 --- a/app/Http/Logics/Dictionary/ItemLogic.php +++ b/app/Http/Logics/Dictionary/ItemLogic.php @@ -4,7 +4,7 @@ declare(strict_types = 1); namespace App\Http\Logics\Dictionary; use App\Exceptions\GeneralException; -use App\Services\HisSoap\Client; +use App\Services\HisHttp\Client; use App\Utils\Traits\Logger; use App\Utils\Traits\MiniProgramAuth; use App\Utils\Traits\UniversalEncryption; @@ -17,7 +17,7 @@ class ItemLogic use MiniProgramAuth; use UniversalEncryption; - private Client $his_soap; + private Client $his_client; /** * ItemLogic Construct @@ -26,7 +26,7 @@ class ItemLogic public function __construct() { $this->authInitialize(); - $this->his_soap = app('HisSoapService'); + $this->his_client = app('HisHttpService'); } /** @@ -35,7 +35,7 @@ class ItemLogic */ public function getLists() { - $response = $this->his_soap->getDictionaryLists(); + $response = $this->his_client->getDictionaryLists(); if (!isset($response['RESULTCODE']) || $response['RESULTCODE'] !== '0') { throw new GeneralException($response['ERRORMSG'] ?? '找不到缴费项目分类列表!', Response::HTTP_SERVICE_UNAVAILABLE); } @@ -51,7 +51,7 @@ class ItemLogic */ public function getDetails(int $type_id): array { - $response = $this->his_soap->getDictionaryDetails($type_id); + $response = $this->his_client->getDictionaryDetails($type_id); if (!isset($response['RESULTCODE']) || $response['RESULTCODE'] !== '0') { throw new GeneralException($response['ERRORMSG'] ?? '找不到缴费项目分类详情!', Response::HTTP_SERVICE_UNAVAILABLE); } diff --git a/app/Http/Logics/Notify/NotifyLogic.php b/app/Http/Logics/Notify/NotifyLogic.php new file mode 100644 index 0000000..b54c9d3 --- /dev/null +++ b/app/Http/Logics/Notify/NotifyLogic.php @@ -0,0 +1,338 @@ +setChannel('notify'); + $this->his_client = app('HisHttpService'); + $this->patient_model = new PatientModel(); + $this->order_model = new OrderModel(); + $this->payment_app = getWeChatMiniProgramPaymentApp(); + } + + /** + * 回调消息确费操作 + * @param Message $notify + * @return bool + * @throws InvalidConfigException|RedisException + */ + public function notifyHandle(Message $notify): bool + { + $lock_id = $this->addLockOrder($notify->out_trade_no); + if (!$lock_id) { + return false; + } + + // 查找订单信息 + $order_info = $this->order_model->getOrderInfoByOrderId($notify['out_trade_no']); + + // 如果订单不存在 / 订单状态不为初始状态 / 订单已经处理过了 + if ( + empty($order_info) || + $order_info->status != Status::NORMAL->value || + $order_info->notify_status == NotifyStatus::ACCEPTED->value + ) { + $this->unlockOrder($notify['out_trade_no'], $lock_id); + return true; + } + + // 设置状态为1 + $order_info->notify_status = NotifyStatus::ACCEPTED->value; + $order_info->transaction_id = $notify->transaction_id; + $order_info->payment_at = date('Y-m-d H:i:s', strtotime($notify->time_end)); + $order_info->save(); + + try { + switch ($order_info->type) { + case Type::TODAY_REGISTRATION->value: + case Type::APPOINTMENT_REGISTRATION->value: + $this->registrationOrderHandle($order_info, $notify); + break; + case Type::OUTPATIENT_PAYMENT->value: + $this->outpatientOrderHandle($order_info, $notify); + break; + default: + break; + } + } catch (GeneralException|Exception $e) { + $err_msg = $e->getMessage().' ON '. $e->getFile(). ':'. $e->getLine(); + recordLog('NotifyLog', $err_msg); + $this->unlockOrder($notify->out_trade_no, $lock_id); + return false; + } + + $this->unlockOrder($notify->out_trade_no, $lock_id); + return true; + } + + /** + * 挂号订单操作 + * @param OrderModel $order_info + * @param Message $notify + * @throws GeneralException + */ + protected function registrationOrderHandle(OrderModel $order_info, Message $notify): void + { + // 挂号确认 + $patient = $order_info->patient; + $record = $order_info->registrationRecord; + $extra = json_decode($record->extra_info, true); + $pay_time = strtotime($notify->time_end); + + $data = [ + $order_info->patient_id, + $order_info->patient_name, + $record->dept_id, + $record->docttor_id, + $record->reg_id, + $extra['SHIFT']['RANKID'], + $record->date, + PayType::WECHAT_PAY->hisCode(), + $order_info->order_id, + '', + '', + '', + '', + (string) ($notify->total_fee / 100), + '' + ]; + $response = $this->his_client->registerConfirm(... $data); + $this->info('挂号订单出入参:'.$order_info->order_id, [$data, $response]); + + if (isset($response['RESULTCODE']) && $response['RESULTCODE'] === '0') { + // 成功流程 + $order_info->orderConfirm($order_info->order_id, $response['VISITNO'], $response); + + // 支付平台业务确认 + $this->unifyConfirm($notify['out_trade_no'], $response['VISITNO'], $notify['openid'], $notify['transaction_id']); + + // 推送成功 + } else if (isset($response['RESULTCODE'])) { + // 失败流程 + $this->handleOrderReverse($order_info, $response['ERRORMSG'] ?? ''); + + // 推送失败 + } else { + // 异常流程 + $order_info->abnormalOrderOpera($order_info->id); + + // 推送异常 + } + } + + /** + * 门诊缴费订单操作 + * @param OrderModel $order_info + * @param Message $notify + * @throws GeneralException + */ + protected function outpatientOrderHandle(OrderModel $order_info, Message $notify): void + { + // 挂号确认 + $patient = $order_info->patient; + $record = $order_info->outpatientPaymentRecord; + $extra = json_decode($record->extra_info, true); + $pay_time = strtotime($notify->time_end); + + $data = [ + $order_info->patient_id, + '0', + date('Y-m-d', $extra['JSRQ']), + $extra['prescription_ids'], + $extra['TREAID'], + '', + $order_info->order_id, + PayType::WECHAT_PAY->hisCode(), + (string) ($order_info['total_fee'] / 100), + (string) ($order_info['self_fee'] / 100) + ]; + $response = $this->his_client->confirmOutpatient(... $data); + $this->info('缴费订单出入参:'.$order_info->order_id, [$data, $response]); + + // 保存返回信息 + if (isset($response['ResultCode']) && $response['ResultCode'] === '0') { + // 成功流程 + $order_info->orderConfirm($order_info->order_id, $response['HOSTRANNO'], $response); + + // 支付平台业务确认 + $this->unifyConfirm($notify['out_trade_no'], $response['HOSTRANNO'], $notify['openid'], $notify['transaction_id']); + + // 推送成功 + + } else if (isset($response['ResultCode'])) { + // 失败流程 + $this->handleOrderReverse($order_info, $response['ERRORMSG']); + + // 推送失败 + + } else { + // 异常流程 + $order_info->abnormalOrderOpera($order_info->id); + + // 推送异常 + + } + } + + /** + * 退款 + * @param string $order_id + * @param string $refund_order_id + * @param int $refund_fee + * @param string $refund_reason + * @return bool + */ + protected function refundPaidOrder(string $order_id, string $refund_order_id, int $refund_fee, string $refund_reason): bool + { + try { + $refund = new RefundOrder($order_id, $refund_order_id, (string)($refund_fee / 100), '确认挂号失败,自动冲正,错误消息:'. $refund_reason); + $response = Unify::common(env('unify'))->order->refund($refund); + $this->info('退号退费结果', $response); + + if ($response['status'] === 200 || $response['success'] === true) { + return true; + } + + return false; + } catch (ReflectionException|Exception $e) { + $err_msg = "订单号:{$order_id}, 退款异常,错误消息:{$e->getMessage()} ON {$e->getFile()}:{$e->getLine()}"; + $this->error($err_msg); + + return false; + } + } + + /** + * 订单冲正 + * @param OrderModel $order_info + * @param string $err_msg + * @return void + */ + protected function handleOrderReverse(OrderModel $order_info, string $err_msg): void + { + $refund_order_id = $order_info->getRefundOrderId($order_info->order_id); + $order_info->createRefundOReverseOrder( + $order_info->id, + $refund_order_id, + PayType::from($order_info->pay_type), + $order_info->fee, + $order_info->open_id, + $order_info->patient_id, + $order_info->patient_name, + Type::from($order_info->type), + SourceId::OFFICIAL_ACCOUNT + ); + + $refund_res = $this->refundPaidOrder($order_info->order_id, $refund_order_id, $order_info->fee, $err_msg); + + // 冲正失败 + if (!$refund_res) { + $this->info('订单号'. $order_info->order_id. '冲正失败'); + $order_info->reverseOrderOpera($refund_order_id, $order_info->fee, false); + return; + } + + $this->info('订单号'. $order_info->order_id. '冲正成功'); + $order_info->reverseOrderOpera($refund_order_id, $order_info->fee, true); + } + + /** + * 订单加锁 + * @param string $order_id + * @return false|string + * @throws RedisException + */ + protected function addLockOrder(string $order_id): false|string + { + $result = $this->addLock($order_id); + $this->info('订单加锁', [$order_id, $result]); + + return $result; + } + + /** + * 订单解锁 + * @param string $order_id + * @param string $lock_id + * @return bool + * @throws RedisException + */ + protected function unlockOrder(string $order_id, string $lock_id): bool + { + // 解锁 + $result = $this->unlock($order_id, $lock_id); + $this->info('订单解锁', [$order_id, $lock_id, $result]); + + return $result; + } + + /** + * 支付平台业务确认 + * @param string $order_id + * @param string $his_serial_no + * @param string $pay_account + * @param string $tran_no + * @return bool + */ + protected function unifyConfirm(string $order_id, string $his_serial_no, string $pay_account, string $tran_no): bool + { + try { + $confirm_order = new ConfirmOrderForEx($order_id, $his_serial_no, $pay_account, $tran_no); + $response = Unify::common(env('unify'))->order->confirmForEx($confirm_order); + $this->info('支付平台确认结果', $response); + + if ($response['status'] === 200 || $response['success'] === true) { + return true; + } + + return false; + } catch (ReflectionException|Exception $e) { + $err_msg = "订单号:{$order_id}, 支付平台确认结果异常,错误消息:{$e->getMessage()} ON {$e->getFile()}:{$e->getLine()}"; + $this->error($err_msg); + + return false; + } + } +} diff --git a/app/Http/Logics/Outpatient/PaymentLogic.php b/app/Http/Logics/Outpatient/PaymentLogic.php new file mode 100644 index 0000000..3398053 --- /dev/null +++ b/app/Http/Logics/Outpatient/PaymentLogic.php @@ -0,0 +1,237 @@ +authInitialize(); + $this->setChannel('outpatient'); + $this->his_client = app('HisHttpService'); + $this->order_model = new Order(); + } + + /** + * 支付 + * @param string $patient_id + * @param string $serial_no + * @param string $prescription_ids + * @param string $reg_id + * @return array + * @throws GeneralException + */ + public function payment(string $patient_id, string $serial_no, string $prescription_ids, string $reg_id): array + { + // 基础信息 + $patient_info = $this->getPatientInfo($patient_id, $this->open_id); + $pending_info = $this->getPendingPrescriptionDetails($patient_id, $serial_no, $prescription_ids, $reg_id); + + // 创建订单 + $order_type = Type::OUTPATIENT_PAYMENT; + $pay_type = PayType::WECHAT_PAY; + $order_id = $this->order_model->getOrderId($pay_type, 'M'); + $total_fee = (float)(string) array_sum(array_column($pending_info['prescription_lists'], 'ZFJE')); + + $order = $this->createOrder($order_id, $pay_type, $total_fee, $order_type, $patient_info, $pending_info); + + // 申请支付 + $pay_data = $this->applyPayment($order_type, $order_id, $total_fee, $patient_info['PATIENTID'], $patient_info['NAME']); + + // 去除无用数据 + unset($pay_data['merchantId'], $pay_data['merchantName'], $pay_data['channelId'], $pay_data['channelName']); + + return $pay_data; + } + + /** + * 获取患者信息 + * @param string $patient_id + * @param string $open_id + * @return mixed + * @throws GeneralException + */ + protected function getPatientInfo(string $patient_id, string $open_id): mixed + { + $info = $this->patient_model->getBindPatientInfo($open_id, $patient_id); + if (empty($info)) { + throw new GeneralException('找不到患者信息,请重新再试!', Response::HTTP_BAD_REQUEST); + } + + $patient_info = $this->his_client->getPatientInfo($info['patient_id'], CardType::OUTPATIENT_NO, $info['name']); + if (!isset($patient_info['RESULTCODE']) || $patient_info['RESULTCODE'] !== '0') { + throw new GeneralException($patient_info['ERRORMSG'] ?? '找不到患者信息,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + // 添加Patient 表ID + $patient_info['id'] = $info['id']; + + $this->info('缴费患者信息', $info); + return $info; + } + + /** + * 获取缴费处方详情 + * @param string $patient_id + * @param string $serial_no + * @param string $prescription_ids + * @param string $reg_id + * @return array + * @ + * @throws GeneralException + */ + protected function getPendingPrescriptionDetails(string $patient_id, string $serial_no, string $prescription_ids, string $reg_id): array + { + $response = $this->his_client->getPendingLists($patient_id); + + if (!isset($response['RESULTCODE']) || $response['RESULTCODE'] !== '0') { + throw new GeneralException($response['ERRORMSG'] ?? '暂无相关缴费记录!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + // 获取具体的缴费详情 + $response = xmlArrayToListByKey($response, 'ITEM'); + foreach ($response['ITEM'] as $v) { + if ($v['JZXH'] === $serial_no) { + $info = $v; + break; + } + } + + if (empty($info)) { + throw new GeneralException('查询不到待缴费处方,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + $response = $this->his_client->getPendingDetails($prescription_ids, $serial_no, $reg_id); + if (!isset($response['RESULTCODE']) || $response['RESULTCODE'] !== '0') { + throw new GeneralException($response['ERRORMSG'] ?? '暂无相关缴费记录!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + // 获取具体的缴费详情 + $response = xmlArrayToListByKey($response, 'ITEM'); + foreach ($response['ITEM'] as $v) { + if (strpos($prescription_ids, $v['CFID'])) { + $info['prescription_lists'][] = $v; + break; + } + } + + if (empty($info['prescription_lists'])) { + throw new GeneralException('查询不到待缴费处方详情,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + $info['prescription_ids'] = $prescription_ids; + return $info; + } + + /** + * 创建订单表 + * @param string $order_id + * @param PayType $pay_type + * @param float $total_fee + * @param Type $order_type + * @param array $patient_info + * @param array $pending_info + * @return mixed + * @throws GeneralException + */ + protected function createOrder(string $order_id, PayType $pay_type, float $total_fee, Type $order_type, array $patient_info, array $pending_info): mixed + { + + // 挂号记录表 + $pay_record_data = [ + 'relate_patient_id' => $patient_info['id'], + 'dept_id' => $pending_info['BQDM'], + 'dept_name' => $pending_info['BQMC'], + 'doctor_id' => $pending_info['YSGH'], + 'doctor_name' => $pending_info['YSMC'], + 'visit_date' => date('Y-m-d', strtotime($pending_info['JZRQ'])), + 'total_amount' => $total_fee, + 'pre_settle_status' => 0, + 'extra_info' => json_encode($pending_info, JSON_UNESCAPED_UNICODE), + ]; + + $order = $this->order_model->createOrder( + $order_id, + $pay_type, + $total_fee * 100, + $this->open_id, + $patient_info['PATIENTID'], + $patient_info['NAME'], + $order_type, + SourceId::MINI_PROGRAM, + $pay_record_data + ); + if (empty($order)) { + throw new GeneralException('创建缴费单失败,请重新再试!'); + } + + $this->info('创建订单,ID:'. $order_id->id); + return $order; + } + + /** + * 申请支付 + * @param Type $order_type + * @param string $order_id + * @param float $reg_fee + * @param string $patient_id + * @param string $patient_name + * @return array + * @throws GeneralException + */ + protected function applyPayment(Type $order_type, string $order_id, float $reg_fee, string $patient_id, string $patient_name): array + { + try { + $order_obj = new CreateOrder( + $order_type->label(), + $order_id, + (string) $reg_fee, + '1', + $patient_id. '|'. $patient_name, + 'A', + $this->open_id, + url('/Api/Notify', [], true) + ); + + $response = Unify::pay(env('unify'))->mini->jsapi($order_obj); + $this->info('jsapi 支付参数', $response); + + if (!$response['success'] || empty($response['response'])) { + throw new GeneralException('申请支付失败,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + return $response['response']; + } catch (InvalidConfigException|RuntimeException|ReflectionException $e) { + throw new GeneralException('申请支付失败,请重新再试!', Response::HTTP_INTERNAL_SERVER_ERROR); + } + } +} diff --git a/app/Http/Logics/Outpatient/PendingLogic.php b/app/Http/Logics/Outpatient/PendingLogic.php new file mode 100644 index 0000000..c4e4d7e --- /dev/null +++ b/app/Http/Logics/Outpatient/PendingLogic.php @@ -0,0 +1,104 @@ +authInitialize(); + $this->his_client = app('HisHttpService'); + } + + /** + * 获取挂号记录列表 + * @param string $patient_id + * @return array + * @throws GeneralException + */ + public function getLists(string $patient_id,): array + { + $response = $this->his_client->getPendingLists($patient_id); + + if (!isset($response['RESULTCODE']) || $response['RESULTCODE'] !== '0') { + throw new GeneralException($response['ERRORMSG'] ?? '暂无相关缴费记录!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + // 缓存2小时 + Cache::set('Outpatient.Pending.'. $this->open_id.'.'. $patient_id, json_encode($response, JSON_UNESCAPED_UNICODE), 2 * 60 * 60); + return $response; + } + + /** + * 获取缴费记录详情 + * @param string $patient_id + * @param string $serial_no + * @param string $prescription_ids + * @param string $reg_id + * @return array + * @throws GeneralException + */ + public function getDetails(string $patient_id, string $serial_no, string $prescription_ids, string $reg_id): array + { + $this->getCachePendingLists($patient_id, $serial_no); + + $response = $this->his_client->getPendingDetails($prescription_ids, $serial_no, $reg_id); + + if (!isset($response['RESULTCODE']) || $response['RESULTCODE'] !== '0') { + throw new GeneralException($response['ERRORMSG'] ?? '暂无相关缴费详情!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + return $response; + } + + /** + * 获取缓存里的记录详情 + * @param string $patient_id + * @param string $serial_no + * @return mixed + * @throws GeneralException + */ + protected function getCachePendingLists(string $patient_id, string $serial_no): mixed + { + $cache_key = 'Outpatient.Pending.'. $this->open_id.'.'. $patient_id; + + $pending_lists = Cache::get($cache_key); + if (empty($pending_lists)) { + throw new GeneralException($response['ERRORMSG'] ?? '查询不到缴费记录,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE); + } + $pending_lists = json_decode($pending_lists, true); + + // 获取具体的缴费详情 + $pending_lists = xmlArrayToListByKey($pending_lists, 'ITEM'); + foreach ($pending_lists['ITEM'] as $v) { + if ($v['JZXH'] === $serial_no) { + $info = $v; + break; + } + } + + if (empty($info)) { + throw new GeneralException('查询不到待缴费处方,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + return $info; + } +} diff --git a/app/Http/Logics/Outpatient/RecordLogic.php b/app/Http/Logics/Outpatient/RecordLogic.php index 4aa98e8..64f50c0 100644 --- a/app/Http/Logics/Outpatient/RecordLogic.php +++ b/app/Http/Logics/Outpatient/RecordLogic.php @@ -6,7 +6,7 @@ namespace App\Http\Logics\Outpatient; use App\Exceptions\GeneralException; use App\Models\Order; use App\Models\RegistrationRecord; -use App\Services\HisSoap\Client; +use App\Services\HisHttp\Client; use App\Utils\Traits\Logger; use App\Utils\Traits\MiniProgramAuth; use Illuminate\Auth\AuthenticationException; @@ -18,7 +18,7 @@ class RecordLogic use Logger; use MiniProgramAuth; - private Client $his_soap; + private Client $his_client; /** * RecordLogic Construct @@ -27,7 +27,7 @@ class RecordLogic public function __construct() { $this->authInitialize(); - $this->his_soap = app('HisSoapService'); + $this->his_client = app('HisHttpService'); } /** @@ -40,7 +40,7 @@ class RecordLogic */ public function getRecordLists(string $patient_id, string $start_date, string $end_date): array { - $response = $this->his_soap->getPaidLists($patient_id, $start_date, $end_date); + $response = $this->his_client->getPaidLists($patient_id, $start_date, $end_date); if (!isset($response['RESULTCODE']) || $response['RESULTCODE'] !== '0') { throw new GeneralException($response['ERRORMSG'] ?? '暂无相关挂号记录!', Response::HTTP_SERVICE_UNAVAILABLE); @@ -62,7 +62,7 @@ class RecordLogic { $this->getCacheRecordInfo($patient_id, $serial_no); - $response = $this->his_soap->getPaidDetails($serial_no); + $response = $this->his_client->getPaidDetails($serial_no); if (!isset($response['RESULTCODE']) || $response['RESULTCODE'] !== '0') { throw new GeneralException($response['ERRORMSG'] ?? '暂无相关缴费详情!', Response::HTTP_SERVICE_UNAVAILABLE); diff --git a/app/Http/Logics/Registration/RecordLogic.php b/app/Http/Logics/Registration/RecordLogic.php index b39be41..21becdb 100644 --- a/app/Http/Logics/Registration/RecordLogic.php +++ b/app/Http/Logics/Registration/RecordLogic.php @@ -9,7 +9,7 @@ use App\Dictionary\Order\Type; use App\Exceptions\GeneralException; use App\Models\Order; use App\Models\RegistrationRecord; -use App\Services\HisSoap\Client; +use App\Services\HisHttp\Client; use App\Utils\Traits\Logger; use App\Utils\Traits\MiniProgramAuth; use Illuminate\Auth\AuthenticationException; @@ -24,7 +24,7 @@ class RecordLogic use Logger; use MiniProgramAuth; - private Client $his_soap; + private Client $his_client; private RegistrationRecord $reg_record_model; @@ -38,7 +38,7 @@ class RecordLogic { $this->authInitialize(); $this->setChannel('refund'); - $this->his_soap = app('HisSoapService'); + $this->his_client = app('HisHttpService'); $this->reg_record_model = new RegistrationRecord(); $this->order_model = new Order(); } @@ -53,7 +53,7 @@ class RecordLogic */ public function getRecordLists(string $patient_id, string $start_date, string $end_date): array { - $response = $this->his_soap->getRegisterRecordLists($patient_id, $start_date, $end_date); + $response = $this->his_client->getRegisterRecordLists($patient_id, $start_date, $end_date); if (!isset($response['RESULTCODE']) || $response['RESULTCODE'] !== '0') { throw new GeneralException($response['ERRORMSG'] ?? '暂无相关挂号记录!', Response::HTTP_SERVICE_UNAVAILABLE); @@ -109,7 +109,7 @@ class RecordLogic $this->info('患者需退号的数据库挂号记录', $reg_record->toArray()); // 检查是否可以退号 - $response = $this->his_soap->checkRefundRegisterStatus($reg_serial_no); + $response = $this->his_client->checkRefundRegisterStatus($reg_serial_no); $this->info('检查是否可进行退号', $response); if (!isset($response['RESULTCODE']) || $response['RESULTCODE'] !== '0') { @@ -117,7 +117,7 @@ class RecordLogic } // 开始退号 - $response = $this->his_soap->refundRegister($reg_serial_no, $order_id, date('Y-m-d'), date('H:i:s'), (string) ($fee / 100)); + $response = $this->his_client->refundRegister($reg_serial_no, $order_id, date('Y-m-d'), date('H:i:s'), (string) ($fee / 100)); $this->info('退号结果', $response); if (!isset($response['RESULTCODE']) || $response['RESULTCODE'] !== '0') { diff --git a/app/Http/Logics/Registration/RegisterLogic.php b/app/Http/Logics/Registration/RegisterLogic.php new file mode 100644 index 0000000..64178bb --- /dev/null +++ b/app/Http/Logics/Registration/RegisterLogic.php @@ -0,0 +1,250 @@ +authInitialize(); + $this->setChannel('registration'); + $this->his_client = app('HisHttpService'); + $this->order_model = new Order(); + $this->patient_model = new Patient(); + } + + /** + * @param string $patient_id + * @param string $date + * @param string $dept_id + * @param string $doctor_id + * @param string $reg_id + * @return array + * @throws GeneralException + */ + public function register(string $patient_id, string $date, string $dept_id, string $doctor_id, string $reg_id): array + { + // 基础信息 + $patient_info = $this->getPatientInfo($patient_id, $this->open_id); + $schedule_info = $this->getRegisterScheduleDetails($date, $dept_id, $doctor_id, $reg_id); + + // 锁号? + + // 创建订单 + $order_type = $date === date('Y-m-d') ? Type::TODAY_REGISTRATION : Type::APPOINTMENT_REGISTRATION; + $pay_type = PayType::WECHAT_PAY; + $order_id = $this->order_model->getOrderId($pay_type, 'M'); + $reg_fee = (float)(string) $schedule_info['FEE']; + + $order = $this->createOrder($order_id, $pay_type, $reg_fee, $order_type, $patient_info, $schedule_info); + + // 申请支付 + $pay_data = $this->applyPayment($order_type, $order_id, $reg_fee, $patient_info['PATIENTID'], $patient_info['NAME']); + + // 去除无用数据 + unset($pay_data['merchantId'], $pay_data['merchantName'], $pay_data['channelId'], $pay_data['channelName']); + + return $pay_data; + } + + /** + * 获取患者信息 + * @param string $patient_id + * @param string $open_id + * @return mixed + * @throws GeneralException + */ + protected function getPatientInfo(string $patient_id, string $open_id): mixed + { + $info = $this->patient_model->getBindPatientInfo($open_id, $patient_id); + if (empty($info)) { + throw new GeneralException('找不到患者信息,请重新再试!', Response::HTTP_BAD_REQUEST); + } + + $patient_info = $this->his_client->getPatientInfo($info['patient_id'], CardType::OUTPATIENT_NO, $info['name']); + if (!isset($patient_info['RESULTCODE']) || $patient_info['RESULTCODE'] !== '0') { + throw new GeneralException($patient_info['ERRORMSG'] ?? '找不到患者信息,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + // 添加Patient 表ID + $patient_info['id'] = $info['id']; + + $this->info('挂号患者信息', $info); + return $info; + } + + /** + * 获取挂号信息 + * @param string $date + * @param string $dept_id + * @param string $doctor_id + * @param string $reg_id + * @return array + * @throws GeneralException + */ + protected function getRegisterScheduleDetails(string $date, string $dept_id, string $doctor_id, string $reg_id): array + { + // 获取排班医生信息 + $is_today = $dept_id === date('Y-m-d') ? '3' : '1'; + $schedule_info = $this->his_client->getDoctorLists($dept_id, $is_today, '', $date); + if (!isset($schedule_info['RESULTCODE']) || $schedule_info['RESULTCODE'] !== '0') { + throw new GeneralException($schedule_info['ERRORMSG'] ?? '找不到该号源,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + // 获取号源信息 + $schedule_info = xmlArrayToListByKey($schedule_info, 'ITEM'); + foreach ($schedule_info['ITEM'] as $v) { + if ($v['DOCTID'] === $doctor_id) { + $v = xmlArrayToListByKey($v, 'SHIFT'); + foreach ($v['SHIFT'] as $v2) { + if ($v2['REGID'] === $reg_id && $v['FDATE'] === $date) { + $v['SHIFT'] = $v2; + $info = $v; + } + } + } + } + + if (empty($info)) { + throw new GeneralException('找不到该号源,请重新再试!', Response::HTTP_BAD_REQUEST); + } + + if (!isset($info['SHIFT']['REGCOUNT']) || $info['SHIFT']['REGCOUNT'] <= 0) { + throw new GeneralException('该号源已挂完,请重新选择号源!', Response::HTTP_BAD_REQUEST); + } + + // 获取科室名称 + $dept_lists = $this->his_client->getDepType('', '','01', $date); + if (!isset($schedule_info['RESULTCODE']) || $schedule_info['RESULTCODE'] !== '0') { + throw new GeneralException($schedule_info['ERRORMSG'] ?? '找不到该号源,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + $dept_lists = xmlArrayToListByKey($dept_lists, 'ITEM'); + foreach ($dept_lists['ITEM'] as $v) { + if ($v['DEPID'] === $dept_id) { + $info['dept_id'] = $v['DEPID']; + $info['dept_name'] = $v['DEPNAME']; + } + } + + $this->info('挂号排班信息', $info); + return $info; + } + + /** + * 创建订单表 + * @param string $order_id + * @param PayType $pay_type + * @param float $reg_fee + * @param Type $order_type + * @param array $patient_info + * @param array $schedule_info + * @return mixed + * @throws GeneralException + */ + protected function createOrder(string $order_id, PayType $pay_type, float $reg_fee, Type $order_type, array $patient_info, array $schedule_info): mixed + { + // 挂号记录表 + $reg_record_data = [ + 'relate_patient_id' => $patient_info['id'], + 'reg_id' => $schedule_info, + 'dept_id' => $schedule_info['dept_id'], + 'dept_name' => $schedule_info['dept_name'], + 'dept_location' => $schedule_info['DEPLOCATION'], + 'doctor_id' => $schedule_info['DOCTID'], + 'doctor_name' => $schedule_info['DOCTNAME'], + 'visit_date' => $schedule_info['SHIFT']['FDATE'], + 'begin_time' => $schedule_info['SHIFT']['STARTTIME'], + 'end_time' => $schedule_info['SHIFT']['ENDTIME'], + 'lock_status' => 0, + 'extra_info' => json_encode($schedule_info, JSON_UNESCAPED_UNICODE), + ]; + + $order = $this->order_model->createOrder( + $order_id, + $pay_type, + $reg_fee * 100, + $this->open_id, + $patient_info['PATIENTID'], + $patient_info['NAME'], + $order_type, + SourceId::MINI_PROGRAM, + $reg_record_data + ); + if (empty($order)) { + throw new GeneralException('创建挂号单失败,请重新再试!'); + } + + $this->info('创建订单,ID:'. $order_id->id); + return $order; + } + + /** + * 申请支付 + * @param Type $order_type + * @param string $order_id + * @param float $reg_fee + * @param string $patient_id + * @param string $patient_name + * @return array|string + * @throws GeneralException + */ + protected function applyPayment(Type $order_type, string $order_id, float $reg_fee, string $patient_id, string $patient_name): array|string + { + try { + $order_obj = new CreateOrder( + $order_type->label(), + $order_id, + (string) $reg_fee, + '1', + $patient_id. '|'. $patient_name, + 'A', + $this->open_id, + url('/Api/Notify', [], true) + ); + + $response = Unify::pay(env('unify'))->mini->jsapi($order_obj); + $this->info('jsapi 支付参数', $response); + + if (!$response['success'] || empty($response['response'])) { + throw new GeneralException('申请支付失败,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + return $response['response']; + } catch (InvalidConfigException|RuntimeException|ReflectionException $e) { + throw new GeneralException('申请支付失败,请重新再试!', Response::HTTP_INTERNAL_SERVER_ERROR); + } + } +} diff --git a/app/Http/Logics/Registration/ScheduleLogic.php b/app/Http/Logics/Registration/ScheduleLogic.php index f3e5025..1181ff3 100644 --- a/app/Http/Logics/Registration/ScheduleLogic.php +++ b/app/Http/Logics/Registration/ScheduleLogic.php @@ -4,7 +4,7 @@ declare(strict_types = 1); namespace App\Http\Logics\Registration; use App\Exceptions\GeneralException; -use App\Services\HisSoap\Client; +use App\Services\HisHttp\Client; use App\Utils\Traits\Logger; use Symfony\Component\HttpFoundation\Response; @@ -12,14 +12,14 @@ class ScheduleLogic { use Logger; - private Client $his_soap; + private Client $his_client; /** * PatientLogic Construct */ public function __construct() { - $this->his_soap = app('HisSoapService'); + $this->his_client = app('HisHttpService'); } /** @@ -30,7 +30,7 @@ class ScheduleLogic */ public function getDeptLists(string $date): array { - $response = $this->his_soap->getDepLists('', '','01', $date); + $response = $this->his_client->getDepType('', '','01', $date); if (!isset($response['RESULTCODE']) || $response['RESULTCODE'] !== '0') { throw new GeneralException($response['ERRORMSG'] ?? '暂无科室排班!', Response::HTTP_SERVICE_UNAVAILABLE); @@ -50,7 +50,7 @@ class ScheduleLogic { $type = $date === date('Y-m-d') ? '3' : '1'; - $response = $this->his_soap->getDoctorLists($dept_id, $type, '', $date); + $response = $this->his_client->getDoctorLists($dept_id, $type, '', $date); if (!isset($response['RESULTCODE']) || $response['RESULTCODE'] !== '0') { throw new GeneralException($response['ERRORMSG'] ?? '该科室暂无医生排班!', Response::HTTP_SERVICE_UNAVAILABLE); } diff --git a/app/Http/Requests/Registration/RegisterRequest.php b/app/Http/Requests/Registration/RegisterRequest.php new file mode 100644 index 0000000..72122ce --- /dev/null +++ b/app/Http/Requests/Registration/RegisterRequest.php @@ -0,0 +1,66 @@ + + */ + /** + * 规则 + * @return array + */ + public function rules(): array + { + return [ + 'date' => 'required|date_format:Y-m-d', + 'dept_id' => 'required', + 'doctor_id' => 'required', + 'reg_id' => 'required' + ]; + } + + /** + * 错误提示语句 + * @return array + */ + public function messages(): array + { + return [ + 'date.required' => '必须选择挂号日期', + 'date.date_format' => '必须选择挂号日期', + 'dept_id.required' => '必须选择挂号科室', + 'doctor_id.required' => '必须选择挂号医生', + 'reg_id.required' => '必须选择挂号时间段', + ]; + } + + /** + * 字段名称 + * @return array + */ + public function attributes(): array + { + return [ + 'date' => '挂号日期', + 'dept_id' => '挂号科室', + 'doctor_id' => '挂号医生', + 'reg_id' => '挂号时间段', + ]; + } +} diff --git a/app/Http/Resources/Outpatient/Pending/PendingDetailsResource.php b/app/Http/Resources/Outpatient/Pending/PendingDetailsResource.php new file mode 100644 index 0000000..1246701 --- /dev/null +++ b/app/Http/Resources/Outpatient/Pending/PendingDetailsResource.php @@ -0,0 +1,58 @@ + + */ + public function toArray(Request $request = null): array + { + $lists = []; + $this->resource = xmlArrayToListByKey($this->resource, 'ITEM'); + + foreach ($this->resource['ITEM'] as $v) { + $lists[] = [ + 'fee_date' => $v['FYRQ'], + 'item_id' => $v['XMXH'], + 'item_code' => $v['XMBH'], + 'item_name' => $v['XMMC'], + 'price' => (float) $v['JG'], + 'quantity' => $v['MCYL'], + 'total_price' => $v['JE'], + 'package_name' => $v['ZTMC'], + 'self_pay_ratio' => $v['ZFBL'], + 'self_pay_fee' => (float) $v['ZFJE'], + 'prescription_id' => $v['CFID'], + 'unit' => $v['UNIT'], + 'prescription_type' => $v['CFTYPE'], + 'dept_id' => $v['BQDM'], + 'dept_name' => $v['BQMC'], + 'doctor_id' => $v['YSGH'], + 'doctor_name' => $v['YSMC'], + // 冗余字段 + 'national_catalog_code' => $v['GJMLBM'] ?: '', + 'single_dose' => $v['YCJL'] ?: '', + 'frequency' => $v['YPYF'] ?: '', + 'medication_days' => $v['YYTS'] ?: '', + 'administration_route' => $v['GYTJ'] ?: '', + 'hospital_approval_flag' => $v['HOSP_APPR_FLAG'] ?: '', + 'chinese_medicine_usage' => $v['TCMDRUG_USED_WAY'] ?: '', + 'external_inspection_flag' => $v['ETIP_FLAG'] ?: '', + 'external_inspection_hospital_code' => $v['ETIP_HOSP_CODE'] ?: '', + 'discharge_medication_flag' => $v['DSCG_TKDRUG_FLAG'] ?: '', + 'maternity_fee_flag' => $v['MATN_FEE_FLAG'] ?: '', + 'external_purchase_prescription_flag' => $v['RX_CIRC_FLAG'] ?: '', + ]; + } + + return $lists; + } +} diff --git a/app/Http/Resources/Outpatient/Pending/PendingListsResource.php b/app/Http/Resources/Outpatient/Pending/PendingListsResource.php new file mode 100644 index 0000000..d43c772 --- /dev/null +++ b/app/Http/Resources/Outpatient/Pending/PendingListsResource.php @@ -0,0 +1,50 @@ + + */ + public function toArray(Request $request = null): array + { + $this->resource = xmlArrayToListByKey($this->resource, 'ITEM'); + + $lists = []; + foreach ($this->resource['ITEM'] as $v) { + $lists[] = [ + 'category' => $v['JZLB'], + 'visit_date' => $v['JZRQ'], + 'diagnosis' => $v['CYZD'], + 'dept_id' => $v['BQDM'], + 'dept_name' => $v['BQMC'], + 'reg_id' => $v['REGID'], + 'prescription_id' => $v['CFID'], + 'total_prescription_amount' => (float) $v['YLFYZE'], + 'single_prescription_amount' => (float) $v['CFFYJE'] ?? 0, + 'doctor_id' => $v['YSGH'], + 'doctor_name' => $v['YSMC'], + 'remark' => $v['BZ'], + 'is_self_pay' => $v['ZFCF'], + 'visit_no' => $v['JZXH'], + 'prescription_number' => $v['CHHM'] ?: '', + 'prescription_type' => $v['CFTYPE'] ?: '', + 'exec_address' => $v['YFMC'] ?: '', + 'medical_type' => $v['MED_TYPE'] ?: '', + 'disease_code' => $v['DISE_CODG'] ?: '', + 'disease_name' => $v['DISE_NAME'] ?: '', + 'settlement_method' => $v['PSN_SETLWAY'] ?: '', + 'out_of_area_flag' => $v['OUT_FLAG'] ?: '' + ]; + } + + return $lists; + } +} diff --git a/app/Models/Order.php b/app/Models/Order.php index 9b99ea9..fde8825 100644 --- a/app/Models/Order.php +++ b/app/Models/Order.php @@ -276,7 +276,6 @@ class Order extends Model $order->save(); if (in_array($order->type, [Type::TODAY_REGISTRATION->value, Type::APPOINTMENT_REGISTRATION->value, Type::OUTPATIENT_PAYMENT->value]) && !empty($response)) { - switch ($order->type) { case Type::TODAY_REGISTRATION->value: case Type::APPOINTMENT_REGISTRATION->value: @@ -284,7 +283,7 @@ class Order extends Model $extra_info = json_decode($record->extra_info, true); $extra_info['confirm_response'] = $response; - $record->update(['reg_id' => $response['AdmNo'] ?? '', 'extra_info' => json_encode($extra_info, JSON_UNESCAPED_UNICODE)]); + $record->update(['extra_info' => json_encode($extra_info, JSON_UNESCAPED_UNICODE)]); break; case Type::OUTPATIENT_PAYMENT->value: $record = $order->outpatientPaymentREcord; diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 8de5fcb..988056e 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,7 +2,8 @@ namespace App\Providers; -use App\Services\HisSoap\Client; +use App\Services\HisSoap\Client as HisSoapClient; +use App\Services\HisHttp\Client as HisHttpClient; use Illuminate\Support\Facades\Route; use Illuminate\Routing\UrlGenerator; use Illuminate\Support\Facades\Schema; @@ -54,9 +55,14 @@ class AppServiceProvider extends ServiceProvider protected function registerHisService(): void { - // His平台服务 + // His平台服务 - soap $this->app->singleton('HisSoapService', function () { - return new Client(); + return new HisSoapClient(); + }); + + // His平台服务 - http + $this->app->singleton('HisHttpService', function () { + return new HisHttpClient(); }); } } diff --git a/app/Services/HisHttp/Client.php b/app/Services/HisHttp/Client.php index caafcd3..dfe1d5c 100644 --- a/app/Services/HisHttp/Client.php +++ b/app/Services/HisHttp/Client.php @@ -416,7 +416,7 @@ class Client */ public function getPendingLists(string $patient_id): mixed { - return $this->requestHandle('POST', 'ListVisitRec ', [ + return $this->requestHandle('POST', 'ListVisitRec', [ 'patientID' => $patient_id, 'registerArea' => '', ... $this->commonRequestData() @@ -448,6 +448,55 @@ class Client ]); } + /** + * 确认缴费 + * @param string $patient_id 患者ID + * @param string $settle_type 结算类型 0 自费,1 门诊统筹 + * @param string $settle_date 结算日期 yyyy-mm-dd + * @param string $prescription_ids 处方号,多张处方用,隔开 + * @param string $reg_id 就诊序号 + * @param string $reg_type 就诊类别 + * @param string $order_id 交易流水号 + * @param string $tran_type 支付方式 12 银联 3 医保 88 微信 89 支付宝 + * @param string $total_fee 本次医疗费用 + * @param string $self_fee 个人自付总额 + * @param string $register_area 挂号区域 + * @return mixed + * @throws GeneralException + */ + public function confirmOutpatient( + string $patient_id, + string $settle_type, + string $settle_date, + string $prescription_ids, + string $reg_id, + string $reg_type, + string $order_id, + string $tran_type, + string $total_fee, + string $self_fee, + string $register_area = '01' + ): mixed + { + // 调用请求处理方法 + return $this->requestHandle('POST', 'PayBillTrade', [ + 'json' => [ + 'patientID' => $patient_id, + 'settleType' => $settle_type, + 'jsrq' => $settle_date, + 'cfid' => $prescription_ids, + 'jzxh' => $reg_id, + 'jzlb' => $reg_type, + 'orderId' => $order_id, + 'tranType' => $tran_type, + 'ylfyze' => $total_fee, + 'grzfje' => $self_fee, + 'registerArea' => $register_area, + ... $this->commonRequestData() + ] + ]); + } + /** * 获取门诊费用清单列表 * @param string $patient_id 患者ID,必填 diff --git a/app/Utils/Helpers.php b/app/Utils/Helpers.php index fe815d8..9cbc52d 100644 --- a/app/Utils/Helpers.php +++ b/app/Utils/Helpers.php @@ -1,6 +1,7 @@ $this->mockRegisterCard($request_data), 'GetCardInfo' => $this->mockGetPatientInfo($request_data), 'GetDepType' => $this->mockGetDepLists($request_data), @@ -59,7 +59,7 @@ class ClientMockHttpTransfer extends HttpTransferAbstract 'SendOutpatientinvoiceEBill' => $this->mockSendElectronInvoiceToHis($request_data), 'GetDictionary' => $this->mockGetDictionaryLists($request_data), 'GetChargeList' => $this->mockGetChargeList($request_data), - default => throw new GeneralException("Method '{$method_name}' not found"), + default => throw new GeneralException("Method '{$request_name}' not found"), }; } @@ -72,8 +72,9 @@ class ClientMockHttpTransfer extends HttpTransferAbstract public function responseFormat(mixed $data): mixed { try { - // 此处为json格式 - return json_decode((string)$data, true); + // 此处为xml格式 + $obj = simplexml_load_string((string)$data, 'SimpleXMLElement', LIBXML_NOCDATA); + return json_decode((string)json_encode($obj), true); } catch (Exception $e) { throw new Exception($e->getMessage()); } @@ -177,30 +178,16 @@ class ClientMockHttpTransfer extends HttpTransferAbstract private function mockGetPendingLists(array $params) { - return [ - 'status' => 'success', - 'message' => 'Pending list retrieved successfully.', - 'data' => [ - [ - 'visit_no' => '12345', - 'patient_id' => $params['PATIENTID'], - 'amount' => '100.00' - ] - ] - ]; + $this->transfer_response = '00441827197303226022门诊202107171.急性胃肠炎2.湿热证226发热门诊04-555585781.5316.210395麦明杰无特殊备注126719474-55558572普通医疗K52.9急性胃肠炎自费01441827199405226019住院202312151.高血压2.冠心病112心内科13-45678901050.75450.520876李海涛长期病管理037582913-45678901住院医疗I10高血压自费1'; + + return $this; } private function mockGetPendingDetails(array $params) { - return [ - 'status' => 'success', - 'message' => 'Pending details retrieved successfully.', - 'data' => [ - 'cf_ids' => $params['CFID'], - 'jz_xh' => $params['JZXH'], - 'details' => 'Detailed description of the treatment.' - ] - ]; + $this->transfer_response = '02021-07-14130727842220尿妊娠试验(金标法)5.6715.6715.674-5549038妊娠检查检验单8妇科门诊10537邝国超123451次口服1天口服给药000002021-07-14130727852231血常规检查12.34112.340.22.474-5549040常规检查检验单8妇科门诊10538张伟678901次静脉注射1天静脉注射000002021-07-15130727863001超声波检查80.00180.000.540.004-5549041影像检查影像单6放射科10539李明78901一次其他1天100002021-07-16130727874002CT检查300.001300.000.390.004-5549042影像检查影像单7影像科10540王磊345671次其他1天100002021-07-17130727885003心电图检查25.00125.000.12.504-5549043心电检查检查单9心内科10541赵云456781次其他1天10000'; + + return $this; } /** diff --git a/app/Utils/Transfer/HisSoapClient/ClientMockSoapTransfer.php b/app/Utils/Transfer/HisSoapClient/ClientMockSoapTransfer.php index 7bc92ee..ea551d4 100644 --- a/app/Utils/Transfer/HisSoapClient/ClientMockSoapTransfer.php +++ b/app/Utils/Transfer/HisSoapClient/ClientMockSoapTransfer.php @@ -143,8 +143,7 @@ class ClientMockSoapTransfer extends SoapTransferAbstract */ private function mockGetDoctorLists(array $params): self { - $this->transfer_response = '0Success1000120001张三内科一诊室主任医师110001300012024-12-271上午班08:0012:0050.00001201510001300022024-12-282下午班14:0018:0055.00002181210001300032024-12-293夜班20:0000:0060.00003108XM0011234567890挂号诊查费50105001000220002李四外科二诊室副主任医师010002300042024-12-271上午班08:0012:0060.00004252010002300052024-12-282下午班14:0018:0065.000052018XM0020987654321挂号诊查费100151500 -'; + $this->transfer_response = '0Success1000120001张三内科一诊室主任医师110001300012024-12-271上午班08:0012:0050.00001201510001300022024-12-282下午班14:0018:0055.00002181210001300032024-12-293夜班20:0000:0060.00003108XM0011234567890挂号诊查费50105001000220002李四外科二诊室副主任医师010002300042024-12-271上午班08:0012:0060.00004252010002300052024-12-282下午班14:0018:0065.000052018XM0020987654321挂号诊查费100151500'; return $this; } diff --git a/app/Utils/Transfer/HttpTransferAbstract.php b/app/Utils/Transfer/HttpTransferAbstract.php index 244aed7..733ca1c 100644 --- a/app/Utils/Transfer/HttpTransferAbstract.php +++ b/app/Utils/Transfer/HttpTransferAbstract.php @@ -26,8 +26,8 @@ abstract class HttpTransferAbstract // 调用接口参数 public mixed $transfer_parameter; - // 调用返回结果 - public ResponseInterface $transfer_response; + // 调用返回结果 string格式用于mock数据 + public ResponseInterface|string $transfer_response; // 运行时间 public array $request_time; diff --git a/config/logging.php b/config/logging.php index 53b5a21..0095816 100644 --- a/config/logging.php +++ b/config/logging.php @@ -165,6 +165,24 @@ return [ 'max_files' => 360, ], + // 挂号日志 + 'registration' => [ + 'driver' => 'custom', + 'via' => GeneralDailyLogger::class, + 'service_type' => 'RegisterLog', + 'level' => Level::Info, + 'max_files' => 30, + ], + + // 挂号日志 + 'outpatient' => [ + 'driver' => 'custom', + 'via' => GeneralDailyLogger::class, + 'service_type' => 'OutpatientLog', + 'level' => Level::Info, + 'max_files' => 30, + ], + // 退费相关日志 'refund' => [ 'driver' => 'custom', @@ -173,6 +191,15 @@ return [ 'level' => Level::Info, 'max_files' => 0, ], + + // 通知相关日志 + 'notify' => [ + 'driver' => 'custom', + 'via' => GeneralDailyLogger::class, + 'service_type' => 'NotifyLog', + 'level' => Level::Info, + 'max_files' => 0, + ], ], ]; diff --git a/database/migrations/2023_03_03_080715_create_registration_records_table.php b/database/migrations/2023_03_03_080715_create_registration_records_table.php index 68d4023..a5965cb 100644 --- a/database/migrations/2023_03_03_080715_create_registration_records_table.php +++ b/database/migrations/2023_03_03_080715_create_registration_records_table.php @@ -24,7 +24,7 @@ return new class () extends Migration { $table->string('visit_date', 10)->index()->comment('就诊日期'); $table->string('begin_time', 5)->index()->comment('开始时间'); $table->string('end_time', 5)->comment('结束时间'); - $table->tinyInteger('lock_status')->index()->comment('锁号状态 1 已锁号 2 已解锁'); + $table->tinyInteger('lock_status')->index()->comment('锁号状态 0 系统不支持锁号 1 已锁号 2 已解锁'); $table->timestamp('lock_at')->nullable()->comment('锁号时间'); $table->timestamp('unlock_at')->nullable()->comment('解锁时间'); $table->text('extra_info')->comment('额外的挂号信息'); diff --git a/database/migrations/2023_03_03_080952_create_outpatient_payment_records_table.php b/database/migrations/2023_03_03_080952_create_outpatient_payment_records_table.php index ba8afae..da9e8bd 100644 --- a/database/migrations/2023_03_03_080952_create_outpatient_payment_records_table.php +++ b/database/migrations/2023_03_03_080952_create_outpatient_payment_records_table.php @@ -21,7 +21,7 @@ return new class () extends Migration { $table->string('doctor_name', 20)->comment('医生名称'); $table->string('visit_date', 10)->index()->comment('就诊日期'); $table->integer('total_amount')->comment('处方总金额'); - $table->tinyInteger('pre_settle_status')->index()->comment('预结算状态 1 已预结算 2 已取消预结算'); + $table->tinyInteger('pre_settle_status')->index()->comment('预结算状态 0 无需预结算 1 已预结算 2 已取消预结算'); $table->timestamp('pre_settle_at')->nullable()->comment('预结算时间'); $table->timestamp('cancel_pre_settle_at')->nullable()->comment('取消预结算时间'); $table->text('extra_info')->comment('额外的门诊信息'); diff --git a/packagist/unify_payment/src/Cores/Exceptions/RuntimeException.php b/packagist/unify_payment/src/Cores/Exceptions/RuntimeException.php index 40c7053..cc04d7a 100644 --- a/packagist/unify_payment/src/Cores/Exceptions/RuntimeException.php +++ b/packagist/unify_payment/src/Cores/Exceptions/RuntimeException.php @@ -4,8 +4,6 @@ declare(strict_types=1); namespace UnifyPayment\Cores\Exceptions; -use Exception; - class RuntimeException extends Exception { /** diff --git a/routes/api.php b/routes/api.php index 3738cb0..4282575 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,9 +1,13 @@ group(function() { Route::post('login', [AuthController::class, 'login']); Route::any('unauthorized', [AuthController::class, 'unauthorized'])->name('login'); + // 支付回调 + Route::any('notify', [NotifyController::class, 'notify']); + Route::middleware(['auth:sanctum'])->group(function() { // 患者模块 Route::prefix('Patient')->group(function () { @@ -28,6 +35,7 @@ Route::middleware(['apiLog'])->group(function() { Route::prefix('Registration')->group(function () { Route::get('/dept', [ScheduleController::class, 'dept']); Route::get('/doctor', [ScheduleController::class, 'doctor']); + Route::post('/{patient_id}/register', [RegisterController::class, 'register']); Route::get('/{patient_id}/record', [RegistrationRecordController::class, 'lists']); Route::post('/{patient_id}/record/{serial_no}/refund', [RegistrationRecordController::class, 'refund']); @@ -35,6 +43,9 @@ Route::middleware(['apiLog'])->group(function() { // 缴费模块 Route::prefix('Outpatient')->group(function () { + Route::get('/{patient_id}/pending', [PendingController::class, 'lists']); + Route::get('/{patient_id}/pending/{serial_no}/', [PendingController::class, 'details']); + Route::post('/{patient_id}/pending/{serial_no}/payment', [PaymentController::class, 'payment']); Route::get('/{patient_id}/record', [OutpatientRecordController::class, 'lists']); Route::get('/{patient_id}/record/{serial_no}/', [OutpatientRecordController::class, 'details']);