Skip to content

⚠️ 本文基于 CRMEB 原版整理,EBAOZU 二开部分可能有差异,以实际系统为准。

支付流程全量说明

概述

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

支付方式类型

1 支付方式列表

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

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

weixin

weixinh5

alipay

yue

integral

offline

cash

2 支付场景

  • 商品购买支付 - 购买商品、服务

  • 会员储值支付 - 购买会员卡

  • 余额充值支付 - 用户账户充值

  • 其他支付 - 各种自定义支付场景

支付系统架构

1 核心组件

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

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 # 支付回调控制器

支付流程详解

1 发起支付流程

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('支付方式不存在');
        }
    }
}

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);
    }
}

2 微信支付流程

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';
    }
}

2.2 微信支付服务

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

  • JSAPI支付 (公众号/小程序)

  • APP支付

  • H5支付

  • Native支付 (扫码支付)

  • Micro支付 (付款码支付)

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];
    }
}

支付回调处理

1 异步回调流程

外部支付平台


API回调接口 (/api/pay/notify/{type})


支付类型判断 (微信/支付宝)


支付验证 (验签)


触发事件 (pay.notify)


监听器 (PayNotifyListener)


回调处理服务 (PayNotifyServices)


订单状态更新


返回处理结果

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;
        }
    }
}

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 支付回调处理服务 (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;
        }
    }
}

支付完成处理

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;
    }
}

支付配置

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

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'
]);

支付安全措施

1 验签机制

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

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

2 重复支付防护

  • 通过订单状态检查防止重复支付

  • 使用分布式锁防止并发支付

3 支付金额校验

  • 支付前校验订单金额与支付金额一致性

  • 防止篡改支付金额

前端支付集成

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
    });
}

2 支付结果处理

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

  • 微信支付:调用微信JS SDK

  • 支付宝支付:处理支付宝返回的支付信息

  • 余额支付:直接跳转支付成功页面

常见问题处理

1 支付失败处理

  1. 检查支付配置是否正确

  2. 检查网络连接

  3. 检查订单状态

  4. 查看支付日志

2 异步回调失败

  1. 检查回调地址是否正确

  2. 检查服务器防火墙设置

  3. 检查SSL证书

  4. 查看服务器日志

3 订单状态不一致

  1. 检查异步回调是否正常处理

  2. 检查数据库事务是否正确

  3. 检查重复支付防护机制

扩展支付方式

1 添加新的支付方式

  1. PayServices 中添加支付方式常量

  2. pay 方法中添加对应处理逻辑

  3. 创建相应的支付服务类

  4. 添加支付回调处理方法

  5. 更新前端支付选择界面

2 自定义支付渠道

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

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

事件钩子

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

  • pay.create.order - 支付订单创建事件

  • pay.notify - 支付回调事件

  • order.pay.success - 订单支付成功事件

  • pay.refund - 支付退款事件

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

EBAOZU V4 多门店租赁商城系统文档