feat: 挂号,缴费费用预结算,检查单查询,定点

fix: 修复港澳台社保卡号判断,预约挂号提醒模板
medical
Rmiku 6 days ago
parent d131a3702a
commit a1ed3ea2dc
  1. 4
      app/Console/Commands/SendAppointmentReminders.php
  2. 54
      app/Dictionary/Medical/OrderType.php
  3. 28
      app/Dictionary/Medical/PatientProperty.php
  4. 30
      app/Dictionary/Medical/PayType.php
  5. 30
      app/Dictionary/Medical/PreSettleStatus.php
  6. 27
      app/Dictionary/Medical/SettleType.php
  7. 35
      app/Dictionary/Report/ImageType.php
  8. 110
      app/Http/Controllers/Outpatient/MedicalController.php
  9. 14
      app/Http/Controllers/Patient/PatientController.php
  10. 77
      app/Http/Controllers/Registration/MedicalController.php
  11. 2
      app/Http/Controllers/Registration/RegisterController.php
  12. 26
      app/Http/Controllers/Report/InspectController.php
  13. 55
      app/Http/Controllers/Test/TestController.php
  14. 81
      app/Http/Logics/Notify/NotifyLogic.php
  15. 497
      app/Http/Logics/Outpatient/MedicalLogic.php
  16. 9
      app/Http/Logics/Outpatient/PaymentLogic.php
  17. 9
      app/Http/Logics/Outpatient/PendingLogic.php
  18. 7
      app/Http/Logics/Outpatient/RecordLogic.php
  19. 32
      app/Http/Logics/Patient/PatientLogic.php
  20. 504
      app/Http/Logics/Registration/MedicalLogic.php
  21. 29
      app/Http/Logics/Report/InspectLogic.php
  22. 1
      app/Http/Requests/Patient/BindPatientRequest.php
  23. 77
      app/Http/Requests/Registration/RegisterPreSettleRequest.php
  24. 37
      app/Http/Resources/Outpatient/Medical/PrescriptionPreSettleResource.php
  25. 37
      app/Http/Resources/Registration/Medical/RegisterPreSettleResource.php
  26. 6
      app/Jobs/SendWeChatMessageJob.php
  27. 2
      app/Models/Order.php
  28. 6
      app/Providers/AppServiceProvider.php
  29. 3
      app/Services/HealthRecordAuth/Client.php
  30. 23
      app/Services/HisHttp/Client.php
  31. 293
      app/Services/HisMedicalHttp/Client.php
  32. 62
      app/Services/MedicalAuth/Client.php
  33. 3
      app/Utils/Helpers.php
  34. 101
      app/Utils/Statics/BuildCacheKeyName.php
  35. 52
      app/Utils/Traits/BuildCacheKeyName.php
  36. 2
      app/Utils/Traits/SendSubscribeMessage.php
  37. 1
      app/Utils/Traits/UniversalEncryption.php
  38. 25
      app/Utils/Transfer/HisMedicalHttpClient/ClientFactory.php
  39. 62
      app/Utils/Transfer/HisMedicalHttpClient/ClientHttpTransfer.php
  40. 128
      app/Utils/Transfer/HisMedicalHttpClient/ClientMockHttpTransfer.php
  41. 4
      config/custom.php
  42. 19
      config/hisservice.php
  43. 13
      config/logging.php
  44. 10
      config/unify.php
  45. 113
      config/unifytest.php
  46. 24
      config/wechat.php
  47. 2
      packagist/unify_payment/src/Cores/BasicClient.php
  48. 1074
      packagist/unify_payment/src/Cores/Struct/CreateMedicalInsuranceOrder.php
  49. 49
      packagist/unify_payment/src/Mock/CreateMedicalInsuranceOrderHandler.php
  50. 21
      packagist/unify_payment/src/Modules/Pay/MiniProgramClient.php
  51. 22
      packagist/unify_payment/src/Modules/Pay/OfficialAccountClient.php
  52. 42
      packagist/unify_payment/tests/PayTest.php
  53. 21
      routes/api.php

@ -41,8 +41,8 @@ class SendAppointmentReminders extends Command
$this->info('Starting to send appointment reminders...'); $this->info('Starting to send appointment reminders...');
// 查询即将到期的预约记录(8小时后的预约) // 查询即将到期的预约记录(8小时后的预约)
$appointments = RegistrationRecord::where('visit_date', now()->toDate()) $appointments = RegistrationRecord::where('visit_date', now()->toDateString())
->where('begin_time', now()->subHours(8)) ->where('begin_time', '>=', now()->addHours(8)->toTimeString())
->where('reminder_sent', 0) ->where('reminder_sent', 0)
->get(); ->get();

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

@ -122,4 +122,18 @@ class PatientController
return jsonResponse(Response::HTTP_OK, 'success', ['phone_info' => $phone_info]); return jsonResponse(Response::HTTP_OK, 'success', ['phone_info' => $phone_info]);
} }
/**
* 医保定点签约
* @param Request $request
* @param string $patient_id
* @return JsonResponse
* @throws GeneralException
*/
public function designated(Request $request, string $patient_id): JsonResponse
{
$this->patient_logic->medicalDesignated($patient_id);
return jsonResponse(Response::HTTP_OK, '签约成功!');
}
} }

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

