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