Skip to content

PHP 接入示例

本文档提供PHP语言接入支付API的完整示例代码。

环境要求

  • PHP >= 7.4
  • cURL 扩展

签名规则说明

  1. 提取非空参数(sign 除外)
  2. 按 ASCII 字典序排序参数名
  3. key=value&key=value 拼接
  4. 末尾拼接 &key=secretKey
  5. 其中 secretKey = MD5(alliesCode . ":" . alliesSecret)
  6. 对最终字符串做 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 签名验证失败

排查:

  1. 确认 secretKey = MD5(alliesCode + ":" + alliesSecret) 是正确的(注意冒号分隔)。
  2. 确保所有空字符串null 参数都被过滤掉。
  3. 确认参数是按 ASCII 升序排列的(PHP 的 ksort($arr, SORT_STRING) 即可)。
  4. 打印出待签名的原始字符串,与服务端日志对比:
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