@ -24,7 +24,7 @@ class RegisterController
} }
/** /**
* 获取挂号记录列表 * 挂号
* @param RegisterRequest $request * @param RegisterRequest $request
* @param string $patient_id 此处为 patient_number * @param string $patient_id 此处为 patient_number
* @return JsonResponse * @return JsonResponse

@ -4,12 +4,13 @@ declare(strict_types = 1);
namespace App\Http\Controllers\Report; namespace App\Http\Controllers\Report;
use App\Exceptions\GeneralException; use App\Exceptions\GeneralException;
use App\Http\Logics\Registration\RecordLogic;
use App\Http\Logics\Report\InspectLogic; use App\Http\Logics\Report\InspectLogic;
use App\Http\Resources\Report\Inspect\DetailsResource; use App\Http\Resources\Report\Inspect\DetailsResource;
use App\Http\Resources\Report\Inspect\ListsResource; use App\Http\Resources\Report\Inspect\ListsResource;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
class InspectController class InspectController
@ -24,6 +25,29 @@ class InspectController
$this->inspect_logic = new InspectLogic(); $this->inspect_logic = new InspectLogic();
} }
/**
* 获取报告提示
* @return JsonResponse
*/
public function tips()
{
if (Redis::exists('report.tips')) {
$tips_lists = Redis::get('report.tips');
$tips_lists = json_decode($tips_lists, true);
} else {
$tips_lists = DB::connection('mysql_admin')->table('department_tips')
->select(DB::raw('ABS(dept_id) as type_id'), 'title', 'message')
->whereIn('dept_id', [-2, -3])
->where('is_enable', 1)
->get()
->toArray();
Redis::setex('report.tips', 4 * 3600, json_encode($tips_lists, JSON_UNESCAPED_UNICODE));
}
return jsonResponse(Response::HTTP_OK, 'success', $tips_lists);
}
/** /**
* 获取检查报告列表 * 获取检查报告列表
* @param Request $request * @param Request $request

@ -5,24 +5,67 @@ namespace App\Http\Controllers\Test;
use App\Dictionary\WeChat\MiniProgram\OpenApi; use App\Dictionary\WeChat\MiniProgram\OpenApi;
use App\Models\Order; use App\Models\Order;
use App\Models\RegistrationRecord;
use App\Utils\Traits\HttpRequest; use App\Utils\Traits\HttpRequest;
use App\Utils\Traits\SendSubscribeMessage; use App\Utils\Traits\SendSubscribeMessage;
use App\Utils\Traits\UniversalEncryption;
use Illuminate\Support\Facades\Redis; use Illuminate\Support\Facades\Redis;
use UnifyPayment\Cores\Struct\RefundOrder; use UnifyPayment\Cores\Struct\RefundOrder;
use UnifyPayment\Unify; use UnifyPayment\Unify;
use function Symfony\Component\Translation\t;
class TestController class TestController
{ {
use HttpRequest;
use UniversalEncryption;
use SendSubscribeMessage; use SendSubscribeMessage;
public function test(): void public function test(): void
{ {
$mini = getWeChatMiniProgramApp(); // $json = [
$response = $mini->getClient()->postJson(OpenApi::CREATE_QR_CODE->value, [ // 'ApplicationId' => '',
'path' => 'pagesA/register/notice?ed=show', // 'ApplicationSecret' => '',
]); // 'Parameter' => [
dd($response->toArray()); // 'BeginDate' => '2025-04-27',
//dd(json_decode('', true)); // 'EndDate' => '2025-04-28',
// 'CardType' => '01',
// 'CardNo' => '65284656',
// ]
// ];
$json = [
"ApplicationId" => "",
"ApplicationSecret"=> "",
"Parameter"=> [
"HospitalId" => "",
"PatientId"=> "2226817",
"PatientName"=> "李欣茹",
"PayAuthNo"=> "",
"UldLatlnt"=> "",
"IsInsSelfPay"=> "0",
"YbPayType"=> "2",
"FeeRecords"=> [
[
"FeeNo" => "-210360897",
"FeeTypeCode" => "51"
]
]
]
];
$start_time = microtime(true);
// $result = $this->request('POST', 'http://192.168.61.44:8010/api/his/GetPatientInfo', [
// 'json' => $json
// ]);
$end_time = microtime(true);
$use_time = sprintf("%.6f", ($start_time - $end_time));
recordLog('MedicalHis', json_encode([
'json' => $json,
'result' => $result,
'use_time' => $use_time
], JSON_UNESCAPED_UNICODE));
// dd($result);
// $response = '1'; // $response = '1';
// $order = Order::where('order_id', 'WXM20250207151636790')->first(); // $order = Order::where('order_id', 'WXM20250207151636790')->first();
// $record = $order->outpatientPaymentRecord; // $record = $order->outpatientPaymentRecord;

@ -6,6 +6,7 @@ namespace App\Http\Logics\Notify;
use App\Dictionary\Order\NotifyStatus; use App\Dictionary\Order\NotifyStatus;
use App\Dictionary\Order\PayType; use App\Dictionary\Order\PayType;
use App\Dictionary\Medical\PayType as MedicalPayType;
use App\Dictionary\Order\SourceId; use App\Dictionary\Order\SourceId;
use App\Dictionary\Order\Status; use App\Dictionary\Order\Status;
use App\Dictionary\Order\Type; use App\Dictionary\Order\Type;
@ -27,8 +28,6 @@ use UnifyPayment\Cores\Struct\ConfirmOrderForEx;
use UnifyPayment\Cores\Struct\QueryOrder; use UnifyPayment\Cores\Struct\QueryOrder;
use UnifyPayment\Cores\Struct\RefundOrder; use UnifyPayment\Cores\Struct\RefundOrder;
use UnifyPayment\Mock\ConfirmOrderForExHandler; use UnifyPayment\Mock\ConfirmOrderForExHandler;
use UnifyPayment\Mock\QueryOrderHandler;
use UnifyPayment\Mock\RefundOrderHandler;
use UnifyPayment\Unify; use UnifyPayment\Unify;
class NotifyLogic class NotifyLogic
@ -39,6 +38,8 @@ class NotifyLogic
protected Client $his_client; protected Client $his_client;
protected MedicalClient $his_medical_client;
protected PaymentApplication $payment_app; protected PaymentApplication $payment_app;
protected PatientModel $patient_model; protected PatientModel $patient_model;
@ -104,7 +105,12 @@ class NotifyLogic
$this->registrationOrderHandle($order_info, $notify); $this->registrationOrderHandle($order_info, $notify);
break; break;
case Type::OUTPATIENT_PAYMENT->value: case Type::OUTPATIENT_PAYMENT->value:
$this->outpatientOrderHandle($order_info, $notify); if ($order_info->pay_type === PayType::MEDICAL_INSURANCE_PAY) {
// 医保支付
$this->outpatientMedicalOrderHandle($order_info, $notify);
} else {
$this->outpatientOrderHandle($order_info, $notify);
}
break; break;
default: default:
break; break;
@ -238,6 +244,75 @@ class NotifyLogic
} }
} }
/**
* 门诊缴费医保订单操作
* @param OrderModel $order_info
* @param Message $notify
* @return void
* @throws GeneralException
*/
public function outpatientMedicalOrderHandle(OrderModel $order_info, Message $notify): void
{
// 缴费确认
$record = $order_info->outpatientPaymentRecord;
$extra_info = json_decode($record->extra_info, true);
$time_end = date('Y-m-d H:i:s', strtotime($notify['time_end']));
$totalAmount = $extra_info['pre_settle_info']['TotalAmount']; // 总金额
$medicare_amount = bcdiv($notify['insurance_fund_fee'], '100'); // 减免金额
$pay_amount = bcdiv($notify['cash_fee'], '100'); // 现金金额
$account_amount = bcdiv($notify['insurance_self_fee'], '100'); // 个账支付金额
$fee_record = [];
foreach ($extra_info['']['feeRecords']['feeRecord'] as $v) {
$fee_record[] = [
'FeeNo' => $v['feeNo'],
'FeeTypeCode' => $v['feeTypeCode'],
];
}
$data = [
$order_info->patient_id,
$order_info->patient_name,
$order_info->order_id,
$extra_info['pre_settle_info']['PrecalId'],
$time_end,
$totalAmount,
$medicare_amount,
$pay_amount,
$account_amount,
MedicalPayType::ONLINE_MEDICARE,
'03',
$fee_record
];
$response = $this->his_client->confirmMedicalOutpatient(... $data);
$this->info('医保缴费订单出入参:'.$order_info->order_id, [$data, $response]);
// 保存返回信息
if (isset($response['success']) && $response['success'] === true) {
// 成功流程
$order_info->orderConfirm($order_info->order_id, $response['response']['ResultId'], $response['response']);
// 支付平台业务确认
$this->unifyConfirm($notify['out_trade_no'], $response['response']['ResultId'], $notify['openid'], $notify['transaction_id']);
// 推送成功
$this->sendOutpatientPaymentSuccessMessage($order_info);
} else if (isset($response['success']) && $response['success'] === false && $response['msg'] !== '服务异常') {
// 失败流程
$this->handleOrderReverse($order_info, $response['msg']);
// 推送失败
$this->sendOutpatientPaymentFailureMessage($order_info);
} else {
// 异常流程
$order_info->abnormalOrderOpera($order_info->id);
// 推送异常
$this->sendOutpatientPaymentFailureMessage($order_info);
}
}
/** /**
* 退款 * 退款
* @param string $order_id * @param string $order_id

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

@ -8,11 +8,10 @@ use App\Dictionary\Order\SourceId;
use App\Dictionary\Order\Type; use App\Dictionary\Order\Type;
use App\Dictionary\Patient\CardType; use App\Dictionary\Patient\CardType;
use App\Exceptions\GeneralException; use App\Exceptions\GeneralException;
use App\Http\Resources\Outpatient\Pending\PendingListsResource;
use App\Models\Order; use App\Models\Order;
use App\Models\Patient; use App\Models\Patient;
use App\Services\HisHttp\Client; use App\Services\HisHttp\Client;
use App\Utils\Traits\BuildCacheKeyName; use App\Utils\Statics\BuildCacheKeyName;
use App\Utils\Traits\Logger; use App\Utils\Traits\Logger;
use App\Utils\Traits\MiniProgramAuth; use App\Utils\Traits\MiniProgramAuth;
use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\AuthenticationException;
@ -22,14 +21,12 @@ use Symfony\Component\HttpFoundation\Response;
use UnifyPayment\Cores\Exceptions\InvalidConfigException; use UnifyPayment\Cores\Exceptions\InvalidConfigException;
use UnifyPayment\Cores\Exceptions\RuntimeException; use UnifyPayment\Cores\Exceptions\RuntimeException;
use UnifyPayment\Cores\Struct\CreateOrder; use UnifyPayment\Cores\Struct\CreateOrder;
use UnifyPayment\Mock\CreateOrderHandler;
use UnifyPayment\Unify; use UnifyPayment\Unify;
class PaymentLogic class PaymentLogic
{ {
use Logger; use Logger;
use MiniProgramAuth; use MiniProgramAuth;
use BuildCacheKeyName;
private Client $his_client; private Client $his_client;
@ -119,8 +116,8 @@ class PaymentLogic
protected function getPendingPrescriptionDetails(string $patient_id, string $serial_no, string $prescription_ids): array protected function getPendingPrescriptionDetails(string $patient_id, string $serial_no, string $prescription_ids): array
{ {
// 缓存键值 // 缓存键值
$pending_lists_cache_key = $this->getOutpatientPendingListsKey($this->open_id, $patient_id); $pending_lists_cache_key = BuildCacheKeyName::getOutpatientPendingListsKey($this->open_id, $patient_id);
$pending_details_cache_key = $this->getOutpatientPendingDetailsKey($this->open_id, $patient_id, $serial_no); $pending_details_cache_key = BuildCacheKeyName::getOutpatientPendingDetailsKey($this->open_id, $patient_id, $serial_no);
// 获取缓存 // 获取缓存
if (Redis::exists($pending_lists_cache_key)) { if (Redis::exists($pending_lists_cache_key)) {

@ -6,7 +6,7 @@ namespace App\Http\Logics\Outpatient;
use App\Exceptions\GeneralException; use App\Exceptions\GeneralException;
use App\Models\Patient; use App\Models\Patient;
use App\Services\HisHttp\Client; use App\Services\HisHttp\Client;
use App\Utils\Traits\BuildCacheKeyName; use App\Utils\Statics\BuildCacheKeyName;
use App\Utils\Traits\Logger; use App\Utils\Traits\Logger;
use App\Utils\Traits\MiniProgramAuth; use App\Utils\Traits\MiniProgramAuth;
use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\AuthenticationException;
@ -18,7 +18,6 @@ class PendingLogic
{ {
use Logger; use Logger;
use MiniProgramAuth; use MiniProgramAuth;
use BuildCacheKeyName;
private Client $his_client; private Client $his_client;
@ -58,7 +57,7 @@ class PendingLogic
} }
// 缓存2小时 // 缓存2小时
Redis::setnx($this->getOutpatientPendingListsKey($this->open_id, $patient_id), 7200, json_encode($response, JSON_UNESCAPED_UNICODE)); Redis::setex(BuildCacheKeyName::getOutpatientPendingListsKey($this->open_id, $patient_id), 7200, json_encode($response, JSON_UNESCAPED_UNICODE));
return $response; return $response;
} }
@ -85,7 +84,7 @@ class PendingLogic
} }
// 缓存2小时 // 缓存2小时
Redis::setnx($this->getOutpatientPendingDetailsKey($this->open_id, $patient_id, $serial_no), 7200, json_encode($response, JSON_UNESCAPED_UNICODE)); Redis::setex(BuildCacheKeyName::getOutpatientPendingDetailsKey($this->open_id, $patient_id, $serial_no), 7200, json_encode($response, JSON_UNESCAPED_UNICODE));
return $response; return $response;
} }
@ -99,7 +98,7 @@ class PendingLogic
protected function getCachePendingLists(string $patient_id, string $serial_no): mixed protected function getCachePendingLists(string $patient_id, string $serial_no): mixed
{ {
// 缓存键值 // 缓存键值
$cache_key = $this->getOutpatientPendingListsKey($this->open_id, $patient_id); $cache_key = BuildCacheKeyName::getOutpatientPendingListsKey($this->open_id, $patient_id);
// 获取缓存 // 获取缓存
if (Redis::exists($cache_key)) { if (Redis::exists($cache_key)) {

@ -6,7 +6,7 @@ namespace App\Http\Logics\Outpatient;
use App\Exceptions\GeneralException; use App\Exceptions\GeneralException;
use App\Models\Patient; use App\Models\Patient;
use App\Services\HisHttp\Client; use App\Services\HisHttp\Client;
use App\Utils\Traits\BuildCacheKeyName; use App\Utils\Statics\BuildCacheKeyName;
use App\Utils\Traits\Logger; use App\Utils\Traits\Logger;
use App\Utils\Traits\MiniProgramAuth; use App\Utils\Traits\MiniProgramAuth;
use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\AuthenticationException;
@ -17,7 +17,6 @@ class RecordLogic
{ {
use Logger; use Logger;
use MiniProgramAuth; use MiniProgramAuth;
use BuildCacheKeyName;
private Patient $patient_model; private Patient $patient_model;
@ -66,7 +65,7 @@ class RecordLogic
} }
// 缓存2小时 // 缓存2小时
$cache_key = $this->getOutpatientPendingListsKey($this->open_id, $patient_id); $cache_key = BuildCacheKeyName::getOutpatientPendingListsKey($this->open_id, $patient_id);
Redis::setex($cache_key, 2 * 60 * 60, json_encode($response, JSON_UNESCAPED_UNICODE)); Redis::setex($cache_key, 2 * 60 * 60, json_encode($response, JSON_UNESCAPED_UNICODE));
return $response; return $response;
} }
@ -103,7 +102,7 @@ class RecordLogic
*/ */
protected function getCacheRecordInfo(string $patient_id, string $serial_no): mixed protected function getCacheRecordInfo(string $patient_id, string $serial_no): mixed
{ {
$cache_key = $this->getOutpatientPendingDetailsKey($this->open_id, $patient_id, $serial_no); $cache_key = BuildCacheKeyName::getOutpatientPendingDetailsKey($this->open_id, $patient_id, $serial_no);
$record_info = Redis::get($cache_key); $record_info = Redis::get($cache_key);
if (empty($record_info)) { if (empty($record_info)) {

@ -9,6 +9,7 @@ use App\Dictionary\WeChat\MiniProgram\OpenApi;
use App\Exceptions\GeneralException; use App\Exceptions\GeneralException;
use App\Models\Patient; use App\Models\Patient;
use App\Services\HisHttp\Client; use App\Services\HisHttp\Client;
use App\Services\HisMedicalHttp\Client as MedicalClient;
use App\Utils\Traits\Logger; use App\Utils\Traits\Logger;
use App\Utils\Traits\MiniProgramAuth; use App\Utils\Traits\MiniProgramAuth;
use App\Utils\Traits\SendSubscribeMessage; use App\Utils\Traits\SendSubscribeMessage;
@ -27,6 +28,8 @@ class PatientLogic
private Client $his_client; private Client $his_client;
private MedicalClient $his_medical_client;
private Patient $patient_model; private Patient $patient_model;
/** /**
@ -38,6 +41,7 @@ class PatientLogic
$this->authInitialize(); $this->authInitialize();
$this->patient_model = new Patient(); $this->patient_model = new Patient();
$this->his_client = app('HisHttpService'); $this->his_client = app('HisHttpService');
$this->his_medical_client = app('HisMedicalHttpClient');
} }
/** /**
@ -299,4 +303,32 @@ class PatientLogic
throw new GeneralException('获取手机号码失败,请稍后再试!', Response::HTTP_BAD_REQUEST); throw new GeneralException('获取手机号码失败,请稍后再试!', Response::HTTP_BAD_REQUEST);
} }
} }
/**
* 医保定点签约
* @param string $patient_number
* @return bool
* @throws GeneralException
*/
public function medicalDesignated(string $patient_number): bool
{
$info = $this->patient_model->getBindPatientInfoByPatientNumber($this->open_id, $patient_number);
if (empty($info)) {
throw new GeneralException('该就诊卡不存在!');
}
// 获取患者信息
$response = $this->his_client->getPatientInfo($info['patient_number'], CardType::OUTPATIENT_NO, '');
if (!isset($response['success']) || !$response['success']) {
throw new GeneralException($response['msg'] ?? '找不到该就诊卡!', Response::HTTP_SERVICE_UNAVAILABLE);
}
$patient_info = &$response['response'];
$result = $this->his_medical_client->medicalDesignated('02', $patient_info['iDCardNo']);
if (!isset($result['ResultCode']) || $result['ResultCode'] !== '1') {
throw new GeneralException($result['ErrorMsg'] ?? '医保定点签约失败,请稍后再试!', Response::HTTP_SERVICE_UNAVAILABLE);
}
return true;
}
} }

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

@ -8,12 +8,13 @@ use App\Dictionary\Order\SourceId;
use App\Dictionary\Order\Status; use App\Dictionary\Order\Status;
use App\Dictionary\Order\Type; use App\Dictionary\Order\Type;
use App\Dictionary\Patient\CardType; use App\Dictionary\Patient\CardType;
use App\Dictionary\Report\ImageType;
use App\Exceptions\GeneralException; use App\Exceptions\GeneralException;
use App\Models\Order; use App\Models\Order;
use App\Models\Patient; use App\Models\Patient;
use App\Models\RegistrationRecord; use App\Models\RegistrationRecord;
use App\Services\HisHttp\Client; use App\Services\HisHttp\Client;
use App\Utils\Traits\BuildCacheKeyName; use App\Utils\Statics\BuildCacheKeyName;
use App\Utils\Traits\Logger; use App\Utils\Traits\Logger;
use App\Utils\Traits\MiniProgramAuth; use App\Utils\Traits\MiniProgramAuth;
use App\Utils\Traits\SendSubscribeMessage; use App\Utils\Traits\SendSubscribeMessage;
@ -29,7 +30,6 @@ class InspectLogic
{ {
use Logger; use Logger;
use MiniProgramAuth; use MiniProgramAuth;
use BuildCacheKeyName;
private Client $his_client; private Client $his_client;
@ -67,7 +67,7 @@ class InspectLogic
throw new GeneralException($response['msg'] ?? '找不到该就诊卡!', Response::HTTP_SERVICE_UNAVAILABLE); throw new GeneralException($response['msg'] ?? '找不到该就诊卡!', Response::HTTP_SERVICE_UNAVAILABLE);
} }
Redis::setex($this->getInspectReportListsKey($this->open_id, $patient_info['patient_id']), 3600, json_encode($response['response'], JSON_UNESCAPED_UNICODE)); Redis::setex(BuildCacheKeyName::getInspectReportListsKey($this->open_id, $patient_info['patient_id']), 3600, json_encode($response['response'], JSON_UNESCAPED_UNICODE));
return $response['response']; return $response['response'];
} }
@ -82,7 +82,10 @@ class InspectLogic
{ {
$patient_info = $this->getPatientInfo($this->open_id, $patient_number); $patient_info = $this->getPatientInfo($this->open_id, $patient_number);
$report_lists_cache_key = $this->getInspectReportListsKey($this->open_id, $patient_info['patient_id']); // 缓存键名
$report_lists_cache_key = BuildCacheKeyName::getInspectReportListsKey($this->open_id, $patient_info['patient_id']);
$report_images_cache_key = BuildCacheKeyName::getInspectReportDetailsImagesKey($this->open_id, $patient_info['patient_id'], $serial_no);
if (!Redis::exists($report_lists_cache_key)) { if (!Redis::exists($report_lists_cache_key)) {
throw new GeneralException('找不到报告详情,请重新再试!', Response::HTTP_BAD_REQUEST); throw new GeneralException('找不到报告详情,请重新再试!', Response::HTTP_BAD_REQUEST);
} }
@ -102,6 +105,24 @@ class InspectLogic
throw new GeneralException('找不到报告详情,请重新再试!', Response::HTTP_BAD_REQUEST); throw new GeneralException('找不到报告详情,请重新再试!', Response::HTTP_BAD_REQUEST);
} }
$images = [];
if (!Redis::exists($report_images_cache_key)) {
$image_response = $this->his_client->getInspectReportImages(ImageType::IMAGE,$serial_no);
if (isset($image_response['success']) && $image_response['success']) {
$images = $image_response['response']['fileBase64'];
}
// 缓存图片base64进去
if (!empty($images)) {
Redis::setex($report_images_cache_key, 3600, json_encode($images, JSON_UNESCAPED_UNICODE));
}
} else {
$images = Redis::get($report_images_cache_key);
$images = json_decode($images, true);
}
$info['pacs'] = $images;
return $info; return $info;
} }

