parent
d131a3702a
commit
a1ed3ea2dc
@ -0,0 +1,54 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace App\Dictionary\Medical; |
||||
|
||||
/** |
||||
* 支付类型 |
||||
*/ |
||||
enum OrderType: string |
||||
{ |
||||
case REG_PAY = 'RegPay'; // 挂号支付 |
||||
|
||||
case MED_PAY = 'MedPay'; // 药费支付 |
||||
|
||||
case DIAG_PAY = 'DiagPay'; // 诊间支付 |
||||
|
||||
case IN_HOSP_PAY = 'InHospPay'; // 住院费支付 |
||||
|
||||
case PHARMACY_PAY = 'PharmacyPay'; // 药店支付 |
||||
|
||||
case INSURANCE_PAY = 'InsurancePay'; // 保险费支付 |
||||
|
||||
case INT_REG_PAY = 'IntRegPay'; // 互联网医院挂号支付 |
||||
|
||||
case INT_RE_DIAG_PAY = 'IntReDiagPay'; // 互联网医院复诊支付 |
||||
|
||||
case INT_PSC_PAY = 'IntPscPay'; // 互联网医院处方支付 |
||||
|
||||
case COVID_EXAM_PAY = 'CovidExamPay'; // 新冠检测费用 |
||||
|
||||
case COVID_ANTIGEN_PAY = 'CvidAntigenPay';// 新冠抗原检测 |
||||
|
||||
/** |
||||
* Label string |
||||
* @return string |
||||
*/ |
||||
public function label(): string |
||||
{ |
||||
return match($this) { |
||||
self::REG_PAY => '挂号支付', |
||||
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 => '新冠抗原检测', |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,28 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace App\Dictionary\Medical; |
||||
|
||||
/** |
||||
* 病人性质 |
||||
*/ |
||||
enum PatientProperty: int |
||||
{ |
||||
|
||||
case OUTPATIENT_PLANNING = 6089; // 门诊统筹 |
||||
|
||||
case OUTPATIENT_MUTUAL_AID = 6095; // 门诊共济 |
||||
|
||||
/** |
||||
* Label string |
||||
* @return string |
||||
*/ |
||||
public function label(): string |
||||
{ |
||||
return match($this) { |
||||
self::OUTPATIENT_PLANNING => '门诊统筹', |
||||
self::OUTPATIENT_MUTUAL_AID => '门诊共济', |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,30 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace App\Dictionary\Medical; |
||||
|
||||
/** |
||||
* 医保支付类型 |
||||
*/ |
||||
enum PayType: int |
||||
{ |
||||
case SELF_PAY = 0; // 自费 |
||||
|
||||
case OFFLINE_MEDICARE = 1; // 线下医保 |
||||
|
||||
case ONLINE_MEDICARE = 2; // 线上医保 |
||||
|
||||
/** |
||||
* Label string |
||||
* @return string |
||||
*/ |
||||
public function label(): string |
||||
{ |
||||
return match($this) { |
||||
self::SELF_PAY => '自费', |
||||
self::OFFLINE_MEDICARE => '线下医保', |
||||
self::ONLINE_MEDICARE => '线上医保', |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,30 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace App\Dictionary\Medical; |
||||
|
||||
/** |
||||
* 医保预结算状态 |
||||
*/ |
||||
enum PreSettleStatus: int |
||||
{ |
||||
case NOT_NEEDED = 0; // 无需预结算 |
||||
|
||||
case SETTLED = 1; // 已预结算 |
||||
|
||||
case CANCELLED = 2; // 已取消预结算 |
||||
|
||||
/** |
||||
* Label string |
||||
* @return string |
||||
*/ |
||||
public function label(): string |
||||
{ |
||||
return match($this) { |
||||
self::NOT_NEEDED => '无需预结算', |
||||
self::SETTLED => '已预结算', |
||||
self::CANCELLED => '已取消预结算', |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,27 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace App\Dictionary\Medical; |
||||
|
||||
/** |
||||
* 医保预结算类型 |
||||
*/ |
||||
enum SettleType: int |
||||
{ |
||||
case CASH = 0; // 使用现金支付 |
||||
|
||||
case MEDICARE_ACCOUNT = 1; // 使用医保个账支付 |
||||
|
||||
/** |
||||
* Label string |
||||
* @return string |
||||
*/ |
||||
public function label(): string |
||||
{ |
||||
return match($this) { |
||||
self::CASH => '使用现金支付', |
||||
self::MEDICARE_ACCOUNT => '使用医保个账支付', |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,35 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace App\Dictionary\Report; |
||||
|
||||
/** |
||||
* 检查报告影像类型 |
||||
*/ |
||||
enum ImageType: string |
||||
{ |
||||
case STUDY_ID = 'styid'; // 查询胶片ID (参数: studyid) |
||||
|
||||
case IMAGE = 'img'; // 获取胶片图像 (参数: 胶片ID) |
||||
|
||||
case IMAGE_BASE64 = 'img64'; // 获取胶片Base64编码 (参数: 胶片ID) |
||||
|
||||
case PDF = 'pdf'; // 获取报告PDF文件 (参数: studyid) |
||||
|
||||
case PDF_BASE64 = 'pdf64'; // 获取报告PDF的Base64编码 (参数: studyid) |
||||
|
||||
/** |
||||
* Label string |
||||
*/ |
||||
public function label(): string |
||||
{ |
||||
return match ($this) { |
||||
self::STUDY_ID => '查询胶片ID', |
||||
self::IMAGE => '获取胶片图像', |
||||
self::IMAGE_BASE64 => '获取胶片Base64编码', |
||||
self::PDF => '获取报告PDF文件', |
||||
self::PDF_BASE64 => '获取报告PDF的Base64编码', |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,110 @@ |
||||
<?php |
||||
declare(strict_types = 1); |
||||
|
||||
namespace App\Http\Controllers\Outpatient; |
||||
|
||||
use App\Dictionary\Medical\SettleType; |
||||
use App\Exceptions\GeneralException; |
||||
use App\Http\Logics\Outpatient\MedicalLogic; |
||||
use App\Http\Resources\Outpatient\Medical\PrescriptionPreSettleResource; |
||||
use Illuminate\Http\JsonResponse; |
||||
use Illuminate\Http\Request; |
||||
use Illuminate\Validation\Rules\Enum; |
||||
use Symfony\Component\HttpFoundation\Response; |
||||
|
||||
class MedicalController |
||||
{ |
||||
protected MedicalLogic $medical_logic; |
||||
|
||||
/** |
||||
* MedicalController Construct. |
||||
*/ |
||||
public function __construct() |
||||
{ |
||||
$this->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); |
||||
} |
||||
} |
@ -0,0 +1,77 @@ |
||||
<?php |
||||
declare(strict_types = 1); |
||||
|
||||
namespace App\Http\Controllers\Registration; |
||||
|
||||
use App\Dictionary\Medical\PatientProperty; |
||||
use App\Dictionary\Medical\SettleType; |
||||
use App\Exceptions\GeneralException; |
||||
use App\Http\Logics\Registration\MedicalLogic; |
||||
use App\Http\Requests\Registration\RegisterPreSettleRequest; |
||||
use App\Http\Resources\Registration\Medical\RegisterPreSettleResource; |
||||
use Illuminate\Http\JsonResponse; |
||||
use Illuminate\Http\Request; |
||||
use Symfony\Component\HttpFoundation\Response; |
||||
|
||||
class MedicalController |
||||
{ |
||||
protected MedicalLogic $medical_logic; |
||||
|
||||
/** |
||||
* MedicalController Construct. |
||||
*/ |
||||
public function __construct() |
||||
{ |
||||
$this->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); |
||||
} |
||||
} |
@ -0,0 +1,497 @@ |
||||
<?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); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,504 @@ |
||||
<?php |
||||
declare(strict_types = 1); |
||||
|
||||
namespace App\Http\Logics\Registration; |
||||
|
||||
use App\Dictionary\Medical\OrderType; |
||||
use App\Dictionary\Medical\PatientProperty; |
||||
use App\Dictionary\Medical\PayType as MedicalPayType; |
||||
use App\Dictionary\Order\PayType; |
||||
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\Admin\Department; |
||||
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('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); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,77 @@ |
||||
<?php |
||||
|
||||
namespace App\Http\Requests\Registration; |
||||
|
||||
use App\Dictionary\Medical\PatientProperty; |
||||
use App\Dictionary\Medical\SettleType; |
||||
use Illuminate\Contracts\Validation\ValidationRule; |
||||
use Illuminate\Foundation\Http\FormRequest; |
||||
use Illuminate\Validation\Rules\Enum; |
||||
|
||||
class RegisterPreSettleRequest extends FormRequest |
||||
{ |
||||
/** |
||||
* Determine if the user is authorized to make this request. |
||||
*/ |
||||
public function authorize(): bool |
||||
{ |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* Get the validation rules that apply to the request. |
||||
* |
||||
* @return array<string, ValidationRule|array|string> |
||||
*/ |
||||
/** |
||||
* 规则 |
||||
* @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' => '结算类型', |
||||
]; |
||||
} |
||||
} |
@ -0,0 +1,37 @@ |
||||
<?php |
||||
declare(strict_types = 1); |
||||
|
||||
namespace App\Http\Resources\Outpatient\Medical; |
||||
|
||||
use Illuminate\Http\Request; |
||||
use Illuminate\Http\Resources\Json\JsonResource; |
||||
|
||||
class PrescriptionPreSettleResource extends JsonResource |
||||
{ |
||||
/** |
||||
* Transform the resource into an array. |
||||
* |
||||
* @return array<string, mixed> |
||||
*/ |
||||
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'] ?? '' |
||||
]; |
||||
} |
||||
} |
@ -0,0 +1,37 @@ |
||||
<?php |
||||
declare(strict_types = 1); |
||||
|
||||
namespace App\Http\Resources\Registration\Medical; |
||||
|
||||
use Illuminate\Http\Request; |
||||
use Illuminate\Http\Resources\Json\JsonResource; |
||||
|
||||
class RegisterPreSettleResource extends JsonResource |
||||
{ |
||||
/** |
||||
* Transform the resource into an array. |
||||
* |
||||
* @return array<string, mixed> |
||||
*/ |
||||
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'] ?? '' |
||||
]; |
||||
} |
||||
} |
@ -0,0 +1,293 @@ |
||||
<?php |
||||
declare(strict_types=1); |
||||
|
||||
namespace App\Services\HisMedicalHttp; |
||||
|
||||
use App\Dictionary\Medical\PatientProperty; |
||||
use App\Dictionary\Medical\PayType as MedicalPayType; |
||||
use App\Dictionary\Medical\SettleType; |
||||
use App\Exceptions\GeneralException; |
||||
use App\Utils\Traits\Logger; |
||||
use App\Utils\Transfer\HisMedicalHttpClient\ClientFactory; |
||||
use App\Utils\Transfer\HttpTransferAbstract; |
||||
use Exception; |
||||
use Symfony\Component\HttpFoundation\Response; |
||||
|
||||
class Client |
||||
{ |
||||
use Logger; |
||||
|
||||
// his服务 |
||||
private HttpTransferAbstract $service; |
||||
|
||||
/** |
||||
* Client Construct |
||||
*/ |
||||
public function __construct() |
||||
{ |
||||
$his_name = 'his_medical_http'; |
||||
$this->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() |
||||
] |
||||
]); |
||||
} |
||||
} |
@ -0,0 +1,101 @@ |
||||
<?php |
||||
declare(strict_types=1); |
||||
|
||||
namespace App\Utils\Statics; |
||||
|
||||
class BuildCacheKeyName |
||||
{ |
||||
/** |
||||
* 获取 检验报告列表 缓存建名 |
||||
* @param string $open_id |
||||
* @param string $patient_id |
||||
* @return string |
||||
*/ |
||||
public static function getInspectReportListsKey(string $open_id, string $patient_id): string |
||||
{ |
||||
return 'report.inspect.'. $open_id. '.'. $patient_id; |
||||
} |
||||
|
||||
/** |
||||
* 获取 检验报告详情图片 缓存建名 |
||||
* @param string $open_id |
||||
* @param string $patient_id |
||||
* @param string $serial_no |
||||
* @return string |
||||
*/ |
||||
public static function getInspectReportDetailsImagesKey(string $open_id, string $patient_id, string $serial_no): string |
||||
{ |
||||
return 'report.inspect.'. $open_id. '.'. $patient_id. '.images.'. $serial_no; |
||||
} |
||||
|
||||
/** |
||||
* 获取 门诊待缴费列表 缓存键名 |
||||
* @param string $open_id |
||||
* @param string $patient_id |
||||
* @return string |
||||
*/ |
||||
public static function getOutpatientPendingListsKey(string $open_id, string $patient_id): string |
||||
{ |
||||
return 'outpatient.pending.'. $open_id. '.'. $patient_id; |
||||
} |
||||
|
||||
/** |
||||
* 获取 门诊待缴费详情 缓存键名 |
||||
* @param string $open_id |
||||
* @param string $patient_id |
||||
* @param string $serial_no |
||||
* @return string |
||||
*/ |
||||
public static function getOutpatientPendingDetailsKey(string $open_id, string $patient_id, string $serial_no): string |
||||
{ |
||||
return 'outpatient.pending.'. $open_id.'.'. $patient_id. '.'. $serial_no; |
||||
} |
||||
|
||||
/** |
||||
* 获取 门诊医保待缴费详情 缓存键名 |
||||
* @param string $open_id |
||||
* @param string $patient_id |
||||
* @param string $serial_no |
||||
* @return string |
||||
*/ |
||||
public static function getOutpatientMedicalPendingDetailsKey(string $open_id, string $patient_id, string $serial_no): string |
||||
{ |
||||
return 'outpatient.medical.pending.'. $open_id.'.'. $patient_id. '.'. $serial_no; |
||||
} |
||||
|
||||
/** |
||||
* 获取用户 免密授权 缓存键名 |
||||
* @param string $open_id |
||||
* @param string $patient_id |
||||
* @return string |
||||
*/ |
||||
public static function getUserMedicalInsuranceAuthKey(string $open_id, string $patient_id): string |
||||
{ |
||||
return 'medical.user.info.'. $open_id.'.'. $patient_id; |
||||
} |
||||
|
||||
/** |
||||
* 获取挂号预结算详情缓存键名 |
||||
* @param string $open_id |
||||
* @param string $reg_id |
||||
* @param int $patient_property |
||||
* @param int $settle_type |
||||
* @return string |
||||
*/ |
||||
public static function getRegistrationPreSettleInfoKey(string $open_id, string $reg_id, int $patient_property, int $settle_type): string |
||||
{ |
||||
return "registration.medical.pre_settle_info.{$open_id}.{$reg_id}.{$patient_property}.{$settle_type}"; |
||||
} |
||||
|
||||
/** |
||||
* 获取缴费预结算详情缓存键名 |
||||
* @param string $open_id |
||||
* @param string $record_id |
||||
* @param int $settle_type 0 医保统筹 1 自费支付 |
||||
* @return string |
||||
*/ |
||||
public static function getOutpatientPrescriptionPreSettleInfoKey(string $open_id, string $record_id, int $settle_type): string |
||||
{ |
||||
return "outpatient.medical.pre_settle_info.{$open_id}.{$record_id}.{$settle_type}"; |
||||
} |
||||
} |
@ -1,52 +0,0 @@ |
||||
<?php |
||||
declare(strict_types=1); |
||||
|
||||
namespace App\Utils\Traits; |
||||
|
||||
trait BuildCacheKeyName |
||||
{ |
||||
/** |
||||
* 获取 检验报告列表 缓存建名 |
||||
* @param string $open_id |
||||
* @param string $patient_id |
||||
* @return string |
||||
*/ |
||||
public function getInspectReportListsKey(string $open_id, string $patient_id): string |
||||
{ |
||||
return 'report.inspect.'. $open_id. '.'. $patient_id; |
||||
} |
||||
|
||||
/** |
||||
* 获取 门诊待缴费列表 缓存键名 |
||||
* @param string $open_id |
||||
* @param string $patient_id |
||||
* @return string |
||||
*/ |
||||
public function getOutpatientPendingListsKey(string $open_id, string $patient_id): string |
||||
{ |
||||
return 'outpatient.pending.'. $open_id. '.'. $patient_id; |
||||
} |
||||
|
||||
/** |
||||
* 获取 门诊待缴费详情 缓存键名 |
||||
* @param string $open_id |
||||
* @param string $patient_id |
||||
* @param string $serial_no |
||||
* @return string |
||||
*/ |
||||
public function getOutpatientPendingDetailsKey(string $open_id, string $patient_id, string $serial_no): string |
||||
{ |
||||
return 'outpatient.pending.'. $open_id.'.'. $patient_id. '.'. $serial_no; |
||||
} |
||||
|
||||
/** |
||||
* 获取用户 免密授权 缓存键名 |
||||
* @param string $open_id |
||||
* @param string $patient_id |
||||
* @return string |
||||
*/ |
||||
public function getUserMedicalInsuranceAuthKey(string $open_id, string $patient_id): string |
||||
{ |
||||
return 'medical.user.info.'. $open_id.'.'. $patient_id; |
||||
} |
||||
} |
@ -0,0 +1,25 @@ |
||||
<?php |
||||
declare(strict_types=1); |
||||
|
||||
namespace App\Utils\Transfer\HisMedicalHttpClient; |
||||
|
||||
use App\Utils\Transfer\HttpTransferAbstract; |
||||
|
||||
class ClientFactory |
||||
{ |
||||
|
||||
/** |
||||
* Get Client Transfer Class |
||||
* @param string $name |
||||
* @return HttpTransferAbstract |
||||
*/ |
||||
public static function getClientTransfer(string $name): HttpTransferAbstract |
||||
{ |
||||
$is_mock = config('hisservice.'. $name. '.is_mock'); |
||||
|
||||
return match ($is_mock) { |
||||
false => new ClientHttpTransfer($name), |
||||
true => new ClientMockHttpTransfer($name), |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,62 @@ |
||||
<?php |
||||
declare(strict_types=1); |
||||
|
||||
namespace App\Utils\Transfer\HisMedicalHttpClient; |
||||
|
||||
use App\Utils\Transfer\HttpTransferAbstract; |
||||
use Exception; |
||||
use JsonException; |
||||
use Psr\Http\Message\ResponseInterface; |
||||
|
||||
class ClientHttpTransfer extends HttpTransferAbstract |
||||
{ |
||||
|
||||
/** |
||||
* MediWayTransfer Construct |
||||
* @param string $his_name |
||||
*/ |
||||
public function __construct(string $his_name = "") |
||||
{ |
||||
parent::__construct($his_name); |
||||
} |
||||
|
||||
/** |
||||
* 初始化 |
||||
*/ |
||||
public function initialize(): void |
||||
{ |
||||
parent::initialize(); |
||||
} |
||||
|
||||
/** |
||||
* 设置客户端选项 |
||||
* @return array |
||||
*/ |
||||
public function ClientHeaders(): array |
||||
{ |
||||
return []; |
||||
} |
||||
|
||||
/** |
||||
* 响应格式化 |
||||
* @return mixed |
||||
*/ |
||||
public function responseFormat(): mixed |
||||
{ |
||||
$response = $this->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 |
||||
]; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,128 @@ |
||||
<?php |
||||
declare(strict_types=1); |
||||
|
||||
namespace App\Utils\Transfer\HisMedicalHttpClient; |
||||
|
||||
use App\Exceptions\GeneralException; |
||||
use App\Utils\Transfer\HttpTransferAbstract; |
||||
use Exception; |
||||
use Random\RandomException; |
||||
|
||||
class ClientMockHttpTransfer extends HttpTransferAbstract |
||||
{ |
||||
/** |
||||
* ClientMockTransfer Construct |
||||
* @param string $his_name |
||||
*/ |
||||
public function __construct(string $his_name = "") |
||||
{ |
||||
parent::__construct($his_name); |
||||
} |
||||
|
||||
/** |
||||
* 设置客户端选项 |
||||
* @return array |
||||
*/ |
||||
public function clientHeaders(): array |
||||
{ |
||||
return []; |
||||
} |
||||
|
||||
/** |
||||
* @param string $class_name |
||||
* @return $this |
||||
*/ |
||||
public function transferClass(string $class_name): static |
||||
{ |
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* @throws GeneralException |
||||
*/ |
||||
public function transferMethod(string $method, string $request_name, array $request_data = []): self |
||||
{ |
||||
// 使用 match 替代 switch |
||||
return match ($request_name) { |
||||
'GetPrecalculatedFeeGh' => $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; |
||||
} |
||||
} |
@ -0,0 +1,113 @@ |
||||
<?php |
||||
|
||||
namespace UnifyPayment; |
||||
|
||||
use Monolog\Formatter\LineFormatter; |
||||
use Monolog\Handler\RotatingFileHandler; |
||||
use Monolog\Logger; |
||||
|
||||
return [ |
||||
|
||||
/* |
||||
|-------------------------------------------------------------------------- |
||||
| Request Platform Config |
||||
|-------------------------------------------------------------------------- |
||||
| domain:支付平台地址。 |
||||
| apiSecret: 应用秘钥 |
||||
| |
||||
*/ |
||||
'domain' => '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, |
||||
] |
||||
]; |
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,49 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace UnifyPayment\Mock; |
||||
|
||||
class CreateMedicalInsuranceOrderHandler extends ResponseHandler |
||||
{ |
||||
/** |
||||
* @return array |
||||
*/ |
||||
public function success(): array |
||||
{ |
||||
$channelId = 'FD00' . $this->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) |
||||
]; |
||||
} |
||||
} |
Loading…
Reference in new issue