微信支付真心最坑爹的支付,总结一下
一:前台页面
<include file="Public/mobile_head"/> <html> <head> <meta http-equiv="content-type" content="text/html;charset=utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1"/> <title>微信支付样例-支付</title> <script type="text/javascript"> //调用微信JS api 支付 function jsApiCall() { WeixinJSBridge.invoke( 'getBrandWCPayRequest', {$jsApiParameters}, function(res){ WeixinJSBridge.log(res.err_msg); //alert(res.err_code+res.err_desc+res.err_msg); var html="您已成功购买{$month_time}个月月卡,立刻获赠游戏币{$coin_award}。游戏UID:{$uid}<br>月卡到期时间: {$month_card_overtime}<br><button class='blue_btn mb_10 '>确定</button>"; $.Dialog.sure(html);//成功调用 提示一秒后自动关闭 $(".dialog_sure").click(function(){ WeixinJSBridge.call('closeWindow'); }); } ); } function callpay() { if (typeof WeixinJSBridge == "undefined"){ if( document.addEventListener ){ document.addEventListener('WeixinJSBridgeReady', jsApiCall, false); }else if (document.attachEvent){ document.attachEvent('WeixinJSBridgeReady', jsApiCall); document.attachEvent('onWeixinJSBridgeReady', jsApiCall); } }else{ jsApiCall(); } } </script> </head> <body> <br/> <font color="#9ACD32"><b>该笔订单支付金额为<span style="color:#f00;font-size:50px">{$mustpay/100}元</span>钱</b></font><br/><br/> <div align="center"> <button style="210px; height:50px; border-radius: 15px;background-color:#FE6714; border:0px #FE6714 solid; cursor: pointer; color:white; font-size:16px;" type="button" onclick="callpay()" >立即支付</button> </div> </body> </html>
两个问题
(1)$jsApiParameters参数要对,包含noticestr,stamp,prepayid等,不能丢失,否则付款按钮不能点击
(2)WeixinJSBridge.call('closeWindow');这是微信的关闭当前页面,不然支付完成还会返回到这里
二.后台php
function index() { if(IS_POST){ /**前台参数post处理部分 略***/ } //用的是weiphp 主要是要获取openid $param ['token'] = get_token (); $param ['openid'] = get_openid (); $config = getAddonConfig ( 'Jssdk' ); $appid = $config['APPID']; $secret = $config['APPSECRET']; //$id = 1; //如有插件中数据id,分享url中应加入id参数 //$url = addons_url ( 'WxPay://WxPay/payorder', $param ); //分享的url需要和WeixinAddonModel中的组装回复url保持相同 //$this->assign ( 'share_url', $url ); $jssdk = new JSSDK($appid, $secret); $jssdk->debug = false; //启用本地调试模式,将官方的两个json文件放到入口文件index.php同级目录即可! $signPackage = $jssdk->GetSignPackage(); $this->assign ( 'signPackage', $signPackage ); //微信支付部分 //此处可以动态获取数据库中的MCHID和KEY /***************************/
//这部分自行配置 /***************************/ $jssdk->MCHID =$config['MCHID']; // 动态MCHID;微信支付分配的商户号 $jssdk->KEY = $config['KEY']; // 动态KEY; $jssdk->SSLCERT_PATH = $config['SSLCERT_PATH']; // 动态KEY; $jssdk->SSLKEY_PATH = $config['SSLKEY_PATH']; // 动态KEY; $jssdk->APPID = $config['APPID']; // 动态KEY; $jssdk->APPSECRET = $config['APPSECRET']; // 动态KEY; //=========步骤2:使用统一支付接口,获取prepay_id============ //使用统一支付接口 $jssdk->parameters['openid'] = $param ['openid']; //trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识。 $jssdk->parameters['body']="订单支付"; //商品或支付单简要描述 $jssdk->parameters['out_trade_no'] = "outtradeno".time(); //商户系统内部的订单号,32个字符内、可包含字母,不可重复 $jssdk->parameters['total_fee'] = 1; //此处单位为分 出现小数点接口报错必须是整数 $jssdk->parameters['notify_url'] = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx 带http的地址 就下面那个alarmnotify方法'; //接收微信支付异步通知回调地址 $jssdk->parameters['trade_type'] = "JSAPI"; //取值如下:JSAPI,NATIVE,APP $jssdk->parameters['spbill_create_ip'] = get_client_ip(); //APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。 //以下非必填参数根据需要添加 //$jssdk->parameters['device_info'] = "013467007045764"; //微信支付分配的终端设备号,商户自定义 $jssdk->parameters['detail'] = $desc; //商品名称明细列表 //$jssdk->parameters['attach'] = "说明"; //附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据 //$jssdk->parameters['fee_type'] = "CNY"; //符合ISO 4217标准的三位字母代码,默认人民币:CNY $jssdk->parameters['time_start'] = time(); //订单生成时间,格式为yyyyMMddHHmmss //$jssdk->parameters['time_expire'] = "20091227091010"; //订单失效时间,格式为yyyyMMddHHmmss //$jssdk->parameters['goods_tag'] = "WXG"; //商品标记,代金券或立减优惠功能的参数 $jssdk->parameters['product_id'] = $product_id; //trade_type=NATIVE,此参数必传。此id为二维码中包含的商品ID,商户自行定义。 $prepay_id = $jssdk->getPrepayId(); //微信生成的预支付回话标识,用于后续接口调用中使用,该值有效期为2小时 $jssdk->prepay_id = $prepay_id ; //微信生成的预支付回话标识,用于后续接口调用中使用,该值有效期为2小时 //=========步骤3:使用jsapi调起支付============ $jsApiParameters = $jssdk->getParameters(); //=========步骤N:当有prepayid再生成订单用户自己生成订单============ if($prepay_id){
//这是自己的业务逻辑,理论上有prepayid之后,自己数据库里面就应该有一张订单,未支付的订单 $data['uid']=$uid; $data['mustpay']=$mustpay; $data['prepay_id']=$prepay_id; $data['goods']=$sel;//选择的商品,目前是select的值 $data['desc']=$desc; $data['product_id'] = $product_id; $data['openid']= $param ['openid']; $data['trade_type']="JSAPI"; $data['time']=time(); $data['body']= $jssdk->parameters['body']; $data['out_trade_no']=$jssdk->parameters['out_trade_no']; M ( "mall_order" )->add($data); } //JSSDK 用户支付完成后的一些系统操作 /* $param1 ['dcnum'] = $jssdk->parameters['out_trade_no']; $param1 ['openid'] = $jssdk->parameters['openid']; $ajaxurl = addons_url ( 'Jssdk://Jssdk/orderpaid', $param1 ); //用户支付完成后,在微信支付返回alarmnotify之前(不保证时序),可以通过ajax调用,进行一些预处理操作 $jsApiParameters = substr($jsApiParameters,1,-1).",success: function (res) { // 支付成功后的js回调函数 }"; */ $param1 ['openid'] = $jssdk->parameters['openid']; $ajaxurl= addons_url ( 'Member://Member/show_info' ,$param); $this->assign("ajaxurl",$ajaxurl); //向页面传整理好的调起支付参数 $this->assign("jsApiParameters",$jsApiParameters); //向页面传整理好的调起支付参数 $this->display (); }
三,通知处理
//支付完成接收支付服务器返回通知 public function alarmnotify(){ $post_data = $GLOBALS ['HTTP_RAW_POST_DATA']; ThinkLog::record ( "alarmnotify". $post_data ); //log的内容大体如下 /* public 'appid' => string 'w345345345a83a9b9b9' (length=18) public 'bank_type' => string 'CFT' (length=3) public 'cash_fee' => string '1' (length=1) public 'fee_type' => string 'CNY' (length=3) public 'is_subscribe' => string 'Y' (length=1) public 'mch_id' => string '1233453902' (length=10) public 'nonce_str' => string 'ma4l5345345rzZN' (length=16) public 'openid' => string 'oV345345345calllGlBdwRU1BjY' (length=28) public 'out_trade_no' => string 'outtradeno14234324' (length=20) public 'result_code' => string 'SUCCESS' (length=7) public 'return_code' => string 'SUCCESS' (length=7) public 'sign' => string '735C329E5234234796E723728969' (length=32) public 'time_end' => string '2034534504523' (length=14) public 'total_fee' => string '1' (length=1) public 'trade_type' => string 'JSAPI' (length=5) public 'transaction_id' => string '13453453453422835' (length=28) */ $result = xmlToArray($post_data); $resp = true; $respdata["return_code"] = "SUCCESS"; $respdata["return_msg"] = ""; $map["appid"] = $result["appid"]; $map["mch_id"] = $result["mch_id"]; $map["openid"] = $result["openid"]; $map["out_trade_no"] = $result["out_trade_no"]; if($result['result_code']=='SUCCESS' && $result['return_code']=='SUCCESS'){ //支付成功了 //进行业务处理 echo "success"; } }
四,微信支付类
<?php /** 类是uctoo写的,貌似现在的类更好,我反正能用就行, * 官方文档:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html * 微信支付:http://pay.weixin.qq.com/wiki/doc/api/index.php?chapter=9_1# * 官方示例:http://demo.open.weixin.qq.com/jssdk/sample.zip * * 微信JSSDK类,主要修改了保存会话信息机制,示例中使用的是文件,这里使用了ThinkPHP的缓存机制,参考官方提供的示例文档 * 新增了调试模式,调用示例如下: * $jssdk = new JSSDK(C('WX_APPID'), C('WX_SECRET')); * $jssdk->debug = true; //启用本地调试模式,将官方的两个json文件放到入口文件index.php同级目录即可! * $signPackage = $jssdk->GetSignPackage(); * @命名空间版本 * @author uctoo (www.uctoo.com) & 阿甘 (QQ:33808 624) * @date 2015-1-10 14:10 */ namespace Com; class JsSdk { private $appId; private $appSecret; public $debug = false; public $parameters;//获取prepay_id时的请求参数 //受理商ID,身份标识 public $MCHID = ''; //商户支付密钥Key。审核通过后,在微信发送的邮件中查看 public $KEY = ''; //=======【JSAPI路径设置】=================================== //获取access_token过程中的跳转uri,通过跳转将code传入jsapi支付页面 public $JS_API_CALL_URL = ''; //=======【证书路径设置】===================================== //证书路径,注意应该填写绝对路径 public $SSLCERT_PATH = '/xxx/xxx/xxxx/WxPayPubHelper/cacert/apiclient_cert.pem'; public $SSLKEY_PATH = '/xxx/xxx/xxxx/WxPayPubHelper/cacert/apiclient_key.pem'; //=======【异步通知url设置】=================================== //异步通知url,商户根据实际开发过程设定 //C('url')."admin.php/order/notify_url.html"; public $NOTIFY_URL = ''; //=======【curl超时设置】=================================== //本例程通过curl使用HTTP POST方法,此处可修改其超时时间,默认为30秒 public $CURL_TIMEOUT = 30; public $prepay_id; public function __construct($appId, $appSecret) { $this->appId = $appId; $this->appSecret = $appSecret; } public function getSignPackage() { $jsapiTicket = $this->getJsApiTicket(); $url = "http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"; $timestamp = time(); $nonceStr = $this->createNonceStr(); // 这里参数的顺序要按照 key 值 ASCII 码升序排序 $string = "jsapi_ticket=$jsapiTicket&noncestr=$nonceStr×tamp=$timestamp&url=$url"; $signature = sha1($string); $signPackage = array( "appId" => $this->appId, "nonceStr" => $nonceStr, "timestamp" => $timestamp, "url" => $url, "signature" => $signature, "rawString" => $string ); return $signPackage; } private function createNonceStr($length = 16) { $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; $str = ""; for ($i = 0; $i < $length; $i++) { $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1); } return $str; } private function getJsApiTicket() { //debug模式 if ($this->debug) { // jsapi_ticket 应该全局存储与更新,以下代码以写入到文件中做示例 $data = json_decode(file_get_contents("jsapi_ticket.json")); } else { //从cache中读取,基于ThinkPHP的缓存机制 $data = (object)(S('jsapi_ticket_json')); } if ($data->expire_time < time()) { $accessToken = $this->getAccessToken(); $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=1&access_token=$accessToken"; $res = json_decode($this->httpGet($url)); $ticket = $res->ticket; if ($ticket) { $data->expire_time = time() + 7200; $data->jsapi_ticket = $ticket; //debug模式 if ($this->debug) { $fp = fopen("jsapi_ticket.json", "w"); fwrite($fp, json_encode($data)); fclose($fp); } else { //将对象以数组的形式进行缓存 S('jsapi_ticket_json', (array)$data); } } } else { $ticket = $data->jsapi_ticket; } return $ticket; } private function getAccessToken() { //debug模式 if ($this->debug) { // access_token 应该全局存储与更新,以下代码以写入到文件中做示例 $data = json_decode(file_get_contents("access_token.json")); dump($data); die(); } else { //从缓存中读取数组并转成对象 $data = (Object)(S('access_token.json')); } if ($data->expire_time < time()) { $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$this->appId&secret=$this->appSecret"; $res = json_decode($this->httpGet($url)); $access_token = $res->access_token; if ($access_token) { $data->expire_time = time() + 7000; $data->access_token = $access_token; //debug模式 if ($this->debug) { $fp = fopen("access_token.json", "w"); fwrite($fp, json_encode($data)); fclose($fp); } else { //缓存数组 S('access_token.json', (array)$data); } } } else { $access_token = $data->access_token; } return $access_token; } private function httpGet($url) { $curl = curl_init(); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_TIMEOUT, 500); curl_setopt($curl, CURLOPT_URL, $url); $res = curl_exec($curl); //错误检测 $error = curl_error($curl); curl_close($curl); //发生错误,抛出异常 if($error) throw new Exception('请求发生错误(表检查是否在授权域名下访问):' . $error); return $res; } //微信支付相关方法 /** * 作用:格式化参数,签名过程需要使用 */ function formatBizQueryParaMap($paraMap, $urlencode) { $buff = ""; ksort($paraMap); foreach ($paraMap as $k => $v) { if($urlencode) { $v = urlencode($v); } //$buff .= strtolower($k) . "=" . $v . "&"; $buff .= $k . "=" . $v . "&"; } $reqPar = ""; if (strlen($buff) > 0) { $reqPar = substr($buff, 0, strlen($buff)-1); } return $reqPar; } /** * 作用:设置jsapi的参数 */ public function getParameters() { $jsApiObj["appId"] = $this->appId; //请求生成支付签名时需要,js调起支付参数中不需要 $timeStamp = time(); $jsApiObj["timeStamp"] = "$timeStamp"; //用大写的timeStamp参数请求生成支付签名 $jsParamObj["appId"] =$jsApiObj["appId"]; //加入一行appid $jsParamObj["timeStamp"] = $jsApiObj["timeStamp"]; //用小写的timestamp参数生成js支付参数,还要注意数据类型,坑! $jsParamObj["nonceStr"] = $jsApiObj["nonceStr"] = $this->createNoncestr(); $jsParamObj["package"] = $jsApiObj["package"] = "prepay_id=$this->prepay_id"; $jsParamObj["signType"] = $jsApiObj["signType"] = "MD5"; $jsParamObj["paySign"] = $jsApiObj["paySign"] = $this->getSign($jsApiObj); $jsParam = json_encode($jsParamObj); return $jsParam; } /** * 获取prepay_id */ function getPrepayId() { $result = $this->xmlToArray($this->postXml()); $prepay_id = $result["prepay_id"]; return $prepay_id; } /** * 作用:将xml转为array */ public function xmlToArray($xml) { //将XML转为array $array_data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); return $array_data; } /** * 作用:post请求xml */ function postXml() { $xml = $this->createXml(); return $this->postXmlCurl($xml,"https://api.mch.weixin.qq.com/pay/unifiedorder",$this->CURL_TIMEOUT); } /** * 作用:以post方式提交xml到对应的接口url */ public function postXmlCurl($xml,$url,$second=30) { //初始化curl $ch = curl_init(); //设置超时 curl_setopt($ch,CURLOP_TIMEOUT, $this->CURL_TIMEOUT); //这里设置代理,如果有的话 //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8'); //curl_setopt($ch,CURLOPT_PROXYPORT, 8080); curl_setopt($ch,CURLOPT_URL, $url); curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE); curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE); //设置header curl_setopt($ch, CURLOPT_HEADER, FALSE); //要求结果为字符串且输出到屏幕上 curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); //post提交方式 curl_setopt($ch, CURLOPT_POST, TRUE); curl_setopt($ch, CURLOPT_POSTFIELDS, $xml); //运行curl $data = curl_exec($ch); curl_close($ch); //返回结果 if($data) { curl_close($ch); return $data; } else { $error = curl_errno($ch); echo "curl出错,错误码:$error"."<br>"; echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>"; curl_close($ch); return false; } } /** * 作用:设置标配的请求参数,生成签名,生成接口参数xml */ function createXml() { $this->parameters["appid"] = $this->appId;//公众账号ID $this->parameters["mch_id"] = $this->MCHID;//商户号 $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串 $this->parameters["sign"] = $this->getSign($this->parameters);//签名 return $this->arrayToXml($this->parameters); } /** * 作用:array转xml */ function arrayToXml($arr) { $xml = "<xml>"; foreach ($arr as $key=>$val) { if (is_numeric($val)) { $xml.="<".$key.">".$val."</".$key.">"; } else $xml.="<".$key."><![CDATA[".$val."]]></".$key.">"; } $xml.="</xml>"; return $xml; } /** * 作用:生成签名 */ public function getSign($Obj) { foreach ($Obj as $k => $v) { $Parameters[$k] = $v; } //签名步骤一:按字典序排序参数 ksort($Parameters); $String = $this->formatBizQueryParaMap($Parameters, false); //echo '【string1】'.$String.'</br>'; //签名步骤二:在string后加入KEY $String = $String."&key=".$this->KEY; //echo "【string2】".$String."</br>"; //签名步骤三:MD5加密 $String = md5($String); //echo "【string3】 ".$String."</br>"; //签名步骤四:所有字符转为大写 $result_ = strtoupper($String); //echo "【result】 ".$result_."</br>"; return $result_; } }
有问题再单独开日志吧...感觉不是很顺畅..比支付宝差远了