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); } } }