2.8 Commerce Payment 模块:支付系统

这是与 Commerce Order 几乎是同样重要的模块,电子商务系统最重要的东西就是订单和支付。

作为支付系统,主要的作用是可以接入各种各样的第三方支付网关,完成订单的 付款 退款 功能。 我们先来看一下,目前Drupal社区都提供了哪些支付网关的接入:

https://docs.drupalcommerce.org/commerce2/developer-guide/payments/available-gateways

可以看到官方上记录的支付网关模块已经达到了 114 个,十分惊人,其中包含了中国常用的 支付宝 和 微信支付。 还有一些没有被官方文档记录的支付网关,也存在一定的数量。

下面我们先来看一下 Commerce Payment 模块的设计。

数据结构

实体

  • commerce_payment 支付单。内容实体,每一笔支付对应一条记录。
  • commerce_payment_gateway 支付网关实例。配置实体,一个网关类型,可以配置多个网关实例。网关实例的意思是,配置了收款商家账号的 网关插件
  • commerce_payment_method 支付方法。内容实体,用于保存消费者用户在特定 网关插件 的支付账户信息。 比如银行卡支付,一个用户可能拥有很多张银行卡,每一张银行卡就相当于用户的一个支付方法。

需要特别注意的是,commerce_payment 和 commerce_payment_method 两个内容实体,它们是有 bundle 的,但是我们在代码里并没有发现它们对应的 配置实体。 我们知道,Drupal 核心提供的内容实体 bundle 机制是通过 配置实体 来实现的,这意味着无法通过 php 代码来创建实体。

我们知道,在Drupal7的时代,Entity API是一个社区模块,并不包含在核心,Drupal8的时候才并进核心。 但当我们去看 Entity模块的主页时,会发现上面依然有8.x的版本,这个Entity模块的 drupal8版本 和Drupal8核心的EntityAPI并不是重复的,一是些没有被采纳进核心的EntityAPI特性,有一些人觉得很有用,所以放在这里,方便开发时使用。 这些特性经过社区验证之后,有一点也许也会并进Drupal8核心。

扯得有点远,现在讲回正题,本模块使用了这个社区的Entity模块的一个特性,那就是通过编写插件的方式来创建 Entity Bundle。

我们查看 commerce_payment 和 commerce_payment_method 两个实体类型的定义时,在注解代码中发现一个名为 bundle_plugin_type 的属性, 它的值是一个插件类型ID,这意味着EntityAPI会为每一个该类型的插件创建一个对应的Entity Bundle。所有该用途的插件,都应该实现 Drupal\entity\BundlePlugin\BundlePluginInterface 接口:

<?php

namespace Drupal\entity\BundlePlugin;

use Drupal\Component\Plugin\PluginInspectionInterface;

/**
 * Interface for plugins which act as entity bundles.
 */
interface BundlePluginInterface extends PluginInspectionInterface {

  /**
   * Builds the field definitions for entities of this bundle.
   *
   * Important:
   * Field names must be unique across all bundles.
   * It is recommended to prefix them with the bundle name (plugin ID).
   *
   * @return \Drupal\entity\BundleFieldDefinition[]
   *   An array of bundle field definitions, keyed by field name.
   */
  public function buildFieldDefinitions();

}

该接口定义了一个 buildFieldDefinitions() 方法,该方法用于返回要创建的 Bundle 的自有字段定义。 这里的字段定义应该使用 Drupal\entity\BundleFieldDefinition::create() 方法来创建。

我们从代码中可以看到,commerce_payment 和 commerce_payment_method 两个实体类型对应的Bundle 插件类型为 @CommercePaymentType 和 @CommercePaymentMethodType

订单实体字段

本模块在订单实体添加了两个字段:

  • payment_gateway 记录订单所使用的支付网关实例。
  • payment_method 记录订单所使用的支付方法。

@CommercePaymentGateway 支付网关插件

支付网关插件类型是有一点儿复杂,这是灵活性的兼容性的代价。但是当我们去理清它所支持支付模式时, 会发现其实也不是很复杂,在设计上还是很简约很优雅的。

支付服务商 和 支付网关

阿里支付宝公司 和 腾讯公司 就是中国最大的两个 支付服务商

它们分别提供了 支付宝 和 微信支付 两个网络支付产品。 从开发人员的角度看,它们就是 支付网关,我们需要编写程序来与它们交互。

支付模式

每种支付网关的业务模式可能不太一样,但是主要可以分为以下两类:

  • 在站支付

    指用户无需离开业务网站,即可进行授权,完成支付。

  • 离站支付

    指用户必须跳转到支付网关的站点进行授权,完成支付,然后再跳转回业务网站。

此外系统还定义了一种特殊的支付模式,叫 Manual 手动支付,这种模式表示没有支付网关的支付模式。 这个模式可以用来实现线下现金支付、线下银行转账,等等,这类支付方式。

