前言:此篇博客是实现微信小程序支付,语言是ThinkPHP(php)+微信小程序,需要商户号,appid,服务器。
参考:
小程序支付API文档
微信小程序开发文档
PHP代码如下:
Index.php
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文件
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 '' .$result['return_code'].'OK ' ;
}
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 = '
' .$this->appid.'
' .$this->mch_id.'
' .$post['nonce_str'].'
' .$out_trade_no.'
' .$sign.'
' ;
//请求接口数据
$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 = '
' .$this->appid.'
' .$this->mch_id.'
' .$post['nonce_str'].'
' .$post['out_trade_no'].'
' .$sign.'
' ;
//请求接口数据
$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 =
'
' .$post['appid'].'
'.$post['body'].'
' .$post['mch_id'].'
' .$post['nonce_str'].'
' .$post['notify_url'].'
' .$post['openid'].'
' .$post['out_trade_no'].'
' .$post['spbill_create_ip'].'
' .$post['total_fee'].'
' .$post['trade_type'].'
' .$sign.'
';
//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."
";
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);
}
})
},
下篇,讲述微信支付退款的内容。。