config = $params; } /** * 配置信息 * @author Devil * @blog http://gong.gg/ * @version 1.0.0 * @date 2018-09-19 * @desc description */ public function Config() { // 基础信息 $base = [ 'name' => '支付宝', // 插件名称 'version' => '1.1.4', // 插件版本 'apply_version' => '不限', // 适用系统版本描述 'apply_terminal'=> ['pc','h5', 'ios', 'android'], // 适用终端 默认全部 ['pc', 'h5', 'ios', 'android', 'alipay', 'weixin', 'baidu', 'toutiao'] 'desc' => '2.0版本,适用PC+H5+APP,即时到帐支付方式,买家的交易资金直接打入卖家支付宝账户,快速回笼交易资金。 立即申请', // 插件描述(支持html) 'author' => 'Devil', // 开发者 'author_url' => 'http://shopxo.net/', // 开发者主页 ]; // 配置信息 $element = [ [ 'element' => 'input', 'type' => 'text', 'default' => '', 'name' => 'appid', 'placeholder' => '应用ID', 'title' => '应用ID', 'is_required' => 0, 'message' => '请填写应用ID', ], [ 'element' => 'textarea', 'name' => 'rsa_public', 'placeholder' => '应用公钥', 'title' => '应用公钥', 'is_required' => 0, 'rows' => 6, 'message' => '请填写应用公钥', ], [ 'element' => 'textarea', 'name' => 'rsa_private', 'placeholder' => '应用私钥', 'title' => '应用私钥', 'is_required' => 0, 'rows' => 6, 'message' => '请填写应用私钥', ], [ 'element' => 'textarea', 'name' => 'out_rsa_public', 'placeholder' => '支付宝公钥', 'title' => '支付宝公钥', 'is_required' => 0, 'rows' => 6, 'message' => '请填写支付宝公钥', ], ]; return [ 'base' => $base, 'element' => $element, ]; } /** * 支付入口 * @author Devil * @blog http://gong.gg/ * @version 1.0.0 * @date 2018-09-19 * @desc description * @param [array] $params [输入参数] */ public function Pay($params = []) { // 参数 if(empty($params)) { return DataReturn('参数不能为空', -1); } // 配置信息 if(empty($this->config) || empty($this->config['appid']) || empty($this->config['rsa_public']) || empty($this->config['rsa_private']) || empty($this->config['out_rsa_public'])) { return DataReturn('支付缺少配置', -1); } // 支付方式 switch(APPLICATION_CLIENT_TYPE) { // web case 'pc' : case 'h5' : if(IsMobile()) { $ret = $this->PayMobile($params); } else { $ret = $this->PayWeb($params); } break; // app,头条小程序 case 'ios' : case 'android' : $ret = $this->PayApp($params); break; default : $ret = DataReturn('没有相关支付模块['.APPLICATION_CLIENT_TYPE.']', -1); } return $ret; } /** * app支付 * @author Devil * @blog http://gong.gg/ * @version 1.0.0 * @date 2019-10-29 * @desc description * @param [array] $params [输入参数] */ private function PayApp($params = []) { // 支付参数 $parameter = array( 'app_id' => $this->config['appid'], 'method' => 'alipay.trade.app.pay', 'format' => 'JSON', 'charset' => 'utf-8', 'sign_type' => 'RSA2', 'timestamp' => date('Y-m-d H:i:s'), 'version' => '1.0', 'return_url' => $params['call_back_url'], 'notify_url' => $params['notify_url'], ); $biz_content = array( 'product_code' => 'QUICK_MSECURITY_PAY', 'subject' => $params['name'], 'out_trade_no' => $params['order_no'], 'total_amount' => (string) $params['total_price'], 'timeout_express' => $this->OrderAutoCloseTime(), ); $parameter['biz_content'] = json_encode($biz_content, JSON_UNESCAPED_UNICODE); // 生成签名参数+签名 $parameter['sign'] = $this->MyRsaSign($this->GetSignContent($parameter)); // 生成支付参数 $value = ''; $i = 0; foreach($parameter as $k=>$v) { if($v != '' && $v != null && "@" != substr($v, 0, 1)) { if($i == 0) { $value .= $k.'='.urlencode($v); } else { $value .= '&'.$k.'='.urlencode($v); } $i++; } } unset($k, $v); return DataReturn('处理成功', 0, $value); } /** * [PayMobile wap手机支付] * @author Devil * @blog http://gong.gg/ * @version 1.0.0 * @datetime 2018-09-28T00:41:09+0800 * @param [array] $params [输入参数] */ private function PayMobile($params = []) { // 支付参数 $parameter = array( 'app_id' => $this->config['appid'], 'method' => 'alipay.trade.wap.pay', 'format' => 'JSON', 'charset' => 'utf-8', 'sign_type' => 'RSA2', 'timestamp' => date('Y-m-d H:i:s'), 'version' => '1.0', 'return_url' => $params['call_back_url'], 'notify_url' => $params['notify_url'], ); $biz_content = array( 'product_code' => 'QUICK_WAP_WAY', 'subject' => $params['name'], 'out_trade_no' => $params['order_no'], 'total_amount' => (string) $params['total_price'], 'timeout_express' => $this->OrderAutoCloseTime(), ); $parameter['biz_content'] = json_encode($biz_content, JSON_UNESCAPED_UNICODE); // 生成签名参数+签名 $parameter['sign'] = $this->MyRsaSign($this->GetSignContent($parameter)); // 接口则直接返回 $html = $this->BuildRequestForm($parameter); if(APPLICATION == 'app') { $result = [ 'data' => $parameter, 'html' => $html, 'url' => 'https://openapi.alipay.com/gateway.do?charset=utf-8', ]; return DataReturn('success', 0, $result); } // web端输出执行form表单post提交 exit($html); } /** * [PayWeb PC支付] * @author Devil * @blog http://gong.gg/ * @version 1.0.0 * @datetime 2018-09-28T00:23:04+0800 * @param [array] $params [输入参数] */ private function PayWeb($params = []) { // 支付参数 $parameter = array( 'app_id' => $this->config['appid'], 'method' => 'alipay.trade.page.pay', 'format' => 'JSON', 'charset' => 'utf-8', 'sign_type' => 'RSA2', 'timestamp' => date('Y-m-d H:i:s'), 'version' => '1.0', 'return_url' => $params['call_back_url'], 'notify_url' => $params['notify_url'], ); $biz_content = array( 'product_code' => 'FAST_INSTANT_TRADE_PAY', 'subject' => $params['name'], 'out_trade_no' => $params['order_no'], 'total_amount' => (string) $params['total_price'], 'timeout_express' => $this->OrderAutoCloseTime(), ); $parameter['biz_content'] = json_encode($biz_content, JSON_UNESCAPED_UNICODE); // 生成签名参数+签名 $parameter['sign'] = $this->MyRsaSign($this->GetSignContent($parameter)); // 接口则直接返回 $html = $this->BuildRequestForm($parameter); if(APPLICATION == 'app') { $result = [ 'data' => $parameter, 'html' => $html, 'url' => 'https://openapi.alipay.com/gateway.do?charset=utf-8', ]; return DataReturn('success', 0, $result); } // web端输出执行form表单post提交 exit($html); } /** * 订单自动关闭的时间 * @author Devil * @blog http://gong.gg/ * @version 1.0.0 * @date 2021-03-24 * @desc description */ public function OrderAutoCloseTime() { return intval(MyC('common_order_close_limit_time', 30, true)).'m'; } /** * 支付回调处理 * @author Devil * @blog http://gong.gg/ * @version 1.0.0 * @date 2018-09-19 * @desc description * @param [array] $params [输入参数] */ public function Respond($params = []) { $data = empty($_POST) ? $_GET : array_merge($_GET, $_POST); ksort($data); // 参数字符串 $prestr = ''; foreach($data AS $key=>$val) { if ($key != 'sign' && $key != 'sign_type' && $key != 'code') { $prestr .= "$key=$val&"; } } $prestr = substr($prestr, 0, -1); // 签名 if(!$this->OutRsaVerify($prestr, $data['sign'])) { return DataReturn('签名校验失败', -1); } // 支付状态 if(!empty($data['trade_no']) && isset($data['total_amount']) && $data['total_amount'] > 0) { $status = false; if(isset($data['trade_status'])) { if(in_array($data['trade_status'], ['TRADE_SUCCESS', 'TRADE_FINISHED'])) { $status = true; } } else { switch($data['method']) { // pc、h5 case 'alipay.trade.wap.pay.return' : case 'alipay.trade.page.pay.return' : if(isset($data['seller_id'])) { $status = true; } break; } } if($status) { return DataReturn('支付成功', 0, $this->ReturnData($data)); } } return DataReturn('处理异常错误', -100); } /** * [ReturnData 返回数据统一格式] * @author Devil * @blog http://gong.gg/ * @version 1.0.0 * @datetime 2018-10-06T16:54:24+0800 * @param [array] $data [返回数据] */ private function ReturnData($data) { // 返回数据固定基础参数 $data['trade_no'] = $data['trade_no']; // 支付平台 - 订单号 $data['buyer_user'] = $data['seller_id']; // 支付平台 - 用户 $data['out_trade_no'] = $data['out_trade_no']; // 本系统发起支付的 - 订单号 $data['subject'] = isset($data['subject']) ? $data['subject'] : ''; // 本系统发起支付的 - 商品名称 $data['pay_price'] = $data['total_amount']; // 本系统发起支付的 - 总价 return $data; } /** * 退款处理 * @author Devil * @blog http://gong.gg/ * @version 1.0.0 * @date 2019-05-28 * @desc description * @param [array] $params [输入参数] */ public function Refund($params = []) { // 参数 $p = [ [ 'checked_type' => 'empty', 'key_name' => 'order_no', 'error_msg' => '订单号不能为空', ], [ 'checked_type' => 'empty', 'key_name' => 'trade_no', 'error_msg' => '交易平台订单号不能为空', ], [ 'checked_type' => 'empty', 'key_name' => 'refund_price', 'error_msg' => '退款金额不能为空', ], ]; $ret = ParamsChecked($params, $p); if($ret !== true) { return DataReturn($ret, -1); } // 退款原因 $refund_reason = empty($params['refund_reason']) ? $params['order_no'].'订单退款'.$params['refund_price'].'元' : $params['refund_reason']; // 退款参数 $parameter = array( 'app_id' => $this->config['appid'], 'method' => 'alipay.trade.refund', 'format' => 'JSON', 'charset' => 'utf-8', 'sign_type' => 'RSA2', 'timestamp' => date('Y-m-d H:i:s'), 'version' => '1.0', ); $biz_content = array( 'out_trade_no' => $params['order_no'], 'trade_no' => $params['trade_no'], 'refund_amount' => (string) $params['refund_price'], 'refund_reason' => $refund_reason, ); $parameter['biz_content'] = json_encode($biz_content, JSON_UNESCAPED_UNICODE); // 生成签名参数+签名 $parameter['sign'] = $this->MyRsaSign($this->GetSignContent($parameter)); // 执行请求 $result = $this->HttpRequest('https://openapi.alipay.com/gateway.do', $parameter); $key = str_replace('.', '_', $parameter['method']).'_response'; // 验证签名 if(!$this->SyncRsaVerify($result, $key)) { return DataReturn('签名验证错误', -1); } // 状态 if(isset($result[$key]['code']) && $result[$key]['code'] == 10000) { // 统一返回格式 $data = [ 'out_trade_no' => isset($result[$key]['out_trade_no']) ? $result[$key]['out_trade_no'] : '', 'trade_no' => isset($result[$key]['trade_no']) ? $result[$key]['trade_no'] : '', 'buyer_user' => isset($result[$key]['buyer_user_id']) ? $result[$key]['buyer_user_id'] : '', 'refund_price' => isset($result[$key]['refund_fee']) ? $result[$key]['refund_fee'] : 0.00, 'return_params' => $result[$key], ]; return DataReturn('退款成功', 0, $data); } // 直接返回支付信息 return DataReturn($result[$key]['sub_msg'].'['.$result[$key]['sub_code'].']', -1000); } /** * 建立请求,以表单HTML形式构造(默认) * @author Devil * @blog http://gong.gg/ * @version 1.0.0 * @date 2019-03-15 * @desc description * @param [array] $params [请求参数数组] * @return [string] [提交表单HTML文本] */ private function BuildRequestForm($params) { $html = "
"; foreach($params as $key=>$val) { if(!empty($val)) { $val = str_replace("'", "'", $val); $html .= ""; } } //submit按钮控件请不要含有name属性 $html .= "
"; $html .= ""; return $html; } /** * [HttpRequest 网络请求] * @author Devil * @blog http://gong.gg/ * @version 1.0.0 * @datetime 2017-09-25T09:10:46+0800 * @param [string] $url [请求url] * @param [array] $data [发送数据] * @return [mixed] [请求返回数据] */ private function HttpRequest($url, $data) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_FAILONERROR, false); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); $body_string = ''; if(is_array($data) && 0 < count($data)) { foreach($data as $k => $v) { $body_string .= $k.'='.urlencode($v).'&'; } curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $body_string); } $headers = array('content-type: application/x-www-form-urlencoded;charset=UTF-8'); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); $reponse = curl_exec($ch); if(curl_errno($ch)) { return false; } else { $httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if(200 !== $httpStatusCode) { return false; } } curl_close($ch); return json_decode($reponse, true); } /** * 获取签名内容 * @author Devil * @blog http://gong.gg/ * @version 1.0.0 * @date 2019-03-15 * @desc description * @param [array] $params [需要签名的参数] */ public function GetSignContent($params) { ksort($params); $string = ""; $i = 0; foreach($params as $k => $v) { if(!empty($v) && "@" != substr($v, 0, 1)) { if ($i == 0) { $string .= "$k" . "=" . "$v"; } else { $string .= "&" . "$k" . "=" . "$v"; } $i++; } } unset($k, $v); return $string; } /** * [MyRsaSign 签名字符串] * @author Devil * @blog http://gong.gg/ * @version 1.0.0 * @datetime 2017-09-24T08:38:28+0800 * @param [string] $prestr [需要签名的字符串] * @return [string] [签名结果] */ private function MyRsaSign($prestr) { if(stripos($this->config['rsa_private'], '-----') === false) { $res = "-----BEGIN RSA PRIVATE KEY-----\n"; $res .= wordwrap($this->config['rsa_private'], 64, "\n", true); $res .= "\n-----END RSA PRIVATE KEY-----"; } else { $res = $this->config['rsa_private']; } return openssl_sign($prestr, $sign, $res, OPENSSL_ALGO_SHA256) ? base64_encode($sign) : null; } /** * [MyRsaDecrypt RSA解密] * @author Devil * @blog http://gong.gg/ * @version 1.0.0 * @datetime 2017-09-24T09:12:06+0800 * @param [string] $content [需要解密的内容,密文] * @return [string] [解密后内容,明文] */ private function MyRsaDecrypt($content) { if(stripos($this->config['rsa_public'], '-----') === false) { $res = "-----BEGIN PUBLIC KEY-----\n"; $res .= wordwrap($this->config['rsa_public'], 64, "\n", true); $res .= "\n-----END PUBLIC KEY-----"; } else { $res = $this->config['rsa_public']; } $res = openssl_get_privatekey($res); $content = base64_decode($content); $result = ''; for($i=0; $iconfig['out_rsa_public'], '-----') === false) { $res = "-----BEGIN PUBLIC KEY-----\n"; $res .= wordwrap($this->config['out_rsa_public'], 64, "\n", true); $res .= "\n-----END PUBLIC KEY-----"; } else { $res = $this->config['out_rsa_public']; } $pkeyid = openssl_pkey_get_public($res); $sign = base64_decode($sign); if($pkeyid) { $verify = openssl_verify($prestr, $sign, $pkeyid, OPENSSL_ALGO_SHA256); openssl_free_key($pkeyid); } return (isset($verify) && $verify == 1) ? true : false; } /** * [SyncRsaVerify 同步返回签名验证] * @author Devil * @blog http://gong.gg/ * @version 1.0.0 * @datetime 2017-09-25T13:13:39+0800 * @param [array] $data [返回数据] * @param [boolean] $key [数据key] */ private function SyncRsaVerify($data, $key) { $string = json_encode($data[$key], JSON_UNESCAPED_UNICODE); return $this->OutRsaVerify($string, $data['sign']); } } ?>