如何使用 Joomla 集成 payment gateway
结论
Joomla 没有可以直接配置的通用支付网关模块。一般需要先安装电商或订单组件,再安装与该组件兼容的支付插件,如 PayPal、Stripe、支付宝或微信支付插件。
支付逻辑不应直接写在 Joomla 的前端模块(Module)里。模块可以显示支付按钮或订单信息,但创建支付订单、验证签名、处理异步通知和更新订单状态等操作,应由组件或支付插件负责。
推荐的集成方式
通常按以下流程集成:
- 安装符合业务需求的电商或订单组件。
- 查看该组件支持的支付插件和 Joomla 版本。
- 安装对应的支付插件。
- 在插件后台填写商户号、API Key、Webhook Secret 等参数。
- 开启支付平台的 Sandbox/Test Mode。
- 配置支付成功、取消和异步通知地址。
- 测试通过后再切换到生产环境。
选择插件时,要核对以下兼容性:
- Joomla 主版本。
- PHP 版本。
- 电商组件及其版本。
- 支付平台当前使用的 API 版本。
- 支持的币种和商户所在地区。
- 是否支持退款、异步通知和重复通知处理。
不同电商组件使用的支付事件和插件接口并不相同。为某个组件开发的支付插件,不能直接安装到另一个组件中使用。
后台安装和配置
在 Joomla 管理后台进入:
System → Install → Extensions
上传支付插件的安装包。安装完成后,进入:
System → Manage → Plugins
找到并启用对应插件。Joomla 版本、后台语言和电商组件不同,具体菜单名称可能略有差异。
支付插件一般需要配置以下参数:
Merchant ID
API Key
API Secret
Webhook Secret
Test Mode
Currency
Return URL
Cancel URL
Webhook URL
API Secret 和 Webhook Secret 都是敏感信息,不要将它们写入模板、JavaScript、公开仓库或前端 HTML。
自行开发时的正确结构
如果现有插件无法满足业务需求,可以开发一个兼容目标订单组件的 Joomla 支付插件。典型目录结构如下:
plugins/
└── payment/
└── examplepay/
├── examplepay.xml
├── services/
│ └── provider.php
└── src/
└── Extension/
└── Examplepay.php
这里的 payment 只是示例。Joomla 核心没有规定所有电商组件都必须使用这个插件组。实际的插件组、事件名称和方法签名,应以目标组件的开发文档为准。
支付插件一般需要处理以下工作:
- 从订单组件读取订单编号和应付金额。
- 在服务器端调用支付平台 API。
- 保存本地订单号与支付平台交易号的对应关系。
- 将用户重定向到收银台,或向前端返回支付参数。
- 接收支付平台发送的 Webhook。
- 验证签名、金额、币种和商户身份。
- 根据通知结果更新订单状态。
创建支付订单示例
下面的代码只用于说明如何在服务器端调用支付 API,并不对应任何支付平台的真实接口。URL、字段名称、签名方式和响应结构,都必须按照支付平台的官方文档替换。
<?php
use Joomla\CMS\Http\HttpFactory;
use Joomla\CMS\Uri\Uri;
function createPayment(array $order, string $apiKey): string
{
$http = HttpFactory::getHttp();
$payload = [
'merchant_order_id' => (string) $order['id'],
'amount' => (int) $order['amount_minor'],
'currency' => (string) $order['currency'],
'return_url' => Uri::root() . 'index.php?option=com_shop&task=payment.return',
'cancel_url' => Uri::root() . 'index.php?option=com_shop&task=payment.cancel',
'webhook_url' => Uri::root() . 'index.php?option=com_shop&task=payment.webhook',
];
$response = $http->post(
'https://api.example-pay.test/v1/payments',
json_encode($payload, JSON_THROW_ON_ERROR),
[
'Authorization' => 'Bearer ' . $apiKey,
'Content-Type' => 'application/json',
]
);
if ($response->code < 200 || $response->code >= 300) {
throw new RuntimeException('Payment API request failed');
}
$result = json_decode($response->body, true, 512, JSON_THROW_ON_ERROR);
if (empty($result['checkout_url'])) {
throw new RuntimeException('Payment API did not return checkout_url');
}
return $result['checkout_url'];
}
金额最好用最小货币单位的整数表示。例如,人民币 10.25 元可以写成 1025 分,这样能避免使用浮点数计算金额。
支付金额必须从服务器端的订单记录中读取,不能直接采用浏览器提交的金额:
// 错误:金额可能被用户修改
$amount = $_POST['amount'];
// 正确思路:根据订单 ID 从数据库重新读取金额
$order = $orderRepository->findById($orderId);
$amount = $order->amount_minor;
Webhook 验签示例
用户进入支付成功页面,只能说明浏览器已经返回网站,不能据此确认付款成功。订单的最终状态应以支付平台签名有效的服务器端通知为准,也可以由服务器主动查询支付结果后确认。
下面是一个使用 HMAC-SHA256 的示例:
<?php
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_PAYMENT_SIGNATURE'] ?? '';
$secret = $configuredWebhookSecret;
$expected = hash_hmac('sha256', $rawBody, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('Invalid signature');
}
$event = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);
if (($event['type'] ?? '') === 'payment.succeeded') {
$orderId = (string) ($event['data']['merchant_order_id'] ?? '');
$amount = (int) ($event['data']['amount'] ?? 0);
$currency = (string) ($event['data']['currency'] ?? '');
// 重新读取订单,并核对金额、币种、商户身份和当前状态。
// 验证全部通过后,再以幂等方式更新订单状态。
}
http_response_code(200);
echo 'OK';
这段代码也只是结构示例。实际支付平台可能采用证书、RSA、Ed25519、时间戳加签或特定的字符串拼接规则,验签时必须严格遵循官方算法。
为什么不建议只做一个 Module
Joomla Module 主要用于显示页面中的局部内容,例如:
- 显示”立即支付”按钮。
- 展示订单摘要。
- 显示用户当前的付款状态。
完整的支付流程还涉及订单生命周期、权限检查、数据库事务、回调路由和日志记录。如果把这些逻辑全部塞进模块,退款、重复通知、支付超时和订单状态冲突都会变得难以处理。
如果页面需要支付按钮,可以让模块只提交订单编号。后端组件收到请求后,先检查当前用户是否有权支付该订单,再创建支付请求:
<form action="/index.php?option=com_shop&task=payment.start" method="post">
<input type="hidden" name="order_id" value="12345">
<button type="submit">立即支付</button>
</form>
在生产环境中,还要通过 Joomla 提供的机制加入 CSRF Token,并在服务器端验证登录用户、订单归属和订单状态。隐藏字段中的数据同样可以被修改,不能直接信任。
必须注意的安全问题
- 为全站启用 HTTPS。
- 私钥和密钥只保存在服务器端配置中。
- 不要在 Joomla 中自行保存银行卡号、CVV 等敏感信息。
- 优先采用支付平台托管的收银台或官方前端组件。
- Webhook 必须验签,不能只根据请求参数判断支付是否成功。
- 验证通知时还要核对订单号、金额、币种和商户身份。
- 对重复 Webhook 做幂等处理,避免重复发货或重复记账。
- 记录交易号和必要的日志,但不要记录完整密钥或敏感支付数据。
- Return URL 只用于显示结果,不能直接将订单确认为已付款。
- 上线前应测试支付成功、失败、取消、超时、重复通知和退款等情况。
使用第三方插件时,还要查看它的维护状态、更新记录和安全公告。来源不明或长期无人维护的支付插件,即使可以正常安装,也不应直接用于生产环境。
备注:内容仅供参考。
