PAYATHON 2026

PHP 测试模式集成 ACH 报错 INVALID MERCH OR PASSWD

支付阿杰

结论

INVALID MERCH OR PASSWD 表示当前网关端点不接受请求中提供的商户认证组合。最常见的情况是误将 Virtual Terminal 的网页登录密码填入 API 请求。

Virtual Terminal 登录密码不能直接用作 Const_AuthParam::PASSWORD。这段代码混用了两套不同的认证方式:

  • createClient、createPaymentMethod 等 SOAP Web Service 请求使用:
    • APILoginID
    • SecureTransactionKey
    • UTCTime
    • TSHash
  • ExecuteSocketQuery/AGI 交易请求通常使用:
    • pg_merchant_id
    • pg_password
    • 对应的 AGI/API transaction password

pg_password 必须填写 sandbox 为 API 或交易处理提供的密码,不能使用 Virtual Terminal 用户的登录密码。除非接口文档明确说明,否则也不能直接把 SecureTransactionKey 当作 pg_password。

正确配置认证信息

登录 sandbox Virtual Terminal,在类似 Gateway Settings、API Settings 或 Web Services 的页面中确认并生成以下信息:

class Const_AuthParam
{
    // SOAP Web Services 身份认证
    const APILoginID = 'sandbox_api_login_id';
    const SecureTransactionKey = 'sandbox_secure_transaction_key';

    // AGI/ExecuteSocketQuery 身份认证
    const MERCHANT_ID = 'sandbox_merchant_id';
    const PASSWORD = 'sandbox_api_or_transaction_password';

    const ENV_MODE = 'test';
}

这几项凭据不一定对应同一个账户概念:

配置项用途
APILoginID标识 SOAP API 调用者
SecureTransactionKey计算 TSHash,不随请求直接发送
MERCHANT_ID标识处理交易的商户
PASSWORDAGI 请求的 API/transaction password
Virtual Terminal 登录密码仅用于网页登录,不应作为 API 密码

Forte 的旧版接口在不同商户后台中可能使用不同的字段名称。如果 Gateway Settings 只显示 APILoginID 和 SecureTransactionKey,没有 AGI/transaction password,需要向 Forte sandbox 支持确认该测试商户是否启用了 AGI,以及 pg_password 应使用哪项凭据。

检查 AGI 请求是否真的发送了认证字段

Authentication 对象只传入了 createClient 和 createPaymentMethod 的 ticket 参数。调用 agiDoTransaction() 时,代码发送的内容只有:

$agiTrxParamsArr = get_object_vars($agiTrxParams);
$nuSoapClient->call('ExecuteSocketQuery', $agiTrxParamsArr);

因此,AGI_Transaction 中必须包含并初始化 pg_merchant_id 和 pg_password。否则,即使 Const_AuthParam 配置正确,实际请求中也可能没有这两个字段。

例如:

class AGI_Transaction
{
    public $pg_merchant_id;
    public $pg_password;
    public $pg_client_id;
    public $pg_payment_method_id;
    public $pg_transaction_type;
    public $pg_total_amount;
    public $ecom_billto_postal_name_first;
    public $ecom_billto_postal_name_last;

    public function __construct()
    {
        $this->pg_merchant_id = Const_AuthParam::MERCHANT_ID;
        $this->pg_password = Const_AuthParam::PASSWORD;
    }
}

排查时也可以在调用前显式赋值:

$agiTrxObj = new AGI_Transaction();

$agiTrxObj->pg_merchant_id = Const_AuthParam::MERCHANT_ID;
$agiTrxObj->pg_password = Const_AuthParam::PASSWORD;
$agiTrxObj->pg_client_id = $client_id;
$agiTrxObj->pg_payment_method_id = $paymentMethod_id;
$agiTrxObj->pg_transaction_type = EFT_TRXType::SALE;
$agiTrxObj->pg_total_amount = 0.10;
$agiTrxObj->ecom_billto_postal_name_first = 'Avinash';
$agiTrxObj->ecom_billto_postal_name_last = 'Kshirsagar';

