微信掃碼支付全解析

Tsy遠發表於2017-09-22

簡單介紹了微信掃碼支付的申請、接入、使用、確認支付結果等相關流程

0 系列文章

系列一 微信App支付全解析
系列二 支付寶App支付全解析
系列三 微信公眾號支付全解析
系列四 微信掃碼支付全解析
系列五 支付寶即時到賬支付全解析
系列六 微信退款全解析
系列七 支付寶退款全解析
系列八 支付寶開放平臺支付更新升級全解析

1 申請

申請步驟直接參考官方文件

主要2個大塊:

  1. 申請開通公眾平臺,建立應用
  2. 申請支付開通商戶平臺

全部申請通過後,獲取支付必須的引數如下:

1.1 AppID和AppSecret

公眾平臺建立的應用唯一標識。
登入微信公眾平臺,進入應用詳情可檢視AppID和AppSecret。

Paste_Image.png
Paste_Image.png

1.2 mch_id

微信支付申請完成之後,微信商戶平臺會給你的郵箱發通知郵件,裡面包含開通支付的商戶資訊

Paste_Image.png
Paste_Image.png

1.3 API祕鑰

即商戶支付祕鑰,主要負責處理通訊相關引數加密。登陸微信商戶平臺(賬號密碼在微信商戶平臺發來的郵件裡)
點選左側的「賬戶設定 - API 安全」(第一次登陸會讓你安裝操作證照,請先安裝操作證照)。點選設定金鑰,設定自己的金鑰。

Paste_Image.png
Paste_Image.png

1.4 商戶證照

用於退款等一些需要證照驗證的介面使用。在微信商戶平臺點選「賬戶中心 - API 安全」,點選「下載證照」

Paste_Image.png
Paste_Image.png

證照下載後,開啟壓縮包會看到「apiclient_cert.pem」和「apiclient_key.pem」和rootca.pem證照。

2 接入流程

參考接入文件

主要幾個步驟:

  1. 統一下單(放在服務端,需要加密引數)
  2. 生成支付引數(放在服務端,需要生成簽名)
  3. 將支付地址生成二維碼即可直接掃支付
  4. 服務端非同步接收支付結果

2.1 統一下單

$appid = "";  //你的appid
$mch_id = "";  //商戶id
$wx_api_key = "";    //商戶api祕鑰
$out_trade_no = "";  //自己業務系統生成的交易no,可以唯一標識
$client_ip = "";  //客戶端ip
$notify_url = "";    //接收支付結果通知url
$openid = "";    //微信授權獲得的openid
$product_id = "";  //掃碼的產品id 業務自定義的唯一物品標識

$UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";  //統一下單地址

$data = array();
$data['appid'] = $appid; 
$data['mch_id'] =$mch_id;
$data['nonce_str'] = randomStr(20);  //隨機20位字串
$data['product_id'] = $product_id;
$data['body'] = "微信掃碼支付測試";
$data['detail'] = "微信掃碼支付測試detail";
$data['out_trade_no'] = $out_trade_no;    
$data['total_fee'] = 1;  //注意 單位是分
$data['spbill_create_ip'] = $client_ip;
$data['openid'] = $openid;
$data['notify_url'] = $notify_url;
$data['trade_type'] = "NATIVE";  //交易型別
$data['sign'] =sign($data, $wx_api_key);    //簽名

//轉為xml格式
$xml_str = arrayToXmlStr($data); 

//傳送請求 使用封裝好的curl_post
$result = curl_post($UNIFIED_ORDER_URL, $xml_str);

//解析得到的值
$get_data = simplexml_load_string($raw_data, 'SimpleXMLElement', LIBXML_NOCDATA);
$get_para = array();
$get_sign = "";
foreach ($get_data->children() as $child) 
{    
  if($child->getName() == 'sign') {        
    $get_sign = strval($child);    
  } else {        
    $get_para[strval($child->getName())] = strval($child);    
   }
}