@ -3,6 +3,7 @@
namespace App\Http\Requests\Patient; namespace App\Http\Requests\Patient;
use App\Dictionary\Patient\IdentifyCardType; use App\Dictionary\Patient\IdentifyCardType;
use Dflydev\DotAccessData\Data;
use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;

@ -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'] ?? ''
];
}
}

@ -152,6 +152,12 @@ class SendWeChatMessageJob implements ShouldQueue, ShouldBeUnique
*/ */
public function failed(string $fail_reason): void public function failed(string $fail_reason): void
{ {
// 错误信息截断
if (!empty($fail_reason) && strlen($fail_reason) >= 180) {
$fail_reason = mb_substr($fail_reason, 0, 180);
$fail_reason .= '...';
}
$this->message->status = MessageStatus::FAILURE->value; $this->message->status = MessageStatus::FAILURE->value;
$this->message->fail_reason = $fail_reason; $this->message->fail_reason = $fail_reason;
$this->message->save(); $this->message->save();

@ -112,7 +112,7 @@ class Order extends Model
/** /**
* 获取订单ID * 获取订单ID
* @param PayType $pay_type 支付类型 * @param PayType $pay_type 支付类型
* @param string $user W 微信 D 大机器 X 小机器 H his * @param string $user W 微信 M 小程序 D 大机器 X 小机器 H his
* @return string * @return string
*/ */
public function getOrderId(PayType $pay_type, string $user = 'D'): string public function getOrderId(PayType $pay_type, string $user = 'D'): string

@ -4,6 +4,7 @@ namespace App\Providers;
use App\Services\HisSoap\Client as HisSoapClient; use App\Services\HisSoap\Client as HisSoapClient;
use App\Services\HisHttp\Client as HisHttpClient; use App\Services\HisHttp\Client as HisHttpClient;
use App\Services\HisMedicalHttp\Client as HisMedicalHttpClient;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Illuminate\Routing\UrlGenerator; use Illuminate\Routing\UrlGenerator;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
@ -64,5 +65,10 @@ class AppServiceProvider extends ServiceProvider
$this->app->singleton('HisHttpService', function () { $this->app->singleton('HisHttpService', function () {
return new HisHttpClient(); return new HisHttpClient();
}); });
// His平台服务 - medical http
$this->app->singleton('HisMedicalHttpClient', function () {
return new HisMedicalHttpClient();
});
} }
} }

