帮助文档
{{userInfo.nickname}}
用户设置 退出登录

{{wikiTitle}}

支付流程全量说明

支付流程全量说明

概述

CRMEB多店系统支持多种支付方式,包括微信支付、支付宝支付、余额支付、积分支付、线下支付等。本文档详细说明整个支付体系的架构、流程和实现细节。

支付方式类型

1.1 支付方式列表

系统支持的主要支付方式:

支付方式 标识 说明
微信支付 weixin 微信公众号、小程序支付
微信H5支付 weixinh5 微信H5支付
支付宝支付 alipay 支付宝APP、H5、扫码支付
余额支付 yue 用户账户余额支付
积分支付 integral 用户积分抵扣支付
线下支付 offline 线下现金支付
现金支付 cash 现金支付

1.2 支付场景

  • 商品购买支付 - 购买商品、服务
  • 会员储值支付 - 购买会员卡
  • 余额充值支付 - 用户账户充值
  • 其他支付 - 各种自定义支付场景

支付系统架构

2.1 核心组件

支付系统架构:
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   支付入口       │    │   支付服务       │    │   支付渠道       │
│   PayServices   │◄──►│  OrderPayServices│◄──►│  微信支付/支付宝  │
└─────────────────┘    └─────────────────┘    └─────────────────┘
         │                       │                       │
         ▼                       ▼                       ▼
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   支付回调处理   │    │   支付完成处理  │    │   支付异步通知   │
│ PayNotifyServices│    │  StoreOrderSuccess│    │   事件监听      │
│                 │    │    Services     │    │   PayNotifyListener│
└─────────────────┘    └─────────────────┘    └─────────────────┘

2.2 主要文件结构

app/services/pay/
├── PayServices.php          # 支付统一入口
├── OrderPayServices.php     # 订单支付服务
├── PayNotifyServices.php    # 支付回调处理
├── PayMchNotifyServices.php # 商户支付回调
├── YuePayServices.php       # 余额支付服务
├── IntegralPayServices.php  # 积分支付服务
├── RechargeServices.php     # 充值支付服务
└── OrderOfflineServices.php # 线下支付服务

app/listener/pay/
├── PayNotifyListener.php    # 支付回调监听器
├── PayMchNotifyListener.php # 商户回调监听器
├── PayCreateOrderListener.php # 订单创建监听器
└── RefundedNotifyListener.php # 退款回调监听器

crmeb/services/
├── AliPayService.php        # 支付宝服务
└── wechat/
    └── Payment.php          # 微信支付服务

app/controller/api/v1/Pay.php # 支付回调控制器

支付流程详解

3.1 发起支付流程

3.1.1 统一支付入口 (PayServices.php)

class PayServices
{
    // 支付方式常量
    const WEIXIN_PAY = 'weixin';
    const YUE_PAY = 'yue';
    const ALIAPY_PAY = 'alipay';
    // ...

