{{wikiTitle}}
支付流程全量说明
复制链接
编辑文档
CRMEB PRO 支付流程全量说明
一、支付概述
CRMEB PRO支持多种支付方式,包括微信支付(JSAPI、小程序、APP、H5、Native扫码、付款码)、支付宝(H5、APP、扫码、付款码)、余额支付、积分支付、线下支付等。
1.1 支付架构
用户发起支付
│
▼
┌─────────────────┐
│ PayServices │ ← 支付统一入口
└────────┬────────┘
│
┌────┴────┬────────────┬──────────┐
▼ ▼ ▼ ▼
微信支付 支付宝 余额支付 线下支付
│ │ │ │
▼ ▼ ▼ ▼
Payment AliPayService UserMoney 手动确认
│ │
▼ ▼
微信API 支付宝API
1.2 相关文件位置
# 支付服务
app/services/pay/
├── PayServices.php # 支付统一入口
├── OrderPayServices.php # 订单支付服务
├── RechargePayServices.php # 充值支付服务
├── VipPayServices.php # 会员支付服务
├── PayNotifyServices.php # 支付回调处理
├── PaySuccessServices.php # 支付成功处理
├── WechatRefundServices.php # 微信退款服务
└── AliPayRefundServices.php # 支付宝退款服务
# 核心支付类库
crmeb/services/
├── AliPayService.php # 支付宝服务
└── wechat/
├── Payment.php # 微信支付封装
└── pay/ # 支付相关类
├── PayClient.php # V3支付客户端
└── PayReponse.php # 支付响应处理
# 支付监听器
app/listener/pay/
├── WechatPaySuccessListener.php # 微信支付成功监听
├── AliPaySuccessListener.php # 支付宝成功监听
├── YuePaySuccessListener.php # 余额支付成功监听
└── OfflinePaySuccessListener.php # 线下支付确认监听
二、支付方式详解
2.1 PayServices - 统一支付入口
namespace app\services\pay;
/**
* 支付统一入口
*/
class PayServices
{
// 支付类型常量
const WEIXIN_PAY = 'weixin'; // 微信支付
const INTEGRAL_PAY = 'integral'; // 积分支付
const YUE_PAY = 'yue'; // 余额支付
const OFFLINE_PAY = 'offline'; // 线下支付
const ALIPAY_PAY = 'alipay'; // 支付宝
const CASH_PAY = 'cash'; // 现金支付
// 支付方式映射
const PAY_TYPE = [
self::WEIXIN_PAY => '微信支付',
self::YUE_PAY => '余额支付',
self::OFFLINE_PAY => '线下支付',
self::ALIPAY_PAY => '支付宝',
self::CASH_PAY => '现金支付',
self::INTEGRAL_PAY => '积分支付',
];
/**
* 付款码值(用于扫码支付)
*/
protected string $authCode = '';
/**
* 设置付款码
*/
public function setAuthCode(string $authCode)
{
$this->authCode = $authCode;
return $this;
}
/**
* 发起支付
* @param string $payType 支付类型
* @param string $openid 用户openid
* @param string $orderId 订单号
* @param string $price 支付金额
* @param string $successAction 成功回调标识
* @param string $body 商品描述
* @param bool $isCode 是否返回二维码
*/
public function pay(
string $payType,
string $openid,
string $orderId,
string $price,
string $successAction,
string $body,
bool $isCode = false
) {
$body = filter_emoji($body);
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 {
if (Payment::instance()->isV3PAy) {
return Payment::instance()->payClient()->miniprogPay($openid, $orderId, $price, $body, $successAction);
}
return Payment::jsPay($openid, $orderId, $price, $successAction, $body);
}
}
case 'weixinh5':
// 微信H5支付
if (Payment::instance()->isV3PAy) {
return Payment::instance()->payClient()->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 {
if (request()->isApp()) {
return Payment::appPay($openid, $orderId, $price, $successAction, $body);
} else {
if (Payment::instance()->isV3PAy) {
return Payment::instance()->payClient()->jsapiPay($openid, $orderId, $price, $body, $successAction);
}
return Payment::jsPay($openid, $orderId, $price, $successAction, $body);
}
}
case self::ALIPAY_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':
case 'store':
// PC扫码支付
return Payment::nativePay($openid, $orderId, $price, $successAction, $body);
default:
throw new ValidateException('支付方式不存在');
}
}
}
三、微信支付流程
3.1 微信支付配置
// 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', ''),
],
// 支付配置
'payment' => [
'app_id' => env('WECHAT_PAYMENT_APPID', ''),
'mch_id' => env('WECHAT_PAYMENT_MCH_ID', ''),
'key' => env('WECHAT_PAYMENT_KEY', ''), // V2密钥
'apiv3_key' => env('WECHAT_PAYMENT_APIV3_KEY', ''), // V3密钥
'cert_path' => env('WECHAT_PAYMENT_CERT_PATH', ''), // 证书路径
'key_path' => env('WECHAT_PAYMENT_KEY_PATH', ''), // 证书密钥
'notify_url' => env('WECHAT_PAYMENT_NOTIFY_URL', ''), // 回调地址
],
];
3.2 JSAPI支付流程
/**
* 订单支付(微信公众号/小程序)
*/
public function orderPay(int $orderId, int $uid, string $payType = 'weixin')
{
// 1. 获取订单信息
$orderInfo = $this->orderServices->get($orderId);
if (!$orderInfo) {
throw new ValidateException('订单不存在');
}
// 2. 验证订单状态
if ($orderInfo['paid'] == 1) {
throw new ValidateException('订单已支付');
}
// 3. 获取用户OpenID
$userInfo = $this->userServices->get($uid);
$openid = $userInfo['openid'] ?? '';
// 4. 生成支付参数
$payPrice = $orderInfo['pay_price'];
$orderId = $orderInfo['order_id'];
$body = '订单支付-' . $orderId;
// 5. 调用支付
/** @var PayServices $payServices */
$payServices = app()->make(PayServices::class);
$result = $payServices->pay(
$payType, // 支付类型
$openid, // 用户OpenID
$orderId, // 订单号
(string)$payPrice, // 支付金额
'order', // 回调类型标识
$body // 商品描述
);
return $result;
}
3.3 微信支付类库
namespace crmeb\services\wechat;
use EasyWeChat\Factory;
/**
* 微信支付封装
*/
class Payment
{
/**
* 是否V3支付
*/
public bool $isV3PAy = false;
/**
* 获取支付实例
*/
public static function instance(): self
{
$instance = new self();
$instance->isV3PAy = sys_config('pay_wechat_type', 0) == 1;
return $instance;
}
/**
* JSAPI支付(公众号)
*/
public static function jsPay(string $openid, string $orderId, string $price, string $notify, string $body): array
{
$payment = self::application()->payment;
$result = $payment->order->unify([
'body' => $body,
'out_trade_no' => $orderId,
'total_fee' => bcmul($price, '100', 0),
'notify_url' => self::getNotifyUrl($notify),
'trade_type' => 'JSAPI',
'openid' => $openid,
]);
if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
return $payment->jssdk->bridgeConfig($result['prepay_id'], false);
}
throw new PayException($result['return_msg'] ?? '支付失败');
}
/**
* 小程序支付
*/
public static function miniPay(string $openid, string $orderId, string $price, string $notify, string $body): array
{
$miniProgram = MiniProgram::application();
$result = $miniProgram->payment->unify([
'body' => $body,
'out_trade_no' => $orderId,
'total_fee' => bcmul($price, '100', 0),
'notify_url' => self::getNotifyUrl($notify),
'trade_type' => 'JSAPI',
'openid' => $openid,
]);
if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
return $miniProgram->payment->jssdk->bridgeConfig($result['prepay_id'], false);
}
throw new PayException($result['return_msg'] ?? '支付失败');
}
/**
* APP支付
*/
public static function appPay(string $openid, string $orderId, string $price, string $notify, string $body): array
{
$payment = self::application()->payment;
$result = $payment->order->unify([
'body' => $body,
'out_trade_no' => $orderId,
'total_fee' => bcmul($price, '100', 0),
'notify_url' => self::getNotifyUrl($notify),
'trade_type' => 'APP',
]);
if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
return $payment->jssdk->appConfig($result['prepay_id']);
}
throw new PayException($result['return_msg'] ?? '支付失败');
}
/**
* H5支付
*/
public static function h5Pay(string $orderId, string $price, string $notify, string $body): string
{
$payment = self::application()->payment;
$result = $payment->order->unify([
'body' => $body,
'out_trade_no' => $orderId,
'total_fee' => bcmul($price, '100', 0),
'notify_url' => self::getNotifyUrl($notify),
'trade_type' => 'MWEB',
'scene_info' => json_encode([
'h5_info' => [
'type' => 'Wap',
'wap_url' => sys_config('site_url'),
'wap_name' => sys_config('site_name'),
]
])
]);
if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
return $result['mweb_url'];
}
throw new PayException($result['return_msg'] ?? '支付失败');
}
/**
* Native扫码支付
*/
public static function nativePay(string $openid, string $orderId, string $price, string $notify, string $body): string
{
$payment = self::application()->payment;
$result = $payment->order->unify([
'body' => $body,
'out_trade_no' => $orderId,
'total_fee' => bcmul($price, '100', 0),
'notify_url' => self::getNotifyUrl($notify),
'trade_type' => 'NATIVE',
'product_id' => $orderId,
]);
if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
return $result['code_url']; // 返回二维码链接
}
throw new PayException($result['return_msg'] ?? '支付失败');
}
/**
* 付款码支付(B扫C)
*/
public static function microPay(string $authCode, string $orderId, string $price, string $notify, string $body): array
{
$payment = self::application()->payment;
$result = $payment->pay([
'body' => $body,
'out_trade_no' => $orderId,
'total_fee' => bcmul($price, '100', 0),
'auth_code' => $authCode,
]);
return $result;
}
/**
* 生成回调地址
*/
protected static function getNotifyUrl(string $notify): string
{
$siteUrl = sys_config('site_url');
return $siteUrl . '/api/pay/notify/wechat/' . $notify;
}
}
3.4 微信V3支付
namespace crmeb\services\wechat\pay;
/**
* 微信V3支付客户端
*/
class PayClient
{
/**
* JSAPI支付
*/
public function jsapiPay(string $openid, string $orderId, string $price, string $body, string $notify): array
{
$params = [
'appid' => $this->appId,
'mchid' => $this->mchId,
'description' => $body,
'out_trade_no' => $orderId,
'notify_url' => $this->getNotifyUrl($notify),
'amount' => [
'total' => (int)bcmul($price, '100', 0),
'currency' => 'CNY',
],
'payer' => [
'openid' => $openid,
],
];
$result = $this->request('POST', '/v3/pay/transactions/jsapi', $params);
return $this->getJsConfig($result['prepay_id']);
}
/**
* 小程序支付
*/
public function miniprogPay(string $openid, string $orderId, string $price, string $body, string $notify): array
{
// 逻辑与jsapiPay类似,使用小程序appid
}
/**
* H5支付
*/
public function h5Pay(string $orderId, string $price, string $body, string $notify): string
{
$params = [
'appid' => $this->appId,
'mchid' => $this->mchId,
'description' => $body,
'out_trade_no' => $orderId,
'notify_url' => $this->getNotifyUrl($notify),
'amount' => [
'total' => (int)bcmul($price, '100', 0),
'currency' => 'CNY',
],
'scene_info' => [
'payer_client_ip' => request()->ip(),
'h5_info' => [
'type' => 'Wap',
],
],
];
$result = $this->request('POST', '/v3/pay/transactions/h5', $params);
return $result['h5_url'];
}
}
四、支付宝支付流程
4.1 支付宝配置
// config/pay.php
return [
'alipay' => [
'app_id' => env('ALIPAY_APP_ID', ''),
'private_key' => env('ALIPAY_PRIVATE_KEY', ''),
'public_key' => env('ALIPAY_PUBLIC_KEY', ''),
'notify_url' => env('ALIPAY_NOTIFY_URL', ''),
'return_url' => env('ALIPAY_RETURN_URL', ''),
],
];
4.2 AliPayService - 支付宝服务
namespace crmeb\services;
use Alipay\EasySDK\Kernel\Factory;
use Alipay\EasySDK\Kernel\Config;
/**
* 支付宝支付服务
*/
class AliPayService
{
protected static $instance;
/**
* 获取实例
*/
public static function instance(): self
{
if (!self::$instance) {
self::$instance = new self();
self::$instance->initialize();
}
return self::$instance;
}
/**
* 初始化SDK
*/
protected function initialize()
{
$options = new Config();
$options->protocol = 'https';
$options->gatewayHost = 'openapi.alipay.com';
$options->signType = 'RSA2';
$options->appId = sys_config('alipay_app_id');
$options->merchantPrivateKey = sys_config('alipay_private_key');
$options->alipayPublicKey = sys_config('alipay_public_key');
$options->notifyUrl = sys_config('site_url') . '/api/pay/notify/alipay';
Factory::setOptions($options);
}
/**
* 创建支付
* @param string $body 商品描述
* @param string $orderId 订单号
* @param string $price 金额
* @param string $notify 回调标识
* @param string $passback 回传参数
* @param string $returnUrl 同步回调地址
* @param bool $isCode 是否返回二维码
*/
public function create(
string $body,
string $orderId,
string $price,
string $notify,
string $passback = '',
string $returnUrl = '',
bool $isCode = false
) {
$notifyUrl = sys_config('site_url') . '/api/pay/notify/alipay/' . $notify;
// 判断客户端类型
if (request()->isApp()) {
// APP支付
return $this->appPay($body, $orderId, $price, $notifyUrl);
} elseif ($isCode) {
// 扫码支付
return $this->qrPay($body, $orderId, $price, $notifyUrl);
} else {
// H5支付
return $this->wapPay($body, $orderId, $price, $notifyUrl, $returnUrl);
}
}
/**
* H5支付(手机网站支付)
*/
public function wapPay(string $body, string $orderId, string $price, string $notifyUrl, string $returnUrl): string
{
$result = Factory::payment()->wap()->pay(
$body, // 商品描述
$orderId, // 订单号
$price, // 金额
$returnUrl, // 同步回调
$notifyUrl // 异步回调
);
return $result->body; // 返回表单HTML
}
/**
* APP支付
*/
public function appPay(string $body, string $orderId, string $price, string $notifyUrl): string
{
$result = Factory::payment()->app()->pay(
$body,
$orderId,
$price,
$notifyUrl
);
return $result->body; // 返回调起支付的参数字符串
}
/**
* 扫码支付
*/
public function qrPay(string $body, string $orderId, string $price, string $notifyUrl): string
{
$result = Factory::payment()->faceToFace()->precreate(
$body,
$orderId,
$price
);
return $result->qrCode; // 返回二维码链接
}
/**
* 付款码支付(当面付)
*/
public function microPay(string $authCode, string $body, string $orderId, string $price, string $notify): array
{
$result = Factory::payment()->faceToFace()->pay(
$body,
$orderId,
$price,
$authCode
);
return [
'code' => $result->code,
'msg' => $result->msg,
'trade_no' => $result->tradeNo ?? '',
];
}
/**
* 退款
*/
public function refund(string $orderId, string $refundNo, float $refundAmount, string $reason = ''): array
{
$result = Factory::payment()->common()->refund(
$orderId,
(string)$refundAmount
);
return [
'code' => $result->code,
'msg' => $result->msg,
'fund_change' => $result->fundChange ?? '',
];
}
/**
* 查询订单
*/
public function query(string $orderId): array
{
$result = Factory::payment()->common()->query($orderId);
return (array)$result;
}
}
五、支付回调处理
5.1 回调路由配置
// route/api.php
Route::group('pay/notify', function() {
// 微信支付回调
Route::rule('wechat/:type', 'api.pay.PayNotify/wechat', 'GET|POST');
// 支付宝回调
Route::rule('alipay/:type', 'api.pay.PayNotify/alipay', 'GET|POST');
});
5.2 回调控制器
namespace app\controller\api\pay;
/**
* 支付回调控制器
*/
class PayNotify
{
/**
* 微信支付回调
*/
public function wechat(string $type)
{
/** @var PayNotifyServices $payNotifyServices */
$payNotifyServices = app()->make(PayNotifyServices::class);
return $payNotifyServices->wechatNotify($type);
}
/**
* 支付宝回调
*/
public function alipay(string $type)
{
/** @var PayNotifyServices $payNotifyServices */
$payNotifyServices = app()->make(PayNotifyServices::class);
return $payNotifyServices->alipayNotify($type);
}
}
5.3 回调处理服务
namespace app\services\pay;
/**
* 支付回调处理服务
*/
class PayNotifyServices
{
/**
* 微信支付回调处理
*/
public function wechatNotify(string $type)
{
try {
// 验证签名并获取数据
$data = Payment::instance()->handleNotify();
if ($data['result_code'] != 'SUCCESS') {
return $this->failResponse('支付失败');
}
$orderId = $data['out_trade_no'];
$transactionId = $data['transaction_id'];
$payPrice = bcdiv($data['total_fee'], '100', 2);
// 根据回调类型处理
switch ($type) {
case 'order':
// 订单支付回调
$this->orderPaySuccess($orderId, $transactionId, $payPrice);
break;
case 'recharge':
// 充值回调
$this->rechargePaySuccess($orderId, $transactionId, $payPrice);
break;
case 'vip':
// 会员购买回调
$this->vipPaySuccess($orderId, $transactionId, $payPrice);
break;
}
return $this->successResponse();
} catch (\Throwable $e) {
Log::error('微信支付回调处理失败: ' . $e->getMessage());
return $this->failResponse($e->getMessage());
}
}
/**
* 支付宝回调处理
*/
public function alipayNotify(string $type)
{
try {
// 验证签名
$data = request()->post();
$verify = AliPayService::instance()->verifyNotify($data);
if (!$verify) {
return 'fail';
}
// 检查支付状态
if (!in_array($data['trade_status'], ['TRADE_SUCCESS', 'TRADE_FINISHED'])) {
return 'success'; // 非成功状态也返回成功,避免重复通知
}
$orderId = $data['out_trade_no'];
$transactionId = $data['trade_no'];
$payPrice = $data['total_amount'];
// 根据类型处理
switch ($type) {
case 'order':
$this->orderPaySuccess($orderId, $transactionId, $payPrice);
break;
// ... 其他类型
}
return 'success';
} catch (\Throwable $e) {
Log::error('支付宝回调处理失败: ' . $e->getMessage());
return 'fail';
}
}
/**
* 订单支付成功处理
*/
protected function orderPaySuccess(string $orderId, string $transactionId, string $payPrice)
{
/** @var PaySuccessServices $paySuccessServices */
$paySuccessServices = app()->make(PaySuccessServices::class);
$paySuccessServices->orderPaySuccess($orderId, [
'transaction_id' => $transactionId,
'pay_price' => $payPrice,
'pay_type' => 'weixin',
'pay_time' => time(),
]);
}
/**
* 成功响应
*/
protected function successResponse(): string
{
return '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
}
/**
* 失败响应
*/
protected function failResponse(string $msg): string
{
return '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[' . $msg . ']]></return_msg></xml>';
}
}
5.4 支付成功处理服务
namespace app\services\pay;
/**
* 支付成功处理服务
*/
class PaySuccessServices
{
/**
* 订单支付成功
*/
public function orderPaySuccess(string $orderId, array $payInfo)
{
// 1. 获取订单
$orderInfo = $this->orderServices->getByOrderId($orderId);
if (!$orderInfo || $orderInfo['paid'] == 1) {
return false;
}
// 2. 使用事务更新
Db::transaction(function() use ($orderInfo, $payInfo) {
// 更新订单状态
$this->orderServices->update($orderInfo['id'], [
'paid' => 1,
'pay_type' => $payInfo['pay_type'],
'pay_price' => $payInfo['pay_price'],
'pay_time' => $payInfo['pay_time'],
'trade_no' => $payInfo['transaction_id'],
'status' => 1, // 待发货
]);
// 3. 扣减库存
$this->productServices->decStock($orderInfo);
// 4. 增加销量
$this->productServices->incSales($orderInfo);
// 5. 分销处理
$this->agentServices->spreadCommission($orderInfo);
// 6. 积分赠送
$this->userServices->giveIntegral($orderInfo);
});
// 7. 触发支付成功事件
event('order.pay.success', [$orderInfo]);
// 8. 发送通知
$this->sendPayNotify($orderInfo);
return true;
}
/**
* 发送支付成功通知
*/
protected function sendPayNotify($orderInfo)
{
// 发送模板消息
// 发送短信通知
// 发送邮件通知
}
}
六、余额支付
6.1 余额支付流程
namespace app\services\pay;
/**
* 余额支付服务
*/
class YuePayServices
{
/**
* 余额支付
*/
public function pay(int $uid, string $orderId, float $payPrice): bool
{
// 1. 获取用户余额
$userInfo = $this->userServices->get($uid);
if (!$userInfo) {
throw new ValidateException('用户不存在');
}
$balance = (float)$userInfo['now_money'];
if ($balance < $payPrice) {
throw new ValidateException('余额不足');
}
// 2. 扣减余额
Db::transaction(function() use ($uid, $orderId, $payPrice, $balance) {
// 扣减用户余额
$this->userServices->update($uid, [
'now_money' => bcsub($balance, $payPrice, 2)
]);
// 记录账单
$this->userBillServices->create([
'uid' => $uid,
'type' => 'pay_money',
'title' => '购买商品',
'number' => $payPrice,
'balance' => bcsub($balance, $payPrice, 2),
'link_id' => $orderId,
'pm' => 0, // 支出
'mark' => '余额支付订单' . $orderId,
]);
});
// 3. 处理支付成功
/** @var PaySuccessServices $paySuccessServices */
$paySuccessServices = app()->make(PaySuccessServices::class);
$paySuccessServices->orderPaySuccess($orderId, [
'transaction_id' => 'yue_' . $orderId,
'pay_price' => $payPrice,
'pay_type' => 'yue',
'pay_time' => time(),
]);
return true;
}
}
七、退款流程
7.1 微信退款
namespace app\services\pay;
/**
* 微信退款服务
*/
class WechatRefundServices
{
/**
* 发起退款
* @param string $orderId 原订单号
* @param string $refundNo 退款单号
* @param float $totalFee 订单金额
* @param float $refundFee 退款金额
* @param string $reason 退款原因
*/
public function refund(string $orderId, string $refundNo, float $totalFee, float $refundFee, string $reason = ''): array
{
// V3接口退款
if (Payment::instance()->isV3PAy) {
return $this->v3Refund($orderId, $refundNo, $totalFee, $refundFee, $reason);
}
// V2接口退款
$payment = Payment::application()->payment;
$result = $payment->refund->byOutTradeNumber(
$orderId, // 原订单号
$refundNo, // 退款单号
bcmul($totalFee, '100', 0), // 订单总金额(分)
bcmul($refundFee, '100', 0), // 退款金额(分)
[
'refund_desc' => $reason ?: '用户申请退款',
]
);
if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
return [
'status' => true,
'refund_id' => $result['refund_id'],
];
}
return [
'status' => false,
'msg' => $result['err_code_des'] ?? $result['return_msg'] ?? '退款失败',
];
}
/**
* V3退款
*/
protected function v3Refund(string $orderId, string $refundNo, float $totalFee, float $refundFee, string $reason): array
{
$params = [
'out_trade_no' => $orderId,
'out_refund_no' => $refundNo,
'reason' => $reason ?: '用户申请退款',
'amount' => [
'refund' => (int)bcmul($refundFee, '100', 0),
'total' => (int)bcmul($totalFee, '100', 0),
'currency' => 'CNY',
],
];
return Payment::instance()->payClient()->refund($params);
}
}
7.2 支付宝退款
namespace app\services\pay;
/**
* 支付宝退款服务
*/
class AliPayRefundServices
{
/**
* 发起退款
*/
public function refund(string $orderId, string $refundNo, float $refundFee, string $reason = ''): array
{
$result = AliPayService::instance()->refund(
$orderId,
$refundNo,
$refundFee,
$reason
);
if ($result['code'] == '10000') {
return [
'status' => true,
'refund_id' => $result['trade_no'] ?? '',
];
}
return [
'status' => false,
'msg' => $result['msg'] ?? '退款失败',
];
}
}
八、完整支付流程图
用户下单
│
▼
创建订单 (StoreOrderCreateServices)
│
▼
选择支付方式
│
├─── 微信支付 ────────────────────┐
│ │
│ ┌─────────────────────────────┐│
│ │ 1. 调用PayServices::pay() ││
│ │ 2. 调用Payment::jsPay() ││
│ │ 3. 返回支付参数给前端 ││
│ │ 4. 前端调起微信支付 ││
│ │ 5. 微信回调notify ││
│ │ 6. PayNotifyServices处理 ││
│ │ 7. PaySuccessServices完成 ││
│ └─────────────────────────────┘│
│ │
├─── 支付宝 ──────────────────────┤
│ │
│ ┌─────────────────────────────┐│
│ │ 1. 调用AliPayService ││
│ │ 2. 返回支付表单/链接 ││
│ │ 3. 跳转支付宝完成支付 ││
│ │ 4. 支付宝回调notify ││
│ │ 5. PaySuccessServices完成 ││
│ └─────────────────────────────┘│
│ │
├─── 余额支付 ────────────────────┤
│ │
│ ┌─────────────────────────────┐│
│ │ 1. 检查余额是否充足 ││
│ │ 2. 扣减用户余额 ││
│ │ 3. 记录账单流水 ││
│ │ 4. PaySuccessServices完成 ││
│ └─────────────────────────────┘│
│ │
└─── 线下支付 ────────────────────┘
│
┌─────────────────────────────┐│
│ 1. 订单创建,状态为待支付 ││
│ 2. 后台管理员确认支付 ││
│ 3. PaySuccessServices完成 ││
└─────────────────────────────┘│
│
▼
支付成功处理
│
┌─────────────────────────────────┼─────────────────────────────────┐
│ │ │
▼ ▼ ▼
更新订单状态 触发事件 发送通知
(paid=1, status=1) event('order.pay.success') (微信/短信/邮件)
│ │
▼ ▼
扣减库存 分销处理
增加销量 积分赠送
九、二次开发注意事项
9.1 新增支付方式
// 1. 在PayServices中添加常量和方法
const NEW_PAY = 'newpay';
// 2. 在pay()方法中添加分支
case self::NEW_PAY:
return $this->newPayService->pay(...);
// 3. 创建对应的支付服务类
// 4. 添加回调路由和处理方法
9.2 支付安全注意事项
- 回调验签 - 必须验证支付平台的签名
- 金额校验 - 回调金额与订单金额必须一致
- 幂等处理 - 防止重复处理同一笔支付
- 日志记录 - 完整记录支付流程日志
- 异常处理 - 捕获并记录所有异常
注意事项
- 支付配置安全:微信支付密钥、支付宝私钥等敏感信息不要提交到代码仓库,使用环境变量或独立配置文件
- 微信支付版本:系统同时支持微信支付 V2 和 V3 版本,新项目建议使用 V3 版本,安全性更高
- 回调地址配置:支付回调地址必须是 HTTPS 且外网可访问,确保在微信/支付宝后台正确配置
- 金额单位:微信支付金额单位是分,支付宝是元,系统内部统一使用元,转换时注意精度
- 退款限制:退款金额不能超过原支付金额,退款后资金原路返回到用户账户
- 订单超时:订单支付有时间限制,超时未支付的订单会被自动关闭,需要配置定时任务处理
- 并发支付:同一订单可能存在多种支付方式同时发起的情况,使用数据库锁或 Redis 锁避免重复支付
- 余额支付安全:余额支付需要验证支付密码,扣款前必须检查余额是否充足
- 支付日志:完整记录支付请求和回调日志,便于问题排查和对账
常见问题
Q: 微信支付提示”签名错误”?
A: 检查以下几点:1) API 密钥是否正确;2) 签名算法是否匹配(V2 用 MD5/HMAC-SHA256,V3 用 RSA);3) 参与签名的参数顺序是否正确Q: 支付成功但订单状态未更新?
A: 检查回调处理:1) 确认回调地址正确且可访问;2) 检查回调日志是否收到通知;3) 确认PayNotifyServices中的处理逻辑是否正常执行Q: 如何处理支付回调重复通知?
A: 在notify方法中首先检查订单是否已支付(paid字段),如果已支付直接返回成功,不再重复处理Q: H5 支付跳转失败?
A: H5 支付对域名有限制,需要在微信商户平台配置 H5 支付域名,且用户必须在微信外的浏览器中打开Q: 余额支付如何设置支付密码?
A: 用户在个人中心设置支付密码,系统使用加密存储。支付时调用checkPayPwd方法验证密码Q: 如何对接新的支付渠道(如银联)?
A: 参考现有支付类的实现,创建新的支付服务类继承基类,实现pay、notify、refund等方法,然后在PayServices中注册
评论({{cateWiki.comment_num}})
最新
最早
{{cateWiki.page_view_num}}人看过该文档
评论(0)
最新
最早
221人看过
登录/注册
即可发表评论
{{item.user ? item.user.nickname : ''}}
(自评)
{{item.content}}
搜索结果
为您找到{{wikiCount}}条结果
{{item.page_view_num}}
{{item.like ? item.like.like_num : 0}}
{{item.comment ? item.comment.comment_num : 0}}
位置:
{{path.name}}
{{(i+1) == getCataloguePathData(item.catalogue).length ? '':'/'}}