if($get_para['return_code'] !== "SUCCESS") {
    //return code fail
}

//驗證簽名
if(!verifySign($get_sign, $get_para, $wx_api_key)) {
    //驗證簽名非法
}

//獲取支付地址
$code_url = $get_para['code_url'];複製程式碼

一些函式:

/**
 * array轉成xml str
 * @param $arr
 */
public static function arrayToXmlStr($arr) {    
  $xml_data = new \SimpleXMLElement("<xml></xml>");    
  Func::arrayToXml($arr, $xml_data);    
  return $xml_data->asXML();
}

/**
 * 生成指定長度的隨機字串(包含大寫英文字母, 小寫英文字母, 數字)
 * @param $length int 需要生成的字串的長度
 * @return string 包含 大小寫英文字母 和 數字 的隨機字串
 */
public static function randomStr($length){    
  //生成一個包含 大寫英文字母, 小寫英文字母, 數字 的陣列    
  $arr = array_merge(range(0, 9), range('a', 'z'), range('A', 'Z'));    
  $str = '';    
  $arr_len = count($arr);    
  for ($i = 0; $i < $length; $i++)    {        
    $rand = mt_rand(0, $arr_len-1);        
    $str.=$arr[$rand];    
  }    
  return $str;
}

/**
 * 微信簽名
 * @param $para mixed 帶簽名引數陣列
 * @param $wx_key string wxkey
 */
public static function sign($para, $wx_key) {    
  $unsign_str = Func::createLinkString(Func::argSort($para)) . "&key=" . $wx_key;    
  $sign = strtoupper(md5($unsign_str));    
  return $sign;
}

/**
 * 微信簽名驗證
 * @param $sign
 * @param $para
 * @param $wx_key
 * @return false-驗證失敗 true-驗證成功
 */
public static function verifySign($sign, $para, $wx_key) {    
  $unsign_str = Func::createLinkString(Func::argSort($para)) . "&key=" . $wx_key;    
  $sign_str = strtoupper(md5($unsign_str));    
  if($sign === $sign_str) {        
    return true;    
  }    
  return false;
}複製程式碼

3. 支付

直接將統一下單後獲取的code_url轉為二維碼即可支付,注意:二維碼的有效時間為2小時。

支付完成後由於是掃碼沒有同步結果返回,建議業務方輪詢檢查服務端該交易狀態。

4 非同步結果通知

注:尤其要注意通知結果驗證成功後要能正確處理重複通知,放置多次發貨造成資金損失

$raw_data = $GLOBALS["HTTP_RAW_POST_DATA"];

$get_data = simplexml_load_string($raw_data, 'SimpleXMLElement', LIBXML_NOCDATA);
$get_para = array();
$get_sign = "";
foreach ($get_data->children() as $child) 
{    
  if($child->getName() == 'sign') {        
    $get_sign = strval($child);    
  } else {        
    $get_para[strval($child->getName())] = strval($child);    
   }
}

if($get_para['return_code'] !== "SUCCESS") {
    //return code fail
    die("<xml><return_code><![CDATA[FAIL]]></return_code></xml>");
}

//驗證簽名
if(!verifySign($get_sign, $get_para, $wx_api_key)) {
    //驗證簽名非法
    //todo
    die("<xml><return_code><![CDATA[FAIL]]></return_code></xml>");
}

//在這其實通知已經接受成功 可以返回成功告訴微信不用再次通知了
echo("<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>");

//業務狀態碼判斷
if ($get_para['result_code'] !== 'SUCCESS') {       //狀態碼錯誤
  //支付錯誤 更改訂單狀態 記錄log等 
  //...
}

//支付成功 更改訂單狀態 記錄log等 
//todo複製程式碼

5 其他

  1. 客戶端收到同步支付結果後建議一段時間內輪詢檢查服務端,獲取服務端的結果,支付最終狀態以服務端為準

結尾

更多文章關注我的公眾號

我的公眾號
我的公眾號

相關文章