From 39b2e501171558f5875cc81793af7af4e8bd52b7 Mon Sep 17 00:00:00 2001 From: xuanchen <122383162@qq.com> Date: Wed, 12 May 2021 11:55:25 +0800 Subject: [PATCH] first commit --- .gitignore | 4 + README.md | 1 + composer.json | 28 ++ config/config.php | 32 +++ src/Action/Init.php | 347 +++++++++++++++++++++++++ src/Action/Order.php | 46 ++++ src/Action/Query.php | 19 ++ src/Action/Refund.php | 46 ++++ src/Action/Sign.php | 25 ++ src/Contracts/CheckCouponContracts.php | 14 + src/Contracts/CouponContracts.php | 30 +++ src/ServiceProvider.php | 35 +++ src/WoUnicom.php | 141 ++++++++++ 13 files changed, 768 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 composer.json create mode 100644 config/config.php create mode 100644 src/Action/Init.php create mode 100644 src/Action/Order.php create mode 100644 src/Action/Query.php create mode 100644 src/Action/Refund.php create mode 100644 src/Action/Sign.php create mode 100644 src/Contracts/CheckCouponContracts.php create mode 100644 src/Contracts/CouponContracts.php create mode 100644 src/ServiceProvider.php create mode 100644 src/WoUnicom.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3bfdf15 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/.idea/ +/.git/ +/vendor +composer.lock \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f3bb5f4 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# 联通沃支付 \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..94f2ad4 --- /dev/null +++ b/composer.json @@ -0,0 +1,28 @@ +{ + "name": "xuanchen/wounicom", + "description": "沃钱包支付", + "license": "MIT", + "homepage": "http://git.yuzhankeji.cn/xuanchen/WoUnicom.git", + "authors": [ + { + "name": "玄尘", + "email": "122383162@qq.com" + } + ], + "require": { + "php": ">=7.1.3", + "laravel/framework": "*" + }, + "autoload": { + "psr-4": { + "XuanChen\\WoUnicom\\": "src/" + } + }, + "extra": { + "laravel": { + "providers": [ + "XuanChen\\WoUnicom\\ServiceProvider" + ] + } + } +} diff --git a/config/config.php b/config/config.php new file mode 100644 index 0000000..50f4d39 --- /dev/null +++ b/config/config.php @@ -0,0 +1,32 @@ + env('WO_UNICOM_MERNO', ''),//商户编号 + 'storeName' => env('WO_UNICOM_STORENAME', ''),//商户名 + 'key' => env('WO_UNICOM_KEY', ''),//商户密钥 + 'signType' => 'RSA_SHA256',//签名方式 + 'woproductcode' => '',//产品编码 + + //接口地址 + 'uri' => [ + 'unicom' => [ + //下单 + 'order' => 'https://www.unicompayment.com/wappay3.0/httpservice/wapPayPageAction.do?reqcharset=UTF-8', + //查询 + 'query' => 'https://www.unicompayment.com/CashierWeb/query/order.htm?reqCharSet=UTF-8', + //单笔退款 + 'refund' => 'https://www.unicompayment.com/CashierWeb/trade/singleRefund.htm?reqCharSet=UTF-8', + ], + 'ysd' => [ + 'order' => env('APP_URL', 'https://card.ysd-bs.com'), + ], + + ], + + //证书地址 + 'certificate' => [ + 'private' => env('WO_YSD_KEY_PATH', ''),//本地私钥 + 'public' => env('WO_YSD_CERT_PATH', ''),//本地公钥 + 'unicom_public' => env('WO_CERT_PATH', ''),//沃钱包公钥 + ], +]; diff --git a/src/Action/Init.php b/src/Action/Init.php new file mode 100644 index 0000000..511fb9d --- /dev/null +++ b/src/Action/Init.php @@ -0,0 +1,347 @@ +merNo = config('wounicom.merNo'); + $this->private = config('wounicom.certificate.private'); + $this->public = config('wounicom.certificate.public'); + $this->unicom_public = config('wounicom.certificate.unicom_public'); + $this->key = config('wounicom.key'); + //检查数据 + $this->checkDefaultData(); + } + + /** + * Notes: 设置参数是否需要小写 + * @Author: 玄尘 + * @Date : 2021/5/12 11:07 + * @param $value + * @return $this + */ + public function setIsLower($value) + { + $this->is_lower = $value; + + return $this; + } + + /** + * Notes: 检查基础数据 + * @Author: 玄尘 + * @Date : 2021/4/30 10:56 + */ + public function checkDefaultData() + { + if (empty($this->merNo) || empty($this->private) || empty($this->public)) { + $this->code = false; + $this->message = '缺少基础参数.'; + } + } + + /** + * Notes: 设置数据 + * @Author: 玄尘 + * @Date : 2021/4/30 10:47 + * @param $data + * @return $this + */ + public function setParams($data) + { + $this->params = $data; + + return $this; + } + + /** + * Notes: 处理传入参数 + * @Author: 玄尘 + * @Date : 2021/4/30 11:28 + */ + public function setSignData() + { + $sign = $this->getSign(); + $this->params['signMsg'] = $sign; + + return $this->params; + } + + /** + * Notes: 验签 + * @Author: 玄尘 + * @Date : 2021/5/11 11:42 + * @param bool $out + * @param false $self + * @return bool + * @throws \Exception + */ + public function checkSign($self = false) + { + $sign = base64_decode($this->sign); + if (!$sign) { + throw new \Exception('签名错误'); + } + + $public_key = $this->getPublic($self); + $pub_key_id = openssl_get_publickey($public_key); + + $signStr = $this->getSignString(); + + if ($pub_key_id) { + $result = (bool) openssl_verify($signStr, $sign, $pub_key_id, 'SHA256'); + openssl_free_key($pub_key_id); + } else { + throw new \Exception('私钥格式有误'); + } + + return $result; + } + + /** + * Notes: 签名 + * @Author: 玄尘 + * @Date : 2020/10/9 15:52 + * @return string + * @throws \Exception + */ + public function getSign(): string + { + $signStr = $this->getSignString(); + $private_key = $this->getPrivate(); + + $privKeyId = openssl_get_privatekey($private_key); + + if (!$privKeyId) { + throw new \Exception('私钥格式有误'); + } + if (openssl_sign($signStr, $signature, $privKeyId, "SHA256")) { + $signature = base64_encode($signature); + } else { + throw new \Exception('签名错误'); + } + + openssl_free_key($privKeyId); + + return $signature; + } + + /** + * Notes: 获取待签名字符串 + * @Author: 玄尘 + * @Date : 2020/9/30 9:38 + * @return string + * @throws \Exception + */ + public function getSignString() + { + $params = $this->checkSignData($this->params); + + if (empty($params)) { + throw new \Exception('获取校验数据失败,缺少数据..'); + } + + ksort($params); + $signStr = $this->str2utf8(urldecode(http_build_query($params, '', '|'))); + + return $signStr; + } + + /** + * Notes: 获取下单数据 + * @Author: 玄尘 + * @Date : 2021/5/6 8:32 + */ + public function getUnicomData($prefix = '$') + { + $str = ''; + + foreach ($this->params as $key => $param) { + $str .= $prefix . strtolower($key) . '=' . $param; + } + + return $this->merNo . trim($str, $prefix); + } + + /** + * Notes: 格式化需要校验的数据 + * @Author: 玄尘 + * @Date : 2021/2/18 15:47 + * @param $params + */ + public function checkSignData($params): array + { + if (isset($params['signMsg'])) { + unset($params['signMsg']); + } + + if (isset($params['signmsg'])) { + unset($params['signmsg']); + } + + if (isset($params['hmac'])) { + unset($params['hmac']); + } + + ksort($params); + if ($this->is_lower) { + $new = []; + + foreach ($params as $key => $param) { + if (strlen($param) > 0 && !is_null($param)) { + $key = strtolower($key); + $new[$key] = $param; + } + } + + return $new; + } else { + return $params; + } + + } + + //获取私钥 + public function getPrivate() + { + return file_get_contents($this->private); + } + + //获取公钥 + public function getPublic($self = false) + { + if ($self) { + return file_get_contents($this->public); + } + + return file_get_contents($this->unicom_public); + + } + + /** + * 将字符串编码转为 utf8 + * @param $str + * @return string + */ + public function str2utf8($str) + { + $encode = mb_detect_encoding($str, ['ASCII', 'UTF-8', 'GB2312', 'GBK', 'BIG5']); + if ($encode != 'UTF-8') { + $str = $str ? $str : mb_convert_encoding($str, 'UTF-8', $encode); + } + $str = is_string($str) ? $str : ''; + + return $str; + } + + /** + * Notes: + * @Author: 玄尘 + * @Date : 2020/12/15 11:23 + * @param $portUrl + * @param $paramArray + * @return array|mixed + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function sendPost($paramArray, $url = null) + { + if ($this->code !== true) { + return; + } + + if (!$url) { + $url = $this->url; + } + + $client = new Client(); + + try { + $response = $client->request('POST', $url, [ + 'form_params' => $paramArray, + 'http_errors' => false, + 'timeout' => 3, + ]); + + if ($response->getStatusCode() == 200) { + $body = $response->getBody(); + $content = str_replace(["\r\n", "\n", "\r", '/n', "\\n",], '&', $body->getContents()); + + parse_str($content, $data); + + if ($data['transRet'] != 'SUCCESS') { + $this->code = false; + $this->message = $data['resultDis']; + } + + } else { + $this->code = false; + $this->message = '接口错误 Post'; + } + + } catch (\Exception $exception) { + $this->code = false; + $this->message = '接口错误 Post' . $exception->getMessage(); + } + + } + + //输出数据 + public function respond() + { + $data = [ + 'code' => $this->code, + 'message' => $this->message, + 'data' => $this->data, + ]; + + return $data; + } + +} diff --git a/src/Action/Order.php b/src/Action/Order.php new file mode 100644 index 0000000..18c28cf --- /dev/null +++ b/src/Action/Order.php @@ -0,0 +1,46 @@ +addData(); + $this->setSignData(); + + return $this->getUnicomData(); + } + + /** + * Notes: 增加基础数据 + * @Author: 玄尘 + * @Date : 2021/4/30 11:24 + */ + public function addData() + { + $params = $this->params; + $params = array_merge($params, [ + 'version' => $this->version, + 'merNo' => $this->merNo, + 'storeName' => config('wounicom.storeName'), + 'signType' => config('wounicom.signType'), + ]); + + $this->params = $params; + + } + +} diff --git a/src/Action/Query.php b/src/Action/Query.php new file mode 100644 index 0000000..b49aea7 --- /dev/null +++ b/src/Action/Query.php @@ -0,0 +1,19 @@ +reSetParams(); + $this->setIsLower(false); + $this->setSignData(); + $url = config('wounicom.uri.unicom.refund'); + $this->sendPost($this->params, $url); + + return $this->respond(); + } + + public function reSetParams() + { + $order = $this->params; + + $data = [ + 'refundReqJournl' => 'R' . date('YmdHis') . sprintf('%06d', rand(1, 999999999)), + 'merNo' => $this->merNo, + 'orderNo' => $order->orderid, + 'orderDate' => $order->created_at->format('Ymd'), + 'payJournl' => $order->payment->out_trade_no, + 'merReqTime' => Carbon::now()->format('YmdHis'), + 'amount' => -1, + 'reason' => '退款', + 'signType' => 'RSA_SHA256', + ]; + + $this->params = $data; + } + +} diff --git a/src/Action/Sign.php b/src/Action/Sign.php new file mode 100644 index 0000000..6e34f8e --- /dev/null +++ b/src/Action/Sign.php @@ -0,0 +1,25 @@ +params['signmsg']; + // $signMsg = str_replace('\\', '', $signMsg); + $signMsg = str_replace(' ', '+', $signMsg); + $this->sign = $signMsg; + + return $this->checkSign(); + + } + +} diff --git a/src/Contracts/CheckCouponContracts.php b/src/Contracts/CheckCouponContracts.php new file mode 100644 index 0000000..d673353 --- /dev/null +++ b/src/Contracts/CheckCouponContracts.php @@ -0,0 +1,14 @@ +app->runningInConsole()) { + $this->publishes([__DIR__ . '/../config/config.php' => config_path('wounicom.php')]); + } + + $this->app->bind('xuanchen.wounicom', function ($app) { + return new WoUnicom(); + }); + } + + /** + * Bootstrap services. + * @return void + */ + public function boot() + { + $this->mergeConfigFrom(__DIR__ . '/../config/config.php', 'wounicom'); + + } + +} diff --git a/src/WoUnicom.php b/src/WoUnicom.php new file mode 100644 index 0000000..de7775d --- /dev/null +++ b/src/WoUnicom.php @@ -0,0 +1,141 @@ +setConfig(); + + return $action; + + } + + /** + * Notes: 查询订单 + * @Author: 玄尘 + * @Date : 2021/4/30 9:39 + * @return \XuanChen\WoUnicom\Action\Query + */ + public function query() + { + return (new Query()); + } + + /** + * Notes: 退款 + * @Author: 玄尘 + * @Date : 2021/4/30 9:37 + */ + public function refund() + { + $action = new Refund(); + $action->setConfig(); + + return $action; + + } + + /** + * Notes: 验签 + * @Author: 玄尘 + * @Date : 2021/5/11 11:51 + * @param $params + */ + public function sign() + { + $action = new Sign(); + $action->setConfig(); + + return $action; + } + + /** + * Notes: 回调数据 + * @Author: 玄尘 + * @Date : 2021/5/11 15:54 + * @param $params + */ + public function callback($inputs) + { + info('weounicom inputs'); + info($inputs); + + $params = str_replace('$', '&', $inputs); + parse_str($params, $data); + + //验签 + $res = $this->sign()->setParams($data)->start(); + + //日志 + $this->unicomLog($data, $res); + + //验签成功 + if ($res === true) { + + $order = \App\Models\Order::where('orderid', $data['orderid'])->first(); + if ($order && $order->state == 'UNPAY') { + $payment = \App\Models\Payment::where('orderable_type', get_class($order)) + ->where('orderable_id', $order->id) + ->latest() + ->first(); + + $payment->state = 'SUCCESS'; + $payment->out_trade_no = $data['payfloodid']; + $payment->type = 'UNICOM'; + $payment->paid_at = Carbon::now(); + $payment->save(); + $order->paid(); + } + + return 'SUCCESS'; + } else { + return '验签失败'; + } + } + + /** + * Notes: 输入日志 + * @Author: 玄尘 + * @Date : 2021/5/11 15:58 + * @param $data + */ + public function unicomLog($data, $str) + { + $inputData = [ + 'orderId' => $data['orderid'], + 'payFloodId' => $data['payfloodid'], + 'payResult' => $data['payresult'], + 'payBalance' => $data['paybalance'], + 'paymentBalanceDetail' => $data['paymentbalancedetail'], + 'respTime' => $data['resptime'], + 'source' => [ + 'data' => $data, + 'check_sign' => $str, + ], + ]; + + return \App\Models\Wounicom::create($inputData); + } + +}