From a1ed3ea2dc780c971a63d27442485bb175d3fd26 Mon Sep 17 00:00:00 2001 From: Rmiku <46063139+Rmiku@users.noreply.github.com> Date: Thu, 26 Jun 2025 11:38:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8C=82=E5=8F=B7=EF=BC=8C=E7=BC=B4?= =?UTF-8?q?=E8=B4=B9=E8=B4=B9=E7=94=A8=E9=A2=84=E7=BB=93=E7=AE=97=EF=BC=8C?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E5=8D=95=E6=9F=A5=E8=AF=A2=EF=BC=8C=E5=AE=9A?= =?UTF-8?q?=E7=82=B9=20fix:=20=E4=BF=AE=E5=A4=8D=E6=B8=AF=E6=BE=B3?= =?UTF-8?q?=E5=8F=B0=E7=A4=BE=E4=BF=9D=E5=8D=A1=E5=8F=B7=E5=88=A4=E6=96=AD?= =?UTF-8?q?=EF=BC=8C=E9=A2=84=E7=BA=A6=E6=8C=82=E5=8F=B7=E6=8F=90=E9=86=92?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Commands/SendAppointmentReminders.php | 4 +- app/Dictionary/Medical/OrderType.php | 54 + app/Dictionary/Medical/PatientProperty.php | 28 + app/Dictionary/Medical/PayType.php | 30 + app/Dictionary/Medical/PreSettleStatus.php | 30 + app/Dictionary/Medical/SettleType.php | 27 + app/Dictionary/Report/ImageType.php | 35 + .../Outpatient/MedicalController.php | 110 ++ .../Controllers/Patient/PatientController.php | 14 + .../Registration/MedicalController.php | 77 ++ .../Registration/RegisterController.php | 2 +- .../Controllers/Report/InspectController.php | 26 +- app/Http/Controllers/Test/TestController.php | 55 +- app/Http/Logics/Notify/NotifyLogic.php | 81 +- app/Http/Logics/Outpatient/MedicalLogic.php | 497 ++++++++ app/Http/Logics/Outpatient/PaymentLogic.php | 9 +- app/Http/Logics/Outpatient/PendingLogic.php | 9 +- app/Http/Logics/Outpatient/RecordLogic.php | 7 +- app/Http/Logics/Patient/PatientLogic.php | 32 + app/Http/Logics/Registration/MedicalLogic.php | 504 ++++++++ app/Http/Logics/Report/InspectLogic.php | 29 +- .../Requests/Patient/BindPatientRequest.php | 1 + .../Registration/RegisterPreSettleRequest.php | 77 ++ .../Medical/PrescriptionPreSettleResource.php | 37 + .../Medical/RegisterPreSettleResource.php | 37 + app/Jobs/SendWeChatMessageJob.php | 6 + app/Models/Order.php | 2 +- app/Providers/AppServiceProvider.php | 6 + app/Services/HealthRecordAuth/Client.php | 3 + app/Services/HisHttp/Client.php | 23 +- app/Services/HisMedicalHttp/Client.php | 293 +++++ app/Services/MedicalAuth/Client.php | 62 +- app/Utils/Helpers.php | 3 +- app/Utils/Statics/BuildCacheKeyName.php | 101 ++ app/Utils/Traits/BuildCacheKeyName.php | 52 - app/Utils/Traits/SendSubscribeMessage.php | 2 +- app/Utils/Traits/UniversalEncryption.php | 1 + .../HisMedicalHttpClient/ClientFactory.php | 25 + .../ClientHttpTransfer.php | 62 + .../ClientMockHttpTransfer.php | 128 ++ config/custom.php | 4 +- config/hisservice.php | 19 +- config/logging.php | 13 +- config/unify.php | 10 +- config/unifytest.php | 113 ++ config/wechat.php | 24 +- .../unify_payment/src/Cores/BasicClient.php | 2 + .../Struct/CreateMedicalInsuranceOrder.php | 1074 +++++++++++++++++ .../CreateMedicalInsuranceOrderHandler.php | 49 + .../src/Modules/Pay/MiniProgramClient.php | 21 +- .../src/Modules/Pay/OfficialAccountClient.php | 22 +- packagist/unify_payment/tests/PayTest.php | 42 + routes/api.php | 21 +- 53 files changed, 3848 insertions(+), 147 deletions(-) create mode 100644 app/Dictionary/Medical/OrderType.php create mode 100644 app/Dictionary/Medical/PatientProperty.php create mode 100644 app/Dictionary/Medical/PayType.php create mode 100644 app/Dictionary/Medical/PreSettleStatus.php create mode 100644 app/Dictionary/Medical/SettleType.php create mode 100644 app/Dictionary/Report/ImageType.php create mode 100644 app/Http/Controllers/Outpatient/MedicalController.php create mode 100644 app/Http/Controllers/Registration/MedicalController.php create mode 100644 app/Http/Logics/Outpatient/MedicalLogic.php create mode 100644 app/Http/Logics/Registration/MedicalLogic.php create mode 100644 app/Http/Requests/Registration/RegisterPreSettleRequest.php create mode 100644 app/Http/Resources/Outpatient/Medical/PrescriptionPreSettleResource.php create mode 100644 app/Http/Resources/Registration/Medical/RegisterPreSettleResource.php create mode 100644 app/Services/HisMedicalHttp/Client.php create mode 100644 app/Utils/Statics/BuildCacheKeyName.php delete mode 100644 app/Utils/Traits/BuildCacheKeyName.php create mode 100644 app/Utils/Transfer/HisMedicalHttpClient/ClientFactory.php create mode 100644 app/Utils/Transfer/HisMedicalHttpClient/ClientHttpTransfer.php create mode 100644 app/Utils/Transfer/HisMedicalHttpClient/ClientMockHttpTransfer.php create mode 100644 config/unifytest.php create mode 100644 packagist/unify_payment/src/Cores/Struct/CreateMedicalInsuranceOrder.php create mode 100644 packagist/unify_payment/src/Mock/CreateMedicalInsuranceOrderHandler.php diff --git a/app/Console/Commands/SendAppointmentReminders.php b/app/Console/Commands/SendAppointmentReminders.php index 3e5397b..9258df1 100644 --- a/app/Console/Commands/SendAppointmentReminders.php +++ b/app/Console/Commands/SendAppointmentReminders.php @@ -41,8 +41,8 @@ class SendAppointmentReminders extends Command $this->info('Starting to send appointment reminders...'); // 查询即将到期的预约记录(8小时后的预约) - $appointments = RegistrationRecord::where('visit_date', now()->toDate()) - ->where('begin_time', now()->subHours(8)) + $appointments = RegistrationRecord::where('visit_date', now()->toDateString()) + ->where('begin_time', '>=', now()->addHours(8)->toTimeString()) ->where('reminder_sent', 0) ->get(); diff --git a/app/Dictionary/Medical/OrderType.php b/app/Dictionary/Medical/OrderType.php new file mode 100644 index 0000000..d660666 --- /dev/null +++ b/app/Dictionary/Medical/OrderType.php @@ -0,0 +1,54 @@ + '挂号支付', + self::MED_PAY => '药费支付', + self::DIAG_PAY => '诊间支付', + self::IN_HOSP_PAY => '住院费支付', + self::PHARMACY_PAY => '药店支付', + self::INSURANCE_PAY => '保险费支付', + self::INT_REG_PAY => '互联网医院挂号支付', + self::INT_RE_DIAG_PAY => '互联网医院复诊支付', + self::INT_PSC_PAY => '互联网医院处方支付', + self::COVID_EXAM_PAY => '新冠检测费用', + self::COVID_ANTIGEN_PAY => '新冠抗原检测', + }; + } +} diff --git a/app/Dictionary/Medical/PatientProperty.php b/app/Dictionary/Medical/PatientProperty.php new file mode 100644 index 0000000..ac3a58a --- /dev/null +++ b/app/Dictionary/Medical/PatientProperty.php @@ -0,0 +1,28 @@ + '门诊统筹', + self::OUTPATIENT_MUTUAL_AID => '门诊共济', + }; + } +} diff --git a/app/Dictionary/Medical/PayType.php b/app/Dictionary/Medical/PayType.php new file mode 100644 index 0000000..09247d7 --- /dev/null +++ b/app/Dictionary/Medical/PayType.php @@ -0,0 +1,30 @@ + '自费', + self::OFFLINE_MEDICARE => '线下医保', + self::ONLINE_MEDICARE => '线上医保', + }; + } +} diff --git a/app/Dictionary/Medical/PreSettleStatus.php b/app/Dictionary/Medical/PreSettleStatus.php new file mode 100644 index 0000000..2bead0d --- /dev/null +++ b/app/Dictionary/Medical/PreSettleStatus.php @@ -0,0 +1,30 @@ + '无需预结算', + self::SETTLED => '已预结算', + self::CANCELLED => '已取消预结算', + }; + } +} diff --git a/app/Dictionary/Medical/SettleType.php b/app/Dictionary/Medical/SettleType.php new file mode 100644 index 0000000..c5a58ed --- /dev/null +++ b/app/Dictionary/Medical/SettleType.php @@ -0,0 +1,27 @@ + '使用现金支付', + self::MEDICARE_ACCOUNT => '使用医保个账支付', + }; + } +} diff --git a/app/Dictionary/Report/ImageType.php b/app/Dictionary/Report/ImageType.php new file mode 100644 index 0000000..a454ddd --- /dev/null +++ b/app/Dictionary/Report/ImageType.php @@ -0,0 +1,35 @@ + '查询胶片ID', + self::IMAGE => '获取胶片图像', + self::IMAGE_BASE64 => '获取胶片Base64编码', + self::PDF => '获取报告PDF文件', + self::PDF_BASE64 => '获取报告PDF的Base64编码', + }; + } +} diff --git a/app/Http/Controllers/Outpatient/MedicalController.php b/app/Http/Controllers/Outpatient/MedicalController.php new file mode 100644 index 0000000..b7aa132 --- /dev/null +++ b/app/Http/Controllers/Outpatient/MedicalController.php @@ -0,0 +1,110 @@ +medical_logic = new MedicalLogic(); + } + + /** + * 跳转免密授权页面 + * @param Request $request + * @return JsonResponse + */ + public function redirectAuth(Request $request): JsonResponse + { + $redirect_url = $this->medical_logic->getMiniProgramAuthRedirectUrl(); + + return jsonResponse(Response::HTTP_OK, 'success', [ + // 固定跳转医保小程序APP ID + 'app_id' => config('custom.redirect_app_id'), + 'url' => $redirect_url + ]); + } + + /** + * 查询患者授权信息 + * @param Request $request + * @param string $patient_id + * @return JsonResponse + * @throws GeneralException + */ + public function userAuthInfo(Request $request, string $patient_id): JsonResponse + { + $validated = $request->validate([ + 'auth_code.require' => 'required', + ], [ + 'auth_code.required' => '查询数据参数错误', + ]); + + $user_info = $this->medical_logic->queryPatientMedicalInsuranceAuthInfo($patient_id, $validated['auth_code']); + + return jsonResponse(Response::HTTP_OK, 'success', $user_info); + } + + /** + * 处方预结算 + * @param Request $request + * @param string $patient_id + * @param string $serial_no + * @return JsonResponse + * @throws GeneralException + */ + public function prescriptionPreSettle(Request $request, string $patient_id, string $serial_no): JsonResponse + { + $validated = $request->validate([ + 'settle_type' => ['required', new Enum(SettleType::class)], + ], [ + 'settle_type.required' => '请选择结算类型', + 'settle_type.Illuminate\Validation\Rules\Enum' => '请选择正确的结算类型', + ]); + + $settle_type = SettleType::from((int) $validated['settle_type']); + + $response = $this->medical_logic->pendingPrescriptionPreSettle($patient_id, $serial_no, $settle_type); + + return jsonResponse(Response::HTTP_OK, 'success', PrescriptionPreSettleResource::make($response)->toArray()); + } + + /** + * 医保支付 + * @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([ + 'settle_type' => ['required', new Enum(SettleType::class)], + ], [ + 'settle_type.required' => '请选择结算类型', + 'settle_type.Illuminate\Validation\Rules\Enum' => '请选择正确的结算类型', + ]); + + $settle_type = SettleType::from((int) $validated['settle_type']); + + $response = $this->medical_logic->medicalPayment($patient_id, $serial_no, $settle_type); + + return jsonResponse(Response::HTTP_OK, 'success', $response); + } +} diff --git a/app/Http/Controllers/Patient/PatientController.php b/app/Http/Controllers/Patient/PatientController.php index ef665a8..c3372f4 100644 --- a/app/Http/Controllers/Patient/PatientController.php +++ b/app/Http/Controllers/Patient/PatientController.php @@ -122,4 +122,18 @@ class PatientController return jsonResponse(Response::HTTP_OK, 'success', ['phone_info' => $phone_info]); } + + /** + * 医保定点签约 + * @param Request $request + * @param string $patient_id + * @return JsonResponse + * @throws GeneralException + */ + public function designated(Request $request, string $patient_id): JsonResponse + { + $this->patient_logic->medicalDesignated($patient_id); + + return jsonResponse(Response::HTTP_OK, '签约成功!'); + } } diff --git a/app/Http/Controllers/Registration/MedicalController.php b/app/Http/Controllers/Registration/MedicalController.php new file mode 100644 index 0000000..0273b17 --- /dev/null +++ b/app/Http/Controllers/Registration/MedicalController.php @@ -0,0 +1,77 @@ +medical_logic = new MedicalLogic(); + } + + /** + * 挂号预结算 + * @param RegisterPreSettleRequest $request + * @param string $patient_id + * @return JsonResponse + * @throws GeneralException + */ + public function registerPreSettle(RegisterPreSettleRequest $request, string $patient_id): JsonResponse + { + $patient_property = PatientProperty::from((int) $request->patient_property); + $settle_type = SettleType::from((int) $request->settle_type); + + $response = $this->medical_logic->registerPreSettle( + $patient_id, + $request->date, + $request->dept_id, + $request->doctor_id, + $request->reg_id, + $patient_property, + $settle_type + ); + + return jsonResponse(Response::HTTP_OK, 'success', RegisterPreSettleResource::make($response)->toArray()); + } + + /** + * 医保支付 + * @param Request $request + * @param string $patient_id + * @return JsonResponse + * @throws GeneralException + */ + public function register(Request $request, string $patient_id): JsonResponse + { + $patient_property = PatientProperty::from((int) $request->patient_property); + $settle_type = SettleType::from((int) $request->settle_type); + + $response = $this->medical_logic->medicalRegister( + $patient_id, + $request->date, + $request->dept_id, + $request->doctor_id, + $request->reg_id, + $patient_property, + $settle_type + ); + + return jsonResponse(Response::HTTP_OK, 'success', $response); + } +} diff --git a/app/Http/Controllers/Registration/RegisterController.php b/app/Http/Controllers/Registration/RegisterController.php index 35547b8..4587945 100644 --- a/app/Http/Controllers/Registration/RegisterController.php +++ b/app/Http/Controllers/Registration/RegisterController.php @@ -24,7 +24,7 @@ class RegisterController } /** - * 获取挂号记录列表 + * 挂号 * @param RegisterRequest $request * @param string $patient_id 此处为 patient_number * @return JsonResponse diff --git a/app/Http/Controllers/Report/InspectController.php b/app/Http/Controllers/Report/InspectController.php index 7d6c71c..57f92c7 100644 --- a/app/Http/Controllers/Report/InspectController.php +++ b/app/Http/Controllers/Report/InspectController.php @@ -4,12 +4,13 @@ declare(strict_types = 1); namespace App\Http\Controllers\Report; use App\Exceptions\GeneralException; -use App\Http\Logics\Registration\RecordLogic; use App\Http\Logics\Report\InspectLogic; use App\Http\Resources\Report\Inspect\DetailsResource; use App\Http\Resources\Report\Inspect\ListsResource; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Redis; use Symfony\Component\HttpFoundation\Response; class InspectController @@ -24,6 +25,29 @@ class InspectController $this->inspect_logic = new InspectLogic(); } + /** + * 获取报告提示 + * @return JsonResponse + */ + public function tips() + { + if (Redis::exists('report.tips')) { + $tips_lists = Redis::get('report.tips'); + $tips_lists = json_decode($tips_lists, true); + } else { + $tips_lists = DB::connection('mysql_admin')->table('department_tips') + ->select(DB::raw('ABS(dept_id) as type_id'), 'title', 'message') + ->whereIn('dept_id', [-2, -3]) + ->where('is_enable', 1) + ->get() + ->toArray(); + + Redis::setex('report.tips', 4 * 3600, json_encode($tips_lists, JSON_UNESCAPED_UNICODE)); + } + + return jsonResponse(Response::HTTP_OK, 'success', $tips_lists); + } + /** * 获取检查报告列表 * @param Request $request diff --git a/app/Http/Controllers/Test/TestController.php b/app/Http/Controllers/Test/TestController.php index c2122dd..7018ab0 100644 --- a/app/Http/Controllers/Test/TestController.php +++ b/app/Http/Controllers/Test/TestController.php @@ -5,24 +5,67 @@ namespace App\Http\Controllers\Test; use App\Dictionary\WeChat\MiniProgram\OpenApi; use App\Models\Order; +use App\Models\RegistrationRecord; use App\Utils\Traits\HttpRequest; use App\Utils\Traits\SendSubscribeMessage; +use App\Utils\Traits\UniversalEncryption; use Illuminate\Support\Facades\Redis; use UnifyPayment\Cores\Struct\RefundOrder; use UnifyPayment\Unify; +use function Symfony\Component\Translation\t; class TestController { + use HttpRequest; + use UniversalEncryption; use SendSubscribeMessage; public function test(): void { - $mini = getWeChatMiniProgramApp(); - $response = $mini->getClient()->postJson(OpenApi::CREATE_QR_CODE->value, [ - 'path' => 'pagesA/register/notice?ed=show', - ]); - dd($response->toArray()); - //dd(json_decode('', true)); +// $json = [ +// 'ApplicationId' => '', +// 'ApplicationSecret' => '', +// 'Parameter' => [ +// 'BeginDate' => '2025-04-27', +// 'EndDate' => '2025-04-28', +// 'CardType' => '01', +// 'CardNo' => '65284656', +// ] +// ]; + + $json = [ + "ApplicationId" => "", + "ApplicationSecret"=> "", + "Parameter"=> [ + "HospitalId" => "", + "PatientId"=> "2226817", + "PatientName"=> "李欣茹", + "PayAuthNo"=> "", + "UldLatlnt"=> "", + "IsInsSelfPay"=> "0", + "YbPayType"=> "2", + "FeeRecords"=> [ + [ + "FeeNo" => "-210360897", + "FeeTypeCode" => "51" + ] + ] + ] + ]; + + $start_time = microtime(true); +// $result = $this->request('POST', 'http://192.168.61.44:8010/api/his/GetPatientInfo', [ +// 'json' => $json +// ]); + $end_time = microtime(true); + $use_time = sprintf("%.6f", ($start_time - $end_time)); + recordLog('MedicalHis', json_encode([ + 'json' => $json, + 'result' => $result, + 'use_time' => $use_time + ], JSON_UNESCAPED_UNICODE)); +// dd($result); + // $response = '1'; // $order = Order::where('order_id', 'WXM20250207151636790')->first(); // $record = $order->outpatientPaymentRecord; diff --git a/app/Http/Logics/Notify/NotifyLogic.php b/app/Http/Logics/Notify/NotifyLogic.php index 03ac8bc..c7e5285 100644 --- a/app/Http/Logics/Notify/NotifyLogic.php +++ b/app/Http/Logics/Notify/NotifyLogic.php @@ -6,6 +6,7 @@ namespace App\Http\Logics\Notify; use App\Dictionary\Order\NotifyStatus; use App\Dictionary\Order\PayType; +use App\Dictionary\Medical\PayType as MedicalPayType; use App\Dictionary\Order\SourceId; use App\Dictionary\Order\Status; use App\Dictionary\Order\Type; @@ -27,8 +28,6 @@ use UnifyPayment\Cores\Struct\ConfirmOrderForEx; use UnifyPayment\Cores\Struct\QueryOrder; use UnifyPayment\Cores\Struct\RefundOrder; use UnifyPayment\Mock\ConfirmOrderForExHandler; -use UnifyPayment\Mock\QueryOrderHandler; -use UnifyPayment\Mock\RefundOrderHandler; use UnifyPayment\Unify; class NotifyLogic @@ -39,6 +38,8 @@ class NotifyLogic protected Client $his_client; + protected MedicalClient $his_medical_client; + protected PaymentApplication $payment_app; protected PatientModel $patient_model; @@ -104,7 +105,12 @@ class NotifyLogic $this->registrationOrderHandle($order_info, $notify); break; case Type::OUTPATIENT_PAYMENT->value: - $this->outpatientOrderHandle($order_info, $notify); + if ($order_info->pay_type === PayType::MEDICAL_INSURANCE_PAY) { + // 医保支付 + $this->outpatientMedicalOrderHandle($order_info, $notify); + } else { + $this->outpatientOrderHandle($order_info, $notify); + } break; default: break; @@ -238,6 +244,75 @@ class NotifyLogic } } + /** + * 门诊缴费医保订单操作 + * @param OrderModel $order_info + * @param Message $notify + * @return void + * @throws GeneralException + */ + public function outpatientMedicalOrderHandle(OrderModel $order_info, Message $notify): void + { + // 缴费确认 + $record = $order_info->outpatientPaymentRecord; + $extra_info = json_decode($record->extra_info, true); + + $time_end = date('Y-m-d H:i:s', strtotime($notify['time_end'])); + $totalAmount = $extra_info['pre_settle_info']['TotalAmount']; // 总金额 + $medicare_amount = bcdiv($notify['insurance_fund_fee'], '100'); // 减免金额 + $pay_amount = bcdiv($notify['cash_fee'], '100'); // 现金金额 + $account_amount = bcdiv($notify['insurance_self_fee'], '100'); // 个账支付金额 + + $fee_record = []; + foreach ($extra_info['']['feeRecords']['feeRecord'] as $v) { + $fee_record[] = [ + 'FeeNo' => $v['feeNo'], + 'FeeTypeCode' => $v['feeTypeCode'], + ]; + } + + $data = [ + $order_info->patient_id, + $order_info->patient_name, + $order_info->order_id, + $extra_info['pre_settle_info']['PrecalId'], + $time_end, + $totalAmount, + $medicare_amount, + $pay_amount, + $account_amount, + MedicalPayType::ONLINE_MEDICARE, + '03', + $fee_record + ]; + $response = $this->his_client->confirmMedicalOutpatient(... $data); + $this->info('医保缴费订单出入参:'.$order_info->order_id, [$data, $response]); + + // 保存返回信息 + if (isset($response['success']) && $response['success'] === true) { + // 成功流程 + $order_info->orderConfirm($order_info->order_id, $response['response']['ResultId'], $response['response']); + + // 支付平台业务确认 + $this->unifyConfirm($notify['out_trade_no'], $response['response']['ResultId'], $notify['openid'], $notify['transaction_id']); + + // 推送成功 + $this->sendOutpatientPaymentSuccessMessage($order_info); + } else if (isset($response['success']) && $response['success'] === false && $response['msg'] !== '服务异常') { + // 失败流程 + $this->handleOrderReverse($order_info, $response['msg']); + + // 推送失败 + $this->sendOutpatientPaymentFailureMessage($order_info); + } else { + // 异常流程 + $order_info->abnormalOrderOpera($order_info->id); + + // 推送异常 + $this->sendOutpatientPaymentFailureMessage($order_info); + } + } + /** * 退款 * @param string $order_id diff --git a/app/Http/Logics/Outpatient/MedicalLogic.php b/app/Http/Logics/Outpatient/MedicalLogic.php new file mode 100644 index 0000000..9bb39aa --- /dev/null +++ b/app/Http/Logics/Outpatient/MedicalLogic.php @@ -0,0 +1,497 @@ +authInitialize(); + $this->setChannel('outpatient'); + $this->his_client = app('HisHttpService'); + $this->his_medical_client = app('HisMedicalHttpClient'); + $this->auth_client = new AuthClient(); + $this->order_model = new Order(); + $this->patient_model = new Patient(); + } + + /** + * 查询患者医保授权信息 + * @param string $patient_number + * @param string $auth_code 授权码 + * @return array + * @throws GeneralException + */ + public function queryPatientMedicalInsuranceAuthInfo(string $patient_number, string $auth_code): array + { + $patient_info = $this->getPatientInfo($this->open_id, $patient_number); + $patient_id = &$patient_info['patientId']; + + // 获取用户pay_auth_no和经纬度 + // $info = $this->auth_client->userQuery($auth_code, $this->open_id); + $info = $this->auth_client->userQuery($auth_code, 'ozOD16zPgRmjdh_O3VvtFtKaiLP0'); + if ($info['code'] !== 0) { + throw new GeneralException($info['message'] ?? '获取用户医保信息异常,请稍后重新再试!', Response::HTTP_BAD_REQUEST); + } + + $user_info = [ + 'pay_auth_no' => $info['pay_auth_no'], + 'user_longitude_latitude' => $info['user_longitude_latitude'] + ]; + + // 有效期为12小时 + $cache_key = BuildCacheKeyName::getUserMedicalInsuranceAuthKey($this->open_id, $patient_id); + Redis::setex($cache_key, 12 * 60 * 60 - 500, json_encode($user_info, JSON_UNESCAPED_UNICODE)); + return $user_info; + } + + /** + * 待缴费处方预结算 + * @param string $patient_number + * @param string $serial_no + * @param SettleType $settle_type + * @return mixed + * @throws GeneralException + */ + public function pendingPrescriptionPreSettle(string $patient_number, string $serial_no, SettleType $settle_type): mixed + { + // 患者信息 + $patient_info = $this->getPatientInfo($this->open_id, $patient_number); + $patient_id = &$patient_info['patientId']; + + // 医保待缴费信息 + $medical_pending_lists = $this->getMedicalPendingPaymentDetails($patient_number, $serial_no); + + // 获取缓存里的结算信息 + $settle_cache_key = BuildCacheKeyName::getOutpatientPrescriptionPreSettleInfoKey($this->open_id, $serial_no, $settle_type->value); + if (Redis::exists($settle_cache_key)) { + $pre_settle_info = Redis::get($settle_cache_key); + $pre_settle_info = json_decode($pre_settle_info, true); + + $this->info('缴费预结算返回数据包(cache)', $pre_settle_info); + return $pre_settle_info; + } + + // 获取患者授权信息 + $auth_info = $this->getCacheUserMedicalInsuranceAuthInfo($patient_id); + // 用户经纬度 + $user_lng_lat = $auth_info['user_longitude_latitude']['longitude']. ','. $auth_info['user_longitude_latitude']['latitude']; + + $fee_record = []; + foreach ($medical_pending_lists['FeeRecords'] as $v) { + $fee_record[] = [ + 'FeeNo' => $v['FeeNo'], + 'FeeTypeCode' => $v['FeeTypeCode'], + ]; + } + $this->info('缴费预结算费用处方集合', $fee_record); + + // 预结算 + $pre_settle_info = $this->prescriptionPreSettle($patient_number, $patient_info['name'], $auth_info['pay_auth_no'], $user_lng_lat, $settle_type, $fee_record); + + // 预结算结果 + $pre_settle_info = reset($pre_settle_info); + $pre_settle_info['PreSettleAt'] = date('Y-m-d H:i:s');// 预结算时间 + $this->info('缴费预结算返回数据包', $pre_settle_info); + + // 缓存2小时 + Redis::setex($settle_cache_key, 7200, json_encode($pre_settle_info, JSON_UNESCAPED_UNICODE)); + + return $pre_settle_info; + } + + /** + * 医保支付 + * @param string $patient_number + * @param string $serial_no + * @param SettleType $settle_type + * @param OrderPayType $pay_type + * @return mixed + * @throws GeneralException + */ + public function medicalPayment(string $patient_number, string $serial_no, SettleType $settle_type, OrderPayType $pay_type = OrderPayType::MEDICAL_INSURANCE_PAY): mixed + { + // 患者数据 + $patient_info = $this->getPatientInfo($this->open_id, $patient_number); + // 医保待缴费单数据 + $medical_pending_info = $this->getMedicalPendingPaymentDetails($patient_number, $serial_no); + // 用户授权数据 + $auth_info = $this->getCacheUserMedicalInsuranceAuthInfo($patient_info['patientId']); + // 预结算数据 + $pre_settle_info = $this->getPrescriptionPreSettleInfo($serial_no, $settle_type); + + // 创建订单数据 + $order_type = Type::OUTPATIENT_PAYMENT; + $order_id = $this->order_model->getOrderId($pay_type, 'M'); + $total_fee = (float)(string) $pre_settle_info['TotalAmount']; + $reduce_fee = (float)(string) $pre_settle_info['MedicareAmount']; + $self_fee = (float)(string) ($settle_type === SettleType::MEDICARE_ACCOUNT ? $pre_settle_info['AccountAmount'] : $pre_settle_info['PayAmount']); + + // 创建订单 + $order = $this->createMedicalOrder($order_id, $pay_type, $total_fee, $reduce_fee, $order_type, $patient_info, $medical_pending_info, $pre_settle_info); + + // 申请医保支付 + $pay_data = $this->applyMedicalPayment($order_type, $order_id, $total_fee, $reduce_fee, $self_fee, $patient_info, $auth_info, $pre_settle_info); + + // 去除支付无用数据 + unset($pay_data['merchantId'], $pay_data['merchantName'], $pay_data['channelId'], $pay_data['channelName']); + + return $pay_data; + } + + /** + * 获取患者信息 + * @param string $open_id + * @param string $patient_number + * @return mixed + * @throws GeneralException + */ + protected function getPatientInfo(string $open_id, string $patient_number): mixed + { + $info = $this->patient_model->getBindPatientInfoByPatientNumber($open_id, $patient_number); + if (empty($info)) { + throw new GeneralException('找不到患者信息,请重新再试!', Response::HTTP_BAD_REQUEST); + } + + $response = $this->his_client->getPatientInfo($info['patient_number'], CardType::OUTPATIENT_NO, ''); + if (!isset($response['success']) || !$response['success']) { + throw new GeneralException($response['msg'] ?? '找不到患者信息,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + // 添加Patient 表ID + $patient_info = &$response['response']; + $patient_info['id'] = $info['id']; + + $this->info('获取缴费患者信息', $patient_info); + return $patient_info; + } + + /** + * 获取待缴费医保处方信息 + * @param string $patient_number + * @param string $record_id + * @return array|mixed + * @throws GeneralException + */ + protected function getMedicalPendingPaymentDetails(string $patient_number, string $record_id): mixed + { + $cache_key = BuildCacheKeyName::getOutpatientMedicalPendingDetailsKey($this->open_id, $patient_number, $record_id); + + // 先获取缓存里的医保处方数据 + if (Redis::exists($cache_key)) { + $info = Redis::get($cache_key); + $info = json_decode($info, true); + + $this->info('获取医保待缴费处方信息(cache)', $info); + return $info; + } + + $start_date = date('Y-m-d', strtotime('-7 days')); + $end_date = date('Y-m-d'); + $response = $this->his_medical_client->getMedicalPendingLists($patient_number, $start_date, $end_date); + + if(empty($response) || empty($response['Result'])) { + throw new GeneralException('暂无相关缴费记录!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + if (!isset($response['ResultCode']) || $response['ResultCode'] !== '1') { + throw new GeneralException($response['ErrorMsg'] ?: '暂无相关缴费记录!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + $info = []; + foreach ($response['Result'] as $k => $v) { + if ($v['DiagnosisRecordId'] === $record_id) { + $info = $v; + } + } + + if (empty($info)) { + throw new GeneralException('暂无相关缴费记录!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + $this->info('获取医保待缴费处方信息', $info); + + // 缓存2小时 + Redis::setex($cache_key, 7200, json_encode($info, JSON_UNESCAPED_UNICODE)); + return $info; + } + + /** + * 处方预结算 + * @param string $patient_number + * @param string $patient_name + * @param string $pay_auth_no + * @param string $user_lng_lat + * @param SettleType $settle_type + * @param array $fee_record + * @return mixed + * @throws GeneralException + */ + protected function prescriptionPreSettle(string $patient_number, string $patient_name, string $pay_auth_no, string $user_lng_lat, SettleType $settle_type, array $fee_record): mixed + { + try { + // 缴费预结算 + $response = $this->his_medical_client->outpatientPreSettle( + $patient_number, + $patient_name, + $pay_auth_no, + $user_lng_lat, + $settle_type, + MedicalPayType::ONLINE_MEDICARE, + $fee_record + ); + $this->info('缴费预结算返回结果', $response); + + } catch (\Exception $e) { + $err_msg = $e->getMessage(). ' On '. $e->getFile(). ':'. $e->getLine(); + $this->error('缴费预结算异常, 异常消息:'. $err_msg); + + throw new GeneralException('缴费预结算失败,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + // 返回 false / 没有返回预结算费用信息 + if(empty($response) || empty($response['Result'])) { + throw new GeneralException('缴费预结算失败,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + if (!isset($response['ResultCode']) || $response['ResultCode'] !== '1') { + throw new GeneralException($response['ErrorMsg'] ?: '缴费预结算遇到未知错误,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + return $response['Result']; + } + + /** + * 获取缓存里的用户医保授权信息 + * @param string $patient_id + * @return mixed + * @throws GeneralException + */ + protected function getCacheUserMedicalInsuranceAuthInfo(string $patient_id): mixed + { + $cache_key = BuildCacheKeyName::getUserMedicalInsuranceAuthKey($this->open_id, $patient_id); + + // 先获取缓存里的医保处方数据 + if (!Redis::exists($cache_key)) { + throw new GeneralException('请先进行医保授权!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + $auth_info = Redis::get($cache_key); + $auth_info = json_decode($auth_info, true); + + $this->info('用户医保授权信息数据包(cache)', $auth_info); + return $auth_info; + } + + /** + * 获取待缴费处方预结算信息 + * @param string $serial_no + * @param SettleType $settle_type + * @return mixed + * @throws GeneralException + */ + protected function getPrescriptionPreSettleInfo(string $serial_no, SettleType $settle_type): mixed + { + $settle_cache_key = BuildCacheKeyName::getOutpatientPrescriptionPreSettleInfoKey($this->open_id, $serial_no, $settle_type->value); + + if (!Redis::exists($settle_cache_key)) { + throw new GeneralException('缴费预结算信息已过期,请重新再试!'); + } + + $pre_settle_info = Redis::get($settle_cache_key); + $pre_settle_info = json_decode($pre_settle_info, true); + + $this->info('缴费预结算返回数据包(cache)', $pre_settle_info); + return $pre_settle_info; + } + + + /** + * 创建医保缴费订单表 + * @param string $order_id + * @param OrderPayType $pay_type + * @param float $total_fee + * @param float $reduce_fee + * @param Type $order_type + * @param array $patient_info + * @param array $medical_pending_info + * @param array $pre_settle_info + * @return mixed + * @throws GeneralException + */ + protected function createMedicalOrder(string $order_id, OrderPayType $pay_type, float $total_fee, float $reduce_fee, Type $order_type, array $patient_info, array $medical_pending_info, array $pre_settle_info): mixed + { + // 缴费记录表 + $pay_record_data = [ + 'relate_patient_id' => $patient_info['id'], + 'dept_id' => $medical_pending_info['DeptId'], + 'dept_name' => $medical_pending_info['DeptName'], + 'doctor_id' => $medical_pending_info['DoctorId'], + 'doctor_name' => $medical_pending_info['DoctorName'], + 'visit_date' => date('Y-m-d', strtotime($medical_pending_info['DiagnosisDate'])), + 'total_amount' => $total_fee, + 'pre_settle_status' => PreSettleStatus::SETTLED->value, + 'pre_settle_at' => $pre_settle_info['PreSettleAt'], + 'extra_info' => json_encode([ + ... $medical_pending_info, + 'pre_settle_info' => $pre_settle_info, + ], JSON_UNESCAPED_UNICODE) + ]; + + $order = $this->order_model->createOrder( + $patient_info['id'], + $order_id, + $pay_type, + $total_fee * 100, + $reduce_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); + return $order; + } + + /** + * 申请医保支付 + * @param Type $order_type 订单类型 + * @param string $order_id 订单号 + * @param float $total_fee 总金额 + * @param float $reduce_fee 减免金额 + * @param float $self_fee 自费金额 + * @param array $patient_info 患者信息 + * @param array $auth_info 用户授权信息 + * @param array $pre_settle_info 预结算信息 + * @return mixed + * @throws GeneralException + */ + protected function applyMedicalPayment(Type $order_type, string $order_id, float $total_fee, float $reduce_fee, float $self_fee, array $patient_info, array $auth_info, array $pre_settle_info): mixed + { + // 患者信息 + $patient_id = &$patient_info['patient_id']; + $patient_name = &$patient_info['name']; + $id_card_no = &$patient_info['idCardNo']; + // $open_id = &$this->open_id; + $open_id = 'ozOD16zPgRmjdh_O3VvtFtKaiLP0'; + + // 医保相关参数判断 + // 医保部分扣费类型 0:统筹+个账 1:个账 2:统筹 + $consume_type = $reduce_fee > 0 ? 2 : 0; + // 支付方式 1:现金 2:医保 3:现金+医保; cash_fee>0, paytype填3; cash_fee=0, paytype填2 + $pay_type = $reduce_fee <= 0 ? 2 : 3; + + // 证件号码类型 + $card_type = getIDCardType($id_card_no); + switch ($card_type) { + default: + case 1: + $user_card_type = 1; + break; + case 2: + $user_card_type = match (true) { + preg_match('/^HKG\d{9}$/', $id_card_no) => 6, + preg_match('/^MAC\d{9}$/', $id_card_no) => 4, + preg_match('/^TWN\d{9}$/', $id_card_no) => 5, + }; + break; + case 3: + case 4: + $user_card_type = 7; + break; + } + + try { + $order_obj = new CreateMedicalInsuranceOrder( + orderType: $order_type->unifyOrderType(), + attach: $patient_id. '|'. $patient_name, + insuranceOrderType: OrderType::DIAG_PAY->value, + orderNo: $order_id, + hospitalName: '珠海市香洲区第二人民医院', + totalAmount: (string) $total_fee, + cashAmount: (string) $self_fee, + allowFeeChange: 0, + spbillCreateIp: request()->ip(), + openid: $open_id, + notifyUrl: config('custom.payment_notify_url'), + title: $order_type->label(), + payType: (string) $pay_type, + cityCode: config('wechat.medical.auth.city_code'), + consumeType: $consume_type, + insuranceAmount: (string) $reduce_fee, + userCardType: $user_card_type, + idCardNo: md5(strtoupper($id_card_no)), + name: $patient_name, + serialNo: $pre_settle_info['MedOrgOrd'], + orgNo: config('wechat.medical.auth.org_codg'), + requestContent: [ + 'payAuthNo' => $auth_info['pay_auth_no'], + 'payOrdId' => $pre_settle_info['PayOrdId'], + 'setlLatlnt' => $auth_info['user_longitude_latitude']['latitude']. ','. $auth_info['user_longitude_latitude']['longitude'], + ], + channelNo: config('wechat.medical.auth.channel') + ); + + $response = Unify::pay(config('unifyTest'))->mini->medicalPay($order_obj); + $this->info('medical Pay 支付参数', $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/PaymentLogic.php b/app/Http/Logics/Outpatient/PaymentLogic.php index 2318b49..58a134d 100644 --- a/app/Http/Logics/Outpatient/PaymentLogic.php +++ b/app/Http/Logics/Outpatient/PaymentLogic.php @@ -8,11 +8,10 @@ use App\Dictionary\Order\SourceId; use App\Dictionary\Order\Type; use App\Dictionary\Patient\CardType; use App\Exceptions\GeneralException; -use App\Http\Resources\Outpatient\Pending\PendingListsResource; use App\Models\Order; use App\Models\Patient; use App\Services\HisHttp\Client; -use App\Utils\Traits\BuildCacheKeyName; +use App\Utils\Statics\BuildCacheKeyName; use App\Utils\Traits\Logger; use App\Utils\Traits\MiniProgramAuth; use Illuminate\Auth\AuthenticationException; @@ -22,14 +21,12 @@ use Symfony\Component\HttpFoundation\Response; use UnifyPayment\Cores\Exceptions\InvalidConfigException; use UnifyPayment\Cores\Exceptions\RuntimeException; use UnifyPayment\Cores\Struct\CreateOrder; -use UnifyPayment\Mock\CreateOrderHandler; use UnifyPayment\Unify; class PaymentLogic { use Logger; use MiniProgramAuth; - use BuildCacheKeyName; private Client $his_client; @@ -119,8 +116,8 @@ class PaymentLogic protected function getPendingPrescriptionDetails(string $patient_id, string $serial_no, string $prescription_ids): array { // 缓存键值 - $pending_lists_cache_key = $this->getOutpatientPendingListsKey($this->open_id, $patient_id); - $pending_details_cache_key = $this->getOutpatientPendingDetailsKey($this->open_id, $patient_id, $serial_no); + $pending_lists_cache_key = BuildCacheKeyName::getOutpatientPendingListsKey($this->open_id, $patient_id); + $pending_details_cache_key = BuildCacheKeyName::getOutpatientPendingDetailsKey($this->open_id, $patient_id, $serial_no); // 获取缓存 if (Redis::exists($pending_lists_cache_key)) { diff --git a/app/Http/Logics/Outpatient/PendingLogic.php b/app/Http/Logics/Outpatient/PendingLogic.php index d38ed05..d3591cc 100644 --- a/app/Http/Logics/Outpatient/PendingLogic.php +++ b/app/Http/Logics/Outpatient/PendingLogic.php @@ -6,7 +6,7 @@ namespace App\Http\Logics\Outpatient; use App\Exceptions\GeneralException; use App\Models\Patient; use App\Services\HisHttp\Client; -use App\Utils\Traits\BuildCacheKeyName; +use App\Utils\Statics\BuildCacheKeyName; use App\Utils\Traits\Logger; use App\Utils\Traits\MiniProgramAuth; use Illuminate\Auth\AuthenticationException; @@ -18,7 +18,6 @@ class PendingLogic { use Logger; use MiniProgramAuth; - use BuildCacheKeyName; private Client $his_client; @@ -58,7 +57,7 @@ class PendingLogic } // 缓存2小时 - Redis::setnx($this->getOutpatientPendingListsKey($this->open_id, $patient_id), 7200, json_encode($response, JSON_UNESCAPED_UNICODE)); + Redis::setex(BuildCacheKeyName::getOutpatientPendingListsKey($this->open_id, $patient_id), 7200, json_encode($response, JSON_UNESCAPED_UNICODE)); return $response; } @@ -85,7 +84,7 @@ class PendingLogic } // 缓存2小时 - Redis::setnx($this->getOutpatientPendingDetailsKey($this->open_id, $patient_id, $serial_no), 7200, json_encode($response, JSON_UNESCAPED_UNICODE)); + Redis::setex(BuildCacheKeyName::getOutpatientPendingDetailsKey($this->open_id, $patient_id, $serial_no), 7200, json_encode($response, JSON_UNESCAPED_UNICODE)); return $response; } @@ -99,7 +98,7 @@ class PendingLogic protected function getCachePendingLists(string $patient_id, string $serial_no): mixed { // 缓存键值 - $cache_key = $this->getOutpatientPendingListsKey($this->open_id, $patient_id); + $cache_key = BuildCacheKeyName::getOutpatientPendingListsKey($this->open_id, $patient_id); // 获取缓存 if (Redis::exists($cache_key)) { diff --git a/app/Http/Logics/Outpatient/RecordLogic.php b/app/Http/Logics/Outpatient/RecordLogic.php index af8b28c..4cf2eb0 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\Patient; use App\Services\HisHttp\Client; -use App\Utils\Traits\BuildCacheKeyName; +use App\Utils\Statics\BuildCacheKeyName; use App\Utils\Traits\Logger; use App\Utils\Traits\MiniProgramAuth; use Illuminate\Auth\AuthenticationException; @@ -17,7 +17,6 @@ class RecordLogic { use Logger; use MiniProgramAuth; - use BuildCacheKeyName; private Patient $patient_model; @@ -66,7 +65,7 @@ class RecordLogic } // 缓存2小时 - $cache_key = $this->getOutpatientPendingListsKey($this->open_id, $patient_id); + $cache_key = BuildCacheKeyName::getOutpatientPendingListsKey($this->open_id, $patient_id); Redis::setex($cache_key, 2 * 60 * 60, json_encode($response, JSON_UNESCAPED_UNICODE)); return $response; } @@ -103,7 +102,7 @@ class RecordLogic */ protected function getCacheRecordInfo(string $patient_id, string $serial_no): mixed { - $cache_key = $this->getOutpatientPendingDetailsKey($this->open_id, $patient_id, $serial_no); + $cache_key = BuildCacheKeyName::getOutpatientPendingDetailsKey($this->open_id, $patient_id, $serial_no); $record_info = Redis::get($cache_key); if (empty($record_info)) { diff --git a/app/Http/Logics/Patient/PatientLogic.php b/app/Http/Logics/Patient/PatientLogic.php index 0634f80..bd61296 100644 --- a/app/Http/Logics/Patient/PatientLogic.php +++ b/app/Http/Logics/Patient/PatientLogic.php @@ -9,6 +9,7 @@ use App\Dictionary\WeChat\MiniProgram\OpenApi; use App\Exceptions\GeneralException; use App\Models\Patient; use App\Services\HisHttp\Client; +use App\Services\HisMedicalHttp\Client as MedicalClient; use App\Utils\Traits\Logger; use App\Utils\Traits\MiniProgramAuth; use App\Utils\Traits\SendSubscribeMessage; @@ -27,6 +28,8 @@ class PatientLogic private Client $his_client; + private MedicalClient $his_medical_client; + private Patient $patient_model; /** @@ -38,6 +41,7 @@ class PatientLogic $this->authInitialize(); $this->patient_model = new Patient(); $this->his_client = app('HisHttpService'); + $this->his_medical_client = app('HisMedicalHttpClient'); } /** @@ -299,4 +303,32 @@ class PatientLogic throw new GeneralException('获取手机号码失败,请稍后再试!', Response::HTTP_BAD_REQUEST); } } + + /** + * 医保定点签约 + * @param string $patient_number + * @return bool + * @throws GeneralException + */ + public function medicalDesignated(string $patient_number): bool + { + $info = $this->patient_model->getBindPatientInfoByPatientNumber($this->open_id, $patient_number); + if (empty($info)) { + throw new GeneralException('该就诊卡不存在!'); + } + + // 获取患者信息 + $response = $this->his_client->getPatientInfo($info['patient_number'], CardType::OUTPATIENT_NO, ''); + if (!isset($response['success']) || !$response['success']) { + throw new GeneralException($response['msg'] ?? '找不到该就诊卡!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + $patient_info = &$response['response']; + $result = $this->his_medical_client->medicalDesignated('02', $patient_info['iDCardNo']); + if (!isset($result['ResultCode']) || $result['ResultCode'] !== '1') { + throw new GeneralException($result['ErrorMsg'] ?? '医保定点签约失败,请稍后再试!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + return true; + } } diff --git a/app/Http/Logics/Registration/MedicalLogic.php b/app/Http/Logics/Registration/MedicalLogic.php new file mode 100644 index 0000000..a79c36f --- /dev/null +++ b/app/Http/Logics/Registration/MedicalLogic.php @@ -0,0 +1,504 @@ +authInitialize(); + $this->setChannel('registration'); + $this->his_client = app('HisHttpService'); + $this->his_medical_client = app('HisMedicalHttpClient'); + $this->auth_client = new AuthClient(); + $this->order_model = new Order(); + $this->patient_model = new Patient(); + } + + /** + * 挂号预结算 + * @param string $patient_number + * @param string $date + * @param string $dept_id + * @param string $doctor_id + * @param string $reg_id + * @param PatientProperty $patient_property + * @param SettleType $settle_type + * @return mixed + * @throws GeneralException + */ + public function registerPreSettle(string $patient_number, string $date, string $dept_id, string $doctor_id, string $reg_id, PatientProperty $patient_property, SettleType $settle_type): mixed + { + // 患者信息 + $patient_info = $this->getPatientInfo($this->open_id, $patient_number); + $patient_id = &$patient_info['patientId']; + + // 获取挂号号源信息 + $schedule_info = $this->getRegisterScheduleDetails($date, $dept_id, $doctor_id, $reg_id); + + // 获取缓存里的结算信息 + $settle_cache_key = BuildCacheKeyName::getRegistrationPreSettleInfoKey($this->open_id, $reg_id, $patient_property->value, $settle_type->value); + if (Redis::exists($settle_cache_key)) { + $pre_settle_info = Redis::get($settle_cache_key); + $pre_settle_info = json_decode($pre_settle_info, true); + + $this->info('挂号预结算返回数据包(cache)', $pre_settle_info); + return $pre_settle_info; + } + + // 获取患者授权信息 + $auth_info = $this->getCacheUserMedicalInsuranceAuthInfo($patient_id); + // 用户经纬度 + $user_lng_lat = $auth_info['user_longitude_latitude']['longitude']. ','. $auth_info['user_longitude_latitude']['latitude']; + + // 预结算 + $pre_settle_info = $this->registrationPreSettle( + $patient_number, + $patient_info['name'], + $schedule_info['scheduleInfo']['feeRecord'], + $schedule_info['scheduleInfo']['feeRecord'], + $reg_id, + $patient_property, + $auth_info['pay_auth_no'], + $user_lng_lat + ); + + // 预结算结果 + $pre_settle_info = reset($pre_settle_info); + $pre_settle_info['PreSettleAt'] = date('Y-m-d H:i:s');// 预结算时间 + $this->info('挂号预结算返回数据包', $pre_settle_info); + + // 缓存2小时 + Redis::setex($settle_cache_key, 7200, json_encode($pre_settle_info, JSON_UNESCAPED_UNICODE)); + + return $pre_settle_info; + } + + /** + * 医保支付 + * @param string $patient_number + * @param string $date + * @param string $dept_id + * @param string $doctor_id + * @param string $reg_id + * @param PatientProperty $patient_property + * @param SettleType $settle_type + * @param OrderPayType $pay_type + * @return mixed + * @throws GeneralException + */ + public function medicalRegister(string $patient_number, string $date, string $dept_id, string $doctor_id, string $reg_id, PatientProperty $patient_property, SettleType $settle_type, OrderPayType $pay_type = OrderPayType::MEDICAL_INSURANCE_PAY): mixed + { + // 患者数据 + $patient_info = $this->getPatientInfo($this->open_id, $patient_number); + // 挂号号源信息 + $medical_pending_info = $this->getRegisterScheduleDetails($date, $dept_id, $doctor_id, $reg_id); + // 用户授权数据 + $auth_info = $this->getCacheUserMedicalInsuranceAuthInfo($patient_info['patientId']); + // 预结算数据 + $pre_settle_info = $this->getRegistrationPreSettleInfo($reg_id, $patient_property, $settle_type); + + // 创建订单数据 + $order_type = $date === date('Y-m-d') ? Type::TODAY_REGISTRATION : Type::APPOINTMENT_REGISTRATION; + $order_id = $this->order_model->getOrderId($pay_type, 'M'); + $total_fee = (float)(string) $pre_settle_info['TotalAmount']; + $reduce_fee = (float)(string) $pre_settle_info['MedicareAmount']; + $self_fee = (float)(string) ($settle_type === SettleType::MEDICARE_ACCOUNT ? $pre_settle_info['AccountAmount'] : $pre_settle_info['PayAmount']); + + // 创建订单 + $order = $this->createMedicalOrder($order_id, $pay_type, $total_fee, $reduce_fee, $order_type, $patient_info, $medical_pending_info, $pre_settle_info); + + // 申请医保支付 + $pay_data = $this->applyMedicalPayment($order_type, $order_id, $total_fee, $reduce_fee, $self_fee, $patient_info, $auth_info, $pre_settle_info); + + // 去除支付无用数据 + unset($pay_data['merchantId'], $pay_data['merchantName'], $pay_data['channelId'], $pay_data['channelName']); + + return $pay_data; + } + + /** + * 获取患者信息 + * @param string $open_id + * @param string $patient_number + * @return mixed + * @throws GeneralException + */ + protected function getPatientInfo(string $open_id, string $patient_number): mixed + { + $info = $this->patient_model->getBindPatientInfoByPatientNumber($open_id, $patient_number); + if (empty($info)) { + throw new GeneralException('找不到患者信息,请重新再试!', Response::HTTP_BAD_REQUEST); + } + + $response = $this->his_client->getPatientInfo($info['patient_number'], CardType::OUTPATIENT_NO, ''); + if (!isset($response['success']) || !$response['success']) { + throw new GeneralException($response['msg'] ?? '找不到患者信息,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + // 添加Patient 表ID + $patient_info = &$response['response']; + $patient_info['id'] = $info['id']; + + $this->info('获取缴费患者信息', $patient_info); + return $patient_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['success']) || !$schedule_info['success']) { + throw new GeneralException($schedule_info['msg'] ?? '找不到该号源,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + // 获取号源信息 + foreach ($schedule_info['response'] as $v) { + if ($v['doctId'] === $doctor_id) { + foreach ($v['doctotVisitInfoList'] as $v2) { + if ($v2['regId'] === $reg_id && $v2['visitDate'] === $date) { + $v['scheduleInfo'] = $v2; + unset($v['doctotVisitInfoList']); + $info = $v; + } + } + } + } + + if (empty($info)) { + throw new GeneralException('找不到该号源,请重新再试!', Response::HTTP_BAD_REQUEST); + } + + if (!isset($info['scheduleInfo']['regCount']) || $info['scheduleInfo']['regCount'] <= 0) { + throw new GeneralException('该号源已挂完,请重新选择号源!', Response::HTTP_BAD_REQUEST); + } + + // 获取科室名称 + /*if (Redis::exists('departments.'. $date)) { + $dept_lists = Redis::get('departments.'. $date); + $dept_lists = json_decode($dept_lists, true); + } else { + $dept_lists = $this->his_client->getDepType('', '','01', $date); + + if (!isset($dept_lists['success']) || !$dept_lists['success']) { + throw new GeneralException($schedule_info['msg'] ?? '找不到该号源,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE); + } + } + + foreach ($dept_lists['response'] as $v) { + if ($v['typeId'] === $dept_id) { + $info['dept_id'] = $v['typeId']; + $info['dept_name'] = $v['typeName']; + } + }*/ + + // 获取科室名称 + $department_info = Department::where('dept_id', $dept_id)->where('is_enable', 1)->first(); + if (!$department_info){ + throw new GeneralException($schedule_info['msg'] ?? '找不到该号源,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + $info['dept_id'] = $department_info['dept_id']; + $info['dept_name'] = $department_info['dept_name']; + + $this->info('挂号排班信息', $info); + return $info; + } + + /** + * 挂号预结算 + * @param string $patient_number + * @param string $patient_name + * @param string $total_fee + * @param string $fee_code + * @param string $reg_id + * @param PatientProperty $patient_property + * @param string $pay_auth_no + * @param string $user_lng_lat + * @return mixed + * @throws GeneralException + */ + protected function registrationPreSettle(string $patient_number, string $patient_name, string $total_fee, string $fee_code, string $reg_id, PatientProperty $patient_property, string $pay_auth_no, string $user_lng_lat): mixed + { + try { + // 挂号预结算 + $response = $this->his_medical_client->registrationPreSettle( + $patient_number, + $patient_name, + $total_fee, + $fee_code, + $reg_id, + $patient_property, + $pay_auth_no, + $user_lng_lat, + ); + $this->info('挂号预结算返回结果', $response); + + } catch (\Exception $e) { + $err_msg = $e->getMessage(). ' On '. $e->getFile(). ':'. $e->getLine(); + $this->error('挂号预结算异常, 异常消息:'. $err_msg); + + throw new GeneralException('挂号预结算失败,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + // 返回 false / 没有返回预结算费用信息 + if(empty($response) || empty($response['Result'])) { + throw new GeneralException('挂号预结算失败,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + if (!isset($response['ResultCode']) || $response['ResultCode'] !== '1') { + throw new GeneralException($response['ErrorMsg'] ?: '挂号预结算遇到未知错误,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + return $response['Result']; + } + + /** + * 获取缓存里的用户医保授权信息 + * @param string $patient_id + * @return mixed + * @throws GeneralException + */ + protected function getCacheUserMedicalInsuranceAuthInfo(string $patient_id): mixed + { + $cache_key = BuildCacheKeyName::getUserMedicalInsuranceAuthKey($this->open_id, $patient_id); + + // 先获取缓存里的医保处方数据 + if (!Redis::exists($cache_key)) { + throw new GeneralException('请先进行医保授权!', Response::HTTP_SERVICE_UNAVAILABLE); + } + + $auth_info = Redis::get($cache_key); + $auth_info = json_decode($auth_info, true); + + $this->info('用户医保授权信息数据包(cache)', $auth_info); + return $auth_info; + } + + /** + * 获取挂号预结算信息 + * @param string $reg_id + * @param PatientProperty $patient_property + * @param SettleType $settle_type + * @return mixed + * @throws GeneralException + */ + protected function getRegistrationPreSettleInfo(string $reg_id, PatientProperty $patient_property, SettleType $settle_type): mixed + { + $settle_cache_key = BuildCacheKeyName::getRegistrationPreSettleInfoKey($this->open_id, $reg_id, $patient_property->value, $settle_type->value); + + if (!Redis::exists($settle_cache_key)) { + throw new GeneralException('挂号预结算信息已过期,请重新再试!'); + } + + $pre_settle_info = Redis::get($settle_cache_key); + $pre_settle_info = json_decode($pre_settle_info, true); + + $this->info('挂号预结算返回数据包(cache)', $pre_settle_info); + return $pre_settle_info; + } + + /** + * 创建医保挂号订单表 + * @param string $order_id + * @param OrderPayType $pay_type + * @param float $total_fee + * @param float $reduce_fee + * @param Type $order_type + * @param array $patient_info + * @param array $schedule_info + * @param array $pre_settle_info + * @return mixed + * @throws GeneralException + */ + protected function createMedicalOrder(string $order_id, PayType $pay_type, float $total_fee, float $reduce_fee, Type $order_type, array $patient_info, array $schedule_info, array $pre_settle_info): mixed + { + // 挂号记录表 + $reg_record_data = [ + 'relate_patient_id' => $patient_info['id'], + 'reg_id' => $schedule_info['scheduleInfo']['regId'], + 'dept_id' => $schedule_info['dept_id'], + 'dept_name' => $schedule_info['dept_name'], + 'dept_location' => '', + 'doctor_id' => $schedule_info['doctId'], + 'doctor_name' => $schedule_info['doctName'], + 'visit_date' => date('Y-m-d', strtotime($schedule_info['scheduleInfo']['visitDate'])), + 'begin_time' => $schedule_info['scheduleInfo']['startTime'], + 'end_time' => $schedule_info['scheduleInfo']['endTime'], + 'lock_status' => 0, + 'extra_info' => json_encode([ + ... $schedule_info, + 'pre_settle_info' => $pre_settle_info, + ], JSON_UNESCAPED_UNICODE) + ]; + + $order = $this->order_model->createOrder( + $patient_info['id'], + $order_id, + $pay_type, + $total_fee * 100, + $reduce_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); + return $order; + } + + /** + * 申请医保支付 + * @param Type $order_type 订单类型 + * @param string $order_id 订单号 + * @param float $total_fee 总金额 + * @param float $reduce_fee 减免金额 + * @param float $self_fee 自费金额 + * @param array $patient_info 患者信息 + * @param array $auth_info 用户授权信息 + * @param array $pre_settle_info 预结算信息 + * @return mixed + * @throws GeneralException + */ + protected function applyMedicalPayment(Type $order_type, string $order_id, float $total_fee, float $reduce_fee, float $self_fee, array $patient_info, array $auth_info, array $pre_settle_info): mixed + { + // 患者信息 + $patient_id = &$patient_info['patient_id']; + $patient_name = &$patient_info['name']; + $id_card_no = &$patient_info['idCardNo']; + // $open_id = &$this->open_id; + $open_id = 'ozOD16zPgRmjdh_O3VvtFtKaiLP0'; + + // 医保相关参数判断 + // 医保部分扣费类型 0:统筹+个账 1:个账 2:统筹 + $consume_type = $reduce_fee > 0 ? 2 : 0; + // 支付方式 1:现金 2:医保 3:现金+医保; cash_fee>0, paytype填3; cash_fee=0, paytype填2 + $pay_type = $reduce_fee <= 0 ? 2 : 3; + + // 证件号码类型 + $card_type = getIDCardType($id_card_no); + switch ($card_type) { + default: + case 1: + $user_card_type = 1; + break; + case 2: + $user_card_type = match (true) { + preg_match('/^HKG\d{9}$/', $id_card_no) => 6, + preg_match('/^MAC\d{9}$/', $id_card_no) => 4, + preg_match('/^TWN\d{9}$/', $id_card_no) => 5, + }; + break; + case 3: + case 4: + $user_card_type = 7; + break; + } + + try { + $order_obj = new CreateMedicalInsuranceOrder( + orderType: $order_type->unifyOrderType(), + attach: $patient_id. '|'. $patient_name, + insuranceOrderType: OrderType::DIAG_PAY->value, + orderNo: $order_id, + hospitalName: '珠海市香洲区第二人民医院', + totalAmount: (string) $total_fee, + cashAmount: (string) $self_fee, + allowFeeChange: 0, + spbillCreateIp: request()->ip(), + openid: $open_id, + notifyUrl: config('custom.payment_notify_url'), + title: $order_type->label(), + payType: (string) $pay_type, + cityCode: config('wechat.medical.auth.city_code'), + consumeType: $consume_type, + insuranceAmount: (string) $reduce_fee, + userCardType: $user_card_type, + idCardNo: md5(strtoupper($id_card_no)), + name: $patient_name, + serialNo: $pre_settle_info['MedOrgOrd'], + orgNo: config('wechat.medical.auth.org_codg'), + requestContent: [ + 'payAuthNo' => $auth_info['pay_auth_no'], + 'payOrdId' => $pre_settle_info['PayOrdId'], + 'setlLatlnt' => $auth_info['user_longitude_latitude']['latitude']. ','. $auth_info['user_longitude_latitude']['longitude'], + ], + channelNo: config('wechat.medical.auth.channel') + ); + + $response = Unify::pay(config('unifyTest'))->mini->medicalPay($order_obj); + $this->info('medical Pay 支付参数', $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/Report/InspectLogic.php b/app/Http/Logics/Report/InspectLogic.php index c61ee3f..c4743d0 100644 --- a/app/Http/Logics/Report/InspectLogic.php +++ b/app/Http/Logics/Report/InspectLogic.php @@ -8,12 +8,13 @@ use App\Dictionary\Order\SourceId; use App\Dictionary\Order\Status; use App\Dictionary\Order\Type; use App\Dictionary\Patient\CardType; +use App\Dictionary\Report\ImageType; use App\Exceptions\GeneralException; use App\Models\Order; use App\Models\Patient; use App\Models\RegistrationRecord; use App\Services\HisHttp\Client; -use App\Utils\Traits\BuildCacheKeyName; +use App\Utils\Statics\BuildCacheKeyName; use App\Utils\Traits\Logger; use App\Utils\Traits\MiniProgramAuth; use App\Utils\Traits\SendSubscribeMessage; @@ -29,7 +30,6 @@ class InspectLogic { use Logger; use MiniProgramAuth; - use BuildCacheKeyName; private Client $his_client; @@ -67,7 +67,7 @@ class InspectLogic throw new GeneralException($response['msg'] ?? '找不到该就诊卡!', Response::HTTP_SERVICE_UNAVAILABLE); } - Redis::setex($this->getInspectReportListsKey($this->open_id, $patient_info['patient_id']), 3600, json_encode($response['response'], JSON_UNESCAPED_UNICODE)); + Redis::setex(BuildCacheKeyName::getInspectReportListsKey($this->open_id, $patient_info['patient_id']), 3600, json_encode($response['response'], JSON_UNESCAPED_UNICODE)); return $response['response']; } @@ -82,7 +82,10 @@ class InspectLogic { $patient_info = $this->getPatientInfo($this->open_id, $patient_number); - $report_lists_cache_key = $this->getInspectReportListsKey($this->open_id, $patient_info['patient_id']); + // 缓存键名 + $report_lists_cache_key = BuildCacheKeyName::getInspectReportListsKey($this->open_id, $patient_info['patient_id']); + $report_images_cache_key = BuildCacheKeyName::getInspectReportDetailsImagesKey($this->open_id, $patient_info['patient_id'], $serial_no); + if (!Redis::exists($report_lists_cache_key)) { throw new GeneralException('找不到报告详情,请重新再试!', Response::HTTP_BAD_REQUEST); } @@ -102,6 +105,24 @@ class InspectLogic throw new GeneralException('找不到报告详情,请重新再试!', Response::HTTP_BAD_REQUEST); } + $images = []; + if (!Redis::exists($report_images_cache_key)) { + $image_response = $this->his_client->getInspectReportImages(ImageType::IMAGE,$serial_no); + + if (isset($image_response['success']) && $image_response['success']) { + $images = $image_response['response']['fileBase64']; + } + + // 缓存图片base64进去 + if (!empty($images)) { + Redis::setex($report_images_cache_key, 3600, json_encode($images, JSON_UNESCAPED_UNICODE)); + } + } else { + $images = Redis::get($report_images_cache_key); + $images = json_decode($images, true); + } + + $info['pacs'] = $images; return $info; } diff --git a/app/Http/Requests/Patient/BindPatientRequest.php b/app/Http/Requests/Patient/BindPatientRequest.php index ae0df08..2507f08 100644 --- a/app/Http/Requests/Patient/BindPatientRequest.php +++ b/app/Http/Requests/Patient/BindPatientRequest.php @@ -3,6 +3,7 @@ namespace App\Http\Requests\Patient; use App\Dictionary\Patient\IdentifyCardType; +use Dflydev\DotAccessData\Data; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; diff --git a/app/Http/Requests/Registration/RegisterPreSettleRequest.php b/app/Http/Requests/Registration/RegisterPreSettleRequest.php new file mode 100644 index 0000000..c8347e6 --- /dev/null +++ b/app/Http/Requests/Registration/RegisterPreSettleRequest.php @@ -0,0 +1,77 @@ + + */ + /** + * 规则 + * @return array + */ + public function rules(): array + { + return [ + 'date' => 'required|date_format:Y-m-d||after_or_equal:today', + 'dept_id' => 'required', + 'doctor_id' => 'required', + 'reg_id' => 'required', + 'patient_property' => ['required', new Enum(PatientProperty::class)], + 'settle_type' => ['required', new Enum(SettleType::class)], + ]; + } + + /** + * 错误提示语句 + * @return array + */ + public function messages(): array + { + return [ + 'date.required' => '必须选择挂号日期', + 'date.date_format' => '必须选择挂号日期', + 'date.after_or_equal' => '挂号日期不得小于今天', + 'dept_id.required' => '必须选择挂号科室', + 'doctor_id.required' => '必须选择挂号医生', + 'reg_id.required' => '必须选择挂号时间段', + 'patient_property.required' => '请选择病人性质', + 'patient_property.Illuminate\Validation\Rules\Enum' => '请选择正确的病人性质', + 'settle_type.required' => '请选择结算类型', + 'settle_type.Illuminate\Validation\Rules\Enum' => '请选择正确的结算类型', + ]; + } + + /** + * 字段名称 + * @return array + */ + public function attributes(): array + { + return [ + 'date' => '挂号日期', + 'dept_id' => '挂号科室', + 'doctor_id' => '挂号医生', + 'reg_id' => '挂号时间段', + 'patient_property' => '病人性质', + 'settle_type' => '结算类型', + ]; + } +} diff --git a/app/Http/Resources/Outpatient/Medical/PrescriptionPreSettleResource.php b/app/Http/Resources/Outpatient/Medical/PrescriptionPreSettleResource.php new file mode 100644 index 0000000..3b95c09 --- /dev/null +++ b/app/Http/Resources/Outpatient/Medical/PrescriptionPreSettleResource.php @@ -0,0 +1,37 @@ + + */ + public function toArray(Request $request = null): array + { + if (empty($this->resource)) { + return []; + } + + return [ + // 总金额 + 'total_amt' => (float) $this->resource['TotalAmount'], + // 减免金额 + 'medical_amt' => (float) $this->resource['MedicareAmount'], + // 现金金额 + 'pay_amt' => (float) $this->resource['PayAmount'], + // 个人账户支付金额 + 'account_amt' => (float) $this->resource['AccountAmount'], + // 其他优惠金额 + 'other_amt' => 0, + // 加收费费用描述 + 'extra_fee_desc' => $this->resource['ExtraFeeDesc'] ?? '' + ]; + } +} diff --git a/app/Http/Resources/Registration/Medical/RegisterPreSettleResource.php b/app/Http/Resources/Registration/Medical/RegisterPreSettleResource.php new file mode 100644 index 0000000..98f9ed2 --- /dev/null +++ b/app/Http/Resources/Registration/Medical/RegisterPreSettleResource.php @@ -0,0 +1,37 @@ + + */ + public function toArray(Request $request = null): array + { + if (empty($this->resource)) { + return []; + } + + return [ + // 总金额 + 'total_amt' => (float) $this->resource['TotalAmount'], + // 减免金额 + 'medical_amt' => (float) $this->resource['MedicareAmount'], + // 现金金额 + 'pay_amt' => (float) $this->resource['PayAmount'], + // 个人账户支付金额 + 'account_amt' => (float) $this->resource['AccountAmount'], + // 其他优惠金额 + 'other_amt' => 0, + // 加收费费用描述 + 'extra_fee_desc' => $this->resource['ExtraFeeDesc'] ?? '' + ]; + } +} diff --git a/app/Jobs/SendWeChatMessageJob.php b/app/Jobs/SendWeChatMessageJob.php index 6165471..e8ac794 100644 --- a/app/Jobs/SendWeChatMessageJob.php +++ b/app/Jobs/SendWeChatMessageJob.php @@ -152,6 +152,12 @@ class SendWeChatMessageJob implements ShouldQueue, ShouldBeUnique */ public function failed(string $fail_reason): void { + // 错误信息截断 + if (!empty($fail_reason) && strlen($fail_reason) >= 180) { + $fail_reason = mb_substr($fail_reason, 0, 180); + $fail_reason .= '...'; + } + $this->message->status = MessageStatus::FAILURE->value; $this->message->fail_reason = $fail_reason; $this->message->save(); diff --git a/app/Models/Order.php b/app/Models/Order.php index 22d88e5..2e9595b 100644 --- a/app/Models/Order.php +++ b/app/Models/Order.php @@ -112,7 +112,7 @@ class Order extends Model /** * 获取订单ID * @param PayType $pay_type 支付类型 - * @param string $user W 微信 D 大机器 X 小机器 H his + * @param string $user W 微信 M 小程序 D 大机器 X 小机器 H his * @return string */ public function getOrderId(PayType $pay_type, string $user = 'D'): string diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index c3585e5..cbeb2b2 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -4,6 +4,7 @@ namespace App\Providers; use App\Services\HisSoap\Client as HisSoapClient; use App\Services\HisHttp\Client as HisHttpClient; +use App\Services\HisMedicalHttp\Client as HisMedicalHttpClient; use Illuminate\Support\Facades\Route; use Illuminate\Routing\UrlGenerator; use Illuminate\Support\Facades\Schema; @@ -64,5 +65,10 @@ class AppServiceProvider extends ServiceProvider $this->app->singleton('HisHttpService', function () { return new HisHttpClient(); }); + + // His平台服务 - medical http + $this->app->singleton('HisMedicalHttpClient', function () { + return new HisMedicalHttpClient(); + }); } } diff --git a/app/Services/HealthRecordAuth/Client.php b/app/Services/HealthRecordAuth/Client.php index 4e1802c..7c8e655 100644 --- a/app/Services/HealthRecordAuth/Client.php +++ b/app/Services/HealthRecordAuth/Client.php @@ -7,6 +7,9 @@ use App\Utils\SM4; use App\Utils\Traits\HttpRequest; use Exception; +/** + * 广东省健康档案授权 + */ class Client { use HttpRequest; diff --git a/app/Services/HisHttp/Client.php b/app/Services/HisHttp/Client.php index de9b648..9793bd5 100644 --- a/app/Services/HisHttp/Client.php +++ b/app/Services/HisHttp/Client.php @@ -3,8 +3,11 @@ declare(strict_types=1); namespace App\Services\HisHttp; +use App\Dictionary\Medical\PayType as MedicalPayType; +use App\Dictionary\Medical\SettleType; use App\Dictionary\Patient\CardType; use App\Dictionary\Patient\Sex; +use App\Dictionary\Report\ImageType; use App\Exceptions\GeneralException; use App\Utils\Traits\Logger; use App\Utils\Transfer\HisHttpClient\ClientFactory; @@ -24,7 +27,7 @@ class Client */ public function __construct() { - $his_name = 'his_api'; + $his_name = 'his_http'; $this->service = ClientFactory::getClientTransfer($his_name); $this->setChannel($his_name); } @@ -675,5 +678,23 @@ class Client ]); } + /** + * 获取检查报告影像结果 + * @param ImageType $type 选择查询的类型, styid=查询胶片id,入参(studyid); img=获取胶片,入参(胶片id); img64=获取胶片base64编码,入参(胶片id); pdf=获取报告pdf文件,入参(studyid); pdf64=获取报告pdf的base64编码,入参(studyid) + * @param string $report_id 根据类型字段选择入参 styid,pdf,pdf64入参为studyid;img,img64为胶片id; + * @return mixed + * @throws GeneralException + */ + public function getInspectReportImages(ImageType $type, string $report_id): mixed + { + return $this->requestHandle('POST', 'QueryImageReport', [ + 'json' => [ + 'fileType' => $type, + 'reportId' => $report_id, + ... $this->commonRequestData() + ] + ]); + } + // Inspect/Check Module End } diff --git a/app/Services/HisMedicalHttp/Client.php b/app/Services/HisMedicalHttp/Client.php new file mode 100644 index 0000000..0c3e28a --- /dev/null +++ b/app/Services/HisMedicalHttp/Client.php @@ -0,0 +1,293 @@ +service = ClientFactory::getClientTransfer($his_name); + $this->setChannel($his_name); + } + + /** + * 公共入参 + * @return array + */ + protected function commonRequestData(): array + { + return []; + } + + /** + * 请求 + * @param string $method_name + * @param string $request_name + * @param array $params + * @return mixed + * @throws GeneralException + */ + protected function requestHandle(string $method_name, string $request_name, array $params): mixed + { + try { + return $this->service + ->transferMethod($method_name, $request_name, $params) + ->getResult(); + + } catch (Exception $e) { + $err_msg = "{$e->getMessage()} ON {$e->getFile()}:{$e->getLine()}"; + $this->error('调用api接口失败, 错误消息:' . $err_msg, $params); + + throw new GeneralException($e->getMessage(), Response::HTTP_SERVICE_UNAVAILABLE); + } + } + + /** + * 医保定点签约 + * @param string $cert_type + * @param string $cert_no + * @return mixed + * @throws GeneralException + */ + public function medicalDesignated(string $cert_type, string $cert_no): mixed + { + // 调用请求处理方法 + return $this->requestHandle('POST', 'HospRedesignated', [ + 'json' => [ + 'ApplicationId' => '', + 'ApplicationSecret' => '', + 'Parameter' => [ + 'mdtrt_cert_type' => $cert_type, + 'mdtrt_cert_no' => $cert_no + ] + ] + ]); + } + + /** + * 挂号预结算 + * @param string $patient_number 门诊号码 + * @param string $patient_name 患者名称 + * @param string $total_fee 挂号总金额 + * @param string $fee_code 费用代码 + * @param string $reg_id 挂号号源ID + * @param PatientProperty $patient_property 病人性质 + * @param string $pay_auth_no 支付授权码 + * @param string $lat_lnt 经纬度信息 + * @return mixed + * @throws GeneralException + */ + public function registrationPreSettle( + string $patient_number, + string $patient_name, + string $total_fee, + string $fee_code, + string $reg_id, + PatientProperty $patient_property, + string $pay_auth_no, + string $lat_lnt, + ): mixed + { + // 调用请求处理方法 + return $this->requestHandle('POST', 'GetPrecalculatedFeeGh', [ + 'json' => [ + 'ApplicationId' => '', + 'ApplicationSecret' => '', + 'Parameter' => [ + 'PatientId' => $patient_number, + 'PatientName' => $patient_name, + 'TotalFee' => $total_fee, + 'FeeCode' => $fee_code, + 'RegId' => $reg_id, + 'Brxz' => $patient_property->value, + 'PayAuthNo' => $pay_auth_no, + 'UldLatlnt' => $lat_lnt, + ] +// 'hospitalId' => '', +// 'PatientId' => $patient_number, +// 'PatientName' => $patient_name, +// 'TotalFee' => '01', +// 'FeeCode' => $fee_code, +// 'RegId' => $reg_id, +// 'Brxz' => $patient_property, +// 'PayAuthNo' => $pay_auth_no, +// 'UldLatlnt' => $lat_lnt, +// ... $this->commonRequestData() + ] + ]); + } + + /** + * 查询就诊记录中的所有诊疗单据 + * @param string $patient_number 就诊卡号 + * @param string $start_date 查询开始日期 yyyy-MM-dd + * @param string $end_date 查询结束日期 yyyy-MM-dd + * @return mixed + * @throws GeneralException + */ + public function getMedicalPendingLists( + string $patient_number, + string $start_date, + string $end_date + ): mixed + { + // 调用请求处理方法 + return $this->requestHandle('POST', 'GetUnpayedList', [ + 'json' => [ + 'ApplicationId' => '', + 'ApplicationSecret' => '', + 'Parameter' => [ + 'BeginDate' => $start_date, + 'EndDate' => $end_date, + 'CardType' => '01', + 'CardId' => $patient_number + ] + +// 'hospitalId' => '', +// 'beginDate' => $start_date, +// 'endDate' => $end_date, +// 'cardType' => '01', +// 'cardId' => $patient_number, +// ... $this->commonRequestData() + ] + ]); + } + + /** + * 门诊缴费预结算 + * @param string $patient_number 门诊号码 + * @param string $patient_name 患者名称 + * @param string $pay_auth_no 支付授权码 + * @param string $lat_lnt 经纬度信息 + * @param SettleType $settle_type 结算类型(是否使用个账) + * @param MedicalPayType $pay_type 医保支付类型 + * @param array $fee_records 处方数组集合 [0 => ["FeeNo"=>"", "FeeTypeCode"=>""], 1 => ...] + * @return mixed + * @throws GeneralException + */ + public function outpatientPreSettle( + string $patient_number, + string $patient_name, + string $pay_auth_no, + string $lat_lnt, + SettleType $settle_type, + MedicalPayType $pay_type, + array $fee_records + ): mixed + { + // 调用请求处理方法 + return $this->requestHandle('POST', 'GetPrecalculatedFee', [ + 'json' => [ + 'ApplicationId' => '', + 'ApplicationSecret'=> '', + 'Parameter'=> [ + 'HospitalId' => '', + 'PatientId'=> $patient_number, + 'PatientName'=> $patient_name, + 'PayAuthNo'=> $pay_auth_no, + 'UldLatlnt'=> $lat_lnt, + 'IsInsSelfPay'=> $settle_type->value, + 'YbPayType'=> $pay_type->value, + 'FeeRecords'=> $fee_records + ] +// 'hospitalId' => '', +// 'patientId' => $patient_number, +// 'patientName' => $patient_name, +// 'payAuthNo' => $pay_auth_no, +// 'uldLatlnt' => $lat_lnt, +// 'isInsSelfPay' => $settle_type->value, +// 'ybPayType' => $pay_type->value, +// 'payType' => '03', +// 'feeRecord' => $fee_records, +// ... $this->commonRequestData() + ] + ]); + } + + /** + * 确认医保缴费 + * @param string $patient_number 门诊号码 + * @param string $patient_name 患者名称 + * @param string $trade_no 订单唯一标识 + * @param string $precal_id 预结算ID + * @param string $trade_date 支付时间 yyyy-MM-dd HH:mm:ss + * @param string $total_amount 总金额 单位元 + * @param string $medicare_amount 统筹金额 单位元 + * @param string $pay_amount 现金金额 单位元 + * @param string $account_amount 个帐金额 单位元 + * @param MedicalPayType $pay_type 医保支付类型 + * @param array $fee_records 处方数组集合 [0 => ['FeeNo"=>"", "FeeTypeCode"=>""], 1 => ...] + * @return mixed + * @throws GeneralException + */ + public function confirmMedicalOutpatient( + string $patient_number, + string $patient_name, + string $trade_no, + string $precal_id, + string $trade_date, + string $total_amount, + string $medicare_amount, + string $pay_amount, + string $account_amount, + MedicalPayType $pay_type, + array $fee_records + ): mixed + { + return $this->requestHandle('POST', 'NotifyPayed', [ + 'json' => [ + 'ApplicationId' => '', + 'ApplicationSecret'=> '', + 'Parameter'=> [ + 'HospitalId' => '', + 'PatientId'=> $patient_number, + 'PatientName'=> $patient_name, + 'TradeNo' => $trade_no, + 'PrecalId' => $precal_id, + 'TradeDate' => $trade_date, + 'TotalAmount' => $total_amount, + 'MedicareAmount' => $medicare_amount, + 'PayAmount' => $pay_amount, + 'AccountAmount' => $account_amount, + 'YbPayType' => $pay_type->value, + 'PayType' => '03', + 'FeeRecord' => $fee_records, + ] +// 'hospitalId' => '', +// 'patientId' => $patient_number, +// 'patientName' => $patient_name, +// 'tradeNo' => $trade_no, +// 'precalId' => $precal_id, +// 'tradeDate' => $trade_date, +// 'totalAmount' => $total_amount, +// 'medicareAmount' => $medicare_amount, +// 'payAmount' => $pay_amount, +// 'accountAmount' => $account_amount, +// 'ybPayType' => $pay_type->value, +// 'payType' => '03', +// 'feeRecord' => $fee_records, +// ... $this->commonRequestData() + ] + ]); + } +} diff --git a/app/Services/MedicalAuth/Client.php b/app/Services/MedicalAuth/Client.php index c093bb9..ec70aaa 100644 --- a/app/Services/MedicalAuth/Client.php +++ b/app/Services/MedicalAuth/Client.php @@ -5,10 +5,8 @@ declare(strict_types=1); namespace App\Services\MedicalAuth; use App\Utils\Traits\HttpRequest; +use App\Utils\Traits\Logger; use Exception; -use GuzzleHttp\Exception\GuzzleException; -use JsonException; -use Psr\Http\Message\ResponseInterface; // use JetBrains\PhpStorm\ArrayShape; /** @@ -16,9 +14,10 @@ use Psr\Http\Message\ResponseInterface; * * @link https://yb.qq.com/yibao-payment/doc?nodeId=83679977515675648 */ -class Authorization +class Client { + use Logger; use HttpRequest; /** @@ -105,9 +104,9 @@ class Authorization * @param array $params * @param string $method * @param array $options - * @return ResponseInterface|string[] + * @return array|mixed */ - protected function requestHanle(string $endpoint, array $params = [], string $method = 'post', array $options = []) + protected function requestHandle(string $endpoint, array $params = [], string $method = 'post', array $options = []): mixed { $timestamp = $this->getMillisecond(); $params = array_merge([ @@ -121,64 +120,42 @@ class Authorization ], $options); try { - $response = $this->request($endpoint, $method, $params); + $response = $this->request($method, $endpoint, $params); $this->writerLog($endpoint, $params, $response); return $response; } catch (Exception $e) { + $err_msg = "{$e->getMessage()} ON {$e->getFile()}:{$e->getLine()}"; + $this->info($endpoint, $params, $err_msg); + return [ 'code' => -1, - //'message' => "{$e->getMessage()} ON {$e->getFile()}:{$e->getLine()}" + //'message' => $err_msg 'message' => $e->getMessage() ]; } } /** - * 记录日志 - * @param string $request_url - * @param mixed $request_data - * @param mixed $response - * @return void - */ - private function writerLog(string $request_url, mixed $request_data, mixed $response): void - { - //写入日志 - $log_content = [ - '[REQUEST TIME] ' . date('Y-m-d H:i:s'), - '[REQUEST URL] ' . $request_url, - '[REQUEST PARAM] ' . json_encode($request_data, JSON_UNESCAPED_UNICODE), - '[RESPONSE PARAM] ' . json_encode($response, JSON_UNESCAPED_UNICODE), - "\r\n", - ]; - - recordLog('authorization', implode("\r\n", $log_content)); - } - - /** - * 获取授权跳转地址 - * @param string $return_url 回调地址 + * 获取小程序授权跳转地址 * @return string */ - public function getAuthRedirectUrl(string $return_url): string + public function getMiniProgramAuthRedirectUrl(): string { - $url = '/oauth/code'; + $url = 'auth/pages/bindcard/auth/index'; $data = [ - 'authType' => 2, - 'isDepart' => 2, - 'bizType' => '04107', - 'appid' => $this->config['app_id'], + 'openType' => 'getAuthCode', 'cityCode' => $this->config['city_code'], 'channel' => $this->config['channel'], 'orgChnlCrtfCodg' => $this->config['org_chnl_crtf_codg'], 'orgCodg' => $this->config['org_codg'], + 'bizType' => '04107', 'orgAppId' => $this->config['org_app_id'], - 'redirectUrl' => $return_url ]; - $redirect_url = $this->auth_host . $url . '?' . http_build_query($data); + $redirect_url = $url . '?' . http_build_query($data); $this->writerLog($url, $data, ''); @@ -189,10 +166,9 @@ class Authorization * 查询用户授权 * @param string $auth_code * @param string $open_id - * @return ResponseInterface|string[] - * @throws GuzzleException + * @return mixed */ - public function userQuery(string $auth_code, string $open_id): array|ResponseInterface + public function userQuery(string $auth_code, string $open_id): mixed { $uri = '/api/mipuserquery/userQuery/' . $this->config['partner_id']; $param = [ @@ -200,6 +176,6 @@ class Authorization 'openid' => $open_id, ]; - return $this->requestHanle($uri, $param); + return $this->requestHandle($uri, $param); } } diff --git a/app/Utils/Helpers.php b/app/Utils/Helpers.php index cda85dc..81429c7 100644 --- a/app/Utils/Helpers.php +++ b/app/Utils/Helpers.php @@ -1,5 +1,6 @@ extra_info, true); - $pay_address = $extra_info['confirm_response']['phyAddress'] ?? ''; + $pay_address = $extra_info['confirm_response']['phyAddress'] ?? ' '; $subscribe_id = SubscribeId::OUTPATIENT_PAYMENT_SUCCESS; $data = [ 'touser' => $order->open_id, diff --git a/app/Utils/Traits/UniversalEncryption.php b/app/Utils/Traits/UniversalEncryption.php index e5a4a56..c2bb38f 100644 --- a/app/Utils/Traits/UniversalEncryption.php +++ b/app/Utils/Traits/UniversalEncryption.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace App\Utils\Traits; + trait UniversalEncryption { private string $key; diff --git a/app/Utils/Transfer/HisMedicalHttpClient/ClientFactory.php b/app/Utils/Transfer/HisMedicalHttpClient/ClientFactory.php new file mode 100644 index 0000000..25255d3 --- /dev/null +++ b/app/Utils/Transfer/HisMedicalHttpClient/ClientFactory.php @@ -0,0 +1,25 @@ + new ClientHttpTransfer($name), + true => new ClientMockHttpTransfer($name), + }; + } +} diff --git a/app/Utils/Transfer/HisMedicalHttpClient/ClientHttpTransfer.php b/app/Utils/Transfer/HisMedicalHttpClient/ClientHttpTransfer.php new file mode 100644 index 0000000..e99a34b --- /dev/null +++ b/app/Utils/Transfer/HisMedicalHttpClient/ClientHttpTransfer.php @@ -0,0 +1,62 @@ +transfer_response; + if ($this->transfer_response instanceof ResponseInterface) { + $response = $this->transfer_response->getBody()->getContents(); + } + + try { + return json_decode($response, true, JSON_THROW_ON_ERROR); + } catch (JsonException|Exception $e) { + return [ + 'ResultCode' => 0, + 'ErrorMsg' => '解析JSON失败', + 'ErrorMsgDev' => $e->getMessage(), + 'Result' => $data + ]; + } + } +} diff --git a/app/Utils/Transfer/HisMedicalHttpClient/ClientMockHttpTransfer.php b/app/Utils/Transfer/HisMedicalHttpClient/ClientMockHttpTransfer.php new file mode 100644 index 0000000..13f5748 --- /dev/null +++ b/app/Utils/Transfer/HisMedicalHttpClient/ClientMockHttpTransfer.php @@ -0,0 +1,128 @@ + $this->mockGetPrecalculatedFeeGh($request_data), + 'GetUnpayedList' => $this->mockGetUnpayedList($request_data), + 'GetPrecalculatedFee' => $this->mockGetPrecalculatedFee($request_data), + 'NotifyPayed' => $this->mockNotifyPayed($request_data), + default => throw new GeneralException("Method '{$request_name}' not found"), + }; + } + + /** + * 响应格式化 + * @return mixed + * @throws Exception + */ + public function responseFormat(): mixed + { + try { + // 此处为json格式 + return json_decode((string)$this->transfer_response, true); + } catch (Exception $e) { + throw new Exception($e->getMessage()); + } + } + + /** + * 获取返回值 + * @param bool $is_format + * @return mixed + * @throws Exception + */ + public function getResult(bool $is_format = true): mixed + { + return $this->responseFormat($this->transfer_response); + } + + /** + * mockGetPrecalculatedFeeGh + * @param array $params + * @return self + */ + private function mockGetPrecalculatedFeeGh(array $params): self + { + $this->transfer_response = ''; + + return $this; + } + + /** + * mockRegisterCard + * @param array $params + * @return self + */ + private function mockGetUnpayedList(array $params): self + { + $this->transfer_response = ''; + + return $this; + } + + /** + * mockGetPrecalculatedFee + * @param array $params + * @return self + */ + private function mockGetPrecalculatedFee(array $params): self + { + $this->transfer_response = ''; + + return $this; + } + + /** + * mockNotifyPayed + * @param array $params + * @return self + */ + private function mockNotifyPayed(array $params): self + { + $this->transfer_response = ''; + + return $this; + } +} diff --git a/config/custom.php b/config/custom.php index 32b70b1..0dc0f25 100644 --- a/config/custom.php +++ b/config/custom.php @@ -10,5 +10,7 @@ return [ // 跳转小程序类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版 'mini_program_message_state' => 'developer', // 支付通知地址 - 'payment_notify_url' => env('APP_URL'). '/Api/notify' + 'payment_notify_url' => env('APP_URL'). '/Api/notify', + // 免密授权跳转小程序APP ID + 'redirect_app_id' => 'wxe183cd55df4b4369' ]; diff --git a/config/hisservice.php b/config/hisservice.php index 4303dec..4473c17 100644 --- a/config/hisservice.php +++ b/config/hisservice.php @@ -12,8 +12,8 @@ return [ // 是否模拟数据 'is_mock' => true, ], - // HIS API服务 -- 方鼎中间件转发 - 'his_api' => [ + // HIS HTTP服务 -- 方鼎中间件转发 + 'his_http' => [ // 测试地址 'url' => 'http://192.168.61.45:8809/api/WeChatDomain/', // 不记录日志的请求数组 @@ -24,5 +24,18 @@ return [ 'options' => [ 'timeout' => 60 ] - ] + ], + // HIS Medical HTTP服务 + 'his_medical_http' => [ + // 测试地址 + 'url' => 'http://192.168.61.44:8010/api/his/', + // 不记录日志的请求数组 + 'not_log_arr' => [''], + // 是否模拟数据 + 'is_mock' => true, + // guzzle/client options + 'options' => [ + 'timeout' => 60 + ] + ], ]; diff --git a/config/logging.php b/config/logging.php index b2fdd62..7169028 100644 --- a/config/logging.php +++ b/config/logging.php @@ -174,6 +174,15 @@ return [ 'max_files' => 360, ], + // HisMedicalApi + 'his_medical_http' => [ + 'driver' => 'custom', + 'via' => GeneralDailyLogger::class, + 'service_type' => 'HisMedicalHttpLog', + 'level' => Level::Info, + 'max_files' => 360, + ], + // 挂号日志 'registration' => [ 'driver' => 'custom', @@ -217,7 +226,9 @@ return [ 'service_type' => 'SendWeChatMessage', 'level' => Level::Info, 'max_files' => 0, - ] + ], + + // ], ]; diff --git a/config/unify.php b/config/unify.php index d960f26..74a56a0 100644 --- a/config/unify.php +++ b/config/unify.php @@ -16,9 +16,9 @@ return [ | apiSecret: 应用秘钥 | */ - 'domain' => 'http://fangding.picp.vip:7878/', + 'domain' => 'http://localhost:9040/', - 'apiSecret' => '16f62721-c156-4514-8743-bd969bed32af', + 'apiSecret' => 'a2800bac-3384-4ad3-9002-89af6c44a3ab', /* |-------------------------------------------------------------------------- @@ -36,11 +36,11 @@ return [ 'channels' => [ 'default' => [ - 'merchantId' => 'ZHXZEY20250124', - 'secret' => '94aaadd840a3b97c6bbee4f440b986ce', + 'merchantId' => 'ZHXZEY20250228', + 'secret' => '2a62ff796006a7a2b2d06e40ea240de9', 'channelId' => 'FD001', 'operatorId' => [ - 'MN001', + 'MINI001', ], ] diff --git a/config/unifytest.php b/config/unifytest.php new file mode 100644 index 0000000..bdf5979 --- /dev/null +++ b/config/unifytest.php @@ -0,0 +1,113 @@ + 'http://localhost:7878/', + + 'apiSecret' => 'ed147e09-4e8a-4fe4-8c72-3394b97264f0', + + /* + |-------------------------------------------------------------------------- + | Payment Channels Config + |-------------------------------------------------------------------------- + | + | 统一支付平台支付渠道配置参数 + | merchantId: 商户号 + | secret: 秘钥 + | channelId: 支付渠道 + | operatorId: [ 操作员1 , 操作员2 ] + | + */ + + 'channels' => [ + + 'default' => [ + 'merchantId' => 'ZHXZEY20250228', + 'secret' => '2a62ff796006a7a2b2d06e40ea240de9', + 'channelId' => 'FD013', + 'operatorId' => [ + 'MINI001', + ], + ] + + ], + + /* + |-------------------------------------------------------------------------- + | Logger Config + |-------------------------------------------------------------------------- + | + | 详细使用方式见 MonoLog 文档:https://github.com/Seldaek/monolog/blob/2.x/README.md + | format: + | LineFormatter 字符串 + | JsonFormatter Json + | ... + | handler: + | StreamHandler 单个文件 + | RotatingFileHandler 每日记录一次 + | RedisHandler Redis + | ... + | handler_with: + | RotatingFileHandler: + | file 文件名称 + | max_files 最大记录日志文件数量,0为无限制 + | level 日志记录等级 + | OtherHandler详见MonoLog文档 + | ... + | + */ + + 'log' => [ + // 时区 + 'timezone' => 'Asia/Shanghai', + // 日期格式 + 'date_format' => 'Y-m-d H:i:s', + // 格式化器 + 'formatter' => LineFormatter::class, + // 对应格式化器构造参数 + 'formatter_with' => [ + // 输出格式字符串 + 'output' => "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n\n" + ], + // 处理器 + 'handler' => RotatingFileHandler::class, + // 对应处理器构造参数 + 'handler_with' => [ + // 文件 + 'file' => storage_path('logs/UnifyLog/payment.log'), + // 最大文件 0 无限制 + 'max_files' => 0, + // 记录等级 + 'level' => Logger::DEBUG + ] + ], + + /* + |-------------------------------------------------------------------------- + | Http Client Config + |-------------------------------------------------------------------------- + | + | 详细使用方式见 Guzzle 文档:https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html + | + */ + 'http' => [ + 'verify' => false, + 'timeout' => 20, + 'connect_timeout' => 5.0, + ] +]; + diff --git a/config/wechat.php b/config/wechat.php index 7f07c4e..2b09291 100644 --- a/config/wechat.php +++ b/config/wechat.php @@ -155,5 +155,27 @@ return [ 'timeout' => 5.0, // 'base_uri' => 'https://api.mch.weixin.qq.com/', // 如果你在国外想要覆盖默认的 url 的时候才使用,根据不同的模块配置不同的 uri ], - ] + ], + + 'medical' => [ + /* + |-------------------------------------------------------------------------- + | Medical Auth 配置 + |-------------------------------------------------------------------------- + | + | 医保免密授权对应配置数据 + | 详见:https://yb.qq.com/yibao-payment/doc?nodeId=83679977515675648 + | + */ + 'auth' =>[ + 'app_id' => env('WECHAT_MEDICAL_AUTH_APP_ID', ''),// AppID + 'partner_id' => env('WECHAT_MEDICAL_AUTH_PARTNER_ID', ''), // 合作方ID + 'partner_secret' => env('WECHAT_MEDICAL_AUTH_PARTNER_SECRET', ''), // 合作方秘钥 + 'city_code' => env('WECHAT_MEDICAL_AUTH_CITY_CODE', ''), // 城市ID 香洲区 + 'channel' => env('WECHAT_MEDICAL_AUTH_CHANNEL', ''), // 渠道号(微信医保平台分配) + 'org_chnl_crtf_codg' => env('WECHAT_MEDICAL_AUTH_ORG_CHNL_CRTF_CODG', ''), // 机构渠道认证编码 + 'org_codg' => env('WECHAT_MEDICAL_AUTH_ORG_CODG', ''), // 定点医药机构编码 + 'org_app_id' => env('WECHAT_MEDICAL_AUTH_ORG_APP_ID', ''), // 定点医药机构应用ID + ], + ], ]; diff --git a/packagist/unify_payment/src/Cores/BasicClient.php b/packagist/unify_payment/src/Cores/BasicClient.php index 909257c..4512c7c 100644 --- a/packagist/unify_payment/src/Cores/BasicClient.php +++ b/packagist/unify_payment/src/Cores/BasicClient.php @@ -243,6 +243,8 @@ class BasicClient throw new RuntimeException('Generate Sign Error:' . $e->getMessage()); } + Log::debug('Generate Sign Content', [$data, $buff]); + return trim($buff); } diff --git a/packagist/unify_payment/src/Cores/Struct/CreateMedicalInsuranceOrder.php b/packagist/unify_payment/src/Cores/Struct/CreateMedicalInsuranceOrder.php new file mode 100644 index 0000000..23ea2c5 --- /dev/null +++ b/packagist/unify_payment/src/Cores/Struct/CreateMedicalInsuranceOrder.php @@ -0,0 +1,1074 @@ +setOrderType($orderType); + $this->setAttach($attach); + $this->setInsuranceOrderType($insuranceOrderType); + $this->setOpenId($openId); + $this->setOrderNo($orderNo); + $this->setHospitalName($hospitalName); + $this->setTotalAmount($totalAmount); + $this->setCashAmount($cashAmount); + $this->setAllowFeeChange($allowFeeChange); + $this->setSpbillCreateIp($spbillCreateIp); + $this->setNotifyUrl($notifyUrl); + $this->setTitle($title); + $this->setReturnUrl($returnUrl); + $this->setPayType($payType); + $this->setCityCode($cityCode); + $this->setConsumeType($consumeType); + $this->setInsuranceAmount($insuranceAmount); + $this->setUserCardType($userCardType); + $this->setIdCardNo($idCardNo); + $this->setName($name); + $this->setSerialNo($serialNo); + $this->setOrgNo($orgNo); + $this->setRequestContent($requestContent); + $this->setChannelNo($channelNo); + $this->setBillNo($billNo); + } + + /** + * Get orderType value + * + * @return string + */ + public function getOrderType(): string + { + return $this->orderType; + } + + /** + * Set orderType value + * + * @param string $orderType + * + * @return CreateMedicalInsuranceOrder + */ + public function setOrderType(string $orderType = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($orderType)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($orderType, true), + gettype($orderType) + ), __LINE__ + ); + } + + $this->orderType = $orderType; + return $this; + } + + /** + * Get attach value + * + * @return string + */ + public function getAttach(): string + { + return $this->attach; + } + + /** + * Set attach value + * + * @param string $attach + * + * @return CreateMedicalInsuranceOrder + */ + public function setAttach(string $attach = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($attach)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($attach, true), + gettype($attach) + ), __LINE__ + ); + } + + $this->attach = $attach; + return $this; + } + + /** + * Get insuranceOrderType value + * + * @return string + */ + public function getInsuranceOrderType(): string + { + return $this->insuranceOrderType; + } + + /** + * Set insuranceOrderType value + * + * @param string $insuranceOrderType + * + * @return CreateMedicalInsuranceOrder + */ + public function setInsuranceOrderType(string $insuranceOrderType = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($insuranceOrderType)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($insuranceOrderType, true), + gettype($insuranceOrderType) + ), __LINE__ + ); + } + + $this->insuranceOrderType = $insuranceOrderType; + return $this; + } + + /** + * Get openId value + * + * @return string + */ + public function getOpenId(): string + { + return $this->openId; + } + + /** + * Set openId value + * + * @param string $openId + * + * @return CreateMedicalInsuranceOrder + */ + public function setOpenId(string $openId = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($openId)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($openId, true), + gettype($openId) + ), __LINE__ + ); + } + + $this->openId = $openId; + return $this; + } + + /** + * Get orderNo value + * + * @return string + */ + public function getOrderNo(): string + { + return $this->orderNo; + } + + /** + * Set orderNo value + * + * @param string $orderNo + * + * @return CreateMedicalInsuranceOrder + */ + public function setOrderNo(string $orderNo = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($orderNo)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($orderNo, true), + gettype($orderNo) + ), __LINE__ + ); + } + + $this->orderNo = $orderNo; + return $this; + } + + /** + * Get hospitalName value + * + * @return string + */ + public function getHospitalName(): string + { + return $this->hospitalName; + } + + /** + * Set hospitalName value + * + * @param string $hospitalName + * + * @return CreateMedicalInsuranceOrder + */ + public function setHospitalName(string $hospitalName = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($hospitalName)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($hospitalName, true), + gettype($hospitalName) + ), __LINE__ + ); + } + + $this->hospitalName = $hospitalName; + return $this; + } + + /** + * Get totalAmount value + * + * @return string + */ + public function getTotalAmount(): string + { + return $this->totalAmount; + } + + /** + * Set totalAmount value + * + * @param string $totalAmount + * + * @return CreateMedicalInsuranceOrder + */ + public function setTotalAmount(string $totalAmount = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($totalAmount)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($totalAmount, true), + gettype($totalAmount) + ), __LINE__ + ); + } + + $this->totalAmount = $totalAmount; + return $this; + } + + /** + * Get cashAmount value + * + * @return string + */ + public function getCashAmount(): string + { + return $this->cashAmount; + } + + /** + * Set cashAmount value + * + * @param string $cashAmount + * + * @return CreateMedicalInsuranceOrder + */ + public function setCashAmount(string $cashAmount = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($cashAmount)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($cashAmount, true), + gettype($cashAmount) + ), __LINE__ + ); + } + + $this->cashAmount = $cashAmount; + return $this; + } + + /** + * Get allowFeeChange value + * + * @return string + */ + public function getAllowFeeChange(): string + { + return $this->allowFeeChange; + } + + /** + * Set allowFeeChange value + * + * @param string $allowFeeChange + * + * @return CreateMedicalInsuranceOrder + */ + public function setAllowFeeChange(string $allowFeeChange = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($allowFeeChange)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($allowFeeChange, true), + gettype($allowFeeChange) + ), __LINE__ + ); + } + + $this->allowFeeChange = $allowFeeChange; + return $this; + } + + /** + * Get spbillCreateIp value + * + * @return string + */ + public function getSpbillCreateIp(): string + { + return $this->spbillCreateIp; + } + + /** + * Set spbillCreateIp value + * + * @param string $spbillCreateIp + * + * @return CreateMedicalInsuranceOrder + */ + public function setSpbillCreateIp(string $spbillCreateIp = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($spbillCreateIp)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($spbillCreateIp, true), + gettype($spbillCreateIp) + ), __LINE__ + ); + } + + $this->spbillCreateIp = $spbillCreateIp; + return $this; + } + + /** + * Get notifyUrl value + * + * @return string + */ + public function getNotifyUrl(): string + { + return $this->notifyUrl; + } + + /** + * Set notifyUrl value + * + * @param string $notifyUrl + * + * @return CreateMedicalInsuranceOrder + */ + public function setNotifyUrl(string $notifyUrl = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($notifyUrl)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($notifyUrl, true), + gettype($notifyUrl) + ), __LINE__ + ); + } + + $this->notifyUrl = $notifyUrl; + return $this; + } + + /** + * Get title value + * + * @return string + */ + public function getTitle(): string + { + return $this->title; + } + + /** + * Set title value + * + * @param string $title + * + * @return CreateMedicalInsuranceOrder + */ + public function setTitle(string $title = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($title)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($title, true), + gettype($title) + ), __LINE__ + ); + } + + $this->title = $title; + return $this; + } + + /** + * Get returnUrl value + * + * @return string + */ + public function getReturnUrl(): string + { + return $this->returnUrl; + } + + /** + * Set returnUrl value + * + * @param string $returnUrl + * + * @return CreateMedicalInsuranceOrder + */ + public function setReturnUrl(string $returnUrl = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($returnUrl)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($returnUrl, true), + gettype($returnUrl) + ), __LINE__ + ); + } + + $this->returnUrl = $returnUrl; + return $this; + } + + /** + * Get payType value + * + * @return string + */ + public function getPayType(): string + { + return $this->payType; + } + + /** + * Set payType value + * + * @param string $payType + * + * @return CreateMedicalInsuranceOrder + */ + public function setPayType(string $payType = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($payType)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($payType, true), + gettype($payType) + ), __LINE__ + ); + } + + $this->payType = $payType; + return $this; + } + + /** + * Get cityCode value + * + * @return string + */ + public function getCityCode(): string + { + return $this->cityCode; + } + + /** + * Set cityCode value + * + * @param string $cityCode + * + * @return CreateMedicalInsuranceOrder + */ + public function setCityCode(string $cityCode = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($cityCode)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($cityCode, true), + gettype($cityCode) + ), __LINE__ + ); + } + + $this->cityCode = $cityCode; + return $this; + } + + /** + * Get consumeType value + * + * @return string + */ + public function getConsumeType(): string + { + return $this->consumeType; + } + + /** + * Set consumeType value + * + * @param string $consumeType + * + * @return CreateMedicalInsuranceOrder + */ + public function setConsumeType(string $consumeType = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($consumeType)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($consumeType, true), + gettype($consumeType) + ), __LINE__ + ); + } + + $this->consumeType = $consumeType; + return $this; + } + + /** + * Get insuranceAmount value + * + * @return string + */ + public function getInsuranceAmount(): string + { + return $this->insuranceAmount; + } + + /** + * Set insuranceAmount value + * + * @param string $insuranceAmount + * + * @return CreateMedicalInsuranceOrder + */ + public function setInsuranceAmount(string $insuranceAmount = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($insuranceAmount)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($insuranceAmount, true), + gettype($insuranceAmount) + ), __LINE__ + ); + } + + $this->insuranceAmount = $insuranceAmount; + return $this; + } + + /** + * Get userCardType value + * + * @return string + */ + public function getUserCardType(): string + { + return $this->userCardType; + } + + /** + * Set userCardType value + * + * @param string $userCardType + * + * @return CreateMedicalInsuranceOrder + */ + public function setUserCardType(string $userCardType = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($userCardType)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($userCardType, true), + gettype($userCardType) + ), __LINE__ + ); + } + + $this->userCardType = $userCardType; + return $this; + } + + /** + * Get idCardNo value + * + * @return string + */ + public function getIdCardNo(): string + { + return $this->idCardNo; + } + + /** + * Set idCardNo value + * + * @param string $idCardNo + * + * @return CreateMedicalInsuranceOrder + */ + public function setIdCardNo(string $idCardNo = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($idCardNo)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($idCardNo, true), + gettype($idCardNo) + ), __LINE__ + ); + } + + $this->idCardNo = $idCardNo; + return $this; + } + + /** + * Get name value + * + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * Set name value + * + * @param string $name + * + * @return CreateMedicalInsuranceOrder + */ + public function setName(string $name = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($name)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($name, true), + gettype($name) + ), __LINE__ + ); + } + + $this->name = $name; + return $this; + } + + /** + * Get serialNo value + * + * @return string + */ + public function getSerialNo(): string + { + return $this->serialNo; + } + + /** + * Set serialNo value + * + * @param string $serialNo + * + * @return CreateMedicalInsuranceOrder + */ + public function setSerialNo(string $serialNo = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($serialNo)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($serialNo, true), + gettype($serialNo) + ), __LINE__ + ); + } + + $this->serialNo = $serialNo; + return $this; + } + + /** + * Get orgNo value + * + * @return string + */ + public function getOrgNo(): string + { + return $this->orgNo; + } + + /** + * Set orgNo value + * + * @param string $orgNo + * + * @return CreateMedicalInsuranceOrder + */ + public function setOrgNo(string $orgNo = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($orgNo)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($orgNo, true), + gettype($orgNo) + ), __LINE__ + ); + } + + $this->orgNo = $orgNo; + return $this; + } + + /** + * Get requestContent value + * + * @return string + */ + public function getRequestContent(): string + { + return $this->requestContent; + } + + /** + * Set requestContent value + * + * @param string $requestContent + * + * @return CreateMedicalInsuranceOrder + */ + public function setRequestContent(string $requestContent = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($requestContent)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($requestContent, true), + gettype($requestContent) + ), __LINE__ + ); + } + + $this->requestContent = $requestContent; + return $this; + } + + /** + * Get channelNo value + * + * @return string + */ + public function getChannelNo(): string + { + return $this->channelNo; + } + + /** + * Set channelNo value + * + * @param string $channelNo + * + * @return CreateMedicalInsuranceOrder + */ + public function setChannelNo(string $channelNo = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($channelNo)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($channelNo, true), + gettype($channelNo) + ), __LINE__ + ); + } + + $this->channelNo = $channelNo; + return $this; + } + + /** + * Get billNo value + * + * @return string + */ + public function getBillNo(): string + { + return $this->billNo; + } + + /** + * Set billNo value + * + * @param string $billNo + * + * @return CreateMedicalInsuranceOrder + */ + public function setBillNo(string $billNo = ''): CreateMedicalInsuranceOrder + { + // validation for constraint: string + if (!is_string($billNo)) { + throw new InvalidArgumentException( + sprintf( + 'Invalid value %s, please provide a string, %s given', + var_export($billNo, true), + gettype($billNo) + ), __LINE__ + ); + } + + $this->billNo = $billNo; + return $this; + } +} diff --git a/packagist/unify_payment/src/Mock/CreateMedicalInsuranceOrderHandler.php b/packagist/unify_payment/src/Mock/CreateMedicalInsuranceOrderHandler.php new file mode 100644 index 0000000..8fdc8e0 --- /dev/null +++ b/packagist/unify_payment/src/Mock/CreateMedicalInsuranceOrderHandler.php @@ -0,0 +1,49 @@ +faker->randomDigit(); + + $data = array_merge($this->getSuccessHeaderData('Query Success.'), [ + 'response' => [ + 'payUrl' => $this->faker->url(), + 'medTransId' => 'M'. $this->faker->time('ymdhis'). $this->faker->regexify('[0-9]{5}'), + 'payAppId' => 'wx' . $this->faker->regexify('[0-9a-z]{15}'), + 'orderNo' => 'FD' . $channelId . $this->faker->time('YmdHis') . $this->faker->regexify('[0-9]{5}'), + 'merchantId' => 'STZL' . $this->faker->time('Ymd'), + 'merchantName' => $this->faker->company(), + 'channelId' => $channelId, + 'channelName' => '微信移动医保' + ] + ]); + + return [ + self::SUCCESS_CODE, + self::JSON_HEADER, + json_encode($data, JSON_UNESCAPED_UNICODE) + ]; + } + + /** + * @return array + */ + public function failure(): array + { + $data = $this->getFailureHeaderData($this->faker->sentence()); + + return [ + self::FAILURE_CODE, + self::JSON_HEADER, + json_encode($data, JSON_UNESCAPED_UNICODE) + ]; + } +} \ No newline at end of file diff --git a/packagist/unify_payment/src/Modules/Pay/MiniProgramClient.php b/packagist/unify_payment/src/Modules/Pay/MiniProgramClient.php index 8968e58..a12e3f1 100644 --- a/packagist/unify_payment/src/Modules/Pay/MiniProgramClient.php +++ b/packagist/unify_payment/src/Modules/Pay/MiniProgramClient.php @@ -8,6 +8,7 @@ use ReflectionException; use UnifyPayment\Cores\BasicClient; use UnifyPayment\Cores\Exceptions\InvalidConfigException; use UnifyPayment\Cores\Exceptions\RuntimeException; +use UnifyPayment\Cores\Struct\CreateMedicalInsuranceOrder; use UnifyPayment\Cores\Struct\CreateOrder; class MiniProgramClient extends BasicClient @@ -24,7 +25,7 @@ class MiniProgramClient extends BasicClient * @throws InvalidConfigException * @throws RuntimeException */ - public function jsapi(CreateOrder $order) + public function jsapi(CreateOrder $order): array|string { $this->payload = array_merge($this->payload, $order->toArray()); $this->payload['sign'] = $this->generateSign($this->payload); @@ -32,4 +33,22 @@ class MiniProgramClient extends BasicClient return $this->request('post', '/index.php/api/pay/miniapp'); } + /** + * WeChat Mini Program Medical Insurance Payment + * + * @param CreateMedicalInsuranceOrder $order + * + * @return array|string + * + * @throws InvalidConfigException + * @throws ReflectionException + * @throws RuntimeException + */ + public function medicalPay(CreateMedicalInsuranceOrder $order): array|string + { + $this->payload = array_merge($this->payload, $order->toArray()); + $this->payload['sign'] = $this->generateSign($this->payload); + + return $this->request('post', '/index.php/api/pay/mpInsurance'); + } } diff --git a/packagist/unify_payment/src/Modules/Pay/OfficialAccountClient.php b/packagist/unify_payment/src/Modules/Pay/OfficialAccountClient.php index f1d78ce..917a1c7 100644 --- a/packagist/unify_payment/src/Modules/Pay/OfficialAccountClient.php +++ b/packagist/unify_payment/src/Modules/Pay/OfficialAccountClient.php @@ -8,6 +8,7 @@ use ReflectionException; use UnifyPayment\Cores\BasicClient; use UnifyPayment\Cores\Exceptions\InvalidConfigException; use UnifyPayment\Cores\Exceptions\RuntimeException; +use UnifyPayment\Cores\Struct\CreateMedicalInsuranceOrder; use UnifyPayment\Cores\Struct\CreateOrder; class OfficialAccountClient extends BasicClient @@ -24,11 +25,30 @@ class OfficialAccountClient extends BasicClient * @throws InvalidConfigException * @throws RuntimeException */ - public function jsapi(CreateOrder $order) + public function jsapi(CreateOrder $order): array|string { $this->payload = array_merge($this->payload, $order->toArray()); $this->payload['sign'] = $this->generateSign($this->payload); return $this->request('post', '/index.php/api/pay/mp'); } + + /** + * WeChat Official Account Medical Insurance Payment + * + * @param CreateMedicalInsuranceOrder $order + * + * @return array|string + * + * @throws InvalidConfigException + * @throws ReflectionException + * @throws RuntimeException + */ + public function medicalPay(CreateMedicalInsuranceOrder $order): array|string + { + $this->payload = array_merge($this->payload, $order->toArray()); + $this->payload['sign'] = $this->generateSign($this->payload); + + return $this->request('post', '/index.php/api/pay/mpInsurance'); + } } diff --git a/packagist/unify_payment/tests/PayTest.php b/packagist/unify_payment/tests/PayTest.php index 089ed70..2afc39a 100644 --- a/packagist/unify_payment/tests/PayTest.php +++ b/packagist/unify_payment/tests/PayTest.php @@ -1,7 +1,9 @@ getConfig())->mini->setMockHandler([$mockHandler])->medicalPay($order); + + $this->assertArrayHasKey('response', $response); + $this->assertEquals([ + 'payUrl', + 'medTransId', + 'payAppId', + 'orderNo', + 'merchantId', + 'merchantName', + 'channelId', + 'channelName' + ], array_keys($response['response'])); + } + public function testOfficialAccountJsapi() { $order = new CreateOrder('当天挂号', 'FD2022080310000055123451223', 0.01, '1', '002197|公众号', 'A', 'ovIGQwOy1e-Zptyug20l5hqI0P5Q', 'https://www.baidu.com'); @@ -49,4 +71,24 @@ class PayTest extends TestCase 'channelName' ], array_keys($response['response'])); } + + public function testOfficialAccountMedicalPay() + { + $order = new CreateMedicalInsuranceOrder('DiagPay', 'ow_gnt5QQQ-Qby1234567890', '202204022005169952975171534816', '某某附属医院', '70', '1', 'https://kyey.test.com/pay/notify/wechatINSUNotify', '门诊缴费', 'https://kyey.test.com/wehospital/payment/paymentindex', '2', 'H53011234567', '20220402200530', '530100', '202204022005169952975171534816', '1', '0ef5c63f34237aeba58d0a6f97f66c13', '姓x', '4', '70', '0', '{"payAuthNo":"AUTH530100202204022006310000034","payOrdId":"ORD530100202204022006350000021","setlLatlnt":"102.682296,25.054260"}', 'AAGN9uhZc5EGyRdairKW7Qnu', 'bd787ae69021fbd7d0b86d080b007bad', '127.0.0.1'); + + $mockHandler = new CreateMedicalInsuranceOrderHandler(true); + $response = Factory::pay($this->getConfig())->official->setMockHandler([$mockHandler])->medicalPay($order); + + $this->assertArrayHasKey('response', $response); + $this->assertEquals([ + 'payUrl', + 'medTransId', + 'payAppId', + 'orderNo', + 'merchantId', + 'merchantName', + 'channelId', + 'channelName' + ], array_keys($response['response'])); + } } \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index 22faaf5..f2d3c17 100644 --- a/routes/api.php +++ b/routes/api.php @@ -4,11 +4,12 @@ use App\Http\Controllers\Auth\AuthController; use App\Http\Controllers\Hospital\IntroduceController; use App\Http\Controllers\Message\TriggerController; use App\Http\Controllers\Notify\NotifyController; -use App\Http\Controllers\Outpatient\MedicalController; +use App\Http\Controllers\Outpatient\MedicalController as OutpatientMedicalController; use App\Http\Controllers\Outpatient\PaymentController; use App\Http\Controllers\Outpatient\PendingController; use App\Http\Controllers\Outpatient\RecordController as OutpatientRecordController; use App\Http\Controllers\Patient\PatientController; +use App\Http\Controllers\Registration\MedicalController as RegistrationMedicalController; use App\Http\Controllers\Registration\RecordController as RegistrationRecordController; use App\Http\Controllers\Registration\RegisterController; use App\Http\Controllers\Registration\ScheduleController; @@ -40,6 +41,9 @@ Route::middleware(['apiLog'])->group(function() { // 获取手机号码 Route::get('/phone', [PatientController::class, 'getPhoneNumber']); + + // 定点签约 + Route::post('/{patient_id}/designate', [PatientController::class, 'designated']); }); // 挂号模块 @@ -49,14 +53,19 @@ Route::middleware(['apiLog'])->group(function() { Route::get('/doctor', [ScheduleController::class, 'doctor']); Route::post('/{patient_id}/register', [RegisterController::class, 'register']); + // 医保预结算 + Route::post('/{patient_id}/medical/settle', [RegistrationMedicalController::class, 'registerPreSettle']); + // 医保支付 + Route::post('/{patient_id}/medical/payment', [RegistrationMedicalController::class, 'register']); + Route::get('/{patient_id}/record', [RegistrationRecordController::class, 'lists']); Route::post('/{patient_id}/record/{serial_no}/refund', [RegistrationRecordController::class, 'refund']); }); // 医保模块 Route::prefix('Medical')->group(function () { - Route::get('/auth/path', [MedicalController::class, 'redirectAuth']); - Route::post('/auth/{patient_id}/info', [MedicalController::class, 'userAuthInfo']); + Route::get('/auth/path', [OutpatientMedicalController::class, 'redirectAuth']); + Route::post('/auth/{patient_id}/info', [OutpatientMedicalController::class, 'userAuthInfo']); }); // 缴费模块 @@ -65,6 +74,11 @@ Route::middleware(['apiLog'])->group(function() { Route::get('/{patient_id}/pending/{serial_no}/', [PendingController::class, 'details']); Route::post('/{patient_id}/pending/{serial_no}/payment', [PaymentController::class, 'payment']); + // 医保预结算 + Route::post('/{patient_id}/pending/{serial_no}/medical/settle', [OutpatientMedicalController::class, 'prescriptionPreSettle']); + // 医保支付 + Route::post('/{patient_id}/pending/{serial_no}/medical/payment', [OutpatientMedicalController::class, 'payment']); + Route::get('/{patient_id}/record', [OutpatientRecordController::class, 'lists']); Route::get('/{patient_id}/record/{serial_no}/', [OutpatientRecordController::class, 'details']); }); @@ -77,6 +91,7 @@ Route::middleware(['apiLog'])->group(function() { // 报告相关模块 Route::prefix('Report')->group(function () { + Route::get('/tips', [InspectController::class, 'tips']); Route::get('/{patient_id}/inspect', [InspectController::class, 'lists']); Route::get('/{patient_id}/inspect/{serial_no}/', [InspectController::class, 'details']); });