    public function pay(string $payType, string $openid, string $orderId, string $price, 
                       string $successAction, string $body, bool $isCode = false)
    {
        switch ($payType) {
            case 'routine': // 微信小程序支付
                if (request()->isApp()) {
                    return Payment::appPay($openid, $orderId, $price, $successAction, $body);
                } else {
                    if (sys_config('pay_routine_open', 0)) {
                        return Payment::miniPay($openid, $orderId, $price, $successAction, $body);
                    } else {
                        // V3支付
                        if (Payment::instance()->isV3PAy) {
                            return Payment::instance()->application()->v3pay->miniprogPay($openid, $orderId, $price, $body, $successAction);
                        }
                        return Payment::jsPay($openid, $orderId, $price, $successAction, $body);
                    }
                }

            case 'weixinh5': // 微信H5支付
                if (Payment::instance()->isV3PAy) {
                    return Payment::instance()->application()->v3pay->h5Pay($orderId, $price, $body, $successAction);
                }
                return Payment::paymentOrder(null, $orderId, $price, $successAction, $body, '', 'MWEB');

            case self::WEIXIN_PAY: // 微信支付
                if ($this->authCode) { // 付款码支付
                    return Payment::microPay($this->authCode, $orderId, $price, $successAction, $body);
                } else {
                    // APP支付
                    if (request()->isApp()) {
                        return Payment::appPay($openid, $orderId, $price, $successAction, $body);
                    } else {
                        // V3支付
                        if (Payment::instance()->isV3PAy) {
                            return Payment::instance()->application()->v3pay->jsapiPay($openid, $orderId, $price, $body, $successAction);
                        }
                        // V2支付
                        return Payment::jsPay($openid, $orderId, $price, $successAction, $body);
                    }
                }

            case self::ALIAPY_PAY: // 支付宝支付
                if ($this->authCode) { // 付款码支付
                    return AliPayService::instance()->microPay($this->authCode, $body, $orderId, $price, $successAction);
                } else {
                    return AliPayService::instance()->create($body, $orderId, $price, $successAction, $openid, $openid, $isCode);
                }

            case 'pc': // PC端支付
            case 'store': // 门店支付
                return Payment::nativePay($openid, $orderId, $price, $successAction, $body);

            default:
                throw new ValidateException('支付方式不存在');
        }
    }
}

3.1.2 订单支付服务 (OrderPayServices.php)

class OrderPayServices
{
    protected $payServices;

    public function __construct(PayServices $services)
    {
        $this->payServices = $services;
    }

    /**
     * 订单发起支付
     */
    public function orderPay(array $orderInfo, string $payType)
    {
        // 1. 检查订单状态
        if ($orderInfo['paid']) {
            throw new ValidateException('订单已支付!');
        }
        if ($orderInfo['pay_price'] <= 0) {
            throw new ValidateException('该支付无需支付!');
        }

        // 2. 获取用户openid(非特定支付方式需要)
        $openid = '';
        if (!in_array($payType, ['weixinh5', 'pc', 'store']) && !request()->isApp()) {
            if ($payType === 'weixin') {
                $userType = 'wechat';
            } else {
                $userType = $payType;
            }
            $services = app()->make(WechatUserServices::class);
            $openid = $services->uidToOpenid($orderInfo['uid'], $userType);
            if (!$openid) {
                throw new ValidateException('获取用户openid失败,无法支付');
            }
        }

        // 3. 生成支付描述
        $site_name = sys_config('site_name');
        $orderInfoServices = app()->make(StoreOrderCartInfoServices::class);
        $body = $orderInfoServices->getCarIdByProductTitle((int)$orderInfo['id']);
        $body = substrUTf8($site_name . '--' . $body, 30);
        $successAction = "product";

        if (!$body) {
            throw new ValidateException('支付参数缺少:请前往后台设置->系统设置-> 填写 网站名称');
        }

        // 4. 发起支付
        return $this->payServices->pay($payType, $openid, $orderInfo['order_id'], 
                                     $orderInfo['pay_price'], $successAction, $body);
    }
}

3.2 微信支付流程

3.2.1 支付宝支付服务 (AliPayService.php)

class AliPayService
{
    protected $config = [
        'appId' => '',
        'merchantPrivateKey' => '', // 应用私钥
        'alipayPublicKey' => '',    // 支付宝公钥
        'notifyUrl' => '',          // 异步通知地址
        'encryptKey' => '',         // AES密钥
    ];