@ -7,6 +7,9 @@ use App\Utils\SM4;
use App\Utils\Traits\HttpRequest; use App\Utils\Traits\HttpRequest;
use Exception; use Exception;
/**
* 广东省健康档案授权
*/
class Client class Client
{ {
use HttpRequest; use HttpRequest;

@ -3,8 +3,11 @@ declare(strict_types=1);
namespace App\Services\HisHttp; namespace App\Services\HisHttp;
use App\Dictionary\Medical\PayType as MedicalPayType;
use App\Dictionary\Medical\SettleType;
use App\Dictionary\Patient\CardType; use App\Dictionary\Patient\CardType;
use App\Dictionary\Patient\Sex; use App\Dictionary\Patient\Sex;
use App\Dictionary\Report\ImageType;
use App\Exceptions\GeneralException; use App\Exceptions\GeneralException;
use App\Utils\Traits\Logger; use App\Utils\Traits\Logger;
use App\Utils\Transfer\HisHttpClient\ClientFactory; use App\Utils\Transfer\HisHttpClient\ClientFactory;
@ -24,7 +27,7 @@ class Client
*/ */
public function __construct() public function __construct()
{ {
$his_name = 'his_api'; $his_name = 'his_http';
$this->service = ClientFactory::getClientTransfer($his_name); $this->service = ClientFactory::getClientTransfer($his_name);
$this->setChannel($his_name); $this->setChannel($his_name);
} }
@ -675,5 +678,23 @@ class Client
]); ]);
} }
/**
* 获取检查报告影像结果
* @param ImageType $type 选择查询的类型, styid=查询胶片id,入参(studyid); img=获取胶片,入参(胶片id); img64=获取胶片base64编码,入参(胶片id); pdf=获取报告pdf文件,入参(studyid); pdf64=获取报告pdf的base64编码,入参(studyid)
* @param string $report_id 根据类型字段选择入参 styid,pdf,pdf64入参为studyid;img,img64为胶片id;
* @return mixed
* @throws GeneralException
*/
public function getInspectReportImages(ImageType $type, string $report_id): mixed
{
return $this->requestHandle('POST', 'QueryImageReport', [
'json' => [
'fileType' => $type,
'reportId' => $report_id,
... $this->commonRequestData()
]
]);
}
// Inspect/Check Module End // Inspect/Check Module End
} }

