香洲二院小程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
mini_xzey/app/Http/Logics/Outpatient/MedicalLogic.php

498 lines
19 KiB

<?php
declare(strict_types = 1);
namespace App\Http\Logics\Outpatient;
use App\Dictionary\Medical\OrderType;
use App\Dictionary\Medical\PayType as MedicalPayType;
use App\Dictionary\Order\PayType as OrderPayType;
use App\Dictionary\Medical\PreSettleStatus;
use App\Dictionary\Medical\SettleType;
use App\Dictionary\Order\SourceId;
use App\Dictionary\Order\Type;
use App\Dictionary\Patient\CardType;
use App\Exceptions\GeneralException;
use App\Models\Order;
use App\Models\Patient;
use App\Services\HisHttp\Client as HisHttpClient;
use App\Services\HisMedicalHttp\Client as HisMedicalHttpClient;
use App\Services\MedicalAuth\Client as AuthClient;
use App\Utils\Statics\BuildCacheKeyName;
use App\Utils\Traits\Logger;
use App\Utils\Traits\MiniProgramAuth;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Support\Facades\Redis;
use ReflectionException;
use Symfony\Component\HttpFoundation\Response;
use UnifyPayment\Cores\Exceptions\InvalidConfigException;
use UnifyPayment\Cores\Exceptions\RuntimeException;
use UnifyPayment\Cores\Struct\CreateMedicalInsuranceOrder;
use UnifyPayment\Unify;
class MedicalLogic
{
use Logger;
use MiniProgramAuth;
private HisHttpClient $his_client;
private HisMedicalHttpClient $his_medical_client;
private AuthClient $auth_client;
private Order $order_model;
private Patient $patient_model;
/**
* PaymentLogic Construct
* @throws AuthenticationException
*/
public function __construct()
{
$this->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);
}
}
}