    /**
     * 付款码支付
     */
    public function microPay(string $authCode, string $title, string $orderId, 
                           string $totalAmount, string $passbackParams)
    {
        try {
            $result = Factory::payment()->faceToFace()->optional('passback_params', $passbackParams)
                                         ->pay($title, $orderId, $totalAmount, $authCode);

            // 触发支付创建事件
            $data = [
                "subject" => $title,
                "out_trade_no" => $orderId,
                "total_amount" => $totalAmount,
                "auth_code" => $authCode,
                "scene" => "bar_code",
                "passback_params" => $passbackParams
            ];
            Event::until('pay.create.order', [$data, $orderId, 'alipay']);

            if ($this->response->success($result)) {
                $response = $result->toMap();
                return [
                    'paid' => $response['code'] === '10000' ? 1 : 0,
                    'message' => $response['sub_msg'] ?? '支付成功',
                    'payInfo' => $response
                ];
            } else {
                throw new PayException('失败原因:' . $result->msg . ',' . $result->subMsg);
            }
        } catch (\Exception $e) {
            throw new PayException($e->getMessage());
        }
    }

    /**
     * 创建支付订单
     */
    public function create(string $title, string $orderId, string $totalAmount, 
                         string $passbackParams, string $quitUrl = '', string $siteUrl = '', bool $isCode = false)
    {
        try {
            if ($isCode) {
                // 二维码支付
                $result = Factory::payment()->faceToFace()->optional('passback_params', $passbackParams)
                                           ->precreate($title, $orderId, $totalAmount);
            } else if (request()->isApp()) {
                // APP支付
                $result = Factory::payment()->app()->optional('passback_params', $passbackParams)
                                          ->pay($title, $orderId, $totalAmount);
            } else {
                // H5支付
                $result = Factory::payment()->wap()->optional('passback_params', $passbackParams)
                                           ->pay($title, $orderId, $totalAmount, $quitUrl, $siteUrl);
            }

            Event::until('pay.create.order', [$data, $orderId, 'alipay']);

            if ($this->response->success($result)) {
                return isset($result->body) ? $result->body : $result;
            } else {
                throw new PayException('失败原因:' . $result->msg . ',' . $result->subMsg);
            }
        } catch (\Exception $e) {
            throw new PayException($e->getMessage());
        }
    }

    /**
     * 处理异步回调
     */
    public static function handleNotify()
    {
        return self::instance()->notify(function ($notify) {
            if (isset($notify['out_trade_no'])) {
                $res = Event::until('pay.notify', [$notify, 'aliyun']);
                if ($res) {
                    return $res;
                } else {
                    return false;
                }
            }
        });
    }

    /**
     * 异步回调处理
     */
    public function notify(callable $notifyFn)
    {
        $paramInfo = app()->request->param();
        unset($paramInfo['type']);

        // 商户订单号
        $postOrder['out_trade_no'] = $paramInfo['out_trade_no'] ?? '';
        // 支付宝交易号
        $postOrder['trade_no'] = $paramInfo['trade_no'] ?? '';
        // 交易状态
        $postOrder['trade_status'] = $paramInfo['trade_status'] ?? '';
        // 备注
        $postOrder['attach'] = isset($paramInfo['passback_params']) ? urldecode($paramInfo['passback_params']) : '';

        if (in_array($postOrder['trade_status'], ['TRADE_SUCCESS', 'TRADE_FINISHED']) && $this->verifyNotify($paramInfo)) {
            try {
                if ($notifyFn($postOrder)) {
                    return 'success';
                }
            } catch (\Exception $e) {
                Log::error('支付宝异步会回调成功,执行函数错误。错误单号:' . $postOrder['out_trade_no']);
            }
        }
        return 'fail';
    }
}

3.2.2 微信支付服务

微信支付由 crmeb\services\wechat\Payment 类处理,支持多种支付方式:

  • JSAPI支付 (公众号/小程序)
  • APP支付
  • H5支付
  • Native支付 (扫码支付)
  • Micro支付 (付款码支付)

3.3 余额支付流程

3.3.1 余额支付服务 (YuePayServices.php)

