本文目录导读:

我来为你详细讲解支付宝支付在PHP中的完整流程。
支付流程概述
基本流程图
用户下单 → 生成订单 → 请求支付宝 → 支付宝处理 → 同步返回 → 异步通知 → 查询验证
环境准备
安装SDK
composer require alipay/alipay-sdk-php
配置文件
// config/alipay.php
return [
'app_id' => '你的应用ID',
'private_key' => '你的应用私钥',
'alipay_public_key' => '支付宝公钥',
'notify_url' => 'https://your-domain.com/pay/notify',
'return_url' => 'https://your-domain.com/pay/return',
'sign_type' => 'RSA2',
'gateway' => 'https://openapi.alipay.com/gateway.do',
'charset' => 'UTF-8'
];
完整实现代码
支付控制器类
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Alipay\EasySDK\Kernel\Factory;
use Alipay\EasySDK\Kernel\Config;
class AlipayController extends Controller
{
protected $alipayConfig;
public function __construct()
{
$this->alipayConfig = config('alipay');
$this->initAlipay();
}
/**
* 初始化支付宝
*/
private function initAlipay()
{
$config = new Config();
$config->protocol = 'https';
$config->gatewayHost = 'openapi.alipay.com';
$config->signType = $this->alipayConfig['sign_type'];
$config->appId = $this->alipayConfig['app_id'];
$config->merchantPrivateKey = $this->alipayConfig['private_key'];
$config->alipayPublicKey = $this->alipayConfig['alipay_public_key'];
Factory::setOptions($config);
}
/**
* 生成支付二维码(PC端)
*/
public function pagePay(Request $request)
{
$orderId = $request->input('order_id');
$amount = $request->input('amount');
$subject = $request->input('subject');
try {
// 创建支付请求
$result = Factory::payment()->page()->pay(
$subject, // 订单标题
$orderId, // 商户订单号
$amount, // 订单金额
$this->alipayConfig['notify_url'], // 异步通知地址
$this->alipayConfig['return_url'] // 同步跳转地址
);
// 跳转到支付宝页面
return redirect($result->body);
} catch (\Exception $e) {
return response()->json([
'code' => 500,
'message' => '创建支付失败: ' . $e->getMessage()
]);
}
}
/**
* APP支付
*/
public function appPay(Request $request)
{
$orderId = $request->input('order_id');
$amount = $request->input('amount');
$subject = $request->input('subject');
try {
// 创建APP支付
$result = Factory::payment()->app()->pay(
$subject,
$orderId,
$amount,
$this->alipayConfig['notify_url']
);
// 返回给客户端调用SDK拉起支付
return response()->json([
'code' => 0,
'data' => [
'order_str' => $result->body
],
'message' => 'success'
]);
} catch (\Exception $e) {
return response()->json([
'code' => 500,
'message' => '创建支付失败: ' . $e->getMessage()
]);
}
}
}
异步通知处理
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class AlipayNotifyController extends Controller
{
/**
* 支付宝异步通知
*/
public function notify(Request $request)
{
$params = $request->all();
// 日志记录,方便调试
Log::info('支付宝异步通知', $params);
// 验证签名和处理业务
$result = app('alipay')->verify($params);
if ($result) {
// 检查商户订单号
$orderId = $params['out_trade_no'];
$tradeNo = $params['trade_no'];
$tradeStatus = $params['trade_status'];
// 处理业务逻辑
$order = Order::where('order_no', $orderId)->first();
if ($order && $order->status == 'pending') {
if ($tradeStatus == 'TRADE_SUCCESS' || $tradeStatus == 'TRADE_FINISHED') {
// 验证金额
if (bccomp($order->amount, $params['total_amount'], 2) === 0) {
// 更新订单状态
$order->status = 'paid';
$order->transaction_id = $tradeNo;
$order->paid_at = now();
$order->save();
// 执行后续业务逻辑(如发货、增加会员等)
$this->afterPaymentSuccess($order);
}
}
}
// 只有返回"success"才表示处理成功
return 'success';
}
return 'fail';
}
/**
* 支付成功后的业务处理
*/
private function afterPaymentSuccess($order)
{
// 更新库存
// 发送邮件通知
// 生成发票等
// 示例:记录支付日志
PaymentLog::create([
'order_id' => $order->id,
'transaction_id' => $order->transaction_id,
'amount' => $order->amount,
'status' => 'success',
'paid_at' => now()
]);
}
}
同步返回处理
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class AlipayReturnController extends Controller
{
/**
* 支付完成后的页面跳转
*/
public function return(Request $request)
{
$params = $request->all();
// 验证签名
$result = app('alipay')->verify($params);
if (!$result) {
return redirect('/order/fail')->with('error', '支付验证失败');
}
$orderId = $params['out_trade_no'];
$tradeStatus = $params['trade_status'];
// 获取订单信息
$order = Order::where('order_no', $orderId)->first();
// 查询订单状态
if ($tradeStatus == 'TRADE_SUCCESS') {
// 订单支付成功
return redirect('/order/success')->with([
'order_no' => $orderId,
'trade_no' => $params['trade_no']
]);
}
return redirect('/order/fail');
}
}
订单查询
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Alipay\EasySDK\Kernel\Factory;
class OrderQueryController extends Controller
{
/**
* 主动查询订单状态
*/
public function queryOrder(Request $request)
{
$orderId = $request->input('order_no');
try {
$result = Factory::payment()->common()->query($orderId);
if ($result->tradeStatus == 'TRADE_SUCCESS') {
// 支付成功,处理业务逻辑
return response()->json([
'code' => 0,
'data' => [
'order_no' => $result->outTradeNo,
'trade_no' => $result->tradeNo,
'total_amount' => $result->totalAmount,
'status' => 'success'
]
]);
}
return response()->json([
'code' => 1,
'message' => '订单未支付'
]);
} catch (\Exception $e) {
return response()->json([
'code' => 500,
'message' => '查询失败: ' . $e->getMessage()
]);
}
}
}
退款功能
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Alipay\EasySDK\Kernel\Factory;
class RefundController extends Controller
{
/**
* 发起退款
*/
public function refund(Request $request)
{
$orderId = $request->input('order_no');
$refundAmount = $request->input('refund_amount');
$refundReason = $request->input('refund_reason', '用户申请退款');
// 生成退款单号
$refundNo = 'REFUND_' . date('YmdHis') . rand(1000, 9999);
try {
$result = Factory::payment()->common()->refund(
$orderId, // 商户订单号
$refundNo, // 退款单号
$refundAmount, // 退款金额
$refundReason // 退款原因
);
// 更新退款信息
if ($result->code == '10000') {
RefundRecord::create([
'order_no' => $orderId,
'refund_no' => $refundNo,
'refund_amount' => $refundAmount,
'status' => 'success',
'refund_time' => now()
]);
return response()->json([
'code' => 0,
'message' => '退款成功',
'data' => [
'refund_no' => $refundNo
]
]);
}
} catch (\Exception $e) {
// 处理退款异常
Log::error('退款失败', [
'order_no' => $orderId,
'refund_no' => $refundNo,
'error' => $e->getMessage()
]);
return response()->json([
'code' => 500,
'message' => '退款失败: ' . $e->getMessage()
]);
}
}
}
前台订单控制器
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class OrderController extends Controller
{
/**
* 下单并支付
*/
public function createAndPay(Request $request)
{
$validator = Validator::make($request->all(), [
'product_id' => 'required|exists:products,id',
'quantity' => 'required|integer|min:1'
]);
if ($validator->fails()) {
return response()->json([
'code' => 400,
'message' => $validator->errors()->first()
]);
}
// 创建订单
$product = Product::find($request->product_id);
$order = Order::create([
'order_no' => 'ORDER_' . date('YmdHis') . rand(1000, 9999),
'product_id' => $product->id,
'quantity' => $request->quantity,
'amount' => $product->price * $request->quantity,
'status' => 'pending',
'created_at' => now()
]);
// 调用支付宝支付
$payController = new AlipayController();
// 如果是PC端
return app(AlipayController::class)->pagePay(new Request([
'order_id' => $order->order_no,
'amount' => $order->amount,
'subject' => $product->name
]));
}
}
路由配置
// routes/web.php
Route::post('/pay/alipay/page', 'AlipayController@pagePay')->name('pay.alipay.page');
Route::post('/pay/alipay/app', 'AlipayController@appPay')->name('pay.alipay.app');
Route::post('/pay/alipay/notify', 'AlipayNotifyController@notify')->name('pay.alipay.notify');
Route::get('/pay/alipay/return', 'AlipayReturnController@return')->name('pay.alipay.return');
Route::post('/order/query', 'OrderQueryController@queryOrder')->name('order.query');
Route::post('/order/refund', 'RefundController@refund')->name('order.refund');
// routes/api.php (APP使用)
Route::post('/pay/alipay/app-pay', 'AlipayController@appPay');
安全注意事项
签名验证
/**
* 安全验证
*/
private function verifyPay($params)
{
try {
// 验证签名
$result = app('alipay')->verify($params);
if (!$result) {
Log::warning('支付宝验证失败', $params);
return false;
}
// 检查应用ID是否匹配
if ($params['app_id'] != config('alipay.app_id')) {
Log::error('应用ID不匹配');
return false;
}
// 检查商户订单号是否存在
$order = Order::where('order_no', $params['out_trade_no'])->first();
if (!$order) {
Log::error('订单不存在: ' . $params['out_trade_no']);
return false;
}
// 检查金额是否一致
if (bccomp($order->amount, $params['total_amount'], 2) !== 0) {
Log::error('金额不匹配', [
'order_amount' => $order->amount,
'paid_amount' => $params['total_amount']
]);
return false;
}
// 检查是否处理过该订单
if ($order->status == 'paid') {
Log::info('订单已处理');
return ['status' => 'processed'];
}
return [
'status' => 'valid',
'order' => $order,
'params' => $params
];
} catch (\Exception $e) {
Log::error('支付验证异常: ' . $e->getMessage());
return false;
}
}
防护措施
/**
* 防止重复通知
*/
private function preventDuplicateNotification($orderId)
{
// 使用缓存或数据库唯一索引
$key = 'payment_notify_' . $orderId;
if (Cache::has($key)) {
return false; // 已处理过
}
// 设置短期缓存
Cache::put($key, true, 600); // 10分钟有效
return true;
}
数据库表结构
-- 订单表
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(64) NOT NULL UNIQUE,
product_id INT NOT NULL,
quantity INT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
status ENUM('pending', 'paid', 'refunded') DEFAULT 'pending',
transaction_id VARCHAR(64),
paid_at DATETIME,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_order_status (status)
);
-- 支付日志表
CREATE TABLE payment_logs (
id INT PRIMARY KEY AUTO_INCREMENT,
order_id INT NOT NULL,
transaction_id VARCHAR(64),
amount DECIMAL(10,2),
status VARCHAR(32),
raw_notify_data TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
测试环境配置
// 沙箱环境 $config->gatewayHost = 'openapi.alipaydev.com'; // 测试账号 // 商户APPID: 9021000122xxxxxx // 商户私钥: MIIEvQIBADANB... // 支付宝公钥: MIIBIjANBgkqh...
常见问题处理
签名验证失败
// 检查密钥格式
$privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" .
chunk_split($config['private_key'], 64, "\n") .
"-----END RSA PRIVATE KEY-----";
金额精度
// 使用高精度计算 $amount = number_format($amount, 2, '.', '');
通知处理超时
// 设置较短的超时时间,快速响应支付宝
set_time_limit(30);
// 先返回成功,异步处理业务
return response()->json([
'code' => 0,
'message' => 'success'
]);
最佳实践
- 测试环境使用沙箱:开发阶段使用支付宝沙箱环境
- 完善日志:记录所有支付相关请求和响应
- 异步处理:通知处理尽量异步化
- 幂等性:确保同一订单不会重复处理
- 金额验证:严格验证支付金额与订单金额一致
- 事务处理:订单状态更新使用数据库事务
这个流程涵盖了从发起支付到异步通知的完整过程,建议在开发时先在沙箱环境测试完整流程,确认无误后再切换到生产环境。