# WooCommerce 支付网关 API

WooCommerce 中的支付网关基于类，可以通过传统的插件进行添加。本指南提供支付网关开发的入门介绍。

## 支付网关的类型

支付网关有多种类型：

1.  **基于表单** - 用户必须点击表单上的一个按钮，然后被重定向到支付处理商的网站。_示例：_ PayPal 标准版，Authorize.net DPM
2.  **基于 iFrame** - 支付网关的支付系统加载在您的商店的 iFrame 中。_示例：_ SagePay Form，PayPal 高级版
3.  **直接** - 支付字段直接显示在结账页面上，用户在点击“提交订单”按钮时完成支付。_示例：_ PayPal Pro，Authorize.net AIM
4.  **离线** - 不进行在线支付。_示例：_ 支票，银行转账

基于表单和 iFrame 的网关会将数据发送到外部服务器，这意味着您需要考虑的安全问题较少。但是，直接网关需要实施服务器安全措施（例如，[SSL 证书](https://woocommerce.com/document/ssl-and-https/)），并且可能需要满足一定的 [PCI 合规性](https://woocommerce.com/document/pci-dss-compliance-and-woocommerce/) 标准。

## 创建基本的支付网关

**注意：** 以下说明适用于默认的结账页面。如果您想为新的结账块添加自定义支付方式，请查看 [支付方式集成文档](/docs/block-development/extensible-blocks/cart-and-checkout-blocks/checkout-payment-methods/payment-method-integration)。

支付网关应该作为附加插件创建，并与 WooCommerce 集成。在插件内部，您需要在插件加载后创建一个类。例如：

```php
add_action( 'plugins_loaded', 'init_your_gateway_class' );
```

重要的是，您的网关类应该继承 WooCommerce 的基本网关类，以便您可以使用重要的方法和 [设置 API](https://developer.woocommerce.com/docs/settings-api/)。

```php
function init_your_gateway_class() {
    class WC_Gateway_Your_Gateway extends WC_Payment_Gateway {}
}
```

您可以在 [API 文档中查看 `WC_Payment_Gateway` 类](https://woocommerce.github.io/code-reference/classes/WC-Payment-Gateway.html)。

除了定义您的类之外，您还需要告诉 WooCommerce (WC) 它的存在。通过过滤 `woocommerce_payment_gateways` 来实现：

```php
function add_your_gateway_class( $methods ) {
    $methods[] = 'WC_Gateway_Your_Gateway';
    return $methods;
}
```

```php
add_filter( 'woocommerce_payment_gateways', 'add_your_gateway_class' );
```

### 必需的方法

大多数方法是从 `WC_Payment_Gateway` 类继承的，但某些方法需要在您的自定义网关中实现。

#### __construct()

在您的构造函数中，您应该定义以下变量：

- `$this->id` - 您的网关的唯一 ID，例如 'your_gateway'
- `$this->icon` - 如果您想在前端显示网关名称旁边的图像，请输入指向图像的 URL。
- `$this->has_fields` - 布尔值。 如果您希望在结账页面显示支付字段（如果进行直接集成），则可以将其设置为 true。
- `$this->method_title` - 在管理页面上显示的支付方法标题。
- `$this->method_description` - 在管理页面上显示的支付方法描述。

您的构造函数还应该定义和加载设置字段：

```php
$this->init_form_fields();
$this->init_settings();
```

稍后我们将介绍 `init_form_fields()`，但它基本上定义了您的设置，然后使用 `init_settings()` 加载。

在调用 `init_settings()` 之后，您可以获取设置并将它们加载到变量中，例如：

```php
$this->title = $this->get_option( 'title' );
```

最后，您需要添加一个保存钩子，用于您的设置：

```php
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
```

#### init_form_fields()

使用此方法来设置 `$this->form_fields` - 这些是在您的网关设置页面上的管理界面中显示的选项，并使用 [WC 设置 API](https://developer.woocommerce.com/docs/settings-api/)。

您的网关的基本设置应包括 _启用/禁用_、_标题_ 和 _描述_：

```php
$this->form_fields = array(
    'enabled' => array(
        'title' => __( '启用 / 禁用', 'woocommerce' ),
        'type' => 'checkbox',
        'label' => __( '启用支票支付', 'woocommerce' ),
        'default' => 'yes'
    ),
    'title' => array(
        'title' => __( '标题', 'woocommerce' ),
        'type' => 'text',
        'description' => __( '这控制用户在结账过程中看到的标题。', 'woocommerce' ),
        'default' => __( '支票支付', 'woocommerce' ),
        'desc_tip' => true,
    ),
    'description' => array(
        'title' => __( '客户消息', 'woocommerce' ),
        'type' => 'textarea',
        'default' => ''
    )
);
```

#### process_payment( $order_id )

现在，我们来看网关最重要的部分：处理支付和处理订单。 `process_payment` 函数还会告诉 WooCommerce 将用户重定向到哪里，这通过返回的数组来实现。

以下是 Cheque 网关中 `process_payment` 函数的一个示例：

```php
function process_payment( $order_id ) {
global $woocommerce;
$order = new WC_Order( $order_id );

    // 标记为待处理 (我们正在等待支票)
    $order->update_status('on-hold', __( 'Awaiting cheque payment', 'woocommerce' ));

    // 清空购物车
    $woocommerce->cart->empty_cart();

    // 返回感谢页面重定向
    return array(
        'result' => 'success',
        'redirect' => $this->get_return_url( $order )
    );

}
```

如您所见，它的作用是：

- 获取并更新正在处理的订单
- 返回成功状态和重定向 URL (在本例中是感谢页面)

Cheque 网关将订单状态设置为“待处理”，因为无法自动验证支付。 但是，如果您正在构建一个直接网关，则可以在此处完成订单。 不要使用 `update_status` 来更新已支付的订单，而应该使用 `payment_complete`：

```php
$order->payment_complete();
```

这可以确保库存减少，并将状态更改为正确的值。

如果支付失败，您应该抛出错误并返回一个包含失败状态的数组：

```php
wc_add_notice( __('Payment error:', 'woothemes') . $error_message, 'error' );
return array(
    'result'   => 'failure'
);
```

WooCommerce 会捕获此错误并在结账页面上显示它。

库存级别是通过操作 (`woocommerce_payment_complete` 以及订单状态之间的转换) 进行更新的，因此在处理支付时，不再需要手动调用减少库存级别的函数。

### 更新订单状态和添加备注

可以使用订单类中的函数来更新订单状态。 只有当订单状态不是“处理中”时，才应这样做（在这种情况下，您应该使用 `payment_complete()`）。 更新为自定义状态的示例：

```php
$order = new WC_Order( $order_id );
$order->update_status('on-hold', __('Awaiting cheque payment', 'woothemes'));
```

上面的示例将状态更新为“待处理”，并添加一条备注，告知所有者正在等待支票。 可以在不更新订单状态的情况下添加备注；这用于添加调试消息：

```php
$order->add_order_note( __('IPN payment completed', 'woothemes') );
```

### 订单状态的最佳实践

- 如果订单已完成，但管理员需要手动验证支付，请使用 **On-Hold** 状态。
- 如果订单失败并且已创建，请将其设置为 **Failed** 状态。
- 如果支付已完成，请让 WooCommerce 处理状态，并使用 `$order->payment_complete()`。 WooCommerce 将使用 **Completed** 或 **Processing** 状态，并处理库存。

## 关于直接支付网关的说明

如果您正在创建一个高级、直接支付网关（即，一个在实际的结账页面上进行支付的网关），则涉及的步骤会更多。首先，您需要在网关构造函数中设置 `has_fields` 为 `true`：

```php
$this->has_fields = true;
```

这会告诉结账系统输出一个名为 'payment_box' 的区域，其中包含您定义的直接支付表单。

创建一个名为 `payment_fields()` 的方法，该方法包含您的表单，通常会包含信用卡详细信息。

下一个可选的方法是 `validate_fields()`。如果表单通过验证，则返回 `true`；如果验证失败，则返回 `false`。您可以使用 `wc_add_notice()` 函数来添加错误信息并在用户界面上显示。

最后，您需要在 `process_payment( $order_id )` 方法中添加支付代码。此代码接收已提交的表单数据，并尝试直接通过支付提供商进行支付。

如果支付失败，您应该输出一个错误信息并返回失败数组：

```php
wc_add_notice( __('Payment error:', 'woothemes') . $error_message, 'error' );
return array(
    'result'   => 'failure',
);
```

如果支付成功，您应该将订单状态设置为已支付，并返回成功数组：

```php
// 支付完成
$order->payment_complete();
```

```php
// 返回感谢页面重定向
return array(
    'result' => 'success',
    'redirect' => $this->get_return_url( $order )
);
```

## 使用支付网关回调（例如 PayPal IPN）

如果您正在构建一个网关，该网关会向您的商店发送回调，以告知您订单的状态，则需要在您的网关中添加代码来处理此操作。

添加回调和回调处理程序最好的方法是使用 WC-API 钩子。一个例子是 PayPal Standard 的做法。它将回调/IPN URL 设置为：

```php
str_replace( 'https:', 'http:', add_query_arg( 'wc-api', 'WC_Gateway_Paypal', home_url( '/' ) ) );
```

然后，它将处理程序挂载到该钩子上：

```php
add_action( 'woocommerce_api_wc_gateway_paypal', array( $this, 'check_ipn_response' ) );
```

当访问该 URL 时，WooCommerce 会调用您的网关并运行该操作。

有关更多信息，请参阅 [WC_API - WooCommerce API 回调](https://woocommerce.com/document/wc_api-the-woocommerce-api-callback/)。

## 网关中的钩子

重要的是要注意，在网关类中添加钩子可能不会触发。网关仅在需要时才会被加载，例如在结账过程中和在管理后台的设置页面。

如果您需要从您的类中挂载到 WordPress 事件，请将钩子放在类外部，或者使用 WC-API。