爲了閱讀不累,我們仍然以故事的形式展開,本次我們服務的客戶是一個手機充值店老闆,他有一個公衆服務號,我們要爲其實現微信瀏覽器內支付功能。
客戶給了我們一個原型圖,是下面的樣子。
需求並不複雜
- 點擊支付按鈕直接彈出如圖二的支付頁面,注意:不進行頁面跳轉。
- 輸入密碼支付成功。跳轉到圖三的成功支付頁面。
準確的說就兩個頁面。
關鍵點普及
首先我對微信支付是有大概瞭解的(官方文檔),爲了將每個場景統一,微信支付提出了一個叫做 預支付交易單 的概念。
商戶系統先調用接口在微信支付服務後臺生成預支付交易單,返回正確的預支付交易回話標識後再按掃碼、JSAPI、APP等不同場景生成交易串調起支付。
而客戶要求的通過微信瀏覽器這種場景發起的支付則屬於JSAPI支付模式,如果你對各種場景下對應的支付模式不清楚,可以看下下面的表格,這將對我們以後開發各種微信支付很有好處。
JSAPI--公衆號支付、NATIVE--原生掃碼支付、APP--APP支付
使用場景 | 支付模式 |
---|---|
PC網站 | NATIVE |
微信瀏覽器 | JSAPI |
門店掃碼 | NATIVE / JSAPI |
手機應用APP | APP |
一些配置工作
想讓JSAPI支付類型的場景生效,我們需要對公衆號進行一些設置。
首先進入公衆號的微信支付頁面設置公衆號授權目錄
注意:發起支付請求的鏈接地址,都必須在支付授權目錄之下。
其次我們需要4個配置參數(AppID、AppSecret、merchant_id、key),merchant_id是你的商戶號、key是你的支付key(在支付平臺可以找到)。
我準備好了這些,現在開始編碼~
開始啦
我設計了一個控制器 ChargeController
namespace app\modules\wechat\controllers;
use Yii;
use yii\web\Controller;
class ChargeController extends Controller {
/**
* 第一個頁面
*/
public function actionIndex(){
return $this->render('index');
}
}
對應視圖
// http://abc.com/wechat/charge-index.html
<h1>¥49.95</h1>
<div>
<button>點擊支付</button>
</div>
現在要實現點擊button後調出微信支付,對此我不懼怕,因爲微信官方已經提供了相應文檔 - 微信內H5調起支付,說白了就是一組能被微信瀏覽器識別的js代碼,我們將含有AppId及預支付交易回話標識等傳進去後微信支付就蹦出來了。
而這個過程一般是在頁面加載過程中這些特殊的js代碼就跟着添加了,這顯然和當前需求有出入,我們要做的是點擊支付後才調用js代碼。
好,那就用ajax來實現它。
看來我要做3步事情,點擊按鈕後
- 通過ajax在服務器上生成一個類似充值訂單的記錄(狀態爲未支付),同時將一組已經擁有了正確參數的js代碼返給瀏覽器。
- ajax接收了js代碼,並且加載,支付彈出等等等等。
- 服務器端要有一個actionNotify方法接收微信服務器的異步通知,將上面的訂單設置爲已支付。
我們說做就做,開始重寫視圖
// charge/index.php
<h1>¥49.95</h1>
<div id="wxJs"></div>
<div>
<button id="payBtn">點擊支付</button>
</div>
<script type="text/javascript">
$('#payBtn').click(function(){
var url = "/index.php?wechat/charge/pay";
$.getJSON(url,{},function(d){
if(d.done == true){
$('#wxJs').html(d.data);
}else{
}
});
});
</script>
wxJs就是用來存放服務器返回的那個可以調起微信支付的js代碼,如果你還看不懂,那麼阿北給你一個看圖說話版再。
至關重要的pay
通過上面的編寫我們實現了不跳轉頁面彈出微信支付佈局,現在我們來編寫這個至關重要的 actionPay 函數。
// ChargeController
use EasyWeChat\Foundation\Application;
use EasyWeChat\Payment\Order;
...
/**
* 該函數被前臺的button觸發
**/
public function actionPay(){
$charge = new Charge();
// 刷刷刷一堆代碼,就生成了未付款訂單。
// 通過EasyWechat來調用
$config = Yii::$app->params['WECHAT'];
$wxApp = new Application($config);
$payment = $wxApp->payment;
$notifyUrl = Yii::$app->request->getHostInfo() . Url::to(['/wechat/charge/notify']);
$attributes = [
'trade_type'=>Order::JSAPI,
'body'=>"商品描述",
'detail'=>"商品詳情",
'out_trade_no'=>$charge->number,
'total_fee'=>$charge->money*100,
'notify_url'=>$notifyUrl,
'openid'=>$this->wxLogin->open_id
];
$order = new Order($attributes);
$result = $payment->prepare($order);
if ($result->return_code == 'SUCCESS' && $result->result_code == 'SUCCESS'){
$prepayId = $result->prepay_id;
$json = $payment->configForPayment($prepayId);
$html = $this->renderPartial('_wxpay',[
'json'=>$json,
'charge'=>$charge
]);
echo Json::encode(['done'=>true,'data'=>$html]);
}
}
...
EasyWechat對微信支付進行了很好的封裝,我對代碼裏關鍵點進行說明
- $payment 是EasyWechat對微信支付的封裝對象。
- $attributes 是我們要傳遞給微信服務器的訂單信息,用來獲取 預支付交易會話標識prepayId的。
- $payment->prepare是具體和微信服務器獲取prepayId的功能實現
- $json 是$payment->configForPayment接收$prepayId後生成的json字符串,這個字符串傳給特殊js代碼就能調其微信支付。
關於EasyWechat對微信支付更多封裝信息情況文檔 https://easywechat.org/zh-cn/docs/payment.html
我想你也看到了下面的代碼
$html = $this->renderPartial('_wxpay',[
'json'=>$json,
'charge'=>$charge
]);
_wxpay視圖就是那種特殊js代碼模板,我使用renderPartial函數得到傳遞完$json的js代碼,而renderPartial的作用是不加載任何佈局文件並且將其返回給變量 $html
再看一眼_wxpay的實現
use yii\helpers\Url;
<script type="text/javascript">
function jsApiCall() {
WeixinJSBridge.invoke(
'getBrandWCPayRequest',
<?= $json ?>,
function(res){
if(res.err_msg === 'get_brand_wcpay_request:ok'){
window.location.href = "<?= Url::to(['/wechat/charge/result','id'=>$charge->id]);?>";
}else if(res.err_msg === 'get_brand_wcpay_request:cancel'){
weui.alert('支付被取消');
}else if(res.err_msg === 'get_brand_wcpay_request:fail'){
weui.alert('支付失敗');
}
}
);
}
function callpay() {
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', jsApiCall, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', jsApiCall);
document.attachEvent('onWeixinJSBridgeReady', jsApiCall);
}
}else{
jsApiCall();
}
}
callpay();
</script>
請對比官方文檔 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6 中的js代碼,其實他們是一樣的。
好的,現在$html代碼傳遞給了微信瀏覽器,如圖所示,微信支付框出來了。
支付後的事情
既然支付都彈出來了,那麼我就輸入了支付密碼,成功後跳轉到結果頁面,看下_wxpay視圖中的這段代碼
支付完跳轉到 ?r=wechat/charge/result,actionResult實現很簡單
public function actionResult($id){
$model = Charge::findOne($id);
return $this->render('result',[
'model'=>$model
]);
}
這個action實現支付結果,那麼我們的服務器如何知道用戶已經支付成功了那?還是說中途放棄。
還記得前面代碼中我們設置的 $notifyUrl 麼?這貨就是幹這個的。
// $notifyUrl = Yii::$app->request->getHostInfo() . Url::to(['/wechat/charge/notify']);
public function actionNotify(){
$config = Yii::$app->params['WECHAT'];
$wxApp = new Application($config);
$payment = $wxApp->payment;
$response = $payment->handleNotify(function ($notify, $successful){
if ($successful) {
$order_arr = json_decode($notify, true);
$transactionId = $order_arr['transaction_id'];
// $order_arr就是微信異步通知給服務器的信息
//todo 我們的邏輯,將charge變爲已支付
}
});
$response->send();
}
有一點要注意,微信服務器的異步通知是POST請求,而Yii2對POST默認使用了csrf驗證,爲了能接收到信息,請在ChargeController控制器裏設置如下代碼
public $enableCsrfValidation = false;
否則收不到通知哦,這點一定要記得。
到此爲止我就完成了客戶的需求,使用EasyWechat爲我節省了大量時間,強烈推薦。