@ -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()
]
]);
}
}

@ -5,10 +5,8 @@ declare(strict_types=1);
namespace App\Services\MedicalAuth; namespace App\Services\MedicalAuth;
use App\Utils\Traits\HttpRequest; use App\Utils\Traits\HttpRequest;
use App\Utils\Traits\Logger;
use Exception; use Exception;
use GuzzleHttp\Exception\GuzzleException;
use JsonException;
use Psr\Http\Message\ResponseInterface;
// use JetBrains\PhpStorm\ArrayShape; // use JetBrains\PhpStorm\ArrayShape;
/** /**
@ -16,9 +14,10 @@ use Psr\Http\Message\ResponseInterface;
* *
* @link https://yb.qq.com/yibao-payment/doc?nodeId=83679977515675648 * @link https://yb.qq.com/yibao-payment/doc?nodeId=83679977515675648
*/ */
class Authorization class Client
{ {
use Logger;
use HttpRequest; use HttpRequest;
/** /**
@ -105,9 +104,9 @@ class Authorization
* @param array $params * @param array $params
* @param string $method * @param string $method
* @param array $options * @param array $options
* @return ResponseInterface|string[] * @return array|mixed
*/ */
protected function requestHanle(string $endpoint, array $params = [], string $method = 'post', array $options = []) protected function requestHandle(string $endpoint, array $params = [], string $method = 'post', array $options = []): mixed
{ {
$timestamp = $this->getMillisecond(); $timestamp = $this->getMillisecond();
$params = array_merge([ $params = array_merge([
@ -121,64 +120,42 @@ class Authorization
], $options); ], $options);
try { try {
$response = $this->request($endpoint, $method, $params); $response = $this->request($method, $endpoint, $params);
$this->writerLog($endpoint, $params, $response); $this->writerLog($endpoint, $params, $response);
return $response; return $response;
} catch (Exception $e) { } catch (Exception $e) {
$err_msg = "{$e->getMessage()} ON {$e->getFile()}:{$e->getLine()}";
$this->info($endpoint, $params, $err_msg);
return [ return [
'code' => -1, 'code' => -1,
//'message' => "{$e->getMessage()} ON {$e->getFile()}:{$e->getLine()}" //'message' => $err_msg
'message' => $e->getMessage() 'message' => $e->getMessage()
]; ];
} }
} }
/** /**
* 记录日志 * 获取小程序授权跳转地址
* @param string $request_url
* @param mixed $request_data
* @param mixed $response
* @return void
*/
private function writerLog(string $request_url, mixed $request_data, mixed $response): void
{
//写入日志
$log_content = [
'[REQUEST TIME] ' . date('Y-m-d H:i:s'),
'[REQUEST URL] ' . $request_url,
'[REQUEST PARAM] ' . json_encode($request_data, JSON_UNESCAPED_UNICODE),
'[RESPONSE PARAM] ' . json_encode($response, JSON_UNESCAPED_UNICODE),
"\r\n",
];
recordLog('authorization', implode("\r\n", $log_content));
}
/**
* 获取授权跳转地址
* @param string $return_url 回调地址
* @return string * @return string
*/ */
public function getAuthRedirectUrl(string $return_url): string public function getMiniProgramAuthRedirectUrl(): string
{ {
$url = '/oauth/code'; $url = 'auth/pages/bindcard/auth/index';
$data = [ $data = [
'authType' => 2, 'openType' => 'getAuthCode',
'isDepart' => 2,
'bizType' => '04107',
'appid' => $this->config['app_id'],
'cityCode' => $this->config['city_code'], 'cityCode' => $this->config['city_code'],
'channel' => $this->config['channel'], 'channel' => $this->config['channel'],
'orgChnlCrtfCodg' => $this->config['org_chnl_crtf_codg'], 'orgChnlCrtfCodg' => $this->config['org_chnl_crtf_codg'],
'orgCodg' => $this->config['org_codg'], 'orgCodg' => $this->config['org_codg'],
'bizType' => '04107',
'orgAppId' => $this->config['org_app_id'], 'orgAppId' => $this->config['org_app_id'],
'redirectUrl' => $return_url
]; ];
$redirect_url = $this->auth_host . $url . '?' . http_build_query($data); $redirect_url = $url . '?' . http_build_query($data);
$this->writerLog($url, $data, ''); $this->writerLog($url, $data, '');
@ -189,10 +166,9 @@ class Authorization
* 查询用户授权 * 查询用户授权
* @param string $auth_code * @param string $auth_code
* @param string $open_id * @param string $open_id
* @return ResponseInterface|string[] * @return mixed
* @throws GuzzleException
*/ */
public function userQuery(string $auth_code, string $open_id): array|ResponseInterface public function userQuery(string $auth_code, string $open_id): mixed
{ {
$uri = '/api/mipuserquery/userQuery/' . $this->config['partner_id']; $uri = '/api/mipuserquery/userQuery/' . $this->config['partner_id'];
$param = [ $param = [
@ -200,6 +176,6 @@ class Authorization
'openid' => $open_id, 'openid' => $open_id,
]; ];
return $this->requestHanle($uri, $param); return $this->requestHandle($uri, $param);
} }
} }

@ -1,5 +1,6 @@
<?php <?php
use App\Dictionary\Patient\IdentifyCardType;
use EasyWeChat\MiniApp\Application as MiniApplication; use EasyWeChat\MiniApp\Application as MiniApplication;
use EasyWeChat\Pay\Application as PayApplication; use EasyWeChat\Pay\Application as PayApplication;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@ -207,7 +208,7 @@ if (!function_exists('getIDCardType')) {
} }
// 港澳台 社保卡号 // 港澳台 社保卡号
if (preg_match("/^[HKG|MAC|TWN][0-9]{9}$/", $id_card)) { if (preg_match("/^(HKG|MAC|TWN)[0-9]{9}$/", $id_card)) {
return 2; return 2;
} }

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

@ -221,7 +221,7 @@ trait SendSubscribeMessage
// 获取取药或执行地址 // 获取取药或执行地址
$extra_info = json_decode($record->extra_info, true); $extra_info = json_decode($record->extra_info, true);
$pay_address = $extra_info['confirm_response']['phyAddress'] ?? ''; $pay_address = $extra_info['confirm_response']['phyAddress'] ?? ' ';
$subscribe_id = SubscribeId::OUTPATIENT_PAYMENT_SUCCESS; $subscribe_id = SubscribeId::OUTPATIENT_PAYMENT_SUCCESS;
$data = [ $data = [
'touser' => $order->open_id, 'touser' => $order->open_id,

@ -3,6 +3,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Utils\Traits; namespace App\Utils\Traits;
trait UniversalEncryption trait UniversalEncryption
{ {
private string $key; private string $key;

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

@ -10,5 +10,7 @@ return [
// 跳转小程序类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版 // 跳转小程序类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版
'mini_program_message_state' => 'developer', 'mini_program_message_state' => 'developer',
// 支付通知地址 // 支付通知地址
'payment_notify_url' => env('APP_URL'). '/Api/notify' 'payment_notify_url' => env('APP_URL'). '/Api/notify',
// 免密授权跳转小程序APP ID
'redirect_app_id' => 'wxe183cd55df4b4369'
]; ];

@ -12,8 +12,8 @@ return [
// 是否模拟数据 // 是否模拟数据
'is_mock' => true, 'is_mock' => true,
], ],
// HIS API服务 -- 方鼎中间件转发 // HIS HTTP服务 -- 方鼎中间件转发
'his_api' => [ 'his_http' => [
// 测试地址 // 测试地址
'url' => 'http://192.168.61.45:8809/api/WeChatDomain/', 'url' => 'http://192.168.61.45:8809/api/WeChatDomain/',
// 不记录日志的请求数组 // 不记录日志的请求数组
@ -24,5 +24,18 @@ return [
'options' => [ 'options' => [
'timeout' => 60 'timeout' => 60
] ]
] ],
// HIS Medical HTTP服务
'his_medical_http' => [
// 测试地址
'url' => 'http://192.168.61.44:8010/api/his/',
// 不记录日志的请求数组
'not_log_arr' => [''],
// 是否模拟数据
'is_mock' => true,
// guzzle/client options
'options' => [
'timeout' => 60
]
],
]; ];

@ -174,6 +174,15 @@ return [
'max_files' => 360, 'max_files' => 360,
], ],
// HisMedicalApi
'his_medical_http' => [
'driver' => 'custom',
'via' => GeneralDailyLogger::class,
'service_type' => 'HisMedicalHttpLog',
'level' => Level::Info,
'max_files' => 360,
],
// 挂号日志 // 挂号日志
'registration' => [ 'registration' => [
'driver' => 'custom', 'driver' => 'custom',
@ -217,7 +226,9 @@ return [
'service_type' => 'SendWeChatMessage', 'service_type' => 'SendWeChatMessage',
'level' => Level::Info, 'level' => Level::Info,
'max_files' => 0, 'max_files' => 0,
] ],
//
], ],
]; ];

