From 502f8597b1931d7a54524065adfd4d596478d376 Mon Sep 17 00:00:00 2001 From: v-Brocloni Date: Mon, 28 Sep 2020 11:55:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E4=BB=98=E5=AE=9D=E9=80=80=E6=AC=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/epay/library/Service.php | 28 ++ .../Yansongda/Pay/Gateways/Alipay/Alipay.php | 4 +- addons/nzf/AliPay.php | 307 ++++++++++++++ addons/nzf/CurlInterface.php | 466 +++++++++++++++++++++ addons/nzf/PayService.php | 98 +++++ addons/nzf/Util.php | 343 +++++++++++++++ addons/nzf/WeChatPay.php | 337 +++++++++++++++ addons/unishop/behavior/Order.php | 24 +- addons/unishop/controller/Order.php | 13 +- addons/unishop/controller/Pay.php | 12 + composer.json | 4 +- 11 files changed, 1630 insertions(+), 6 deletions(-) create mode 100644 addons/nzf/AliPay.php create mode 100644 addons/nzf/CurlInterface.php create mode 100644 addons/nzf/PayService.php create mode 100644 addons/nzf/Util.php create mode 100644 addons/nzf/WeChatPay.php diff --git a/addons/epay/library/Service.php b/addons/epay/library/Service.php index 701ce33..285b1bf 100644 --- a/addons/epay/library/Service.php +++ b/addons/epay/library/Service.php @@ -2,10 +2,12 @@ namespace addons\epay\library; +use addons\unishop\model\Config; use Exception; use think\Log; use think\Response; use think\Session; +use Yansongda\Pay\Gateways\Alipay\Alipay; use Yansongda\Pay\Pay; /** @@ -156,6 +158,32 @@ class Service } /** + * @param string $orderNo 订单号 + * @param string $type 支付类型 wechat alipay + * @param int $amount 退款金额 + */ + public static function refund($orderNo,$type,$amount){ + $config = Service::getConfig($type); + $pay = new Pay($config); + if ($type == "alipay") { + $config_biz = [ + 'out_trade_no' => $orderNo, + 'refund_amount' => $amount, + ]; + $pay->driver($type)->gateway()->refund($config_biz); + } else { + $config_biz = [ + 'out_trade_no' => $orderNo, + 'out_refund_no' => $orderNo, + 'total_fee' => bcmul($amount, 100), + 'refund_fee' => bcmul($amount, 100), + ]; + $pay->driver($type)->gateway()->refund($config_biz); + } + + } + + /** * 创建支付对象 * @param string $type 支付类型 * @param array $config 配置信息 diff --git a/addons/epay/library/Yansongda/Pay/Gateways/Alipay/Alipay.php b/addons/epay/library/Yansongda/Pay/Gateways/Alipay/Alipay.php index 0d5c5d4..96d309a 100644 --- a/addons/epay/library/Yansongda/Pay/Gateways/Alipay/Alipay.php +++ b/addons/epay/library/Yansongda/Pay/Gateways/Alipay/Alipay.php @@ -53,8 +53,8 @@ abstract class Alipay implements GatewayInterface 'charset' => 'UTF-8', 'sign_type' => 'RSA2', 'version' => '1.0', - 'return_url' => $this->user_config->get('return_url', ''), - 'notify_url' => $this->user_config->get('notify_url', ''), + 'notifyurl' => \addons\unishop\model\Config::getByName('ali_notify_url')['value'], + 'returnurl' => \addons\unishop\model\Config::getByName('ali_return_url')['value'], 'timestamp' => date('Y-m-d H:i:s'), 'sign' => '', 'biz_content' => '', diff --git a/addons/nzf/AliPay.php b/addons/nzf/AliPay.php new file mode 100644 index 0000000..d77593c --- /dev/null +++ b/addons/nzf/AliPay.php @@ -0,0 +1,307 @@ +getAliPayUrl($params); + $url = '/fx/?r=weChat/we-chat/q-code&qCode=' . urlencode($getUrl['data']['codUrl']) . '&_math=' . rand(100, 999); + return Util::returnArrSu('', array('codUrl' => $url, 'price' => $par['total_fee'])); + } + + /** + * Function Description:支付宝web支付 + * Function Name: webPay + * @param $param array + * @return array + * + * @author nzf + */ + public function webPay($param) + { + //交易参数 + $params['body'] = $param['name']; + $params['subject'] = $param['name']; + $params['order_id'] = $param['order_id']; + $params['price'] = $param['total_fee']; + $params['timeout_express'] = '1m'; + $params['goType'] = 2; + $params['method'] = 'alipay.trade.wap.pay'; + $return = static::getAliPayUrl($params); + return $return; + } + + /********************通用方法**************************/ + /** + * Function Description:获取阿里支付pay + * Function Name: getAliPayUrl + * @param $params array + * + * @return array + * + * @author 倪宗�? + */ + private static function getAliPayUrl($params) + { + $sysParams = self::getSysParams(); + $sysParams["method"] = $params['method']; + //交易参数 + $body['productCode'] = 'QUICK_WAP_PAY'; + $body['body'] = $params['body']; + $body['subject'] = $params['subject']; + $body['out_trade_no'] = $params['order_id']; + $body['total_amount'] = $params['price']; + $body['timeout_express'] = "1m"; + + $sysParams['biz_content'] = json_encode($body); + if ($params['goType'] == 1) {//如果是页面跳�? + $sysParams['sign'] = urlencode(static::getSign($sysParams)); + $paramString = static::getSignContent($sysParams); + $requestUrl = static::$gatewayUrl . "?" . $paramString; + return Util::returnArrSu('', ['payData' => $requestUrl]); + } elseif ($params['goType'] == 2) {//如果是参数返�? + $sysParams['sign'] = static::getSign($sysParams); + $result = self::buildRequestForm($sysParams); + return Util::returnArrSu('', ['payData' => $result]); + } else { + $sysParams['sign'] = urlencode(static::getSign($sysParams)); + $paramString = static::getSignContent($sysParams); + $requestUrl = static::$gatewayUrl . "?" . $paramString; + $curl = new CurlInterface('', 5); + $result = $curl->execute($requestUrl, 'GET'); + $result = json_decode($result, true); + $return['codUrl'] = ''; + if ($result['alipay_trade_precreate_response']['code'] == '10000') { + $return['codUrl'] = $result['alipay_trade_precreate_response']['qr_code']; + } + return Util::returnArrSu('', $return); + } + } + + /** + * Function Description:取消订单 + * Function Name: cancelOrder + * @param $params + * + * @return array + * + * @author 倪宗�? + */ + public static function cancelOrder($params) + { + $sysParams = self::getSysParams(); + $sysParams["method"] = 'alipay.trade.refund'; + $body = array( + 'out_trade_no' => $params['order_id'],//订单�? + 'refund_amount' => $params['refund_fee'],//退款金额 + 'refund_reason' => $params['memo'], + 'out_request_no' => $params['order_id'] . '-' . date('YmdHis') . rand(100, 999)//�?款原�? + ); + unset($sysParams["notify_url"]); + unset($sysParams["return_url"]); + $sysParams['biz_content'] = json_encode($body); + $sysParams['sign'] = static::getSign($sysParams); + $sysParams['sign'] = urlencode(static::getSign($sysParams)); + $paramString = static::getSignContent($sysParams); + $requestUrl = static::$gatewayUrl."?".$paramString; + //调用款接 + $curlInterface = new CurlInterface($sysParams, 5); + $result = $curlInterface->execute($requestUrl, 'GET'); + $result = json_decode(mb_convert_encoding($result, 'utf-8'), true); + if (isset($result['alipay_trade_refund_response']) && $result['alipay_trade_refund_response']['code'] == '10000') { + return Util::returnArrSu(); + } + return Util::returnArrEr('退款失败!'); + } + + /** + * Function Description:获取系统通用参数 + * Function Name: getSysParams + * @param $app_id string + * @param $order_id string + * @return mixed + * + * @author 倪宗�? + */ + private static function getSysParams() + { + //组装系统参数 + $sysParams["app_id"] = Config::getByName('ali_app_id')['value']; + $sysParams["version"] = '1.0'; + $sysParams["format"] = 'json'; + $sysParams["sign_type"] = 'RSA2'; + + $sysParams["timestamp"] = date("Y-m-d H:i:s"); + $sysParams["alipay_sdk"] = 'alipay-sdk-php-20160411'; + $sysParams["prod_code"] = ''; + $sysParams["notify_url"] = Config::getByName('ali_notify_url')['value']; + $sysParams["return_url"] = Config::getByName('ali_return_url')['value']; + $sysParams["charset"] = "utf-8"; + return $sysParams; + } + + /** + * Function Description:建立请求,以表单HTML形式构�?�(默认�? + * Function Name: buildRequestForm + * @param $para_temp + * + * @return string 提交表单HTML文本 + * + * @author 倪宗�? + */ + protected static function buildRequestForm($para_temp) + { + $sHtml = "
"; + while (list ($key, $val) = each($para_temp)) { + if (false === static::checkEmpty($val)) { + $val = str_replace("'", "'", $val); + $sHtml .= ""; + } + } + return $sHtml; + } + + /** + * Function Description:获取签名 + * Function Name: getSign + * @param $params + * @param $app_id + * @return string + * + * @author 倪宗�? + */ + public static function getSign($params) + { + if (isset($params['sign'])) { + unset($params['sign']); + } + $content = self::getSignContent($params); + $priKey = Config::getByName('ali_private_key')['value']; +// $res = "-----BEGIN RSA PRIVATE KEY-----\n" . +// wordwrap($priKey, 64, "\n", true) . +// "\n-----END RSA PRIVATE KEY-----"; +// openssl_sign($content, $sign, $res); +// $sign = base64_encode($sign); +// return $sign; + + $res = "-----BEGIN RSA PRIVATE KEY-----\n". + wordwrap($priKey, 64, "\n", true). + "\n-----END RSA PRIVATE KEY-----"; + + openssl_sign($content, $sign, $res, OPENSSL_ALGO_SHA256); + + return base64_encode($sign); + } + + + /** + * Function Description:将数组转换为符合阿里支付的路由参�? + * Function Name: getSignContent + * @param $params + * + * @return string + * + * @author 倪宗�? + */ + public static function getSignContent($params) + { + ksort($params); + $stringToBeSigned = ""; + $i = 0; + foreach ($params as $k => $v) { + if (false === self::checkEmpty($v) && "@" != substr($v, 0, 1)) { + // 转换成目标字符集 + $v = self::characet($v, 'utf-8'); + + if ($i == 0) { + $stringToBeSigned.="$k"."="."$v"; + } else { + $stringToBeSigned.="&"."$k"."=".$v; + } + $i++; + } + } + unset ($k, $v); + return $stringToBeSigned; + } + + /** + * Function Description:校验$value是否非空 + * Function Name: checkEmpty + * @param $value + * + * @return bool + * + * @author 倪宗�? + */ + private static function checkEmpty($value) + { + if (!isset($value)) { + return true; + } + if ($value === null) { + return true; + } + if (trim($value) === "") { + return true; + } + return false; + } + + /** + * Function Description:转换字符集编�? + * Function Name: characet + * @param $data + * @param $targetCharset + * + * @return string + * + * @author 倪宗�? + */ + private static function characet($data, $targetCharset) + { + if (!empty($data)) { + $fileType = 'utf-8'; + if (strcasecmp($fileType, $targetCharset) != 0) { + $data = mb_convert_encoding($data, $targetCharset); + } + } + return $data; + } +} \ No newline at end of file diff --git a/addons/nzf/CurlInterface.php b/addons/nzf/CurlInterface.php new file mode 100644 index 0000000..1a9631a --- /dev/null +++ b/addons/nzf/CurlInterface.php @@ -0,0 +1,466 @@ +setBody($body, $type); + $this->setBaseUrl(); + } + + /*****************==========参数设置函数开始=========******************/ + /** + * Function Description:设置是否返回头部信息 + * Function Name: setCurlOptHeader + * @param $curlOptHeader + * + * + * @author 倪宗锋 + */ + public function setCurlOptHeader($curlOptHeader) + { + $this->curlOptHeader = $curlOptHeader; + } + + /** + * Function Description:获取交易概要 + * Function Name: getCurlGetInfo + * + * @return null + * + * @author 倪宗锋 + */ + public function getCurlGetInfo() + { + return $this->curlGetInfo; + } + + /** + * Function Description:获取返回值报文 + * Function Name: getResponseBody + * + * @return null + * + * @author 倪宗锋 + */ + public function getResponseBody() + { + return $this->responseBody; + } + + /** + * Function Description:获取全路径地址 + * Function Name: getUrl + * + * @return null + * + * @author 倪宗锋 + */ + public function getUrl() + { + return $this->url; + } + + /** + * Function Description:设置路径 在baseUrl基础上 + * Function Name: setUrl + * @param $url + * + * + * @author 倪宗锋 + */ + public function setUrl($url) + { + $this->url = $this->baseUrl . $url; + } + + /** + * Function Description:设置过期时间 + * Function Name: setTimeOut + * @param $timeOut + * + * + * @author 倪宗锋 + */ + public function setTimeOut($timeOut) + { + $this->timeOut = $timeOut; + } + + /** + * Function Description:设置传值方式 + * Function Name: setVerb + * @param $verb + * + * + * @author 倪宗锋 + */ + public function setVerb($verb) + { + $this->verb = $verb; + } + + /** + * Function Description:设置baseUrl + * Function Name: setBaseUrl + * @param $baseUrl string + * + * @author 倪宗锋 + */ + public function setBaseUrl($baseUrl = '') + { + $this->baseUrl = $baseUrl; + } + + /** + * Function Description:获取BaseUrl + * Function Name: getBaseUrl + * + * @return null + * + * @author 倪宗锋 + */ + public function getBaseUrl() + { + return $this->baseUrl; + } + + /** + * Function Description:设置是否有返回值 + * Function Name: setNotReturn + * @param $str + * + * + * @author 倪宗锋 + */ + public function setNotReturn($str) + { + $this->notReturn = $str; + } + + /** + * Function Description:设置请求报文 $body 必须是个数组 + * Function Name: setBody + * @param $body + * @param $type int 1:json 2:xml 3 发送原数据接收xml 4 发送原数据 接收json 5:都是原数据 + * + * @author 倪宗锋 + */ + public function setBody($body, $type = 1) + { + $this->body = ''; + if (is_array($body)) { + if ($type == 1) { + $this->body = json_encode($body); + $this->requestLength = strlen($this->body); + } elseif ($type == 2) { + $this->body = '' . Util::arraysToXml(['response' => $body]); + $this->requestLength = strlen($this->body); + } elseif (in_array($type, array(3, 4, 5))) { + $this->body = $body; + } + } else { + $this->body = $body; + } + $this->bodyType = $type; + } + + /** + * Function Description:设置ssl类型 + * Function Name: setCert + * @param $certArr array + * + * + * @author 倪宗锋 + */ + public function setCert($certArr) + { + $this->cert = true; + $this->certPem = $certArr['SSLCERT_PATH']; + $this->keyPem = $certArr['SSLKEY_PATH']; + } + + /*****************==========参数设置函数结束=========******************/ + /*****************==========调用接口并返回结果===开始======******************/ + + /** + * Function Description:执行交易 + * Function Name: execute + * @param $url string sap地址 + * @param $verb string 请求方式 post|get + * @return array + * @author nizongfeng + * Modify Date:2016.11.10 + */ + public function execute($url, $verb = 'GET') + { + $url = preg_replace('# #','%20',$url); + $this->verb = $verb; + $this->url = $this->baseUrl . $url; + + $this->logMessage .= date('Y-m-d H:i:s') . " Url : {$this->url}"; + $this->logMessage .= ' Method:' . $this->verb . PHP_EOL; + if (is_array($this->body)) { + $this->logMessage .= "sendContent: " . json_encode($this->body) . PHP_EOL; + } else { + $this->logMessage .= "sendContent: {$this->body}" . PHP_EOL; + } + + $ch = curl_init($this->url); + try { + switch (strtoupper($this->verb)) { + case 'GET': + $this->executeGet($ch); + break; + case 'POST': + $this->executePost($ch); + break; + case 'PUT': + $this->executePut($ch); + break; + case 'DELETE': + $this->executeDelete($ch); + break; + default: + $this->logMessage .= "current verb: {$this->verb}, is an invalid REST verb." . PHP_EOL; + break; + } + } catch (\Exception $e) { + $this->logMessage .= $e->getMessage() . PHP_EOL; + } + curl_close($ch); + $ch = null; + return $this->getResult(); + } + + /** + * Function Description:获取返回数据 + * Function Name: getResult + * + * @return array|mixed + * + * @author nizongfeng + * Modify Date:2016.11.10 + */ + public function getResult() + { + if (in_array($this->bodyType, array(1, 4))) { + $return = json_decode($this->responseBody, true); + } elseif (in_array($this->bodyType, array(2, 3))) { + $return = Util::xmlToArray($this->responseBody); + } elseif ($this->bodyType == 5) { + $return = $this->responseBody; + } else { + $return = ''; + } +// file_put_contents(APP_PATH . '/log/curl/' . date('Y-m-d') . '.log', $this->logMessage . PHP_EOL, FILE_APPEND); + return $return; + } + + /*******************=====GET传值=====********************/ + /** + * Function Description:GET传值 + * Function Name: executeGet + * @param $ch + * + * @return void + * + * @author nizongfeng 2016.11.10 + */ + protected function executeGet($ch) + { + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); + $this->doExecute($ch); + } + + /*******************=====POST传值=====********************/ + /** + * Function Description:POST传值 + * Function Name: executePost + * @param $ch + * + * @return void + * + * @author nizongfeng 2016.11.10 + */ + protected function executePost($ch) + { +// curl_setopt($ch, CURLOPT_VERBOSE, true); + curl_setopt($ch, CURLOPT_POST, true); + if (is_array($this->body)) { + $cnt = $this->getmaxdim($this->body); + if ($cnt > 1) { + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($this->body)); + } else { + curl_setopt($ch, CURLOPT_POSTFIELDS, $this->body); + } + } else { + curl_setopt($ch, CURLOPT_POSTFIELDS, $this->body); + } + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); + $this->doExecute($ch); + } + + /*******************=====PUT传值=====********************/ + /** + * Function Description:PUT传值 + * Function Name: executePut + * @param $ch + * + * + * @author 倪宗锋 + */ + protected function executePut($ch) + { + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); + curl_setopt($ch, CURLOPT_POSTFIELDS, $this->body); + $this->doExecute($ch); + } + + /*******************=====DELETE传值=====********************/ + /** + * Function Description:DELETE传值 + * Function Name: executeDelete + * @param $ch + * + * + * @author 倪宗锋 + */ + protected function executeDelete($ch) + { + curl_setopt($ch, CURLOPT_POSTFIELDS, $this->body); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); + $this->doExecute($ch); + } + + /*******************=====传值及接收=====********************/ + /** + * Function Description: 传值及接受数据 + * Function Name: doExecute + * @param $curlHandle + * + * @return void + * + * @author nizongfeng 2015.12.08 + */ + protected function doExecute(&$curlHandle) + { + + if ($this->verb != 'get') { + $this->setCurlOpts($curlHandle); + } + curl_setopt($curlHandle, CURLOPT_HEADER, $this->curlOptHeader); + curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curlHandle, CURLOPT_TIMEOUT, $this->timeOut); + + //记录报文发送时间 + $sendTime = microtime(true); + $this->responseBody = curl_exec($curlHandle); + //记录报文返回时间 + $responseTime = microtime(true); + $timeIncrement = round(floatval($responseTime - $sendTime), 3); + //记录返回的报文信息 + $this->logMessage .= "response: {$this->responseBody}" . PHP_EOL; + $curlInfo = curl_getinfo($curlHandle); + $this->curlGetInfo = $curlInfo; + if (empty($curlInfo['primary_port'])) { + $curlInfo['primary_port'] = ''; + } + $curlInfoStr = ''; + if (isset($_SERVER['SERVER_ADDR']) && $_SERVER['SERVER_PORT']) { + $curlInfoStr = " toIP {$curlInfo['primary_ip']}:{$curlInfo['primary_port']}"; + $curlInfoStr .= " selfIP {$_SERVER['SERVER_ADDR']} {$_SERVER['SERVER_PORT']}"; + } + //记录通信信息及性能指标 + $this->logMessage .= "Info: " . json_encode($curlInfoStr) . PHP_EOL; + $this->logMessage .= "sendTime: " . date('H:i:s', $sendTime) . " , responseTime: "; + $this->logMessage .= date('H:i:s', $responseTime) . " , timeIncrement:" . $timeIncrement . 's' + . PHP_EOL; + $curlError = curl_error($curlHandle); + if ($curlError) { + $this->logMessage .= "Error: " . $curlError . PHP_EOL; + } + } + + /*******************=====头部设置=====********************/ + /** + * Function Description:头部设置 + * Function Name: setCurlOpts + * @param $curlHandle + * + * + * @author 倪宗锋 + */ + protected function setCurlOpts(&$curlHandle) + { + if ($this->cert == 1) { + //设置证书 + //使用证书:cert 与 key 分别属于两个.pem文件 + curl_setopt($curlHandle, CURLOPT_SSLCERTTYPE, 'PEM'); + curl_setopt($curlHandle, CURLOPT_SSLCERT, $this->certPem); + curl_setopt($curlHandle, CURLOPT_SSLKEYTYPE, 'PEM'); + curl_setopt($curlHandle, CURLOPT_SSLKEY, $this->keyPem); + } + + } + /*****************==========调用接口并返回结果===结束======******************/ + //获取数组是几维数组 + private function getmaxdim($vDim) + { + if (!is_array($vDim)) return 0; + else { + $max1 = 0; + foreach ($vDim as $item1) { + $t1 = $this->getmaxdim($item1); + if ($t1 > $max1) $max1 = $t1; + } + return $max1 + 1; + } + } + +} + +?> \ No newline at end of file diff --git a/addons/nzf/PayService.php b/addons/nzf/PayService.php new file mode 100644 index 0000000..620bfb1 --- /dev/null +++ b/addons/nzf/PayService.php @@ -0,0 +1,98 @@ +unifiedOrderByOrderIdForSao($params); + return $return; + } + + /** + * Des:取消订单 + * Name: cancel + * @param $params array order_id:订单ID name:订单名称 total_fee:总金额-元 refund_fee退款金额 + * @param int $type 3、微信 4、支付宝 + * @return array + * @author 倪宗锋 + */ + public static function cancel($params, $type) + { + if ($type == 3) {//微信支付 + $pay = new WeChatPay(); + $return = $pay->cancelOrder($params); + } else {//阿里支付 + $pay = new AliPay(); + $return = $pay->cancelOrder($params); + } + return $return; + } + + /** + * Des: 直接付款 + * Name: pay + * @param $params array + * $order_id string 订单表 订单ID + * $name string 产品名称 + * $total_fee int 总金额 单位元 + * $openid string 用户opendid + * @param $type 1微信 2支付宝 + * + * @return array + * @author 倪宗锋 + */ + public static function pay($params,$type = 1) + { + if($type == 1) { + $pay = new WeChatPay();//微信支付类 + }else { + $pay = new AliPay();//支付宝支付类 + } + $return = $pay->webPay($params); + return $return['data']['payData']; + } + + + /** + * Des: 支付 目前只支持微信直接支付 + * Name: pay + * @param $orderId string 订单ID + * + * @return array + * @author 倪宗锋 + */ + public static function checkIsPay($orderId) + { + $pay = new WeChatPay(); + $return = $pay->checkIsPay($orderId); + return $return; + } +} \ No newline at end of file diff --git a/addons/nzf/Util.php b/addons/nzf/Util.php new file mode 100644 index 0000000..069cc03 --- /dev/null +++ b/addons/nzf/Util.php @@ -0,0 +1,343 @@ +' . self::arraysToXml(['response' => $array]); + return $xml; + } + + + /** + * Function Description:数组转换成xml + * Function Name: arrayToXml + * @param $array + * @param $key + * @return string + * + * @author 倪宗锋 + */ + public static function arraysToXml($array, $key = '') + { + $string = ''; + if (count($array) == 0) { + return ''; + } + foreach ($array as $k => $v) { + if (is_array($v) && isset($v['0'])) { + $string .= self::arraysToXml($v, $k);//是数组或者对像就的递归调用 + } else { + if ($key != '') { + $k = $key; + } + $string .= '<' . $k . '>'; + if (is_array($v) || is_object($v)) {//判断是否是数组,或者,对像 + $string .= self::arraysToXml($v);//是数组或者对像就的递归调用 + } elseif (is_numeric($v)) { + $string .= $v; + } elseif ($v != '') { + $string .= ''; + } else { + $string .= ''; + } + $string .= ''; + } + } + return $string; + } + + /** + * Function Description:xml转换为json + * Function Name: xml_to_json + * @param $source + * + * @return string + * + * @author 倪宗锋 + */ + public static function xmlToJson($source) + { + if (is_file($source)) { //传的是文件,还是xml的string的判断 + $xml_array = simplexml_load_file($source); + } else { + $xml_array = simplexml_load_string(trim($source)); + } + $json = json_encode($xml_array, true); + return $json; + } + + /** + * Function Description:xml转换成数组 + * Function Name: xmlToArray + * @param $source + * + * @return mixed + * + * @author 倪宗锋 + */ + public static function xmlToArray($source) + { + libxml_disable_entity_loader(true); + $getResult = json_decode(json_encode(simplexml_load_string(trim($source), 'SimpleXMLElement', LIBXML_NOCDATA)), true); + return $getResult; + } + + /** Function Description:加密解密函数 + * Function Name: authCode + * @param $string + * @param string $operation + * @param int $expiry + * @return string|array + * @author 倪宗锋 + */ + static function authCode($string, $operation = 'DECODE', $expiry = 0) + { + $key = 'udM5A8S50eg8veH15dd0m601de7073N8Bcn7d1I8Res7C7o7z274D6y342I4C7t7'; + $ckey_length = 4; // 随机密钥长度 取值 0-32; + // 加入随机密钥,可以令密文无任何规律,即便是原文和密钥完全相同,加密结果也会每次不同,增大破解难度。 + // 取值越大,密文变动规律越大,密文变化 = 16 的 $ckey_length 次方 + // 当此值为 0 时,则不产生随机密钥 + + $key = md5($key); + $keya = md5(substr($key, 0, 16)); + $keyb = md5(substr($key, 16, 16)); + $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length) : substr(md5(microtime()), -$ckey_length)) : ''; + + $cryptkey = $keya . md5($keya . $keyc); + $key_length = strlen($cryptkey); + + $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0) . substr(md5($string . $keyb), 0, 16) . $string; + $string_length = strlen($string); + + $result = ''; + $box = range(0, 255); + + $rndkey = array(); + for ($i = 0; $i <= 255; $i++) { + $rndkey[$i] = ord($cryptkey[$i % $key_length]); + } + + for ($j = $i = 0; $i < 256; $i++) { + $j = ($j + $box[$i] + $rndkey[$i]) % 256; + $tmp = $box[$i]; + $box[$i] = $box[$j]; + $box[$j] = $tmp; + } + + for ($a = $j = $i = 0; $i < $string_length; $i++) { + $a = ($a + 1) % 256; + $j = ($j + $box[$a]) % 256; + $tmp = $box[$a]; + $box[$a] = $box[$j]; + $box[$j] = $tmp; + $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256])); + } + + if ($operation == 'DECODE') { + if ((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)) { + return substr($result, 26); + } else { + return ''; + } + } else { + return $keyc . str_replace('=', '', base64_encode($result)); + } + + } + + + + /** + * 获取客户端IP地址 + * @return mixed + */ + public static function getclientip() + { + static $realip = NULL; + + if ($realip !== NULL) { + return $realip; + } + if (isset($_SERVER)) { + if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { //但如果客户端是使用代理服务器来访问,那取到的就是代理服务器的 IP 地址,而不是真正的客户端 IP 地址。要想透过代理服务器取得客户端的真实 IP 地址,就要使用 $_SERVER["HTTP_X_FORWARDED_FOR"] 来读取。 + $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); + /* 取X-Forwarded-For中第一个非unknown的有效IP字符串 */ + foreach ($arr AS $ip) { + $ip = trim($ip); + if ($ip != 'unknown') { + $realip = $ip; + break; + } + } + } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {//HTTP_CLIENT_IP 是代理服务器发送的HTTP头。如果是"超级匿名代理",则返回none值。同样,REMOTE_ADDR也会被替换为这个代理服务器的IP。 + $realip = $_SERVER['HTTP_CLIENT_IP']; + } else { + if (isset($_SERVER['REMOTE_ADDR'])) { //正在浏览当前页面用户的 IP 地址 + $realip = $_SERVER['REMOTE_ADDR']; + } else { + $realip = '0.0.0.0'; + } + } + } else { + //getenv环境变量的值 + if (getenv('HTTP_X_FORWARDED_FOR')) {//但如果客户端是使用代理服务器来访问,那取到的就是代理服务器的 IP 地址,而不是真正的客户端 IP 地址。要想透过代理服务器取得客户端的真实 IP 地址 + $realip = getenv('HTTP_X_FORWARDED_FOR'); + } elseif (getenv('HTTP_CLIENT_IP')) { //获取客户端IP + $realip = getenv('HTTP_CLIENT_IP'); + } else { + $realip = getenv('REMOTE_ADDR'); //正在浏览当前页面用户的 IP 地址 + } + } + preg_match("/[\d\.]{7,15}/", $realip, $onlineip); + $realip = !empty($onlineip[0]) ? $onlineip[0] : '0.0.0.0'; + return $realip; + } + + /** + * Des:获取浏览器机器类型 + * Name: get_device_type + * @return string + * @author 倪宗锋 + */ + public static function get_device_type() + { + //全部变成小写字母 + $agent = strtolower($_SERVER['HTTP_USER_AGENT']); + $type = 'other'; + //分别进行判断 + if (strpos($agent, 'iphone') || strpos($agent, 'ipad')) { + $type = 'ios'; + } + + if (strpos($agent, 'android')) { + $type = 'android'; + } + return $type; + } + +} \ No newline at end of file diff --git a/addons/nzf/WeChatPay.php b/addons/nzf/WeChatPay.php new file mode 100644 index 0000000..c232817 --- /dev/null +++ b/addons/nzf/WeChatPay.php @@ -0,0 +1,337 @@ + $params['name'],//名称 + 'order_id' => (string)$params['order_id'] . '_' . rand(1000, 9999),//支付ID + 'total_fee' => (string)$params['total_fee'] * 100,//总金 + 'notify_url' => $params['notify_url'],//支付回调接口 + 'trade_type' => 'MWEB',//用户的OPENID + 'scene_info' => [ + 'h5_info' => [ + 'type' => 'Wap', + 'wap_url' => Config::getByName('name')['value'], + 'wap_name' => Config::getByName('name')['value'] + ] + ] + ); + $getpay = $this->unifiedOrderForWeChat($data);//统一下单API + if ($getpay['flag'] == false) { + return $getpay; + } + if (empty($getpay['data']['mweb_url'])) { + return Util::returnArrEr('预支付会话异常!'); + } + return Util::returnArrSu('', ['pay_url' => $getpay['data']['mweb_url']]); + } + /************************微信浏览器内web支付**************************************/ + /** + * Function Description:去微信下单并获取返回 + * Function Name: unifiedOrderByOrderId + * @param $params array + * $order_id string 订单表 订单ID + * $name string 产品名称 + * $total_fee int 总金额 单位分 + * $openid string 用户opendid + * @return array + * + * @author nzf + */ + public function webPay($params) + { + if (empty($params['notify_url'])) { + $params['notify_url'] = Config::getByName('notify_url')['value'];; + } + $data = array( + 'name' => $params['name'],//名称 + 'order_id' => (string)$params['order_id'] . '_' . rand(1000, 9999),//支付ID + 'total_fee' => (string)$params['total_fee'] * 100,//总金 + 'notify_url' => $params['notify_url'],//支付回调接口 + 'openid' => $params['openid']//用户的OPENID + ); + $getPrepayId = $this->unifiedOrderForWeChat($data);//统一下单API + if ($getPrepayId['flag'] == false) { + return $getPrepayId; + } + //设置成功返回的结果数 + $return = array( + 'appId' => Config::getByName('app_id')['value'],//微信�?放平台审核�?�过的应用APPID + 'package' => 'prepay_id=' . $getPrepayId['data']['prepay_id'],//微信返回的支付交易会话ID + 'nonceStr' => self::getNonceStr(),//随机字符�? + 'signType' => 'MD5', + 'timeStamp' => strval(time()),//当前时间�? + ); + $return['paySign'] = self::getSign($return); + return Util::returnArrSu('', array('payData' => $return, 'price' => $params['total_fee'])); + } + + /** + * Function Description:统一下单API + * Function Name: unifiedOrder + * @param $params array + * attach 附加数据,在查询API和支付知中原样返回,该字段主要用于商户携带订单的自定义数 + * line_name 线路名称 + * order_id 订单ID + * total_fee 总金 + * notify_url 回调地址 + * + * @return array + * + * @author nzf + */ + public function unifiedOrderForWeChat($params) + { + $data = array( + 'appid' => Config::getByName('app_id')['value'],//微信开放平台审核过的应用APPID + 'attach' => empty($params['attach']) ? '' : $params['attach'], + 'body' => $params['name'],//产品名称 + 'mch_id' => Config::getByName('mch_id')['value'],//商户微信支付分配的商户号 + 'nonce_str' => $this->getNonceStr(),//随机字符 + 'notify_url' => $params['notify_url'],//通知地址 + 'out_trade_no' => $params['order_id'],//商户订单ID加上当前时间 + 'spbill_create_ip' => $_SERVER['REMOTE_ADDR'],//用户端实际ip + 'total_fee' => $params['total_fee'],//订单总金额,单位为分 + 'trade_type' => empty($params['trade_type']) ? 'JSAPI' : $params['trade_type'],//交易类型 + ); + if (empty($params['openid']) == false) {//存在openid + $data['openid'] = $params['openid']; + } + if (isset($params['scene_info'])) { + $data['scene_info'] = json_encode($params['scene_info']); + } + $data['sign'] = $this->getSign($data);//交易签名 + $curl = new CurlInterface($data, 2);//函数 + $curl->setBaseUrl($this->unifiedOrderUrl); + $result = $curl->execute('', 'POST'); + if (empty($result['prepay_id'])) { + return Util::returnArrEr('预支付交易会话异常!'); + } + return Util::returnArrSu('', array('prepay_id' => $result['prepay_id'], 'mweb_url' => empty($result['mweb_url']) ? '' : $result['mweb_url'])); + } + /************************扫描支付**************************************/ + + /** + * Function Description:去微信下单并获取返回 + * Function Name: unifiedOrderByOrderId + * @param $par + * @return array + * + * @author nzf + */ + public function unifiedOrderByOrderIdForSao($par) + { + $notify_url = Config::getByName('notify_url')['value']; + $data = array( + 'name' => $par['name'],//线路名称 + 'order_id' => (string)$par['order_id'] . '_' . rand(100, 999),//订单ID + 'total_fee' => $par['total_fee'] * 100,//总金 + 'notify_url' => $notify_url + ); + $codUrl = $this->unifiedOrderForSao($data);//统一下单API + if ($codUrl['flag'] == false) { + return $codUrl; + } + //设置成功返回的结果数�? + $url = '/fx/?r=weChat/we-chat/q-code&qCode=' . urlencode($codUrl['data']['code_url']) . '&_math=' . rand(100, 999); + return Util::returnArrSu('', array('codUrl' => $url, 'price' => $par['total_fee'])); + } + + /** + * Function Description:统一下单API + * Function Name: unifiedOrder + * @param $params array + * attach 附加数据,在查询API和支付知中原样返回,该字段主要用于商户携带订单的自定义数 + * line_name 线路名称 + * order_id 订单ID + * total_fee 总金 + * notify_url 回调地址 + * + * @return array + * + * @author 倪宗�? + */ + public function unifiedOrderForSao($params) + { + $data = array( + 'appid' => Config::getByName('app_id')['value'],//微信�?放平台审核�?�过的应用APPID + 'mch_id' => Config::getByName('mch_id')['value'],//商户�? 微信支付分配的商户号 + 'nonce_str' => $this->getNonceStr(),//随机字符�? + 'body' => $params['name'],//线路名称 �? 上订单ID + 'out_trade_no' => $params['order_id'],//商户订单ID加上当前时间 + 'total_fee' => $params['total_fee'],//订单总金额,单位为分 + 'spbill_create_ip' => $_SERVER['REMOTE_ADDR'],//用户端实际ip + 'notify_url' => $params['notify_url'],//通知地址 + 'trade_type' => 'NATIVE',//交易类型 + ); + $data['sign'] = $this->getSign($data);//交易签名 + $curl = new CurlInterface($data, 2);//函数�? + $curl->setBaseUrl($this->unifiedOrderUrl); + $result = $curl->execute('', 'POST'); + if (empty($result['prepay_id'])) { + return Util::returnArrEr('预支付交易会话异常!'); + } + return Util::returnArrSu('', array('prepay_id' => $result['prepay_id'], 'code_url' => $result['code_url'])); + } + + /***************************************退款****************************************/ + + /** + * Des:微信退款接口 + * Name: cancelOrder + * @param $params + * @return array + * @author 倪宗锋 + */ + public static function cancelOrder($params) + { + $arr = array( + 'appid' => Config::getByName('app_id')['value'], + 'mch_id' => Config::getByName('mch_id')['value'], + 'nonce_str' => static::getNonceStr(), + 'out_trade_no' => (string)$params['order_id'],//订单ID + 'out_refund_no' => (string)date('YmdHis') . rand(100, 999),//退款ID + 'total_fee' => (string)$params['total_fee'] * 100,//订单总金额 元 + 'refund_fee' => (string)$params['refund_fee'] * 100,//退款金额 元 + 'op_user_id' => Config::getByName('mch_id')['value'] + ); + $arr['sign'] = static::getSign($arr); + $curl = new CurlInterface($arr, 2);//函数类 + $certArr = [ + "SSLCERT_PATH"=>ROOT_PATH.Config::getByName('cert_path')['value'], + "SSLKEY_PATH"=>ROOT_PATH.Config::getByName('key_path')['value'] + ]; + $curl->setCert($certArr); + $curl->setBaseUrl('https://api.mch.weixin.qq.com/secapi/pay/refund'); + $result = $curl->execute('', 'POST'); + if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') { + return Util::returnArrSu(); + } + $msg = $result['return_msg']; + if ($msg == 'OK') { + $msg = $result['err_code_des']; + } + return Util::returnArrEr('退款失败!' . $msg); + + } + + /*************************************通用方法**************************************/ + /** + * Des:检测是否已经支付 + * Name: checkIsPay + * @param $orderId + * @return array + * @author 倪宗锋 + */ + public static function checkIsPay($orderId) + { + $arr = array( + 'appid' => Config::getByName('app_id')['value'], + 'mch_id' => Config::getByName('mch_id')['value'], + 'out_trade_no' => (string)$orderId,//订单ID + 'nonce_str' => static::getNonceStr(), + ); + $arr['sign'] = static::getSign($arr); + $curl = new CurlInterface($arr, 2);//函数类 + $certArr = [ + "SSLCERT_PATH"=>ROOT_PATH.Config::getByName('cert_path')['value'], + "SSLKEY_PATH"=>ROOT_PATH.Config::getByName('key_path')['value'] + ]; + $curl->setCert($certArr); + $curl->setBaseUrl('https://api.mch.weixin.qq.com/pay/orderquery'); + $result = $curl->execute('', 'POST'); + if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') { + return Util::returnArrSu('', $result); + } + $msg = $result['return_msg']; + if ($msg == 'OK') { + $msg = $result['err_code_des']; + } + return Util::returnArrEr('退款失败!' . $msg); + } + + /** + * Function Description:获取签名 + * Function Name: getSign + * @param $params + * @return string + * + * @author nzf + */ + public static function getSign($params) + { + if (isset($params['sign'])) { + unset($params['sign']); + } + //签名步骤按字典序排序参 + ksort($params); + $string = self::ToUrlParams($params); + //签名步骤二:在string后加入KEY + $string = $string . "&key=" . Config::getByName('key')['value']; + //签名步骤三:MD5加密 + $string = md5($string); + //签名步骤四:有字符转为大 + $result = strtoupper($string); + return $result; + + } + + /** + * Function Description:格式化参�? 格式化成url参数 + * Function Name: ToUrlParams + * @param $params + * + * @return string + * + * @author 倪宗�? + */ + public static function ToUrlParams($params) + { + $buff = ""; + foreach ($params as $k => $v) { + if ($k != "sign" && $v != "" && !is_array($v)) { + $buff .= $k . "=" . $v . "&"; + } + } + $buff = trim($buff, "&"); + return $buff; + } + + /** + * Function Description:产生的随机字符串 不长 + * Function Name: getNonceStr + * @param int $length + * + * @return string + * + * @author 倪宗�? + */ + public static function getNonceStr($length = 32) + { + $chars = "abcdefghijklmnopqrstuvwxyz0123456789"; + $str = ""; + for ($i = 0; $i < $length; $i++) { + $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1); + } + return $str; + } +} \ No newline at end of file diff --git a/addons/unishop/behavior/Order.php b/addons/unishop/behavior/Order.php index 2b3a4ce..3ca2be7 100644 --- a/addons/unishop/behavior/Order.php +++ b/addons/unishop/behavior/Order.php @@ -2,6 +2,7 @@ namespace addons\unishop\behavior; +use addons\epay\library\Service; use addons\unishop\extend\Ali; use addons\unishop\extend\Hashids; use addons\unishop\extend\Wechat; @@ -256,7 +257,7 @@ $address=[]; * 行为一:退款 * @param array $params 订单数据 */ - public function orderRefund(&$params) + public function orderRefundOld(&$params) { $order = &$params; @@ -281,4 +282,25 @@ $address=[]; // More ... } + + /** + * 订单退款 + * 行为一:退款 + * @param array $params 订单数据 + */ + public function orderRefund(&$params) + { + $order = &$params; + // 行为一 + switch ($order['pay_type']) { + case \addons\unishop\model\Order::PAY_WXPAY: + Service::refund($params["out_trade_no"],"wechat",$params["total_price"]); + break; + case \addons\unishop\model\Order::PAY_ALIPAY: + Service::refund($params['out_trade_no'],"alipay",$params['total_price']); + break; + } + + // More ... + } } diff --git a/addons/unishop/controller/Order.php b/addons/unishop/controller/Order.php index 6b07710..b190bdf 100644 --- a/addons/unishop/controller/Order.php +++ b/addons/unishop/controller/Order.php @@ -9,6 +9,8 @@ namespace addons\unishop\controller; +use addons\nzf\AliPay; +use addons\nzf\PayService; use addons\unishop\extend\Hashids; use addons\unishop\model\Area; use addons\unishop\model\Config; @@ -600,9 +602,15 @@ class Order extends Base $order->status = \addons\unishop\model\Order::STATUS_REFUND; $order->refund_status = \addons\unishop\model\Order::REFUND_STATUS_AGREE; $result = $order->save(); - Hook::add('order_refund', 'addons\\unishop\\behavior\\Order'); if ($result !== false){ - Hook::listen('order_refund', $row); + //order_id:订单ID name:订单名称 total_fee:总金额-元 refund_fee退款金额 + $param = [ + "order_id"=>$order['out_trade_no'], + "total_fee"=>$order['total_price'], + "refund_fee"=>$order['total_price'], + "name"=>$order['订单退款'], + ]; + PayService::cancel($param,$order["pay_type"]); $this->success("success",[]); } $this->error("订单信息错误"); @@ -651,4 +659,5 @@ class Order extends Base } + } diff --git a/addons/unishop/controller/Pay.php b/addons/unishop/controller/Pay.php index 0315bb5..3f64ad0 100644 --- a/addons/unishop/controller/Pay.php +++ b/addons/unishop/controller/Pay.php @@ -10,6 +10,7 @@ namespace addons\unishop\controller; use addons\epay\library\Service; +use addons\nzf\AliPay; use addons\unishop\extend\Ali; use addons\unishop\extend\Hashids; use addons\unishop\extend\Wechat; @@ -240,4 +241,15 @@ class Pay extends Base } +// public function notify(){ +// $order = [ +// "order_id"=>"202009275f7076e5c6f2f23", +// "total_fee"=>11, +// "memo"=>'取消订单', +// "refund_fee"=>11, +// +// ];//order_id:订单ID name:订单名称 total_fee:总金额-元 refund_fee退款金额 +// AliPay::cancelOrder($order); +// +// } } diff --git a/composer.json b/composer.json index e36b911..1a71ef4 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,9 @@ "phpmailer/phpmailer": "~6.0.6", "karsonzhang/fastadmin-addons": "~1.1.9", "overtrue/pinyin": "~3.0", - "phpoffice/phpspreadsheet": "^1.2" + "phpoffice/phpspreadsheet": "^1.2", + "ext-json": "*", + "ext-curl": "*" }, "config": { "preferred-install": "dist"