今天写小程序的支付接口,参照的当然是微信支付API了。(结尾附上第二步全部代码php版)
另外,我也参照了简书上的这篇文章,浅显易懂:https://www.jianshu.com/p/72f5c1e3f8a5
其实小程序中唤起微信支付不外乎以下几个步骤:
1.获取openid
小程序获取openid是分两个步骤的
首先小程序前端通过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_code
红字为所需参数,appid和secret手动配置(开发者平台申请),code就是小程序前端获取的code了
2.获取prepay_id和paySign
拿着刚才的openid和订单号(自定义订单号)、订单金额(手动输入金额)以及其他平台参数拼接成一个xml文件作为请求体
通过后台接口内部访问微信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; }