@ -16,9 +16,9 @@ return [
| apiSecret: 应用秘钥 | apiSecret: 应用秘钥
| |
*/ */
'domain' => 'http://fangding.picp.vip:7878/', 'domain' => 'http://localhost:9040/',
'apiSecret' => '16f62721-c156-4514-8743-bd969bed32af', 'apiSecret' => 'a2800bac-3384-4ad3-9002-89af6c44a3ab',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -36,11 +36,11 @@ return [
'channels' => [ 'channels' => [
'default' => [ 'default' => [
'merchantId' => 'ZHXZEY20250124', 'merchantId' => 'ZHXZEY20250228',
'secret' => '94aaadd840a3b97c6bbee4f440b986ce', 'secret' => '2a62ff796006a7a2b2d06e40ea240de9',
'channelId' => 'FD001', 'channelId' => 'FD001',
'operatorId' => [ 'operatorId' => [
'MN001', 'MINI001',
], ],
] ]

@ -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,
]
];

@ -155,5 +155,27 @@ return [
'timeout' => 5.0, 'timeout' => 5.0,
// 'base_uri' => 'https://api.mch.weixin.qq.com/', // 如果你在国外想要覆盖默认的 url 的时候才使用,根据不同的模块配置不同的 uri // 'base_uri' => 'https://api.mch.weixin.qq.com/', // 如果你在国外想要覆盖默认的 url 的时候才使用,根据不同的模块配置不同的 uri
], ],
] ],
'medical' => [
/*
|--------------------------------------------------------------------------
| Medical Auth 配置
|--------------------------------------------------------------------------
|
| 医保免密授权对应配置数据
| 详见:https://yb.qq.com/yibao-payment/doc?nodeId=83679977515675648
|
*/
'auth' =>[
'app_id' => env('WECHAT_MEDICAL_AUTH_APP_ID', ''),// AppID
'partner_id' => env('WECHAT_MEDICAL_AUTH_PARTNER_ID', ''), // 合作方ID
'partner_secret' => env('WECHAT_MEDICAL_AUTH_PARTNER_SECRET', ''), // 合作方秘钥
'city_code' => env('WECHAT_MEDICAL_AUTH_CITY_CODE', ''), // 城市ID 香洲区
'channel' => env('WECHAT_MEDICAL_AUTH_CHANNEL', ''), // 渠道号(微信医保平台分配)
'org_chnl_crtf_codg' => env('WECHAT_MEDICAL_AUTH_ORG_CHNL_CRTF_CODG', ''), // 机构渠道认证编码
'org_codg' => env('WECHAT_MEDICAL_AUTH_ORG_CODG', ''), // 定点医药机构编码
'org_app_id' => env('WECHAT_MEDICAL_AUTH_ORG_APP_ID', ''), // 定点医药机构应用ID
],
],
]; ];

