# WooCommerce 支付令牌 API

WooCommerce 2.6 引入了一个 API，用于存储和管理网关的支付令牌。用户还可以从他们的账户设置中管理这些令牌，并在结账时从已保存的支付令牌中进行选择。

本指南提供了一些有用的教程，用于使用新的 API，以及您可用的各种方法。

## 教程

### 为您的网关添加支付令牌 API 支持

在以下示例中，我们将使用 Simplify Commerce 网关。

#### 步骤 0：扩展正确的网关基础类

WooCommerce 提供了两个用于网关的基础类。这些类与令牌 API 一起在 2.6 中引入。它们是 `WC_Payment_Gateway_CC`（用于基于信用卡的令牌）和 `WC_Payment_Gateway_eCheck`（用于基于电子支票的令牌）。它们包含一些用于在结账时生成支付表单的有用代码，并且应该能够覆盖大多数情况。

如果您需要，您还可以通过扩展抽象类 `WC_Payment_Gateway` 来实现您自己的网关基础类。

由于 Simplify 处理信用卡，因此我们扩展了信用卡网关。

```php
class WC_Gateway_Simplify_Commerce extends WC_Payment_Gateway_CC
```

#### 步骤 1：'Supports' 数组

我们需要告诉 WooCommerce 我们的网关支持令牌化。与其他网关功能一样，这在网关的 `__construct` 方法中，在一个名为 `supports` 的数组中定义。

这是 Simplify 的数组：

```php
$this->supports = array(
    'subscriptions',
    'products',
    ...
    'refunds',
    'pre-orders',
);
```

将 `tokenization` 添加到此数组中。

#### 第二步：定义一种方法，用于从“我的账户”中添加/保存新的付款方式

当从“我的账户”部分添加新的付款方式时，将调用表单处理程序，该处理程序会调用网关的 `add_payment_method` 方法。

您的 `add_payment_method` 应该返回一个包含两个键的数组，一个 `result` 字符串和一个 `redirect` URL：

```php
array(
   'result'   => 'success', // 或者 'failure'
   'redirect' => wc_get_endpoint_url( 'payment-methods' ),
);
```

在进行任何验证（即，确保从支付提供商处获取了所需的令牌和数据）后，您可以开始构建一个新的令牌，方法是创建以下类之一的实例：`WC_Payment_Token_CC` 或 `WC_Payment_Token_eCheck`。 类似于网关，您也可以扩展抽象类 `WC_Payment_Token` 并定义您自己的令牌类型，如果需要的话。有关这三个类及其方法的更多信息，请参阅本文档的后续部分。

由于 Simplify 使用信用卡，因此我们将使用信用卡类。

`$token = new WC_Payment_Token_CC();`

我们将使用各种 `set_` 方法来传递有关我们令牌的信息。 首先，我们将传递令牌字符串和网关 ID，以便将令牌与 Simplify 相关联。

```php
$token->set_token( $token_string );
$token->set_gateway_id( $this->id ); // `$this->id` 引用在 `__construct` 中设置的网关 ID
```

此时，我们可以设置任何其他我们想要存储在令牌中的必要信息。 信用卡需要卡类型（Visa、MasterCard 等）、卡号的后四位、到期月份和到期年份。

```php
$token->set_card_type( 'visa' );
$token->set_last4( '1234' );
$token->set_expiry_month( '12' );
$token->set_expiry_year( '2018' );
```

在大多数情况下，您还希望将令牌与特定用户关联：

`$token->set_user_id( get_current_user_id() );`

最后，在构建了令牌对象后，我们可以将令牌保存到数据库中。

`$token->save();`

如果令牌已成功保存，`save` 将返回 `true`；如果发生错误（例如缺少字段），则返回 `false`。

#### 第三步：在结账时保存付款方式

WooCommerce 还允许客户在“我的账户”之外，在结账过程中保存新的付款令牌。您需要添加一些代码到您的网关的 `process_payment` 函数中，以使其正常工作。

要确定是否需要保存新的付款方式，您可以检查以下 POST 字段，如果选择了“保存到账户”复选框，则该字段应返回 `true`。

`wc-{$gateway_id}-new-payment-method`