class YuePayServices extends BaseServices
{
    /**
     * 订单余额支付
     */
    public function yueOrderPay(array $orderInfo, int $uid)
    {
        if (!$orderInfo) {
            throw new ValidateException('订单不存在');
        }

        if ($orderInfo['paid']) {
            throw new ValidateException('该订单已支付!');
        }

        $type = 'pay_product';
        if (isset($orderInfo['member_type'])) {
            $type = 'pay_member';
        }

        // 获取用户信息
        $services = app()->make(UserServices::class);
        $userInfo = $services->getUserInfo($uid);

        // 检查余额是否足够
        if ($userInfo['now_money'] < $orderInfo['pay_price']) {
            return ['status' => 'pay_deficiency', 'msg' => '余额不足' . floatval($orderInfo['pay_price'])];
        }

        // 执行支付
        $this->transaction(function () use ($services, $orderInfo, $userInfo, $type) {
            // 扣除用户余额
            $res = false !== $services->bcDec($userInfo['uid'], 'now_money', $orderInfo['pay_price'], 'uid');

            switch ($type) {
                case 'pay_product': // 商品支付
                    $id = $orderInfo['id'] ?? 0;
                    $orderSerives = app()->make(StoreOrderServices::class);
                    $orderInfo = $orderSerives->get($id)->toArray();

                    // 记录余额变动
                    $now_money = bcsub((string)$userInfo['now_money'], (string)$orderInfo['pay_price'], 2);
                    $number = $orderInfo['pay_price'];
                    $userMoneyServices = app()->make(UserMoneyServices::class);
                    $res = $res && $userMoneyServices->income('pay_product', $userInfo['uid'], $number, $now_money, $orderInfo['id']);

                    // 订单支付成功处理
                    $orderServices = app()->make(StoreOrderSuccessServices::class);
                    $res = $res && $orderServices->paySuccess($orderInfo, PayServices::YUE_PAY, ['userInfo' => $userInfo]);
                    break;

                case 'pay_member': // 会员支付
                    $OtherOrderServices = app()->make(OtherOrderServices::class);
                    $res = $res && $OtherOrderServices->paySuccess($orderInfo, PayServices::YUE_PAY, ['userInfo' => $userInfo]);
                    break;
            }

            if (!$res) {
                throw new ValidateException('余额支付失败!');
            }
        });

        return ['status' => true];
    }
}

支付回调处理

4.1 异步回调流程

外部支付平台
      │
      ▼
API回调接口 (/api/pay/notify/{type})
      │
      ▼
支付类型判断 (微信/支付宝)
      │
      ▼
支付验证 (验签)
      │
      ▼
触发事件 (pay.notify)
      │
      ▼
监听器 (PayNotifyListener)
      │
      ▼
回调处理服务 (PayNotifyServices)
      │
      ▼
订单状态更新
      │
      ▼
返回处理结果

4.2 支付回调控制器 (Pay.php)

class Pay
{
    /**
     * 支付回调
     */
    public function notify(string $type)
    {
        switch (urldecode($type)) {
            case 'alipay':
                return AliPayService::handleNotify(); // 支付宝回调
                break;
            case 'routine':
                return Payment::instance()->setAccessEnd(Payment::MINI)->handleNotify(); // 小程序回调
                break;
            case 'wechat':
                return Payment::instance()->setAccessEnd(Payment::WEB)->handleNotify(); // 公众号回调
                break;
            case 'app':
                return Payment::instance()->setAccessEnd(Payment::APP)->handleNotify(); // APP回调
                break;
        }
    }

    /**
     * 退款回调
     */
    public function refund(string $type)
    {
        switch (urldecode($type)) {
            case 'routine':
                return Payment::instance()->setAccessEnd(Payment::MINI)->handleRefundedNotify();
                break;
            case 'wechat':
                return Payment::instance()->setAccessEnd(Payment::WEB)->handleRefundedNotify();
                break;
            case 'app':
                return Payment::instance()->setAccessEnd(Payment::APP)->handleRefundedNotify();
                break;
        }
    }
}

4.3 支付回调监听器 (PayNotifyListener.php)