@ -243,6 +243,8 @@ class BasicClient
throw new RuntimeException('Generate Sign Error:' . $e->getMessage()); throw new RuntimeException('Generate Sign Error:' . $e->getMessage());
} }
Log::debug('Generate Sign Content', [$data, $buff]);
return trim($buff); return trim($buff);
} }

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

@ -8,6 +8,7 @@ use ReflectionException;
use UnifyPayment\Cores\BasicClient; use UnifyPayment\Cores\BasicClient;
use UnifyPayment\Cores\Exceptions\InvalidConfigException; use UnifyPayment\Cores\Exceptions\InvalidConfigException;
use UnifyPayment\Cores\Exceptions\RuntimeException; use UnifyPayment\Cores\Exceptions\RuntimeException;
use UnifyPayment\Cores\Struct\CreateMedicalInsuranceOrder;
use UnifyPayment\Cores\Struct\CreateOrder; use UnifyPayment\Cores\Struct\CreateOrder;
class MiniProgramClient extends BasicClient class MiniProgramClient extends BasicClient
@ -24,7 +25,7 @@ class MiniProgramClient extends BasicClient
* @throws InvalidConfigException * @throws InvalidConfigException
* @throws RuntimeException * @throws RuntimeException
*/ */
public function jsapi(CreateOrder $order) public function jsapi(CreateOrder $order): array|string
{ {
$this->payload = array_merge($this->payload, $order->toArray()); $this->payload = array_merge($this->payload, $order->toArray());
$this->payload['sign'] = $this->generateSign($this->payload); $this->payload['sign'] = $this->generateSign($this->payload);
@ -32,4 +33,22 @@ class MiniProgramClient extends BasicClient
return $this->request('post', '/index.php/api/pay/miniapp'); return $this->request('post', '/index.php/api/pay/miniapp');
} }
/**
* WeChat Mini Program Medical Insurance Payment
*
* @param CreateMedicalInsuranceOrder $order
*
* @return array|string
*
* @throws InvalidConfigException
* @throws ReflectionException
* @throws RuntimeException
*/
public function medicalPay(CreateMedicalInsuranceOrder $order): array|string
{
$this->payload = array_merge($this->payload, $order->toArray());
$this->payload['sign'] = $this->generateSign($this->payload);
return $this->request('post', '/index.php/api/pay/mpInsurance');
}
} }

@ -8,6 +8,7 @@ use ReflectionException;
use UnifyPayment\Cores\BasicClient; use UnifyPayment\Cores\BasicClient;
use UnifyPayment\Cores\Exceptions\InvalidConfigException; use UnifyPayment\Cores\Exceptions\InvalidConfigException;
use UnifyPayment\Cores\Exceptions\RuntimeException; use UnifyPayment\Cores\Exceptions\RuntimeException;
use UnifyPayment\Cores\Struct\CreateMedicalInsuranceOrder;
use UnifyPayment\Cores\Struct\CreateOrder; use UnifyPayment\Cores\Struct\CreateOrder;
class OfficialAccountClient extends BasicClient class OfficialAccountClient extends BasicClient
@ -24,11 +25,30 @@ class OfficialAccountClient extends BasicClient
* @throws InvalidConfigException * @throws InvalidConfigException
* @throws RuntimeException * @throws RuntimeException
*/ */
public function jsapi(CreateOrder $order) public function jsapi(CreateOrder $order): array|string
{ {
$this->payload = array_merge($this->payload, $order->toArray()); $this->payload = array_merge($this->payload, $order->toArray());
$this->payload['sign'] = $this->generateSign($this->payload); $this->payload['sign'] = $this->generateSign($this->payload);
return $this->request('post', '/index.php/api/pay/mp'); return $this->request('post', '/index.php/api/pay/mp');
} }
/**
* WeChat Official Account Medical Insurance Payment
*
* @param CreateMedicalInsuranceOrder $order
*
* @return array|string
*
* @throws InvalidConfigException
* @throws ReflectionException
* @throws RuntimeException
*/
public function medicalPay(CreateMedicalInsuranceOrder $order): array|string
{
$this->payload = array_merge($this->payload, $order->toArray());
$this->payload['sign'] = $this->generateSign($this->payload);
return $this->request('post', '/index.php/api/pay/mpInsurance');
}
} }

@ -1,7 +1,9 @@
<?php <?php
use UnifyPayment\Cores\Struct\CreateMedicalInsuranceOrder;
use UnifyPayment\Cores\Struct\CreateOrder; use UnifyPayment\Cores\Struct\CreateOrder;
use UnifyPayment\Factory; use UnifyPayment\Factory;
use UnifyPayment\Mock\CreateMedicalInsuranceOrderHandler;
use UnifyPayment\Mock\CreateOrderHandler; use UnifyPayment\Mock\CreateOrderHandler;
class PayTest extends TestCase class PayTest extends TestCase
@ -28,6 +30,26 @@ class PayTest extends TestCase
], array_keys($response['response'])); ], array_keys($response['response']));
} }
public function testMiniProgramMedicalPay()
{
$order = new CreateMedicalInsuranceOrder('DiagPay', 'ow_gnt5QQQ-Qby1234567890', '202204022005169952975171534816', '某某附属医院', '70', '1', 'https://kyey.test.com/pay/notify/wechatINSUNotify', '门诊缴费', 'https://kyey.test.com/wehospital/payment/paymentindex', '2', 'H53011234567', '20220402200530', '530100', '202204022005169952975171534816', '1', '0ef5c63f34237aeba58d0a6f97f66c13', '姓x', '4', '70', '0', '{"payAuthNo":"AUTH530100202204022006310000034","payOrdId":"ORD530100202204022006350000021","setlLatlnt":"102.682296,25.054260"}', 'AAGN9uhZc5EGyRdairKW7Qnu', 'bd787ae69021fbd7d0b86d080b007bad', '127.0.0.1');
$mockHandler = new CreateMedicalInsuranceOrderHandler(true);
$response = Factory::pay($this->getConfig())->mini->setMockHandler([$mockHandler])->medicalPay($order);
$this->assertArrayHasKey('response', $response);
$this->assertEquals([
'payUrl',
'medTransId',
'payAppId',
'orderNo',
'merchantId',
'merchantName',
'channelId',
'channelName'
], array_keys($response['response']));
}
public function testOfficialAccountJsapi() public function testOfficialAccountJsapi()
{ {
$order = new CreateOrder('当天挂号', 'FD2022080310000055123451223', 0.01, '1', '002197|公众号', 'A', 'ovIGQwOy1e-Zptyug20l5hqI0P5Q', 'https://www.baidu.com'); $order = new CreateOrder('当天挂号', 'FD2022080310000055123451223', 0.01, '1', '002197|公众号', 'A', 'ovIGQwOy1e-Zptyug20l5hqI0P5Q', 'https://www.baidu.com');
@ -49,4 +71,24 @@ class PayTest extends TestCase
'channelName' 'channelName'
], array_keys($response['response'])); ], array_keys($response['response']));
} }
public function testOfficialAccountMedicalPay()
{
$order = new CreateMedicalInsuranceOrder('DiagPay', 'ow_gnt5QQQ-Qby1234567890', '202204022005169952975171534816', '某某附属医院', '70', '1', 'https://kyey.test.com/pay/notify/wechatINSUNotify', '门诊缴费', 'https://kyey.test.com/wehospital/payment/paymentindex', '2', 'H53011234567', '20220402200530', '530100', '202204022005169952975171534816', '1', '0ef5c63f34237aeba58d0a6f97f66c13', '姓x', '4', '70', '0', '{"payAuthNo":"AUTH530100202204022006310000034","payOrdId":"ORD530100202204022006350000021","setlLatlnt":"102.682296,25.054260"}', 'AAGN9uhZc5EGyRdairKW7Qnu', 'bd787ae69021fbd7d0b86d080b007bad', '127.0.0.1');
$mockHandler = new CreateMedicalInsuranceOrderHandler(true);
$response = Factory::pay($this->getConfig())->official->setMockHandler([$mockHandler])->medicalPay($order);
$this->assertArrayHasKey('response', $response);
$this->assertEquals([
'payUrl',
'medTransId',
'payAppId',
'orderNo',
'merchantId',
'merchantName',
'channelId',
'channelName'
], array_keys($response['response']));
}
} }