如果您之前为用户提供了已保存的令牌，您还可以查看 `wc-{$gateway_id}-payment-token` 字段，如果该字段的值为 `new`，则表示选择了“使用新卡”/“使用新的付款方式”单选按钮。

一旦确定需要保存令牌，您就可以像在第二步中那样，使用 `set_` 和 `save` 方法来保存令牌。

#### 第 4 步：处理支付时获取令牌

如果用户选择了某个令牌，您需要在网关中处理支付时检索该已保存的令牌。这应该也在您的 `process_payment` 方法中完成。

您可以使用类似于以下的代码片段来检查是否应使用现有令牌：

```php
if ( isset( $_POST['wc-simplify_commerce-payment-token'] ) && 'new' !== $_POST['wc-simplify_commerce-payment-token'] ) {
```

`wc-{$gateway_id}}-payment-token` 将返回所选令牌的 ID。

然后，您可以从 ID 加载令牌（稍后在本文档中将详细介绍 `WC_Payment_Tokens` 类）：

```php
$token_id = wc_clean( $_POST['wc-simplify_commerce-payment-token'] );
$token    = WC_Payment_Tokens::get( $token_id );
```

这**不**会检查加载的令牌是否属于当前用户。您可以使用简单的检查来完成此操作：

```php
// 令牌用户 ID 与当前用户 ID 不匹配... 停止支付处理。
if ( $token->get_user_id() !== get_current_user_id() ) {
    // 可选地使用 `wc_add_notice` 显示通知
    return;
}
```

在加载了令牌并执行了任何必要的检查后，您可以使用 `$token->get_token()` 来获取实际的令牌字符串（以便将其传递给您的支付提供商）。

### 创建新的令牌类型

如果您提供的电子支票 (eCheck) 和信用卡 (CC) 令牌类型不能满足您的需求，您可以扩展抽象类 `WC_Payment_Token` 并创建新的令牌类型。 如果您这样做，则需要包含一些内容。

#### 第 0 步：扩展 `WC_Payment_Token` 并命名您的类型

首先，扩展 `WC_Payment_Token` 并为新的类型提供一个名称。 让我们看看 `eCheck` 令牌类的构建方式，因为它是在 WooCommerce 核心中提供的最基本的令牌类型。

一个最基本的令牌文件的外观如下：

```php
class WC_Payment_Token_eCheck extends WC_Payment_Token {

    /** @protected string 令牌类型字符串 */
    protected $type = 'eCheck';

}
```

此令牌类型的名称为 'eCheck'。 `$type` 中提供的值需要与类名匹配（即：`WC_Payment_Token_$type`）。

#### 第一步：提供一个验证方法

在将令牌保存到数据库之前，会执行一些基本的验证。`WC_Payment_Token` 检查实际令牌值是否已设置，以及上面定义的 `$type`。 如果您想验证其他数据的存在（例如，eCheck 需要最后 4 位数字）或长度（到期月份应为 2 个字符），您可以提供自己的 `validate()` 方法。

`validate()` 方法应返回 `true`，如果一切正常；如果出现问题，则返回 `false`。

始终确保在添加自己的逻辑之前，调用 `WC_Payment_Token` 的 `validate()` 方法。

```php
public function validate() {
    if ( false === parent::validate() ) {
	       return false;
	}
```

现在，我们可以添加一些逻辑来处理“最后 4 位”数字。

```php
if ( ! $this->get_last4() ) {
    return false;
}
```

最后，如果顺利执行到 `validate()` 方法的末尾，则返回 `true`。

```php
    return true;
}
```

#### 第二步：为额外数据提供 `get_` 和 `set_` 方法

现在，您可以为每个您想要公开的数据添加自己的方法。 提供了方便的函数，可以轻松地存储和检索数据。 所有数据都存储在元数据表中，因此您无需创建自己的表或向现有表添加新字段。

为每个您想要捕获的数据提供一个 `get_` 和 `set_` 方法。 对于 eCheck，这是“last4”，表示支票的最后 4 位数字。

```php
public function get_last4() {
    return $this->get_meta( 'last4' );
}

public function set_last4( $last4 ) {
    $this->add_meta_data( 'last4', $last4, true );
}
```

就此为止！ 这些元数据函数由 [WC_Data](https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/includes/abstracts/abstract-wc-data.php) 提供。