class PayNotifyListener
{
    public function handle($event)
    {
        [$notify, $type] = $event;

        if (isset($notify['attach']) && $notify['attach']) {
            $outTradeNo = $notify['out_trade_no'];

            if ($notify['attach'] == 'product') { // 购买商品
                // 记录支付原始返回数据
                $orderService = app()->make(StoreOrderSuccessServices::class);
                $orderService->update(['order_id' => $outTradeNo], ['notify_data' => json_encode($notify)]);
            }

            // 处理订单号格式
            if (($count = strpos($notify['out_trade_no'], '_')) !== false) {
                if ($type == 'aliyun') {
                    $notify['trade_no'] = $notify->out_trade_no;
                }
                $notify['out_trade_no'] = substr($notify['out_trade_no'], $count + 1);
            }

            $tradeNo = $type == 'wechat' ? $notify['transaction_id'] : $notify['trade_no'];

            // 调用对应的回调处理方法
            return (new Hook(PayNotifyServices::class, $type))->listen($notify['attach'], $outTradeNo, $tradeNo);
        }

        return false;
    }
}

4.4 支付回调处理服务 (PayNotifyServices.php)

class PayNotifyServices
{
    /**
     * 微信商品支付成功回调
     */
    public function wechatProduct(string $order_id = null, string $trade_no = null)
    {
        try {
            $services = app()->make(StoreOrderSuccessServices::class);
            $orderInfo = $services->getOne(['order_id' => $order_id]);

            if (!$orderInfo) {
                $orderInfo = $services->getOne(['unique' => $order_id]);
                if (!$orderInfo) return true;
            }

            if ($orderInfo->paid) return true; // 已支付

            return $services->paySuccess($orderInfo->toArray(), PayServices::WEIXIN_PAY, ['trade_no' => $trade_no]);
        } catch (\Exception $e) {
            return false;
        }
    }

    /**
     * 微信充值成功回调
     */
    public function wechatUserRecharge(string $order_id = null, string $trade_no = null)
    {
        try {
            $userRecharge = app()->make(UserRechargeServices::class);
            if ($userRecharge->be(['order_id' => $order_id, 'paid' => 1])) return true;
            return $userRecharge->rechargeSuccess($order_id, ['trade_no' => $trade_no]);
        } catch (\Exception $e) {
            return false;
        }
    }

    /**
     * 支付宝商品支付回调
     */
    public function aliyunProduct(string $order_id = null, string $trade_no = null)
    {
        if (!$order_id || !$trade_no) {
            return false;
        }

        try {
            $services = app()->make(StoreOrderSuccessServices::class);
            $orderInfo = $services->getOne(['order_id' => $order_id]);
            if (!$orderInfo) return true;
            if ($orderInfo->paid) return true;

            return $services->paySuccess($orderInfo->toArray(), PayServices::ALIAPY_PAY, ['trade_no' => $trade_no]);
        } catch (\Throwable $e) {
            return false;
        }
    }
}

支付完成处理

5.1 订单支付成功处理 (StoreOrderSuccessServices.php)

class StoreOrderSuccessServices
{
    /**
     * 订单支付成功
     */
    public function paySuccess(array $orderInfo, string $payType, array $extend = [])
    {
        $this->transaction(function () use ($orderInfo, $payType, $extend) {
            $updateData = [
                'paid' => 1,
                'pay_type' => $payType,
                'pay_time' => date('Y-m-d H:i:s'),
                'unique' => $orderInfo['order_id']
            ];

            // 更新订单状态
            $res = $this->dao->update(['id' => $orderInfo['id']], $updateData);

            // 记录支付日志
            $res = $res && $this->addPayLog($orderInfo['order_id'], $payType, $orderInfo['pay_price'], $extend);

            // 触发支付成功事件
            $res = $res && Event::until('order.pay.success', [$orderInfo, $payType, $extend]);

            if (!$res) {
                throw new ValidateException('订单支付成功处理失败');
            }
        });

        return true;
    }
}

支付配置

6.1 支付配置管理

