2021.01.11
关于支付宝退款问题
1 调用支付宝sdk
2 自己写封装
1 调用支付宝sdk,网络是比较强大的,只要搜索都能搜索到。
1 public function refund_and_query($data) 2 { 3 import("alipayaop/AopClient", EXTEND_PATH); 4 import("alipayaop/request/AlipayTradeRefundRequest", EXTEND_PATH); 5 // include('alipayaop/AopClient.php'); 6 // // include('alipayaop/request/AlipayTradeAppPayRequest.php'); 7 // include('alipayaop/request/AlipayTradeRefundRequest.php'); 8 9 10 $aop = new \AopClient(); 11 $aop->gatewayUrl = 'https://openapi.alipay.com/gateway.do'; 12 $aop->appId = $this->config['app_id']; 13 $aop->rsaPrivateKey = $this->config['rsaPrivateKey']; 14 $aop->alipayrsaPublicKey=$this->config['alipayPublicKey']; 15 $aop->apiVersion = $this->version; 16 $aop->signType = $this->sign_type; 17 $aop->postCharset=$this->charset; 18 $aop->format='json'; 19 $request = new \AlipayTradeRefundRequest(); 20 $bizcontent = json_encode([ 21 'out_trade_no'=>$data['out_trade_no'], 22 // 'trade_no'=> $wepay_serial, 23 'refund_amount'=> $data['refund_fee'], 24 'refund_reason'=>'正常退款' 25 ]); 26 $request->setBizContent($bizcontent); 27 $result = $aop->execute($request); 28 FLog($result,'refund_and_query_alipay'); 29 $responseNode = str_replace(".", "_", $request->getApiMethodName()) . "_response"; 30 31 $resultCode = $result->$responseNode->code; 32 33 if(!empty($resultCode)&&$resultCode == 10000){ 34 return ['code'=>1,'msg'=>'退款成功']; 35 } else { 36 $resultMsg = $result->$responseNode->sub_msg; 37 return ['code'=>0,'msg'=>$resultMsg]; 38 } 39 }
对于以上使用sdk 出来的结果以及调用方式来说相当的简单。我这边是直接使用官方提供的sdk 引入后 直接使用 import 调用
无任何需要注意的地方只要能保证输入的值没有问题,就百分百成功,而且不像支付的时候会遇到各种问题,至少我这边没有
2 自己写封装
无论关于使用sdk还是自己写 我都是在以前支付封装中加入书写的。下面不给出完整的代码,只针对支付退款。并且由于时间问题,我这里没有给出特别完美的代码,只能说带入没有问题。主要说的是在这个过程中发现的问题
1 public function refund_and_query1($data) 2 { 3 $biz_content=[ 4 'out_trade_no'=>$data['out_trade_no'], 5 'refund_amount'=>$data['refund_fee'], 6 'refund_reason'=>'正常退款' 7 ]; 8 $postdata=[ 9 'app_id'=>$this->config['app_id'], 10 'version'=>$this->version, 11 'format'=>'json', 12 'sign_type'=>$this->sign_type, 13 'method'=>'alipay.trade.refund', 14 'timestamp'=>date('Y-m-d H:i:s'), 15 // 'timestamp'=>'2021-01-11 10:41:23', 16 17 'charset'=>$this->charset, 18 ]; 19 20 // dump($biz_content); 21 22 23 // openssl_sign($str_q, $sign, $rsaPrivateKey, OPENSSL_ALGO_SHA256); 24 25 $bizcontentD=['biz_content'=>json_encode($biz_content)]; 26 $biz_content_res=$this->alisign(array_merge($bizcontentD,$postdata)); 27 $postdata['sign']=$biz_content_res['sign']; 28 dump($postdata['sign']); 29 // die; 30 31 32 33 $str=$this->keyandvalStr($postdata,1); 34 35 36 37 38 //系统参数放入GET请求串 39 $requestUrl = $this->submitUrl . "?".$str; 40 41 dump($requestUrl); 42 43 44 45 46 //发起HTTP请求 47 try { 48 $resp = $this->curl($requestUrl, $bizcontentD); 49 dump($resp); 50 } catch (Exception $e) { 51 $this->logCommunicationError($sysParams["method"], $requestUrl, "HTTP_ERROR_" . $e->getCode(), $e->getMessage()); 52 return false; 53 } 54 }
针对以上代码发现的问题。
1、对于 biz_content 的处理,不同于支付的时候,除了在sign中使用 在生成数据字符串拼接的时候并没有用到,而在http请求的时候却存在着一定的使用
2、在发起HTTP请求的时候,最好还是使用官方提供的curl,因为自己写的时候总是会报 验签错误
protected function curl($url, $postFields = null) { $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); $postBodyString = ""; $encodeArray = Array(); $postMultipart = false; if (is_array($postFields) && 0 < count($postFields)) { foreach ($postFields as $k => $v) { if ("@" != substr($v, 0, 1)) //判断是不是文件上传 { $postBodyString .= "$k=" . $v . "&"; $encodeArray[$k] = $v; } else //文件上传用multipart/form-data,否则用www-form-urlencoded { $postMultipart = true; $encodeArray[$k] = new \CURLFile(substr($v, 1)); } } unset ($k, $v); curl_setopt($ch, CURLOPT_POST, true); if ($postMultipart) { curl_setopt($ch, CURLOPT_POSTFIELDS, $encodeArray); } else { curl_setopt($ch, CURLOPT_POSTFIELDS, substr($postBodyString, 0, -1)); } } if (!$postMultipart) { $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)) { throw new Exception(curl_error($ch), 0); } else { $httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if (200 !== $httpStatusCode) { throw new Exception($reponse, $httpStatusCode); } } curl_close($ch); return $reponse; }
重新更新后提示几点注意项
1 生成的公私钥为2048位,上传支付宝公钥自己保留私钥做数据加密
2 上传支付宝公钥后会出现两个公钥 一个是自己提供的 一个是支付宝端生成的,保留支付宝公钥,主要做回调验签使用
3 这次改好后就不需要再为支付宝无法验签发愁了。回调中调用 rsaCheck 将获取的数据原封不动的传入 就行了喔 是不是很方便 看图了解
忘记之前有没有做过测试 不过这次的测试是 mobile 直接过 没有问题
1 <?php 2 namespace pay; 3 4 /** 5 * 支付宝支付 6 */ 7 class Alipay 8 { 9 // 公私钥 2048 位 10 private $config = [ 11 "app_id"=>"*****************", 12 "rsaPrivateKey"=>"*******************************", 13 "alipayPublicKey"=>"*********************",//支付宝公钥 14 15 ]; 16 17 private $charset = 'utf-8'; 18 private $sign_type = 'RSA2'; 19 private $version = '1.0'; 20 21 22 private $calltype = 'app';//调用端口 app mobile pc 23 private $submitUrl = "https://openapi.alipay.com/gateway.do" ;//提交地址 24 // private $submitUrl = "https://openapi.alipaydev.com/gateway.do";//测试地址 25 private $submit_auto = true; 26 27 public function __construct($payTypeInfo=[]){ 28 if($payTypeInfo) 29 { 30 foreach ($payTypeInfo as $k => $val) { 31 if($k=='calltype') 32 { 33 $this->calltype=$val['val']!=''?$val['val']:$this->calltype; 34 }else{ 35 // $this->config[$k]=$val['val']; 36 } 37 } 38 } 39 } 40 public function index($data) 41 { 42 // 端口检测 43 $res=$this->calltypeCheck($data); 44 if($res['code']==0){ return $res; } 45 46 $calltype=$this->calltype; 47 return $this->$calltype($data); 48 } 49 50 public function app($data) 51 { 52 $str=$this->keyandvalStr($this->postdata($data),1); 53 Flog($str); 54 // $str=$this->submitUrl.'?'.$str; 55 // //返回链接不能带官方https链接 56 return ['code'=>1,'data'=>['type'=>'app','str'=>$str]]; 57 } 58 59 public function pc($data) 60 { 61 $html=$this->formsubmithtml($this->submitUrl,$this->postdata($data)); 62 // var_dump($html); 63 return ['code'=>1,'data'=>['type'=>'pc','html'=>$html]]; 64 } 65 66 public function mobile($data) 67 { 68 // dump($data); 69 $str=$this->keyandvalStr($this->postdata($data),1); 70 $url="https://openapi.alipay.com/gateway.do?".$str; 71 return ['code'=>1,'data'=>['type'=>'mobile','url'=>$url]]; 72 } 73 74 75 // 获取数据 76 public function postdata($data) 77 { 78 $calltypeD=$this->calltypeD(); 79 80 $return_url=$data['return_url']; 81 $notify_url=$data['notify_url']; 82 83 // 对subiect进行 & 删除 84 $subject=str_replace("&","",$data['subject']); 85 86 $biz_content=[ 87 'body'=>'', 88 'subject'=>$subject, 89 'out_trade_no'=>$data['pay_sn'], 90 'timeout_express'=>'30m', 91 'total_amount'=>$data['total'], 92 'product_code'=>$calltypeD['product_code'], 93 "goods_type"=>1,//商品类型 0虚拟商品 1实物商品 94 ]; 95 $post=[ 96 'app_id'=>$this->config['app_id'], 97 'method'=>$calltypeD['method'], 98 'format'=>'json', 99 'charset'=>$this->charset, 100 'sign_type'=>$this->sign_type, 101 'return_url'=>$return_url, 102 'sign'=>'', 103 'timestamp'=>date('Y-m-d H:i:s'), 104 'version'=>$this->version, 105 'notify_url'=>$notify_url, 106 'biz_content'=>json_encode($biz_content), 107 ]; 108 109 $signdata=$this->alisign($post); 110 111 return $signdata; 112 } 113 114 // 根据类型获取想要的信息 115 public function calltypeD($field='') 116 { 117 $D=[ 118 'app'=>['product_code'=>'QUICK_MSECURITY_PAY','method'=>'alipay.trade.app.pay'], 119 'pc'=>['product_code'=>'FAST_INSTANT_TRADE_PAY','method'=>'alipay.trade.page.pay'], 120 'mobile'=>['product_code'=>'QUICK_WAP_WAY','method'=>'alipay.trade.wap.pay'], 121 ]; 122 if($field!='') 123 { 124 return isset($D[$this->calltype])?$D[$this->calltype][$field]:''; 125 }else{ 126 return isset($D[$this->calltype])?$D[$this->calltype]:[]; 127 } 128 129 130 } 131 132 // 端口检测 133 public function calltypeCheck($data) 134 { 135 // 对配置信息进行检测 136 if($this->config['app_id']=='') { return ['code'=>0,'msg'=>'请先配置 app_id']; } 137 if($this->config['rsaPrivateKey']=='') { return ['code'=>0,'msg'=>'请先配置 rsaPrivateKey']; } 138 139 return ['code'=>1,'msg'=>'检测通过']; 140 } 141 142 143 /************************** 支付宝公用方法 ************************************/ 144 145 public function alisign($data) 146 { 147 $rsaPrivateKey=$this->config['rsaPrivateKey']; 148 $buff = ""; 149 ksort($data); 150 $buff=$this->keyandvalStr($data); 151 $str = chunk_split($rsaPrivateKey, 64, "\n"); 152 $res = "-----BEGIN RSA PRIVATE KEY-----\n$str-----END RSA PRIVATE KEY-----\n"; 153 154 $sign=$this->ras2Sign($buff,$res); 155 $data['sign']=$sign; 156 return $data; 157 } 158 public function keyandvalStr($data,$flg=0) 159 { 160 $buff=""; 161 foreach ($data as $k => $v) { 162 if($flg!=0 && $v != "" && !is_array($v)) 163 { 164 $v=urlencode($v); 165 $buff .= $k . "=" . $v . "&"; 166 }else{ 167 if($k != "sign" && $v != "" && !is_array($v)){ 168 $buff .= $k . "=" . $v . "&"; 169 } 170 } 171 } 172 $buff = trim($buff, "&"); 173 return $buff; 174 } 175 176 public function ras2Sign($buff,$res) 177 { 178 $sign=''; 179 openssl_sign($buff, $sign, $res, OPENSSL_ALGO_SHA256); 180 $sign = base64_encode($sign); 181 return $sign; 182 } 183 public function formsubmithtml($action,$data) 184 { 185 $sHtml = "<form id='alipaysubmit' name='alipaysubmit' action='".$action."' method='POST'>"; 186 foreach ($data as $key => $value) { 187 $val = str_replace("'","'",$value); 188 $sHtml.= "<input type='hidden' name='".$key."' value='".$val."'/>"; 189 } 190 $sHtml = $sHtml."<input type='submit' class='paysubmit' value='ok' ></form>"; 191 if($this->submit_auto==true) 192 { 193 $sHtml = $sHtml."<script>document.forms['alipaysubmit'].submit();</script>"; 194 } 195 196 return $sHtml; 197 } 198 199 200 201 /******************************回调验签*************************************/ 202 public function rsaCheck($params) 203 { 204 $gmt_create=$params['gmt_create']; 205 $config=$this->config; 206 $sign = $params['sign']; 207 208 unset($params['sign']); 209 unset($params['sign_type']); 210 return $this->verify($this->getCheckSignContent($params), $sign, $config['alipayPublicKey'], $this->sign_type); 211 } 212 213 function getCheckSignContent($params) 214 { 215 ksort($params); 216 $stringToBeSigned = ""; 217 $i = 0; 218 foreach ($params as $k => $v) { 219 if ($i == 0) { 220 $stringToBeSigned .= "$k" . "=" . "$v"; 221 } else { 222 $stringToBeSigned .= "&" . "$k" . "=" . "$v"; 223 } 224 $i++; 225 } 226 227 unset ($k, $v); 228 return $stringToBeSigned; 229 } 230 231 232 233 function verify($data, $sign, $alipayPublicKey, $signType = 'RSA') 234 { 235 $res = "-----BEGIN PUBLIC KEY-----\n" . 236 wordwrap($alipayPublicKey, 64, "\n", true) . 237 "\n-----END PUBLIC KEY-----"; 238 239 ($res) or die('支付宝RSA公钥错误。请检查公钥文件格式是否正确'); 240 241 //调用openssl内置方法验签,返回bool值 242 243 $result = FALSE; 244 if ("RSA2" == $signType) { 245 $result = (openssl_verify($data, base64_decode($sign), $res, OPENSSL_ALGO_SHA256) === 1); 246 } else { 247 $result = (openssl_verify($data, base64_decode($sign), $res) === 1); 248 } 249 250 return $result; 251 } 252 264 }
在规矩中有自己的不规矩,走出不一样的路才是属于自己的路