微信小程序實現支付接口

最近做小程序涉及到微信支付,連微信支付都沒有做過的我無從下手,在網上搜索到了幾篇帖子也沒看明白,沒辦法只好照着某一篇(來源:微信小程序中實現微信支付小程序支付,詳細過程)硬着頭皮先寫了,最後經過幾次調試終獲成功,所以我對支付做一個總結,分享出來方便他人。

一,準備小程序id,小程序密鑰,商戶號,商戶密鑰,需要openid(微信登錄後獲得,調用https://api.weixin.qq.com/sns/jscode2session)

二,接口開發

接口有3個:預支付,支付,回調,php代碼在後面

(一)預支付:(1)獲取參數:openid,商品信息,金額;(2)設定回調接口地址,自己生成訂單號,可將訂單號與openid和商品信息一併寫入mysql,等支付完成後在修改狀態;(3)把所有字段彙集起來做簽名一起提交給統一下單地址(https://api.mch.weixin.qq.com/pay/unifiedorder),成功的話會返回prepay_id,將prepay_id輸出(給 支付接口)。

統一下單需要的參數:

<xml>
<appid>小程序id</appid>
<body>商品信息,比如商品名稱</body>
<mch_id>商戶號</mch_id>
<nonce_str>隨機字符串,僅用於加密</nonce_str>
<notify_url>回調接口地址</notify_url>
<openid>openid,即用戶id</openid>
<out_trade_no>訂單號,自己生成</out_trade_no>
<spbill_create_ip>服務器ip</spbill_create_ip>
<total_fee>金額,單位分</total_fee>
<trade_type>交易類型,默認JSAPI</trade_type>
<sign>以上所有字段的簽名</sign>
</xml>

統一下單返回數據:

<xml><return_code>狀態碼,SUCCESS|FAIL</return_code>
<return_msg>狀態信息</return_msg>
<appid>小程序id</appid>
<mch_id>商戶號</mch_id>
<nonce_str>微信生成的隨機字符串</nonce_str>
<sign>簽名</sign>
<result_code>同return_code?</result_code>
<prepay_id>預支付id,重要</prepay_id>
<trade_type>交易類型,默認JSAPI</trade_type>
</xml>

(二)支付:支付接口很簡單,獲取prepay_id後計算簽名返回即可,小程序拿到簽名後會發起真正的支付。此時會提醒用戶輸入密碼,如下圖:

(三)回調:當支付成功時,微信會調用此接口。可以實現自己業務邏輯,比如訂單完成後修改狀態。

接口輸入xml數據:

<xml><appid>小程序id</appid>
<bank_type>銀行類型,CFT</bank_type>
<cash_fee>金額</cash_fee>
<fee_type>金額類型,CNY</fee_type>
<is_subscribe><![CDATA[N]]></is_subscribe>
<mch_id>商戶號</mch_id>
<nonce_str>隨機字符串,自己生成發給微信的</nonce_str>
<openid>openid</openid>
<out_trade_no>訂單號</out_trade_no>
<result_code>狀態碼,SUCCESS|FAIL</result_code>
<return_code>狀態碼,SUCCESS|FAIL</return_code>
<sign>簽名</sign>
<time_end>支付完成時間</time_end>
<total_fee>金額</total_fee>
<trade_type><![CDATA[JSAPI]]></trade_type>
<transaction_id>微信交易號</transaction_id>
</xml>

驗證簽名正確後,處理自己業務流程。

三,小程序端調用流程

小程序流程不懂,流程可參考文檔 微信小程序中實現微信支付 在提交prepay_id時需要從字符串中截取出來。

四,php代碼

(1)預支付prepay.php:

/**
 * 預支付請求接口(POST)
 * @param string $openid   openid
 * @param string $body    商品簡單描述
 * @param string $total_fee 金額,單位分
 * @return  json的數據
 */
require_once("config.php"); // config.php記錄了小程序id,密鑰,商戶號及密鑰
require_once("utils.php");  // 一些功能函數

$openid = Request('openid');
$body = Request('body'); // 商品信息,比如title
$total_fee = RequestInt('total_fee', 1); // 金額

flog("prepay openid:$openid total_fee:$total_fee ");

$nonce_str =    nonce_str(); //隨機字符串
$notify_url =   "https://你的域名/notify.php"; // 回調地址
$out_trade_no = trade_no();      //商戶訂單號,隨機生成
$spbill_create_ip = '你的ip';  // 服務器ip
$trade_type = 'JSAPI';//交易類型 默認

// 訂單發起時,可將信息存入mysql
$sql = "insert into table";
...
flog("prepay:$sql");


//這裏是按照順序的 因爲下面的簽名是按照順序 排序錯誤 肯定出錯
$post['appid'] = $appid;
$post['body'] = $body;
$post['mch_id'] = $mch_id;
$post['nonce_str'] = $nonce_str;//隨機字符串
$post['notify_url'] = $notify_url;
$post['openid'] = $openid;
$post['out_trade_no'] = $out_trade_no;
$post['spbill_create_ip'] = $spbill_create_ip;//終端的ip
$post['total_fee'] = $total_fee;
$post['trade_type'] = $trade_type;
$sign = sign($post);//簽名


$post_xml = '<xml>
<appid>'.$appid.'</appid>
<body>'.$body.'</body>
<mch_id>'.$mch_id.'</mch_id>
<nonce_str>'.$nonce_str.'</nonce_str>
<notify_url>'.$notify_url.'</notify_url>
<openid>'.$openid.'</openid>
<out_trade_no>'.$out_trade_no.'</out_trade_no>
<spbill_create_ip>'.$spbill_create_ip.'</spbill_create_ip>
<total_fee>'.$total_fee.'</total_fee>
<trade_type>'.$trade_type.'</trade_type>
<sign>'.$sign.'</sign>
</xml> ';
/*
$arr = xml($post_xml);//全要大寫
print_r($arr);
exit();
*/

//統一接口prepay_id
$url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
$xml = http_request($url,$post_xml);
flog("prepay: get xml: $xml");

$arr = xml2arr($xml);
if ($arr['return_code'] == 'SUCCESS' && $arr['return_code'] == 'SUCCESS') {
  $time = time();
  $tmp=array();//臨時數組用於簽名
  $tmp['appId'] = $appid;
  $tmp['nonceStr'] = $nonce_str;
  $tmp['package'] = 'prepay_id='.$arr['prepay_id'];
  $tmp['signType'] = 'MD5';
  $tmp['timeStamp'] = "$time";

  $data['error'] = 0;
  $data['timeStamp'] = "$time";//時間戳
  $data['nonceStr'] = $nonce_str;//隨機字符串
  $data['signType'] = 'MD5';//簽名算法,暫支持 MD5
  $data['package'] = 'prepay_id='.$arr['prepay_id'];//統一下單接口返回的 prepay_id 參數值,提交格式如:prepay_id=*
  $data['paySign'] = sign($tmp);//簽名,具體簽名方案參見微信公衆號支付幫助文檔;
  $data['out_trade_no'] = $out_trade_no;
} else {
  $data['error'] = 1;
  $data['msg'] = $arr['return_msg'];
  $data['wxcode'] = $arr['return_code'];
}

flog("prepay ok:".json_encode($data));

echo json_encode($data); //小程序需要的數據 返回前端

(2)支付pay.php

/**
 * 進行支付接口(POST)
 * @param string $prepay_id 預支付ID(調用prepay()方法之後的返回數據中獲取)
 * @return  json的數據
 */

require_once("config.php");
require_once("utils.php");

$prepay_id = Request('prepay_id');

$data = array(
      'appId'   => $appid,
      'nonceStr'  => nonce_str(),
      'package' => 'prepay_id='.$prepay_id,
      'signType'  => 'MD5',
      'timeStamp' => time()
);

$data['paySign'] = sign($data);

flog("pay:".json_encode($data));
echo json_encode($data);

(3)回調notify.php

require_once("config.php");
require_once("utils.php");

  $xml = file_get_contents("php://input"); // 獲取輸入
  //$xml = $GLOBALS['HTTP_RAW_POST_DATA'];

  flog("notify: get xml:$xml");

  //將服務器返回的XML數據轉化爲數組
  $data = xml2arr($xml);
  // 保存微信服務器返回的簽名sign
  $data_sign = $data['sign'];
  // sign不參與簽名算法
  unset($data['sign']);
  $sign = sign($data);

  flog("notify sign:$sign");
  // 判斷簽名是否正確  判斷支付狀態
  if (($sign===$data_sign) && ($data['return_code']=='SUCCESS') && ($data['return_code']=='SUCCESS') ) {
    $result = $data;
    //獲取服務器返回的數據
    $order_sn = $data['out_trade_no'];      //訂單單號
    $openid = $data['openid'];          //付款人openID
    $total_fee = $data['total_fee'];      //付款金額
    $transaction_id = $data['transaction_id'];  //微信支付流水號

    //可根據訂單號更新數據庫
    $sql = "update table set where";
    ...

    // TODO: 支付完成後,業務流程需處理

  } else {
    $result = false;
  }
  // 返回狀態給微信服務器
  if ($result) {
    $str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
  } else {
    $str='<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[簽名失敗]]></return_msg></xml>';
  }
  flog("notify result:$str");
  echo $str; // 返回給微信
  //return $result; // 這一步沒必要

(4)utils.php

  // Author: [email protected] (Kunlong She)
  // Created Time:2018-08-20 11:29:24
function RequestInt($str, $default = 0) {
  if (isset($_REQUEST[$str])) {
    return intval($_REQUEST[$str]);
  } else {
    return $default;
  }
}

function Request($str) {
  if (isset($_REQUEST[$str])) {
    $rst = str_replace("'", "''", $_REQUEST[$str]);
    $rst = str_replace("\\", "\\\\", $rst);
    return $rst;
  } else {
    return "";
  }
}

function Str($str) {
  if (empty($str)) {
    return "";
  } else {
    return $str;
  }
}

function flog($str) {
  $fp = fopen("/tmp/a.log", "a+");
  $date = date("Y-m-d H:i:s");
  fprintf($fp, "$date $str\n");
  fclose($fp);
}

// 隨機字符串,僅用於簽名
function nonce_str(){
  $result = '';
  $str = 'QWERTYUIOPASDFGHJKLZXVBNMqwertyuioplkjhgfdsamnbvcxz';
  for ($i=0;$i<32;$i++){
    $result .= $str[rand(0,48)];
  }
  return $result;
}

// 訂單號 隨機字符串
// 隨便怎麼實現,儘量保證唯一,可以根據它來確定訂單狀態
function trade_no() {
  $result = date("YmdHis", time());
  $str = "0123456789";
  for ($i=0;$i<4;$i++) {
    $result .= $str[rand(0,9)];
  }
  return $result;
}

//curl
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;
}


//簽名函數,未排序,傳入數據需要對key按字母序排好,否則簽名不正確
function sign($data){
  $stringA = '';
  foreach ($data as $key=>$value){
    if(!$value) continue;
    if($stringA) $stringA .= '&'.$key."=".$value;
    else $stringA = $key."=".$value;
  }
  $wx_key = '你的key';//申請支付後有給予一個商戶賬號和密碼,登陸後自己設置key
  $stringSignTemp = $stringA.'&key='.$wx_key;//申請支付後有給予一個商戶賬號和密碼,登陸後自己設置key 
  return strtoupper(md5($stringSignTemp));
}


//獲取xml,注意key統一轉成了小寫
function xml2arr($xml){
  $p = xml_parser_create();
  xml_parse_into_struct($p, $xml, $vals, $index);
  xml_parser_free($p);
  $data = "";
  foreach ($index as $key=>$value) {
    if($key == 'xml' || $key == 'XML') continue;
    $tag = $vals[$value[0]]['tag'];
    $value = $vals[$value[0]]['value'];
    $data[strtolower($tag)] = $value;
  }
  return $data;
}

五,一些問題

(1)代碼根據網上提供的改編而來,如果需要thinkphp上運行,要麼csdn上找一份源碼,要麼依照這個改寫

(2)回調函數是給微信調用的,裏面可以不寫return(thinkphp中有return),簽名驗證成功後可以寫業務邏輯,可通過訂單號處理,預支付寫入訂單號,回調修改訂單號完成訂單及後續處理。

(3)小程序id修改後,小程序與接口都需要修改,數據庫中的openid需要重新登錄,否則appid與openid不匹配。

(4)簽名函數需要商戶key

。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章