了解 WooCommerce 金流的基本架构後,我们来进行串接的实作,在开始前先回顾一下目前的外挂结构:
iron-pay
├── composer.json
├── composer.lock
├── iron-pay.php
├── src
│ ├── Options.php
│ └── Posts
│ └── ShopOrder
│ └── Metabox.php
└── vendor
首先我们假设铁人付这家金流公司的支付运作模式,以信用卡与虚拟帐号转帐两种付款方式为例,说明实际状况中可能会遇到的方法以及对应的设计方式,理论上同一家金流公司都会采用同一种逻辑,本文为了示范不同情境所以采用不同逻辑,实际情况以金流商提供的技术文件为主。
建立订单采用 HTTPS 协议, 将订单资料以 POST ( HTTP Method ) 传送,返回结果采用 JSON 格式,返回结果如下:
{
"status": "200",
"info": "订单建立成功",
"data": {
"html": "https://ironpay.test/payment",
"out_trade_no": "111"
}
}
当按下结帐按钮时要发起 API 请求,在经过验证请求来源後会回传付款网址,取得该网址後提供给 WC_Payment_Gateway
的 process_payment()
来进行跳转。
建立订单采用 HTTPS 协议, 将订单资料以 POST ( HTTP Method ) 传送後立即返回取号结果,等消费者转帐完成後金流商由服务器呼叫客户网站的通知付款网址。
接下来我们先在 src 建立 Gateways 资料夹,并新增 CreditCard.php
、VirtualAccount.php
、Request.php
以及 Response.php
四个档案,并另外在 src 资料夹增加一个工具类别 Utility.php
:
iron-pay
├── composer.json
├── composer.lock
├── iron-pay.php
├── src
│ ├── Gateways
│ │ ├── CreditCard.php
│ │ ├── Request.php
│ │ ├── Response.php
│ │ └── VirtualAccount.php
│ ├── Options.php
│ ├── Posts
│ │ └── ShopOrder
│ │ └── Metabox.php
│ └── Utility.php
└── vendor
CreditCard 与 VirtualAccount 分别为实作信用卡与虚拟帐号的类别,Request 为建立订单的请求类别,Response 负责处理从金流商回传的资料。根据上一篇的架构,CreditCard.php
内容如下:
<?php
namespace Irp\Gateways;
defined( 'ABSPATH' ) || exit;
/**
* Set payment class
*/
function add_irp_credit_card() {
class CreditCard extends \WC_Payment_Gateway {
public function __construct() {
$this->id = 'irp_credit_card';
$this->icon = '';
$this->has_fields = false;
$this->method_title = '铁人付信用卡';
$this->method_description = '使用铁人付信用卡付款';
$this->init_form_fields();
$this->init_settings();
$this->title = $this->get_option( 'title' );
$this->description = $this->get_option( 'description' );
add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
}
public static function register() {
add_filter( 'woocommerce_payment_gateways', array( __CLASS__, 'add_gateway_class' ) );
}
public function add_gateway_class( $methods ) {
$methods[] = __CLASS__;
return $methods;
}
public function init_form_fields() {
$this->form_fields = array(
'enabled' => array(
'title' => '启用/停用',
'label' => '启用付款',
'type' => 'checkbox',
'default' => 'no',
),
'title' => array(
'title' => '付款方式名称',
'type' => 'text',
),
'description' => array(
'title' => '付款方式描述',
'type' => 'textarea',
'css' => 'max-width: 400px;',
),
);
}
public function process_payment( $order_id ) {
// 处理付款请求
}
}
CreditCard::register();
}
add_action( 'plugins_loaded', 'Irp\Gateways\add_irp_credit_card' );
可以看到除了基本的架构外,我们新增了两个方法,一个是静态方法 reigster()
来管理勾点 woocommerce_payment_gateways
的类别名称注册,另一个 add_gateway_class()
方法来取得类别名称,最後再用 CreditCard::register()
呼叫勾点,就能把新增付款方式的功能包在同一个函式之中。
然後我们依样画葫芦来建立虚拟帐号的付款方式,开启 VirtualAccount.php,贴入以下程序码:
<?php
namespace Irp\Gateways;
defined( 'ABSPATH' ) || exit;
/**
* Set payment class
*/
function add_irp_virtual_account() {
class VirtualAccount extends \WC_Payment_Gateway {
public function __construct() {
$this->id = 'irp_virtual_account';
$this->icon = '';
$this->has_fields = false;
$this->method_title = '铁人付虚拟帐号';
$this->method_description = '使用铁人付虚拟帐号付款';
// 以下略
}
VirtualAccount::register();
}
add_action( 'plugins_loaded', 'Irp\Gateways\add_irp_virtual_account' );
完成後就能在 WooCommerce 设定页看到铁人付相关的付款方式:
在实务中,如果不同的 Gateway 有相同的属性或方法,可以把共用元素独立成抽象类别,然後让抽象类别继承 WC_Payment_Gateway
,要实作的付款方式再继承这个抽象类别,或是把相同的部分拆成 Trait,就能方便管理不同付款方式之间的共有属性与方法。
请求的部分因为有很多参数要汇整以及带有不同的请求方式,所以我们把请求拆成独立的 Request 类别来处理,然後再於付款方式里面的 process_payment()
来建立实例,并传入 Gateway 类别本身来完成请求,Request 基本架构如下:
<?php
namespace Irp\Gateways;
defined( 'ABSPATH' ) || exit;
class Request {
/**
* The gateway instance
*
* @var WC_Payment_Gateway
*/
protected $gateway;
/**
* Constructor
*
* @param WC_Payment_Gateway $gateway The payment gateway instance.
*/
public function __construct( $gateway ) {
$this->gateway = $gateway;
}
}
首先,我们要取得 Gateway 实例的相关属性与方法,所以使用 $gateway
来存放付款类别,并在建构式指定给 $gateway
属性。
<?php
namespace Irp\Gateways;
defined( 'ABSPATH' ) || exit;
class Request {
// 略
public function __construct( $gateway ) {
// 略
}
/**
* Build transaction args.
*
* @param WC_Order $order The order object.
* @return array
*/
public function get_transaction_args( $order ) {
$args = apply_filters(
$this->gateway->id . '_transaction_args' ,
array(
'nonce_str' => 'nonce',
'orgno' => '商家代号',
'out_trade_no' => $order->get_order_number(),
'secondtimestamp' => time(),
'total_fee' => $order->get_total(),
),
$order
);
return $args;
}
}
接下来透过 get_transaction_args()
方法来整理要传送给金流商的参数,这边的参数阵列会放在 apply_filters()
里面,这个 WordPress 内建的函式让我们可以自订勾点,第一个参数传勾点名称、第二个传阵列内容,第三个可以让这个勾点带入参数,之後我们就可以用 add_filter( 'irp_credit_card_transaction_args', 'callback', 10, 2 )
方式来动态新增参数:
function add_transaction_args( $args, $order ){
return array_merge(
$args,
array(
'returnurl' => home_url( 'wc-api/iron-pay' ),
'backurl' => home_url( 'wc-api/iron-pay-offline' ),
)
);
}
add_filter( 'irp_credit_card_transaction_args', 'add_transaction_args', 10, 2 )
因为每个付款方式可能会需要带入不同的参数,所以 Request 这边先写好共用的参数,在付款方式那边就可以使用勾点 Filter 额外新增需要的参数。
Utility 类别定义了一系列静态方法,像是做演算法的杂凑、产出随机字串、Log 方法等等,这里面的方法大部分可以通用在不同的专案之中,像是一种工具箱的概念。接下来是第一种呼叫 API 的方式:
<?php
namespace Irp\Gateways;
defined( 'ABSPATH' ) || exit;
class Request {
// 略
public function __construct( $gateway ) {
// 略
}
/**
* Build transaction args.
*
* @param WC_Order $order The order object.
* @return array
*/
public function get_transaction_args( $order ) {
// 略
}
/**
* Request api and get redirect url
*
* @param WC_Order $order The order object.
* @return void
*/
public function build_request( $order ) {
$order = new WC_Order( $order );
$options = array(
'method' => 'POST',
'timeout' => 60,
'body' => $this->get_transaction_args( $order ),
);
$response = wp_remote_request( '金流商 API 请求网址', $options );
if ( ! is_wp_error( $response ) ) {
// API 回传资料处理
$body = json_decode( wp_remote_retrieve_body( $response ), true );
// 取得跳转网址後返回
return $body['data']['html']; // 取得跳转网址:'https://ironpay.test/payment';
} else {
// API 请求失败的处理
}
}
}
这一段需要值得注意的是 wp_remote_request()
这个函式,它封装了 WordPress 的 HTTP 请求物件,透过它可以很方便的发起 HTTP 请求,第一个参数为请求网址,第二个为要传送的参数,我们把金流商所需的资料藉由 get_transaction_args()
取得後放在 body 进行传送,wp_remote_request()
的如果请求正确回传 JSON 时,则用 wp_remote_retrieve_body()
取得 JSON 後进行格式转换,如果请求的结果有误,使用 is_wp_error()
来进行判断。
第二种传送方法是使用表单:
<?php
namespace Irp\Gateways;
defined( 'ABSPATH' ) || exit;
class Request {
// 略
public function __construct( $gateway ) {
// 略
}
/**
* Build transaction args.
*
* @param WC_Order $order The order object.
* @return array
*/
public function get_transaction_args( $order ) {
// 略
}
/**
* Request api and get redirect url
*
* @param WC_Order $order The order object.
* @return void
*/
public function build_request( $order ) {
// 略
}
/**
* Generate the form and redirect to IronPay
*
* @param WC_Order $order The order object.
* @return void
*/
public function build_request_form( $order ) {
$order = new \WC_Order( $order );
try {
?>
<div>请稍候重新导向中...</div>
<form method="post" id="IrpForm" action="<?php echo esc_url( 'API 请求网址' ); ?>" accept="UTF-8" accept-charset="UTF-8">
<?php
$fields = $this->get_transaction_args( $order );
foreach ( $fields as $key => $value ) {
echo '<input type="hidden" name="' . esc_html( $key ) . '" value="' . esc_html( $value ) . '">';
}
?>
</form>
<script type="text/javascript">
document.getElementById('IrpForm').submit();
</script>
<?php
} catch ( Exception $e ) {
Utility::log( $e->getMessage() . ' ' . $e->getTraceAsString() );
}
}
}
准备好两种传送方式後,让我们回到 CreditCard.php,继续信用卡的付款实作 process_payment()
:
<?php
namespace Irp\Gateways;
defined( 'ABSPATH' ) || exit;
/**
* Set payment class
*/
function add_irp_credit_card() {
class CreditCard extends \WC_Payment_Gateway {
public function __construct() {
// 略
}
public static function register() {
// 略
}
public function add_gateway_class( $methods ) {
// 略
}
public function init_form_fields() {
// 略
}
public function process_payment( $order_id ) {
$order = new \WC_Order( $order_id );
$request = new Request( $this );
$return_url = $request->build_request( $order );
return array(
'result' => 'success',
'redirect' => $return_url,
);
}
}
CreditCard::register();
}
add_action( 'plugins_loaded', 'Irp\Gateways\add_irp_credit_card' );
我们先建立了 Request 实例并将付款方式类别本身当作参数传进去,然後使用 build_request()
来取得跳转网址,这边记得要先取得 WooCommerce 的 $order
订单物件後传给它,才能在 Request 实例中取得订单资讯,最後返回一个付款网址,并将取得的网址指定给 redirect。
接下来是处理虚拟转帐的付款实作,开启 VirtualAccount.php:
<?php
namespace Irp\Gateways;
defined( 'ABSPATH' ) || exit;
/**
* Set payment class
*/
function add_irp_virtual_account() {
class VirtualAccount extends \WC_Payment_Gateway {
public function __construct() {
// 略
add_action( 'woocommerce_receipt_' . $this->id, array( $this, 'receipt_page' ) );
}
public static function register() {
// 略
}
public function add_gateway_class( $methods ) {
// 略
}
public function init_form_fields() {
// 略
}
/**
* Process payment
*
* @param string $order_id The order id.
* @return array
*/
public function process_payment( $order_id ) {
$order = new \WC_Order( $order_id );
return array(
'result' => 'success',
'redirect' => $order->get_checkout_payment_url( true ),
);
}
/**
* Redirect to IronPay payment page
*
* @param WC_Order $order The order object.
* @return void
*/
public function receipt_page( $order ) {
$request = new Request( $this );
$request->build_request_form( $order );
}
}
VirtualAccount::register();
}
add_action( 'plugins_loaded', 'Irp\Gateways\add_irp_virtual_account' );
需要注意的地方有三个:
process_payment()
指定的跳转网址为 get_checkout_payment_url()
,这是指站内的付款页面
新增 receipt_page()
方法发起 Request 的前端表单传送方法 build_request_form()
在建构式新增勾点 woocommerce_receipt_irp_virtual_account
来触发 receipt_page()
方法
这边的运作的逻辑是当消费者按下结帐按钮後,直接跳转去结帐完成页,然後从结帐完成页产生的表单来做资料传送,这样就不用处理 API 的回传结果直接进行页面跳转判断。
当资料传送过去後,接下来就是要准备 WC API 来接收金流商的回传结果,下一篇我们继续处理接收回传资料的部分。
本文同步发表於:https://oberonlai.blog/tw/woocommerce-payment-request/#more
Android 手机 行动电话 小人图示 talkback 无障碍按钮 导览列 捷径 协助工具按钮开...
os.walk 找子目录下特定类型档案,鬼打墙好几天。也写了一两篇po上来,就当是"叠床架...
class Report(): def content(self): flex_message = ...
https://wolkesau.medium.com/kotlin-语言-5ad3d8f208e4...
承认我们都有一些资讯焦虑 我们生活在快速变动的时代,无时无刻都有新的产业跟名词冒出,数据驱动决策、区...