From c53bf7df08fce53b4ee2e6abecf572416f35263f Mon Sep 17 00:00:00 2001 From: elf <360197197@qq.com> Date: Wed, 24 May 2023 02:27:40 +0800 Subject: [PATCH] you --- app/Command/JinlingCommand.php | 25 ++ app/Controller/Payment/PayController.php | 72 +++++- app/Helper/Efps/AbstractApi.php | 33 +-- app/Helper/Efps/Api.php | 160 ++++++++++++- app/Helper/Efps/Config.php | 54 ++--- app/Helper/Efps/Request/AbstractRequest.php | 32 --- .../Efps/Request/UnifiedPaymentRequest.php | 216 ------------------ app/Helper/Efps/Result.php | 20 +- app/Helper/Efps/Signer.php | 118 +++++----- app/Helper/StringHelper.php | 11 + app/Request/BindCardConfirmRequest.php | 11 + app/Request/BindCardRequest.php | 11 + app/Request/ProtocolPayConfirmRequest.php | 11 + app/Request/ProtocolPayPreRequest.php | 11 + app/Request/RegisterRequest.php | 11 + app/Request/UnBindCardRequest.php | 11 + certs/dev/efps.cer | 23 ++ certs/dev/user.pfx | Bin 0 -> 2598 bytes certs/prod/efps.cer | 23 ++ certs/prod/user.cer | 22 ++ certs/prod/user.pfx | Bin 0 -> 2560 bytes config/routes.php | 6 + 22 files changed, 490 insertions(+), 391 deletions(-) delete mode 100644 app/Helper/Efps/Request/AbstractRequest.php delete mode 100644 app/Helper/Efps/Request/UnifiedPaymentRequest.php create mode 100644 app/Request/BindCardConfirmRequest.php create mode 100644 app/Request/BindCardRequest.php create mode 100644 app/Request/ProtocolPayConfirmRequest.php create mode 100644 app/Request/ProtocolPayPreRequest.php create mode 100644 app/Request/RegisterRequest.php create mode 100644 app/Request/UnBindCardRequest.php create mode 100644 certs/dev/efps.cer create mode 100644 certs/dev/user.pfx create mode 100644 certs/prod/efps.cer create mode 100644 certs/prod/user.cer create mode 100644 certs/prod/user.pfx diff --git a/app/Command/JinlingCommand.php b/app/Command/JinlingCommand.php index db697a0..8c7fa5f 100644 --- a/app/Command/JinlingCommand.php +++ b/app/Command/JinlingCommand.php @@ -4,10 +4,12 @@ declare(strict_types=1); namespace App\Command; +use App\Helper\Efps\Api; use App\Helper\Signer; use App\Service\AppService; use App\Service\MerchantService; use App\Service\PaymentService; +use Hyperf\Command\Annotation\Command; use Hyperf\Command\Command as HyperfCommand; use Hyperf\Contract\ContainerInterface; @@ -38,6 +40,29 @@ class JinlingCommand extends HyperfCommand public function handle(): void { + /*Api::register([ + 'merId' => "100", // 562238003185933 + 'backUrl' => 'http://www.baidu.com', + 'certificateName' => '测试', + 'lawyerCertType' => 0, + 'lawyerCertNo' => '430481198104234557', + 'lawyerCertPhotoFront' => 'http://5b0988e595225.cdn.sohucs.com/images/20191031/e6a31c2611f042bbbfd39297243437a7.png', + 'lawyerCertPhotoBack' => 'http://5b0988e595225.cdn.sohucs.com/images/20191031/e6a31c2611f042bbbfd39297243437a7.png', + 'certificateTo' => '20500211', + 'contactPhone' => '18888888888', + 'accountType' => 3, + 'canDownType' => '01', + ]);*/ + Api::bindCard([ + 'memberId' => '562238003185933', + 'mchtOrderNo' => time() . rand(1000, 9999), + 'userName' => '测试', + 'phoneNum' => '18888888888', + 'bankCardNo' => '6214835911385365', + 'bankCardType' => 'debit', + 'certificatesNo' => '430481198104234557', + ]); + return; echo \App\Helper\Efps\Signer::sign('sss'); $sm3 = new \OneSm\Sm3(); diff --git a/app/Controller/Payment/PayController.php b/app/Controller/Payment/PayController.php index cb22fc4..08bb9aa 100644 --- a/app/Controller/Payment/PayController.php +++ b/app/Controller/Payment/PayController.php @@ -4,7 +4,15 @@ declare(strict_types=1); namespace App\Controller\Payment; +use App\Exception\BusinessException; +use App\Helper\Efps\Api; +use App\Request\BindCardConfirmRequest; +use App\Request\BindCardRequest; use App\Request\JsapiPayRequest; +use App\Request\ProtocolPayConfirmRequest; +use App\Request\ProtocolPayPreRequest; +use App\Request\RegisterRequest; +use App\Request\UnBindCardRequest; use Hyperf\HttpServer\Contract\RequestInterface; use App\Service\PaymentService; @@ -27,13 +35,63 @@ class PayController extends AbstractController ]); } - public function unified() + public function register(RequestInterface $request) { - $payRequest = new JsapiPayRequest($request->all()); - $order = $this->paymentService->js($payRequest->getApp(), $payRequest->getData()); - return $this->success([ - 'pay_url' => $order->pay_url, - 'order_no' => $order->order_no, - ]); + $req = new RegisterRequest($request->all()); + $result = Api::register($req->getData()); + if (!$result->isSuccess()) { + throw new BusinessException($result->getMessage()); + } + return $this->success($result->getData()); + } + + public function bindCard(RequestInterface $request) + { + $req = new BindCardRequest($request->all()); + $result = Api::bindCard($req->getData()); + if (!$result->isSuccess()) { + throw new BusinessException($result->getMessage()); + } + return $this->success($result->getData()); + } + + public function bindCardConfirm(RequestInterface $request) + { + $req = new BindCardConfirmRequest($request->all()); + $result = Api::bindCardConfirm($req->getData()); + if (!$result->isSuccess()) { + throw new BusinessException($result->getMessage()); + } + return $this->success($result->getData()); + } + + public function unBindCard(RequestInterface $request) + { + $req = new UnBindCardRequest($request->all()); + $result = Api::unBindCard($req->getData()); + if (!$result->isSuccess()) { + throw new BusinessException($result->getMessage()); + } + return $this->success($result->getData()); + } + + public function protocolPayPreRequest(RequestInterface $request) + { + $req = new ProtocolPayPreRequest($request->all()); + $result = Api::protocolPayPre($req->getData()); + if (!$result->isSuccess()) { + throw new BusinessException($result->getMessage()); + } + return $this->success($result->getData()); + } + + public function protocolPayConfirm(RequestInterface $request) + { + $req = new ProtocolPayConfirmRequest($request->all()); + $result = Api::protocolPayConfirm($req->getData()); + if (!$result->isSuccess()) { + throw new BusinessException($result->getMessage()); + } + return $this->success($result->getData()); } } diff --git a/app/Helper/Efps/AbstractApi.php b/app/Helper/Efps/AbstractApi.php index e97f64a..a03d850 100644 --- a/app/Helper/Efps/AbstractApi.php +++ b/app/Helper/Efps/AbstractApi.php @@ -12,12 +12,20 @@ abstract class AbstractApi { protected static $client; - public static function request(AbstractRequest $request) { - $params = $request->getParams(); + protected static $env = 'dev'; + + public static function getConfig($key) + { + $config = Config::get(self::$env); + return $config ? ($config[$key] ?? null) : null; + } + + public static function request($uri, $params, $sign) { try { - Log::info('url:' . $request->getUrl(), [], 'omipay'); - $response = self::getClient()->post($request->getUrl(), [ - 'query' => $params + Log::info('url:' . $uri, [], 'efps'); + $response = self::getClient()->post($uri, [ + 'json' => $params, + 'headers' => self::getXEfpsHeaders($sign), ]); Log::info('request:', $params, 'efps'); $body = (string)$response->getBody(); @@ -43,11 +51,8 @@ abstract class AbstractApi protected static function getClient(): Client { if (!self::$client) { self::$client = new Client([ - 'base_uri' => Config::get('base_url'), + 'base_uri' => self::getConfig('baseUrl'), 'handler' => HandlerStack::create(new CoroutineHandler()), - 'headers' => [ - 'Content-Type' => 'application/json', - ], 'timeout' => 5, 'swoole' => [ 'timeout' => 10, @@ -58,15 +63,13 @@ abstract class AbstractApi return self::$client; } - protected static function getHeaders($sign, $encKey, $timestamp) + protected static function getXEfpsHeaders($sign) { return [ - 'x-efps-sign-no' => '', - 'x-efps-sign-type' => 'RSAwithSHA256', + 'x-efps-sign-no' => self::getConfig('signNo'), + 'x-efps-sign-type' => 'SHA256withRSA', 'x-efps-sign' => $sign, - 'x-efps-timestamp' => $timestamp, - 'x-efps-version' => '2.0', - 'x-efps-enc-key' => $encKey, + 'x-efps-timestamp' => date('YmdHis') ]; } } \ No newline at end of file diff --git a/app/Helper/Efps/Api.php b/app/Helper/Efps/Api.php index adbdbf3..233dd7d 100644 --- a/app/Helper/Efps/Api.php +++ b/app/Helper/Efps/Api.php @@ -7,18 +7,154 @@ use App\Helper\StringHelper; class Api extends AbstractApi { - public static function unifiedPayment($outTradeNo, $orderInfo, $payAmount, $notifyUrl, $redirectUrl) + public static function register($params) { - $request = new UnifiedPaymentRequest(); - $request->setVersion('3.0'); - $request->setOutTradeNo($outTradeNo); - $request->setOrderInfo($orderInfo); - $request->setPayAmount($payAmount); - $request->setNotifyUrl($notifyUrl); - $request->setRedirectUrl($redirectUrl); - $request->setTransactionStartTime(date('YmdHis')); - $request->setAreaInfo([]); - $request->setNonceStr(StringHelper::getRandomString(32)); - return self::request($request); + $merId = $params['merId'] ?? ''; + $backUrl = $params['backUrl'] ?? ''; + $certificateName = $params['certificateName'] ?? ''; + $lawyerCertType = $params['lawyerCertType'] ?? 0; + $lawyerCertNo = $params['lawyerCertNo'] ?? ''; + $lawyerCertPhotoFront = $params['lawyerCertPhotoFront'] ?? ''; + $lawyerCertPhotoBack = $params['lawyerCertPhotoBack'] ?? ''; + $certificateTo = $params['certificateTo'] ?? ''; + $contactPhone = $params['contactPhone'] ?? ''; + $accountType = $params['accountType'] ?? 3; + $canDownType = $params['canDownType'] ?? '01'; + + $uri = '/api/cust/SP/Personal/apply'; + $params = [ + 'version' => '3.0', + 'acqSpId' => self::getConfig('customerCode'), + 'merId' => $merId, + 'backUrl' => $backUrl, + 'certificateName' => $certificateName, + 'lawyerCertType' => $lawyerCertType, + 'lawyerCertNo' => $lawyerCertNo, + 'lawyerCertPhotoFront' => $lawyerCertPhotoFront, + 'lawyerCertPhotoBack' => $lawyerCertPhotoBack, + 'certificateTo' => $certificateTo, + 'contactPhone' => $contactPhone, + 'accountType' => $accountType, + 'canDownType' => $canDownType, + ]; + $sign = Signer::sign(json_encode($params)); + return self::request($uri, $params, $sign); + } + + public static function bindCard($params) + { + $memberId = $params['memberId'] ?? ''; + $mchtOrderNo = $params['mchtOrderNo'] ?? StringHelper::generateOrderNo(); + $userName = $params['userName'] ?? ''; + $phoneNum = $params['phoneNum'] ?? ''; + $bankCardNo = $params['bankCardNo'] ?? ''; + $bankCardType = $params['bankCardType'] ?? ''; + $certificatesNo = $params['certificatesNo'] ?? ''; + $expired = $params['expired'] ?? ''; + $cvn = $params['cvn'] ?? ''; + + $uri = '/api/txs/protocol/bindCard'; + $params = [ + 'version' => '3.0', + 'customerCode' => self::getConfig('customerCode'), + 'mchtOrderNo' => $mchtOrderNo, + 'memberId' => $memberId, + 'userName' => Signer::publicEncrypt($userName), + 'phoneNum' => Signer::publicEncrypt($phoneNum), + 'bankCardNo' => Signer::publicEncrypt($bankCardNo), + 'bankCardType' => $bankCardType, + 'certificatesType' => '01', + 'certificatesNo' => Signer::publicEncrypt($certificatesNo), + 'nonceStr' => StringHelper::getRandomString(32), + ]; + if ($bankCardType == 'credit') { + $params['expired'] = Signer::publicEncrypt($expired); + $params['cvn'] = Signer::publicEncrypt($cvn); + } + $sign = Signer::sign(json_encode($params)); + return self::request($uri, $params, $sign); + } + + public static function bindCardConfirm($params) + { + $smsNo = $params['smsNo'] ?? ''; + $smsCode = $params['smsCode'] ?? ''; + $memberId = $params['memberId'] ?? ''; + $uri = '/api/txs/protocol/bindCardConfirm'; + $params = [ + 'version' => '3.0', + 'customerCode' => self::getConfig('customerCode'), + 'smsNo' => $smsNo, + 'memberId' => $memberId, + 'smsCode' => $smsCode, + 'nonceStr' => StringHelper::getRandomString(32), + ]; + $sign = Signer::sign(json_encode($params)); + return self::request($uri, $params, $sign); + } + + public static function unBindCard($params) + { + $protocol = $params['protocol'] ?? ''; + $memberId = $params['memberId'] ?? ''; + $uri = '/api/txs/protocol/unBindCard'; + $params = [ + 'version' => '3.0', + 'customerCode' => self::getConfig('customerCode'), + 'protocol' => $protocol, + 'memberId' => $memberId, + 'nonceStr' => StringHelper::getRandomString(32), + ]; + $sign = Signer::sign(json_encode($params)); + return self::request($uri, $params, $sign); + } + + public static function protocolPayPre($params) + { + $outTradeNo = $params['outTradeNo'] ?? StringHelper::generateOrderNo(); + $protocol = $params['protocol'] ?? ''; + $smsNo = $params['smsNo'] ?? ''; + $smsCode = $params['smsCode'] ?? ''; + $payAmount = $params['payAmount'] ?? 0; + $orderInfo = []; + $orderInfo['Id'] = $outTradeNo; + $orderInfo['businessType'] = '130001'; + $orderInfo['goodsList'] = [['name' => 'pay', 'number' => 'one', 'amount' => $$payAmount]]; + + $uri = '/api/txs/protocol/protocolPayPre'; + $params = [ + 'version' => '3.0', + 'customerCode' => self::getConfig('customerCode'), + 'outTradeNo' => $outTradeNo, + 'protocol' => $protocol, + // 'smsNo' => $smsNo, + // 'smsCode' => $smsCode, + 'orderInfo' => $orderInfo, + 'payAmount' => $payAmount, + 'payCurrency' => 'CNY', + 'isInstalments' => 0, + 'transactionStartTime' => date('YmdHis'), + 'nonceStr' => StringHelper::getRandomString(32), + ]; + $sign = Signer::sign(json_encode($params)); + return self::request($uri, $params, $sign); + } + + public static function protocolPayConfirm($params) + { + $smsCode = $params['smsCode'] ?? ''; + $protocol = $params['protocol'] ?? ''; + $token = $params['token'] ?? ''; + $uri = '/api/txs/protocol/protocolPayConfirm'; + $params = [ + 'version' => '3.0', + 'customerCode' => self::getConfig('customerCode'), + 'token' => $token, + 'protocol' => $protocol, + 'smsCode' => $smsCode, + 'nonceStr' => StringHelper::getRandomString(32), + ]; + $sign = Signer::sign(json_encode($params)); + return self::request($uri, $params, $sign); } } \ No newline at end of file diff --git a/app/Helper/Efps/Config.php b/app/Helper/Efps/Config.php index e142f13..baab405 100644 --- a/app/Helper/Efps/Config.php +++ b/app/Helper/Efps/Config.php @@ -4,42 +4,26 @@ namespace App\Helper\Efps; class Config { - private static $params = [ - 'app_id' => '', - 'secret_key' => 'cea6f34bea8640dea91fd8b7a926a9a5', - 'base_url' => 'https://efps.epylinks.cn', - 'public_key' => '', - 'private_key' => '-----BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7ibPHSAc2glW5 -ZHKhSJR8SHTwkAlN9H0KhiX+NzxuZirUd8VhyPKFcOeSvf+lRo8P7EHd2Hsfly39 -SuTZv4LUqsU0ODw0bXuC7ys1P6kctUc8Pi5Zg7l2FpVQZIwxkszevuYmjSQ7NTA7 -GAS/4pZX6C4yyDObQBV5nmSnhGXzKk/z3c6O829jgVlbFMAKKF//Q4TBiooOeZpY -rgkGtaZSN50puM+WDn7S/ETwZELBi2jRDqg6bJKYsCVPHW/yPdnMS0BMQcDhJsI8 -3BwNvBiJQo4yZ0RmLw3SmIjcThieo74fcHH0Le0TFZRD1dl2OOgPO4lCFxFV6BM0 -wARseqddAgMBAAECggEBAIXZb2XBQ9ykw3hRd/si6U+XC1eTBgEMiZ5URpOdatVE -uDby0P7MxEN3ZOB4GRkmNf9gWVZ0JtRSO3G33YSISmFtDNkLdfTZWzkFaKpVqGaj -/5bArqYW/OyKi8FYMjNDmlM0nuFPBVf4y1ax+tnVaAaP4UE/YI3i/DDUWvSw627U -PqTAAEEeJi624UsVIVLbiWQkaztiutPQYTxONYKt1xcI3OUUBETvRy621czlWIhO -I/bbThawyu4xLrN8eDa6KSeD+LvC3u5IumroDCgoKrOSuY3GesWN7rPm1uVTMCDe -CacQkc2Nx28PiiRFj8XiVzSdHFIEiNopLpHlIv6U+6ECgYEA9DlBhqZoX8pxc2Nq -tm+MLXeY16lr9W23em+51TIEgBnMSne2+yY4+ni0b4x0buFXgH7bR2mSoHzVDvjJ -nYA82YlYtKI6QiSUsKU0WDfzPWqUrTfkt2JUjIwBBFN4vY070Kcu3bV7CsoXLg2E -E6ThtRn7VCQ/+8R/uyU+kuWDXxkCgYEAxJS008rYLfDl6Mqt4gBWdX0OOmck+jQ7 -nT5c/sPggi222z3ka+RGWRERbG40EXiP6xL0AEQ5GJhYITKODnd8XmeOQyMnlFwP -uD33cjhwdOhH2EYTW7KUumo1Dc1ZfPThNk3SnVLvTPBD9cli4vbsU73YhvQp/BDA -3aw3RhscBuUCgYEA5AJ4nL/L/nLBDNuqi30FQIXCGsbAVjkC7bpVoye5b+emBXhT -S5NZ6u66dtKI+eREj2DgVIHKNS+Wsw2vHe7V4LsMKEi1X39LmsgCYMKLw7E38aiX -TmbtTPKBGIrd1QqA58LOTIvcviwDDCnuP3DWkQAa12momuPP5OdWzkqdJjECgYEA -w1kbURRUO2MWtX0jymCXim1ZhEQXhOP/EcV1WF6CbhrLiZc5tNXF6qCBdgUVjP8H -1YyiGNmy+3P4sBSzAkFOv+mcf68hl9bccDRz/3eCmUpyisMoXYlbLtx4GF0mPnyC -iRpQ37IYx5ZDkq4rrGvAcX9I+uMMDccAQcjvrKUn9tkCgYAcx/Usx7dsJyq9qt0x -nPryFkGoDjUg4U5WFvN4d4et4oMDAvA4iFkPxdLlGokYtLC0cEsAmKEqVKVs/Zfa -FyqcOZZSfgy6c90YmbXTmEHIayR05IxVRrL0T+/CGtnYGsNPLGNCSL9eU7zxA+QW -HeJnufFo3VmRzqOQcqrLfi/scQ== ------END PRIVATE KEY-----' + private static array $params = [ + 'dev' => [ + 'customerCode' => '562265003122220', + 'signNo' => '562265003122220003', + 'baseUrl' => 'http://test-efps.epaylinks.cn', + 'privateKeyFilePath' => BASE_PATH . '/certs/dev/user.pfx', + 'publicKeyFilePath' => BASE_PATH . '/certs/dev/efps.cer', + 'privateKeyPassword' => '123456', + ], + 'prod' => [ + 'customerCode' => '562276004021027', + 'signNo' => '562276004021027002', + 'baseUrl' => 'https://efps.epaylinks.cn', + 'privateKeyFilePath' => BASE_PATH . '/certs/prod/user.pfx', + 'publicKeyFilePath' => BASE_PATH . '/certs/prod/efps.cer', + 'privateKeyPassword' => 'iUixTxtl8N2Ntlx1LqZ', + ] ]; - public static function get($key) { - return self::$params[$key] ?: null; + public static function get($env): ?array { + return self::$params[$env] ?: null; } } \ No newline at end of file diff --git a/app/Helper/Efps/Request/AbstractRequest.php b/app/Helper/Efps/Request/AbstractRequest.php deleted file mode 100644 index 8469291..0000000 --- a/app/Helper/Efps/Request/AbstractRequest.php +++ /dev/null @@ -1,32 +0,0 @@ -uri; - } - - public function getParams() { - return $this->_params; - } - - public function __get($name) - { - return $this->_params[$name]; - } - - public function __set($name, $value) - { - $this->_params[$name] = $value; - } -} \ No newline at end of file diff --git a/app/Helper/Efps/Request/UnifiedPaymentRequest.php b/app/Helper/Efps/Request/UnifiedPaymentRequest.php deleted file mode 100644 index 068aee6..0000000 --- a/app/Helper/Efps/Request/UnifiedPaymentRequest.php +++ /dev/null @@ -1,216 +0,0 @@ -version = $version; - } - - /** - * @param mixed $outTradeNo - */ - public function setOutTradeNo($outTradeNo): void - { - $this->outTradeNo = $outTradeNo; - } - - /** - * @param mixed $customerCode - */ - public function setCustomerCode($customerCode): void - { - $this->customerCode = $customerCode; - } - - /** - * @param mixed $clientIp - */ - public function setClientIp($clientIp): void - { - $this->clientIp = $clientIp; - } - - /** - * @param mixed $orderInfo - */ - public function setOrderInfo($orderInfo): void - { - $this->orderInfo = $orderInfo; - } - - /** - * @param mixed $payAmount - */ - public function setPayAmount($payAmount): void - { - $this->payAmount = $payAmount; - } - - /** - * @param mixed $payCurrency - */ - public function setPayCurrency($payCurrency): void - { - $this->payCurrency = $payCurrency; - } - - /** - * @param mixed $noCreditCards - */ - public function setNoCreditCards($noCreditCards): void - { - $this->noCreditCards = $noCreditCards; - } - - /** - * @param mixed $notifyUrl - */ - public function setNotifyUrl($notifyUrl): void - { - $this->notifyUrl = $notifyUrl; - } - - /** - * @param mixed $redirectUrl - */ - public function setRedirectUrl($redirectUrl): void - { - $this->redirectUrl = $redirectUrl; - } - - /** - * @param mixed $attachData - */ - public function setAttachData($attachData): void - { - $this->attachData = $attachData; - } - - /** - * @param mixed $transactionStartTime - */ - public function setTransactionStartTime($transactionStartTime): void - { - $this->transactionStartTime = $transactionStartTime; - } - - /** - * @param mixed $transactionEndTime - */ - public function setTransactionEndTime($transactionEndTime): void - { - $this->transactionEndTime = $transactionEndTime; - } - - /** - * @param mixed $payMethod - */ - public function setPayMethod($payMethod): void - { - $this->payMethod = $payMethod; - } - - /** - * @param mixed $subAppId - */ - public function setSubAppId($subAppId): void - { - $this->subAppId = $subAppId; - } - - /** - * @param mixed $channelMchtNo - */ - public function setChannelMchtNo($channelMchtNo): void - { - $this->channelMchtNo = $channelMchtNo; - } - - /** - * @param mixed $enablePayChannels - */ - public function setEnablePayChannels($enablePayChannels): void - { - $this->enablePayChannels = $enablePayChannels; - } - - /** - * @param mixed $instalmentsNum - */ - public function setInstalmentsNum($instalmentsNum): void - { - $this->instalmentsNum = $instalmentsNum; - } - - /** - * @param mixed $storeId - */ - public function setStoreId($storeId): void - { - $this->storeId = $storeId; - } - - /** - * @param mixed $alipayStoreId - */ - public function setAlipayStoreId($alipayStoreId): void - { - $this->alipayStoreId = $alipayStoreId; - } - - /** - * @param mixed $extUserInfo - */ - public function setExtUserInfo($extUserInfo): void - { - $this->extUserInfo = $extUserInfo; - } - - /** - * @param mixed $terminalInfo - */ - public function setTerminalInfo($terminalInfo): void - { - $this->terminalInfo = $terminalInfo; - } - - /** - * @param mixed $areaInfo - */ - public function setAreaInfo($areaInfo): void - { - $this->areaInfo = $areaInfo; - } - - /** - * @param mixed $extendParams - */ - public function setExtendParams($extendParams): void - { - $this->extendParams = $extendParams; - } - - /** - * @param mixed $coupons - */ - public function setCoupons($coupons): void - { - $this->coupons = $coupons; - } - - /** - * @param mixed $nonceStr - */ - public function setNonceStr($nonceStr): void - { - $this->nonceStr = $nonceStr; - } -} \ No newline at end of file diff --git a/app/Helper/Efps/Result.php b/app/Helper/Efps/Result.php index 5bb6bb5..b0861dd 100644 --- a/app/Helper/Efps/Result.php +++ b/app/Helper/Efps/Result.php @@ -23,16 +23,18 @@ class Result return $this->data[$key] ?? null; } - public function toArray() + public function getMessage() { - $data = []; - if ($this->isSuccess()) { - $data['is_success'] = true; - $data['pay_url'] = $this->get('pay_url'); - } else { - $data['error_code'] = $this->get('error_code'); - $data['error_msg'] = $this->get('error_msg'); - } + return $this->data['returnMsg']; + } + + public function getData() + { + $data = $this->data; + unset($data['returnCode']); + unset($data['returnMsg']); + unset($data['nonceStr']); + unset($data['customerCode']); return $data; } } \ No newline at end of file diff --git a/app/Helper/Efps/Signer.php b/app/Helper/Efps/Signer.php index eaebfdb..98024ec 100644 --- a/app/Helper/Efps/Signer.php +++ b/app/Helper/Efps/Signer.php @@ -2,82 +2,70 @@ namespace App\Helper\Efps; +use App\Exception\BusinessException; use App\Helper\StringHelper; class Signer { - private static $privateKey = '-----BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7ibPHSAc2glW5 -ZHKhSJR8SHTwkAlN9H0KhiX+NzxuZirUd8VhyPKFcOeSvf+lRo8P7EHd2Hsfly39 -SuTZv4LUqsU0ODw0bXuC7ys1P6kctUc8Pi5Zg7l2FpVQZIwxkszevuYmjSQ7NTA7 -GAS/4pZX6C4yyDObQBV5nmSnhGXzKk/z3c6O829jgVlbFMAKKF//Q4TBiooOeZpY -rgkGtaZSN50puM+WDn7S/ETwZELBi2jRDqg6bJKYsCVPHW/yPdnMS0BMQcDhJsI8 -3BwNvBiJQo4yZ0RmLw3SmIjcThieo74fcHH0Le0TFZRD1dl2OOgPO4lCFxFV6BM0 -wARseqddAgMBAAECggEBAIXZb2XBQ9ykw3hRd/si6U+XC1eTBgEMiZ5URpOdatVE -uDby0P7MxEN3ZOB4GRkmNf9gWVZ0JtRSO3G33YSISmFtDNkLdfTZWzkFaKpVqGaj -/5bArqYW/OyKi8FYMjNDmlM0nuFPBVf4y1ax+tnVaAaP4UE/YI3i/DDUWvSw627U -PqTAAEEeJi624UsVIVLbiWQkaztiutPQYTxONYKt1xcI3OUUBETvRy621czlWIhO -I/bbThawyu4xLrN8eDa6KSeD+LvC3u5IumroDCgoKrOSuY3GesWN7rPm1uVTMCDe -CacQkc2Nx28PiiRFj8XiVzSdHFIEiNopLpHlIv6U+6ECgYEA9DlBhqZoX8pxc2Nq -tm+MLXeY16lr9W23em+51TIEgBnMSne2+yY4+ni0b4x0buFXgH7bR2mSoHzVDvjJ -nYA82YlYtKI6QiSUsKU0WDfzPWqUrTfkt2JUjIwBBFN4vY070Kcu3bV7CsoXLg2E -E6ThtRn7VCQ/+8R/uyU+kuWDXxkCgYEAxJS008rYLfDl6Mqt4gBWdX0OOmck+jQ7 -nT5c/sPggi222z3ka+RGWRERbG40EXiP6xL0AEQ5GJhYITKODnd8XmeOQyMnlFwP -uD33cjhwdOhH2EYTW7KUumo1Dc1ZfPThNk3SnVLvTPBD9cli4vbsU73YhvQp/BDA -3aw3RhscBuUCgYEA5AJ4nL/L/nLBDNuqi30FQIXCGsbAVjkC7bpVoye5b+emBXhT -S5NZ6u66dtKI+eREj2DgVIHKNS+Wsw2vHe7V4LsMKEi1X39LmsgCYMKLw7E38aiX -TmbtTPKBGIrd1QqA58LOTIvcviwDDCnuP3DWkQAa12momuPP5OdWzkqdJjECgYEA -w1kbURRUO2MWtX0jymCXim1ZhEQXhOP/EcV1WF6CbhrLiZc5tNXF6qCBdgUVjP8H -1YyiGNmy+3P4sBSzAkFOv+mcf68hl9bccDRz/3eCmUpyisMoXYlbLtx4GF0mPnyC -iRpQ37IYx5ZDkq4rrGvAcX9I+uMMDccAQcjvrKUn9tkCgYAcx/Usx7dsJyq9qt0x -nPryFkGoDjUg4U5WFvN4d4et4oMDAvA4iFkPxdLlGokYtLC0cEsAmKEqVKVs/Zfa -FyqcOZZSfgy6c90YmbXTmEHIayR05IxVRrL0T+/CGtnYGsNPLGNCSL9eU7zxA+QW -HeJnufFo3VmRzqOQcqrLfi/scQ== ------END PRIVATE KEY-----'; - - private static $publicKey = ''; - - public static function sign($params, $timestamp, $version = 2) + protected static $env = 'dev'; + + private static function getConfig($key) { - ksort($params); - $paramRows = []; - foreach ($params as $key => $value) { - $paramRows[] = $key . '=' . $value; - } - $queryString = implode('&', $paramRows); - if ($version == 1) { - return self::rsaWithSHA256Sign($queryString, self::$privateKey); - } - $randomKey = StringHelper::getRandomString(32); - $encKey = self::rsaWithSHA256Encypt($randomKey, self::$publicKey); - $sm3 = new \OneSm\Sm3(); - $signBody = $sm3->sign($queryString . $timestamp . $encKey); - return self::rsaWithSHA256Sign($signBody, self::$privateKey); + $config = Config::get(self::$env); + return $config ? ($config[$key] ?? null) : null; } - public static function rsaWithSHA256Sign($content, $privateKey) - { - $key = openssl_get_privatekey($privateKey); - // openssl_private_encrypt($content, $signature, $privateKey, OPENSSL_PKCS1_PADDING); - openssl_sign($content, $signature, $key, OPENSSL_ALGO_SHA256); - openssl_free_key($key); - return base64_encode($signature); + public static function sign($data) { + $certs = []; + openssl_pkcs12_read( + file_get_contents(self::getConfig('privateKeyFilePath')), + $certs, + self::getConfig('privateKeyPassword') + ); //其中password为你的证书密码 + + if (empty($certs)) { + throw new BusinessException('请检查RSA私钥配置'); + } + + openssl_sign($data, $sign, $certs['pkey'],OPENSSL_ALGO_SHA256); + $sign = base64_encode($sign); + return $sign; } - public static function rsaWithSHA256Encypt($content, $publicKey) - { - $key = openssl_get_publickey($publicKey); - openssl_public_decrypt($content, $signature, $key, OPENSSL_PKCS1_PADDING); - openssl_free_key($key); - return base64_encode($signature); + public static function verify($data, $sign) { + + //读取公钥文件 + $pubKey = file_get_contents(self::getConfig('publicKeyFilePath')); + + $res = openssl_get_publickey($pubKey); + + if (empty($res)) { + throw new BusinessException('RSA公钥错误, 请检查公钥文件格式是否正确'); + } + //调用openssl内置方法验签,返回bool值 + + $result = (bool)openssl_verify($data, base64_decode($sign), $res, OPENSSL_ALGO_SHA256); + // 释放资源 + openssl_free_key($res); + + return $result; } - public function verify($content, $sign, $publicKey) + public static function publicEncrypt($data) { - $key = openssl_get_publickey($publicKey); - $ok = openssl_verify($content, base64_decode($sign), $key, 'SHA256'); - openssl_free_key($key); - return $ok; - } + //读取公钥文件 + $pubKey = file_get_contents(self::getConfig('publicKeyFilePath')); + $res = openssl_get_publickey($pubKey); + + if (empty($res)) { + throw new BusinessException('RSA公钥错误, 请检查公钥文件格式是否正确'); + } + + $crypttext = ""; + openssl_public_encrypt($data,$crypttext, $res ); + openssl_free_key($res); + + return(base64_encode($crypttext)); + } } \ No newline at end of file diff --git a/app/Helper/StringHelper.php b/app/Helper/StringHelper.php index 122849c..6aff791 100644 --- a/app/Helper/StringHelper.php +++ b/app/Helper/StringHelper.php @@ -35,4 +35,15 @@ class StringHelper return $password; } + + public static function generateOrderNo() { + $now = time(); + $key = RedisKey::getGenerateOrderNoKey($now); + $incrId = Redis::incr($key); + $incrId = '' . $incrId; + Redis::expire($key, 5*60); + $padLength = 6 - strlen($incrId); + $incrId = str_pad($incrId, $padLength, '0', STR_PAD_LEFT); + return date('YmdHis', $now) . $incrId; + } } \ No newline at end of file diff --git a/app/Request/BindCardConfirmRequest.php b/app/Request/BindCardConfirmRequest.php new file mode 100644 index 0000000..ee328c4 --- /dev/null +++ b/app/Request/BindCardConfirmRequest.php @@ -0,0 +1,11 @@ +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 literal 0 HcmV?d00001 diff --git a/certs/prod/efps.cer b/certs/prod/efps.cer new file mode 100644 index 0000000..5e9bc00 --- /dev/null +++ b/certs/prod/efps.cer @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID0jCCArqgAwIBAgIJAP7Hut/02gF6MA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV +BAYTAkdaMQswCQYDVQQIDAJHRDELMAkGA1UEBwwCR1oxDTALBgNVBAoMBEVQQVkx +EjAQBgNVBAsMCWVwYXlsaW5rczENMAsGA1UEAwwEeWlscjEjMCEGCSqGSIb3DQEJ +ARYUeWlsaXJlbkBlcGF5bGlua3MuY24wHhcNMjMwNDEzMDE1MDM5WhcNMzMwNDEw +MDE1MDM5WjB+MQswCQYDVQQGEwJHWjELMAkGA1UECAwCR0QxCzAJBgNVBAcMAkda +MQ0wCwYDVQQKDARFUEFZMRIwEAYDVQQLDAllcGF5bGlua3MxDTALBgNVBAMMBHlp +bHIxIzAhBgkqhkiG9w0BCQEWFHlpbGlyZW5AZXBheWxpbmtzLmNuMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1SL1lt41Phow3aZKQhB0zXNCX4DNqMkr +88/auTe/Lq7llBNYWtPJeABg6K+8JHAs6L90yzQN3mUU3k4gKFLCJPQEIlK9sPKA +AJQnDAs+3PUucgl1QCJnmeSRQKQCGfFjmsGOn+deyV/Lx4gHd3ytpErUDM1iEfen +YHQACuGlRK9G7w90V0UnF7YJca5FCB/wzL3QjqcQC6Z9e/bYXQES7fQH8vX+ajBr +gvi9nMVjFugsZk5sLkwGeYuY4/HMuUWQ8wW+Fvu8cJKtb48sqTl1eTPMhKPwgrgY +EB+RIBA4Ls/gF4/7/dQJA18xFS60Dbn7LS6IP6OTkO9BP4k+qi0kLQIDAQABo1Mw +UTAdBgNVHQ4EFgQUZlpKcM3zf5c8fed6tWnRVVQfC4AwHwYDVR0jBBgwFoAUZlpK +cM3zf5c8fed6tWnRVVQfC4AwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsF +AAOCAQEAyIhFS5AzMVNYXqjRxkKIgdEFt3DXVWermW6L/w0S774ebflQT7fabZhP ++NirUB9VP2hM4axZEhXSXYBeRMw9ahs/It0OrBNbmH+N4bIUVLdGeMoGpjjV5vvd +OV5Tagna7hhaK0F3iBOnjDiO17INxMiRx9yt7n41ml+4VLAbaLY1l0CGouNmrkzT +15CxLnB29m0Xq3V+YsLfDV5ohUNtbFX3NrBUXSHUUf5n8YWqJWTQl2TkeMoYAJbs +WSdkZGNB5KGDUORRPjabpEs2+0+uGHgVr3Llxc3rEVgLSX0wFe1ytLbIW0ibmQV+ +tpiZYSSfN6lzzX9NnQk3zGLv+UfIag== +-----END CERTIFICATE----- diff --git a/certs/prod/user.cer b/certs/prod/user.cer new file mode 100644 index 0000000..b3793f2 --- /dev/null +++ b/certs/prod/user.cer @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDlzCCAn+gAwIBAgIUbeOvQXeD5BI4jOTpkUv+70RShvcwDQYJKoZIhvcNAQEL +BQAwWzELMAkGA1UEBhMCQ04xDzANBgNVBAgMBkZ1SmlhbjEPMA0GA1UEBwwGWGlh +TWVuMQ4wDAYDVQQKDAVGdXN1bzEMMAoGA1UECwwDY3d4MQwwCgYDVQQDDANjd3gw +HhcNMjMwNTIyMTQ0MjE4WhcNMzMwNTE5MTQ0MjE4WjBbMQswCQYDVQQGEwJDTjEP +MA0GA1UECAwGRnVKaWFuMQ8wDQYDVQQHDAZYaWFNZW4xDjAMBgNVBAoMBUZ1c3Vv +MQwwCgYDVQQLDANjd3gxDDAKBgNVBAMMA2N3eDCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBALuJs8dIBzaCVblkcqFIlHxIdPCQCU30fQqGJf43PG5mKtR3 +xWHI8oVw55K9/6VGjw/sQd3Yex+XLf1K5Nm/gtSqxTQ4PDRte4LvKzU/qRy1Rzw+ +LlmDuXYWlVBkjDGSzN6+5iaNJDs1MDsYBL/illfoLjLIM5tAFXmeZKeEZfMqT/Pd +zo7zb2OBWVsUwAooX/9DhMGKig55mliuCQa1plI3nSm4z5YOftL8RPBkQsGLaNEO +qDpskpiwJU8db/I92cxLQExBwOEmwjzcHA28GIlCjjJnRGYvDdKYiNxOGJ6jvh9w +cfQt7RMVlEPV2XY46A87iUIXEVXoEzTABGx6p10CAwEAAaNTMFEwHQYDVR0OBBYE +FF9zWOWuZ8LYknEJf5MfrFIP/HGqMB8GA1UdIwQYMBaAFF9zWOWuZ8LYknEJf5Mf +rFIP/HGqMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAEC4LnW7 +J69MLiUt2ypYjSmhyauJixINdG6r53JUaKw2d0Dd73PmYmU1lyTFJEOOFhK3w9hU +5cgExxcn1AZdb2vtzwETv7dgRJHbxVVhl6JsPvFw82b9ALGgFbq5d2Kyewa5eLBh +2IiThu19BBbvs0H6MjP7Kim1fx0z/aIK9F5b9zJxFw35t35Jz1cziXbpS2ha2HDS +0EKrqESbX37iZ8jzO4sMb5hPDZ+BuugFOmZbbPoHqqKZw1l9EWO4Lm36PezMTpwP +1dGRRjcxRWcZ9k4zbdmxzNGL+yxSjl4kvTDjr8ygQmTGBrH7f2z/5LaU48IeNZtl +eEM6UAUyVJDtLl4= +-----END CERTIFICATE----- diff --git a/certs/prod/user.pfx b/certs/prod/user.pfx new file mode 100644 index 0000000000000000000000000000000000000000..777eb7825ed47e11f6f0f9fea8b366c67eee3f88 GIT binary patch literal 2560 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