今天寫小程序的支付接口,參照的固然是微信支付API了。(結尾附上第二步所有代碼php版)javascript
另外,我也參照了簡書上的這篇文章,淺顯易懂:https://www.jianshu.com/p/72f5c1e3f8a5php
其實小程序中喚起微信支付不外乎如下幾個步驟:前端
1.獲取openidjava
小程序獲取openid是分兩個步驟的web
首先小程序前端經過wx.login獲取code,而後用這個code經過後臺接口內部訪問微信官方API獲取openid、session_key數據庫
https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=CODE&grant_type=authorization_codejson
紅字爲所需參數,appid和secret手動配置(開發者平臺申請),code就是小程序前端獲取的code了小程序
2.獲取prepay_id和paySign微信小程序
拿着剛纔的openid和訂單號(自定義訂單號)、訂單金額(手動輸入金額)以及其餘平臺參數拼接成一個xml文件做爲請求體api
經過後臺接口內部訪問微信API接口:https://api.mch.weixin.qq.com/pay/unifiedorder
若是參數無誤,將如期返回prepay_id和paySign
3.前端拉起支付
拿到了所需參數,前端就能夠發起:wx.requestPayment 來拉起支付了。
wx.requestPayment({ 'timeStamp':timeStamp, 'nonceStr': nonceStr, 'package': 'prepay_id='+res.data.prepay_id, 'signType': 'MD5', 'paySign': res.data._paySignjs, 'success':function(res){ console.log(res); }, 'fail':function(res){ console.log('fail:'+JSON.stringify(res)); } })
如此就完成了小程序的支付,下面就要說一下今天踩的坑了。
1.appid問題
前期前端用的appid是用的他我的的appid,要知道小程序用戶受權小程序所生成的openid,是須要用appid來參與驗籤
的,這直接致使,後期將appid更換爲線上appid後,全部的測試用戶openid(這個openid在用戶受權以後直接存到數據
庫了,因此實際上咱們省略了第一步,直接從數據庫拿當前用戶的數據庫存儲的openid)走如上第二步的時候,都會報
openid和appid不匹配的錯誤,將前期用戶數據刪除,並更正appid以及其餘參數,從新受權後的openid就不會產生不匹
配的錯誤了。
第二步獲取prepay_id和paySign代碼(php版本):
<?php if (!defined('BASEPATH')) exit('No direct script access allowed'); define(APPID, 'wx74bmn4nbf81f1593e'); define(MCHID, '1510771171'); define(KEY, 'WlUprCqVM53L4MnI6Dz2Nmz7f44'); define(APPSECRET, '3b8e3202c39c67712879f6724fc8b42'); define(NOTIFY_URL, SITE_URL.'web/dayrui/controllers/return_url.php'); require_once FCPATH . 'branch/fqb/D_Wxapp.php'; require 'WxPay.class.php'; class Wxapp extends D_Wxapp { ... //支付,紅字參數依次是openid、訂單號、訂單說明、訂單金額 public function pay(){ $pay = $this->req; $weixinpay = new WxPay(APPID, $pay['openid'], MCHID, KEY, $pay['out_trade_no'], $pay['body'], $pay['total_fee']); exit_json(1, $weixinpay->pay()); } }
WxPay.class.php能夠直接用,只是注意get_ip()函數是用來獲取客戶端ip的,這裏稍加封裝了一下,會在後面給出封裝函數代碼
//WxPay.class.php <?php class WxPay { protected $appid; protected $mch_id; protected $key; protected $openid; protected $out_trade_no; protected $body; protected $total_fee; function __construct ($appid, $openid, $mch_id, $key, $out_trade_no, $body,$total_fee){ $this->appid = $appid; $this->openid = $openid; $this->mch_id = $mch_id; $this->key = $key; $this->out_trade_no = $out_trade_no; $this->body = $body; $this->total_fee = $total_fee; } public function pay (){ //統一下單接口 $return=$this->weixinapp (); return $return; } //微信小程序接口 private function weixinapp (){ //統一下單接口 $unifiedorder=$this->unifiedorder (); $parameters=array ('appId'=>$this->appid ,//小程序ID 'timeStamp'=>''.time().'',//時間戳 'nonceStr'=>$this->createNoncestr (),//隨機串 'package'=>'prepay_id='.$unifiedorder['prepay_id'],//數據包 'signType'=>'MD5'//簽名方式 ); $parameters['paySign']=$this->getSign ($parameters); return $parameters; } //統一下單接口 private function unifiedorder (){ $url='https://api.mch.weixin.qq.com/pay/unifiedorder'; $parameters=array ( 'appid'=>$this->appid , 'mch_id'=>$this->mch_id , 'nonce_str'=>$this->createNoncestr(), 'body'=>$this->body, 'out_trade_no'=>$this->out_trade_no , 'total_fee'=>$this->total_fee, 'spbill_create_ip'=>get_ip(), 'notify_url'=> NOTIFY_URL, 'openid'=>$this->openid, 'trade_type'=>'JSAPI' ); //統一下單簽名 //pre($parameters); $parameters['sign']=$this->getSign ($parameters); $xmlData=$this->arrayToXml ($parameters); $return=$this->xmlToArray ($this->postXmlCurl ($xmlData,$url,60)); return $return; } private static function postXmlCurl ($xml,$url,$second=30){ $ch=curl_init(); //設置超時 curl_setopt($ch,CURLOPT_TIMEOUT ,$second); 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_setopt($ch,CURLOPT_CONNECTTIMEOUT ,20); curl_setopt($ch,CURLOPT_TIMEOUT ,40); set_time_limit(0); //運行curl $data=curl_exec($ch); //返回結果 if ($data){ curl_close($ch); return $data; }else { $error=curl_errno($ch); curl_close($ch); throw new WxPayException("curl出錯,錯誤碼:$error"); } } //數組轉換成xml private function arrayToXml ($arr){ $xml="<root>"; foreach ($arr as $key=>$val){ if (is_array($val)){ $xml.="<".$key.">".arrayToXml ($val)."</".$key.">"; }else { $xml.="<".$key.">".$val."</".$key.">"; } } $xml.="</root>"; return $xml; } //xml轉換成數組 private function xmlToArray ($xml){ //禁止引用外部xml實體 libxml_disable_entity_loader(true); $xmlstring=simplexml_load_string($xml,'SimpleXMLElement',LIBXML_NOCDATA ); $val=json_decode(json_encode($xmlstring),true); return $val; } //做用:產生隨機字符串,不長於32位 private function createNoncestr ($length=32){ $chars="abcdefghijklmnopqrstuvwxyz0123456789"; $str=""; for ($i=0; $i<$length; $i++){ $str.=substr($chars,mt_rand(0,strlen($chars)-1),1); } return $str; } //做用:生成簽名 private function getSign ($Obj){ foreach ($Obj as $k=>$v){ $Parameters[$k]=$v; } //簽名步驟一:按字典序排序參數 ksort($Parameters); $String=$this->formatBizQueryParaMap ($Parameters,false); //簽名步驟二:在string後加入KEY $String=$String."&key=".$this->key ; //簽名步驟三:MD5加密 $String=md5($String); //簽名步驟四:全部字符轉爲大寫 $result_=strtoupper($String); return $result_; } ///做用:格式化參數,簽名過程須要使用 private function formatBizQueryParaMap ($paraMap,$urlencode){ $buff=""; ksort($paraMap); foreach ($paraMap as $k=>$v){ if ($urlencode){ $v=urlencode($v); } $buff.=$k."=".$v."&"; } $reqPar; if (strlen($buff)>0){ $reqPar=substr($buff,0,strlen($buff)-1); } return $reqPar; } }
get_ip():
function get_ip(){ //判斷服務器是否容許$_SERVER if(isset($_SERVER)){ if(isset($_SERVER[HTTP_X_FORWARDED_FOR])){ $realip = $_SERVER[HTTP_X_FORWARDED_FOR]; }elseif(isset($_SERVER[HTTP_CLIENT_IP])) { $realip = $_SERVER[HTTP_CLIENT_IP]; }else{ $realip = $_SERVER[REMOTE_ADDR]; } }else{ //不容許就使用getenv獲取 if(getenv("HTTP_X_FORWARDED_FOR")){ $realip = getenv( "HTTP_X_FORWARDED_FOR"); }elseif(getenv("HTTP_CLIENT_IP")) { $realip = getenv("HTTP_CLIENT_IP"); }else{ $realip = getenv("REMOTE_ADDR"); } } return $realip; }