commit 39bfe97021982ac9229a1eeb12714ea567cd684d Author: xuanchen <122383162@qq.com> Date: Mon Aug 28 09:36:03 2023 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5cbefec --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/.idea/ +/.git/ +/vendor +composer.lock +artisan \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..559f3a7 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# 沃支付 WEB收银台支付接口文档 diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..37d8cdc --- /dev/null +++ b/composer.json @@ -0,0 +1,28 @@ +{ + "name": "xuanchen/wounicom", + "description": "沃钱包支付-WEB收银台支付接口文档", + "license": "MIT", + "homepage": "http://git.yuzhankeji.cn/xuanchen/WoUnicom.git", + "authors": [ + { + "name": "xuanchen", + "email": "122383162@qq.com" + } + ], + "require": { + "php": ">=7.1.3", + "laravel/framework": "*" + }, + "autoload": { + "psr-4": { + "XuanChen\\WoUnicomWeb\\": "src/" + } + }, + "extra": { + "laravel": { + "providers": [ + "XuanChen\\WoUnicomWeb\\ServiceProvider" + ] + } + } +} diff --git a/config/config.php b/config/config.php new file mode 100644 index 0000000..e0ef789 --- /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://epay.10010.com/wappay3.0/httpservice/wapPayPageAction.do?reqcharset=UTF-8', + //查询 + 'query' => 'https://epay.10010.com/CashierWeb/query/order.htm?reqCharSet=UTF-8', + //单笔退款 + 'refund' => 'https://epay.10010.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..426b708 --- /dev/null +++ b/src/Action/Init.php @@ -0,0 +1,357 @@ +merNo = config('wounicomweb.merNo'); + $this->private = config('wounicomweb.certificate.private'); + $this->public = config('wounicomweb.certificate.public'); + $this->unicom_public = config('wounicomweb.certificate.unicom_public'); + $this->key = config('wounicomweb.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); + + $this->data = $data; + + if (isset($data['queryResult']) && $data['queryResult'] != 'SUCCESS') { + throw new \Exception('返回的数据错误'); + } + + if (isset($data['transRet']) && $data['transRet'] != 'SUCCESS') { + throw new \Exception($data['resultDis']); + } + + $this->code = true; + } else { + throw new \Exception('未正常返回数据'); + } + } catch (\Exception $exception) { + $this->code = false; + $this->message = '接口错误 '.$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..1d98ee6 --- /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('wounicomweb.storeName'), + 'signType' => config('wounicomweb.signType'), + ]); + + $this->params = $params; + + } + +} diff --git a/src/Action/Query.php b/src/Action/Query.php new file mode 100644 index 0000000..f70884c --- /dev/null +++ b/src/Action/Query.php @@ -0,0 +1,41 @@ +setIsLower(false); + $this->addData(); + $this->setSignData(); + $url = config('wounicomweb.uri.unicom.query'); + $this->sendPost($this->params, $url); + + return $this->respond(); + } + + public function addData() + { + $order = $this->params; + + $data = [ + 'merNo' => $this->merNo, + 'orderNo' => $order->orderid, + 'orderDate' => $order->created_at->format('Ymd'), + 'charSet' => 'UTF-8', + 'signType' => config('wounicomweb.signType'), + ]; + + $this->params = $data; + + } + +} diff --git a/src/Action/Refund.php b/src/Action/Refund.php new file mode 100644 index 0000000..c34ab28 --- /dev/null +++ b/src/Action/Refund.php @@ -0,0 +1,46 @@ +reSetParams(); + $this->setIsLower(false); + $this->setSignData(); + $url = config('wounicomweb.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' => $order->payment->amount * 100, + '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..e338306 --- /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 @@ + 'json', + 'outsource' => 'json', + ]; + +} diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php new file mode 100644 index 0000000..446baaf --- /dev/null +++ b/src/ServiceProvider.php @@ -0,0 +1,36 @@ +app->runningInConsole()) { + $this->publishes([__DIR__.'/../config/config.php' => config_path('wounicomweb.php')]); + } + + $this->app->bind('xuanchen.wounicomweb', function ($app) { + return new WoUnicom(); + }); + } + + /** + * Bootstrap services. + * + * @return void + */ + public function boot() + { + $this->mergeConfigFrom(__DIR__.'/../config/config.php', 'wounicom'); + } + +} diff --git a/src/Traits/Log.php b/src/Traits/Log.php new file mode 100644 index 0000000..5735ffc --- /dev/null +++ b/src/Traits/Log.php @@ -0,0 +1,46 @@ +log = LogModel::create([ + 'orderId' => $data['orderid'], + 'payFloodId' => $data['payfloodid'], + 'payResult' => $data['payresult'], + 'payBalance' => $data['paybalance'], + 'paymentBalanceDetail' => $data['paymentbalancedetail'], + 'respTime' => $data['resptime'], + 'source' => [ + 'data' => $data, + 'check_sign' => $str, + ], + ]); + } + + /** + * Notes: 更新 + * @Author: 玄尘 + * @Date : 2021/7/23 11:03 + * @param $data + */ + public function updateLog($data) + { + $this->log->outsource = $data; + $this->log->save(); + } + +} \ No newline at end of file diff --git a/src/WoUnicomWeb.php b/src/WoUnicomWeb.php new file mode 100644 index 0000000..8d25276 --- /dev/null +++ b/src/WoUnicomWeb.php @@ -0,0 +1,201 @@ +setConfig(); + + return $action; + } + + /** + * Notes: 查询订单 + * + * @Author: 玄尘 + * @Date : 2021/4/30 9:39 + * @return \XuanChen\WoUnicomWeb\Action\Query + */ + public function query() + { + $action = new Query(); + $action->setConfig(); + + return $action; + } + + /** + * 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) + { + if (empty($inputs)) { + return '缺少必要参数'; + } + + $params = str_replace('$', '&', $inputs); + parse_str($params, $data); + + //验签 + $res = $this->sign()->setParams($data)->start(); + + //日志 + $this->createLog($data, $res); + + //验签成功 + if ($res === true) { + $channel = Helper::getChannel($data['orderid']); + switch ($channel) { + case 'welfare': + $order = WelfareOrder::where('orderid', $data['orderid'])->first(); + if ($order && $order->status == 'UNPAY') { + $payment = $order->payment; + + $payment->paid([ + 'type' => 'UNICOM', + 'payfloodid' => $data['payfloodid'], + ]); + $order->paid(); + } + break; + case 'school': + $order = ActivitySchoolOrder::where('orderid', $data['orderid'])->first(); + if ($order && $order->state == 'UNPAY') { + $payment = 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(); + } + break; + case 'petro': + $order = ActivityPetroOrder::where('orderid', $data['orderid'])->first(); + if ($order && $order->state == 'UNPAY') { + $payment = 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(); + } + break; + case 'unicom': + $order = ActivityUnicomOrder::where('orderid', $data['orderid'])->first(); + if ($order && $order->state == 'UNPAY') { + $payment = 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(); + } + break; + case 'web': + default: + $order = \App\Models\Order::where('orderid', $data['orderid'])->first(); + if ($order && $order->state == 'UNPAY') { + $payment = 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(); + } + break; + } + $this->updateLog(['SUCCESS']); + return 'SUCCESS'; + } else { + $this->updateLog(['验签失败']); + return '验签失败'; + } + } + +}