PAYATHON 2026

如何使用 wysow repository 创建 PostFinance payment request?

支付小周

结论

当前代码只是生成了支付表单,并未真正提交。

SimpleFormGenerator::render() 返回一段带有隐藏字段的 HTML <form>。$this->set('paymentForm', $paymentForm) 只是将这段 HTML 传给 concrete5 视图。如果接下来通过普通重定向访问 PostFinance 地址,浏览器会发起新的 GET 请求,表单中的 PSPID、ORDERID、AMOUNT、CURRENCY 和签名等 POST 数据不会随重定向一起发送。

正确的处理流程是:

  1. 在服务器端生成 PostFinance 表单。
  2. 在中间跳转页面中原样输出表单。
  3. 由浏览器自动提交表单,不要使用 PHP 直接重定向到 PostFinance。

解决方法

在支付方法中继续生成表单:

$passphrase = new Passphrase($shaInSecret);
$shaComposer = new AllParametersShaComposer($passphrase);

$paymentRequest = new EcommercePaymentRequest($shaComposer);

$paymentRequest->setPspid($postfinancePsid);
$paymentRequest->setOrderid((string) $customerOrder->getOrderId());
$paymentRequest->setPostFinanceUri(EcommercePaymentRequest::TEST);

$paymentRequest->setAmount((int) $amount);
$paymentRequest->setCurrency('CHF');
$paymentRequest->setCustomername($customerName);
$paymentRequest->setOwnerAddress($address);

$paymentRequest->validate();

$formGenerator = new SimpleFormGenerator();
$paymentForm = $formGenerator->render($paymentRequest);

$this->set('paymentForm', $paymentForm);

然后在 redirect view 中输出表单,并让浏览器自动提交:

<?= $paymentForm ?>

<script>
document.addEventListener('DOMContentLoaded', function () {
    var form = document.querySelector('form[action*="postfinance"]');

    if (form) {
        form.submit();
    }
});
</script>

<noscript>
    <p>请点击支付按钮继续前往 PostFinance。</p>
</noscript>

如果页面中可能还有其他表单,不建议使用范围过宽的 document.querySelector('form')。可以先查看生成的 HTML,再使用表单的实际 id 或其他稳定的选择器:

<?= $paymentForm ?>

<script>
document.addEventListener('DOMContentLoaded', function () {
    var form = document.getElementById('postfinance-payment-form');

    if (form) {
        form.submit();
    }
});
</script>

这里的 postfinance-payment-form 必须替换为生成表单中实际存在的 id。如果生成器没有设置 id,可以根据它的实际输出选择表单,或者在条件允许时为表单添加明确标识。

为什么普通重定向不起作用

PostFinance 的 Hosted Payment Page 通常要求浏览器通过 POST 提交参数:

<form method="post" action="https://...">
    <input type="hidden" name="PSPID" value="...">
    <input type="hidden" name="ORDERID" value="...">
    <input type="hidden" name="AMOUNT" value="...">
    <input type="hidden" name="CURRENCY" value="CHF">
    <input type="hidden" name="SHASIGN" value="...">
</form>

下面的操作只会让浏览器跳转到目标 URL:

header('Location: https://...');

HTTP 302 重定向不会把这些隐藏字段转换成新的 POST 请求。因此,浏览器可能成功进入 PostFinance 页面,但该页面无法识别订单金额和客户信息。

建议增加返回地址

返回地址不能替代提交订单所需的基本字段,但在实际接入时,通常还要配置支付成功、失败、取消和异常后的返回地址。

wysow 库中具体的 setter 名称可能因安装版本而异,应以当前版本的 EcommercePaymentRequest 类为准。常见的参数包括:

ACCEPTURL
DECLINEURL
CANCELURL
EXCEPTIONURL

如果当前版本提供了对应方法,可以按照其 API 设置,例如:

$paymentRequest->setAccepturl($successUrl);
$paymentRequest->setDeclineurl($failureUrl);
$paymentRequest->setCancelurl($cancelUrl);
$paymentRequest->setExceptionurl($exceptionUrl);

方法名的大小写及其是否可用,需要查看已安装包中的类定义,不能只根据上述示例作出假设。

需要重点检查的事项

金额必须使用最小货币单位

PostFinance 的 AMOUNT 通常使用最小货币单位,而不是带小数点的金额。例如,CHF 12.50 应传递:

$amount = 1250;
$paymentRequest->setAmount($amount);

不要直接进行浮点数乘法后再强制转换。浮点精度问题可能导致金额少一分或多一分。可以按照业务系统使用的金额类型,进行可靠的换算和舍入:

$amountInMinorUnits = (int) round(((float) $orderTotal) * 100);
$paymentRequest->setAmount($amountInMinorUnits);

如果 Community Store 已经提供了以最小货币单位表示的金额,就不要再次乘以 100。

不要转义生成的表单 HTML

视图必须将 $paymentForm 作为 HTML 输出。如果使用会自动转义的模板语法,浏览器可能只会显示表单源码文本,而不会生成可提交的表单。

也不要把生成的 PostFinance 表单放入另一个 <form>。HTML 不支持嵌套表单,浏览器可能重新组织 DOM,导致隐藏字段不再属于实际提交的表单。

验证签名配置

需要确认以下配置与 PostFinance 后台一致:

  • 使用 SHA-IN secret,不要误用 SHA-OUT secret。
  • 哈希算法与 PostFinance 后台配置一致。
  • 测试环境使用测试 PSPID 和测试端点。
  • 由库正确处理参与签名的字段、字段排序和大小写。
  • 调用 validate() 或生成签名后,不要再修改支付参数。

检查浏览器实际发送的请求

只检查生成的 HTML,不能证明表单已经成功提交。需要在浏览器开发者工具的 Network 面板中确认:

  • 请求方法为 POST。
  • 请求目标是 PostFinance 的支付端点。
  • Request Payload 或 Form Data 中包含 PSPID、ORDERID、AMOUNT、CURRENCY 和 SHASIGN。
  • 页面没有 JavaScript 错误,Content Security Policy 也没有阻止脚本执行。

问题不在于缺少某个 payment detail 参数,而在于生成的表单是否已经输出到页面,并由浏览器通过 POST 真正提交。

备注:内容仅供参考。