支付相关的配置信息存储在系统配置表中:

  • 微信支付配置:wechat_appid, wechat_mch_id, wechat_key, wechat_cert_pem, wechat_key_pem
  • 支付宝配置:ali_pay_appid, alipay_merchant_private_key, alipay_public_key
  • 支付开关:pay_wechat_open, pay_alipay_open, pay_yue_open, pay_integral_open

6.2 支付配置获取

// 获取微信支付配置
$wechatConfig = SystemConfigService::more([
    'wechat_appid', 'wechat_mch_id', 'wechat_key', 'wechat_cert_pem', 'wechat_key_pem'
]);

// 获取支付宝配置
$alipayConfig = SystemConfigService::more([
    'ali_pay_appid', 'alipay_merchant_private_key', 'alipay_public_key'
]);

支付安全措施

7.1 验签机制

  • 支付宝:使用官方SDK的验签功能
  • 微信:使用微信官方SDK的验签功能

7.2 重复支付防护

  • 通过订单状态检查防止重复支付
  • 使用分布式锁防止并发支付

7.3 支付金额校验

  • 支付前校验订单金额与支付金额一致性
  • 防止篡改支付金额

前端支付集成

8.1 前端支付API

// 发起支付
export function payOrder(orderId, payType) {
    return request.post('pay/order', {
        order_id: orderId,
        pay_type: payType
    });
}

// 余额支付
export function yuePayOrder(orderId) {
    return request.post('pay/yue', {
        order_id: orderId
    });
}

8.2 支付结果处理

前端需要处理不同的支付结果:

  • 微信支付:调用微信JS SDK
  • 支付宝支付:处理支付宝返回的支付信息
  • 余额支付:直接跳转支付成功页面

常见问题处理

9.1 支付失败处理

  1. 检查支付配置是否正确
  2. 检查网络连接
  3. 检查订单状态
  4. 查看支付日志

9.2 异步回调失败

  1. 检查回调地址是否正确
  2. 检查服务器防火墙设置
  3. 检查SSL证书
  4. 查看服务器日志

9.3 订单状态不一致

  1. 检查异步回调是否正常处理
  2. 检查数据库事务是否正确
  3. 检查重复支付防护机制

扩展支付方式

10.1 添加新的支付方式

  1. PayServices 中添加支付方式常量
  2. pay 方法中添加对应处理逻辑
  3. 创建相应的支付服务类
  4. 添加支付回调处理方法
  5. 更新前端支付选择界面

10.2 自定义支付渠道

// 自定义支付服务示例
class CustomPayService
{
    public function pay($orderId, $amount, $extra = [])
    {
        // 实现自定义支付逻辑
        // 调用第三方支付接口
        // 返回支付信息
    }

    public function handleNotify($data)
    {
        // 处理自定义支付回调
        // 验证数据
        // 更新订单状态
        // 返回处理结果
    }
}

事件钩子

系统提供了以下支付相关事件:

  • pay.create.order - 支付订单创建事件
  • pay.notify - 支付回调事件
  • order.pay.success - 订单支付成功事件
  • pay.refund - 支付退款事件

这些事件可以通过监听器进行扩展处理。

{{cateWiki.like_num}}人点赞
0人点赞
评论({{cateWiki.comment_num}}) {{commentWhere.order ? '评论从旧到新':'评论从新到旧'}} {{cateWiki.page_view_num}}人看过该文档
评论(0) {{commentWhere.order ? '评论从旧到新':'评论从新到旧'}} 9人看过该文档
评论
{{item.user ? item.user.nickname : ''}} (自评)
{{item.content}}
{{item.create_time}} 删除
{{item.like ? item.like.like_num : 0}} {{replyIndex == index ? '取消回复' : '回复'}}
评论
{{items.user ? items.user.nickname : '暂无昵称'}} (自评)
{{items.content}}
{{items.create_time}} 删除
{{items.like ? items.like.like_num : 0}} {{replyIndexJ == (index+'|'+indexJ) ? '取消回复' : '回复'}}
评论
目录
  • {{item}}