之前给客户做自动发卡网站,需要接入支付宝支付功能——原本以为照着官方SDK改改就能用,结果在命名空间、回调验证、配置加载上接连踩坑,折腾了大半天才搞定。相信很多用ThinkPHP3.2做老项目的同学,对接第三方支付时都会遇到类似问题。今天把完整的对接流程和避坑点整理出来,你照着做就能少走弯路。
在写代码前,得先明白支付接口的“交互逻辑”——其实就是商户系统和支付宝系统的“数据传接”,核心分5步:
简单说:我们负责“发请求、收通知、更状态”,支付宝负责“收付款、发通知”。
首先得把支付宝官方SDK里的核心文件找出来,再按ThinkPHP3.2的规范放好——目录结构错了,后面调用类的时候会报“找不到文件”的错。
这5个文件是关键,少一个都不行:
alipay_core.function.php
:支付宝公用工具函数(比如参数排序、签名生成);alipay_notify.class.php
:回调通知处理类(验证支付宝通知的真实性);alipay_submit.class.php
:请求提交类(生成支付表单,跳转到支付宝页面);alipay_md5.function.php
:MD5加密函数(用于签名验证);cacert.pem
:SSL证书文件(CURL请求时校验支付宝的HTTPS证书,防止伪造请求)。ThinkPHP3.2推荐把第三方扩展放在Extend
目录下,我按这个规范建的目录:
Extend/
└── Payment/ # 支付相关扩展
└── Alipay/ # 支付宝接口目录
├── Alipay.php # 自定义的支付入口类(封装官方SDK)
├── cacert.pem # SSL证书文件
└── lib/ # 官方核心类库
├── alipay_core.function.php
├── alipay_notify.class.php
├── alipay_submit.class.php
└── alipay_md5.function.php
为了方便维护,把支付宝的partner
(商户ID)、seller_email
(收款支付宝账号)、key
(商户密钥)等参数,单独写在Common/Conf/alipay.php
里:
<?php
return array(
'partner' => '2088xxxxxxxxx', // 你的商户ID,从支付宝商户平台获取
'seller_email' => 'xxx@xxx.com', // 收款支付宝账号
'key' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', // 商户密钥,注意和公钥区分
'input_charset' => 'utf-8', // 编码格式,建议utf-8
'sign_type' => 'MD5', // 签名方式,官方推荐MD5或RSA
'transport' => 'http', // 通信方式,http或https(推荐https)
);
重要:要在Common/Conf/config.php
里加一句'LOAD_EXT_CONFIG' => 'alipay'
,否则用C('alipay')
获取不到配置!
整个支付功能分3部分:创建订单并提交到支付宝、同步回调处理(用户付款后跳回我们网站)、异步回调处理(支付宝后台通知我们支付结果)。
把官方SDK的提交逻辑封装成静态方法,方便在控制器里调用。注意要加命名空间(ThinkPHP3.2支持命名空间,不加会报类找不到):
<?php
namespace Extend\Payment\Alipay; // 命名空间要和目录结构对应
// 引入官方核心类(如果没自动加载的话)
require_once 'lib/alipay_core.function.php';
require_once 'lib/alipay_md5.function.php';
require_once 'lib/alipay_submit.class.php';
class Alipay {
/**
* 生成支付表单,跳转到支付宝
* @param array $alipay_config 支付宝配置(从C('alipay')获取)
* @param array $args 订单参数(订单号、金额等)
*/
public static function pay($alipay_config, $args) {
/************************** 固定参数 **************************/
$payment_type = "1"; // 支付类型,固定为1(即时到账),不能改
$notify_url = $args['notify_url']; // 异步回调地址(支付宝后台调用)
$return_url = $args['return_url']; // 同步回调地址(用户付款后跳回)
/************************** 订单参数 **************************/
$out_trade_no = $args['out_trade_no']; // 商户唯一订单号(自己生成,比如时间戳+随机数)
$subject = $args['name']; // 订单名称(比如“会员充值-100元”)
$total_fee = $args['total']; // 订单金额(保留2位小数,比如100.00)
$body = $args['content']; // 订单描述(可选,比如“2024年9月会员充值”)
$show_url = $args['show_url']; // 商品展示地址(可选,比如网站首页)
/************************** 构造请求参数 **************************/
$parameter = array(
"service" => "create_direct_pay_by_user", // 即时到账接口服务名,固定
"partner" => trim($alipay_config['partner']),
"seller_email" => trim($alipay_config['seller_email']),
"payment_type" => $payment_type,
"notify_url" => $notify_url,
"return_url" => $return_url,
"out_trade_no" => $out_trade_no,
"subject" => $subject,
"total_fee" => $total_fee,
"body" => $body,
"show_url" => $show_url,
"anti_phishing_key" => "", // 防钓鱼时间戳,可选
"exter_invoke_ip" => "", // 客户端IP,可选
"_input_charset" => trim(strtolower($alipay_config['input_charset']))
);
/************************** 提交到支付宝 **************************/
$alipaySubmit = new \AlipaySubmit($alipay_config); // 实例化提交类
$html_text = $alipaySubmit->buildRequestForm($parameter, "get", "点击跳转到支付宝支付");
echo $html_text; // 输出表单,自动跳转到支付宝
}
}
在用户点击“立即购买”后,先在数据库创建订单,再调用上面的Alipay::pay()
方法提交到支付宝。以Home/IndexController
为例:
<?php
namespace Home\Controller;
use Think\Controller;
use Extend\Payment\Alipay\Alipay; // 导入支付宝类的命名空间
class IndexController extends Controller {
/**
* 用户下单,提交到支付宝
*/
public function doBuy() {
// 1. 先获取用户提交的订单信息(比如商品ID、金额)
$goodsId = I('post.goods_id');
$goods = M('Goods')->find($goodsId); // 从商品表获取商品信息
if (!$goods) {
$this->error('商品不存在!');
}
// 2. 生成唯一订单号(避免重复,比如时间戳+随机数)
$out_trade_no = date('YmdHis') . mt_rand(1000, 9999);
// 3. 把订单信息存入数据库(状态设为0:待支付)
$orderData = array(
'number' => $out_trade_no, // 订单号
'goods_id' => $goodsId,
'name' => $goods['name'], // 商品名称
'price' => $goods['price'], // 商品金额
'status' => 0, // 0=待支付,1=已支付,2=已取消
'create_time' => time()
);
$orderId = M('Order')->add($orderData);
if (!$orderId) {
$this->error('创建订单失败!');
}
// 4. 构造支付参数,提交到支付宝
$baseUrl = 'http://' . $_SERVER['HTTP_HOST']; // 网站根域名
$args = array(
'out_trade_no' => $out_trade_no, // 订单号
'notify_url' => $baseUrl . '/index.php/Home/Index/notifyUrl', // 异步回调地址
'return_url' => $baseUrl . '/index.php/Home/Index/returnUrl', // 同步回调地址
'name' => $goods['name'], // 订单名称
'total' => $goods['price'], // 订单金额
'content' => '购买商品:' . $goods['name'], // 订单描述
'show_url' => $baseUrl . '/index.php/Home/Goods/detail/id/' . $goodsId // 商品详情页
);
// 调用支付宝支付方法(传入配置和订单参数)
Alipay::pay(C('alipay'), $args);
}
}
用户在支付宝支付完成后,会自动跳回我们设置的return_url
。这里主要做“页面提示”(比如显示“支付成功”),但不能仅依赖同步回调更新订单状态(因为用户可能关闭页面,导致回调没执行):
public function returnUrl() {
$alipayConfig = C('alipay');
// 1. 引入支付宝通知类,验证回调的真实性(防止伪造请求)
require_once EXTEND_PATH . 'Payment/Alipay/lib/alipay_notify.class.php';
$alipayNotify = new \AlipayNotify($alipayConfig);
$verifyResult = $alipayNotify->verifyReturn(); // 验证同步通知
if ($verifyResult) {
// 验证成功:获取支付宝返回的参数
$outTradeNo = $_GET['out_trade_no']; // 商户订单号
$tradeNo = $_GET['trade_no']; // 支付宝交易号
$tradeStatus = $_GET['trade_status']; // 交易状态
// 只有“交易成功”或“交易完成”才更新订单状态
if ($tradeStatus == 'TRADE_SUCCESS' || $tradeStatus == 'TRADE_FINISHED') {
$orderModel = M('Order');
$order = $orderModel->where(array('number' => $outTradeNo))->find();
if ($order && $order['status'] == 0) { // 确保订单存在且未被处理过
$orderModel->where(array('id' => $order['id']))->save(array(
'status' => 1, // 改为已支付
'trade_no' => $tradeNo, // 记录支付宝交易号
'pay_time' => time()
));
}
// 跳转到支付成功页面,显示订单信息
$this->assign('order', $order);
$this->display('paySuccess');
} else {
$this->error('支付状态异常:' . $tradeStatus);
}
} else {
// 验证失败:可能是伪造的回调
$this->error('支付验证失败,请联系客服!');
}
}
支付宝会在用户支付完成后,后台自动调用我们设置的notify_url
——这是更新订单状态的“核心依赖”,因为即使用户关闭页面,支付宝也会多次重试回调(确保我们收到通知):
public function notifyUrl() {
$alipayConfig = C('alipay');
// 1. 引入通知类,验证回调真实性
require_once EXTEND_PATH . 'Payment/Alipay/lib/alipay_notify.class.php';
$alipayNotify = new \AlipayNotify($alipayConfig);
$verifyResult = $alipayNotify->verifyNotify(); // 验证异步通知
if ($verifyResult) {
// 验证成功:处理订单
$outTradeNo = $_POST['out_trade_no']; // 商户订单号
$tradeNo = $_POST['trade_no']; // 支付宝交易号
$tradeStatus = $_POST['trade_status']; // 交易状态
if ($tradeStatus == 'TRADE_SUCCESS' || $tradeStatus == 'TRADE_FINISHED') {
$orderModel = M('Order');
$order = $orderModel->where(array('number' => $outTradeNo))->find();
if ($order && $order['status'] == 0) { // 避免重复处理
$updateRes = $orderModel->where(array('id' => $order['id']))->save(array(
'status' => 1,
'trade_no' => $tradeNo,
'pay_time' => time()
));
// 这里还可以加其他逻辑,比如给用户充值会员、发送短信通知等
echo "success"; // 必须输出“success”,支付宝才会停止重试回调
} else {
echo "success"; // 即使订单已处理,也输出success(避免重复回调)
}
} else {
echo "fail"; // 状态异常,告诉支付宝重试
}
} else {
// 验证失败,输出fail
echo "fail";
// 可以加日志记录,方便调试(比如把$_POST写入log文件)
// file_put_contents('./alipay_notify_log.txt', date('Y-m-d H:i:s') . json_encode($_POST) . "\n", FILE_APPEND);
}
}
alipay_notify.class.php
和alipay_submit.class.php
加命名空间,调用时一直报“Class not found”。后来在这两个文件顶部加了namespace Extend\Payment\Alipay;
,问题才解决。return_url
里加了?order_id=123
,结果支付宝回调时直接报错。支付宝要求回调地址必须是“纯路径”,不能带?
和参数,只能通过订单号从数据库查信息。notifyUrl
时,处理完订单没输出“success”,导致支付宝每隔一段时间就重试一次回调,订单被重复更新。后来才知道,支付宝会根据输出判断是否回调成功,只有输出“success”才会停止重试。alipay_notify.class.php
里加日志记录,把$_POST
(异步回调)或$_GET
(同步回调)的参数写入文件,方便排查参数是否正确。partner
、key
、input_charset
等配置是否和支付宝商户平台一致,特别是密钥不要搞混(MD5密钥和RSA密钥是两回事)。最后总结下:ThinkPHP3.2对接支付宝即时到账,核心是“按规范放文件、加命名空间、处理好回调验证”。虽然步骤多,但只要理清流程,避开上面的坑,一次就能对接成功。如果还有问题,建议多看看支付宝官方文档,或者把日志贴出来排查——支付功能不能马虎,一定要确保每一步都验证到位!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。