Files

8.8 KiB
Raw Permalink Blame History

Mqpay — 示例支付插件(真实微信 / 支付宝)

通过监听 app/member/controller/Payment.php 暴露的 Payment* 事件 接入真实微信/支付宝支付, 核心控制器零改动,所有支付能力由本插件注入(基于 Yansongda Pay v3)。

前置依赖

composer require yansongda/pay  # ^3.0

.env 中配置真实密钥(对应 config/pay.php):

# 微信支付
PAY_WECHAT_MCH_ID=你的商户号
PAY_WECHAT_APP_ID=你的AppID
PAY_WECHAT_SECRET_KEY=APIv3密钥
# PAY_WECHAT_CERT_CLIENT=/path/apiclient_cert.pem
# PAY_WECHAT_CERT_KEY=/path/apiclient_key.pem

# 支付宝
PAY_ALIPAY_APP_ID=你的AppID
PAY_ALIPAY_PUBLIC_KEY=支付宝公钥
PAY_ALIPAY_PRIVATE_KEY=应用私钥

未安装 SDK 或未配置密钥时,插件会优雅降级:methods 仍返回通道,create 返回友好错误提示,不会致命报错。

监听的事件契约

事件 订阅方法 入参 返回值
PaymentMethods onPaymentMethods [] [['code'=>'wechat'|'alipay','name','desc','icon','sort']]
PaymentOrderCreate onPaymentOrderCreate ['order'=>[...]] 无(落库 Mqpay_order
PaymentCreate onPaymentCreate ['order'=>[...],'params'=>[...]] ['type'=>'qrcode','qrcode_url','order_sn']
PaymentNotify onPaymentNotify ['method','post','get'] 'success' / 'fail'(网关要求)
PaymentFreeActive onPaymentFreeActive ['user','plan']

前端流程

  1. 用户选择套餐 → 弹窗列出 wechat / alipay 两个通道;
  2. 确认后 POST /user/payment/create → 触发 PaymentOrderCreate + PaymentCreate
  3. 插件调用 Yansongda 统一下单(扫码),前端用返回的 qrcode_url 渲染二维码;
  4. 前端每 2 秒轮询 POST /Mqpay/pay/query(带 order_sn)检测支付结果;
  5. 微信/支付宝异步通知 POST /Mqpay/pay/notify?method=xxx → 插件验签、标记订单、触发 UserMemberActivate 事件,返回 success

会员开通:UserMemberActivate 监听器

支付成功后本插件仅完成订单激活(把 wxapp_mqpay_order.status 置 1), 真正的「会员权益写入」(更新用户组 / 会员有效期 / 资金流水 / 操作日志)通过触发 UserMemberActivate 事件交给业务层处理,保证插件与业务解耦。

监听器位置

  • 实现:app/member/listener/UserMemberActivate.php

    • handle($event) 读取 $event['uid']$event['plan_id']$event['order']$event['method']
    • 依据 config('member.plans') 取套餐的 duration/level/gid
    • 在原未过期时间基础上顺延会员期限(避免重复购买覆盖);
    • 写入 4 处:用户主表 vip_expire/vip_level/gid、用户组关联 user_group_access、 资金流水 user_bill、操作日志 user_log
  • 注册:app/event.phplisten 数组(app/event.php:27-29):

    'UserMemberActivate' => [
        app\member\listener\UserMemberActivate::class,
    ],
    

    注:该监听器注册在全局 app/event.php(非插件 info.php),因此与具体支付插件解耦—— 任何触发 UserMemberActivate 事件的插件都能复用同一套会员开通逻辑。 (app/event.php 注释里残留的 paydemo 字样是重命名前身,可忽略。)

触发点

本插件在两处触发该事件:

触发路径 调用链
安卓免签自动确认 POST /mqpay/pay/notify-appnotifyApp() 验签 → 按 real_amount 精确匹配 → confirmOrder()
后台人工确认 后台 Confirm 接口 → Pay.phpconfirmOrder()

confirmOrder() 触发代码示例(subscribe/Payment.php:326):

Event::trigger('UserMemberActivate', [
    'uid'     => $order['uid'],
    'plan_id' => $order['plan_id'],
    'order'   => $order,
    'method'  => 'personal',
]);

支付钩子完整调用链路(时序图)

用户浏览器        app/member/controller/Payment    addon/mqpay/subscribe/Payment   安卓监听App      会员监听器
    │                      │                              │                          │              │
    │─ GET methods ───────▶│                              │                          │              │
    │                      │ Event::trigger('PaymentMethods')                           │              │
    │                      │────────────────────────────▶│ onPaymentMethods()        │              │
    │◀─ 方式列表 ──────────│◀─────────────────────────────│                          │              │
    │                      │                              │                          │              │
    │─ POST create(personal)▶│                            │                          │              │
    │                      │ 生成 order                   │                          │              │
    │                      │ Event::trigger('PaymentOrderCreate')                     │              │
    │                      │────────────────────────────▶│ onPaymentOrderCreate()    │              │
    │                      │ Event::trigger('PaymentCreate')                           │              │
    │                      │────────────────────────────▶│ onPaymentCreate()         │              │
    │                      │                              │ personalQrcode()+jitter   │              │
    │◀─ 收款码+精确金额 ───│◀─────────────────────────────│                          │              │
    │                      │                              │  ║ 用户扫码转账到账 ║       │              │
    │                      │                              │                          │ 监听通知栏    │
    │                      │                              │                          │─ POST /mqpay/pay/notify-app(amount)▶│
    │                      │                              │  notifyApp() 验签 md5(key+amount+sn+key)   │
    │                      │                              │  按 real_amount 精确匹配  │              │
    │                      │                              │  confirmOrder(order_sn)   │              │
    │                      │                              │   status=1                │              │
    │                      │                              │   Event::trigger('UserMemberActivate')      │
    │                      │                              │────────────────────────── 开通会员 ──────▶│
    │◀─ 轮询/刷新看到已开通─│                              │                          │              │

网关支付(微信/支付宝)走 onPaymentNotify 异步回调;免签personal)无网关回调, 由 notify-app(安卓)或后台 Confirm 触发 confirmOrder(),殊途同归到 UserMemberActivate

钩子 / 事件速查表

事件名 订阅方法 订阅位置 触发位置(Payment.php 返回值
PaymentMethods onPaymentMethods subscribe/Payment.php methods() array 方式列表
PaymentOrderCreate onPaymentOrderCreate subscribe/Payment.php create() 无(写库)
PaymentCreate onPaymentCreate subscribe/Payment.php create() array/null 支付参数
PaymentNotify onPaymentNotify subscribe/Payment.php notify() 'success'/'fail'
PaymentFreeActive onPaymentFreeActive subscribe/Payment.php create()
UserMemberActivate handle() app/member/listener/UserMemberActivate.php confirmOrder()subscribe/Payment.php 无(写库/日志)

注册方式对照

  • 支付钩子(前 5 个):在 addon/mqpay/info.phpevents.subscribe 声明订阅器类, 框架 AppService::loadAddonRelevant() 启动时自动 loadEvent 注册。
  • 会员开通(最后 1 个):在全局 app/event.phplisten 中注册,与支付插件解耦。

相关文档