(easywechat + Laravel 5.8)整理 PC 端微信扫码支付全过程

业务场景描述:

用户点击站点页面的 “购买” –> 即弹出二维码 –> 用户用微信扫描二维码 –> 根据微信的指引完成支付 –> 支付成功后页面提示支付成功并跳转

与微信之间的交互就三步:

1.传参,请求微信统一下单接口,获取支付二维码

2.接收微信的通知 (微信通过上一步参数中的回调地址,把支付结果发送给我的服务器)

3.请求微信查看订单的接口,如果支付成功就跳转页面

下面的记录也基本按照上面的流程.

准备工作:

安装 overtrue/laravel-wechat

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-&gt;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')-&gt;name('web.payment.place_order'); // 请求微信接口, 查看订单支付状态 Route::get('/payment/paid', 'PaymentController@paid')-&gt;name('web.payment.paid'); PaymentController 里的支付逻辑 下面是具体的逻辑,用户点击支付后,先创建一条记录在 PayLog (用来记录支付的详细信息,所以这里还需要建 Paylog 的 model 和 migration, migration 的内容我附在最后了,都是微信返回的字段,基本可以直接 copy 来用的) class PaymentController extends Controller {     // 请求微信接口的公用配置, 所以单独提出来     private function payment()     {         $config = [             // 必要配置, 这些都是之前在 .env 里配置好的             'app_id' =&gt; config('wechat.payment.default.app_id'),             'mch_id' =&gt; config('wechat.payment.default.mch_id'),             'key'    =&gt; config('wechat.payment.default.key'),   // API 密钥             'notify_url' =&gt; 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)-&gt;first())-&gt;pirce;         // 创建 Paylog 记录         PayLog::create([             'appid' =&gt; config('wechat.payment.default.app_id'),             'mch_id' =&gt; config('wechat.payment.default.mch_id'),             'out_trade_no' =&gt; $order_sn,             'post_id' =&gt; $id         ]);         $app = $this-&gt;payment();         $total_fee = env('APP_DEBUG') ? 1 : $post_price;         // 用 easywechat 封装的方法请求微信的统一下单接口         $result = $app-&gt;order-&gt;unify([             'trade_type'       =&gt; 'NATIVE', // 原生支付即扫码支付,商户根据微信支付协议格式生成的二维码,用户通过微信“扫一扫”扫描二维码后即进入付款确认界面,输入密码即完成支付。               'body'             =&gt; '投资平台-订单支付', // 这个就是会展示在用户手机上巨款界面的一句话, 随便写的             'out_trade_no'     =&gt; $order_sn,             'total_fee'        =&gt; $total_fee,             'spbill_create_ip' =&gt; request()-&gt;ip(), // 可选,如不传该参数,SDK 将会自动获取相应 IP 地址         ]);         if ($result['result_code'] == 'SUCCESS') {             // 如果请求成功, 微信会返回一个 'code_url' 用于生成二维码             $code_url = $result['code_url'];             return [                 'code'     =&gt; 200,                 // 订单编号, 用于在当前页面向微信服务器发起订单状态查询请求                 'order_sn' =&gt; $order_sn,                 // 生成二维码                 'html' =&gt; QrCode::size(200)-&gt;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-&gt;payment();         // 用 easywechat 封装的方法接收微信的信息, 根据 $message 的内容进行处理, 之后要告知微信服务器处理好了, 否则微信会一直请求这个 url, 发送信息         $response = $app-&gt;handlePaidNotify(function($message, $fail){             // 首先查看 order 表, 如果 order 表有记录, 表示已经支付过了             $order = Order::where('order_sn', $message['out_trade_no'])-&gt;first();             if ($order) {                 return true; // 如果已经生成订单, 表示已经处理完了, 告诉微信不用再通知了             }             // 查看支付日志             $payLog = PayLog::where('out_trade_no', $message['out_trade_no'])-&gt;first();             if (!$payLog || $payLog-&gt;paid_at) { // 如果订单不存在 或者 订单已经支付过了                 return true; // 告诉微信,我已经处理完了,订单没找到,别再通知我了             }             // return_code 表示通信状态,不代表支付状态             if ($message['return_code'] === 'SUCCESS') {                 // 用户是否支付成功                 if ($message['result_code'] === 'SUCCESS') {                     // 更新支付时间为当前时间                     $payLog-&gt;paid_at = now();                     $post_id = $payLog-&gt;post_id;                     // 联表查询 post 的相关信息                     $post_title = $payLog-&gt;post-&gt;title;                     $post_price = $payLog-&gt;post-&gt;price;                     $post_original_price = $payLog-&gt;post-&gt;original_price;                     $post_cover = $payLog-&gt;post-&gt;post_cover;                     $post_description = $payLog-&gt;post-&gt;description;                     $user_id = $payLog-&gt;post-&gt;user_id;                     // 创建订单记录                     Order::create([                         'order_sn' =&gt; $message['out_trade_no'],                         'total_fee' =&gt; $message['total_fee'],                         'pay_log_id' =&gt; $payLog-&gt;id,                         'status' =&gt; 1,                         'user_id' =&gt; $user_id,                         'paid_at' =&gt; $payLog-&gt;paid_at,                         'post_id' =&gt; $post_id,                         'post_title' =&gt; $post_title,                         'post_price' =&gt; $post_price,                         'post_original_price' =&gt; $post_original_price,                         'post_cover' =&gt; $post_cover,                         'post_description' =&gt; $post_description,                     ]);                     // 更新 PayLog, 这里的字段都是根据微信支付结果通知的字段设置的(https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7&amp;index=8)                     PayLog::where('out_trade_no', $message['out_trade_no'])-&gt;update([                         'appid' =&gt; $message['appid'],                         'bank_type' =&gt; $message['bank_type'],                         'total_fee' =&gt; $message['total_fee'],                         'trade_type' =&gt; $message['trade_type'],                         'is_subscribe' =&gt; $message['is_subscribe'],                         'mch_id' =&gt; $message['mch_id'],                         'nonce_str' =&gt; $message['nonce_str'],                         'openid' =&gt; $message['openid'],                         'sign' =&gt; $message['sign'],                         'cash_fee' =&gt; $message['cash_fee'],                         'fee_type' =&gt; $message['fee_type'],                         'transaction_id' =&gt; $message['transaction_id'],                         'time_end' =&gt; $payLog-&gt;paid_at,                         'result_code' =&gt; $message['result_code'],                         'return_code' =&gt; $message['return_code'],                     ]);                 }             } else {                 // 如果支付失败, 也更新 PayLog, 跟上面一样, 就是多了 error 信息                 PayLog::where('out_trade_no', $message['out_trade_no'])-&gt;update([                     'appid' =&gt; $message['appid'],                     'bank_type' =&gt; $message['bank_type'],                     'total_fee' =&gt; $message['total_fee'],                     'trade_type' =&gt; $message['trade_type'],                     'is_subscribe' =&gt; $message['is_subscribe'],                     'mch_id' =&gt; $message['mch_id'],                     'nonce_str' =&gt; $message['nonce_str'],                     'openid' =&gt; $message['openid'],                     'sign' =&gt; $message['sign'],                     'cash_fee' =&gt; $message['cash_fee'],                     'fee_type' =&gt; $message['fee_type'],                     'transaction_id' =&gt; $message['transaction_id'],                     'time_end' =&gt; $payLog-&gt;paid_at,                     'result_code' =&gt; $message['result_code'],                     'return_code' =&gt; $message['return_code'],                     'err_code' =&gt; $message['err_code'],                     'err_code_des' =&gt; $message['err_code_des'],                 ]);                 return $fail('通信失败,请稍后再通知我');             }             return true; // 返回处理完成         });         // 这里是必须这样返回的, 会发送给微信服务器处理结果         return $response;     }

