本文目录导读:

在 PHP 项目中对接支付接口,虽然具体 API 会因支付服务商(如微信支付、支付宝、PayPal、Stripe 等)而异,但核心流程和逻辑是通用的。
下面是一个标准的、模块化的对接指南,适合大多数 PHP 项目(无论是原生 PHP 还是 ThinkPHP、Laravel 等框架)。
核心流程(以用户付款为例)
- 用户在前端点击“支付”按钮。
- 前端请求后端 PHP(生成订单,获取支付参数)。
- 后端 PHP 处理逻辑:
- 校验订单(金额、库存、状态)。
- 生成订单记录(状态:未支付)。
- 调用支付服务商 SDK,生成支付参数(如二维码链接、支付令牌、Form 表单)。
- PHP 返回支付参数给前端。
- 前端调起支付组件(跳转支付页面、弹出二维码、调用 App 支付)。
- 用户完成支付。
- 支付服务商异步通知 PHP 后端(最重要的一步)。
- PHP 后端验证签名 -> 更新订单状态为“已支付” -> 返回成功标识给支付服务商。
准备工作:获取商户密钥
在每个支付平台(支付宝/微信开放平台等)都需要完成:
- 注册商户账号。
- 创建应用,获取
AppID。 - 配置密钥:
- 支付宝:配置 RSA2 公钥/私钥(推荐使用沙箱环境测试)。
- 微信支付:获取商户号
MchID、APIv2或APIv3密钥,下载证书文件。
- 设置回调地址(Notify URL),也就是你的服务器接收支付结果通知的 URL。
PHP 支付模块封装示例(模块化设计)
建议创建一个 PaymentService 类,而不是把逻辑散落在各处。
依赖管理(强烈建议使用 Composer)
# 支付宝官方 SDK composer require alipay/alipay-sdk-php # 微信支付官方 SDK (推荐使用 APIv3) composer require wechatpay/wechatpay-guzzle-middleware # 通用支付网关 (如果你的项目对接多家) # composer require hprose/hprose-php # 或其他
支付服务类骨架 (PaymentService.php)
<?php
namespace App\Services;
use Alipay\EasySDK\Kernel\Factory;
use WechatPay\GuzzleMiddleware\WechatPayMiddleware;
use GuzzleHttp\Client;
class PaymentService
{
// 支付宝配置
private $alipayConfig;
// 微信支付配置
private $wechatConfig;
public function __construct()
{
// 通常从 .env 文件读取,不要写死
$this->alipayConfig = [
'app_id' => env('ALIPAY_APP_ID'),
'alipay_public_key' => env('ALIPAY_PUBLIC_KEY'),
'merchant_private_key' => env('ALIPAY_MERCHANT_PRIVATE_KEY'),
'notify_url' => env('APP_URL') . '/api/payment/alipay/notify',
];
$this->wechatConfig = [
'mchid' => env('WECHAT_MCHID'),
'serial' => env('WECHAT_SERIAL'),
'private_key_path' => env('WECHAT_PRIVATE_KEY_PATH'),
'apiv3_key' => env('WECHAT_API_V3_KEY'),
];
}
// 支付宝 - 手机网站/PC网站支付
public function alipayPagePay($orderId, $amount, $subject)
{
// 1. 初始化 SDK
Factory::setOptions($this->alipayConfig);
// 2. 调用 API 生成支付页面 form 或 URL
$result = Factory::payment()->page()->pay(
$subject, // 订单标题
$orderId, // 商户订单号
$amount, // 金额 (单位: 元)
$this->alipayConfig['notify_url'] // 回调地址
);
// 3. 返回给前端的数据(一个自动提交的 Form 表单或者跳转链接)
// 对于 PC 网站,一般是返回 HTML 代码
return response($result->body);
// 对于小程序/APP,会返回 trade_no (交易号)
}
// 微信支付 - JSAPI (H5/小程序) 或 Native (扫码)
public function wechatNativePay($orderId, $amount, $description)
{
// 1. 构建支付参数
$params = [
'mchid' => $this->wechatConfig['mchid'],
'out_trade_no' => $orderId,
'appid' => env('WECHAT_APPID'),
'description' => $description,
'notify_url' => env('APP_URL') . '/api/payment/wechat/notify',
'amount' => [
'total' => (int)($amount * 100), // 微信金额单位为分
'currency' => 'CNY'
],
];
// 2. 发起 HTTP 请求 (使用官方 SDK 或 Guzzle)
// ... 这里省略具体签名和请求代码
// 3. 获取返回的 code_url(二维码链接)
$codeUrl = $response['code_url'];
// 4. 生成二维码,或直接返回链接给前端
return [
'code_url' => $codeUrl,
'out_trade_no' => $orderId,
];
}
// 统一下单入口 (根据前端传递的 channel)
public function unifyOrder($channel, $orderId, $amount, $subject)
{
switch ($channel) {
case 'alipay_wap':
return $this->alipayPagePay($orderId, $amount, $subject);
case 'wechat_native':
return $this->wechatNativePay($orderId, $amount, $subject);
default:
throw new \Exception("不支持的支付方式");
}
}
}
处理异步通知(最关键的环节)
支付成功后的回调必须由服务端接收,因为这是安全的(通过签名验证)。绝对不能只依赖前端跳转。
支付宝异步通知示例 (alipayNotify)
支付宝会 POST 一个表单到你的 notify_url。
// 路由: POST /api/payment/alipay/notify
public function alipayNotify(Request $request)
{
$postData = $request->post(); // 支付宝发来的数据
// 1. 签名验证 (必须做! 防止伪造通知)
Factory::setOptions($this->alipayConfig);
$isVerify = Factory::payment()->common()->verifyNotify($postData);
if (!$isVerify) {
// 签名失败,记录日志
return 'fail'; // 必须返回 fail,以便支付宝重发
}
// 2. 验证业务参数
$outTradeNo = $postData['out_trade_no']; // 我们的订单号
$tradeNo = $postData['trade_no']; // 支付宝交易号
$totalAmount = $postData['total_amount'];
$tradeStatus = $postData['trade_status'];
// 3. 只处理交易成功状态
if ($tradeStatus === 'TRADE_SUCCESS' || $tradeStatus === 'TRADE_FINISHED') {
// 4. 查询本地订单,防止重复回调
$order = OrderModel::where('order_no', $outTradeNo)->first();
// 重要: 检查订单状态,防止重复更新
if (!$order || $order->status === 'paid') {
return 'success'; // 已处理,直接返回成功
}
// 重要: 验证金额是否一致 (防止坏人修改金额)
if (abs(floatval($order->total_amount) - floatval($totalAmount)) > 0.01) {
// 金额不匹配,记录日志并报警
return 'fail';
}
// 5. 更新订单状态 (使用事务或锁)
\DB::transaction(function () use ($order, $tradeNo) {
$order->status = 'paid';
$order->transaction_id = $tradeNo;
$order->paid_at = now();
$order->save();
// 6. 触发后续业务逻辑 ( 给用户加积分, 发短信, 更新库存)
event(new OrderPaid($order));
});
// 7. 返回成功字符串(必须是 success,不能是其他)
return 'success';
}
// 其他状态,暂时忽略,但返回 success 防止重试
return 'success';
}
微信支付异步通知示例 (wechatNotify)
微信的通知是加密的 XML 或 JSON(APIv3 是 JSON)。
// 路由: POST /api/payment/wechat/notify
public function wechatNotify(Request $request)
{
// 微信 APIv3 通知是 JSON 格式, 且 body 是加密的
// 1. 需要先解密 (使用 APIv3 密钥)
// $decrypted = WechatPayDecrypt::decrypt($request->getContent(), $this->wechatConfig['apiv3_key']);
// 2. 验证签名 (微信平台证书签名)
// $isValid = WechatPayVerify::verify(...);
// 3. 解析数据
// $resource = $decrypted['resource'];
// $outTradeNo = $resource['out_trade_no'];
// $transactionId = $resource['transaction_id'];
// $tradeSuccess = $resource['trade_state'] === 'SUCCESS';
// 4. 业务逻辑 (同支付宝,检查订单、更新状态)
if ($tradeSuccess) {
// ... 更新订单逻辑 ...
// 更新库存等
}
// 5. 返回给微信: 必须返回 HTTP 200 和 特定格式的 JSON
return response()->json([
'code' => 'SUCCESS',
'message' => '成功'
]);
// 如果返回错误,微信会重试 3-7 天
}
安全与规范避坑指南
- 绝对不能在前端拼接支付逻辑! 所有请求支付服务商的动作必须在后端完成,前端只负责展示后端返回的结果并调起支付组件。
- 校验金额:在异步通知中,必须拿回调中的
total_amount与你数据库中的order.total_amount做比对,防止被篡改。 - 幂等性处理:支付回调可能会重复收到(网络抖动、重试机制),务必判断订单是否已经是
paid状态,如果已支付则直接return 'success'并跳出,不要重复发积分或发货。 - 签名验证:支付回调是唯一可信的通知方式,必须验证签名,验证通过后才更新数据库。
- 日志记录:记录原始回调数据
$request->all()和签名验证结果,方便以后排查问题。 - 订单号唯一性:使用
uniqid+recorder_id或 UUID 生成订单号,防止重复调用。 - 使用 HTTPS:前后端通信、支付回调地址必须使用 HTTPS,防止中间人攻击。
- 备好退款接口:支付功能上线前,建议先写好退款逻辑(PHP 调用服务商退款 API)。
常见问题与排查
验签失败:大概率是密钥配置不正确,或公钥/私钥搞反了,重新检查支付宝或微信的密钥设置页面。收不到回调:- 检查服务器防火墙,确保端口 80/443 开放。
- 检查
notify_url是否能被外网访问,且不要有 等参数(支付服务商会覆盖)。 - 检查代码是否返回了正确的
success(支付宝)或{"code":"SUCCESS"}(微信),如果是fail会被重试但仍可能失败。
重复通知:正常现象,支付服务商会自动重试,你的代码必须能处理同一个通知多次执行,并且结果一致(幂等)。
如果项目对安全性要求较高,或希望快速接入多种支付方式,推荐使用 Laravel 的 laravel/cashier 或 Yansongda/pay 这类成熟的 Composer 包,它们封装了底层的签名、解密和很多安全细节。