如何为使用 WordPress 创建的 Soho Hotel Theme 网站集成支付网关?
结论
支付网关通常不是直接“集成到 Soho Hotel Theme”中,而是接入网站正在使用的预订或结账系统。首先要确认酒店订单由哪个插件处理:
- 如果预订最终生成 WooCommerce 订单,应安装相应的 WooCommerce 支付网关插件,并在 WooCommerce 中配置。
- 如果使用 Soho Hotel Theme 自带或捆绑的预订插件,应优先采用该插件支持的支付方式、官方扩展或接口。
- 如果当前预订系统不支持目标网关,则需要根据网关 API 开发 WordPress 插件,处理支付创建、回调验签和订单状态同步。
仅修改主题模板或添加付款按钮,无法构成完整且安全的支付集成。
先确认订单由谁处理
可以在 WordPress 后台检查以下内容:
- 打开“插件”页面,查看是否安装了 WooCommerce,以及名称中带有 Booking、Hotel、Reservation 等字样的插件。
- 创建一笔测试预订,检查后台是否生成 WooCommerce 订单。
- 查看后台有没有“支付方式”“Payment Gateways”或类似设置。
- 确认结账页面由哪个插件生成。
不同版本或发行渠道的 Soho Hotel Theme 可能搭配不同的预订组件,不能只根据主题名称判断配置入口。具体设置应以网站当前安装的插件及其文档为准。
方案一:通过 WooCommerce 接入
如果每笔预订都会生成 WooCommerce 订单,通过 WooCommerce 接入通常最省事,后续维护成本也比较低。
安装支付网关插件
在“插件 → 安装插件”中搜索支付服务商提供的 WooCommerce 插件,例如 Stripe、PayPal,或者当地银行和聚合支付平台提供的插件。
最好选择由支付服务商或 WooCommerce 官方维护的扩展。安装前需要确认:
- 是否支持当前使用的 WordPress、WooCommerce 和 PHP 版本;
- 是否支持网站的币种和经营地区;
- 是否支持退款、预授权、押金或延迟扣款;
- 是否提供 Sandbox/Test Mode;
- 是否依赖 webhook;
- 是否兼容当前的预订插件。
配置支付方式
配置入口通常在:
WordPress 后台
→ WooCommerce
→ 设置
→ 付款
启用相应网关后,一般需要填写:
- Merchant ID;
- API Key 或 Client ID;
- API Secret;
- Webhook Secret;
- 测试环境和正式环境开关;
- 支付成功、失败及取消后的跳转设置;
- 支持的币种;
- webhook/callback URL。
实际字段名称由支付服务商决定。不要把测试环境密钥用于正式环境,也不要在前端 JavaScript 或主题模板中暴露 API Secret。
检查预订产品设置
酒店房型或预订项目必须能够生成正确的订单金额,还需要逐项确认:
- 房价、税费和服务费是否计入订单总额;
- 订金与尾款是否按预期计算;
- 优惠券是否正确影响应付金额;
- 付款完成后是否锁定库存或房态;
- 付款失败、取消或超时后是否释放房态;
- 退款后订单状态与预订状态是否同步。
如果预订插件只负责展示房型,没有把预订转换为 WooCommerce 订单,那么只安装 WooCommerce 网关不会生效。此时还需要预订插件提供 WooCommerce 桥接功能。
方案二:使用预订插件自带的支付功能
如果 Soho Hotel Theme 配套的预订插件自行管理订单,可以进入该插件的设置页面,查找类似以下路径的选项:
Booking Settings
→ Payment
→ Payment Gateways
具体菜单名称可能有所不同。常见设置包括:
- 启用在线支付;
- 选择支付渠道;
- 设置全额付款或订金比例;
- 填写商户账号和 API 凭据;
- 配置测试模式;
- 设置支付成功、取消和失败页面;
- 注册 webhook 地址;
- 设置已付款订单对应的预订状态。
如果插件只支持少量内置网关,不要直接修改插件源码。升级插件时,这些改动会被覆盖,还可能造成订单状态无法正常同步。应先查找官方扩展,或者通过插件公开的 action、filter、REST API 完成接入。
方案三:开发自定义 WooCommerce 网关
没有现成插件时,再考虑自行开发。代码应放在独立插件中,不要写入 Soho Hotel Theme 的父主题。
下面是一个简化的跳转式 WooCommerce 网关结构。示例中的接口地址、字段和签名方式均为占位内容,必须根据实际支付服务商的 API 文档替换。
<?php
/**
* Plugin Name: Custom Hotel Payment Gateway
*/
defined('ABSPATH') || exit;
add_action('plugins_loaded', function () {
if (!class_exists('WC_Payment_Gateway')) {
return;
}
class WC_Gateway_Hotel_Custom extends WC_Payment_Gateway
{
public function __construct()
{
$this->id = 'hotel_custom';
$this->method_title = 'Hotel Custom Gateway';
$this->method_description = 'Redirect customers to an external payment page.';
$this->has_fields = false;
$this->init_form_fields();
$this->init_settings();
$this->title = $this->get_option('title');
$this->enabled = $this->get_option('enabled');
$this->merchant_id = $this->get_option('merchant_id');
$this->api_secret = $this->get_option('api_secret');
add_action(
'woocommerce_update_options_payment_gateways_' . $this->id,
[$this, 'process_admin_options']
);
}
public function init_form_fields()
{
$this->form_fields = [
'enabled' => [
'title' => 'Enable',
'type' => 'checkbox',
'label' => 'Enable this payment method',
'default' => 'no',
],
'title' => [
'title' => 'Title',
'type' => 'text',
'default' => 'Online payment',
],
'merchant_id' => [
'title' => 'Merchant ID',
'type' => 'text',
],
'api_secret' => [
'title' => 'API Secret',
'type' => 'password',
],
];
}
public function process_payment($order_id)
{
$order = wc_get_order($order_id);
if (!$order) {
wc_add_notice('Unable to create the payment.', 'error');
return ['result' => 'failure'];
}
$response = wp_remote_post('https://api.example.com/payments', [
'timeout' => 30,
'headers' => [
'Authorization' => 'Bearer ' . $this->api_secret,
'Content-Type' => 'application/json',
],
'body' => wp_json_encode([
'merchant_id' => $this->merchant_id,
'reference' => (string) $order->get_id(),
'amount' => (string) $order->get_total(),
'currency' => $order->get_currency(),
'return_url' => $this->get_return_url($order),
]),
]);
if (is_wp_error($response)) {
wc_add_notice('The payment service is temporarily unavailable.', 'error');
return ['result' => 'failure'];
}
$status = wp_remote_retrieve_response_code($response);
$data = json_decode(wp_remote_retrieve_body($response), true);
if ($status < 200 || $status >= 300 || empty($data['checkout_url'])) {
wc_add_notice('The payment could not be initialized.', 'error');
return ['result' => 'failure'];
}
$order->update_status('pending', 'Waiting for payment confirmation.');
return [
'result' => 'success',
'redirect' => esc_url_raw($data['checkout_url']),
];
}
}
});
add_filter('woocommerce_payment_gateways', function ($gateways) {
$gateways[] = 'WC_Gateway_Hotel_Custom';
return $gateways;
});
生产环境还必须处理服务端 webhook。不能因为用户打开了“支付成功”页面,就直接将订单标记为已付款。
回调处理至少包括:
- 读取支付平台发送的原始请求体;
- 使用 webhook secret 验证签名;
- 核对商户号、金额、币种和支付状态;
- 根据可信的商户订单号查找订单;
- 防止重复处理同一事件;
- 支付成功后调用
$order->payment_complete(); - 保存支付平台的交易编号;
- 返回支付平台要求的 HTTP 状态码。
示意代码如下:
add_action('rest_api_init', function () {
register_rest_route('hotel-payment/v1', '/webhook', [
'methods' => 'POST',
'callback' => 'hotel_payment_webhook',
'permission_callback' => '__return_true',
]);
});
function hotel_payment_webhook(WP_REST_Request $request)
{
$raw_body = $request->get_body();
$signature = $request->get_header('x-payment-signature');
if (!hotel_verify_signature($raw_body, $signature)) {
return new WP_REST_Response(['error' => 'Invalid signature'], 401);
}
$event = json_decode($raw_body, true);
if (!is_array($event) || empty($event['order_id'])) {
return new WP_REST_Response(['error' => 'Invalid payload'], 400);
}
$order = wc_get_order(absint($event['order_id']));
if (!$order) {
return new WP_REST_Response(['error' => 'Order not found'], 404);
}
if (($event['status'] ?? '') === 'paid' && !$order->is_paid()) {
// 在此之前还应核对金额、币种、商户号和事件唯一标识。
$transaction_id = sanitize_text_field($event['transaction_id'] ?? '');
$order->payment_complete($transaction_id);
}
return new WP_REST_Response(['received' => true], 200);
}
hotel_verify_signature() 必须使用支付服务商规定的算法实现,例如 HMAC-SHA256。签名字段的拼接方式也必须以服务商文档为准,不能自行猜测。
上线前必须测试
至少应覆盖以下场景:
- 正常付款成功;
- 用户取消付款;
- 支付失败;
- 支付页面超时;
- webhook 延迟或重复发送;
- 用户付款后没有返回网站;
- 金额或币种不匹配;
- 全额退款和部分退款;
- 同一房间被多人同时预订;
- 移动端结账;
- 邮件通知和订单状态变化;
- 从测试环境切换到正式环境后的真实小额交易。
测试时还应确认 Soho Hotel Theme 的缓存、AJAX 结账和页面优化功能是否会影响支付脚本。结账页、订单确认页和 webhook 地址通常不应使用页面缓存。
安全与维护注意事项
- 网站必须启用有效的 HTTPS。
- 优先使用支付平台托管的结账页或令牌化组件,避免服务器直接接触银行卡信息。
- API Secret 和 webhook secret 只能保存在服务端。
- webhook 必须验签,并核对金额、币种和订单号。
- 不要根据前端参数或跳转 URL 判断付款是否成功。
- 自定义代码应放入独立插件,至少也要放在子主题中。
- 更新 WordPress、WooCommerce、预订插件或 Soho Hotel Theme 前,先在测试站验证完整的支付流程。
- 启用日志时,不要记录银行卡资料、验证码、完整密钥或其他敏感信息。
- 正式收款前,还应确认支付服务商的开户、币种、税务和 PCI DSS 要求。
具体选择哪种集成方式,取决于当前 Soho Hotel 网站使用的预订插件和目标支付网关。确认这两项后,才能确定准确的后台入口、兼容插件和 API 实现方式。
备注:内容仅供参考。