上面有用到 pay_logs 表和 posts 表的联表查询,一篇 post 可以有多个 pay_logs, 所以是一对多的关系,在 PayLog.php 里设置一下:

public function post() {     return $this-&gt;belongsTo(Post::class); }

————— 与微信交互的第二步 (接收信息), 完成 ————–

请求微信 overtrue/laravel-wechat接口

请求微信查看订单状态接口,路由在交互第一步已经写过了

public function paid(Request $request)     {         $out_trade_no = $request-&gt;get('out_trade_no');         $app = $this-&gt;payment();         // 用 easywechat 封装的方法请求微信         $result = $app-&gt;order-&gt;queryByOutTradeNumber($out_trade_no);         if ($result['trade_state'] === 'SUCCESS')              return [                 'code' =&gt; 200,                 'msg' =&gt; 'paid'             ];         }else{             return [                 'code' =&gt; 202,                 'msg' =&gt; 'not paid'             ];         }     }

—————- 与微信交互的第三步 (查看订单状态), 完成 —————-

附: pay_logs 表的 migration

由于此表的字段基本就是微信支付结果通知的字段,所以附在下面方便下次使用:

public function up()     {         Schema::create('pay_logs', function (Blueprint $table) {             $table-&gt;bigIncrements('id');             // 根据自身业务设计的字段             $table-&gt;integer('post_id')-&gt;default(0)-&gt;comment('文章id');             // 以下均是微信支付结果通知接口返回的字段             $table-&gt;string('appid', 255)-&gt;default('')-&gt;comment('微信分配的公众账号ID');             $table-&gt;string('mch_id', 255)-&gt;default('')-&gt;comment('微信支付分配的商户号');             $table-&gt;string('bank_type', 16)-&gt;default('')-&gt;comment('付款银行');             $table-&gt;integer('cash_fee')-&gt;default(0)-&gt;comment('现金支付金额');             $table-&gt;string('fee_type', 8)-&gt;default('')-&gt;comment('货币种类');             $table-&gt;string('is_subscribe', 1)-&gt;default('')-&gt;comment('是否关注公众账号');             $table-&gt;string('nonce_str', 32)-&gt;default('')-&gt;comment('随机字符串');             $table-&gt;string('openid', 128)-&gt;default('')-&gt;comment('用户标识');             $table-&gt;string('out_trade_no', 32)-&gt;default('')-&gt;comment('商户系统内部订单号');             $table-&gt;string('result_code', 16)-&gt;default('')-&gt;comment('业务结果');             $table-&gt;string('return_code', 16)-&gt;default('')-&gt;comment('通信标识');             $table-&gt;string('sign', 32)-&gt;default('')-&gt;comment('签名');             $table-&gt;string('prepay_id', 64)-&gt;default('')-&gt;comment('微信生成的预支付回话标识,用于后续接口调用中使用,该值有效期为2小时');             $table-&gt;dateTime('time_end')-&gt;nullable()-&gt;comment('支付完成时间');             $table-&gt;integer('total_fee')-&gt;default(0)-&gt;comment('订单金额');             $table-&gt;string('trade_type', 16)-&gt;default('')-&gt;comment('交易类型');             $table-&gt;string('transaction_id', 32)-&gt;default('')-&gt;comment('微信支付订单号');             $table-&gt;string('err_code', 32)-&gt;default('')-&gt;comment('错误代码');             $table-&gt;string('err_code_des', 128)-&gt;default('')-&gt;comment('错误代码描述');             $table-&gt;string('device_info', 32)-&gt;default('')-&gt;comment('设备号');             $table-&gt;text('attach')-&gt;nullable()-&gt;comment('商家数据包');             $table-&gt;nullableTimestamps();         });     }

以上,就是从页面到下单到支付到页面跳转的全过程记录。除了很久以前跟着 Laravel-china 教程做过一次,这算是真正第一次自己摸索,根据自己的需求做的一次。网上分享的文章教程也很多,但都是大神级别的,很多地方都一笔带过,对于我这种 junior 的感觉就是东一榔头,西一棒槌,很难 follow. 我尽最大努力把笔记整理得细致些,希望对跟我一样的 beginner 有帮助。看着是很长啊,但是,真的实现也真得这么多内容吧,至少以我目前的水平是这样的.

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享