#### 第三步：使用您的新令牌类型

现在，您可以直接在创建新令牌时使用您的新令牌类型：

```php
`$token = new WC_Payment_Token_eCheck();`
// 设置令牌属性
$token->save()
```

或者，当使用 `WC_Payment_Tokens::get( $token_id )` 时，它将返回您的新令牌类型。

## 类

### WC_Payment_Tokens

此类提供了一组有用的方法，用于与支付令牌进行交互。 所有方法都是静态的，可以在不创建类的实例的情况下调用。

#### get_customer_tokens( $customer_id, $gateway_id = '' )

返回一个包含 `$customer_id` 中指定客户的令牌对象的数组。 您可以通过提供网关 ID 来按网关进行过滤。

```php
// 获取当前用户的全部令牌
$tokens = WC_Payment_Tokens::get_customer_tokens( get_current_user_id() );
// 获取用户 42 的全部令牌
$tokens = WC_Payment_Tokens::get_customer_tokens( 42 );
// 获取当前用户的所有 Simplify 令牌
$tokens = WC_Payment_Tokens::get_customer_tokens( get_current_user_id(), 'simplify_commerce' );
```

#### get_customer_default_token( $customer_id )

返回标记为“默认”的支付令牌对象（即在结账时自动选择的令牌）。 如果用户没有默认支付令牌/没有支付令牌，此函数将返回 null。

```php
// 获取当前用户的默认支付令牌
$token = WC_Payment_Tokens::get_customer_default_token( get_current_user_id() );
// 获取用户 520 的默认支付令牌
$token = WC_Payment_Tokens::get_customer_default_token( 520 );
```

#### get_order_tokens( $order_id )

订单可以关联支付令牌（例如，用于订阅产品和续费）。 您可以使用此函数获取关联的令牌列表。 另外，您还可以使用 `WC_Order` 的 `get_payment_tokens()` 函数来获得相同的结果。

```php
// 获取与订单 25 关联的支付令牌
$tokens = WC_Payment_Tokens::get_order_tokens( 25 );
// 通过 WC_Order 获取与订单 25 关联的支付令牌
$order = wc_get_order( 25 );
$tokens =  $order->get_payment_tokens();
```

#### get( $token_id )

返回由提供的 `$token_id` 指定的单个支付令牌对象。

```php
// 获取支付令牌 52
$token = WC_Payment_Tokens::get( 52 );
```

#### delete( $token_id )

删除提供的令牌。

```php
// 删除支付令牌 52
WC_Payment_Tokens::delete( 52 );
```

#### set_users_default( $user_id, $token_id )

将提供的令牌 (`$token_id`) 设置为提供的用户 (`$user_id`) 的默认令牌。 它会移除当前设置为默认的令牌，并设置新的令牌。

```php
// 将用户 17 的默认令牌设置为令牌 82
WC_Payment_Tokens::set_users_default( 17, 82 );
```

#### get_token_type_by_id( $token_id )

如果您知道令牌的 ID，但不知道它的类型（信用卡、电子支票等），可以使用此函数。

```php
// 确定支付令牌 23 是信用卡令牌
$type = WC_Payment_Tokens::get_token_type_by_id( 23 );
```

### WC_Payment_Token_CC

`set_` 方法**不会**更新数据库中的令牌。 您必须调用 `save()`、`create()`（仅用于新令牌）或 `update()`（仅用于现有令牌）。

#### validate()

确保信用卡令牌存储了后 4 位数字，有效期年份的格式为 YYYY，有效期月份的格式为 MM，卡类型以及实际的令牌。

```php
$token = new WC_Payment_Token_CC();
$token->set_token( 'token here' );
$token->set_last4( '4124' );
$token->set_expiry_year( '2017' );
$token->set_expiry_month( '1' ); // 长度不正确
$token->set_card_type( 'visa' );
var_dump( $token->validate() ); // bool(false)
$token->set_expiry_month( '01' );
var_dump( $token->validate() ); // bool(true)
```

#### get_card_type()

获取卡类型（visa、mastercard 等）。

```php
$token = WC_Payment_Tokens::get( 42 );
echo $token->get_card_type();
```

#### set_card_type( $type )

