初始化代码

master
X14XA\shengli 1 week ago
parent 5c15b77747
commit ca55df873a
  1. 5
      .gitignore
  2. 109
      README.md
  3. 18
      composer.json
  4. 19
      phpunit.xml
  5. 92
      src/Constant.php
  6. 105
      src/HFPayClient.php
  7. 28
      src/exception/ApiException.php
  8. 30
      src/exception/BizException.php
  9. 14
      src/exception/InitException.php
  10. 30
      src/exception/ParamsException.php
  11. 14
      src/exception/SignException.php
  12. 58
      src/factory/BaseFactory.php
  13. 221
      src/factory/impl/ManageFactory.php
  14. 86
      src/factory/impl/MarketFactory.php
  15. 214
      src/factory/impl/QueryFactory.php
  16. 132
      src/factory/impl/TradeFactory.php
  17. 22
      src/factory/object/RequestObj.php
  18. 23
      src/factory/object/ResponseObj.php
  19. 30
      src/factory/object/manage/BindCashCard.php
  20. 24
      src/factory/object/manage/CertificateInfo.php
  21. 18
      src/factory/object/manage/DownloadFiles.php
  22. 25
      src/factory/object/manage/UnBindCard.php
  23. 78
      src/factory/object/market/OpenAccountRequest.php
  24. 15
      src/factory/object/market/OpenAccountResponse.php
  25. 15
      src/factory/object/market/WalletManageInfoResponse.php
  26. 17
      src/factory/object/query/BalanceInfo.php
  27. 21
      src/factory/object/query/BindCardStatus.php
  28. 21
      src/factory/object/query/ChainBalance.php
  29. 33
      src/factory/object/query/OpenStatus.php
  30. 23
      src/factory/object/query/RecordLog.php
  31. 43
      src/factory/object/query/TransStatus.php
  32. 21
      src/factory/object/query/WalletStatus.php
  33. 17
      src/factory/object/trade/CloseOrder.php
  34. 28
      src/factory/object/trade/DelayDivide.php
  35. 71
      src/factory/object/trade/DelayDivideRequest.php
  36. 79
      src/factory/object/trade/DeviceInfo.php
  37. 43
      src/factory/object/trade/DivDetail.php
  38. 54
      src/factory/object/trade/ObjectInfo.php
  39. 73
      src/factory/object/trade/RefundGoodsRequest.php
  40. 24
      src/factory/object/trade/RefundGoodsResponse.php
  41. 24
      src/factory/object/trade/RefundInfo.php
  42. 74
      src/factory/object/trade/RefundRequest.php
  43. 21
      src/factory/object/trade/UnifiedOrder.php
  44. 172
      src/factory/object/trade/UnifiedOrderRequest.php
  45. 83
      src/http/ErrCodeHelper.php
  46. 47
      src/http/Factory.php
  47. 21
      src/http/IHttpClient.php
  48. 78
      src/http/Response.php
  49. 276
      src/http/impl/HFPayHttpClient.php
  50. 12
      src/library/ISigner.php
  51. 162
      src/library/impl/SignerHttpClient.php
  52. 34
      src/utils/StrFilterHelper.php

5
.gitignore vendored

@ -0,0 +1,5 @@
composer.lock
.idea/
vendor/
tests/
.phpunit.result.cache

