業務場景描述:
用戶點擊站點頁面的 “購買” –> 即彈出二維碼 –> 用戶用微信掃描二維碼 –> 根據微信的指引完成支付 –> 支付成功后頁面提示支付成功并跳轉
與微信之間的交互就三步:
1.傳參,請求微信統一下單接口,獲取支付二維碼
2.接收微信的通知 (微信通過上一步參數中的回調地址,把支付結果發送給我的服務器)
3.請求微信查看訂單的接口,如果支付成功就跳轉頁面
下面的記錄也基本按照上面的流程.
準備工作:
composer?require?"overtrue/laravel-wechat:~5.0"
創建配置文件:
php?artisan?vendor:publish?--provider="OvertrueLaravelWeChatServiceProvider"
修改應用根目錄下的 config/wechat.php 中對應的參數 (這部分直接 copy /paste 就行了) :
'payment'?=>?[ ?????????'default'?=>?[ ?????????????'sandbox'????????????=>?env('WECHAT_PAYMENT_SANDBOX',?false), ?????????????'app_id'?????????????=>?env('WECHAT_PAYMENT_APPID',?''), ?????????????'mch_id'?????????????=>?env('WECHAT_PAYMENT_MCH_ID',?'your-mch-id'), ?????????????'key'????????????????=>?env('WECHAT_PAYMENT_KEY',?'key-for-signature'), ?????????????'cert_path'??????????=>?env('WECHAT_PAYMENT_CERT_PATH',?'path/to/cert/apiclient_cert.pem'),????//?XXX:?絕對路徑!!!! ?????????????'key_path'???????????=>?env('WECHAT_PAYMENT_KEY_PATH',?'path/to/cert/apiclient_key.pem'),??????//?XXX:?絕對路徑!!!! ?????????????'notify_url'?????????=>?env('WECHAT_PAYMENT_NOTIFY_URL',''),???????????????????????????//?默認支付結果通知地址 ?????????], ?????????//?... ?????],
需要配置的就是上面這個數組里的內容,但其實都是需要在 .env 文件中配置的:
#?wechat_payment WECHAT_PAYMENT_SANDBOX=false #?真正需要配置的就下面這四行 WECHAT_PAYMENT_APPID=xxxxxxxxxxxxxxx?//?自己的 WECHAT_PAYMENT_MCH_ID=xxxxxxx??//?自己的 WECHAT_PAYMENT_KEY=xxxxxxxxxxxxxxxxxxxx??//?自己的 WECHAT_PAYMENT_NOTIFY_URL='test.abc.com/payment/notify'?//?這個地址只要是外網能夠訪問到項目的任何地址都可以,?不是需要在微信那里配置的那種,?現在還不知道怎么定義沒關系,?后面用到的時候自然就有了 SWAGGER_VERSION=3.0
安裝overtrue/laravel-wechat 生成二維碼的包
在 composer.json 文件中添加如下:
"require":?{ ????"simplesoftwareio/simple-qrcode":?"~2" }
在終端執行: composer update, 后面會用到.
—————————————— 以上是準備工作,下面開始按照流程 —————————————
用戶點擊 “購買” overtrue/laravel-wechat –> 彈出二維碼
這里是請求了微信的 overtrue/laravel-wechat 接口.
我處理的邏輯是:
用戶發起購買請求時,先在創建支付日志里創建一條記錄,等到用戶完成付款,再創建訂單記錄.
新建一個 PaymentController 專門處理微信支付的邏輯 (跟 OrderController 是兩碼事). 對于用戶點擊 “購買” 的請求,我用 “place_order” 的方法處理,也就是在這里請求微信的 [統一下單] 接口.
頁面上發起下單請求的部分
Html 部分:
(二維碼的 modal 框就是根據 Bootstrap 的文檔寫的)
<button> ????掃碼支付 </button> <!-- 二維碼, 隨便放在當前頁面的那里都可以, 因為是通過 axios 控制, 請求成功后才會彈出的 --> <div> ????????<div> ????????????<div> ????????????????<div> ????????????????????<p>微信掃碼支付</p> ????????????????????<br> ????????????????????{{--生成的二維碼會放在這里--}} ????????????????????<div></div> ????????????????</div> ????????????</div> ????????</div> ????</div>
JS 部分:
$('#order').click(function?()?{ ????/**?請求下單接口?**/ ????axios.get("/payment/place_order",?{ ????????params:?{ ????????????id:?"{{?$post->id?}}" ????????} ????}).then(function?(response)?{ ????????if?(response.data.code?==?200)?{ ????????????/**?把生成的二維碼放到頁面上?*/ ????????????$('#qrcode2').html(response.data.html); ????????????/**?彈出二維碼?**/ ????????????$('#qrcode').modal('show'); ????????????/**?設置定時器,?即一彈出二維碼就開始不斷請求查看支付狀態,?直到收到支付成功的返回,?再終止定時器?**/ ????????????var?timer?=?setInterval(function?()?{ ????????????????/**?在這里請求微信支付狀態的接口?**/ ????????????????axios.get('/payment/paid',?{ ????????????????????params:?{ ????????????????????'out_trade_no':response.data.order_sn, ????????????????????} ????????????????}).then(function?(response)?{ ????????????????????if?(response.data.code?==?200)?{ ????????????????????????/**?如果支付成功,?就取消定時器,?并重新加載頁面?*/ ????????????????????????window.clearInterval(timer); ????????????????????????window.location.reload(); ????????????????????????} ????????????????????}).catch(function?(error)?{ ????????????????????????????console.log(error); ????????????????????????}); ????????????????????},?3000); ????????????????} ????????????}).catch(function?(error)?{ ????????????????????console.log(error); ????????????????}); ????????????});
創建路由
這里先把上面 JS 部分請求的兩個路由都先寫出來了,下面先說明第一個:
//?請求微信統一下單接口 Route::get('/payment/place_order',?'PaymentController@place_order')->name('web.payment.place_order'); //?請求微信接口,?查看訂單支付狀態 Route::get('/payment/paid',?'PaymentController@paid')->name('web.payment.paid'); PaymentController?里的支付邏輯 下面是具體的邏輯,用戶點擊支付后,先創建一條記錄在?PayLog?(用來記錄支付的詳細信息,所以這里還需要建?Paylog?的?model?和?migration,?migration?的內容我附在最后了,都是微信返回的字段,基本可以直接?copy?來用的) class?PaymentController?extends?Controller { ????//?請求微信接口的公用配置,?所以單獨提出來 ????private?function?payment() ????{ ????????$config?=?[ ????????????//?必要配置,?這些都是之前在?.env?里配置好的 ????????????'app_id'?=>?config('wechat.payment.default.app_id'), ????????????'mch_id'?=>?config('wechat.payment.default.mch_id'), ????????????'key'????=>?config('wechat.payment.default.key'),???//?API?密鑰 ????????????'notify_url'?=>?config('wechat.payment.default.notify_url'),???//?通知地址 ????????]; ????????//?這個就是?easywechat?封裝的了,?一行代碼搞定,?照著寫就行了 ????????$app?=?Factory::payment($config); ????????return?$app; ????} ????//?向微信請求統一下單接口,?創建預支付訂單 ????public?function?place_order($id) ????{ ????????//?因為沒有先創建訂單,?所以這里先生成一個隨機的訂單號,?存在?pay_log?里,?用來標識訂單,?支付成功后再把這個訂單號存到?order?表里 ????????$order_sn?=?date('ymd').substr(time(),-5).substr(microtime(),2,5); ????????//?根據文章?id?查出文章價格 ????????$post_price?=?optional(Post::where('id',?$id)->first())->pirce; ????????//?創建?Paylog?記錄 ????????PayLog::create([ ????????????'appid'?=>?config('wechat.payment.default.app_id'), ????????????'mch_id'?=>?config('wechat.payment.default.mch_id'), ????????????'out_trade_no'?=>?$order_sn, ????????????'post_id'?=>?$id ????????]); ????????$app?=?$this->payment(); ????????$total_fee?=?env('APP_DEBUG')???1?:?$post_price; ????????//?用?easywechat?封裝的方法請求微信的統一下單接口 ????????$result?=?$app->order->unify([ ????????????'trade_type'???????=>?'NATIVE',?//?原生支付即掃碼支付,商戶根據微信支付協議格式生成的二維碼,用戶通過微信“掃一掃”掃描二維碼后即進入付款確認界面,輸入密碼即完成支付。?? ????????????'body'?????????????=>?'投資平臺-訂單支付',?//?這個就是會展示在用戶手機上巨款界面的一句話,?隨便寫的 ????????????'out_trade_no'?????=>?$order_sn, ????????????'total_fee'????????=>?$total_fee, ????????????'spbill_create_ip'?=>?request()->ip(),?//?可選,如不傳該參數,SDK?將會自動獲取相應?IP?地址 ????????]); ????????if?($result['result_code']?==?'SUCCESS')?{ ????????????//?如果請求成功,?微信會返回一個?'code_url'?用于生成二維碼 ????????????$code_url?=?$result['code_url']; ????????????return?[ ????????????????'code'?????=>?200, ????????????????//?訂單編號,?用于在當前頁面向微信服務器發起訂單狀態查詢請求 ????????????????'order_sn'?=>?$order_sn, ????????????????//?生成二維碼 ????????????????'html'?=>?QrCode::size(200)->generate($code_url), ????????????]; ????????} ????} }
———– 與微信交互的第一步 (請求統一下單接口) 完成 ———–
接收微信的 overtrue/laravel-wechat
路由
微信根據上面請求中傳參的 notify_url 請求我的服務器,發送支付結果給我,那么必然是 post 請求:
Route::post('/payment/notify',?'paymentController@notify');
取消 csrf 驗證
但是,微信服務器發起的 post 請求無法通過 csrf token 驗證,所以必須取消用于微信的路由的驗證,在 app/Http/Middleware/VerifyCsrfToken 文件中:
protected?$except?=?[ ????????// ????????'payment/notify' ????]; 在?PaymentController.php?文件中處理接收微信信息的邏輯 ????//?接收微信支付狀態的通知 ????public?function?notify() ????{ ????????$app?=?$this->payment(); ????????//?用?easywechat?封裝的方法接收微信的信息,?根據?$message?的內容進行處理,?之后要告知微信服務器處理好了,?否則微信會一直請求這個?url,?發送信息 ????????$response?=?$app->handlePaidNotify(function($message,?$fail){ ????????????//?首先查看?order?表,?如果?order?表有記錄,?表示已經支付過了 ????????????$order?=?Order::where('order_sn',?$message['out_trade_no'])->first(); ????????????if?($order)?{ ????????????????return?true;?//?如果已經生成訂單,?表示已經處理完了,?告訴微信不用再通知了 ????????????} ????????????//?查看支付日志 ????????????$payLog?=?PayLog::where('out_trade_no',?$message['out_trade_no'])->first(); ????????????if?(!$payLog?||?$payLog->paid_at)?{?//?如果訂單不存在?或者?訂單已經支付過了 ????????????????return?true;?//?告訴微信,我已經處理完了,訂單沒找到,別再通知我了 ????????????} ????????????//?return_code?表示通信狀態,不代表支付狀態 ????????????if?($message['return_code']?===?'SUCCESS')?{ ????????????????//?用戶是否支付成功 ????????????????if?($message['result_code']?===?'SUCCESS')?{ ????????????????????//?更新支付時間為當前時間 ????????????????????$payLog->paid_at?=?now(); ????????????????????$post_id?=?$payLog->post_id; ????????????????????//?聯表查詢?post?的相關信息 ????????????????????$post_title?=?$payLog->post->title; ????????????????????$post_price?=?$payLog->post->price; ????????????????????$post_original_price?=?$payLog->post->original_price; ????????????????????$post_cover?=?$payLog->post->post_cover; ????????????????????$post_description?=?$payLog->post->description; ????????????????????$user_id?=?$payLog->post->user_id; ????????????????????//?創建訂單記錄 ????????????????????Order::create([ ????????????????????????'order_sn'?=>?$message['out_trade_no'], ????????????????????????'total_fee'?=>?$message['total_fee'], ????????????????????????'pay_log_id'?=>?$payLog->id, ????????????????????????'status'?=>?1, ????????????????????????'user_id'?=>?$user_id, ????????????????????????'paid_at'?=>?$payLog->paid_at, ????????????????????????'post_id'?=>?$post_id, ????????????????????????'post_title'?=>?$post_title, ????????????????????????'post_price'?=>?$post_price, ????????????????????????'post_original_price'?=>?$post_original_price, ????????????????????????'post_cover'?=>?$post_cover, ????????????????????????'post_description'?=>?$post_description, ????????????????????]); ????????????????????//?更新?PayLog,?這里的字段都是根據微信支付結果通知的字段設置的(https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7&index=8) ????????????????????PayLog::where('out_trade_no',?$message['out_trade_no'])->update([ ????????????????????????'appid'?=>?$message['appid'], ????????????????????????'bank_type'?=>?$message['bank_type'], ????????????????????????'total_fee'?=>?$message['total_fee'], ????????????????????????'trade_type'?=>?$message['trade_type'], ????????????????????????'is_subscribe'?=>?$message['is_subscribe'], ????????????????????????'mch_id'?=>?$message['mch_id'], ????????????????????????'nonce_str'?=>?$message['nonce_str'], ????????????????????????'openid'?=>?$message['openid'], ????????????????????????'sign'?=>?$message['sign'], ????????????????????????'cash_fee'?=>?$message['cash_fee'], ????????????????????????'fee_type'?=>?$message['fee_type'], ????????????????????????'transaction_id'?=>?$message['transaction_id'], ????????????????????????'time_end'?=>?$payLog->paid_at, ????????????????????????'result_code'?=>?$message['result_code'], ????????????????????????'return_code'?=>?$message['return_code'], ????????????????????]); ????????????????} ????????????}?else?{ ????????????????//?如果支付失敗,?也更新?PayLog,?跟上面一樣,?就是多了?error?信息 ????????????????PayLog::where('out_trade_no',?$message['out_trade_no'])->update([ ????????????????????'appid'?=>?$message['appid'], ????????????????????'bank_type'?=>?$message['bank_type'], ????????????????????'total_fee'?=>?$message['total_fee'], ????????????????????'trade_type'?=>?$message['trade_type'], ????????????????????'is_subscribe'?=>?$message['is_subscribe'], ????????????????????'mch_id'?=>?$message['mch_id'], ????????????????????'nonce_str'?=>?$message['nonce_str'], ????????????????????'openid'?=>?$message['openid'], ????????????????????'sign'?=>?$message['sign'], ????????????????????'cash_fee'?=>?$message['cash_fee'], ????????????????????'fee_type'?=>?$message['fee_type'], ????????????????????'transaction_id'?=>?$message['transaction_id'], ????????????????????'time_end'?=>?$payLog->paid_at, ????????????????????'result_code'?=>?$message['result_code'], ????????????????????'return_code'?=>?$message['return_code'], ????????????????????'err_code'?=>?$message['err_code'], ????????????????????'err_code_des'?=>?$message['err_code_des'], ????????????????]); ????????????????return?$fail('通信失敗,請稍后再通知我'); ????????????} ????????????return?true;?//?返回處理完成 ????????}); ????????//?這里是必須這樣返回的,?會發送給微信服務器處理結果 ????????return?$response; ????}
上面有用到 pay_logs 表和 posts 表的聯表查詢,一篇 post 可以有多個 pay_logs, 所以是一對多的關系,在 PayLog.php 里設置一下:
public?function?post() { ????return?$this->belongsTo(Post::class); }
————— 與微信交互的第二步 (接收信息), 完成 ————–
請求微信 overtrue/laravel-wechat接口
請求微信查看訂單狀態接口,路由在交互第一步已經寫過了
public?function?paid(Request?$request) ????{ ????????$out_trade_no?=?$request->get('out_trade_no'); ????????$app?=?$this->payment(); ????????//?用?easywechat?封裝的方法請求微信 ????????$result?=?$app->order->queryByOutTradeNumber($out_trade_no); ????????if?($result['trade_state']?===?'SUCCESS')? ????????????return?[ ????????????????'code'?=>?200, ????????????????'msg'?=>?'paid' ????????????]; ????????}else{ ????????????return?[ ????????????????'code'?=>?202, ????????????????'msg'?=>?'not?paid' ????????????]; ????????} ????}
—————- 與微信交互的第三步 (查看訂單狀態), 完成 —————-
附: pay_logs 表的 migration
由于此表的字段基本就是微信支付結果通知的字段,所以附在下面方便下次使用:
public?function?up() ????{ ????????Schema::create('pay_logs',?function?(Blueprint?$table)?{ ????????????$table->bigIncrements('id'); ????????????//?根據自身業務設計的字段 ????????????$table->integer('post_id')->default(0)->comment('文章id'); ????????????//?以下均是微信支付結果通知接口返回的字段 ????????????$table->string('appid',?255)->default('')->comment('微信分配的公眾賬號ID'); ????????????$table->string('mch_id',?255)->default('')->comment('微信支付分配的商戶號'); ????????????$table->string('bank_type',?16)->default('')->comment('付款銀行'); ????????????$table->integer('cash_fee')->default(0)->comment('現金支付金額'); ????????????$table->string('fee_type',?8)->default('')->comment('貨幣種類'); ????????????$table->string('is_subscribe',?1)->default('')->comment('是否關注公眾賬號'); ????????????$table->string('nonce_str',?32)->default('')->comment('隨機字符串'); ????????????$table->string('openid',?128)->default('')->comment('用戶標識'); ????????????$table->string('out_trade_no',?32)->default('')->comment('商戶系統內部訂單號'); ????????????$table->string('result_code',?16)->default('')->comment('業務結果'); ????????????$table->string('return_code',?16)->default('')->comment('通信標識'); ????????????$table->string('sign',?32)->default('')->comment('簽名'); ????????????$table->string('prepay_id',?64)->default('')->comment('微信生成的預支付回話標識,用于后續接口調用中使用,該值有效期為2小時'); ????????????$table->dateTime('time_end')->nullable()->comment('支付完成時間'); ????????????$table->integer('total_fee')->default(0)->comment('訂單金額'); ????????????$table->string('trade_type',?16)->default('')->comment('交易類型'); ????????????$table->string('transaction_id',?32)->default('')->comment('微信支付訂單號'); ????????????$table->string('err_code',?32)->default('')->comment('錯誤代碼'); ????????????$table->string('err_code_des',?128)->default('')->comment('錯誤代碼描述'); ????????????$table->string('device_info',?32)->default('')->comment('設備號'); ????????????$table->text('attach')->nullable()->comment('商家數據包'); ????????????$table->nullableTimestamps(); ????????}); ????}
以上,就是從頁面到下單到支付到頁面跳轉的全過程記錄。除了很久以前跟著 Laravel-china 教程做過一次,這算是真正第一次自己摸索,根據自己的需求做的一次。網上分享的文章教程也很多,但都是大神級別的,很多地方都一筆帶過,對于我這種 junior 的感覺就是東一榔頭,西一棒槌,很難 follow. 我盡最大努力把筆記整理得細致些,希望對跟我一樣的 beginner 有幫助。看著是很長啊,但是,真的實現也真得這么多內容吧,至少以我目前的水平是這樣的.