WelcartにWebPay決済を導入する

必要に迫られWordPressのECプラグイン「Welcart」にクレジット決済サービス「WebPay」を組み込む事が出来たので、記録を残しておきます。
多少強引なところもありますが、プラグイン化してあります。
#####各公式サイト
[Welcart Home ショッピングカート for Wordpress | ECサイト構築プラグイン](http://www.welcart.com/)
[WebPay: 開発者向けクレジットカード決済サービス](https://webpay.jp/)
#####デモサイト
組み込んだ状態を試すことができます。
テーマはWelcartデフォルト、カード登録等のカスタマイズもしています。
[川江本舗通販部 | ec_prjct.jp](http://ec.prjct.jp/)
#####ファイル構成
>webpay-for-welcart(プラグインフォルダ)
　|- webpay-php-full-2.1.1(WebPay公式サイトをよりダウンロードしたものを解凍)
　|- webpay-admin.php(管理画面：WebPayから発行されるコードを保存します)
　|- webpay-for-welcart.php(プラグイン本体)

###管理画面
WebPayから発行される公開錠と非公開錠をWordPressに保存しておくページを作ります。
![capture image.jpeg](https://qiita-image-store.s3.amazonaws.com/0/17079/268b4ca6-9112-1bda-f67d-98a817cb80e9.jpeg)

```php:webpay-admin.php
<?php
class WebpaySetting {   // 管理画面
  function __construct() {
    add_action('admin_menu', array($this, 'add_pages'));
  }
  function add_pages() {
    add_submenu_page(USCES_PLUGIN_BASENAME, 'WebPay設定', 'WebPay設定', 'administrator', 'webpay_setting', array($this, 'webpay_setting_page'));
  }
  function webpay_setting_page() {
  //$_POST['webpay_public_key'])があったら保存
  if ( isset($_POST['webpay_public_key']) && isset($_POST['webpay_secret_key'])) {
    check_admin_referer('paTh8kid');
    $public_key = $_POST['webpay_public_key'];
    update_option('webpay_public_key', $public_key);
    $secret_key = $_POST['webpay_secret_key'];
    update_option('webpay_secret_key', $secret_key);
    echo ('<div class="updated fade"><p><strong>保存しました。</strong></p></div>');
  }
  echo '
  <div class="wrap">
  <h2>WebPay設定</h2>
  <form action="" method="post">
  ';
  wp_nonce_field('paTh8kid');
  $public_key = get_option('webpay_public_key');
  $secret_key = get_option('webpay_secret_key');
  $public_key_text = isset($public_key['text']) ? $public_key['text']: null;
  $secret_key_text = isset($secret_key['text']) ? $secret_key['text']: null;
  $html = <<<END
  <table class="form-table">
  <tr valign="top">
  <th scope="row"><label for="inputpublic">公開可能鍵</label></th>
  <td><input name="webpay_public_key[text]" type="text" id="inputpublic" value="$public_key_text" class="regular-text" /></td>
  </tr>
  <tr valign="top">
  <th scope="row"><label for="inputsecret">非公開鍵</label></th>
  <td><input name="webpay_secret_key[text]" type="text" id="inputsecret" value="$secret_key_text" class="regular-text" /></td>
  </tr>
  </table>
  <p class="submit"><input type="submit" name="Submit" class="button-primary" value="変更を保存" /></p>
  </form>
  <!-- /.wrap --></div>
END;
  echo($html);
    }
}
if(is_admin()){
  $showtext = new WebpaySetting;
}
```

ほとんど参考サイトのコードそのままです。

```php
add_submenu_page(USCES_PLUGIN_BASENAME, 'WebPay設定', 'WebPay設定', 'administrator', 'webpay_setting', array($this, 'webpay_setting_page'));
```

とすることで、WelcartShopのサブメニューとして表示するようにしています。![スクリーンショット 2014-09-28 15.39.27--.png](https://qiita-image-store.s3.amazonaws.com/0/17079/1a52fd9a-0ff3-f4b2-3a80-6cb66e08f8d1.png)

###プラグイン本体

```php:webpay-for-welcart.php
<?php
/*
Plugin Name: WebPay for Welcart
Description: WelcartにWebPay決済を導入
Author: 川江　修
Version: 0.3
*/
```

先頭にお決まりのフレーズを書き込むことでWordPressがプラグインとして認識してくれます。
後は、この下に各フィルタフックに対応した内容を足していくだけです。
####ライブラリを読み込む

```php
require "webpay-php-full-2.1.1/autoload.php";
use WebPay\WebPay;

if( is_admin() ){
	require_once("webpay-admin.php");
}
```

WebPayのライブラリと管理画面を読み込みます。

####支払方法にクレジットカードを追加
![capture image3.jpeg](https://qiita-image-store.s3.amazonaws.com/0/17079/d7e5862f-40aa-5f84-490f-7b7a1e3d4766.jpeg)

じつは、Welcart基本設定で支払方法を追加し、説明欄にWebPayが提供するCheckoutHelperのコードをそのまま記入すればCheckoutHelperを使うことができます。
※決済種別は「分割払い」決済モジュールは指定しません。
![capture image.jpeg](https://qiita-image-store.s3.amazonaws.com/0/17079/aedcb487-decd-f2f9-6952-5855c8e003ac.jpeg)
ですが、コードの打ち間違いや他に説明文を入れていて改変してしまったりということのリスクを減らすためにショートコード（もどき）を記入してプログラムで変換します。普通にショートコードを定義しても拾ってくれませんので、str_replaceを使って変換しました。ここでは[[webpay_btn]]としています。![capture image4.jpeg](https://qiita-image-store.s3.amazonaws.com/0/17079/b11cc60d-b8c0-ef29-7be3-f4ba671a94ee.jpeg)
以下が置き換えのコードです。

```php
add_filter('usces_fiter_the_payment_method','usces_fiter_the_payment_method_webpay',10,2);
function usces_fiter_the_payment_method_webpay($payments, $value){
  $public_key = get_option('webpay_public_key');
  $search = "[[webpay_btn]]";
  $pattern = "/$search/s";

  $replace =<<<END
<script src="https://checkout.webpay.jp/v2/"
 class="webpay-button"
 data-key="{$public_key['text']}"
 data-partial="true"
 data-lang="ja">
</script>
END;

  foreach ($payments as $id => $payment) {
    $explanation = $payment['explanation'];
    if (preg_match($pattern,$explanation)) {
      $payment['explanation'] = str_replace($search,$replace,$explanation);
      $payments[$id] = $payment;
    }
  }
  return $payments;
}
```

usces_fiter_the_payment_methodにフィルタをかけ、支払方法の説明に[[webpay_btn]]という文字があれば置き換えして返します。
$public_key = get_option('webpay_public_key'); は、管理画面で保存した公開錠を読み込んでいます。
なお、支払いページでクレジットカードを選択しているのにカード情報を入力していない場合にエラーを表示する、又は『次へ』ボタンを押せなくする等の工夫も必要かと思われますが、ここでは割愛します。
####確認画面にトークンコードを引き継ぐ
![capture image5.jpeg](https://qiita-image-store.s3.amazonaws.com/0/17079/03caec75-aea3-0c94-22be-62f7d7638eea.jpeg)
「上記内容で注文する」ボタンの直前にフィルタがあるので、CheckoutHelperで発行されたトークンコードを埋め込みます。

```php
add_filter('usces_filter_confirm_inform','my_usces_filter_confirm_inform',10,5);
function my_usces_filter_confirm_inform($html, $payments, $acting_flag, $rand, $purchase_disabled){
  $token = isset($_REQUEST['webpay-token']) ? $_REQUEST['webpay-token'] : null;
  if($token){
    $html .= '<input type="hidden" name="webpay-token" value="'.$token.'"/>';
  }
  return $html;
}
```

####WebPayで決済
注文が確定すればWebPayにトークンコードを送り決済を完了させます。
Welcartの受注データベースに書き込んだ後のフックを使いました。

```php
add_action('usces_post_reg_orderdata','my_usces_post_reg_orderdata',10,2);
function my_usces_post_reg_orderdata($order_id, $results = array() ) {
  global $usces;
  // 決済内容をデータベースから読み込む
  $row = $usces->get_order_data($order_id, 'direct');
  // クレジットカード払いでない場合は何もせず終了
  if($row['order_payment_name'] !='クレジットカード') return;
  // トークンを取得
  $token = isset($_REQUEST['webpay-token']) ? $_REQUEST['webpay-token'] : null;
  if (!$token) {
    die('予期しないエラーです(no token data)');
  }

  // 金額を計算
  $price = intval($row['order_item_total_price']) - intval($row['order_usedpoint']) + intval($row['order_discount'])
    + intval($row['order_shipping_charge']) + intval($row['order_cod_fee']) + intval($row['order_tax']);

  // WebPayでわかりやすいように注文番号、名前、メールアドレスを注釈にする
  $description = $row['ID'] .' : '.$row['order_name1'].$row['order_name2'].'  mail : '.$row['order_email'];

  // 非公開錠を取得
  $secret_key = get_option('webpay_secret_key');
  // 以下、サンプルプログラムのまま
  $webpay = new WebPay($secret_key['text']);
  try {
    // 決済を実行
    $ret = $webpay->charge->create(array(
       "amount" => $price,
       "currency"=>"jpy",
       "card" => $token,
       "description" => $description  //注釈
    ));
    // 以下エラーハンドリング
  } catch (\WebPay\ErrorResponse\CardException $e) {
    $data = $e->getData()->error;
    // カードが拒否された場合
    print('Status is:' . $e->getStatus() . "\n");
    print('Type is:' . $data->type . "\n");
    print('Code is:' . $data->code . "\n");
    print('Param is:' . $data->param . "\n");
    print('Message is:' . $data->message . "\n");
    exit('Error');
  } catch (\WebPay\ErrorResponse\InvalidRequestException $e) {
    // リクエストで指定したパラメータが不正な場合
    print("InvalidRequestException\n");
    print('Param is:' . $e->getParam() . "\n");
    print('Message is:' . $e->getMessage() . "\n");
    exit('Error');
  } catch (\WebPay\ErrorResponse\AuthenticationException $e) {
    // 認証に失敗した場合
    print("AuthenticationException\n");
    print('Param is:' . $e->getParam() . "\n");
    print('Message is:' . $e->getMessage() . "\n");
    exit('Error');
  } catch (\WebPay\ErrorResponse\ApiException $e) {
    // WebPayのサーバでエラーが起きた場合
    print("ApiException\n");
    print('Message is:' . $e->getMessage() . "\n");
    exit('Error');
  } catch (\WebPay\ApiConnectionException $e) {
    // APIへの接続エラーが起きた場合
    print("ApiConnectionException\n");
    print('Message is:' . $e->getMessage() . "\n");
    exit('Error');
  } catch (\WebPay\InvalidRequestException $e) {
    // リクエストで指定したパラメータが不正で、リクエストがおこなえなかった場合
    print("InvalidRequestException");
    print('Message is:' . $e->getMessage() . "\n");
    exit('Error');
  } catch (\Exception $e) {
    // WebPayとは関係ない例外の場合
    print("Unexpected exception\n");
    print('Message is:' . $e->getMessage() . "\n");
    exit('Error');
  }
  //　決済終了
}
```

以上で決済終了です。
WebPayの管理画面・課金の履歴で確認できます。


###追記：「次へ」ボタンを無効化
リクエストを頂きましたので、支払い方法でクレジットカードを選択したのにカード情報が入っていない場合に「次へ」ボタンを押せないようにする方法です。
CheckoutHelperを組み込んだ段階でname="webpay-token"というinput:hiddenが作られています。
支払い方法が変化する度に、この要素と支払い方法の値をチェックして「支払い方法がクレジットカード and トークンがない(カード情報が入っていない)」場合のみ「次へ」ボタンを無効にするというスクリプトを書きます。

```javascript
jQuery(function($) {
  $("[name='offer[payment_name]']").change(function() {
    var payment, token;
    payment = $("[name='offer[payment_name]']:checked").val();
    token = $('[name="webpay-token"]').val();
    if ((payment === 'クレジットカード') && (!token)) {
      $('[name="confirm"]').attr('disabled', true);
    } else {
      $('[name="confirm"]').attr('disabled', false);
    }
  });
});
```

このページでしか使わないのでどこかに埋め込むのが正解でしょうが外部ファイルにして読み込むようにしています。
上記のコードを「webpay4welcart.js」として保存した場合、

```PHP
add_action('wp_enqueue_scripts','add_webpay4welcart_js');
function add_webpay4welcart_js() {
	$jsUrl = plugins_url('webpay4welcart.js', __FILE__);
	wp_enqueue_script('webpay4welcart',$jsUrl, array(), '1.0', false);
}
```

をプラグイン本体に追加します。
####追記の追記
しかし、このままだとクレジットカードを選んだ後にカード情報を入力しても「次へ」は無効化されたままになっていますので、これを解除してやる必要があります。
先ほどのwebpay4welcart.jsに以下のコードを追加します。

```javascript
card_inp_ok = function() {
  $('[name="confirm"]').attr('disabled', false);
};
```
プラグイン本体のCheckoutHelperを呼び出す部分にカードの確認が取れたらこの関数を呼び出すようにセットします。

```php
$replace =<<<END
<script src="https://checkout.webpay.jp/v2/"
class="webpay-button"
data-key="{$public_key['text']}"
data-partial="true"
data-on-created="card_inp_ok" // ここを追加(このコメントは追加しない)
data-lang="ja">
</script>
END;
```
これで想定していた動きになる筈です。