支付方式是所有电商程序的重头戏,卖家最关心的是怎么让客户的钱进到自己的账户。WooCommerce对支付方式的扩展有详细的介绍,对电商一窍不通的我决定先从文档开始学习。要知道如何在WooCommerce插件中增加支付接口,就要研究它的Payment Gateway API。

Payment Gateway API官方文档

支付网关分类

支付网关通常用一下的几种形式呈现:

基于表单—— 用户必须点击表单中的按钮,表单会提交到支付接口提供商的网站进行处理,例如PayPal standard

基于iframe——网关支付系统直接在网站的iframe中显示,例如SagePay Form

直接显示(Direct)——支付网关处理付款所需要的信息(字段)直接显示在网站的结账页面(checkout page),用户点击“place order”按钮后付款成功,例如PayPal Pro

离线付款(offline)——付款过程无需网络,例如:支票,银行转账

直接在自己网站进行支付(Direct)需要配置服务器提供安全保证(启用SSL认证等),可能还需要通过PCI认证(Payment Card Industry,通常指PCI DDS:Payment Card Industry Data Security Standard,支付卡产业数据安全标准)。如果想避免这些麻烦,使用支付网关来帮助你完成付款处理。

创建一个基本的支付网关

新的支付方式应该以WordPress插件的形式添加到WooCommerce中。创建支付网关插件之前,先来看一个简单的例子,这个例子已经集成在WooCommerce核心文件中。

解析支票支付网关

支票支付网关并没做什么复杂的事,也不与第三方产生数据交换——但它可以向你展示如何定义和加载自定义设置选项,是一个好的开始。

首先,新的网关应该是继承WC_Payment_Gateway的class,WC_Payment_Gateway是基础,负责产生设置选项、定义相关功能。