@ -4,11 +4,12 @@ use App\Http\Controllers\Auth\AuthController;
use App\Http\Controllers\Hospital\IntroduceController; use App\Http\Controllers\Hospital\IntroduceController;
use App\Http\Controllers\Message\TriggerController; use App\Http\Controllers\Message\TriggerController;
use App\Http\Controllers\Notify\NotifyController; use App\Http\Controllers\Notify\NotifyController;
use App\Http\Controllers\Outpatient\MedicalController; use App\Http\Controllers\Outpatient\MedicalController as OutpatientMedicalController;
use App\Http\Controllers\Outpatient\PaymentController; use App\Http\Controllers\Outpatient\PaymentController;
use App\Http\Controllers\Outpatient\PendingController; use App\Http\Controllers\Outpatient\PendingController;
use App\Http\Controllers\Outpatient\RecordController as OutpatientRecordController; use App\Http\Controllers\Outpatient\RecordController as OutpatientRecordController;
use App\Http\Controllers\Patient\PatientController; use App\Http\Controllers\Patient\PatientController;
use App\Http\Controllers\Registration\MedicalController as RegistrationMedicalController;
use App\Http\Controllers\Registration\RecordController as RegistrationRecordController; use App\Http\Controllers\Registration\RecordController as RegistrationRecordController;
use App\Http\Controllers\Registration\RegisterController; use App\Http\Controllers\Registration\RegisterController;
use App\Http\Controllers\Registration\ScheduleController; use App\Http\Controllers\Registration\ScheduleController;
@ -40,6 +41,9 @@ Route::middleware(['apiLog'])->group(function() {
// 获取手机号码 // 获取手机号码
Route::get('/phone', [PatientController::class, 'getPhoneNumber']); Route::get('/phone', [PatientController::class, 'getPhoneNumber']);
// 定点签约
Route::post('/{patient_id}/designate', [PatientController::class, 'designated']);
}); });
// 挂号模块 // 挂号模块
@ -49,14 +53,19 @@ Route::middleware(['apiLog'])->group(function() {
Route::get('/doctor', [ScheduleController::class, 'doctor']); Route::get('/doctor', [ScheduleController::class, 'doctor']);
Route::post('/{patient_id}/register', [RegisterController::class, 'register']); Route::post('/{patient_id}/register', [RegisterController::class, 'register']);
// 医保预结算
Route::post('/{patient_id}/medical/settle', [RegistrationMedicalController::class, 'registerPreSettle']);
// 医保支付
Route::post('/{patient_id}/medical/payment', [RegistrationMedicalController::class, 'register']);
Route::get('/{patient_id}/record', [RegistrationRecordController::class, 'lists']); Route::get('/{patient_id}/record', [RegistrationRecordController::class, 'lists']);
Route::post('/{patient_id}/record/{serial_no}/refund', [RegistrationRecordController::class, 'refund']); Route::post('/{patient_id}/record/{serial_no}/refund', [RegistrationRecordController::class, 'refund']);
}); });
// 医保模块 // 医保模块
Route::prefix('Medical')->group(function () { Route::prefix('Medical')->group(function () {
Route::get('/auth/path', [MedicalController::class, 'redirectAuth']); Route::get('/auth/path', [OutpatientMedicalController::class, 'redirectAuth']);
Route::post('/auth/{patient_id}/info', [MedicalController::class, 'userAuthInfo']); Route::post('/auth/{patient_id}/info', [OutpatientMedicalController::class, 'userAuthInfo']);
}); });
// 缴费模块 // 缴费模块
@ -65,6 +74,11 @@ Route::middleware(['apiLog'])->group(function() {
Route::get('/{patient_id}/pending/{serial_no}/', [PendingController::class, 'details']); Route::get('/{patient_id}/pending/{serial_no}/', [PendingController::class, 'details']);
Route::post('/{patient_id}/pending/{serial_no}/payment', [PaymentController::class, 'payment']); Route::post('/{patient_id}/pending/{serial_no}/payment', [PaymentController::class, 'payment']);
// 医保预结算
Route::post('/{patient_id}/pending/{serial_no}/medical/settle', [OutpatientMedicalController::class, 'prescriptionPreSettle']);
// 医保支付
Route::post('/{patient_id}/pending/{serial_no}/medical/payment', [OutpatientMedicalController::class, 'payment']);
Route::get('/{patient_id}/record', [OutpatientRecordController::class, 'lists']); Route::get('/{patient_id}/record', [OutpatientRecordController::class, 'lists']);
Route::get('/{patient_id}/record/{serial_no}/', [OutpatientRecordController::class, 'details']); Route::get('/{patient_id}/record/{serial_no}/', [OutpatientRecordController::class, 'details']);
}); });
@ -77,6 +91,7 @@ Route::middleware(['apiLog'])->group(function() {
// 报告相关模块 // 报告相关模块
Route::prefix('Report')->group(function () { Route::prefix('Report')->group(function () {
Route::get('/tips', [InspectController::class, 'tips']);
Route::get('/{patient_id}/inspect', [InspectController::class, 'lists']); Route::get('/{patient_id}/inspect', [InspectController::class, 'lists']);
Route::get('/{patient_id}/inspect/{serial_no}/', [InspectController::class, 'details']); Route::get('/{patient_id}/inspect/{serial_no}/', [InspectController::class, 'details']);
}); });

Loading…
Cancel
Save