本文目录导读:

在PHP中对接支付SDK(如支付宝、微信支付、PayPal等)的流程大同小异,核心步骤主要分为申请商户号、下载SDK、配置参数、发起支付和接收回调。
以下是通用的对接方法论,并附上针对国内主流支付(支付宝/微信)的具体代码示例和避坑指南。
第一步:前期准备(非常重要)
- 申请商户账号:去支付宝开放平台或微信支付商户平台注册。
- 获取密钥:
- 支付宝:获取 AppID、应用私钥、支付宝公钥,需要下载支付宝官方密钥工具生成 RSA2 密钥对。
- 微信支付:获取 AppID、商户号(MchID)、APIv3 密钥(APIv3 Key)、证书序列号(以及商户私钥)。
- 配置回调域名:在支付平台后台配置“支付回调(Notify)”和“跳转(Return)”的公网 URL。
第二步:安装 SDK
建议使用官方 SDK,或基于官方 API 封装的 Composer 包。
支付宝(Ant):
composer require alipaysdk/easysdk # 或老版本 composer require alipay/alipay-sdk-php
微信支付:
composer require wechatpay/wechatpay # 或基于官方API v3封装的常用包 composer require wechatpay/wechatpay-guzzle-middleware
第三步:后端发起支付请求(生成订单)
原理:后端将订单参数加密发送给支付平台,支付平台返回一个支付链接或表单,将其返回给前端。
示例 1:支付宝(电脑网站支付 / 手机网站支付)
此时后端需要生成一个 form 表单或直接返回字符串。
<?php
// 引入 SDK
use Alipay\EasySDK\Kernel\Factory;
use Alipay\EasySDK\Kernel\Config;
class AlipayService
{
private $config;
public function __construct()
{
// 配置参数(建议从配置文件读取)
$this->config = new Config();
$this->config->protocol = 'https';
$this->config->gatewayHost = 'openapi.alipay.com';
$this->config->signType = 'RSA2';
$this->config->appId = '你的APP_ID';
$this->config->merchantPrivateKey = '你的私钥内容';
$this->config->alipayPublicKey = '支付宝公钥内容';
$this->config->notifyUrl = 'https://你的域名.com/payment/notify';
$this->config->encryptKey = '';
}
public function pagePay($orderNo, $amount, $subject)
{
Factory::setOptions($this->config);
// 调用支付宝接口
$result = Factory::payment()->pagePay()
->optional('total_amount', $amount)
->optional('subject', $subject)
->optional('out_trade_no', $orderNo)
->optional('product_code', 'FAST_INSTANT_TRADE_PAY')
->page('https://你的域名.com/payment/return'); // 同步跳转地址
// $result->body 包含自动提交的 HTML 表单,直接输出到页面即可跳转
return $result->body;
}
}
// 前端此时接收到的是一段可以自动提交的 HTML 表单
?>
示例 2:微信支付(Native 扫码支付 / JSAPI 公众号支付)
此时后端会返回一个 Code URL 或 prepay_id。
<?php
use WeChatPay\Builder;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Util\PemUtil;
class WechatPayService
{
private $appid;
private $mchid;
private $apiv3Key;
private $merchantPrivateKeyFilePath;
private $merchantCertificateSerial;
public function __construct()
{
// 必须使用绝对路径
$this->appid = '你的APPID';
$this->mchid = '你的商户号';
$this->apiv3Key = 'APIv3密钥';
// 加载商户私钥(apiclient_key.pem)
$merchantPrivateKeyFilePath = '/path/to/apiclient_key.pem';
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
$merchantCertificateSerial = '证书序列号';
$this->instance = Builder::factory([
'mchid' => $this->mchid,
'serial' => $merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => ['path/to/wechatpay_platform_cert.pem'], // 平台证书(用于验签和加密)
]);
}
public function nativePay($orderNo, $amount, $description)
{
// amount 单位是“分”
$resp = $this->instance->chain('/v3/pay/transactions/native')
->post(['json' => [
'appid' => $this->appid,
'mchid' => $this->mchid,
'description' => $description,
'out_trade_no' => $orderNo,
'notify_url' => 'https://你的域名.com/payment/notify',
'amount' => ['total' => $amount, 'currency' => 'CNY'],
]]);
// 返回给前端,让前端生成二维码
return $resp['code_url'];
}
}
第四步:接收异步回调(核心步骤)
这是最容易出错的环节,支付成功后,支付平台会向你的 notify_url 发送一次 POST 通知,你需要验签(验证通知确实来自支付平台),然后修改订单状态,最后返回特定字符串。
支付宝异步回调
<?php
use Alipay\EasySDK\Kernel\Factory;
use Alipay\EasySDK\Kernel\Util\Signer;
public function notify()
{
// 1. 获取支付宝 POST 过来的数据
// $postData = $_POST; // 注意需要去除空白字符等
// 使用 SDK 验签(支付宝 SDK 通常有封装,或者直接使用内置的验签方法)
$config = $this->config; // 复用上述配置
Factory::setOptions($config);
$result = Factory::payment()->common()->verifyNotify($_POST);
if ($result === true) {
// 2. 验签成功,业务处理
// 商户订单号
$outTradeNo = $_POST['out_trade_no'];
// 支付宝交易号
$tradeNo = $_POST['trade_no'];
// 交易状态
$tradeStatus = $_POST['trade_status'];
// 订单金额(验签后,务必校验金额与数据库订单是否一致,防止篡改)
$totalAmount = $_POST['total_amount'];
if ($tradeStatus === 'TRADE_SUCCESS' || $tradeStatus === 'TRADE_FINISHED') {
// 检查订单状态是否已支付,避免重复处理
// 更新数据库状态为已支付
}
// 3. 响应支付宝:必须输出 "success"(不带引号),否则支付宝会不断重试
return 'success';
} else {
// 验签失败
return 'failure';
}
}
微信支付异步回调
微信回调的数据是加密的,需要使用 APIv3 Key 解密。
<?php
public function notify(\Psr\Http\Message\RequestInterface $request)
{
// 1. 获取报文头信息
$serialNo = $request->getHeader('Wechatpay-Serial')[0]; // 平台证书序列号
$timestamp = $request->getHeader('Wechatpay-Timestamp')[0];
$nonce = $request->getHeader('Wechatpay-Nonce')[0];
$signature = $request->getHeader('Wechatpay-Signature')[0];
// 2. 获取响应体
$body = $request->getBody()->getContents();
$data = json_decode($body, true);
// 3. 解密资源字段($data['resource'])
// 需要用到密钥 $data['resource']['ciphertext'] 和 $data['resource']['nonce'] 和 associated_data
// 使用 SDK 库内置方法解密(假设使用官方中间件)
// $decrypted = $this->instance->decryptApiV3($ciphertext, $nonce, $associatedData);
// $transaction = json_decode($decrypted, true);
// 4. 业务处理(与支付宝相同)
// $outTradeNo = $transaction['out_trade_no'];
// $transaction['trade_state'] === 'SUCCESS'
// 5. 返回微信特定格式
// 返回 HTTP 200 状态码,并返回以下 JSON
return response()->json([
'code' => 'SUCCESS',
'message' => '成功'
], 200);
}
第五步:常见问题与避坑指南
- 金额单位陷阱:
- 支付宝(元):1元 = 传入
00。 - 微信支付(分):1元 = 传入
100。微信支付如果传入小数会报错。
- 支付宝(元):1元 = 传入
- 回调必须返回特定字符串:
- 支付宝:必须输出
success或failure(注意是全小写)。 - 微信支付:必须返回
{"code":"SUCCESS", "message":"成功"}且 HTTP 状态码必须是 200,如果返回非200或响应错误,微信会连续重试多次,可能会导致订单被重复处理。
- 支付宝:必须输出
- 验证金额与订单号:在回调中,务必拿支付平台返回的
amount和order_no与数据库中的记录进行比对,防止“订单金额篡改”攻击(即支付1分钱购买100元商品)。 - 订单状态幂等性:由于网络原因,同一个支付结果可能回调多次,在回调处理中,必须判断数据库订单状态如果已支付则直接返回 success,避免重复执行发放积分、发货等操作。
- 证书路径:在 PHP 中经常遇到“证书路径错误”,务必使用绝对路径(如
dirname(__FILE__) . '/cert/apiclient_key.pem'),避免相对路径在 CLI 和 Web 环境下不一致。
代码结构建议
如果你不想重复造轮子,强烈建议封装一个 PayInterface,内部定义 createOrder() 和 verifyNotify()。
interface PayInterface
{
public function createOrder($orderData); // 返回跳转链接或二维码
public function verifyNotify($requestParams); // 验签并处理业务
}
所有主流支付平台(支付宝、微信)都有非常完善的 PHP SDK 和调试工具(如支付宝沙箱环境)。建议先跑通沙箱测试,再切换到正式环境,遇到问题先检查“证书/密钥”是否正确配置。