微信小程序支付
<?php class Index{ // 首先获取小程序如下配置 private $wxpayconf = [ 'appid' => 'wx0ad699db1a9ff6d3', // 小程序appid 'mch_id' => 1513841901, // 小程序商户号 'key' => 'qwer122asdauiyfue1nm65qa12dds1r1', // key 获取设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置,支付API秘钥 //'appsecret' => 'd624aca96d0349eec924cde1baec36cb', // 小程序 APPSecret,这里暂时用不到 'order_url' => 'https://api.mch.weixin.qq.com/pay/unifiedorder', //统一下单接口地址 'orderquery_url' => 'https://api.mch.weixin.qq.com/pay/orderquery',//查询订单接口地址 'closeorder_url' => 'https://api.mch.weixin.qq.com/pay/closeorder',//关闭订单接口地址 'refund_url' => 'https://api.mch.weixin.qq.com/secapi/pay/refund',//申请退款接口地址 'refundquery_url' => 'https://api.mch.weixin.qq.com/pay/refundquery'//查询退款接口地址 ]; /* * 1.预支付 通过微信 https://api.mch.weixin.qq.com/pay/unifiedorder 接口进行预支付,详见 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1&index=1 * 2.通过预支付返回的prepay_id 进行重新组建数据,组建成功后返回给前端,前端进行弹框支付 ,重新组建数据详见 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=3 * 3.notify_url 地址就是回调地址,在前端支付成功后,微信会往这个地址发送支付成功订单的相关信息,小程序这个地址不允许有参数,在这个地址中处理微信发送的支付成功订单信息 */ // 统一下单 function index() { //这里是封装支付接口给服务商,所以一些商品信息需要获取 $openid = $_POST ['openid']; $notify_url = $_POST ['notify_url']; // 支付成功后回调地址,类似 http://www.gaoqingsong.com/index.php 这样后面不能有参数,这里因为封装接口给别人用,所以对方需要提供一个回调接口,将微信回调的xml信息返回给对方 $body = $_POST ['body']; //商品描述 $out_trade_no = $_POST ['out_trade_no']; // 订单号 $total_fee = $_POST ['total_fee']; // 订单总金额,单位为分 $userIP = $_POST ['userIP']; // 远程终端ip $userIP = !empty($userIP) ? $userIP : $_SERVER ['REMOTE_ADDR']; //获取随机字符串 $nonce_str = $this->getNonceStr (); $sign ['appid'] = $this->wxpayconf ['appid']; $sign ['mch_id'] = $this->wxpayconf ['mch_id']; $sign ['nonce_str'] = $nonce_str; $sign ['body'] = !empty($body) ? $body : '集妆箱商品'; $sign ['out_trade_no'] = $out_trade_no; $sign ['total_fee'] = $total_fee; $sign ['spbill_create_ip'] = $userIP; // 终端客户端ip $sign ['trade_type'] = 'JSAPI'; // 小程序支付值固定:JSAPI $sign ['openid'] = $openid; $sign ['notify_url'] = 'https://xcxoauth.beauty-box.cn/index/index/payres';//$notify_url;//先将客户的$notify_url保存在订单表数据中(代码中未写),微信回调先调用内部的payres方法,payres记录相关信息后,再找到对应订单数据中的$notify_url将微信的xml信息回调给客户; $sign ['sign'] = $this->getSign ( $sign, $this->wxpayconf ['key'] ); // 微信统一下单接口 // 先将数组换成XML格式 $xmlData = $this->arrayToXml ( $sign ); // 提交订单数据,预支付 $xml_result = $this->postXmlCurl ( $xmlData, $this->wxpayconf ['order_url'] ); // 将返回信息从XML还原回数组 $wx_result = $this->xmlToArray ( $xml_result ); //如果预支付提交订单没问题,则组织相关信息返回给前端,进行支付方,支付成功后,微信会往回调页 $notify_url 发送订单信息 if ($wx_result ['return_code'] == 'SUCCESS' && $wx_result ['result_code'] == 'SUCCESS') { $prepay_id = $wx_result ['prepay_id']; $return ['appId'] = $this->wxpayconf ['appid']; $return ['timeStamp'] = ( string ) time (); $return ['nonceStr'] = $nonce_str; $return ['package'] = 'prepay_id=' . $prepay_id; $return ['signType'] = 'MD5'; $return ['paySign'] = $this->getSign ( $return, $this->wxpayconf ['key'] ); echo json_encode ( $return ); } else { if ($wx_result ['return_code'] == 'FAIL') { $info ['return_msg'] = $wx_result ['return_msg']; } if ($wx_result ['return_code'] == 'SUCCESS' && $wx_result ['result_code'] == 'FAIL') { $info ['err_code'] = $wx_result ['err_code']; $info ['err_code_des'] = $wx_result ['err_code_des']; } echo json_encode ( $info ); } } //支付结果回调方法 public function payres(){ //记录日志 $log_filename = "log/payres.txt"; $log_content = ' ================ 访问接口 payres ================= 时间 : ' . date("Y-m-d H:i:s"); file_put_contents ( $log_filename, $log_content, FILE_APPEND ); //获取微信回调post传回的xml数据 $xml_result = file_get_contents('php://input'); $log_content = "\n\r\n\r=====接收到的xml数据 : " . $xml_result . " ======================================================\n\r"; file_put_contents ( $log_filename, $log_content, FILE_APPEND ); // 将返回信息从XML还原回数组 $wx_result = $this->xmlToArray ( $xml_result ); //订单表数据对象,这里假设本地有订单表,字段名和微信订单参数名一致,保存下单时的订单数据 //这里模型框架用 thinkPHP5 $this->order_model = new OrderModel (); if ($wx_result ['return_code'] == 'SUCCESS') { //先获取下订单数据 $where = array(); $where['out_trade_no'] = $wx_result['out_trade_no']; $where['nonce_str'] = $wx_result['nonce_str']; //$where['sign'] = $wx_result['sign']; $where['total_fee'] = $wx_result['total_fee']; $order = $this->order_model->where($where)->find(); //订单信息 $log_content = "\n\r\n\r=====查询本地的对应订单信息 : " . json_encode($order) . " ======================================================\n\r"; file_put_contents ( $log_filename, $log_content, FILE_APPEND ); //如果订单存在并且为未支付状态,订单表字段 pay_status 支付状态,1成功,默认0待支付 if(!empty($order['id']) && $order['pay_status'] != 1){ //支付成功后保存数据,并往供应商提供的回调页面传微信回调信息 $data = array(); $data['id'] = $order['id']; $data['time_end'] = !empty($wx_result['time_end'])?$wx_result['time_end']:null; $data['openid'] = !empty($wx_result['openid'])?$wx_result['openid']:null; $data['bank_type'] = !empty($wx_result['bank_type'])?$wx_result['bank_type']:null; $data['cash_fee'] = !empty($wx_result['cash_fee'])?$wx_result['cash_fee']:null; $data['transaction_id'] = !empty($wx_result['transaction_id'])?$wx_result['transaction_id']:null; $data['return_code'] = !empty($wx_result['return_code'])?$wx_result['return_code']:null; $data['result_code'] = !empty($wx_result['result_code'])?$wx_result['result_code']:null; $data['notify_str'] = $xml_result; $data['notify_time'] = date("Y-m-d H:i:s"); if($wx_result ['result_code'] == 'SUCCESS'){ $data['pay_status'] = 1; } //支付成功后保存的数据 $log_content = "\n\r\n\r=====支付成功后保存的数据 : " . json_encode($data) . " ======================================================\n\r"; file_put_contents ( $log_filename, $log_content, FILE_APPEND ); //保存支付数据 if($this->order_model->save($data, $data['id']) !== false){ /* //如果是封装支付接口,则还需把微信返回的xml数据传给接口使用者 //给第三方返回信息 $this->postXmlCurl ( $xml_result, $order ['notify_url'] ); //保存成功后回传客户端的url $log_content = "\n\r\n\r=====保存成功后回传客户端的url : " . $order ['notify_url'] . " ======================================================\n\r"; $log_content .= "\n\r\n\r=====保存成功后回传客户端的xml : " . $xml_result . " ======================================================\n\r"; file_put_contents ( $log_filename, $log_content, FILE_APPEND ); */ //给微信返回信息 $sign ['return_code'] = 'SUCCESS'; $sign ['return_msg'] = 'OK'; // 先将数组换成XML格式 $xmlData = $this->arrayToXml ( $sign ); $log_content = "\n\r\n\r=====返回给微信端 xml : " . $xmlData . " ======================================================\n\r"; $log_content .= "\n\r\n\r===== payres 接口结束======================================================\n\r"; file_put_contents ( $log_filename, $log_content, FILE_APPEND ); echo $xmlData;exit; } } } $log_content = "\n\r\n\r===== payres 接口结束======================================================\n\r"; file_put_contents ( $log_filename, $log_content, FILE_APPEND ); } //查询订单,详见 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_2 public function orderquery(){ $out_trade_no = $_POST ['out_trade_no']; // 商户订单号 //获取随机字符串 $nonce_str = $this->getNonceStr (); $sign ['appid'] = $this->wxpayconf ['appid']; $sign ['mch_id'] = $this->wxpayconf ['mch_id']; $sign ['out_trade_no'] = $out_trade_no; $sign ['nonce_str'] = $nonce_str; $sign ['sign'] = $this->getSign ( $sign, $this->wxpayconf ['key'] ); // 先将数组换成XML格式 $xmlData = $this->arrayToXml ( $sign ); // 提交查询订单数据 $xml_result = $this->postXmlCurl ( $xmlData, $this->wxpayconf ['orderquery_url'] ); // 将返回信息从XML还原回数组 $wx_result = $this->xmlToArray ( $xml_result ); //从查询订单起,返回结果不需要再签名,直接将结果返回,判断逻辑由调用端操作 //返回结果详见 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_2 返回结果 echo json_encode ($wx_result); exit; } //关闭订单,详见 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_3 public function closeorder(){ $out_trade_no = $_POST ['out_trade_no']; // 商户订单号 //获取随机字符串 $nonce_str = $this->getNonceStr (); $sign ['appid'] = $this->wxpayconf ['appid']; $sign ['mch_id'] = $this->wxpayconf ['mch_id']; $sign ['out_trade_no'] = $out_trade_no; $sign ['nonce_str'] = $nonce_str; $sign ['sign'] = $this->getSign ( $sign, $this->wxpayconf ['key'] ); // 先将数组换成XML格式 $xmlData = $this->arrayToXml ( $sign ); // 提交查询订单数据 $xml_result = $this->postXmlCurl ( $xmlData, $this->wxpayconf ['closeorder_url'] ); // 将返回信息从XML还原回数组 $wx_result = $this->xmlToArray ( $xml_result ); //返回结果详见 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_3 返回结果 echo json_encode ($wx_result); exit; } //申请退款,详见 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_4 //退款需用到证书,在 postXmlCurl 方法中用到的证书在,微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->证书下载 。证书文件有四个,这里用三个,详见 postXmlCurl 方法中证书调用 function refund(){ $out_trade_no = $_POST ['out_trade_no']; // 订单号 $out_refund_no = $_POST ['out_refund_no']; // 商户退款单号 $total_fee = $_POST ['total_fee']; // 订单总金额,单位为分 $refund_fee = $_POST ['refund_fee']; // 退款金额,单位为分 $notify_url = $_POST ['notify_url'];//退款结果通知url //获取随机字符串 $nonce_str = $this->getNonceStr (); $sign ['appid'] = $this->wxpayconf ['appid']; $sign ['mch_id'] = $this->wxpayconf ['mch_id']; $sign ['nonce_str'] = $nonce_str; $sign ['out_trade_no'] = $out_trade_no; $sign ['out_refund_no'] = $out_refund_no; $sign ['total_fee'] = $total_fee; $sign ['refund_fee'] = $refund_fee; $sign ['notify_url'] = $notify_url; $sign ['sign'] = $this->getSign ( $sign, $this->wxpayconf ['key'] ); // 先将数组换成XML格式 $xmlData = $this->arrayToXml ( $sign ); // 提交退款申请 $xml_result = $this->postXmlCurl ( $xmlData, $this->wxpayconf ['refund_url'], true ); // 将返回信息从XML还原回数组 $wx_result = $this->xmlToArray ( $xml_result ); //返回结果详见 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_4 返回结果 echo json_encode ($wx_result); exit; } //退款查询,详见 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_5 function refundquery(){ $out_refund_no = $_POST ['out_refund_no']; // 商户退款单号 //获取随机字符串 $nonce_str = $this->getNonceStr (); $sign ['appid'] = $this->wxpayconf ['appid']; $sign ['mch_id'] = $this->wxpayconf ['mch_id']; $sign ['nonce_str'] = $nonce_str; $sign ['out_refund_no'] = $out_refund_no; $sign ['sign'] = $this->getSign ( $sign, $this->wxpayconf ['key'] ); // 先将数组换成XML格式 $xmlData = $this->arrayToXml ( $sign ); // 提交退款申请 $xml_result = $this->postXmlCurl ( $xmlData, $this->wxpayconf ['refundquery_url']); // 将返回信息从XML还原回数组 $wx_result = $this->xmlToArray ( $xml_result ); //返回结果详见 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_5 返回结果 echo json_encode ($wx_result); exit; } // 签名方法 function getSign($params, $key1) { // 签名步骤一:按字典序排序数组参数 ksort ( $params ); $singstring = ''; foreach ( $params as $key => $value ) { $singstring .= '&' . $key . '=' . $value; } $string = $singstring . "&key=" . $key1; // 签名步骤三:MD5加密 $string = ltrim ( $string, '&' ); $string = md5 ( $string ); // 签名步骤四:所有字符转为大写 $result = strtoupper ( $string ); return $result; } // 数组转xml function arrayToXml($arr, $is_array = false) { if (! $is_array) { $xml = '<xml>'; } foreach ( $arr as $key => $val ) { if (is_array ( $val )) { $xml .= "<" . $key . ">" . $this->arrayToXml ( $val, true ) . "</" . $key . ">"; } else { $xml .= "<" . $key . ">" . $val . "</" . $key . ">"; } } if (! $is_array) { $xml .= "</xml>"; } return $xml; } // 数组转xml,备用,第一个不好用,用这个 protected function arrayToXml_bak($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; } // XML 转数组 protected function xmlToArray($xml) { $array_data = json_decode ( json_encode ( simplexml_load_string ( $xml, 'SimpleXMLElement', LIBXML_NOCDATA ) ), true ); return $array_data; } // 发送xml请求方法,$verifyhost是否验证主机证书,退款时需要验证 private static function postXmlCurl($xml, $url, $verifyhost = false, $second = 30) { $ch = curl_init (); curl_setopt ( $ch, CURLOPT_URL, $url ); curl_setopt ( $ch, CURLOPT_HEADER, FALSE ); curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, TRUE ); // 是否需要证书验证,支付时不需要,退款时需要验证证书 if($verifyhost == true){ curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, TRUE);//证书检查 curl_setopt( $ch, CURLOPT_SSLCERTTYPE, 'pem'); curl_setopt( $ch, CURLOPT_SSLCERT, '/var/www/html/xcxoauth.beauty-box.cn/public/cert/apiclient_cert.pem'); curl_setopt( $ch, CURLOPT_SSLCERTTYPE, 'pem'); curl_setopt( $ch, CURLOPT_SSLKEY, '/var/www/html/xcxoauth.beauty-box.cn/public/cert/apiclient_key.pem'); //curl_setopt( $ch, CURLOPT_SSLCERTTYPE, 'pem'); curl_setopt( $ch, CURLOPT_CAINFO, '/var/www/html/xcxoauth.beauty-box.cn/public/cert/rootca.pem'); }else{ curl_setopt ( $ch, CURLOPT_SSL_VERIFYPEER, FALSE ); curl_setopt ( $ch, CURLOPT_SSL_VERIFYHOST, FALSE ); } // post提交方式 curl_setopt ( $ch, CURLOPT_POST, TRUE ); curl_setopt ( $ch, CURLOPT_POSTFIELDS, $xml ); curl_setopt ( $ch, CURLOPT_CONNECTTIMEOUT, 20 ); // 设置超时 curl_setopt ( $ch, CURLOPT_TIMEOUT, $second ); 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" ); } } /* * 生成随机字符串方法 */ protected function getNonceStr($length = 32) { $chars = "abcdefghijklmnopqrstuvwxyz0123456789"; $str = ""; for($i = 0; $i < $length; $i ++) { $str .= substr ( $chars, mt_rand ( 0, strlen ( $chars ) - 1 ), 1 ); } return $str; } }