$response = $webServ->agiDoTransaction($agiTrxObj);

字段名必须与 AGI 接口的要求完全一致,包括大小写和下划线。

TSHash 的生成方式

SOAP Web Service 的认证摘要应基于:

HMAC-MD5(APILoginID + "|" + UTCTime, SecureTransactionKey)

现有实现的基本结构没有问题:

private function setTsHash()
{
    $strToHash = $this->APILoginID . '|' . $this->UTCTime;

    $this->TSHash = hash_hmac(
        'md5',
        $strToHash,
        Const_AuthParam::SecureTransactionKey,
        false
    );
}

还需要逐项检查:

  1. APILoginID 和 SecureTransactionKey 是否来自同一个 sandbox 商户配置。
  2. UTCTime 是否采用接口要求的 UTC/.NET ticks 格式。
  3. 服务器时间是否存在明显偏差。
  4. 如果接口文档要求使用大写十六进制摘要,应改为:
$this->TSHash = strtoupper(
    hash_hmac(
        'md5',
        $strToHash,
        Const_AuthParam::SecureTransactionKey,
        false
    )
);

是否需要大写,应以当前接口版本的 WSDL 或文档为准,不能只根据错误信息判断。

TSHash 错误通常会影响携带 Authentication ticket 的 SOAP 调用。如果错误只出现在 ExecuteSocketQuery,应先检查 pg_merchant_id 和 pg_password。

确认所有凭据属于同一环境

测试和生产凭据不能混用。以下各项必须全部属于 sandbox:

  • APILoginID
  • SecureTransactionKey
  • MERCHANT_ID
  • AGI/API transaction password
  • SOAP WSDL 地址
  • ExecuteSocketQuery 地址

即使 merchant ID 正确,只要密码来自生产环境、其他 sandbox 商户或 Virtual Terminal 用户账户,网关仍可能返回:

INVALID MERCH OR PASSWD

还要检查 merchant ID 的格式。某些接口要求使用完整的商户标识,其中可能包含前缀或固定格式。不要自行删除前导字符、空格或其他组成部分。

修正重复创建客户的问题

当前代码调用了两次 insertNewClient():

$webServ->insertNewClient($client);
$client_id = $webServ->insertNewClient($client);

这样可能会创建两条客户记录,也可能导致第一次调用产生的错误被第二次调用覆盖。应改为:

$client_id = $webServ->insertNewClient($client);

if ($client_id === -1 || !is_numeric($client_id)) {
    print_r($webServ->getError());
    exit;
}

客户创建成功后,再继续创建付款方式:

$paymentMethod->ClientID = $client_id;

$paymentMethod_id = $webServ->insertNewPaymentMethod($paymentMethod);

if ($paymentMethod_id === -1 || !is_numeric($paymentMethod_id)) {
    print_r($webServ->getError());
    exit;
}

这样才能判断错误来自 createClient、createPaymentMethod,还是最后的 ExecuteSocketQuery。

建议的排查顺序

  1. 停止使用 Virtual Terminal 登录密码作为 Const_AuthParam::PASSWORD。
  2. 在 sandbox 的 Gateway Settings 中重新确认或生成 APILoginID 和 SecureTransactionKey。
  3. 确认 AGI 要求的 API/transaction password。如果后台没有提供,请联系 Forte sandbox 支持。
  4. 检查 AGI_Transaction 是否包含并实际发送了 pg_merchant_id 和 pg_password。
  5. 确认所有认证信息和请求端点都属于 sandbox。
  6. 分别打印每一步的错误,判断失败发生在 SOAP Web Service 还是 AGI。
  7. 调试日志只记录字段是否存在,不要写入完整密码、银行账号或 SecureTransactionKey。

仅凭 merchant ID 看起来正确,无法排除认证错误。当前需要先理清两套认证方式:SOAP 调用使用 APILoginID + SecureTransactionKey + TSHash,AGI 调用使用接口规定的 pg_merchant_id + pg_password。Virtual Terminal 的网页登录密码不属于这两套 API 凭据。

备注:内容仅供参考。