基于thinkphp5架構
微信小程式或微信支付相關操作
目錄
支付
退款
訂單查詢
退款查詢
支付成功,進行回調
退款成功 進行回調
用到的方法
支付
/**
* 預支付請求接口(POST)
* @param string $openid openid
* @param string $body 商品簡單描述
* @param string $order_sn 訂單編号
* @param string $total_fee 金額
* @return json的資料
*/
public function prepay()
{
tp_log('預支付請求資料===>' . json_encode($_POST), 'prepay', request()->controller());
$goods_user = db('tf_goods_user')->where(array('order_no' => $_POST['order_no']))->find();
$goods = db('tf_goods')->where(array('id' => $goods_user['goods_id']))->find();
//判斷産品的數量
if (($goods['sales_initial'] - $goods['sales_actual']) <= 0) {
$return['status'] = 0;
$return['info'] = '此産品已售完';
exit(json_encode($return));
}
$order_no = $_POST['order_no']; //訂單号
$is_sale = $_POST['is_sale'];
$openid = $_POST['openid'];
$goods_name = $_POST['goods_name'];
$pay_price = $_POST['price'];
$attach['is_sale'] = $_POST['is_sale'];
$attach['sale_id'] = $_POST['sale_id'];
$nonce_str = $this->nonce_str();//随機字元串
$order_no_ssh = $this->get_orderssh(); //商戶訂單号
//注意 訂單号為自己建立的訂單 和支付沒有任何關系 商戶訂單号是支付單号 用來支付的 每當訂單号發起支付 就會有新的商戶訂單号 商戶訂單号是不重複的 發起一次支付就要生成新的商戶訂單号 要儲存下來 訂單表要有對應的支付訂單表 而訂單号與商戶訂單号是一對多的關系 訂單表最終儲存的商戶訂單号是支付成功的那個 也可以 沒調起支付就根據訂單号去修改訂單表的商戶訂單号 隻有商戶訂單号才能發起退款 相當于商戶訂單号統計記錄到訂單表裡面 退款查詢等操作 商戶訂單号 其實訂單生成就不會改資料了 也可以用訂單号改為商戶訂單号 這樣支付 取消 在支付 也可以的
(out_trade_no)
//組裝支付資料
$data = [
'appid' => config('pay.APPID'),
'mch_id' => config('pay.MCHID'),
'nonce_str' => $nonce_str,
'body' => $goods_name, //商品名稱組合
'attach' => json_encode($attach),
'out_trade_no' => $order_no_ssh,//$order_no, //訂單号 商戶訂單号
'total_fee' => intval($pay_price * 100),
'spbill_create_ip' => $_SERVER['REMOTE_ADDR'],
'notify_url' => config('pay.NOTIFY_URL'),
'trade_type' => 'JSAPI',
'openid' => $openid
];
//訂單支付表建立訂單支付資料
$p_o_data['createtime'] = time();
$p_o_data['order_no'] = $order_no;
$p_o_data['order_no_ssh'] = $order_no_ssh;
$p_o_data['ready'] = json_encode($data);
$p_o_return = db('tf_pay_order')->insert($p_o_data);
if(!$p_o_return){
//失敗
$return['status'] = -1;
$return['info'] = $p_o_data;
exit(json_encode($return));
}
// 擷取簽名
$sign = $this->makeSign($data);
$data['sign'] = $sign;
$xml = $this->toXml($data);
$url = 'https://api.mch.weixin.qq.com/pay/unifiedorder'; //發起支付接口連結
//發起預支付請求
$prepay_return_reslut_xml = $this->http_request($url, $xml);
$xml_to_arr = $this->fromXml($prepay_return_reslut_xml);
$return_result = json_encode($xml_to_arr, true);
tp_log('預支付請求傳回資料array===>' .$return_result , 'prepay', request()->controller());
//記錄預支付傳回資訊
db('tf_goods_order')->where(array('order_no' => $order_no))
->update(array(
'go_pay' => $return_result,
'updatetime' => time(),
'updateuser' => $openid
));
if($xml_to_arr['return_code'] == 'SUCCESS' && $xml_to_arr['result_code'] == 'SUCCESS'){
//成功
$time = time();
//臨時數組用于簽名
$tmp = [
'appId' => config('pay.APPID'),
'nonceStr' => $nonce_str,
'package' => 'prepay_id='.$xml_to_arr['prepay_id'],
'signType' => 'MD5',
'timeStamp' => "$time",
];
$data['timeStamp'] = "$time";//時間戳
$data['nonceStr'] = $nonce_str;//随機字元串
$data['signType'] = 'MD5';//簽名算法,暫支援 MD5
$data['package'] = 'prepay_id='.$xml_to_arr['prepay_id'];//統一下單接口傳回的 prepay_id 參數值,送出格式如:prepay_id=*
$data['paySign'] = $this->makeSign($tmp);//簽名,具體簽名方案參見微信公衆号支付幫助文檔;$data['out_trade_no'] = $out_trade_no;
$return['status'] = 1;
$return['info'] = $data;
}else{
//失敗
$return['status'] = -1;
$return['info'] = $xml_to_arr;
}
exit(json_encode($return));
}
//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;
}
退款
/**
* 申請退款API
* @param $transaction_id
* @param double $total_fee 賬單總金額
* @param double $refund_fee 退款金額
* @return bool
* @throws BaseException
*/
public function refund()
{
$transaction_id = '4200000712202007274705432240';
$total_fee = '0.01';
$refund_fee = '0.01';
// 目前時間
$time = time();
// 生成随機字元串
$nonceStr = md5($time . $transaction_id . $total_fee . $refund_fee);
// API參數
$params = [
'appid' => config('pay.APPID'),
'mch_id' => config('pay.MCHID'),
'nonce_str' => $nonceStr,
'transaction_id' => $transaction_id,
'out_refund_no' => $time,
'total_fee' => $total_fee * 100,
'refund_fee' => $refund_fee * 100,
'notify_url' => config('pay.NOTIFY_URL_REFUND'),//退款回調位址
];
// 生成簽名
$params['sign'] = $this->makeSign($params);
tp_log('退款請求資料===>' . json_encode($params), 'refund', request()->controller());
// 請求API
$url = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
$result = $this->post($url, $this->toXml($params), true, $this->getCertPem());
$prepay = $this->fromXml($result);
// 請求失敗
if (empty($result)) {
throw new BaseException(['msg' => '微信退款api請求失敗']);
}
// 格式化傳回結果
$prepay = $this->fromXml($result);
tp_log('退款傳回資料===>' . json_encode($prepay), 'refund', request()->controller());
// 請求失敗
// if ($prepay['return_code'] === 'FAIL') {
// throw new BaseException(['msg' => 'return_msg: ' . $prepay['return_msg']]);
// }
// if ($prepay['result_code'] === 'FAIL') {
// throw new BaseException(['msg' => 'err_code_des: ' . $prepay['err_code_des']]);
// }
return true;
}
/**
* 模拟POST請求
* @param $url
* @param array $data
* @param bool $useCert
* @param array $sslCert
* @return mixed
*/
public function post($url, $data = [], $useCert = false, $sslCert = [])
{
$header = [
'Content-type: application/json;'
];
$curl = curl_init();
//如果有配置代理這裡就設定代理
// if(WxPayConfig::CURL_PROXY_HOST != "0.0.0.0"
// && WxPayConfig::CURL_PROXY_PORT != 0){
// curl_setopt($ch,CURLOPT_PROXY, WxPayConfig::CURL_PROXY_HOST);
// curl_setopt($ch,CURLOPT_PROXYPORT, WxPayConfig::CURL_PROXY_PORT);
// }
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_POST, TRUE);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
if ($useCert == true) {
// 設定證書:cert 與 key 分别屬于兩個.pem檔案
curl_setopt($curl, CURLOPT_SSLCERTTYPE, 'PEM');
curl_setopt($curl, CURLOPT_SSLCERT, $sslCert['certPem']);
curl_setopt($curl, CURLOPT_SSLKEYTYPE, 'PEM');
curl_setopt($curl, CURLOPT_SSLKEY, $sslCert['keyPem']);
}
$result = curl_exec($curl);
curl_close($curl);
return $result;
}
訂單查詢
/**
* 訂單查詢
* @param $out_trade_no
* @return mixed
* @throws BaseException
*/
public function orderquery()
{
$transaction_id = '4200000712202007274705432240';//微信訂單号
// 目前時間
$time = time();
// 生成随機字元串
$nonce_str = md5($time . mt_rand(00000,99999));
//API參數
$params = [
'appid' => config('pay.APPID'), //公衆号ID
'mch_id' => config('pay.MCHID'), //商戶号
'transaction_id' => $transaction_id, //商戶訂單号
'nonce_str' => $nonce_str, // 随機字元串
];
//生成簽名
$params['sign'] = $this->makeSign($params);
//請求API
$url = 'https://api.mch.weixin.qq.com/pay/orderquery';
$result = $this->post($url, $this->toXml($params));
$prepay = $this->fromXml($result);
// 請求失敗
if ($prepay['return_code'] === 'FAIL') {
throw new BaseException(['msg' => $prepay['return_msg'], 'code' => 0]);
}
if ($prepay['result_code'] === 'FAIL') {
throw new BaseException(['msg' => $prepay['err_code_des'], 'code' => 0]);
}
return $prepay;
}
退款查詢
/**
* 退款查詢
* @param $out_trade_no
* @return mixed
* @throws BaseException
*/
public function refundquery()
{
$transaction_id = '4200000712202007274705432240';//微信訂單号
// 目前時間
$time = time();
// 生成随機字元串
$nonce_str = md5($time . mt_rand(00000,99999));
//API參數
$params = [
'appid' => config('pay.APPID'), //公衆号ID
'mch_id' => config('pay.MCHID'), //商戶号
'transaction_id' => $transaction_id, //商戶訂單号
'nonce_str' => $nonce_str, // 随機字元串
];
//生成簽名
$params['sign'] = $this->makeSign($params);
//請求API
$url = 'https://api.mch.weixin.qq.com/pay/refundquery';
$result = $this->post($url, $this->toXml($params));
$prepay = $this->fromXml($result);
dump($prepay);die;
// 請求失敗
if ($prepay['return_code'] === 'FAIL') {
throw new BaseException(['msg' => $prepay['return_msg'], 'code' => 0]);
}
if ($prepay['result_code'] === 'FAIL') {
throw new BaseException(['msg' => $prepay['err_code_des'], 'code' => 0]);
}
return $prepay;
}
支付成功,進行回調
public function index()
{
$data = file_get_contents('php://input');
$data = $this->FromXml($data);
tp_log('支付回調資料===>' . json_encode($data), 'index', request()->controller());
// 儲存微信伺服器傳回的簽名sign
$data_sign = $data['sign'];
// sign不參與簽名算法
unset($data['sign']);
$sign = $this->makeSign($data);//回調驗證簽名
$str_success = '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
$str_error = '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[簽名失敗]]></return_msg></xml>';
if (($sign === $data_sign) && ($data['return_code'] == 'SUCCESS') && ($data['result_code'] == 'SUCCESS')) {
// 支付成功 進行你的邏輯處理
}
echo $str_success;//str_error 告知微信 你已的邏輯處理完畢 不用再推送或再次推送你結果
}
退款成功 進行回調
/*
* 小程式 退款結果通知
*/
public function refund(){
$data = file_get_contents('php://input');
$data = $this->FromXml($data);
tp_log('退款回調資料===>' . json_encode($data), 'refund', request()->controller());
//對加密的字元串解密
$req_info_xml = openssl_decrypt(base64_decode($data['req_info']), 'aes-256-ecb', md5(config('pay.KEY')),OPENSSL_RAW_DATA);
$req_info = $this->FromXml($req_info_xml);
// // 儲存微信伺服器傳回的簽名sign
// $data_sign = $data['sign'];
// // sign不參與簽名算法
// unset($data['sign']);
// $sign = $this->makeSign($data);//回調驗證簽名
//
// $str_success = '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
// $str_error = '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[簽名失敗]]></return_msg></xml>';
//
//
//
// if (($sign === $data_sign) && ($data['return_code'] == 'SUCCESS') && ($data['result_code'] == 'SUCCESS')) {
// tp_log('退款成功===>', 'refund', request()->controller());
// //去修改訂單的狀态 和支付回調的一樣 修改成功 告知微信 不在推送
// }
}
用到的方法
/**
* 生成簽名
* @param $values
* @return string 本函數不覆寫sign成員變量,如要設定簽名需要調用SetSign方法指派
*/
private function makeSign($values)
{
//簽名步驟一:按字典序排序參數
ksort($values);
$string = $this->toUrlParams($values);
//簽名步驟二:在string後加入KEY
$string = $string . '&key=' . config('pay.KEY');
//簽名步驟三:MD5加密
$string = md5($string);
//簽名步驟四:所有字元轉為大寫
$result = strtoupper($string);
return $result;
}
private function ToUrlParams($array)
{
$buff = "";
foreach ($array as $k => $v) {
if ($k != "sign" && $v != "" && !is_array($v)) {
$buff .= $k . "=" . $v . "&";
}
}
$buff = trim($buff, "&");
return $buff;
}
/**
* 輸出xml字元
* @param $values
* @return bool|string
*/
private function toXml($values)
{
if (!is_array($values)
|| count($values) <= 0
) {
return false;
}
$xml = "<xml>";
foreach ($values as $key => $val) {
if (is_numeric($val)) {
$xml .= "<" . $key . ">" . $val . "</" . $key . ">";
} else {
$xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
}
}
$xml .= "</xml>";
return $xml;
}
/**
* 将xml轉為array
* @param $xml
* @return mixed
*/
private function fromXml($xml)
{
// 禁止引用外部xml實體
libxml_disable_entity_loader(true);
return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
}
/**
* 擷取cert證書檔案
* @return array
* @throws BaseException
*/
private function getCertPem()
{
// if (empty($this->config['cert_pem']) || empty($this->config['key_pem'])) {
// throw new BaseException(['msg' => '請先到背景小程式設定填寫微信支付證書檔案']);
// }
// cert目錄
$filePath = EXTEND_PATH.'wxpay/cert/';
return [
'certPem' => $filePath . 'apiclient_cert.pem',
'keyPem' => $filePath . 'apiclient_key.pem'
];
}
/**
* 生成商戶訂單号
*/
public function get_orderssh()
{
return date("YmdHis") . mt_rand(10000000, 99999999);
}
證書路徑:

config配置