如何使用 wysow repository 创建 PostFinance payment request?
结论
当前代码只是生成了支付表单,并未真正提交。
SimpleFormGenerator::render() 返回一段带有隐藏字段的 HTML <form>。$this->set('paymentForm', $paymentForm) 只是将这段 HTML 传给 concrete5 视图。如果接下来通过普通重定向访问 PostFinance 地址,浏览器会发起新的 GET 请求,表单中的 PSPID、ORDERID、AMOUNT、CURRENCY 和签名等 POST 数据不会随重定向一起发送。
正确的处理流程是:
- 在服务器端生成 PostFinance 表单。
- 在中间跳转页面中原样输出表单。
- 由浏览器自动提交表单,不要使用 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 真正提交。
备注:内容仅供参考。
