From ca55df873a3dedf9e33a65f30569d32854858534 Mon Sep 17 00:00:00 2001 From: "X14XA\\shengli" Date: Mon, 2 Mar 2026 16:32:48 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 + README.md | 111 ++++++- composer.json | 18 ++ phpunit.xml | 19 ++ src/Constant.php | 92 ++++++ src/HFPayClient.php | 105 +++++++ src/exception/ApiException.php | 28 ++ src/exception/BizException.php | 30 ++ src/exception/InitException.php | 14 + src/exception/ParamsException.php | 30 ++ src/exception/SignException.php | 14 + src/factory/BaseFactory.php | 58 ++++ src/factory/impl/ManageFactory.php | 221 ++++++++++++++ src/factory/impl/MarketFactory.php | 86 ++++++ src/factory/impl/QueryFactory.php | 214 ++++++++++++++ src/factory/impl/TradeFactory.php | 132 +++++++++ src/factory/object/RequestObj.php | 22 ++ src/factory/object/ResponseObj.php | 23 ++ src/factory/object/manage/BindCashCard.php | 30 ++ src/factory/object/manage/CertificateInfo.php | 24 ++ src/factory/object/manage/DownloadFiles.php | 18 ++ src/factory/object/manage/UnBindCard.php | 25 ++ .../object/market/OpenAccountRequest.php | 78 +++++ .../object/market/OpenAccountResponse.php | 15 + .../market/WalletManageInfoResponse.php | 15 + src/factory/object/query/BalanceInfo.php | 17 ++ src/factory/object/query/BindCardStatus.php | 21 ++ src/factory/object/query/ChainBalance.php | 21 ++ src/factory/object/query/OpenStatus.php | 33 +++ src/factory/object/query/RecordLog.php | 23 ++ src/factory/object/query/TransStatus.php | 43 +++ src/factory/object/query/WalletStatus.php | 21 ++ src/factory/object/trade/CloseOrder.php | 17 ++ src/factory/object/trade/DelayDivide.php | 28 ++ .../object/trade/DelayDivideRequest.php | 71 +++++ src/factory/object/trade/DeviceInfo.php | 79 +++++ src/factory/object/trade/DivDetail.php | 43 +++ src/factory/object/trade/ObjectInfo.php | 54 ++++ .../object/trade/RefundGoodsRequest.php | 73 +++++ .../object/trade/RefundGoodsResponse.php | 24 ++ src/factory/object/trade/RefundInfo.php | 24 ++ src/factory/object/trade/RefundRequest.php | 74 +++++ src/factory/object/trade/UnifiedOrder.php | 21 ++ .../object/trade/UnifiedOrderRequest.php | 172 +++++++++++ src/http/ErrCodeHelper.php | 83 ++++++ src/http/Factory.php | 47 +++ src/http/IHttpClient.php | 21 ++ src/http/Response.php | 78 +++++ src/http/impl/HFPayHttpClient.php | 276 ++++++++++++++++++ src/library/ISigner.php | 12 + src/library/impl/SignerHttpClient.php | 162 ++++++++++ src/utils/StrFilterHelper.php | 34 +++ 52 files changed, 2997 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 composer.json create mode 100644 phpunit.xml create mode 100644 src/Constant.php create mode 100644 src/HFPayClient.php create mode 100644 src/exception/ApiException.php create mode 100644 src/exception/BizException.php create mode 100644 src/exception/InitException.php create mode 100644 src/exception/ParamsException.php create mode 100644 src/exception/SignException.php create mode 100644 src/factory/BaseFactory.php create mode 100644 src/factory/impl/ManageFactory.php create mode 100644 src/factory/impl/MarketFactory.php create mode 100644 src/factory/impl/QueryFactory.php create mode 100644 src/factory/impl/TradeFactory.php create mode 100644 src/factory/object/RequestObj.php create mode 100644 src/factory/object/ResponseObj.php create mode 100644 src/factory/object/manage/BindCashCard.php create mode 100644 src/factory/object/manage/CertificateInfo.php create mode 100644 src/factory/object/manage/DownloadFiles.php create mode 100644 src/factory/object/manage/UnBindCard.php create mode 100644 src/factory/object/market/OpenAccountRequest.php create mode 100644 src/factory/object/market/OpenAccountResponse.php create mode 100644 src/factory/object/market/WalletManageInfoResponse.php create mode 100644 src/factory/object/query/BalanceInfo.php create mode 100644 src/factory/object/query/BindCardStatus.php create mode 100644 src/factory/object/query/ChainBalance.php create mode 100644 src/factory/object/query/OpenStatus.php create mode 100644 src/factory/object/query/RecordLog.php create mode 100644 src/factory/object/query/TransStatus.php create mode 100644 src/factory/object/query/WalletStatus.php create mode 100644 src/factory/object/trade/CloseOrder.php create mode 100644 src/factory/object/trade/DelayDivide.php create mode 100644 src/factory/object/trade/DelayDivideRequest.php create mode 100644 src/factory/object/trade/DeviceInfo.php create mode 100644 src/factory/object/trade/DivDetail.php create mode 100644 src/factory/object/trade/ObjectInfo.php create mode 100644 src/factory/object/trade/RefundGoodsRequest.php create mode 100644 src/factory/object/trade/RefundGoodsResponse.php create mode 100644 src/factory/object/trade/RefundInfo.php create mode 100644 src/factory/object/trade/RefundRequest.php create mode 100644 src/factory/object/trade/UnifiedOrder.php create mode 100644 src/factory/object/trade/UnifiedOrderRequest.php create mode 100644 src/http/ErrCodeHelper.php create mode 100644 src/http/Factory.php create mode 100644 src/http/IHttpClient.php create mode 100644 src/http/Response.php create mode 100644 src/http/impl/HFPayHttpClient.php create mode 100644 src/library/ISigner.php create mode 100644 src/library/impl/SignerHttpClient.php create mode 100644 src/utils/StrFilterHelper.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6d337a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +composer.lock +.idea/ +vendor/ +tests/ +.phpunit.result.cache diff --git a/README.md b/README.md index 31450e6..db15a35 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,110 @@ -# hfpay-lib +# 说明 -汇付SDK \ No newline at end of file +汇付SDK + +使用PHP-DI容器 + +## 要求 + +php >= 7.4 + +## 配置 + +```php +$config=[ + //环境 + 'debug' => true, + //本地签名服务器 + 'sign_server' => '', + //版本号,默认为10 + 'version' => '10', + //商户ID + 'mid' => '6666000100064457', + //签名服务器上的文件路径(非本地签名文件) + 'sign_file' => '', + //签名密码 + 'sign_pwd' => '123456', + //签名服务器上的证书路径(非本地签名文件) + 'cert_file' => '' +]; +``` + +## 初始化 + +```php +$client = HFPayClient::getInstance()->init($config); +``` + +## 使用 + +### 一. 市场接口 + +#### 1. 开户 + +```php +$client->market()->create() +``` + +#### 2. 钱包管理 + +```php +$client->market()->walletManage() +``` + +### 二. 查询接口 + +### 三.管理接口 + +### 四.交易接口 + +## 示例 + +```php + true, + //本地签名服务器 + 'sign_server' => 'https://sign.location.server/', + 'version' => '10', + //商户ID + 'mid' => '666000001', + 'sign_file' => '/CFCA/HF123.pfx', + 'sign_pwd' => '123456', + 'cert_file' => '/CFCA/cert/CFCA_1234.cer' +]; + +$client = HFPayClient::getInstance()->init($params); + +// 使用 +try{ +$response=$client->query()->wallet('用户ID'); +if($response->isSuccess()){ + //获取原始HTML数据 + $response->getSource(); + //获取 array 结果 + $response->getBody(); + //获取解密后的结果 + $response->getDecodeBody(); +} +}catch (\Tansilu\HfPayLib\exception\SignException $e){ + +}catch (\Tansilu\HfPayLib\exception\BizException $e){ +// +} +?> +``` + +## 回调通知处理 + +```php +$checkValue=$_POST['check_value'] + +$sign=new \Tansilu\HfPayLib\library\impl\SignerHttpClient( + $config +); + +$result=$sign->decode($checkValue); + +print_r($result) +``` \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..fe29886 --- /dev/null +++ b/composer.json @@ -0,0 +1,18 @@ +{ + "name": "tansilu/hfpay-lib", + "version": "0.0.1", + "type": "library", + "require": { + "guzzlehttp/guzzle": "^7.10", + "php-di/php-di": "^6.4", + "ext-json": "*" + }, + "require-dev": { + "phpunit/phpunit": "^9.6" + }, + "autoload": { + "psr-4": { + "Tansilu\\HfPayLib\\": "src/" + } + } +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..91d4c14 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,19 @@ + + + + + tests + + + + + + + + + + ./app + + + \ No newline at end of file diff --git a/src/Constant.php b/src/Constant.php new file mode 100644 index 0000000..23965f0 --- /dev/null +++ b/src/Constant.php @@ -0,0 +1,92 @@ +container = $builder->build(); + + //添加HTTP接口 + $client = new HFPayHttpClient($config); + $this->container->set(IHttpClient::class, $client); + return $this; + } + + /** + * @throws \Exception + */ + private function __construct() + { + + } + + + /** + * 查询接口 + * + * @throws DependencyException + * @throws NotFoundException + */ + public function query(): QueryFactory + { + return $this->container->make(QueryFactory::class); + } + + /** + * 市场接入接口 + * + * @throws DependencyException + * @throws NotFoundException + */ + public function market(): MarketFactory + { + return $this->container->make(MarketFactory::class); + } + + /** + * 管理接口 + * + * @throws DependencyException + * @throws NotFoundException + */ + public function manage(): ManageFactory + { + return $this->container->make(ManageFactory::class); + } + + /** + * 交易接口 + * + * @return TradeFactory + * @throws DependencyException + * @throws NotFoundException + */ + public function trade(): TradeFactory + { + return $this->container->make(TradeFactory::class); + } +} \ No newline at end of file diff --git a/src/exception/ApiException.php b/src/exception/ApiException.php new file mode 100644 index 0000000..b6afc13 --- /dev/null +++ b/src/exception/ApiException.php @@ -0,0 +1,28 @@ +errCode; + } + + public function setErrCode(string $errCode): void + { + $this->errCode = $errCode; + } + + public function __construct($message = "", $errCode = '', Throwable $previous = null) + { + $this->errCode = $errCode; + parent::__construct($message, 1, $previous); + } + +} \ No newline at end of file diff --git a/src/exception/BizException.php b/src/exception/BizException.php new file mode 100644 index 0000000..15a4301 --- /dev/null +++ b/src/exception/BizException.php @@ -0,0 +1,30 @@ +errCode; + } + + public function setErrCode(string $errCode): void + { + $this->errCode = $errCode; + } + + public function __construct($message = "", $errCode = '', Throwable $previous = null) + { + $this->errCode = $errCode; + parent::__construct($message, 1, $previous); + } + + + +} \ No newline at end of file diff --git a/src/exception/InitException.php b/src/exception/InitException.php new file mode 100644 index 0000000..6c4db8e --- /dev/null +++ b/src/exception/InitException.php @@ -0,0 +1,14 @@ +errCode; + } + + public function setErrCode(string $errCode): void + { + $this->errCode = $errCode; + } + + public function __construct($message = "", $errCode = '', Throwable $previous = null) + { + $this->errCode = $errCode; + parent::__construct($message, 1, $previous); + } + + + +} \ No newline at end of file diff --git a/src/exception/SignException.php b/src/exception/SignException.php new file mode 100644 index 0000000..0edd5c5 --- /dev/null +++ b/src/exception/SignException.php @@ -0,0 +1,14 @@ +postJson($uri, $data); + return $this->parseResponse($response); + } + + /** + * GET 请求 + * + * @throws BizException + * @throws ApiException + */ + public function processGet(string $uri, array $data): array + { + $response = $this->get($uri, $data); + return $this->parseResponse($response); + } + + /** + * @throws BizException + * @throws ApiException + */ + private function parseResponse(Response $response) + { + if(!$response->isSuccess()) { + throw new ApiException('API请求过程中遇到错误' . $response->getCode()); + } + $b = $response->getBody(); + + if(!$b['success']) { + throw new BizException($b['message'], $b['errorCode']); + } + return $b['data'] ?? []; + } + +} \ No newline at end of file diff --git a/src/factory/impl/ManageFactory.php b/src/factory/impl/ManageFactory.php new file mode 100644 index 0000000..b7e8995 --- /dev/null +++ b/src/factory/impl/ManageFactory.php @@ -0,0 +1,221 @@ + $customId, + 'order_id' => $orderId, + 'order_date' => $orderDate, + 'bank_id' => $bankId, + 'card_num' => $cardNo, + 'card_type' => $cardType, + 'default_cash_flag' => $cashFlag + ]; + if($cardType == Constant::CARD_TYPE_COMPANY) { + if(empty($bankId)) { + throw new ParamsException('对公账户需要填写银行代码'); + } + } + if(!empty($branchName)) { + $params['branch_name'] = $branchName; + } + if(!empty($cardProv)) { + $params['card_prov'] = $cardProv; + } + if(!empty($cardArea)) { + $params['card_area'] = $cardArea; + } + if(!empty($attach)) { + $params['mer_priv'] = $attach; + } + if(!empty($extension)) { + $params['extension'] = $extension; + } + if(!empty($bgRetUrl)) { + $params['bg_ret_url'] = $bgRetUrl; + } + $result = $this->post('/api/acou/bind08', $params); + if(!$result->isSuccess()) { + return null; + } + return BindCashCard::make($result->getBody()); + } + + + /** + * 银行卡解绑 + * + * @param string $customId + * @param string $orderId + * @param string $orderDate + * @param string $bindCardId + * @param string $cardType + * @param string $bgRetUrl + * @param string $attach + * @param string $extension + * @param string $devInfoJson + * + * @return UnBindCard|null + * + * @see https://hfpay.cloudpnr.com/customers/nft/#/manage/unbindCard + */ + public function unBindCard(string $customId, string $orderId, string $orderDate, string $bindCardId, string $cardType, + string $bgRetUrl, + string $attach = '', string $extension = '', string $devInfoJson = '' + ): ?UnBindCard + { + $params = [ + 'user_cust_id' => $customId, + 'order_id' => $orderId, + 'order_date' => $orderDate, + 'bank_card_id' => $bindCardId, + 'card_buss_type' => $cardType, + 'bg_ret_url' => $bgRetUrl, + ]; + if(!empty($attach)) { + $params['mer_priv'] = $attach; + } + if(!empty($extension)) { + $params['extension'] = $extension; + } + if(!empty($devInfoJson)) { + $params['dev_info_json'] = $devInfoJson; + } + $result = $this->post('/api/acou/unbd01', $params); + + if(!$result->isSuccess()) { + return null; + } + return UnBindCard::make($result->getBody()); + } + + /** + * 文件下载 + * + * @param string $fileDate + * @param string $fileType + * + * @return DownloadFiles + * + * @see https://hfpay.cloudpnr.com/customers/nft/#/manage/fileDownload + */ + function fileDownload(string $fileDate, string $fileType = Constant::FILE_TYPE_CHARGE): ?DownloadFiles + { + $params = [ + 'file_date' => $fileDate, + 'file_type' => $fileType + ]; + $result = $this->post('/api/alse/file02', $params); + if(!$result->isSuccess()) { + return null; + } + + return DownloadFiles::make($result->getBody()); + } + + + /** + * 电子凭证下载 + * + * 后台返回(异步) + * + * @param string $customId + * @param string $orderId + * @param string $orderDate + * @param string $originalOrderDate + * @param string $bgRetUrl + * @param array $originalOrderId + * @param string $stage + * @param string $attach + * @param string $extension + * + * @return ?CertificateInfo + * + * https://hfpay.cloudpnr.com/customers/nft/#/manage/electronicCertificate + */ + function eCertificate( + string $customId, + string $orderId, + string $orderDate, + string $originalOrderDate, + string $bgRetUrl, + array $originalOrderId = [], + string $stage = Constant::GUARANTEE_STAGE_IN, + string $attach = '', + string $extension = '' + ): ?CertificateInfo + { + $params = [ + 'user_cust_id' => $customId, + 'order_id' => $orderId, + 'order_date' => $orderDate, + 'bg_ret_url' => $bgRetUrl, + 'org_order_date' => $originalOrderDate, + 'guarantee_stage' => $stage, + ]; + if(!empty($attach)) { + $params['mer_priv'] = $attach; + } + if(!empty($extension)) { + $params['extension'] = $extension; + } + if(!empty($originalOrderId)) { + $params['org_order_id'] = join(',', $originalOrderId); + } + $result = $this->post('/api/alse/file05', $params); + + if(!$result->isSuccess()) { + return null; + } + + return CertificateInfo::make($result->getBody()); + } +} \ No newline at end of file diff --git a/src/factory/impl/MarketFactory.php b/src/factory/impl/MarketFactory.php new file mode 100644 index 0000000..a738ea4 --- /dev/null +++ b/src/factory/impl/MarketFactory.php @@ -0,0 +1,86 @@ +toParams(); + + $data = $this->post('/api/hfpwallet/w00003', $request); + + if(!$data->isSuccess()) { + throw new BizException('账户创建失败', $data->getCode()); + } + return OpenAccountResponse::make($data->getBody()); + } + + + /** + * 钱包管理 + * + * @param string $customId + * @param string $orderId + * @param string $orderDate + * @param string $accountId + * @param string $retUrl + * @param string $attach + * @param string $faceLicenseKey + * + * @return WalletManageInfoResponse|null + * + * @see https://hfpay.cloudpnr.com/customers/nft/#/compositeMarket/walletManage + */ + function walletManage(string $customId, string $orderId, string $orderDate, string $accountId = '', string $retUrl = '', + + string $attach = '', + string $faceLicenseKey = ''): ?WalletManageInfoResponse + { + $params = [ + 'user_cust_id' => $customId, + 'order_date' => $orderDate, + 'order_id' => $orderId, + ]; + if(empty($accountId)) { + $params['acct_id'] = $accountId; + } + if(!empty($retUrl)) { + $params['ret_url'] = $retUrl; + } + if(!empty($attach)) { + $params['mer_priv'] = $attach; + } + if(!empty($faceLicenseKey)) { + $params['face_license_key'] = $faceLicenseKey; + } + + $result = $this->post('/api/hfpwallet/w00004', $params); + if(!$result->isSuccess()) { + return null; + } + return WalletManageInfoResponse::make($result->getBody()); + } +} \ No newline at end of file diff --git a/src/factory/impl/QueryFactory.php b/src/factory/impl/QueryFactory.php new file mode 100644 index 0000000..8256331 --- /dev/null +++ b/src/factory/impl/QueryFactory.php @@ -0,0 +1,214 @@ +post('/api/alse/qry009', [ + 'order_id' => $orderId, + 'order_date' => $orderDate, + 'trans_type' => $transType + ]); + if(!$result->isSuccess()) { + return null; + } + return OpenStatus::make($result->getBody()); + } + + /** + * 钱包状态 + * + * @param string $customId 用户客户号 + * + * @return WalletStatus|null + * + * @see https://hfpay.cloudpnr.com/customers/nft/#/query/walletStateQuery + */ + function wallet(string $customId): ?WalletStatus + { + $result = $this->post('/api/alse/qry016', [ + 'user_cust_id' => $customId + ]); + if(!$result->isSuccess()) { + return null; + } + return WalletStatus::make($result->getBody()); + } + + /** + * 查询交易状态 + * + * @param string $orderID + * @param string $orderDate + * @param string $transType + * @param string $attach + * @param string $extension + * + * @return TransStatus|null + * + * @see https://hfpay.cloudpnr.com/customers/nft/#/query/transactionStateQuery + */ + function trans(string $orderID, string $orderDate, string $transType, string $attach = '', string $extension = ''): ?TransStatus + { + $params = [ + 'order_id' => $orderID, + 'order_date' => $orderDate, + 'trans_type' => $transType + ]; + if(!empty($attach)) { + $params['mer_priv'] = $attach; + } + if(!empty($extension)) { + $params['extension'] = $extension; + } + $result = $this->post('/api/alse/qry008', $params); + if(!$result->isSuccess()) { + return null; + } + return TransStatus::make($result->getBody()); + } + + /** + * 查询用户绑卡信息 + * + * @param string $userId + * @param string $cardId + * + * @return BindCardStatus|null + * + * @see https://hfpay.cloudpnr.com/customers/nft/#/query/bindCardInfoQuery + */ + function bindCard(string $userId, string $cardId = ''): ?BindCardStatus + { + $params = [ + 'user_cust_id' => $userId + ]; + if(!empty($cardId)) { + $params['bind_card_id'] = $cardId; + } + $result = $this->post('/api/alse/qry004', $params); + + if(!$result->isSuccess()) { + return null; + } + return BindCardStatus::make($result->getBody()); + } + + /** + * 余额查询 + * + * @param string $customId + * @param string $accountId + * @param string $isGuarantee + * + * @return BalanceInfo|null + * @throws ParamsException + * + * @see https://hfpay.cloudpnr.com/customers/nft/#/query/balanceQuery + */ + function balance(string $customId, string $accountId = '', string $isGuarantee = ''): ?BalanceInfo + { + $params = [ + 'user_cust_id' => $customId + ]; + if($isGuarantee != '1') { + if(empty($accountId)) { + throw new ParamsException('账户号不能为空', 'PARAMS_ACCOUNT_ID_IS_EMPTY'); + } + $params['acct_id'] = $accountId; + } + if(!empty($isGuarantee)) { + $params['is_query_guarantee'] = $isGuarantee; + } + $result = $this->post('/api/alse/qry001', $params); + if(!$result->isSuccess()) { + return null; + } + return BalanceInfo::make($result->getBody()); + } + + /** + * 流水记录查询 + * + * @param string $customId + * @param string $accountId + * @param string $startDate + * @param string $endDate + * @param int $pageSize + * @param int $pageNum + * @param string $productId + * @param string $productSeqId + * + * @return RecordLog|null + * + * @see https://hfpay.cloudpnr.com/customers/nft/#/query/accountRecordQuery + */ + function record(string $customId, string $accountId, string $startDate, string $endDate, int $pageSize, int $pageNum, string $productId = '', string $productSeqId = ''): ?RecordLog + { + $params = [ + 'user_cust_id' => $customId, + 'acct_id' => $accountId, + 'trans_start_date' => $startDate, + 'trans_end_date' => $endDate, + 'page_size' => $pageSize, + 'page_num' => $pageNum, + ]; + if(!empty($productId)) { + $params['product_id'] = $productId; + } + if(!empty($productSeqId)) { + $params['product_req_seq_id'] = $productSeqId; + } + + $result = $this->post('/api/alse/qry005', $params); + if(!$result->isSuccess()) { + return null; + } + return RecordLog::make($result->getBody()); + } + + /** + * 链上余额查询 + * + * @param string $customId + * + * @return ChainBalance|null + * + * @see https://hfpay.cloudpnr.com/customers/nft/#/query/chainBalanceQuery + */ + function chainBalance(string $customId): ?ChainBalance + { + $params = [ + 'user_cust_id' => $customId, + ]; + + $result = $this->post('/api/alse/qry022', $params); + if(!$result->isSuccess()) { + return null; + } + return ChainBalance::make($result->getBody()); + } +} \ No newline at end of file diff --git a/src/factory/impl/TradeFactory.php b/src/factory/impl/TradeFactory.php new file mode 100644 index 0000000..39c1455 --- /dev/null +++ b/src/factory/impl/TradeFactory.php @@ -0,0 +1,132 @@ +toParams(); + + $result = $this->post('/api/hfpwallet/pay033', $params); + if(!$result->isSuccess()) { + return null; + } + return UnifiedOrder::make($result->getBody()); + } + + /** + * 延时分账确认 + * + * @param DelayDivideRequest $request + * + * @return DelayDivide|null + * @throws ParamsException + * + * @see https://hfpay.cloudpnr.com/customers/nft/#/transaction/delayDivideConfirm + */ + function delayDivideConfirm(DelayDivideRequest $request): ?DelayDivide + { + $params = $request->toParams(); + $result = $this->post('/api/hfpwallet/pay033', $params); + if(!$result->isSuccess()) { + return null; + } + return DelayDivide::make($result->getBody()); + } + + /** + * 关闭订单 + * + * @param string $orderId + * @param string $orderDate + * @param string $merPriv + * + * @return CloseOrder|null + * + * @see https://hfpay.cloudpnr.com/customers/nft/#/transaction/closeOrder + */ + function closeOrder(string $orderId, string $orderDate, string $merPriv = ''): ?CloseOrder + { + $params = [ + 'order_id' => $orderId, + 'order_date' => $orderDate, + ]; + if(!empty($merPriv)) { + $params['mer_priv'] = $merPriv; + } + $result = $this->post('/api/hfpwallet/pay034', $params); + + if(!$result->isSuccess()) { + return null; + } + return CloseOrder::make($result->getBody()); + } + + /** + * 退款 + * + * @param RefundRequest $request + * + * @return RefundInfo|null + * @throws ParamsException + * + * @see https://hfpay.cloudpnr.com/customers/nft/#/transaction/refund + */ + function refund(RefundRequest $request): ?RefundInfo + { + $params = $request->toParams(); + $result = $this->post('/api/hfpay/reb001', $params); + if(!$result->isSuccess()) { + return null; + } + return RefundInfo::make($result->getBody()); + + } + + /** + * 退货 + * + * @param RefundGoodsRequest $request + * + * @return RefundGoodsResponse|null + * @throws ParamsException + * + * @see https://hfpay.cloudpnr.com/customers/nft/#/transaction/refundGoods + */ + function refundGoods(RefundGoodsRequest $request): ?RefundGoodsResponse + { + $params = $request->toParams(); + $result = $this->post('/api/hfpay/reb002', $params); + if(!$result->isSuccess()) { + return null; + } + return RefundGoodsResponse::make($result->getBody()); + } +} \ No newline at end of file diff --git a/src/factory/object/RequestObj.php b/src/factory/object/RequestObj.php new file mode 100644 index 0000000..36a9950 --- /dev/null +++ b/src/factory/object/RequestObj.php @@ -0,0 +1,22 @@ +data[$name] = $value; + } + + public function __get($name) + { + if(isset($this->data[$name])) { + return $this->data[$name]; + } + return null; + } + +} \ No newline at end of file diff --git a/src/factory/object/ResponseObj.php b/src/factory/object/ResponseObj.php new file mode 100644 index 0000000..bf334b1 --- /dev/null +++ b/src/factory/object/ResponseObj.php @@ -0,0 +1,23 @@ +data = $data; + return $self; + } + + private array $data; + + public function __get($name) + { + if(array_key_exists($name, $this->data)) { + return $this->data[$name]; + } + return null; + } +} \ No newline at end of file diff --git a/src/factory/object/manage/BindCashCard.php b/src/factory/object/manage/BindCashCard.php new file mode 100644 index 0000000..7cc7329 --- /dev/null +++ b/src/factory/object/manage/BindCashCard.php @@ -0,0 +1,30 @@ +data['order_date'])) { + throw new ParamsException('订单日期不能为空', 'order_date'); + } + if(empty($this->data['order_id'])) { + throw new ParamsException('订单号不能为空', 'order_id'); + } + if(empty($this->data['user_name'])) { + throw new ParamsException('用户姓名不能为空', 'user_name'); + } + if(empty($this->data['market_type'])) { + throw new ParamsException('应用市场类型不能为空', 'market_type'); + } + if(!in_array($this->data['market_type'], [Constant::MARKET_MASTER, Constant::MARKET_SUB])) { + throw new ParamsException('应用市场类型不正确', 'market_type'); + } + if(empty($this->data['acct_usage_type'])) { + throw new ParamsException('账户用途不能为空', 'acct_usage_type'); + } + if(!in_array($this->data['acct_usage_type'], [Constant::USE_TO_BUY, Constant::USE_TO_TRANS])) { + throw new ParamsException('账户用途类型不正确', 'acct_usage_type'); + } + if(empty($this->data['id_card'])) { + throw new ParamsException('证件号不能为空', 'id_card'); + } + if(empty($this->data['id_card_type'])) { + throw new ParamsException('证件类型不能为空', 'id_card_type'); + } + if(!in_array($this->data['id_card_type'], [Constant::ID_TYPE_CARD])) { + throw new ParamsException('证件类型不支持', 'id_card_type'); + } + if(empty($this->data['user_id'])) { + throw new ParamsException('用户ID不能为空', 'user_id'); + } + if(!empty($this->data['ret_url'])) { + StrFilterHelper::fileUrl($this->data['ret_url']); + } + if(!empty($this->data['bg_ret_url'])) { + StrFilterHelper::fileUrl($this->data['bg_ret_url']); + } + + return $this->data; + } +} \ No newline at end of file diff --git a/src/factory/object/market/OpenAccountResponse.php b/src/factory/object/market/OpenAccountResponse.php new file mode 100644 index 0000000..65090bf --- /dev/null +++ b/src/factory/object/market/OpenAccountResponse.php @@ -0,0 +1,15 @@ +data['order_id'])) { + throw new ParamsException('订单ID不能为空', 'order_id'); + } + $params["order_id"] = $this->data['order_id']; + if(empty($this->data['order_date'])) { + throw new ParamsException('订单日期不能为空', 'order_date'); + } + $params["order_date"] = $this->data['order_date']; + if(empty($this->data['org_order_id'])) { + throw new ParamsException('原支付的订单号不能为空', 'org_order_id'); + } + $params["org_order_id"] = $this->data['org_order_id']; + if(empty($this->data['org_order_date'])) { + throw new ParamsException('原支付订单日期不能为空', 'org_order_date'); + } + $params["org_order_date"] = $this->data['org_order_date']; + if(empty($this->data['org_trans_type'])) { + throw new ParamsException('原交易类型不能为空', 'org_trans_type'); + } + $params["org_trans_type"] = $this->data['org_trans_type']; + if(empty($this->data['trans_amt'])) { + throw new ParamsException('订单确认金额不能为空', 'trans_amt'); + } + if($this->data['div_details'] != null) { + $params["div_details"] = json_encode($this->data['div_details'], JSON_UNESCAPED_UNICODE); + } + if($this->data['dev_info_json'] != null) { + $params['dev_info_json'] = $this->data['dev_info_json']->toJson(); + } + if(!empty($this->data['mer_priv'])) { + $params['mer_priv'] = $this->data['mer_priv']; + } + if(!empty($this->data['extension'])) { + $params['extension'] = $this->data['extension']; + } + if(!empty($this->data['share_fee_mode'])) { + $params['share_fee_mode'] = $this->data['share_fee_mode']; + } + + return $params; + } +} \ No newline at end of file diff --git a/src/factory/object/trade/DeviceInfo.php b/src/factory/object/trade/DeviceInfo.php new file mode 100644 index 0000000..39c70d4 --- /dev/null +++ b/src/factory/object/trade/DeviceInfo.php @@ -0,0 +1,79 @@ +devType = $devType; + $self->ipAddr = $ipAddr; + $self->MAC = $mac; + + return $self; + } + + private array $data; + + public function __get($name) + { + if(array_key_exists($name, $this->data)) { + return $this->data[$name]; + } + return null; + } + + public function __set($name, $value) + { + $this->data[$name] = $value; + } + + + public function toJSON(): string + { + if(empty($this->data['devType'])) { + throw new ParamsException('设备类型不能为空', 'devInfoJson.devType'); + } + if(empty($this->data['ipAddr'])) { + throw new ParamsException('设备IP地址不能为空', 'devInfoJson.ipAddr'); + } + if(empty($this->data['MAC']) && empty($this->data['IMEI'])) { + throw new ParamsException('设备MAC或IMEI不能为空', 'devInfoJson.MAC|devInfoJson.IMEI'); + } + return json_encode($this->data, JSON_UNESCAPED_UNICODE); + } +} \ No newline at end of file diff --git a/src/factory/object/trade/DivDetail.php b/src/factory/object/trade/DivDetail.php new file mode 100644 index 0000000..5dfa368 --- /dev/null +++ b/src/factory/object/trade/DivDetail.php @@ -0,0 +1,43 @@ +divCustId = $divCustId; + $self->divAcctId = $divAcctId; + $self->divAmt = $amount; + $self->riskDivType = $riskDivType; + return $self; + } + + private array $data; + + public function __get($name) + { + if(array_key_exists($name, $this->data)) { + return $this->data[$name]; + } + return null; + } + + public function __set($name, $value) + { + $this->data[$name] = $value; + } + + + public function toJSON(): string + { + return json_encode($this->data, JSON_UNESCAPED_UNICODE); + } +} \ No newline at end of file diff --git a/src/factory/object/trade/ObjectInfo.php b/src/factory/object/trade/ObjectInfo.php new file mode 100644 index 0000000..fb2c613 --- /dev/null +++ b/src/factory/object/trade/ObjectInfo.php @@ -0,0 +1,54 @@ +marketType = $marketType; + return $self; + } + + private array $data; + + public function __get($name) + { + if(array_key_exists($name, $this->data)) { + return $this->data[$name]; + } + return null; + } + + public function __set($name, $value) + { + $this->data[$name] = $value; + } + + + public function toJSON(): string + { + return json_encode($this->data, JSON_UNESCAPED_UNICODE); + } +} \ No newline at end of file diff --git a/src/factory/object/trade/RefundGoodsRequest.php b/src/factory/object/trade/RefundGoodsRequest.php new file mode 100644 index 0000000..2b56c94 --- /dev/null +++ b/src/factory/object/trade/RefundGoodsRequest.php @@ -0,0 +1,73 @@ +data['order_date'])) { + throw new ParamsException('订单日期不能为空', 'order_date'); + } + if(empty($this->data['order_id'])) { + throw new ParamsException('订单号不能为空', 'order_id'); + } + if(empty($this->data['org_order_date'])) { + throw new ParamsException('原交易订单日期不能为空', 'org_order_date'); + } + $params['org_order_date'] = $this->data['org_order_date']; + if(empty($this->data['org_order_id'])) { + throw new ParamsException('原交易订单号不能为空', 'org_order_id'); + } + + if(empty($this->data['trans_amt'])) { + throw new ParamsException('退款金额不能为空', 'trans_amt'); + } + if(!empty($params['div_details'])) { + $params['div_details'] = json_encode($this->data['div_details'], JSON_UNESCAPED_UNICODE); + } + + if(!empty($this->data['remark'])) { + $params['remark'] = $this->data['remark']; + } + if(!empty($this->data['bg_ret_url'])) { + $params['bg_ret_url'] = StrFilterHelper::fileUrl($this->data['bg_ret_url']); + } + if(!empty($this->data['mer_priv'])) { + $params['mer_priv'] = $this->data['mer_priv']; + } + if(!empty($this->data['extension'])) { + $params['extension'] = $this->data['extension']; + } + if(!empty($this->data['dev_info_json'])) { + $params['dev_info_json'] = $this->data['dev_info_json']->toJSON(); + } + return $params; + } +} \ No newline at end of file diff --git a/src/factory/object/trade/RefundGoodsResponse.php b/src/factory/object/trade/RefundGoodsResponse.php new file mode 100644 index 0000000..868f293 --- /dev/null +++ b/src/factory/object/trade/RefundGoodsResponse.php @@ -0,0 +1,24 @@ +data['order_date'])) { + throw new ParamsException('订单日期不能为空', 'order_date'); + } + $params['order_date'] = $this->data['order_date']; + + if(empty($this->data['order_id'])) { + throw new ParamsException('订单号不能为空', 'order_id'); + } + $params['order_id'] = $this->data['order_id']; + if(empty($this->data['org_order_date'])) { + throw new ParamsException('原交易订单日期不能为空', 'org_order_date'); + } + $params['org_order_date'] = $this->data['org_order_date']; + if(empty($this->data['org_order_id'])) { + throw new ParamsException('原交易订单号不能为空', 'org_order_id'); + } + if(!empty($params['trans_type'])) { + $params['trans_type'] = $this->data['trans_type']; + } + if(empty($this->data['trans_amt'])) { + throw new ParamsException('退款金额不能为空', 'trans_amt'); + } + if(!empty($params['div_details'])) { + $params['div_details'] = json_encode($this->data['div_details'], JSON_UNESCAPED_UNICODE); + } + if(!empty($this->data['remark'])) { + $params['remark'] = $this->data['remark']; + } + if(!empty($this->data['bg_ret_url'])) { + $params['bg_ret_url'] = StrFilterHelper::fileUrl($this->data['bg_ret_url']); + } + if(!empty($this->data['mer_priv'])) { + $params['mer_priv'] = $this->data['mer_priv']; + } + if(!empty($this->data['extension'])) { + $params['extension'] = $this->data['extension']; + } + if(!empty($this->data['dev_info_json'])) { + $params['dev_info_json'] = $this->data['dev_info_json']->toJSON(); + } + return $params; + } + +} \ No newline at end of file diff --git a/src/factory/object/trade/UnifiedOrder.php b/src/factory/object/trade/UnifiedOrder.php new file mode 100644 index 0000000..dce3de3 --- /dev/null +++ b/src/factory/object/trade/UnifiedOrder.php @@ -0,0 +1,21 @@ +data['order_date'])) { + throw new ParamsException('订单日期不能为空', 'order_date'); + } + $params['order_date'] = $this->data['order_date']; + + if(empty($this->data['order_id'])) { + throw new ParamsException('订单号不能为空', 'order_id'); + } + $params['order_id'] = $this->data['order_id']; + if(empty($this->data['object_info'])) { + throw new ParamsException('藏品信息不能为空', 'object_info'); + } + $objectInfo = $this->data['object_info']; + if($objectInfo->marketType == null) { + throw new ParamsException('藏品交易市场类型不正确', 'object_info.marketType'); + } + if($objectInfo->marketType == Constant::MARKET_MASTER) { + if($this->data['user_name'] == null && $this->data['user_cust_id'] == null) { + throw new ParamsException('付款人信息不能为空', 'user_name|user_cust_id'); + } + //证件信息 + if($this->data['user_cust_id'] == null) { + if($this->data['id_card_type'] == null) { + throw new ParamsException('用户证件信息不能为空', 'id_card_type'); + } + if($this->data['id_card'] == null) { + throw new ParamsException('用户证件号不能为空', 'id_card'); + } + } + } else if($objectInfo->marketType == Constant::MARKET_SUB) { + if($this->data['user_cust_id'] == null) { + throw new ParamsException('用户客户号不能为空', 'user_cust_id'); + } + } + if($this->data['user_name']) { + $params['user_name'] = $this->data['user_name']; + } + if($this->data['user_cust_id'] != null) { + $params['user_cust_id'] = $this->data['user_cust_id']; + } + if($this->data['id_card_type'] == null) { + $params['id_card_type'] = $this->data['id_card_type']; + } + if($this->data['id_card'] != null) { + $params['id_card'] = $this->data['id_card']; + } + if(!empty($this->data['div_type'])) { + $params['div_type'] = $this->data['div_type']; + } + + if($this->data['div_details'] != null && empty($this->data['div_details'])) { + //分账 + $params['div_details'] = json_encode($this->data['div_details']); + } + if($this->data['trans_amt'] == null) { + throw new ParamsException('交易金额不能为空', 'trans_amt'); + } + if($this->data['order_expire_time'] != null) { + $params['order_expire_time'] = $this->data['order_expire_time']; + } + if($this->data['ret_url'] != null) { + $params['ret_url'] = $this->data['ret_url']; + } + if(!empty($this->data['bg_ret_url'])) { + $params['bg_ret_url'] = StrFilterHelper::fileUrl($this->data['bg_ret_url']); + } + if(!empty($this->data['mer_priv'])) { + $params['mer_priv'] = $this->data['mer_priv']; + } + if(!empty($this->data['extension'])) { + $params['extension'] = $this->data['extension']; + } + if(!empty($this->data['goods_tag'])) { + $params['goods_tag'] = $this->data['goods_tag']; + } + if(!empty($this->data['attach_info'])) { + $params['attach_info'] = $this->data['attach_info']; + } + if(!empty($this->data['goods_desc'])) { + $params['goods_desc'] = $this->data['goods_desc']; + } + if(!empty($this->data['goods_tag'])) { + $params['goods_tag'] = $this->data['goods_tag']; + } + if(!empty($this->data['goods_type'])) { + $params['goods_type'] = $this->data['goods_type']; + } + if(empty($this->data['dev_info_json'])) { + throw new ParamsException('设备信息不能为了', 'dev_info_json'); + } + $devInfoJson = $this->data['dev_info_json']; + $devInfoStr = $devInfoJson->toJSON(); + $params['dev_info_json'] = $devInfoStr; + + //藏品 + if(empty($this->data['object_info'])) { + throw new ParamsException('藏品信息不能为空', 'object_info'); + } + $params['object_info'] = $this->data['object_info']->toJSON(); + if(!empty($this->data['wx_app_id'])) { + $params['wx_app_id'] = $this->data['wx_app_id']; + } + if(!empty($this->data['wx_applet_app_id'])) { + $params['wx_applet_app_id'] = $this->data['wx_applet_app_id']; + } + if(!empty($this->data['ali_app_id'])) { + $params['ali_app_id'] = $this->data['ali_app_id']; + } + if(!empty($this->data['term_type'])) { + $params['term_type'] = $this->data['term_type']; + } + if(!empty($this->data['limit_pay'])) { + $params['limit_pay'] = $this->data['limit_pay']; + } + if(!empty($this->data['face_license_key'])) { + $params['face_license_key'] = $this->data['face_license_key']; + } + + return $params; + } +} \ No newline at end of file diff --git a/src/http/ErrCodeHelper.php b/src/http/ErrCodeHelper.php new file mode 100644 index 0000000..87c6cb8 --- /dev/null +++ b/src/http/ErrCodeHelper.php @@ -0,0 +1,83 @@ + '请求成功', +// 'C00001' => '请求处理中', +// 'C00002' => '请求已受理', +// 'C00003' => '请求失败', +// 'C00005' => '查询失败', +// 'C00006' => '交易关闭', +// 'C00007' => '记录不存在', +// 'C00008' => '用户不存在', +// 'C00098' => '系统超时', +// 'C00099' => '系统异常', +// 'C00097' => '并发异常', +// 'C00096' => '系统繁忙', +// 'C00100' => '请求参数非法', +// 'C00101' => '商户无此接口权限', +// 'C00102' => '验证签名失败', +// 'C00103' => '商户状态异常', +// 'C00104' => '用户状态异常', +// 'C00105' => '账户状态异常', +// 'C00106' => '商户签名未配置', +// 'C00107' => '消息类型与签名内容不一致', +// 'C00108' => '商户客户号与签名内容不一致', +// 'C00109' => '版本号与签名内容不一致', +// 'C00110' => '商户号不存在', +// 'C00111' => '用户客户号不存在', +// 'C00112' => '账号不存在', +// 'C00113' => '页面数据被篡改', +// 'C00114' => '订单号重复', +// 'C00115' => '账户可用余额不足', +// 'C00116' => '商户配置异常', +// 'C00117' => '账户余额查询失败', +// 'C00118' => '未查询到内容', +// 'C00119' => '暂时不支持贷记卡', +// 'C00120' => '银行卡号不正确', +// 'C00121' => '银行卡相关信息不完整或格式不正确', +// 'C00122' => '手续费计算异常', +// 'C00123' => '手续费余额不足', +// 'C00124' => '短信验证码发送手机号与验证手机号不一致', +// 'C00126' => '短信验证码已失效请重新获取', +// 'C00127' => '短信验证码发送接口与接口不一致', +// 'C00128' => '短信验证码发送过于频繁或单日发送次数超限', +// 'C00129' => '请获取短信验证码', +// 'C00130' => '短信验证码不正确', +// 'C00131' => '短信订单号重复', +// 'C00132' => '用户未设置交易密码', +// 'C00133' => '绑卡信息不存在', +// 'C00134' => '手续费金额不得大于等于交易金额', +// 'C00800' => '风控信息验证失败', +// 'C00801' => '单笔交易限制或其他', +// 'C00802' => '商户超限额或限次', +// 'C00803' => '商户未开通该功能权限', +// 'C00804' => '商户交易限制', +// 'C00805' => '人脸识别系统异常', +// 'C00806' => '等待人脸识别校验', +// 'C00807' => '风控待二次校验', +// 'C00808' => '风控二次校验失败', +// 'S00017' => '未知交易类型', +// 'S00018' => '未查询到订单信息', +// 'S00050' => '当前钱包账户未上链', +// 'A40150' => '钱包信息查询失败', +// 'A40155' => '当前用户不是钱包户', +// ]; +// if(isset($err[$code])) { +// throw new BizException($err[$code] . ' ' . $code . ' ' . $desc, $code); +// } + if($code != 'C00000') { + throw new BizException($desc . ' ' . $code, $code); + } + } +} \ No newline at end of file diff --git a/src/http/Factory.php b/src/http/Factory.php new file mode 100644 index 0000000..f91b99b --- /dev/null +++ b/src/http/Factory.php @@ -0,0 +1,47 @@ +client = $client; + } + + public function request(string $method, string $url, array $query = [], array $data = []): Response + { + return $this->client->request($method, $url, $query, $data); + } + + public function get(string $url, array $query = []): Response + { + return $this->client->get($url, $query); + + } + + public function post(string $url, array $data = []): Response + { + return $this->client->post($url, $data); + } + + public function put(string $url, array $data = []): Response + { + return $this->client->put($url, $data); + } + + public function delete(string $url, array $data = []): Response + { + return $this->client->delete($url, $data); + } + + public function postJson(string $url, array $data = []): Response + { + return $this->client->postJson($url, $data); + } +} \ No newline at end of file diff --git a/src/http/IHttpClient.php b/src/http/IHttpClient.php new file mode 100644 index 0000000..5dee64e --- /dev/null +++ b/src/http/IHttpClient.php @@ -0,0 +1,21 @@ +statusCode = $response->getStatusCode(); + + $self->source = $response->getBody()->getContents(); + + if(!empty($self->source)) { + $contentType = $response->getHeader('Content-Type'); + if(empty($contentType)) { + throw new ApiException('反馈的消息未指明内容格式Content-Type'); + } + $bodyFormat = explode(';', $contentType[0]); + switch ($bodyFormat[0]) { + case 'text/html': + case 'application/json': + $self->body = json_decode($self->source, true); + break; + case 'application/xml': + case 'text/plain': + default: + throw new ApiException('反馈的数据格式不支持处理' . $bodyFormat[0]); + } + } + return $self; + } + + public static function tempInit(int $code, array $body) + { + $self = new self(); + $self->statusCode = $code; + $self->body = $body; + return $self; + } + + /** + * @return bool 是否成功 + */ + public function isSuccess(): bool + { + return $this->statusCode >= 200 && $this->statusCode < 300; + } + + public function getCode(): int + { + return $this->statusCode; + } + + public function getBody(): array + { + return $this->body; + } + + public function getSource(): string + { + return $this->source; + } +} \ No newline at end of file diff --git a/src/http/impl/HFPayHttpClient.php b/src/http/impl/HFPayHttpClient.php new file mode 100644 index 0000000..fc6c3ff --- /dev/null +++ b/src/http/impl/HFPayHttpClient.php @@ -0,0 +1,276 @@ +config = $config; + + //先获取Token + $options = $this->getHttpClientOption($config['debug'] ? + self::TEST_ENDPOINT : self::API_ENDPOINT); + $this->client = new Client($options); + + //签名客户端 + $this->signClient = new SignerHttpClient( + $config['sign_server'], + $this->config['sign_file'], + $this->config['sign_pwd'], + $this->config['cert_file']); + } + + /** + * 客户端参数 + * + * @param string $baseUri + * + * @return array + */ + private function getHttpClientOption(string $baseUri): array + { + return [ + 'base_uri' => $baseUri, + 'connect_timeout' => 10, + 'timeout' => 30, + 'headers' => [ + 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36', + 'Accept' => 'application/json', + //'referer' => $baseUrl, + 'Cache-Control' => 'no-cache', + 'accept-encoding' => 'gzip, deflate, br, zstd', + 'accept-language' => 'zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7' + ], + 'allow_redirects' => [ + 'max' => 3, + 'track_redirects' => false, + 'strict' => false, + ], + //'debug' => true, + ]; + } + + /** + * 获取请求参数 + * + * @return array + */ + private function getRequestOptions(): array + { + return [ + 'debug' => false, + //不抛出异常 + 'http_errors' => false, + 'headers' => [ + 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36', + //目前只能接受JSON的数据格式 + 'Accept' => 'application/json', + //'referer' => $baseUrl, + 'Cache-Control' => 'no-cache', + 'accept-encoding' => 'gzip, deflate', + 'accept-language' => 'zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7' + ] + ]; + } + + /** + * @var SignerHttpClient 签名客户端 + */ + private ISigner $signClient; + + + /** + * @param string $method + * @param string $uri + * @param array|null $query + * @param array|null $body + * @param string $format + * + * @return Response + * @throws ApiException + * @throws BizException + * @throws SignException + */ + function request(string $method, string $uri, ?array $query = [], ?array $body = [], string $format = 'form'): Response + { + $options = $this->getRequestOptions(); + + if(!empty($query)) { + $options['query'] = array_filter($query, fn($v) => $v !== null); + } + //添加固定的参数 + //版本号 + if(empty($body['version'])) { + $body['version'] = $this->config['version'] ?? ''; + } + if(empty($body['mer_cust_id'])) { + $body['mer_cust_id'] = $this->config['mid'] ?? ''; + } + if(empty($body['bg_ret_url']) && !empty($this->config['callback'])) { + $body['bg_ret_url'] = $this->config['callback']; + } + + //将内容生成签名 + $sign = $this->signClient->sign($body); + $body['check_value'] = $sign; + +// echo '请求参数 #####'; +// print_r($body); +// echo '请求参数 end'; + if(empty($format)) { + $format = 'form'; + } + switch ($format) { + case 'json': + $options['json'] = array_filter($body, fn($v) => $v !== null); + break; + case 'form': + $options['form_params'] = array_filter($body, fn($v) => $v !== null); + break; + case 'body': + $options['body'] = array_filter($body, fn($v) => $v !== null); + break; + } + + try { + $resp = $this->client->request($method, $uri, $options); + } catch (GuzzleException $e) { + throw new ApiException($e->getMessage()); + } + $response = Response::make($resp); + + if(!$response->isSuccess()) { + throw new ApiException('汇付反馈状态不正确', $resp->getStatusCode()); + } + //验证数据 + $body = $response->getBody(); + if(!$this->signClient->verify($body)) { + throw new ApiException('签名验证未通过'); + } + //解码 + $decodeBody = $this->signClient->decode($body['check_value']); + ErrCodeHelper::parseError($decodeBody['resp_code'], $decodeBody['resp_desc']); + return Response::tempInit(200, $decodeBody); + } + + /** + * @param string $uri + * @param array|null $data + * + * @return Response + * @throws ApiException + * @throws BizException + * @throws SignException + */ + function get(string $uri, ?array $data = []): Response + { + return $this->request('GET', $uri, $data); + } + + /** + * @param string $uri + * @param array|null $data + * + * @return Response + * @throws ApiException + * @throws BizException + * @throws SignException + */ + function post(string $uri, ?array $data = []): Response + { + return $this->request('POST', $uri, [], $data, 'form'); + } + + /** + * @param string $uri + * @param array|null $data + * + * @return Response + * @throws ApiException + * @throws BizException + * @throws SignException + */ + function patch(string $uri, ?array $data = []): Response + { + return $this->request('PATCH', $uri, [], $data, 'body'); + } + + /** + * @param string $uri + * @param array|null $data + * + * @return Response + * @throws ApiException + * @throws BizException + * @throws SignException + */ + function put(string $uri, ?array $data = []): Response + { + return $this->request('PUT', $uri, [], $data, 'body'); + } + + /** + * @param string $uri + * @param array|null $data + * + * @return Response + * @throws ApiException + * @throws BizException + * @throws SignException + */ + function delete(string $uri, ?array $data = []): Response + { + return $this->request('DELETE', $uri, [], $data, 'body'); + } + + /** + * @param string $uri + * @param array|null $data + * @param bool $needToken + * + * @return Response + * @throws ApiException + * @throws BizException + * @throws SignException + */ + function postJson(string $uri, ?array $data = [], bool $needToken = true): Response + { + return $this->request('POST', $uri, [], $data, 'json', $needToken); + } +} \ No newline at end of file diff --git a/src/library/ISigner.php b/src/library/ISigner.php new file mode 100644 index 0000000..bec7dec --- /dev/null +++ b/src/library/ISigner.php @@ -0,0 +1,12 @@ +pfxFileName = $pfxFileName; + $this->pfxFilePwd = $pfxFilePwd; + $this->certFile = $certFile; + $option = [ + 'base_uri' => $server, + 'connect_timeout' => 10, + 'timeout' => 30, + 'headers' => [ + 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36', + 'Accept' => 'application/json', + //'referer' => $baseUrl, + 'Cache-Control' => 'no-cache', + 'accept-encoding' => 'gzip, deflate, br, zstd', + 'accept-language' => 'zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7' + ], + 'allow_redirects' => [ + 'max' => 3, + 'track_redirects' => false, + 'strict' => false, + ], + ]; + $this->client = new Client($option); + } + + private function getRequestOptions(): array + { + return [ + 'debug' => false, + //不抛出异常 + 'http_errors' => false, + 'headers' => [ + 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36', + //目前只能接受JSON的数据格式 + 'Accept' => 'application/json', + //'referer' => $baseUrl, + 'Cache-Control' => 'no-cache', + 'accept-encoding' => 'gzip, deflate', + 'accept-language' => 'zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7' + ] + ]; + } + + /** + * 生成签名数据 + * + * @param array $data + * + * @return string + * @throws ApiException + * @throws BizException + * @throws SignException + */ + function sign(array $data): string + { + if(empty($data)) { + throw new SignException('待签名的数据不能为空'); + } + + $requestData = [ + 'data' => json_encode([ + 'pfx_file_name' => $this->pfxFileName, + 'pfx_file_pwd' => $this->pfxFilePwd, + ], JSON_UNESCAPED_UNICODE), + 'params' => json_encode( + $data, + JSON_UNESCAPED_UNICODE + ), + ]; + $options = $this->getRequestOptions(); + $options['form_params'] = $requestData; + + try { + $result = $this->client->post('/hfpcfca/cfca/makeSign', $options); + $response = Response::make($result); + if(!$response->isSuccess()) { + throw new SignException('签名请求失败,请检查签名服务器 ' . $response->getCode()); + } + $body = $response->getBody(); + //处理错误码 + ErrCodeHelper::parseError($body['resp_code'], $body['resp_desc']); + if(empty($body['check_value'])) { + throw new SignException('签名服务器未反馈任何签名数据'); + } + return $body['check_value']; + } catch (GuzzleException $e) { + throw new SignException('签名服务不可用' . $e->getMessage()); + } + } + + function verify(array $data): bool + { + return true; + } + + /** + * 解码 + * + * @param string $data + * + * @return array + * @throws BizException + * @throws SignException + * @throws ApiException + */ + function decode(string $data): array + { + $requestData = [ + 'params' => json_encode([ + 'check_value' => $data, + 'cert_file' => $this->certFile + ], JSON_UNESCAPED_UNICODE), + ]; + + $options = $this->getRequestOptions(); + $options['form_params'] = $requestData; + + try { + $resp = $this->client->post('/hfpcfca/cfca/verifySign', $options); + } catch (GuzzleException $e) { + throw new ApiException('签名服务器请求失败 ' . $e->getMessage()); + } + $response = Response::make($resp); + if(!$response->isSuccess()) { + throw new SignException('签名检查请求失败,请检查签名服务器'); + } + $body = $response->getBody(); + ErrCodeHelper::parseError($body['resp_code'], $body['resp_desc']); + if(empty($body['params'])) { + return []; + } + return json_decode($body['params'], true); + } +} \ No newline at end of file diff --git a/src/utils/StrFilterHelper.php b/src/utils/StrFilterHelper.php new file mode 100644 index 0000000..7a64b30 --- /dev/null +++ b/src/utils/StrFilterHelper.php @@ -0,0 +1,34 @@ +