设置信用卡类型。这是一个自由格式的文本字段，但可以使用以下值，WooCommerce 将显示格式化的标签。可以通过 `woocommerce_credit_card_type_labels` 过滤器添加新的标签。

```php
$token = WC_Payment_Tokens::get( 42 );
$token->set_last4( 'visa' );
echo $token->get_card_type(); // 返回 visa
```

支持的类型/标签：

```php
array(
	'mastercard'       => __( 'MasterCard', 'woocommerce' ),
	'visa'             => __( 'Visa', 'woocommerce' ),
	'discover'         => __( 'Discover', 'woocommerce' ),
	'american express' => __( 'American Express', 'woocommerce' ),
	'diners'           => __( 'Diners', 'woocommerce' ),
	'jcb'              => __( 'JCB', 'woocommerce' ),
) );
```

#### get_expiry_year()

获取信用卡的到期年份。

```php
$token = WC_Payment_Tokens::get( 42 );
echo $token->get_expiry_year;
```

#### set_expiry_year( $year )

设置信用卡的到期年份。格式为 YYYY。

```php
$token = WC_Payment_Tokens::get( 42 );
$token->set_expiry_year( '2018' );
echo $token->get_expiry_year(); // 返回 2018
```

#### get_expiry_month()

获取信用卡的到期月份。

```php
$token = WC_Payment_Tokens::get( 42 );
echo $token->get_expiry_month();
```

#### set_expiry_month( $month )

设置信用卡的到期月份。格式为 MM。

```php
$token = WC_Payment_Tokens::get( 42 );
$token->set_expiry_year( '12' );
echo $token->get_expiry_month(); // 返回 12
```

#### get_last4()

获取存储的信用卡号码的后 4 位数字。

```php
$token = WC_Payment_Tokens::get( 42 );
echo $token->get_last4();
```

#### set_last4( $last4 )

设置存储的信用卡号码的后 4 位数字。

```php
$token = WC_Payment_Tokens::get( 42 );
$token->set_last4( '2929' );
echo $token->get_last4(); // 返回 2929
```

### WC_Payment_Token_eCheck

`set_` 方法**不会**更新数据库中的令牌。您必须调用 `save()`、`create()`（仅适用于新令牌）或 `update()`（仅适用于现有令牌）。

#### validate()

确保 eCheck 令牌存储了后 4 位数字以及实际的令牌。

```php
$token = new WC_Payment_Token_eCheck();
$token->set_token( 'token here' );
var_dump( $token->validate() ); // bool(false)
$token->set_last4( '4123' );
var_dump( $token->validate() ); // bool(true)
```

#### get_last4()

获取存储的账户号码的后 4 位数字。

```php
$token = WC_Payment_Tokens::get( 42 );
echo $token->get_last4();
```

#### set_last4( $last4 )

设置存储的信用卡号码的后 4 位数字。

```php
$token = WC_Payment_Tokens::get( 42 );
$token->set_last4( '2929' );
echo $token->get_last4(); // 返回 2929
```

### WC_Payment_Token

您不应该直接使用 `WC_Payment_Token` 类。请使用其中一个内置的令牌类 (`WC_Payment_Token_CC` 用于信用卡，`WC_Payment_Token_eCheck` 用于电子支票)。 如果上述方法都不适用于您，您可以扩展此类。 此部分中定义的所有方法都可供这些类使用。

`set_` 方法**不会**更新数据库中的令牌。 您必须调用 `save()` (保存)，`create()` (仅用于创建新令牌)，或 `update()` (仅用于更新现有令牌)。

#### get_id()

获取令牌的 ID。

```php
// 获取用户 ID 为 26 的默认令牌的 ID
$token = WC_Payment_Tokens::get_customer_default_token( 26 );
echo $token->get_id();
```

#### get_token()

获取实际的令牌字符串（用于与支付处理商通信）。

```php
$token = WC_Payment_Tokens::get( 49 );
echo $token->get_token();
```

#### set_token( $token )

设置令牌字符串。

```php
// $api_token 来自向支付处理商的 API 请求。
$token = WC_Payment_Tokens::get( 42 );
$token->set_token( $api_token );
echo $token->get_token(); // 返回我们的令牌
```

#### get_type()

获取令牌的类型。可以是 CC（信用卡）或 eCheck（电子支票）。 这也会返回任何新引入的类型。

