Mercury payment system 支付金额未正确显示
结论
问题出在初始化支付请求。函数虽然接收了 $amount,实际发送给 Mercury 的金额却被写死为 50.23:
$soap_request .= ' 50.23 ';
因此,支付网关不会显示调用函数时传入的 $amount。金额会在 InitializePayment 阶段与 paymentId 绑定。之后将 paymentId POST 到 CheckoutPOS.aspx 时,通常无法通过页面跳转请求再次修改金额。
请将硬编码金额替换为格式化后的 $amount,并确认它写入了 SOAP XML 中正确的金额元素。
修正金额参数
先将金额转换为 Mercury 接口要求的十进制格式:
$formatted_amount = number_format((float) $amount, 2, '.', '');
$formatted_tax = number_format((float) $tax, 2, '.', '');
构造 SOAP 请求时使用这两个变量:
$soap_request .= ' ' . $formatted_amount . ' ';
$soap_request .= ' ' . $formatted_tax . ' ';
原来的代码:
$soap_request .= ' 50.23 ';
至少应改为:
$soap_request .= ' ' . number_format((float) $amount, 2, '.', '') . ' ';
例如:
$payment_id = get_ms_payment_id(
$ms_merchant_id,
$ms_password,
$order_header_id,
125.50,
0.00,
$success_url,
$failure_url
);
此时,初始化请求中实际传递的金额应为:
125.50
不要传入带货币符号或千位分隔符的值,如 $125.50、1,250.00。
检查完整的 SOAP XML
题目中的 $soap_request 只显示了空白字符串和数据值,没有显示 XML 元素名称。这可能是代码展示时标签被过滤,也可能是原始请求没有按预期生成。
发送请求前,可以临时记录原始 XML,确认金额位于 InitializePayment 要求的金额元素中:
error_log($soap_request);
需要检查以下内容:
- XML 是否完整,格式是否合法;
$formatted_amount是否位于正确的金额元素中;- 金额元素的名称和大小写是否与当前 WSDL 一致;
$tax是单独的税额,还是已经包含在$amount中;- XML 中是否有多个金额字段,导致金额写入了错误位置;
- XML 声明、SOAP Envelope 和命名空间是否正确。
由于示例没有保留具体的 XML 标签,无法准确判断金额元素的名称。请以当前 Mercury HostedCheckout WSDL 或接入文档为准,不要只按字段顺序拼接数据。
避免直接拼接未经转义的 XML
商户编号、订单号、URL 和备注等值可能包含 &、< 或 >。直接拼接这些内容可能破坏 XML,至少应先进行 XML 转义:
function xml_value($value)
{
return htmlspecialchars(
(string) $value,
ENT_XML1 | ENT_QUOTES,
'UTF-8'
);
}
使用方式:
$formatted_amount = number_format((float) $amount, 2, '.', '');
$formatted_tax = number_format((float) $tax, 2, '.', '');
$merchant_id = xml_value($ms_merchant_id);
$password = xml_value($ms_password);
$order_id = xml_value($order_header_id);
$success = xml_value($success_url);
$failure = xml_value($failure_url);
更稳妥的方法是使用 DOMDocument、XMLWriter 或 SoapClient 生成 SOAP XML,避免依赖空格和字段顺序。
检查请求地址和 HTTP 头
当前代码中的请求地址和 HTTP 头并不一致。URL 是:
$url = "https://hc.mercurydev.net/hcws/hcservice.asmx";
手动设置的请求路径和主机却是:
"POST /ws/ws.asmx HTTP/1.1",
"Host: w1.mercurypay.com",
URL 指向 hc.mercurydev.net/hcws/hcservice.asmx,HTTP 头引用的却是另一个主机和路径。即使当前能够返回 paymentId,也建议删除这两项,由 cURL 根据 $url 自动生成请求行和 Host:
$header = [
"Content-Type: text/xml; charset=utf-8",
'SOAPAction: "http://www.mercurypay.com/InitializePayment"',
];
完整的 cURL 配置可以整理为:
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => $header,
CURLOPT_POSTFIELDS => $soap_request,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 30,
]);
$result = curl_exec($ch);
if ($result === false) {
throw new RuntimeException(
'Mercury request failed: ' . curl_error($ch)
);
}
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code < 200 || $http_code >= 300) {
throw new RuntimeException(
'Mercury returned HTTP status ' . $http_code
);
}
检查跳转到网关时提交的 paymentId
向以下地址提交数据时:
https://hc.mercurydev.net/CheckoutPOS.aspx
请确认 POST 字段的名称、大小写和编码完全符合 Mercury 文档。除了检查字段值是否像有效的 paymentId,还要确认:
- 请求使用
POST,而不是查询字符串或GET; - 隐藏字段名称与文档一致;
paymentId没有被重复进行 URL 编码;- 初始化接口和支付网关属于同一环境;
- 没有将测试环境生成的 ID 发往生产环境,也没有反向混用;
- 没有重复使用已消费或已失效的
paymentId。
跳转表单通常类似下面的结构,但字段名必须以 Mercury 文档为准:
<form method="post" action="https://hc.mercurydev.net/CheckoutPOS.aspx">
<input type="hidden" name="PaymentID" value="实际返回的 paymentId">
<button type="submit">Pay</button>
</form>
如果文档规定的字段名不是 PaymentID,请改用文档中的准确名称。
不要只解析 paymentId
返回 paymentId 并不一定说明服务端接受了所有初始化参数。应保存并检查完整的 SOAP 响应,查看其中是否还有状态码、错误消息或警告字段:
error_log($result);
同时确认 curl_exec()、HTTP 状态码和 XML 解析均正常。当前代码直接访问多层数组:
$x_data["soap:Envelope"]["soap:Body"]
如果响应中的命名空间前缀发生变化,例如从 soap: 变为 soapenv:,这种解析方式可能失效。生产代码应根据 XML 命名空间解析响应,不要依赖前缀文本。
其他注意事项
正式环境中不要关闭 TLS 证书验证:
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
应保持证书验证开启,并正确配置系统 CA:
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
排查时,每次初始化都应使用唯一的 $order_header_id,避免服务端将重复订单号识别为已有交易。修改金额后,还必须重新调用 InitializePayment 获取新的 paymentId,因为旧的 paymentId 仍与旧金额关联。
先修正硬编码的 50.23,再核对 SOAP 请求中的金额元素、测试环境地址和网关 POST 字段。问题通常不在网关页面的显示逻辑,而在于 paymentId 初始化时已经绑定了错误的金额。
备注:内容仅供参考。
