# 微信集成解决方案(PHP版) ## 一、环境准备 ### 安装微信SDK ```bash # 安装EasyWeChat SDK composer require w7corp/easywechat:~6.0 ``` ## 二、基础配置 ### 2.1 微信公众号配置 ```php // config/wechat.php return [ 'official_account' => [ 'app_id' => env('WECHAT_OFFICIAL_ACCOUNT_APPID', ''), 'secret' => env('WECHAT_OFFICIAL_ACCOUNT_SECRET', ''), 'token' => env('WECHAT_OFFICIAL_ACCOUNT_TOKEN', ''), 'aes_key' => env('WECHAT_OFFICIAL_ACCOUNT_AES_KEY', ''), ], 'redis' => [ 'host' => env('REDIS_HOST', '127.0.0.1'), 'password' => env('REDIS_PASSWORD', null), 'port' => env('REDIS_PORT', 6379), 'database' => 0, ], ]; ``` ### 2.2 环境变量配置 ```env # .env WECHAT_OFFICIAL_ACCOUNT_APPID=your_appid WECHAT_OFFICIAL_ACCOUNT_SECRET=your_secret WECHAT_OFFICIAL_ACCOUNT_TOKEN=your_token WECHAT_OFFICIAL_ACCOUNT_AES_KEY=your_aes_key ``` ## 三、微信通知系统实现 ### 3.1 消息模板定义 ```php // app/Constants/WxTemplateConstants.php 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'title' => '预约提醒', 'data' => [ 'first' => '您有新的预约提醒', 'keyword1' => '预约项目', 'keyword2' => '预约时间', 'keyword3' => '预约地点', 'remark' => '请准时到达' ] ]; // 技师模板 const TECHNICIAN_NEW_ORDER = [ 'template_id' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'title' => '新订单通知', 'data' => [ 'first' => '您有新的订单', 'keyword1' => '订单编号', 'keyword2' => '服务项目', 'keyword3' => '预约时间', 'remark' => '请及时确认' ] ]; } ``` ### 3.2 通知服务实现 ```php // app/Services/WxNotificationService.php app = Factory::officialAccount(config('wechat.official_account')); } /** * 发送会员预约提醒 */ public function sendMemberAppointmentReminder($openId, $data) { $template = WxTemplateConstants::MEMBER_APPOINTMENT_REMINDER; return $this->sendTemplate($openId, $template['template_id'], [ 'first' => ['value' => $template['data']['first']], 'keyword1' => ['value' => $data['service_name']], 'keyword2' => ['value' => $data['appointment_time']], 'keyword3' => ['value' => $data['location']], 'remark' => ['value' => $template['data']['remark']] ]); } /** * 发送技师新订单通知 */ public function sendTechnicianNewOrder($openId, $data) { $template = WxTemplateConstants::TECHNICIAN_NEW_ORDER; return $this->sendTemplate($openId, $template['template_id'], [ 'first' => ['value' => $template['data']['first']], 'keyword1' => ['value' => $data['order_no']], 'keyword2' => ['value' => $data['service_name']], 'keyword3' => ['value' => $data['appointment_time']], 'remark' => ['value' => $template['data']['remark']] ]); } /** * 发送模板消息 */ private function sendTemplate($openId, $templateId, $data) { try { $result = $this->app->template_message->send([ 'touser' => $openId, 'template_id' => $templateId, 'data' => $data ]); // 记录日志 \Log::info('WxNotification', [ 'openId' => $openId, 'templateId' => $templateId, 'data' => $data, 'result' => $result ]); return $result; } catch (\Exception $e) { \Log::error('WxNotification Error', [ 'message' => $e->getMessage(), 'openId' => $openId, 'templateId' => $templateId ]); throw $e; } } } ``` ### 3.3 队列处理 ```php // app/Jobs/SendWxNotificationJob.php openId = $openId; $this->type = $type; $this->data = $data; } public function handle(WxNotificationService $service) { try { switch ($this->type) { case 'member_appointment': $service->sendMemberAppointmentReminder($this->openId, $this->data); break; case 'technician_order': $service->sendTechnicianNewOrder($this->openId, $this->data); break; } } catch (\Exception $e) { // 失败重试 if ($this->attempts() < 3) { $this->release(30); } } } } ``` ### 3.4 使用示例 ```php // 在业务代码中使用 use App\Jobs\SendWxNotificationJob; // 发送会员预约提醒 dispatch(new SendWxNotificationJob($openId, 'member_appointment', [ 'service_name' => '全身按摩', 'appointment_time' => '2024-01-20 14:30', 'location' => 'xxx店' ])); // 发送技师新订单通知 dispatch(new SendWxNotificationJob($openId, 'technician_order', [ 'order_no' => 'ORDER2024011001', 'service_name' => '全身按摩', 'appointment_time' => '2024-01-20 14:30' ])); ``` ## 四、错误处理与监控 ### 4.1 异常处理 ```php // app/Exceptions/WxNotificationException.php data = $data; } public function getData() { return $this->data; } } ``` ### 4.2 监控实现 ```php // app/Services/WxMonitorService.php $total, 'success' => (int)($stats['success'] ?? 0), 'fail' => (int)($stats['fail'] ?? 0), 'success_rate' => $total > 0 ? round($stats['success'] / $total * 100, 2) : 0 ]; } } ``` ## 五、配置说明 ### 5.1 微信公众平台配置 1. 登录微信公众平台 2. 获取开发者ID(AppID)和开发者密码(AppSecret) 3. 设置服务器配置 4. 配置消息模板 ### 5.2 注意事项 1. 所有配置信息应该放在环境变量中 2. 定期检查access_token的有效性 3. 做好日志记录和异常处理 4. 考虑消息发送的频率限制 ## 六、微信公众号登录系统 ### 6.1 数据库设计 ```sql -- 微信用户表 CREATE TABLE `wx_users` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `openid` varchar(64) NOT NULL COMMENT '微信openid', `unionid` varchar(64) DEFAULT NULL COMMENT '微信unionid', `nickname` varchar(50) DEFAULT NULL COMMENT '微信昵称', `avatar` varchar(255) DEFAULT NULL COMMENT '头像', `user_id` bigint(20) DEFAULT NULL COMMENT '关联用户ID', `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_openid` (`openid`), KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微信用户表'; ``` ### 6.2 前端实现 ```javascript // resources/js/wechat-auth.js // 获取微信授权URL function getWxAuthUrl() { const redirectUri = encodeURIComponent(window.location.origin + '/wechat/callback'); const appId = process.env.MIX_WECHAT_APPID; return `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect`; } // 检查是否在微信浏览器中 function isWechatBrowser() { const ua = navigator.userAgent.toLowerCase(); return ua.indexOf('micromessenger') !== -1; } // 微信登录处理 function handleWechatLogin() { if (isWechatBrowser()) { // 检查是否已有token const token = localStorage.getItem('token'); if (!token) { // 跳转到微信授权页面 window.location.href = getWxAuthUrl(); } } } ``` ### 6.3 后端实现 #### 6.3.1 路由配置 ```php // routes/web.php Route::prefix('wechat')->group(function () { Route::get('/auth', 'WechatAuthController@auth'); Route::get('/callback', 'WechatAuthController@callback'); }); ``` #### 6.3.2 控制器实现 ```php // app/Http/Controllers/WechatAuthController.php wechatAuthService = $wechatAuthService; } /** * 发起微信授权 */ public function auth() { $redirectUrl = url('/wechat/callback'); return $this->wechatAuthService->getAuthUrl($redirectUrl); } /** * 处理微信回调 */ public function callback(Request $request) { try { $code = $request->input('code'); if (empty($code)) { throw new \Exception('授权失败'); } // 处理微信授权 $result = $this->wechatAuthService->handleCallback($code); // 返回token给前端 return view('wechat.callback', ['token' => $result['token']]); } catch (\Exception $e) { \Log::error('WechatAuth Error', ['message' => $e->getMessage()]); return response()->json(['message' => '授权失败'], 400); } } } ``` #### 6.3.3 服务层实现 ```php // app/Services/WechatAuthService.php app = Factory::officialAccount(config('wechat.official_account')); } /** * 获取授权URL */ public function getAuthUrl($redirectUrl) { $response = $this->app->oauth->scopes(['snsapi_userinfo']) ->redirect($redirectUrl); return $response; } /** * 处理微信回调 */ public function handleCallback($code) { try { // 获取OAuth用户信息 $user = $this->app->oauth->user(); $original = $user->getOriginal(); // 查找或创建微信用户 $wxUser = WxUser::firstOrCreate( ['openid' => $original['openid']], [ 'unionid' => $original['unionid'] ?? null, 'nickname' => $original['nickname'], 'avatar' => $original['headimgurl'] ] ); // 关联或创建系统用户 $systemUser = $this->findOrCreateSystemUser($wxUser, $original); // 生成token $token = $this->generateToken($systemUser); return [ 'token' => $token, 'user' => $systemUser ]; } catch (\Exception $e) { \Log::error('WechatAuth Callback Error', [ 'code' => $code, 'message' => $e->getMessage() ]); throw $e; } } /** * 查找或创建系统用户 */ private function findOrCreateSystemUser($wxUser, $wxInfo) { if ($wxUser->user_id) { return User::find($wxUser->user_id); } // 创建新用户 $user = User::create([ 'name' => $wxInfo['nickname'], 'avatar' => $wxInfo['headimgurl'], 'password' => bcrypt(Str::random(16)) ]); // 关联微信用户 $wxUser->update(['user_id' => $user->id]); return $user; } /** * 生成JWT Token */ private function generateToken($user) { return auth()->login($user); } } ``` #### 6.3.4 回调页面实现 ```php // resources/views/wechat/callback.blade.php 微信授权回调 ``` ### 6.4 使用说明 #### 6.4.1 前端集成 ```javascript // 在应用入口检查微信登录状态 document.addEventListener('DOMContentLoaded', () => { handleWechatLogin(); }); // 监听登录成功消息 window.addEventListener('message', (event) => { if (event.data.type === 'wechat_auth_success') { // 存储token localStorage.setItem('token', event.data.token); // 刷新页面或更新状态 window.location.reload(); } }); ``` #### 6.4.2 接口调用示例 ```php // 在需要微信登录的接口中添加中间件 Route::middleware(['auth:api', 'wechat.auth'])->group(function () { // 需要微信登录的路由 }); ``` ### 6.5 安全考虑 1. **Token安全** - 使用HTTPS传输 - 设置合理的token过期时间 - 实现token刷新机制 2. **数据安全** - 用户信息加密存储 - 敏感信息脱敏处理 - 定期清理过期token 3. **防护措施** - 实现防重放攻击 - 添加请求频率限制 - 记录异常登录行为 ### 6.6 注意事项 1. 确保微信公众号已获得网页授权能力 2. 在微信公众平台正确配置授权回调域名 3. 处理用户取消授权的情况 4. 实现用户解绑功能 5. 考虑多设备登录策略 ## 七、微信公众号支付集成 ### 7.1 数据库设计 ```sql -- 支付订单表 CREATE TABLE `wx_payment_orders` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `order_no` varchar(64) NOT NULL COMMENT '商户订单号', `transaction_id` varchar(64) DEFAULT NULL COMMENT '微信支付订单号', `user_id` bigint(20) NOT NULL COMMENT '用户ID', `openid` varchar(64) NOT NULL COMMENT '用户openid', `amount` int(11) NOT NULL COMMENT '支付金额(分)', `description` varchar(128) NOT NULL COMMENT '商品描述', `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '支付状态:0未支付 1支付中 2已支付 3已退款 4已关闭', `pay_time` datetime DEFAULT NULL COMMENT '支付时间', `notify_data` text COMMENT '回调原始数据', `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), KEY `idx_user_id` (`user_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微信支付订单表'; ``` ### 7.2 配置文件 ```php // config/wechat.php return [ // ... 其他配置 ... 'payment' => [ 'default' => [ 'app_id' => env('WECHAT_PAYMENT_APPID', ''), 'mch_id' => env('WECHAT_PAYMENT_MCH_ID', ''), 'key' => env('WECHAT_PAYMENT_KEY', ''), 'cert_path' => storage_path('app/wechat/cert/apiclient_cert.pem'), 'key_path' => storage_path('app/wechat/cert/apiclient_key.pem'), 'notify_url' => env('WECHAT_PAYMENT_NOTIFY_URL', ''), ], ], ]; ``` ### 7.3 支付服务实现 #### 7.3.1 支付服务类 ```php // app/Services/WxPaymentService.php payment = Factory::payment(config('wechat.payment.default')); } /** * 创建支付订单 */ public function createOrder($userId, $openid, $amount, $description) { // 创建商户订单号 $orderNo = date('YmdHis') . Str::random(6); // 创建订单记录 $order = WxPaymentOrder::create([ 'order_no' => $orderNo, 'user_id' => $userId, 'openid' => $openid, 'amount' => $amount, 'description' => $description, 'status' => 0 ]); // 调用微信统一下单 $result = $this->payment->order->unify([ 'body' => $description, 'out_trade_no' => $orderNo, 'total_fee' => $amount, 'trade_type' => 'JSAPI', 'openid' => $openid, ]); if ($result['return_code'] === 'SUCCESS' && $result['result_code'] === 'SUCCESS') { // 更新订单状态 $order->update(['status' => 1]); // 生成支付参数 $params = $this->payment->jssdk->bridgeConfig($result['prepay_id']); return [ 'order_no' => $orderNo, 'params' => $params ]; } throw new \Exception($result['err_code_des'] ?? '创建支付订单失败'); } /** * 处理支付回调 */ public function handlePaymentNotify($message) { // 查找订单 $order = WxPaymentOrder::where('order_no', $message['out_trade_no'])->first(); if (!$order) { return false; } // 验证支付金额 if ($message['total_fee'] != $order->amount) { return false; } // 更新订单状态 $order->update([ 'status' => 2, 'transaction_id' => $message['transaction_id'], 'pay_time' => date('Y-m-d H:i:s'), 'notify_data' => json_encode($message) ]); // 触发支付成功事件 event(new PaymentSuccessEvent($order)); return true; } /** * 查询订单状态 */ public function queryOrder($orderNo) { $result = $this->payment->order->queryByOutTradeNumber($orderNo); if ($result['return_code'] === 'SUCCESS' && $result['result_code'] === 'SUCCESS') { return [ 'paid' => $result['trade_state'] === 'SUCCESS', 'transaction_id' => $result['transaction_id'] ?? null, 'trade_state' => $result['trade_state'], 'trade_state_desc' => $result['trade_state_desc'] ]; } return false; } /** * 申请退款 */ public function refund($orderNo, $refundAmount, $reason = '') { $order = WxPaymentOrder::where('order_no', $orderNo)->first(); if (!$order || $order->status != 2) { throw new \Exception('订单状态不正确'); } $result = $this->payment->refund->byOutTradeNumber($orderNo, date('YmdHis') . Str::random(6), $order->amount, $refundAmount, [ 'refund_desc' => $reason ]); if ($result['return_code'] === 'SUCCESS' && $result['result_code'] === 'SUCCESS') { // 更新订单状态 $order->update(['status' => 3]); return true; } throw new \Exception($result['err_code_des'] ?? '申请退款失败'); } } ``` ### 7.4 控制器实现 ```php // app/Http/Controllers/WxPaymentController.php paymentService = $paymentService; } /** * 创建支付订单 */ public function createOrder(Request $request) { $this->validate($request, [ 'amount' => 'required|integer|min:1', 'description' => 'required|string|max:127' ]); try { $result = $this->paymentService->createOrder( auth()->id(), $request->user()->wx_user->openid, $request->input('amount'), $request->input('description') ); return response()->json($result); } catch (\Exception $e) { return response()->json(['message' => $e->getMessage()], 400); } } /** * 支付回调 */ public function notify() { $response = $this->paymentService->payment->handlePaidNotify(function($message, $fail) { $result = $this->paymentService->handlePaymentNotify($message); return $result ? true : $fail('处理失败'); }); return $response; } /** * 查询订单 */ public function query(Request $request) { $orderNo = $request->input('order_no'); $result = $this->paymentService->queryOrder($orderNo); return response()->json($result ?: ['message' => '查询失败'], $result ? 200 : 400); } } ``` ### 7.5 路由配置 ```php // routes/api.php Route::prefix('payment')->middleware(['auth:api'])->group(function () { Route::post('order', 'WxPaymentController@createOrder'); Route::get('query', 'WxPaymentController@query'); }); // routes/web.php Route::any('payment/notify', 'WxPaymentController@notify'); ``` ### 7.6 前端调用示例 ```javascript // 发起支付 async function wxPay(amount, description) { try { // 1. 创建订单 const response = await axios.post('/api/payment/order', { amount, description }); // 2. 调起支付 const params = response.data.params; WeixinJSBridge.invoke('getBrandWCPayRequest', params, function(res) { if (res.err_msg === 'get_brand_wcpay_request:ok') { // 支付成功 alert('支付成功'); } else { // 支付失败 alert('支付失败:' + res.err_msg); } }); } catch (error) { console.error('支付失败', error); alert('创建订单失败'); } } // 查询订单状态 async function queryOrder(orderNo) { try { const response = await axios.get('/api/payment/query', { params: { order_no: orderNo } }); return response.data; } catch (error) { console.error('查询订单失败', error); return null; } } ``` ### 7.7 安全考虑 1. **支付安全** - 必须使用HTTPS - 验证签名 - 校验订单金额 - 防止重复支付 - 敏感信息加密存储 2. **证书管理** - 安全存储证书文件 - 定期更新证书 - 限制证书文件访问权限 3. **异常处理** - 处理网络超时 - 处理重复通知 - 记录详细日志 ### 7.8 注意事项 1. 确保微信支付商户号已开通 2. 正确配置支付回调域名 3. 处理订单超时 4. 实现订单关闭功能 5. 考虑并发支付情况 6. 实现退款功能 7. 做好金额计算(避免浮点数精度问题) 8. 定期对账