@ -1,3 +1,110 @@
# hfpay-lib
# 说明
汇付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
<?php
$config = [
//使用环境
'debug' => 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)
```

@ -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/"
}
}
}

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
colors="true">
<testsuites>
<testsuite name="Application Test Suite">
<directory suffix="Test.php">tests</directory>
</testsuite>
</testsuites>
<!-- <filter>-->
<!-- <whitelist processUncoveredFilesFromWhitelist="true">-->
<!-- <directory suffix=".php">./app</directory>-->
<!-- </whitelist>-->
<!-- </filter>-->
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./app</directory>
</include>
</coverage>
</phpunit>

@ -0,0 +1,92 @@
<?php
namespace Tansilu\HfPayLib;
class Constant
{
/**
* 证件类型:身份证
*/
const ID_TYPE_CARD = '10';
/**
* 用于购买的钱包,仅用于购买
*/
const USE_TO_BUY = 'buyers_wallet';
/**
* 用于购买交易的钱包,可用于收款和转账
*/
const USE_TO_TRANS = 'wallet';
/**
* 交易类型
* 1 企业开户 2 个体户开户 5快捷绑卡 6取现绑卡 7个人开户 8钱包户
*/
const TRANS_TYPE_COMPANY = 1;
const TRANS_TYPE_USER = 2;
const TRANS_TYPE_QUICK_CARD = 5;
const TRANS_TYPE_CASH_CARD = 6;
const TRANS_TYPE_PERSON = 7;
const TRANS_TYPE_WALLET = 8;
const CARD_TYPE_USER = '0';
const CARD_TYPE_COMPANY = '1';
/**
* 取现卡 标识符 0非取现卡 1取现卡
*/
const CARD_CASH_FLAG_NONE = 0;
const CARD_CASH_FLAG_ACTIVE = 1;
/**
* 文件类型
*/
const FILE_TYPE_CHARGE = '1';
const FILE_TYPE_CASH = '2';
const FILE_TYPE_PAY = '3';
const FILE_TYPE_TRANSFER = '4';
const FILE_TYPE_ORDER = '5';
const FILE_TYPE_REFUND = '6';
/**
* 担保阶段
* A:担保入账;
* B:担保出账;
* C:担保完成
*/
const GUARANTEE_STAGE_IN = 'A';
const GUARANTEE_STAGE_OUT = 'B';
const GUARANTEE_STAGE_COMPLETE = 'C';
/**
* 分账类型 0实时分账 1延时分账
*/
const DIV_TYPE_REAL = 0;
const DIV_TYPE_LAZY = 1;
/**
* 终端类型
*/
const TERMINAL_WX_APPLET = 1;
const TERMINAL_ALI_APPLET = 2;
const TERMINAL_APP = 3;
const TERMINAL_H5 = 4;
/**
* 藏品市场类型 1一级市场 2二级市场
*/
const MARKET_MASTER = 1;
const MARKET_SUB = 2;
/**
* 设备类型
*/
const DEVICE_TYPE_PC = 1;
const DEVICE_TYPE_MOBILE = 2;
/**
* 分账用途 1:主交易金额:2:商户服务费:3:其他费用
* */
const RISK_DIV_TYPE_MASTER = 1;
const RISK_DIV_TYPE_FEE = 2;
const RISK_DIV_TYPE_OTHER = 3;
}

@ -0,0 +1,105 @@
<?php
namespace Tansilu\HfPayLib;
use DI\Container;
use DI\ContainerBuilder;
use DI\DependencyException;
use DI\NotFoundException;
use Tansilu\HfPayLib\exception\InitException;
use Tansilu\HfPayLib\factory\impl\ManageFactory;
use Tansilu\HfPayLib\factory\impl\MarketFactory;
use Tansilu\HfPayLib\factory\impl\QueryFactory;
use Tansilu\HfPayLib\factory\impl\TradeFactory;
use Tansilu\HfPayLib\http\IHttpClient;
use Tansilu\HfPayLib\http\impl\HFPayHttpClient;
class HFPayClient
{
public static ?HFPayClient $instance = null;
public static function getInstance(): HFPayClient
{
if(self::$instance == null) {
self::$instance = new HFPayClient();
}
return self::$instance;
}
private Container $container;
/**
* @param array $config
*
* @return HFPayClient
* @throws InitException
* @throws \Exception
*/
public function init(array $config): HFPayClient
{
$builder = new ContainerBuilder();
//添加配置
$this->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);
}
}

@ -0,0 +1,28 @@
<?php
namespace Tansilu\HfPayLib\exception;
use Exception;
use Throwable;
class ApiException extends Exception
{
protected string $errCode;
public function getErrCode(): string
{
return $this->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);
}
}

@ -0,0 +1,30 @@
<?php
namespace Tansilu\HfPayLib\exception;
use Exception;
use Throwable;
class BizException extends Exception
{
protected string $errCode;
public function getErrCode(): string
{
return $this->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);
}
}

@ -0,0 +1,14 @@
<?php
namespace Tansilu\HfPayLib\exception;
use Exception;
use Throwable;
class InitException extends Exception
{
public function __construct($message = "", $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

@ -0,0 +1,30 @@
<?php
namespace Tansilu\HfPayLib\exception;
use Exception;
use Throwable;
class ParamsException extends Exception
{
protected string $errCode;
public function getErrCode(): string
{
return $this->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);
}
}

@ -0,0 +1,14 @@
<?php
namespace Tansilu\HfPayLib\exception;
use Exception;
use Throwable;
class SignException extends Exception
{
public function __construct($message = "", $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

@ -0,0 +1,58 @@
<?php
namespace Tansilu\HfPayLib\factory;
use Tansilu\HfPayLib\exception\ApiException;
use Tansilu\HfPayLib\exception\BizException;
use Tansilu\HfPayLib\http\Factory;
use Tansilu\HfPayLib\http\Response;
class BaseFactory extends Factory
{
/**
* JSON 请求
*
* @param string $uri
* @param array $data
*
* @return array
* @throws ApiException
* @throws BizException
*/
public function processJson(string $uri, array $data): array
{
$response = $this->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'] ?? [];
}
}

@ -0,0 +1,221 @@
<?php
namespace Tansilu\HfPayLib\factory\impl;
use Tansilu\HfPayLib\Constant;
use Tansilu\HfPayLib\exception\ParamsException;
use Tansilu\HfPayLib\factory\object\manage\BindCashCard;
use Tansilu\HfPayLib\factory\object\manage\CertificateInfo;
use Tansilu\HfPayLib\factory\object\manage\DownloadFiles;
use Tansilu\HfPayLib\factory\object\manage\UnBindCard;
use Tansilu\HfPayLib\http\Factory;
class ManageFactory extends Factory
{
/**
* 绑定取现卡
*
* @param string $customId
* @param string $orderId
* @param string $orderDate
* @param string $cardNo
* @param string $cardType
* @param string $bankId
* @param string $branchName
* @param string $cardProv
* @param string $cardArea
* @param string $cashFlag
* @param string $bgRetUrl
* @param string $attach
* @param string $extension
*
* @return BindCashCard|null
* @throws ParamsException
*
* @ses https://hfpay.cloudpnr.com/customers/nft/#/manage/bindCashCard
*/
function bindCashCard(
string $customId,
string $orderId,
string $orderDate,
string $cardNo,
string $cardType = Constant::CARD_TYPE_USER,
string $bankId = '',
string $branchName = '',
string $cardProv = '',
string $cardArea = '',
string $cashFlag = Constant::CARD_CASH_FLAG_NONE,
string $bgRetUrl = '',
string $attach = '',
string $extension = ''
): ?BindCashCard
{
$params = [
'user_cust_id' => $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());
}
}

@ -0,0 +1,86 @@
<?php
namespace Tansilu\HfPayLib\factory\impl;
use Tansilu\HfPayLib\Constant;
use Tansilu\HfPayLib\exception\BizException;
use Tansilu\HfPayLib\exception\ParamsException;
use Tansilu\HfPayLib\factory\BaseFactory;
use Tansilu\HfPayLib\factory\object\market\OpenAccountRequest;
use Tansilu\HfPayLib\factory\object\market\OpenAccountResponse;
use Tansilu\HfPayLib\factory\object\market\WalletManageInfoResponse;
use Tansilu\HfPayLib\factory\object\query\WalletStatus;
/**
* 综合市场管理
*/
class MarketFactory extends BaseFactory
{
/**
* 开户
*
* @throws BizException
* @throws ParamsException
*
* @see https://hfpay.cloudpnr.com/customers/nft/#/compositeMarket/walletOpenAccount
*/
public function create(
OpenAccountRequest $params
): OpenAccountResponse
{
$request = $params->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());
}
}

@ -0,0 +1,214 @@
<?php
namespace Tansilu\HfPayLib\factory\impl;
use Tansilu\HfPayLib\exception\ParamsException;
use Tansilu\HfPayLib\factory\BaseFactory;
use Tansilu\HfPayLib\factory\object\query\BalanceInfo;
use Tansilu\HfPayLib\factory\object\query\BindCardStatus;
use Tansilu\HfPayLib\factory\object\query\ChainBalance;
use Tansilu\HfPayLib\factory\object\query\OpenStatus;
use Tansilu\HfPayLib\factory\object\query\RecordLog;
use Tansilu\HfPayLib\factory\object\query\TransStatus;
use Tansilu\HfPayLib\factory\object\query\WalletStatus;
class QueryFactory extends BaseFactory
{
/**
* 开户状态查询
*
* @param string $orderId
* @param string $orderDate
* @param string $transType
*
* @return ?OpenStatus
*
* @ses https://hfpay.cloudpnr.com/customers/nft/#/query/openAccountQuery
*/
function openStatus(string $orderId, string $orderDate, string $transType): ?OpenStatus
{
$result = $this->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());
}
}

@ -0,0 +1,132 @@
<?php
namespace Tansilu\HfPayLib\factory\impl;
use Tansilu\HfPayLib\exception\ParamsException;
use Tansilu\HfPayLib\factory\object\trade\CloseOrder;
use Tansilu\HfPayLib\factory\object\trade\DelayDivide;
use Tansilu\HfPayLib\factory\object\trade\DelayDivideRequest;
use Tansilu\HfPayLib\factory\object\trade\RefundGoodsRequest;
use Tansilu\HfPayLib\factory\object\trade\RefundGoodsResponse;
use Tansilu\HfPayLib\factory\object\trade\RefundInfo;
use Tansilu\HfPayLib\factory\object\trade\RefundRequest;
use Tansilu\HfPayLib\factory\object\trade\UnifiedOrder;
use Tansilu\HfPayLib\factory\object\trade\UnifiedOrderRequest;
use Tansilu\HfPayLib\http\Factory;
/**
* 交易接口
*/
class TradeFactory extends Factory
{
/**
* 统一下单
*
* @param UnifiedOrderRequest $request
*
* @return UnifiedOrder|null
* @throws ParamsException
* @see https://hfpay.cloudpnr.com/customers/nft/#/transaction/unifiedOrder
*/
function unifiedOrder(
UnifiedOrderRequest $request
): ?UnifiedOrder
{
$params = $request->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());
}
}

@ -0,0 +1,22 @@
<?php
namespace Tansilu\HfPayLib\factory\object;
trait RequestObj
{
private array $data;
public function __set($name, $value)
{
$this->data[$name] = $value;
}
public function __get($name)
{
if(isset($this->data[$name])) {
return $this->data[$name];
}
return null;
}
}

@ -0,0 +1,23 @@
<?php
namespace Tansilu\HfPayLib\factory\object;
trait ResponseObj
{
public static function make(array $data): self
{
$self = new self();
$self->data = $data;
return $self;
}
private array $data;
public function __get($name)
{
if(array_key_exists($name, $this->data)) {
return $this->data[$name];
}
return null;
}
}

@ -0,0 +1,30 @@
<?php
namespace Tansilu\HfPayLib\factory\object\manage;
use Tansilu\HfPayLib\factory\object\ResponseObj;
/**
* 取现卡绑定信息
*
* @property-read $resp_code 应答返回码
* @property-read $resp_desc 应答返回描述
* @property-read $mer_cust_id 商户唯一标识号
* @property-read $user_cust_id 用户唯一标识号
* @property-read $acct_id 用户账户号
* @property-read $order_date 订单日期
* @property-read $order_id 订单号
* @property-read $platform_seq_id 本平台交易唯一标识号
* @property-read $bank_id 银行代号
* @property-read $card_num 银行卡号
* @property-read $bind_card_id 绑定ID
* @property-read $card_prov 银行卡开户省份
* @property-read $card_area 银行卡开户地区
* @property-read $mer_priv 商户私有域
* @property-read $extension 扩展域
*
*/
class BindCashCard
{
use ResponseObj;
}

@ -0,0 +1,24 @@
<?php
namespace Tansilu\HfPayLib\factory\object\manage;
use Tansilu\HfPayLib\factory\object\ResponseObj;
/**
* @property-read $resp_code 应答返回码
* @property-read $resp_desc 应答返回描述
* @property-read $mer_cust_id 商户客户号
* @property-read $order_id 交易订单号
* @property-read $order_date 交易订单日期
* @property-read $org_order_id 原交易订单号
* @property-read $org_order_date 原交易订单日期
* @property-read $guarantee_stage 交易阶段
* @property-read $error_order_id 无效订单号
* @property-read $bg_ret_url 异步通知地址
* @property-read $mer_priv 商户私有域
* @property-read $extension 扩展域
*/
class CertificateInfo
{
use ResponseObj;
}

@ -0,0 +1,18 @@
<?php
namespace Tansilu\HfPayLib\factory\object\manage;
use Tansilu\HfPayLib\factory\object\ResponseObj;
/**
* 文件下载信息
*
* @property-read $resp_code 应答返回码
* @property-read $resp_desc 应答返回描述
* @property-read $mer_cust_id 商户客户号
* @property-read $file_infos 文件信息列表
*/
class DownloadFiles
{
use ResponseObj;
}

@ -0,0 +1,25 @@
<?php
namespace Tansilu\HfPayLib\factory\object\manage;
use Tansilu\HfPayLib\factory\object\ResponseObj;
/**
* 解绑银行卡
*
* @property-read $resp_code 应答返回码
* @property-read $resp_desc 应答返回描述
* @property-read $mer_cust_id 商户唯一标识号
* @property-read $user_cust_id 用户唯一标识号
* @property-read $order_date 订单日期
* @property-read $order_id 订单号
* @property-read $bind_card_id 银行卡绑定ID
* @property-read $card_buss_type 银行卡类型
* @property-read $bg_ret_url 后台返回地址
* @property-read $mer_priv 商户私有域
* @property-read $extension 扩展域
*/
class UnBindCard
{
use ResponseObj;
}

@ -0,0 +1,78 @@
<?php
namespace Tansilu\HfPayLib\factory\object\market;
use Tansilu\HfPayLib\Constant;
use Tansilu\HfPayLib\exception\ParamsException;
use Tansilu\HfPayLib\factory\object\RequestObj;
use Tansilu\HfPayLib\utils\StrFilterHelper;
/**
* @property string $order_date 订单日期
* @property string $order_id 订单号
* @property string $user_name 用户姓名
* @property string $market_type 应用市场
* @property string $acct_usage_type 账户用途
* @property string $id_card 证件号
* @property string $id_card_type 证件类型
* @property string $user_id 用户ID
* @property string $user_mobile 手机号
* @property string $ret_url 页面回调地址
* @property string $mer_priv 商户私有域
* @property string $extension 扩展域
* @property string $bg_ret_url 商户后台应答地址
* @property string $face_license_key 人脸licenseKey
*/
class OpenAccountRequest
{
use RequestObj;
/**
* @throws ParamsException
*/
public function toParams(): array
{
if(empty($this->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;
}
}

@ -0,0 +1,15 @@
<?php
namespace Tansilu\HfPayLib\factory\object\market;
use Tansilu\HfPayLib\factory\object\ResponseObj;
/**
* 开户
*
* @property-read $redirect_url 跳转地址
*/
class OpenAccountResponse
{
use ResponseObj;
}

@ -0,0 +1,15 @@
<?php
namespace Tansilu\HfPayLib\factory\object\market;
use Tansilu\HfPayLib\factory\object\ResponseObj;
/**
* 账户管理
*
* @property-read $redirect_url 管理跳转地址
*/
class WalletManageInfoResponse
{
use ResponseObj;
}

@ -0,0 +1,17 @@
<?php
namespace Tansilu\HfPayLib\factory\object\query;
use Tansilu\HfPayLib\factory\object\ResponseObj;
/**
* 余额查询
*
*
*
* @see https://hfpay.cloudpnr.com/customers/nft/#/query/balanceQuery
*/
class BalanceInfo
{
use ResponseObj;
}

@ -0,0 +1,21 @@
<?php
namespace Tansilu\HfPayLib\factory\object\query;
use Tansilu\HfPayLib\factory\object\ResponseObj;
/**
* 绑卡信息
*
* @property-read $resp_code 应答返回码 定长6位String 必选 C00000–调用成功
* @property-read $resp_desc 应答返回描述 变长String 必选 返回码的对应中文描述
* @property-read $mer_cust_id 商户客户号 定长16位String 必选 由汇付生成,商户的唯一性标识
* @property-read $user_cust_id 用户客户号 定长16位String 必选 由汇付生成,用户的唯一性标识
* @property-read $card_list 卡列表 String(Json) 必选 绑定的银行卡列表
*
* @see https://hfpay.cloudpnr.com/customers/nft/#/query/bindCardInfoQuery
*/
class BindCardStatus
{
use ResponseObj;
}

@ -0,0 +1,21 @@
<?php
namespace Tansilu\HfPayLib\factory\object\query;
use Tansilu\HfPayLib\factory\object\ResponseObj;
/**
* 链上账户余额
*
* @property-read $resp_code 应答返回码
* @property-read $resp_desc 应答描述
* @property-read $mer_cust_id 商户客户号
* @property-read $user_cust_id 用户客户号
* @property-read $user_id 用户ID
* @property-read $chain_addr 链账户地址
* @property-read $chain_balance 链账户余额
*/
class ChainBalance
{
use ResponseObj;
}

@ -0,0 +1,33 @@
<?php
namespace Tansilu\HfPayLib\factory\object\query;
use Tansilu\HfPayLib\factory\object\ResponseObj;
/**
* 开户状态
*
* @property-read $resp_code 应答返回码
* @property-read $resp_desc 应答描述
* @property-read $mer_cust_id 商户客户号
* @property-read $user_cust_id 用户客户号
* @property-read $order_date 订单日期
* @property-read $order_id 订单号
* @property-read $trans_type 交易类型
* @property-read $trans_stat 交易状态
* @property-read $trans_resp_code 交易返回码
* @property-read $trans_resp_desc 交易返回描述
* @property-read $platform_seq_id 原交易流水号
* @property-read $acct_id 账户信息
* @property-read $fee_cust_id 手续费客户号
* @property-read $fee_amt 手续费金额
* @property-read $fee_acct_id 手续费子账号
* @property-read $mer_priv 商户私有域
* @property-read $extension 扩展域
*
* @see https://hfpay.cloudpnr.com/customers/nft/#/query/openAccountQuery
*/
class OpenStatus
{
use ResponseObj;
}

@ -0,0 +1,23 @@
<?php
namespace Tansilu\HfPayLib\factory\object\query;
use Tansilu\HfPayLib\factory\object\ResponseObj;
/**
* 流水记录
*
* @property-read $resp_code 应答返回码
* @property-read $resp_desc 应答返回描述
* @property-read $mer_cust_id 商户客户号
* @property-read $user_cust_id 用户客户号
* @property-read $acct_id 账户号
* @property-read $total_count 总条数
* @property-read $acct_logs 账务流水
*
* @see https://hfpay.cloudpnr.com/customers/nft/#/query/accountRecordQuery
*/
class RecordLog
{
use ResponseObj;
}

@ -0,0 +1,43 @@
<?php
namespace Tansilu\HfPayLib\factory\object\query;
use Tansilu\HfPayLib\factory\object\ResponseObj;
/**
* 交易状态
*
* @property-read $resp_code 应答返回码 定长6位String 必选 返回码,该字段只对本次查询接口调用负责,不是交易的返回码
* @property-read $resp_desc 应答返回描述 变长String 必选 返回码的对应中文描述
* @property-read $mer_cust_id 商户客户号 定长16位String 必选 由汇付生成,商户的唯一性标识
* @property-read $user_cust_id 用户客户号 定长16位String 可选 由汇付生成,用户唯一性标识
* @property-read $order_id 订单号 变长50位String 必选
* @property-read $order_date 订单日期 定长8位String 必选 格式:yyyyMMdd,例如20180101
* @property-read $trans_type 交易类型 定长2位String 必选
* 1、当请求参数的trans_type=36时,并且这笔交易成功,则返回字段的trans_type=3表示用户实际使用快捷支付,返回字段的trans_type=13表示用户实际使用余额支付,返回字段的trans_type=6表示用户实际使用支付宝正扫支付,返回字段的trans_type=10表示用户实际使用支付宝统一下单支付,返回字段的trans_type=11表示用户实际使用微信公众号支付,返回字段的trans_type=12表示用户实际使用微信小程序支付。
* 2、平台只有知道该笔订单实际使用哪种支付方式时,才能指定后续延时分账确认、退款时的交易类型
* @property-read $trans_stat 交易状态 定长1位String 必选 I:初始 P:处理中 S:成功 F:失败 C:订单关闭
* 表示这笔订单的交易状态,通过该字段来判断这笔交易是交易成功、交易失败、交易处理中
* @property-read $trans_resp_code 交易返回码 定长6位String 可选 数据库中的返回码,没有就不返。
* 示这笔订单的返回码,通过该字段来判断这笔交易是交易成功、交易失败、交易处理中
* @property-read $trans_resp_desc 交易返回描述 变长String 可选 数据库中的返回描述
* @property-read $fee_amt 手续费金额 变长14位String 可选
* @property-read $fee_acct_id 手续费账户号 变长9位String 可选
* @property-read $fee_cust_id 手续费客户号 定长16位String 可选
* @property-read $bg_bank_message 银行返回描述 变长200位String 可选
* @property-read $trans_amt 交易金额 变长14位String 可选 泛指交易金额,金额格式必须保留两位小数,例如2.00
* @property-read $platform_seq_id 原交易流水号 变长20位String 必选 原交易返回的“本平台交易唯一标识号”
* @property-read $out_trans_id 外部订单流水号 变长64位String 可选 指支付宝、微信、银联流水号
* @property-read $div_detail 分账账户串 变长String 可选 库表中的分账串,参见下文中的分账账户串
* @property-read $payer_bank_no 付款方银行编号 变长String 可选
* @property-read $payer_acct_id 付款方银行账号 变长String 可选
* @property-read $payer_acct_nm 付款方银行账户名 变长String 可选
* @property-read $mer_priv 商户私有域 变长120位String 可选
* @property-read $extension 扩展域 变长512位String 可选
*
* @see https://hfpay.cloudpnr.com/customers/nft/#/query/transactionStateQuery
*/
class TransStatus
{
use ResponseObj;
}

@ -0,0 +1,21 @@
<?php
namespace Tansilu\HfPayLib\factory\object\query;
use Tansilu\HfPayLib\factory\object\ResponseObj;
/**
* 钱包状态
*
* @property-read $resp_code 应答返回码
* @property-read $resp_desc 应答描述
* @property-read $user_cust_id 用户客户号
* @property-read $user_name 用户姓名
* @property-read $acct_info_list 账户列表
*
* @see https://hfpay.cloudpnr.com/customers/nft/#/query/walletStateQuery
*/
class WalletStatus
{
use ResponseObj;
}

@ -0,0 +1,17 @@
<?php
namespace Tansilu\HfPayLib\factory\object\trade;
use Tansilu\HfPayLib\factory\object\ResponseObj;
/**
* @property-read $resp_code 应答返回码
* @property-read $resp_desc 应答返回描述
* @property-read $mer_cust_id 商户客户号
* @property-read $order_id 订单号
* @property-read $order_date 订单日期
*/
class CloseOrder
{
use ResponseObj;
}

@ -0,0 +1,28 @@
<?php
namespace Tansilu\HfPayLib\factory\object\trade;
use Tansilu\HfPayLib\factory\object\ResponseObj;
/**
* @property-read $resp_code 应答返回码
* @property-read $resp_desc 应答返回描述
* @property-read $mer_cust_id 商户客户号
* @property-read $order_id 订单号
* @property-read $order_date 订单日期
* @property-read $platform_seq_id 本平台交易唯一标识号
* @property-read $org_order_id 原订单号
* @property-read $org_order_date 原订单日期
* @property-read $org_trans_type 原交易类型
* @property-read $trans_amt 订单确认金额
* @property-read $in_cust_id 入账客户号
* @property-read $in_acct_id 入账账户号
* @property-read $div_details 入账明细
* @property-read $mer_priv 商户私有域
* @property-read $share_fee_mode 手续费分摊模式
* @property-read $extension 扩展域
*/
class DelayDivide
{
use ResponseObj;
}

@ -0,0 +1,71 @@
<?php
namespace Tansilu\HfPayLib\factory\object\trade;
use Tansilu\HfPayLib\exception\ParamsException;
use Tansilu\HfPayLib\factory\object\RequestObj;
/**
* @property string $order_id 订单号
* @property string $order_date 订单日期
* @property string $org_order_id 原订单号
* @property string $org_order_date 原订单日期
* @property string $org_trans_type 原交易类型
* @property string $trans_amt 订单确认金额
* @property array $div_details 分账账户串
* @property DeviceInfo $dev_info_json 设备信息
* @property string $mer_priv 商户私有域
* @property string $extension 扩展域
* @property string $share_fee_mode 手续费分摊模式标志
*/
class DelayDivideRequest
{
use RequestObj;
public function toParams(): array
{
$params = [];
if(empty($this->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;
}
}

@ -0,0 +1,79 @@
<?php
namespace Tansilu\HfPayLib\factory\object\trade;
use Tansilu\HfPayLib\exception\ParamsException;
/**
* @property $devType 设备类型
* @property $ipAddr IP地址
* @property $devSysType 设备系统类型
* @property $UUID UUID
* @property $MAC MAC
* @property $IMEI IMEI码
* @property $IMSI IMSI码
* @property $ICCID ICCID码
* @property $MEID MEID码
* @property $SEID SEID
* @property $ipProvName IP省
* @property $ipCityName IP市
* @property $ipAreaName IP地区
* @property $ipType IP地址类型
* @property $cenX 定位地址(经度)
* @property $cenY 定位地址(纬度)
* @property $ipProvCode 定位地址(省编码)
* @property $ipCityCode 定位地址(市编码)
* @property $ipAreaCode 定位地址(区编码)
* @property $provName 定位地址(省)
* @property $cityName 定位地址(市)
* @property $areaName 定位地址(区)
* @property $unDevCode 用户交易设备(唯一识别码)
* @property $serviceProviderIp 商户服务IP地址
* @property $merUrl 商户网址
*/
class DeviceInfo
{
public static function make(
string $devType,
string $ipAddr,
string $mac
): self
{
$self = new self();
$self->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);
}
}

@ -0,0 +1,43 @@
<?php
namespace Tansilu\HfPayLib\factory\object\trade;
/**
* @property $divCustId 分账客户号
* @property $divAcctId 分账账户号
* @property $divAmt 分账金额
* @property $riskDivType 风控分账用途
*/
class DivDetail
{
public static function make(string $divCustId, string $divAcctId, string $amount, string $riskDivType): self
{
$self = new self();
$self->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);
}
}

@ -0,0 +1,54 @@
<?php
namespace Tansilu\HfPayLib\factory\object\trade;
/**
* 藏品信息
*
* @property string $objectType 藏品系列
* @property string $objectName 藏品名称
* @property string $objectTime 上架时间
* @property int $marketType 藏品交易市场类型
* @property string $objectAddr 藏品合约地址
* @property string $chainBelong 藏品认证网络
* @property string $chainId 藏品tokenId
* @property string $objectStandard 藏品区块链合约标准
* @property string $objectTransactions 藏品交易记录
* @property string $objectIssuerName 发行方名称
* @property string $regMobileNo 买家注册手机号
* @property string $regDate 买家注册时间
* @property string $regIpAddr 买家注册ip地址
* @property string $regCertId 买家注册证件号
* @property string $regCustId 买家注册用户ID
*
*/
class ObjectInfo
{
public static function make(int $marketType): self
{
$self = new self();
$self->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);
}
}

@ -0,0 +1,73 @@
<?php
namespace Tansilu\HfPayLib\factory\object\trade;
use Tansilu\HfPayLib\exception\ParamsException;
use Tansilu\HfPayLib\factory\object\RequestObj;
use Tansilu\HfPayLib\utils\StrFilterHelper;
/**
* @property string $order_date 订单日期
* @property string $order_id 订单号
* @property string $org_order_date 原确认订单日期
* @property string $org_order_id 原确认订单号
* @property string $trans_amt 退货金额
* @property array $div_details 退货明细
* @property string $remark 备注
* @property string $bg_ret_url 后台返回地址
* @property string $mer_priv 商户私有域
* @property string $extension 扩展域
* @property DeviceInfo $dev_info_json 设备静态信息
*/
class RefundGoodsRequest
{
use RequestObj;
/**
* @return array
* @throws ParamsException
*/
public function toParams(): array
{
$params = [];
if(empty($this->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;
}
}

@ -0,0 +1,24 @@
<?php
namespace Tansilu\HfPayLib\factory\object\trade;
use Tansilu\HfPayLib\factory\object\ResponseObj;
/**
* @property $resp_code 应答返回码
* @property $resp_desc 应答返回描述
* @property $mer_cust_id 商户唯一标识号
* @property $order_date 订单日期
* @property $order_id 订单号
* @property $trans_amt 退货金额
* @property $fee_amt 退款手续费
* @property $div_details 退货明细
* @property $platform_seq_id 本平台交易唯一标识号
* @property $bg_ret_url 后台返回地址
* @property $mer_priv 商户私有域
* @property $extension 扩展域
*/
class RefundGoodsResponse
{
use ResponseObj;
}

@ -0,0 +1,24 @@
<?php
namespace Tansilu\HfPayLib\factory\object\trade;
use Tansilu\HfPayLib\factory\object\ResponseObj;
/**
* @property-read $resp_code 应答返回码
* @property-read $resp_desc 应答返回描述
* @property-read $mer_cust_id 商户唯一标识号
* @property-read $order_date 订单日期
* @property-read $order_id 订单号
* @property-read $trans_amt 退款金额
* @property-read $fee_amt 退款手续费
* @property-read $div_details 退款明细
* @property-read $platform_seq_id 本平台交易唯一标识号
* @property-read $bg_ret_url 后台返回地址
* @property-read $mer_priv 商户私有域
* @property-read $extension 扩展域
*/
class RefundInfo
{
use ResponseObj;
}

@ -0,0 +1,74 @@
<?php
namespace Tansilu\HfPayLib\factory\object\trade;
use Tansilu\HfPayLib\exception\ParamsException;
use Tansilu\HfPayLib\factory\object\RequestObj;
use Tansilu\HfPayLib\utils\StrFilterHelper;
/**
* @property $order_date 订单日期
* @property $order_id 订单号
* @property $org_order_date 原交易订单日期
* @property $org_order_id 原交易订单号
* @property $trans_type 原交易类型
* @property $trans_amt 退款金额
* @property array $div_details 退款明细
* @property $remark 备注
* @property $bg_ret_url 后台返回地址
* @property $mer_priv 商户私有域
* @property $extension 扩展域
* @property DeviceInfo $dev_info_json 设备静态信息
*/
class RefundRequest
{
use RequestObj;
public function toParams(): array
{
$params = [];
if(empty($this->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;
}
}

@ -0,0 +1,21 @@
<?php
namespace Tansilu\HfPayLib\factory\object\trade;
use Tansilu\HfPayLib\factory\object\ResponseObj;
/**
* @property-read $resp_code 应答返回码
* @property-read $resp_desc 应答返回描述
* @property-read $mer_cust_id 商户客户号
* @property-read $order_id 订单号
* @property-read $order_date 订单日期
* @property-read $trans_amt 订单金额
* @property-read $mer_priv 商户私有域
* @property-read $uuid 会话ID
* @property-read $pay_url 跳转收银台地址
*/
class UnifiedOrder
{
use ResponseObj;
}

@ -0,0 +1,172 @@
<?php
namespace Tansilu\HfPayLib\factory\object\trade;
use Tansilu\HfPayLib\Constant;
use Tansilu\HfPayLib\exception\ParamsException;
use Tansilu\HfPayLib\factory\object\RequestObj;
use Tansilu\HfPayLib\utils\StrFilterHelper;
/**
* 统一下单请求参数配置
*
* @property string $order_date 订单日期
* @property string $order_id 订单号
* @property string $user_cust_id 用户客户号
* @property string $user_name 用户姓名
* @property string $id_card_type 用户证件类型
* @property string $id_card 用户证件号
* @property string $div_type 分账区分
* @property array $div_details 分账账户串
* @property string $trans_amt 交易金额
* @property string $order_expire_time 订单超时时间
* @property string $ret_url 前台返回地址
* @property string $bg_ret_url 后台返回地址
* @property string $mer_priv 商户私有域
* @property string $extension 扩展域
* @property string $goods_tag 商品标记
* @property string $attach_info 附加信息
* @property string $goods_desc 商品描述
* @property string $goods_type 商品类型
* @property DeviceInfo $dev_info_json 设备信息
* @property ObjectInfo $object_info 藏品信息
* @property string $wx_app_id 微信公众号appId
* @property string $wx_applet_app_id 微信小程序appId
* @property string $ali_app_id 支付宝appId
* @property string $term_type 终端类型
* @property string $limit_pay 禁用贷记卡
* @property string $face_license_key 人脸licenseKey
*/
class UnifiedOrderRequest
{
use RequestObj;
/**
* @return array
* @throws ParamsException
*/
public function toParams(): array
{
$params = [];
if(empty($this->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;
}
}

@ -0,0 +1,83 @@
<?php
namespace Tansilu\HfPayLib\http;
use Tansilu\HfPayLib\exception\BizException;
class ErrCodeHelper
{
/**
* @throws BizException
*/
public static function parseError(string $code, $desc): void
{
// $err = [
// //'C00000' => '请求成功',
// '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);
}
}
}

@ -0,0 +1,47 @@
<?php
namespace Tansilu\HfPayLib\http;
class Factory
{
protected IHttpClient $client;
protected string $base = '';
public function __construct(IHttpClient $client)
{
$this->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);
}
}

@ -0,0 +1,21 @@
<?php
namespace Tansilu\HfPayLib\http;
interface IHttpClient
{
function request(string $method, string $uri, ?array $query = [], ?array $body = [], string $format = 'json'): Response;
function get(string $uri, ?array $data = []): Response;
function post(string $uri, ?array $data = []): Response;
function patch(string $uri, ?array $data = []): Response;
function put(string $uri, ?array $data = []): Response;
function delete(string $uri, ?array $data = []): Response;
function postJson(string $uri, ?array $data = []): Response;
}

@ -0,0 +1,78 @@
<?php
namespace Tansilu\HfPayLib\http;
use Psr\Http\Message\ResponseInterface;
use Tansilu\HfPayLib\exception\ApiException;
class Response
{
/**
* @var int 状态码
*/
private int $statusCode = 200;
private array $body = [];
private string $source;
/**
* @throws ApiException
*/
public static function make(ResponseInterface $response): Response
{
$self = new self();
$self->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;
}
}

@ -0,0 +1,276 @@
<?php
namespace Tansilu\HfPayLib\http\impl;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Tansilu\HfPayLib\exception\ApiException;
use Tansilu\HfPayLib\exception\BizException;
use Tansilu\HfPayLib\exception\InitException;
use Tansilu\HfPayLib\exception\SignException;
use Tansilu\HfPayLib\http\ErrCodeHelper;
use Tansilu\HfPayLib\http\IHttpClient;
use Tansilu\HfPayLib\http\Response;
use Tansilu\HfPayLib\library\impl\SignerHttpClient;
use Tansilu\HfPayLib\library\ISigner;
class HFPayHttpClient implements IHttpClient
{
/**
* 接口地址
*/
private const API_ENDPOINT = 'https://hfpay.cloudpnr.com';
private const TEST_ENDPOINT = 'https://hfpay.testpnr.com';
private const API_VERSION = '10';
/**
* @var Client HTTP客户端
*/
private Client $client;
private array $config;
/**
*
* @throws InitException
*/
public function __construct(array $config = [])
{
if(empty($config['sign_server'])) {
throw new InitException('签名服务器地址不正确');
}
$this->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);
}
}

@ -0,0 +1,12 @@
<?php
namespace Tansilu\HfPayLib\library;
interface ISigner
{
function sign(array $data): string;
function verify(array $data): bool;
function decode(string $data): array;
}

@ -0,0 +1,162 @@
<?php
namespace Tansilu\HfPayLib\library\impl;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Tansilu\HfPayLib\exception\ApiException;
use Tansilu\HfPayLib\exception\BizException;
use Tansilu\HfPayLib\exception\SignException;
use Tansilu\HfPayLib\http\ErrCodeHelper;
use Tansilu\HfPayLib\http\Response;
use Tansilu\HfPayLib\library\ISigner;
class SignerHttpClient implements ISigner
{
private Client $client;
private string $pfxFileName;
private string $pfxFilePwd;
private string $certFile;
public function __construct(
string $server,
string $pfxFileName,
string $pfxFilePwd,
string $certFile
)
{
$this->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);
}
}

@ -0,0 +1,34 @@
<?php
namespace Tansilu\HfPayLib\utils;
use Tansilu\HfPayLib\exception\ParamsException;
class StrFilterHelper
{
public static function fileUrl(string $url): string
{
$disableWord = [
'recv',
'rbsmag',
'cashmag',
'cash',
'ubs',
'acctmag',
'buser',
'busermag',
'mtp',
'mtpmag',
'muser',
'musermag',
'ubsmag',
'mag'
];
foreach ($disableWord as $word) {
if(strpos($url, $word) !== false) {
throw new ParamsException('URL中存在禁用的字符' . $word, 'DISABLED_STRING');
}
}
return $url;
}
}
Loading…
Cancel
Save