class WC_Cheque extends WC_Payment_Gateway {

接着定义构造函数(constructor)

public function __construct() {
    $this->id = 'cheque';
    $this->icon = apply_filters('woocommerce_cheque_icon', '');
    $this->has_fields = false;
 
    // Load the form fields.
    $this->init_form_fields();
 
    // Load the settings.
    $this->init_settings();
 
    // Define user set variables
    $this->title = $this->settings['title'];
    $this->description = $this->settings['description'];
 
    // Actions
    add_action('woocommerce_update_options_payment_gateways', array(&$this, 'process_admin_options'));
    add_action('woocommerce_thankyou_cheque', array(&$this, 'thankyou_page'));
 
    // Customer Emails
    add_action('woocommerce_email_before_order_table', array(&$this, 'email_instructions'), 10, 2);
}

id 是网关的唯一标识,是WooCommerce区分不同支付方式的重要依据

icon 表示网关的icon图标,应该是icon的图片url,会在结账页面显示

has_fields 如果设置为true,网关支付需要的字段会直接显示在结账页面,如果你使用前面提到的“Direct”方式,这项要设置为true

init_form_fields 会在下文解释

title 支付网关的名称,会显示在结账页面,在本例中,网关名称从后台设置中读取

description 支付网关的描述,显示在结账页面

Actions 向三个actions增加了功能:

woocommerce_update_options_payment_gateways —— 保存后台设置时执行,插入的函数负责保存我们的自定义设置

woocommerce_thankyou_cheque —— 付款结束后的thankyou页面执行该action

woocommerce_email_before_order_table —— 产生email模板时执行,可以用来加入自定义字段

接下来描述init_form_fields的功能,该方法定义我们要在后台显示的选项字段

$this->form_fields = array(
    'enabled' => array(
        'title' => __( 'Enable/Disable', 'woothemes' ),
        'type' => 'checkbox',
        'label' => __( 'Enable Cheque Payment', 'woothemes' ),
        'default' => 'yes'
    ),
    'title' => array(
        'title' => __( 'Title', 'woothemes' ),
        'type' => 'text',
        'description' => __( 'This controls the title which the user sees during checkout.', 'woothemes' ),
        'default' => __( 'Cheque Payment', 'woothemes' )
    ),
    'description' => array(
        'title' => __( 'Customer Message', 'woothemes' ),
        'type' => 'textarea',
        'description' => __( 'Let the customer know the payee and where they should be
 sending the cheque too and that their order won\'t be shipping until you receive it.', 'woothemes' ),
        'default' => 'Please send your cheque to Store Name, Store Street, Store Town,
 Store State / County, Store Postcode.'
    )
);

定义了三个选项分别用于设置:是否启用该网关、网关的名称和给用户的消息。

每个选项以下面的格式定义

'setting-name' => array(
    'title' => __( 'Title for setting', 'woothemes' ),
    'type' => 'checkbox|text|textarea',
    'label' => __( 'Label for checkbox setting', 'woothemes' ),
    'description' => __( 'Description for setting' ),
    'default' => 'default value'
),

产生的是表单元素,是产生input还是checkbox或者textarea等,是由type参数决定的

定义完选项,就要知道如何显示,于是有了下一个function

public function admin_options() {
    ?>
    <h3><?php _e('Cheque Payment', 'woothemes'); ?></h3>
    <p><?php _e('Allows cheque payments. Why would you take cheques in this day and 
age? Well you probably wouldn\'t but it does allow you to make test purchases for testing 
order emails and the \'success\' pages etc.', 'woothemes'); ?></p>
    <table class="form-table">
    <?php
        // Generate the HTML For the settings form.
        $this->generate_settings_html();
    ?>
    </table>
    <?php
} // End admin_options()

接下来要处理用户在结账页面看到的内容

function payment_fields() {
    if ($this->description) echo wpautop(wptexturize($this->description));
}
 
function thankyou_page() {
    if ($this->description) echo wpautop(wptexturize($this->description));
}

payment_fields负责显示结账页面所需的支付信息,例如选择direct方式时,可能需要用户输入信用卡卡号等。支票方式无需额外信息,所以这里只显示描述字段。

thankyou_page,该函数在thankyou页面执行,可以用来输出一些自定义信息

接下来是最重要的功能——接受付款和处理订单。支票支付网关不会产生在线支付行为,但需要将订单状态改成on-hold(告知管理员该订单正在等待支票付款),并告诉WooCommerce将用户定向到thankyou页面,这是通过返回success状态(数组形式返回)完成的

function process_payment( $order_id ) {
    global $woocommerce;
 
    $order = &new WC_Order( $order_id );
 
    // Mark as on-hold (we're awaiting the cheque)
    $order->update_status('on-hold', __('Awaiting cheque payment', 'woothemes'));
 
    // Remove cart
    $woocommerce->cart->empty_cart();
 
    // Empty awaiting payment session
    unset($_SESSION['order_awaiting_payment']);
 
    // Return thankyou redirect
    return array(
        'result'    => 'success',
        'redirect'  => add_query_arg('key', $order->order_key, add_query_arg('order', 
$order_id, get_permalink(get_option('woocommerce_thanks_page_id'))))
    );
 
}

最后,将该支付网关加入到WooCommerce中

function add_cheque_gateway( $methods ) {
    $methods[] = 'WC_Cheque'; return $methods;
}
add_filter('woocommerce_payment_gateways', 'add_cheque_gateway' );

支票支付网关的完整代码在classes/gateways/class-wc-cheque.php中

如何更改订单状态和添加通知

更改订单状态可以通过使用order class中的功能完成,例如

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

将订单状态更改为on-hold并通知店主该订单正在等待支票付款。

即使不更改订单状态,也可以添加通知,在调试时使用。

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

当支付完成后,用payment_complete()方法改变状态,而不是update_status()

	
$order->payment_complete();

这样可以保证存货量计算正确,订单状态正确更新。

如何新的支付方式做成插件

支票网关直接集成在WooCommerce的核心文件中,如果是你要增加新的支付方式,应该用插件形式加入。用插件形式,只需要将上述代码包裹在一个class中,如下所示,其它与正常插件没有区别

add_action('plugins_loaded', 'init_your_gateway', 0);
 
function init_your_gateway() {
 
    if ( ! class_exists( 'woocommerce_payment_gateway' ) ) { return; }

这样可保证你的代码只有在woocommerce安装时才会执行。

Direct 网关

如果使用Direct方式,付款将在网站的结账页面进行,而不会跳转到第三方网站。我们的代码要有所变化。

首先,has_fields设置为true

$this->has_fields = true;

这样checkout页面会输出一个包含付款表单的“payment_box”,字段由你定义

创建方法payment_fields——这个payment fields包含你的表单字段,例如要求用户输入信用卡详情的表单,非direct方式中,这些信息是在第三方网站输入的。下面是Payment pro的简化版

/**
 * Payment form on checkout page
 */
 function payment_fields() {
      global $woocommerce;
      ?>
      <?php if ($this->description) : ?><p><?php echo $this->description; ?></p><?php endif; ?>
      <fieldset>
      <p class="form-row form-row-first">
           <label for="paypal_pro_cart_number"><?php _e("Credit Card number", 'woothemes') ?> <span class="required">*</span></label>
           <input type="text" class="input-text" name="paypal_pro_card_number" />
      </p>
      <div class="clear"></div>
      <p class="form-row form-row-first">
           <label for="cc-expire-month"><?php _e("Expiration date", 'woothemes') ?> <span class="required">*</span></label>
           <select name="paypal_pro_card_expiration_month" id="cc-expire-month" class="woocommerce-select woocommerce-cc-month">
                <option value=""><?php _e('Month', 'woothemes') ?></option>
                <?php
                $months = array();
                for ($i = 1; $i <= 12; $i++) :
                     $timestamp = mktime(0, 0, 0, $i, 1);
                     $months[date('n', $timestamp)] = date('F', $timestamp);
                endfor;
                foreach ($months as $num => $name) printf('<option value="%u">%s</option>', $num, $name);
                ?>
           </select>
           <select name="paypal_pro_card_expiration_year" id="cc-expire-year" class="woocommerce-select woocommerce-cc-year">
                <option value=""><?php _e('Year', 'woothemes') ?></option>
                <?php
                for ($i = date('y'); $i <= date('y') + 15; $i++) printf('<option value="%u">20%u</option>', $i, $i);
                ?>
           </select>
      </p>
      <p class="form-row form-row-last">
           <label for="paypal_pro_card_csc"><?php _e("Card security code", 'woothemes') ?> 
<span class="required">*</span></label>
<input type="text" class="input-text" id="paypal_pro_card_csc" name="paypal_pro_card_csc" maxlength="4" style="width:4em;" />
      </p>
      <div class="clear"></div>
      </fieldset>
      <?php
 }

下一个功能validate_fields()是可选的,如果你需要对表单所填内容进行验证,就定义该方法,如果顺利通过验证就返回true,否则返回false。可以使用$woocommerce->add_error()方法添加错误信息并显示给用户。

最后,你需要创建一个新的process_payment( $order_id )方法,这个新方法需要获取表单中的数据并直接通过支付网关提供商进行支付。

如果支付失败,显示错误信息

$woocommerce->add_error(__('Payment error:', 'woothemes') . $error_message);
 return;

如果支付成功,将订单状态更改为“paid”,并返回一个数组

// Payment complete
 $order->payment_complete();
 
 // Remove cart
 $woocommerce->cart->empty_cart();
 
 // Empty awaiting payment session
 unset($_SESSION['order_awaiting_payment']);
 
 // Return thank you page redirect
 return array(
 'result' => 'success',
 'redirect' => add_query_arg('key', $order->order_key, add_query_arg('order', $order_id, get_permalink(get_option('woocommerce_thanks_page_id'))))
 );

To be continued …

28条留言

  1. 抱歉我補充一下,這是銀行給我的範例 ..
    input type=”Hidden” name=”TA” value=”消費金額(這裏好像需要從購物車中找出價錢的相關參數)”
    input type=”Hidden” name=”TA” value=”訂單編號(這裏好像需要從購物車中找出商品編號的相關參數)”
    不知道我這樣的理解是不是正確的 …

    1. 中国的支付机构基本没有官方支持woocommerce的,但所有支付方式都能集成进去。要问为啥,woocommerce向支付网关发送数据不是post就是get,post不就是提交一个表单过去吗,只是在哪里提交的问题,这个在哪里提交就是woocommerce定的规矩。

      post方式就是在woocommerce的checkout页面生成一个html表单,可以选择让用户确认一下再提交,也可以js直接提交。
      方法大概是这样

      add_action( 'woocommerce_receipt_[网关ID]'  array( $this, 'receipt_page' ) );
      function receipt_page( $order_id  ){
          $order = wc_get_order( $order_id ); //获取订单产品信息
          //利用order里的信息写一个html表单出来
         //表单里要有一个submit按钮,可以让用户自己点
         //或者用jquery帮用户点一下
          wc_enqueue_js( '
                  $.blockUI({
                          message: "' . esc_js( __( 'Thank you for your order. ) ) . '",
                          baseZ: 99999,
                          overlayCSS:
                          {
                              background: "#fff",
                              opacity: 0.6
                          },
                          css: {
                              padding:        "20px",
                              zindex:         "9999999",
                              textAlign:      "center",
                              color:          "#555",
                              border:         "3px solid #aaa",
                              backgroundColor:"#fff",
                              cursor:         "wait",
                              lineHeight:     "24px",
                          }
                      });
                  jQuery("#[submit按钮ID]").click();//提交
              ' );
      }
      

      里面echo的样式只是为了把Thank you for your order这句话放到一个overlay效果的box里。最后的click()才是用js帮用户点一下按钮。

      1. 非常感謝您的指導與範例教學。這些經驗都對我了解woocommerce有非常大的幫助。非常感謝您。我想我還真要把php的基礎打好,再來研究一下了解wordpress的架構,到時候再看woocommerce也許才會有一點概念吧。對於一個沒有任何基礎的人來說,官方文檔的api對我來說就算是真的看的懂意思,但是也不知道該如何去使用並撰寫 .. 您真是個好人,非常感謝您的幫忙。謝謝!

    2. 学习php的话,谷歌里搜索learn php,有很多网站,比如http://www.learn-php.org/
      对wordpress开发,也不是非要精通php才行,精通wordpress可能更重要些。了解php的基本语法,以及面向对象的基础就行。
      对于WordPress,你必须理解action和filter的原理,不然根本不知道自己在干啥。

      1. 謝謝你的建議,我再來好好的學習一下。還是要有點穩固的基礎才行 …

      2. HELLO ~ 抱歉,我又來問一個問題了@@
        想要請教一下關於 wordpress 裡面的 “評論” 這個詞,要怎麼把它改成留言或者更改其他我要的文字顯示呢?我看了很多網路上的文章都無法使用或者是網路上說要搜尋或修改的地方我的找不到相關代碼 @@

        1. wordpress是可翻译的,分为系统语言包和主题插件的语言包,你首先得弄清楚你要改的“评论”俩字属于哪种情况。

          系统语言包位于wp-content/languages下,主题插件的位于各自文件夹里,文件后缀是.po,最后起作用的是.mo,.mo由.po生成。

          你需要了解WordPress的翻译机制,如何编辑语言包文件,woocommerce有一篇关于翻译的文章,可以参考下
          http://docs.woothemes.com/document/woocommerce-localization/

          还有一种用代码改的方式
          https://codex.wordpress.org/Plugin_API/Filter_Reference/gettext_with_context
          filter就像广电总局,所有电影台必须先经过它这一道关口审核,不好听的词直接删除或者替换,这个gettext filter就是这意思。这是不用修改语言包却能改变翻译的方式,但“评论”俩字太宽泛,这种方式不好控制。

          1. 恩恩,好的謝謝你。我會在自己研究看看。

  2. 你好,看了關於您的woocommerce的研究心得,得到許多的幫助。謝謝您的無私分享!不曉得能否請教一下,有沒有辦法獲取commerce商城的一些(商品編號|商品名稱|商品價錢)的相關表單參數呢?因為我有使用銀行線上刷卡,他們需要我自己用html做一個按鈕,將剛剛說的那些參數帶進來再透過post傳送到銀行端頁面去處理…但是我找了好久也研究了好久,始終沒有答案…造成困擾非常抱歉!感謝您。

      1. hi 你好,謝謝你的回覆。不過我不是要在產品詳情頁獲取這些信息,而是需要在用戶點擊購買並且到了付費頁面的時候,我需要使用html製作一個按鈕,該按鈕名稱叫(線上刷卡)。並利用html將用戶購買的商品編號,商品名稱,商品價格的參數取出並透過html的表單按鈕一起post到銀行端的頁面去。
        抱歉,也許講的不是很能夠讓人理解。請見諒~

      2. 不過我想到了,似乎沒有那麼簡單就能完成的感覺,因為就算能夠把商品各項參數使用html並post到銀行頁面,但是當用戶刷卡結束後會再回傳一些參數,這部分好像就需要跟woocommerce在做結合,所以應該是需要針對這個線上刷卡的功能另外寫一個外掛模組吧,不然商品似乎沒辦法接收到是否已付款或者未付款。或者應該利用官方提供的api文件去寫吧,不過我也許我在php還是個新手菜鳥,所以看不懂也不太理解那些api應該如何使用 … 不過還是非常感謝您的回覆,您的回覆在我所有發問中是對我最有實質上幫助的了。謝謝您!

        1. 你要问的是woocommerce如何集成网银支付吗,首先恕我直言如果是php新手,写这种插件不容易。
          如果坚持要尝试一下,可以给你一些建议。不需要在付款页面显示一些表单(似信用卡支付显示卡号有效期字段等等),可以直接参考woocommerce自带的paypal写法,具体文件是
          woocommerce/includes/gateways/paypal/class-wc-gateway-paypal.php.

          每个网关都要集成woocommerce的网关class,也就是开头那句

          class WC_Gateway_Paypal extends WC_Payment_Gateway

          WC_Gateway_Paypal这个名字十分重要,当你的支付网关向woocommerce发送用户付款结果的数据时,woocommerce如何判断是哪个网关发来的?就是靠这个class名字,你的网关要向woocommerce发一条这样的url

          http://yourdomain.com/wc-api/WC_Gateway_Paypal/[其它参数]

          woocommerce就靠wc-api后面的名字区分网关,[其它参数]则是数据,这种格式一般用于异步通知。

          继续往下找

          public function process_payment( $order_id )

          这个函数就是你提交订单后负责跳转到网关付款的函数,你要找的产品信息代码这里就是,通过参数$order_id来获取本次订单的信息

          $order = wc_get_order( $order_id );

          用户付款后检查返回的数据,代码参考class-wc-gateway-paypal-ipn-handler.php,这个是异步回传的处理方式
          在__construct里找到

          add_action( 'woocommerce_api_wc_gateway_paypal', array( $this, 'check_response' ) );

          注意带有wc_gateway_paypal的action名称,刚才提到woocommerce如何知道哪个网关传来数据,那么加载到这个action的函数就是你的网关提供的处理数据的功能。网关不通,这个action名字也不同,总是就是woocommerce_api_[class名称小写]。

          用什么格式发送数据,返回数据如何处理,这个要根据你的网关api来,所以你要同时明白网关api和woocommerce集成网关的原理,才能写这个插件。建议你参考paypal的写法,多用var_dump查看变量的值,去掉没用函数,保留你需要的,就能大概写一个网关出来。

          对了,要更正一点,你说的创建一个“线上支付”按钮来传数据,这个在woocommerce里的做法应该是用place order按钮代替这个按钮,你可以看下paypal支付时place order按钮的名字就是Proceed to PayPal,具体代码是

          $this->order_button_text  = __( 'Proceed to PayPal', 'woocommerce' );

          所以别再想着找什么地方创建一个按钮的事了,在checkout页面的事必须要按照woocommerce的规矩来。

          最后,把官方文档看懂
          http://docs.woothemes.com/document/payment-gateway-api/

          1. hello ~ 真的很感謝您的回覆,您的教學及建議對我非常的有幫助!對於官方文檔我這種英文非常差的可能真的需要花點時間去仔細研究研究才行。不過我想再問一個問題,真是抱歉 … 因為在我詢問銀行的結果,銀行告訴我說他們並不支援woocommerce購物車模組,他們說他們不需要串api,只需要在結帳頁面做一個以下html範例的表單(這是銀行給我的串接規格書的範例)

            Untitled Document

            傳送的資料如下

            <input name="bttsubmit" type="submit" class="font" value="送出"

            不過如果依照您剛說的(在checkout页面的事必须要按照woocommerce的规矩来)我又覺得非常有道理,可是這樣是不是代表也許這家銀行提供的這種方式真的不適合使用了?對不起!問題真的挺多的@@請見諒!
            (想順便請教一下您對於學習PHP有什麼好的建議或者網站可以看的嗎@@)

          2. 抱歉!剛剛那個範例沒有打成功…
            form name=”form1″ method=”post” action=””
            input type=”Hidden” name=”MID” value=”訂單編號”
            input type=”Hidden” name=”TA” value=”消費金額”
            input type=”Hidden” name=”U” value=”URL”

  3. 美女,请问怎么去掉付款方式呢,不要的付款方式去掉,怎么看你有没有回复我呢?是否有邮件提醒

    另外问一个其他的,能否整合短信注册wordpress呢?有没有这方面的案例供膜拜

    1. 有回复留言的通知邮件。
      付款方式在woocommerce > 设置 > 结账里控制。
      短信注册的案例我没有,很抱歉。

  4. Strip中国的用户注册不了,需要由美国的社保卡和银行卡

  5. 在尝试实现支付宝网银支付,$this->has_fields = true;后如何读取payment_fields()里用户提交的数据传给alipay args array?谢谢。

    1. 抱歉,我没研究过网银支付,官方文档里写网银支付与即时到账的唯一区别是defaultbank和payment为必传字段,为何要使用$this->has_fields = true?
      如果你确实需要用户填写一些信息再提交,可以参考woocmmerce信用卡支付的插件,比如stripe for woocommerce

      1. 要使用$this->has_fields = true 是因为需要用户选择银行。
        谢谢指引,我去看看信用卡支付的插件。

  6. 美女,你知道Mijireh和stripe之间的区别么,是一种东西,还是两者缺一不可?急求

    1. 我没有国外信用卡所以这两个我没有用过。
      这是两个不同的提供商,收费有区别,提供的服务都是信用卡支付,对你来说,就是在哪里注册账户的问题。
      Mijireh是woocommerce自带的,stripe需要另外安装插件。
      woocommerce 2.2+Mijireh也要下载插件
      https://wordpress.org/plugins/woocommerce-mijireh-checkout/

  7. 站長可否把上述cheque的講解做成一個Plugin讓我們下載並學習?

    1. 看woocommerce自带的gateway就行,这个东西经常更新,看它自带的更靠谱。
      比如学习带异步通知的,看paypal(class-wc-gateway-paypal.php)
      简单点的就看银行汇款的(class-wc-gateway-bacs.php)
      这些都在woocommerce/classes/gateways目录下

评论功能已关闭