这三种模式的支付网关插件分别需要实现如下接口:

  • OnsitePaymentGatewayInterface
  • OffsitePaymentGatewayInterface
  • ManualPaymentGatewayInterface

开发者应该细读这三个接口的代码注析,上面把三种模式的流程和接口实现方法都说得很明白,此处不再作翻译。

支持性接口

上文提及的三个接口,实际上还继承了一些支持性接口:

  • SupportsAuthorizationsInterface 支持授权

    系统把 Authorize 和 Capture 定义为在与支付网关交互过程中的两个操作。

    Authorize 指的是向支付服务商网关发关请求,要求授权支付一定金额。

    Capture 指的是利用已获得的授权凭证,请求支付服务商网关执行一笔交易,把一定金额从用户的账号,转移到商家的收款账号。

    有的支付网关是把这两个操作一起完成的,有的支付网关是把这两个操作分开的。 但不管怎么样,系统在 Payment process 这个结账面板提供了一个配置选项,让管理员可以把系统配置为两种方式的任意一种。 如果是 Authorize and capture,两个操作一起完成,如果是 Authorize only (requires manual capture after checkout), 则需要在结账流程完成后,管理员到订单支付管理页面点击 Capture 按钮执行操作。 当然,默认配置是 Authorize and capture,分开操作应该是非常特殊的业务情景才需要用到。

    如果交易方式是 Authorize only,此外还有一个 Void 操作,在 Capture 之前可以点击 Void 按钮取消交易。Capture 操作一般是有时效性的,如果超过了这个时效,Authorize 操作创建的交易也会自动失效, 这个时效是用 commerce_payment 实体的 authorized 和 expires 字段记录的。

    讲回这个接口,其实它只有一个方法,capturePayment(PaymentInterface $payment, Price $amount = NULL), 只有支持把两个操作分开的支付网关,才能实现此接口。

  • SupportsVoidsInterface 支持取消交易

    参考上文关于 SupportsAuthorizationsInterface 接口的说明。 实现了此接口的支付网关,订单支付管理页面才会出现 Void 按钮。

  • SupportsStoredPaymentMethodsInterface 支持保存用户支付方法

    关于什么是 支付方法,请参考上文关于 commerce_payment_method 内容实体的说明。 如果一个支付网关插件能够支持用户账户信息重用的话,那它可以实现这个接口,使得用户在后续交易中不必再次输入账户信息, 同时,用户还可以在个人中心页面管理自己的 支付方法,添加或删除。

  • SupportsUpdatingStoredPaymentMethodsInterface 支持更新用户支付方法

    在实上 SupportsStoredPaymentMethodsInterface 接口的基础上,如果实现此接口, 用户还可以在个人中心页面编辑自己已有的 支付方法

  • SupportsNotificationsInterface 支持异步交易状态通知

    当请求支付网站执行交易时,可能会顺利成功,也不一定能成功,比如账户余额不足等原因。 有些支付网关支持用HTTP请求的方式,异步向商家服务器发送交易状态变动通知, 可以实现这个接口,在 onNotify(Request $request)方法中读取这个异步HTTP请求,相应地对本地的数进行更新。

  • SupportsRefundsInterface 支持退款

    在部分的网关都支持退款,当插件实现了此接口后,网站管理员可以在订单支付管理页面看到一个 Refund 按钮, 点击之后会打开一个退款表单,填写退款金额后,可以请求插件执行退款操作,插件开发者可以在refundPayment(PaymentInterface $payment, Price $amount = NULL) 方法中编写退款业务代码。

    值得注意的是,它是支持部分退款的,如果网关不支持部分退款,那么开发者可以自行检查用户输入的金额, 用表单错误信息提示用户不能部分退款。

  • HasPaymentInstructionsInterface 支持在支付完成时显示提示信息

    支付网关实现了此接口,系统就会把提示信息通过 hook_preprocess_commerce_checkout_completion_message() 勾子显示到 completion_message 结账面板中。

结账面板

支付是与结账过程紧密相关的,本模块对 Commerce Ckeckout 模块有关键的依赖。 本模块实现了两个结账面板:

  • payment_information 让用户先择订单所使用的支付方案

    这个面板替换了 billing_information 面板,因为开发者认为 billing_information 的信息是支付过程中必要的, 所以他把 billing_information 的功能写到了 payment_information 中,并把 billing_information 删除掉。 这并不太友好,社区很多人都提出了这个问题,相信后面会有所改进。

  • payment_process 执行支付交易

    用于显示 off-site 模式支付网关插件定义的 offsite-payment 表单,以及执行插件的相关调用。

 

 

欢迎大家加QQ群讨论:747120338

评论 (写第一个评论)