微信支付小程序支付
前言:此篇博客是實現微信小程序支付,語言是ThinkPHP(php)+微信小程序,需要商戶號,appid,服務器。
參考:
小程序支付API文檔
微信小程序開發文檔
PHP代碼如下:
Index.php
<?php
mespace app\pay\controller;
use think\Controller;
use think\Db;
use think\Exception;
use think\Request;
class Index extends Controller{
private $appid = '****';
private $secret = '****';
public function buyOrder(){
$request=Request::instance();
if( !$request->param('openid')){ return json_encode(['state'=>-1,'msg'=>'err lack field! openid']) ; }
if( !$request->param('order_price') ){ return json_encode(['state'=>-1,'msg'=>'err lack field!']) ; }
if( !is_float((float)$request->param('order_price') ) ){ return json_encode( ['state' => -1 , 'msg' => 'err type of field '] );}
$orderInfo['order_price'] = (float)$request->param('order_price');
$orderInfo['openid']=$request->param('openid');
//生成訂單號
$orderInfo['order_no'] = date( 'Ymd' , time() ). time() . rand(1000000,9999999);
//訂單名稱
$orderInfo['order_title'] = '微信支付測試';
//開啓事務
Db::startTrans();
try{
//需要修改
$url = 'http://***/public/pay/Pay/index';
$result = $this->post_curl( $url , $orderInfo );
//此處等於200
if( !$result['state'] == 200 ){ Db::rollback(); return json_encode( [ 'state' => 0 , 'msg' => '訂單生成失敗!' , 'result' => $result ] ); }
//查看用戶是否已經下單,刪除未支付訂單 Record_order 臨時訂單表
// $order = Db::table('record_order')->where([ 'openid'=>$orderInfo['openid'] , 'state' => 0 ])->find();
// if( $order ){ Db::table('record_order')->where( 'id' , $order['id'] )->delete() ; }
//保存訂單
$is_in = Db::table('record_order')->insert([
'id' => $orderInfo['order_no'] ,
'openid' => $orderInfo['openid'] ,
'price' => $orderInfo['order_price'] ,
'nonce_str' => $result['nonceStr'] ,
'state'=>0,
'timestamp' => date( 'Y-m-d h:i:s' ) ]);
if( !$is_in ){ Db::rollback(); return json_encode( [ 'state' => -1 , 'msg' => '訂單保存失敗!請重試!']) ; }
//下單記錄
// Db::table('List_ticket')->update( [ 'id' => $tickes[$i]['id'] , 'openid' => $orderInfo['openid'] , 'time_add' => time() ] );
Db::commit();
}catch (Exception $e){
Db::rollback();
return json_encode(['state'=>0 , 'msg' => '系統錯誤']) ;
}
return json_encode( ['state' => 1 , 'msg' => '下單成功!' , 'result' => $result ]);
}
public function checkOrder(){
$request=Request::instance();
if( !$request->param('out_trade_no') ){ return json_encode( ['state' => -1 , 'msg' => 'lack field of out_trade_no '] ) ; }
$orderInfo['out_trade_no'] = $request->param('out_trade_no');
if( !$request->param('openid') ){ return json_encode( ['state' => -1 , 'msg' => 'lack field of openid'] ) ; }
$orderInfo['openid'] = $request->param('openid');
if( !$request->param('nonceStr') ){ return json_encode( ['state' => -1 , 'msg' => 'lack field of nonceStr '] ) ; }
$orderInfo['nonceStr'] = $request->param('nonceStr');
//檢查有沒有訂單返回
$orderInfo_result = Db::table('record_odcb')->where('out_trade_no',$orderInfo['out_trade_no'])->find();
if(!$orderInfo_result){
//當微信未能callback返回時,主動查詢票據
$url = 'http://***/public/pay/Pay/checkOrder';
$result = $this->post_curl( $url , $orderInfo );
if( !$result['state'] == 1 ){ return json_encode( ['state'=>0 , 'msg'=>'支付失敗!' ] ) ; }
else {
$res=Db::table('record_order')->where('nonceStr',$orderInfo['nonceStr'])->update('state',2);
if($res){
return json_encode( ['state' => 1 , 'msg' => '支付成功,訂單狀態修改成功'] ) ;
}else{
return json_encode( ['state' => 1 , 'msg' => '支付成功,訂單狀態修改失敗'] ) ;
}
}
}
return json_encode( ['state' => 1 , 'msg' => '支付成功,訂單狀態修改成功'] ) ;
}
/**
* 獲取用戶信息
*/
/**
* @return string
* 名稱:獲取用戶信息
* url http://localhost/jingwei/public/test/Wx/getInfo
* 需要 $appid $secret
* 傳入 encryptedData iv js_code
* 返回 -1 or 1
*
*
*/
public function getInfo(){
$request=Request::instance();
if (!$request->param('encryptedData')) {return json_encode(['state' => -1, 'msg' => 'err lack field!']);}
$data['encryptedData'] = $request->param('encryptedData');
if (!$request->param('iv')) {return json_encode(['state' => -1, 'msg' => 'err lack field!']);}
$data['iv'] = $request->param('iv');
if (!$request->param('js_code')) {return json_encode(['state' => -1, 'msg' => 'err lack field!']);}
$data['js_code'] = $request->param('js_code');
//獲取Session 獲取openid
$session_key = json_decode($this->getSession($this->appid, $this->secret, $data['js_code']), true);
if (!isset($session_key['openid'])) {
return json_encode(['state' => -1, 'msg' => '請重新登錄!']);
}
$openid = $session_key['openid'];
$errCode = $this->decryptData($session_key['session_key'], $data['encryptedData'], $data['iv'], $result);
if ($errCode !=0) {return json_encode(['state' => $errCode, 'msg' => 'err at getPhoneNumber ']);}
$phoneData = json_decode($result, true);
// return json_encode(['state' => 1, 'msg' => '獲取手機號成功!','phoneNumber'=>$phoneData['phoneNumber'], 'openid' => $openid, 'info' =>$phoneData]);
//存儲
$res=Db::table('user')->where('openid',$openid)->find();
if($res){
$update=['phone'=>$phoneData['phoneNumber']];
Db::table('user')->where('openid',$openid)->update($update);
return json_encode(['state' => 1, 'msg' => '更新成功!', 'openid' => $openid, 'info' =>$phoneData]);
}else{
$insert=[
'openid'=> $openid,
'phone'=> $phoneData['phoneNumber']
];
Db::table('user')->insert($insert);
return json_encode(['state' => 1, 'msg' => '插入成功!', 'openid' => $openid, 'info' =>$phoneData]);
}
}
//登錄憑證校驗
private function getSession( $appid , $secret , $js_code ){
//userInfo用戶登錄
if( !$js_code ){ return json_encode([ 'state' => -2 , 'msg' => 'err lack js_code']) ; }
if( !$appid ){ return json_encode([ 'state' => -2 , 'msg' => 'err lack appid']) ; }
if( !$secret ){ return json_encode([ 'state' => -2 , 'msg' => 'err lack secrrt']) ; }
$url = "https://api.weixin.qq.com/sns/jscode2session?appid=$this->appid&secret=$this->secret&js_code=$js_code&grant_type=authorization_code";
$data = $this->get_curl( $url );
return json_encode($data) ;
}
//手機號碼解密模塊
protected function decryptData( $sessionKey , $encryptedData, $iv, &$data ){
if (strlen($sessionKey) != 24) { return -41001 ; }
$aesKey=base64_decode($sessionKey);
if (strlen($iv) != 24) { return -41002; }
$aesIV=base64_decode($iv);
$aesCipher=base64_decode($encryptedData);
$result= openssl_decrypt( $aesCipher, "AES-128-CBC", $aesKey, 1, $aesIV );
$dataObj=json_decode( $result );
if( $dataObj == NULL ) { return -41003; }
if( $dataObj->watermark->appid != $this->appid )
{
return -41003;
}
$data = $result;
return 0;
}
//get請求
private function get_curl( $url ){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
$output = json_decode($output,true);
return $output;
}
//post請求
private function post_curl( $url , $data ){
$data = json_encode($data);
$headerArray =array("Content-type:application/json;charset='utf-8'","Accept:application/json");
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST,FALSE);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
curl_setopt($curl,CURLOPT_HTTPHEADER,$headerArray);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($curl);
curl_close($curl);
if( is_object($output) ){ return json_decode($output,true); }
return json_decode($output,true);
}
}
Pay.php文件
<?php
namespace app\pay\controller;
use think\Controller;
use think\Db;
use think\Request;
class Pay extends Controller
{
private $mch_id = '****' ; //商家號
private $appid = '****';
public function index(){
$request = Request::instance();
//用戶openid
if( !$request->param('openid') ) {
return json_encode( [ 'state' => -1 , 'msg' => 'no field : orderInfo!' ] );
}
$orderInfo['openid'] = $request->param('openid');
//訂單名
if( !$request->param('order_title') ) {
return json_encode( [ 'state' => -1 , 'msg' => 'no field : order_title!' ] );
} $orderInfo['body'] = $request->param('order_title');
//訂單號
if( !$request->param('order_no') ) {
return json_encode( [ 'state' => -1 , 'msg' => 'no field : order_no!' ] );
} $orderInfo['out_trade_no'] = $request->param('order_no');
//訂單價(元)
if( !$request->param('order_price') ) { return json_encode( [ 'state' => -1 , 'msg' => 'no field : order_price!' ] ); }
if( !is_float((float)$request->param('order_price') ) ){ return json_encode( ['state' => -1 , 'msg' => 'err type of field '] );}
$orderInfo['order_price'] = (float)$request->param('order_price');
$this->pay( $orderInfo );
}
//支付回調
public function callBack(){
$testxml = file_get_contents("php://input");
$jsonxml = json_encode(simplexml_load_string($testxml, 'SimpleXMLElement', LIBXML_NOCDATA));
$result = json_decode($jsonxml, true);
if($result && $result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS'){
$orderInfo['out_trade_no'] = $result['out_trade_no'];
$orderInfo['transaction_id'] = $result['transaction_id'];
$orderInfo['openid'] = $result['openid'];
$orderInfo['total_fee'] = $result['total_fee']*0.01;
$orderInfo['nonce_str'] = $result['nonce_str'];
$orderInfo['time_end'] = $result['time_end'];
//數據插入錯誤,信息臨時儲存
if( ! Db::table('record_odcb')->insert($orderInfo) ){
$str = '' ;
foreach ( $result as $key => $val ){ $str .= $key . '=' . $val . '&' ; }
Db::table('record_oderr')->insert($orderInfo);
return ;
}
//開啓事務
Db::startTrans();
try{
//修改訂單爲已支付狀態
Db::table('record_order')->where( 'id' , $orderInfo['out_trade_no'] )->update(['state'=>2]);
//執行事務
Db::commit();
} catch (\Exception $e) {
Db::rollback();
return ;
}
return '<xml><return_code>'.$result['return_code'].'</return_code><return_msg>OK</return_msg></xml>' ;
}
return ;
}
/***
* @return string
* 名稱:查詢訂單
*
*/
public function checkOrder(){
//請求限制
$request = Request::instance();
if( !$request->param('out_trade_no') ){ return json_encode( ['state' => -1 , 'msg' => 'err type of field '] ) ; }
$out_trade_no = $request->param('out_trade_no');
if( !$request->param('openid') ){ return json_encode( ['state' => -1 , 'msg' => 'err type of field '] ) ; }
$openid = $request->param('openid');
if( !$request->param('nonceStr') ){ return json_encode( ['state' => -1 , 'msg' => 'err type of field '] ) ; }
$nonceStr = $request->param('nonceStr');
//簽名字段
$post['appid'] = $this->appid ;
$post['mch_id'] = $this->mch_id ;
$post['nonce_str'] = $nonceStr ;
$post['out_trade_no'] = $out_trade_no ;
//字段排序
$post = array_filter($post);
//簽名
$sign = $this->sign($post);
//拼接xml字符
$xml = '<xml>
<appid>'.$this->appid.'</appid>
<mch_id>'.$this->mch_id.'</mch_id>
<nonce_str>'.$post['nonce_str'].'</nonce_str>
<out_trade_no>'.$out_trade_no.'</out_trade_no>
<sign>'.$sign.'</sign>
</xml>' ;
//請求接口數據
$result = $this->http_request( 'https://api.mch.weixin.qq.com/pay/orderquery' , $xml );
$data_arr = $this->xml($result);
if( $data_arr['RESULT_CODE'] != 'SUCCESS' ){ return json_encode([ 'state' => 0 , 'msg' => 'err no order ']) ; }
//此處是針對微信支付成功,但沒有進行回調callback,而進行的查詢
return json_encode([ 'state'=>1 , 'msg' => 'success']) ;
}
/***
* @return string
* 名稱:微信支付調用關單接口
* 描述:應用場景在調取微信支付超時(5分鐘)進行調用此接口,進行關閉賬單
*/
public function closeorder(){
//請求限制
// $request = Request::instance();
// if( !$request->param('out_trade_no') ){ return json_encode( ['state' => -1 , 'msg' => 'lack of field '] ) ; }
// $out_trade_no = $request->param('out_trade_no');
// if( !$request->param('nonceStr') ){ return json_encode( ['state' => -1 , 'msg' => 'lack of field '] ) ; }
// $nonceStr = $request->param('nonceStr');
$out_trade_no = '2020020715810491235452259';
$nonceStr = 'I2E7BNYML3SGD27QGNNW08LSKY9ODUKY';
//簽名字段
$post['appid'] = $this->appid ;
$post['mch_id'] = $this->mch_id ;
$post['nonce_str'] = $nonceStr ;
$post['out_trade_no'] = $out_trade_no ;
//字段排序
$post = array_filter($post);
//簽名
$sign = $this->sign($post);
//拼接xml字符
$xml = '<xml>
<appid>'.$this->appid.'</appid>
<mch_id>'.$this->mch_id.'</mch_id>
<nonce_str>'.$post['nonce_str'].'</nonce_str>
<out_trade_no>'.$post['out_trade_no'].'</out_trade_no>
<sign>'.$sign.'</sign>
</xml>' ;
//請求接口數據
$result = $this->http_request( 'https://api.mch.weixin.qq.com/pay/closeorder' , $xml );
$data_arr = $this->xml($result);
// if( ){ return json_encode([ 'state' => 0 , 'msg' => 'err close order ']) ; }
if( $data_arr['RETURN_CODE'] == 'SUCCESS'&&$data_arr['RESULT_CODE'] == 'SUCCESS' ){
//關單操作失敗
return json_encode([ 'state'=>1 , 'msg' => 'success','data'=>$data_arr]) ;
}
//關單操作失敗
return json_encode([ 'state' => 0 , 'msg' => 'err close order','data'=>$data_arr]) ;
}
//微信支付
private function pay( $orderInfo ){
//這裏是按照順序的 因爲下面的簽名是按照順序 排序錯誤 肯定出錯
$post['appid'] = $this->appid; //appid.如果是公衆號 就是公衆號的appid
$post['body'] = $orderInfo['body']; //商品描述
$post['mch_id'] = $this->mch_id; //商戶號
$post['nonce_str'] = $this->nonce_str(); //隨機字符串
$post['notify_url'] = 'http://******/public/pay/Pay/callBack'; //回調的url【自己填寫】
$post['openid'] = $orderInfo['openid'] ; //用戶openid
$post['out_trade_no'] = $orderInfo['out_trade_no'] ; //商戶訂單號;
$post['spbill_create_ip'] = '47.97.***'; //服務器的ip;
$post['total_fee'] = $orderInfo['order_price']*100; // 微信支付單位是分,所以這裏需要*100
$post['trade_type'] = 'JSAPI'; //交易類型 默認
$post = array_filter($post);
$sign = $this->sign($post);//簽名
$post_xml =
'<xml>
<appid>'.$post['appid'].'</appid>
<body>'.$post['body'].'</body>
<mch_id>'.$post['mch_id'].'</mch_id>
<nonce_str>'.$post['nonce_str'].'</nonce_str>
<notify_url>'.$post['notify_url'].'</notify_url>
<openid>'.$post['openid'].'</openid>
<out_trade_no>'.$post['out_trade_no'].'</out_trade_no>
<spbill_create_ip>'.$post['spbill_create_ip'].'</spbill_create_ip>
<total_fee>'.$post['total_fee'].'</total_fee>
<trade_type>'.$post['trade_type'].'</trade_type>
<sign>'.$sign.'</sign>
</xml>';
//print_r($post_xml);die;
//統一接口prepay_id
$url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
$xml = $this->http_request($url,$post_xml);
$array = $this->xml($xml);//全要大寫
// print_r($array);
if($array['RETURN_CODE'] == 'SUCCESS' && $array['RESULT_CODE'] == 'SUCCESS'){
$time = time();
$tmp=[];//臨時數組用於簽名
$tmp['appId'] = $post['appid'];
$tmp['nonceStr'] = $post['nonce_str'];
$tmp['package'] = 'prepay_id='.$array['PREPAY_ID'];
$tmp['signType'] = 'MD5';
$tmp['timeStamp'] = "$time";
$data['state'] = 200;
$data['timeStamp'] = "$time";//時間戳
$data['nonceStr'] = $post['nonce_str'];//隨機字符串
$data['signType'] = 'MD5';//簽名算法,暫支持 MD5
$data['package'] = 'prepay_id='.$array['PREPAY_ID'];//統一下單接口返回的 prepay_id 參數值,提交格式如:prepay_id=*
$data['paySign'] = $this->sign($tmp);//簽名,具體簽名方案參見微信公衆號支付幫助文檔;
$data['out_trade_no'] = $post['out_trade_no'];
}else{
$data['state'] = 0;
$data['text'] = "error";
$data['RETURN_CODE'] = $array['RETURN_CODE'];
$data['RETURN_MSG'] = $array['RETURN_MSG'];
}
echo json_encode($data);
}
/** 以下是方法 **/
//隨機32位字符串
private function nonce_str(){
$result = '';
$str = 'QWERTYUIOPASDFGHJKLZXCVBNM1234567890';
for ($i=0;$i<32;$i++){
$result .= $str[rand(0,35)];
}
return $result;
}
//生成訂單號
private function order_number(){ return date('Ymd',time()).time().rand(10,99); }
//簽名 $data要先排好順序
private function sign($data){
$stringA = '';
foreach ($data as $key=>$value){
if(!$value) continue;
if($stringA){ $stringA .= "&$key=$value" ; }
else{ $stringA = $key.'='.$value ; }
}
$wx_key = 'abcdefghijklmnopqrstuvwxyzABCD12';//申請支付後有給予一個商戶賬號和密碼,登陸後自己設置的key
$stringSignTemp = $stringA.'&key='.$wx_key;
return strtoupper(md5($stringSignTemp));
}
//curl請求
public function http_request($url,$data = null,$headers=array())
{
$curl = curl_init();
if( count($headers) >= 1 ){
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
}
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
if (!empty($data)){
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
}
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($curl);
curl_close($curl);
return $output;
}
//獲取xml
private function xml($xml){
$p = xml_parser_create();
xml_parse_into_struct($p, $xml, $vals, $index);
xml_parser_free($p);
$data = [];
foreach ( $vals as $key=>$value) {
if( $vals[$key]['tag'] == 'xml' || $vals[$key]['tag'] == 'XML') continue;
$tag = $vals[ $key ][ 'tag' ];
$value = $vals[ $key ][ 'value' ];
$data[$tag] = $value;
}
return $data;
}
/**
* @param string $url
* @param string $postData
* @param array $options
* @return bool|mixed
* 微信退款使用curl方法
*/
protected function curlPost($url = '', $postData = '', $options = array()) {
if (is_array($postData)) {
$postData = http_build_query($postData);
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
curl_setopt($ch, CURLOPT_TIMEOUT, 30); //設置cURL允許執行的最長秒數
if (!empty($options)) {
curl_setopt_array($ch, $options);
}
//https請求 不驗證證書和host
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
//第一種方法,cert 與 key 分別屬於兩個.pem文件
//默認格式爲PEM,可以註釋
curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
curl_setopt($ch, CURLOPT_SSLCERT, ROOT_PATH.'vendor'.DS.'wxpay'.DS.'cert'.DS.'apiclient_cert.pem'); //證書絕對路徑
//默認格式爲PEM,可以註釋
curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
curl_setopt($ch, CURLOPT_SSLKEY, ROOT_PATH.'vendor'.DS.'wxpay'.DS.'cert'.DS.'apiclient_key.pem');//證書絕對路徑
//第二種方式,兩個文件合成一個.pem文件
//curl_setopt($ch,CURLOPT_SSLCERT,getcwd().'/all.pem');
$data = curl_exec($ch);
if ($data) {
curl_close($ch);
return $data;
} else {
$error = curl_errno($ch);
echo "curl出錯,錯誤碼: ".$error."<br>";
curl_close($ch);
return false;
}
}
}
小程序代碼
<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber" >獲取手機號碼 </button>
<form bindsubmit="formSubmit" bindreset="formReset"> <view> <view>金額</view> <view><input name="moneyNum" placeholder="請輸入金額" /></view> <view><button form-type="submit">Submit提交</button></view> <view><button form-type="reset">Reset重置</button></view></view></form>
getPhoneNumber: function (e) {
wx.login({
success(res) {
if
(!res.code) { wx.showToast({ title: '登錄失敗!', icon: 'none'
}); return; }
var data
= {
encryptedData: e.detail.encryptedData,
iv:
e.detail.iv,
js_code: res.code
}
var url
= 'http://*****/public/pay/Index/getInfo';
wx.request({
url:
url,
data:
data,
method:'POST',
header: {
"content-type": "application/x-www-form-urlencoded"
},
success(res){
conso***le.log(res);
//
var data = JSON.parse(res.data);
console.log(res.data.openid);
app.globalData.openid = res.data.openid
},
fail(res){
console.log(res);
}
})
}
})
},
canIUse: wx.canIUse('button.open-type.getUserInfo'),
formSubmit: function (e) {
var that=this;
console.log(e.detail.value.moneyNum)
var url = 'http://**/public/pay/index/buyOrder';
var data={
openid: app.globalData.openid,
order_price: e.detail.value.moneyNum
};
console.log(data);
wx.request({
url: url,
data: data,
method: 'POST',
header: {
"content-type": "application/x-www-form-urlencoded"
},
success(res) {
console.log(res);
console.log(res.data);
if (res.data.state == 1) { that.pay(res.data); return; }
wx.showToast({ title: res.data.msg, icon: 'none' })
},
fail(res) {
console.log(res);
}
})
},
下篇,講述微信支付退款的內容。。