PHP 接入示例
本文档提供PHP语言接入支付API的完整示例代码。
环境要求
- PHP >= 7.4
- cURL 扩展
签名规则说明
- 提取非空参数(sign 除外)
- 按 ASCII 字典序排序参数名
- 按
key=value&key=value拼接 - 末尾拼接
&key=secretKey - 其中
secretKey = MD5(alliesCode . ":" . alliesSecret) - 对最终字符串做 MD5 并转大写
sign = MD5("k1=v1&k2=v2&key=secretKey") -> 大写完整示例代码
1. 签名工具类 (SignUtil.php)
php
<?php
/**
* 签名工具类 - MD5签名
*/
class SignUtil
{
/**
* 计算 secretKey
* secretKey = MD5(alliesCode . ":" . alliesSecret)
*/
public static function calcSecretKey(string $alliesCode, string $alliesSecret): string
{
return strtoupper(md5($alliesCode . ":" . $alliesSecret));
}
/**
* 生成请求签名
* @param array $params 业务参数(包含 sign 会被自动跳过)
* @param string $secretKey MD5(alliesCode:alliesSecret)
* @return string 签名结果(大写)
*/
public static function buildSign(array $params, string $secretKey): string
{
// 1. 提取非空参数(sign 除外)
$filtered = [];
foreach ($params as $key => $value) {
if (strcasecmp($key, "sign") === 0) continue;
if ($value === null || $value === "") continue;
$filtered[$key] = (string)$value;
}
// 2. 按 ASCII 字典序排序参数名
ksort($filtered, SORT_STRING);
// 3. 按 key=value&key=value 拼接
$pairs = [];
foreach ($filtered as $key => $value) {
$pairs[] = $key . "=" . $value;
}
// 4. 末尾拼接 &key=secretKey
$raw = implode("&", $pairs) . "&key=" . $secretKey;
// 5. MD5 后转大写
return strtoupper(md5($raw));
}
/**
* 验证响应签名
* @param array $response 服务端响应参数
* @param string $secretKey MD5(alliesCode:alliesSecret)
* @return bool 签名是否有效
*/
public static function verifySign(array $response, string $secretKey): bool
{
if (!isset($response["sign"]) || empty($response["sign"])) {
return false;
}
$calculated = self::buildSign($response, $secretKey);
return strcasecmp($response["sign"], $calculated) === 0;
}
}2. API 客户端类 (PaymentClient.php)
php
<?php
/**
* 支付API客户端 - 使用明文 JSON + MD5 签名
*/
class PaymentClient
{
private $apiUrl;
private $alliesCode;
private $secretKey;
/**
* @param string $apiUrl 如 https://pay.example.com/api/alliesPay
* @param string $alliesCode 商户编号
* @param string $alliesSecret 商户密钥
*/
public function __construct(string $apiUrl, string $alliesCode, string $alliesSecret)
{
$this->apiUrl = rtrim($apiUrl, "/");
$this->alliesCode = $alliesCode;
$this->secretKey = SignUtil::calcSecretKey($alliesCode, $alliesSecret);
}
/**
* 使用 cURL 发送 POST JSON 请求
*/
private function sendRequest(string $path, array $data): array
{
// 1. 附加签名
$data["sign"] = SignUtil::buildSign($data, $this->secretKey);
$payload = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $this->apiUrl . "/" . ltrim($path, "/"),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"Content-Length: " . strlen($payload),
],
CURLOPT_SSL_VERIFYPEER => false, // 开发环境可禁用 SSL 验证
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_TIMEOUT => 30,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
throw new Exception("cURL Error: " . $error);
}
if ($httpCode < 200 || $httpCode >= 300) {
throw new Exception("HTTP Error " . $httpCode . ": " . $response);
}
$parsed = json_decode($response, true);
if ($parsed === null) {
throw new Exception("Invalid JSON response: " . $response);
}
// 2. 验证响应签名
if (is_array($parsed["data"] ?? null)) {
$resp = $parsed["data"];
} else {
$resp = $parsed;
}
if (!SignUtil::verifySign($resp, $this->secretKey)) {
throw new Exception("响应签名验证失败");
}
return $parsed;
}
/**
* 代收下单
*/
public function createOrder(array $params): array
{
$payload = array_merge([
"userCode" => $this->alliesCode,
], $params);
return $this->sendRequest("/order", $payload);
}
/**
* 订单查询
*/
public function queryOrder(string $outTradeNo): array
{
$payload = [
"userCode" => $this->alliesCode,
"outTradeNo" => $outTradeNo,
];
return $this->sendRequest("/query", $payload);
}
/**
* 余额查询
*/
public function queryBalance(int $tradeType): array
{
$payload = [
"userCode" => $this->alliesCode,
"tradeType" => $tradeType,
];
return $this->sendRequest("/queryBalance", $payload);
}
}3. 使用示例
示例 1: 代收下单
php
<?php
require_once "SignUtil.php";
require_once "PaymentClient.php";
// ==================== 配置 ====================
$apiUrl = "https://pay.example.com/api/alliesPay";
$alliesCode = "IG20250911001";
$alliesSecret = "your-secret-key";
// ==================== 创建代收订单 ====================
try {
$client = new PaymentClient($apiUrl, $alliesCode, $alliesSecret);
$orderParams = [
"orderNo" => "TEST" . time(),
"amount" => "100.00",
"paymentMethod" => "GCASH", // 或直接传数字 31
"productId" => "0",
"returnUrl" => "https://example.com/return",
"notifyUrl" => "https://example.com/notify",
"clientIp" => "127.0.0.1",
];
echo "发送代收下单请求...\n";
$result = $client->createOrder($orderParams);
echo "响应码: " . $result["code"] . "\n";
echo "响应信息: " . $result["msg"] . "\n";
if ($result["code"] === "0000") {
$data = $result["data"];
echo "支付链接: " . $data["payUrl"] . "\n";
echo "平台订单号: " . $data["outTradeNo"] . "\n";
}
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
}示例 2: 订单查询
php
<?php
require_once "SignUtil.php";
require_once "PaymentClient.php";
$apiUrl = "https://pay.example.com/api/alliesPay";
$alliesCode = "IG20250911001";
$alliesSecret = "your-secret-key";
try {
$client = new PaymentClient($apiUrl, $alliesCode, $alliesSecret);
$outTradeNo = "PO20250213001";
echo "查询订单: " . $outTradeNo . "\n";
$result = $client->queryOrder($outTradeNo);
echo "响应码: " . $result["code"] . "\n";
if ($result["code"] === "0000") {
$data = $result["data"];
echo "订单金额: " . $data["totalAmount"] . "\n";
echo "订单状态: " . $data["tradeStatus"] . "\n";
echo "支付时间: " . $data["tradeTime"] . "\n";
}
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
}回调处理示例
php
<?php
/**
* 代收/代收回调处理
*/
function handleCallback(string $alliesCode, string $alliesSecret): void
{
// 1. 读取回调原始 JSON(注意:所有参数都是明文字段,直接读取即可)
$raw = file_get_contents("php://input");
$data = json_decode($raw, true);
if (!$data) {
echo json_encode(["code" => "FAIL", "msg" => "Invalid JSON"]);
return;
}
// 2. 记录日志
file_put_contents(
"callback.log",
date("Y-m-d H:i:s") . " " . $raw . PHP_EOL,
FILE_APPEND
);
// 3. 验证签名
$secretKey = SignUtil::calcSecretKey($alliesCode, $alliesSecret);
if (!SignUtil::verifySign($data, $secretKey)) {
echo json_encode(["code" => "FAIL", "msg" => "Invalid Sign"]);
return;
}
// 4. 按业务逻辑处理订单
$orderNo = $data["orderNo"] ?? ""; // 商户订单号
$outTradeNo = $data["outTradeNo"] ?? ""; // 平台订单号
$status = $data["tradeStatus"] ?? ""; // 订单状态
// TODO: 例如根据 tradeStatus 更新数据库订单状态
// 5. 返回固定格式
echo json_encode(["code" => "SUCCESS", "msg" => "OK"]);
}
// 调用处理函数
handleCallback("IG20250911001", "your-secret-key");Laravel 集成示例
支付服务类
php
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
class PaymentService
{
private string $apiUrl;
private string $alliesCode;
private string $secretKey;
public function __construct()
{
$this->apiUrl = config("payment.api_url");
$this->alliesCode = config("payment.allies_code");
$this->secretKey = SignUtil::calcSecretKey(
$this->alliesCode,
config("payment.allies_secret")
);
}
/**
* 创建支付订单
*/
public function createOrder(array $orderData): array
{
$params = array_merge($orderData, [
"userCode" => $this->alliesCode,
]);
$params["sign"] = SignUtil::buildSign($params, $this->secretKey);
$response = Http::withHeaders([
"Content-Type" => "application/json",
])->post($this->apiUrl . "/order", $params);
return $response->json();
}
/**
* 处理回调
*/
public function handleNotify(array $data): array
{
if (!SignUtil::verifySign($data, $this->secretKey)) {
return ["code" => "FAIL", "msg" => "Invalid Sign"];
}
// TODO: 更新订单状态等业务逻辑
return ["code" => "SUCCESS", "msg" => "OK"];
}
}控制器
php
<?php
namespace App\Http\Controllers;
use App\Services\PaymentService;
use Illuminate\Http\Request;
class PaymentController extends Controller
{
public function __construct(private PaymentService $paymentService) {}
public function create(Request $request)
{
$result = $this->paymentService->createOrder([
"orderNo" => "ORDER" . strtoupper(uniqid()),
"amount" => $request->input("amount"),
"paymentMethod" => $request->input("payment_method", "GCASH"),
"returnUrl" => route("payment.return"),
"notifyUrl" => route("payment.notify"),
"clientIp" => $request->ip(),
]);
if ($result["code"] === "0000") {
return redirect($result["data"]["payUrl"]);
}
return back()->with("error", $result["msg"]);
}
public function notify(Request $request)
{
return response()->json(
$this->paymentService->handleNotify($request->all())
);
}
public function return(Request $request)
{
return view("payment.result");
}
}常见问题
1. 签名计算不一致
问题: 服务端返回 1012 签名验证失败
排查:
- 确认
secretKey = MD5(alliesCode + ":" + alliesSecret)是正确的(注意冒号分隔)。 - 确保所有空字符串和
null参数都被过滤掉。 - 确认参数是按 ASCII 升序排列的(PHP 的
ksort($arr, SORT_STRING)即可)。 - 打印出待签名的原始字符串,与服务端日志对比:
php
// 临时调试:输出签名前的原始字符串
$params = [...];
$filtered = array_filter($params, fn($v) => $v !== "" && $v !== null);
unset($filtered["sign"]);
ksort($filtered, SORT_STRING);
$raw = http_build_query($filtered, "", "&") . "&key=" . $secretKey;
echo "sign raw: " . $raw . "\n";
echo "sign : " . strtoupper(md5($raw)) . "\n";2. cURL SSL 错误
开发环境: 临时禁用 SSL 验证(参考 PaymentClient 示例)
生产环境: 下载并使用 CA 证书
php
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_CAINFO, "/path/to/cacert.pem");3. 编码问题
确保 JSON 使用 UTF-8 编码,json_encode 默认即为 UTF-8,无需额外处理。
完整代码结构
.
├── SignUtil.php # MD5 签名工具类
├── PaymentClient.php # API 客户端(代收/查询/余额)
├── callback.php # 回调处理
└── example.php # 调用示例- 签名工具类 -
SignUtil::buildSign()/SignUtil::verifySign() - API 客户端 - 基于 cURL 发送明文 JSON,自动附加/验证签名
- Laravel 集成 - 可选,使用 Guzzle/HTTP