```php
$token = WC_Payment_Tokens::get( 49 );
echo $token->get_type();
```

#### get_user_id()

获取与令牌关联的用户 ID。

```php
$token = WC_Payment_Tokens::get( 49 );
if ( $token->get_user_id() === get_current_user_id() ) {
    // 此令牌属于当前用户。
}
```

#### set_user_id( $user_id )

将令牌与用户关联。

```php
$token = WC_Payment_Tokens::get( 42 );
$token->set_user_id( '21' ); // 此令牌现在属于用户 21。
echo $token->get_last4(); // 返回 2929
```

#### get_gateway_id

获取与令牌关联的网关。

```php
$token = WC_Payment_Tokens::get( 49 );
$token->get_gateway_id();
```

#### set_gateway_id( $gateway_id )

设置与令牌关联的网关。 这应该与您网关中定义的 "ID" 匹配。 例如，'simplify_commerce' 是核心中 Simplify 实现的 ID。

```php
$token->set_gateway_id( 'simplify_commerce' );
echo $token->get_gateway_id();
```

#### is_default()

如果令牌被标记为用户的默认令牌，则返回 true。 默认令牌将在结账时自动选择。

```php
$token = WC_Payment_Tokens::get( 42 ); // 令牌 42 是用户 3 的默认令牌
var_dump( $token->is_default() ); // 返回 true
$token = WC_Payment_Tokens::get( 43 ); // 令牌 43 是用户 3 的令牌，但不是默认的
var_dump( $token->is_default() ); // 返回 false
```

#### set_default( $is_default )

切换一个令牌的“默认”标志。 传递 `true` 将其设置为默认值，如果它只是另一个令牌，则传递 `false`。 这**不会**取消设置任何其他可能设置为默认值的令牌。 您可以使用 `WC_Payment_Tokens::set_users_default()` 来处理这种情况。

```php
$token = WC_Payment_Tokens::get( 42 ); // 令牌 42 是用户 3 的默认令牌
var_dump( $token->is_default() ); // 返回 true
$token->set_default( false );
var_dump( $token->is_default() ); // 返回 false
```

#### validate()

进行检查，以确保令牌和令牌类型（信用卡、电子支票等）都存在。 请参阅 `WC_Payment_Token_CC::validate()` 或 `WC_Payment_Token_eCheck::validate()` 以了解用法。

#### read( $token_id )

从数据库加载现有的令牌对象。 请参阅 `WC_Payment_Tokens::get()`，它是此函数的别名。

```php
// 加载信用卡令牌，ID 为 55，用户 ID 为 5
$token = WC_Payment_Token_CC();
$token->read( 55 );
echo $token->get_id(); // 返回 55
echo $token->get_user_id(); // 返回 5
```

#### update()

更新现有的令牌。 这将获取所有更改的字段（`set_` 函数），并将它们实际保存到数据库中。 返回 `true` 或 `false`，具体取决于是否成功。

```php
$token = WC_Payment_Tokens::get( 42 ); // 信用卡令牌
$token->set_expiry_year( '2020' );
$token->set_expiry_month( '06 ');
$token->update();
```

#### create()

这将创建一个新的令牌到数据库中。 因此，一旦您构建了它，`create()` 将在数据库中创建一个新的令牌，其中包含详细信息。 返回 `true` 或 `false`，具体取决于是否成功。

```php
$token = new WC_Payment_Token_CC();
// 设置 last4、到期年份、月份和卡类型
$token->create(); // 保存到数据库
```

#### save()

`save()` 可以替代 `update()` 和 `create()`。 如果您正在处理现有的令牌，则 `save()` 将调用 `update()`。 新的令牌将调用 `create()`。 返回 `true` 或 `false`，具体取决于是否成功。

```php
// 调用 update
$token = WC_Payment_Tokens::get( 42 ); // 信用卡令牌
$token->set_expiry_year( '2020' );
$token->set_expiry_month( '06 ');
$token->save();
// 调用 create
$token = new WC_Payment_Token_CC();
// 设置 last4、到期年份、月份和卡类型
$token->save();
```

#### delete()

从数据库中删除一个令牌。

```php
$token = WC_Payment_Tokens::get( 42 );
$token->delete();
```