cakephp3 で pay.jp クレジットカード決済を導入

cakephp3 で pay.jp クレジットカード決済を導入

webhookは
初回 subscription.created
2回目以降 subscription.renewed
が返ってくる


まずは composer で
https://github.com/payjp/payjp-php
インストール

# 改訂版

まずはテーブルを作成

```

CREATE TABLE `payjps` (
  `id` bigint(20) NOT NULL COMMENT 'ユーザーIDと同一',
  `customer_id` varchar(255) CHARACTER SET utf8 DEFAULT NULL COMMENT 'カスタマーID',
  `teiki_id` varchar(255) CHARACTER SET utf8 DEFAULT NULL COMMENT '定期課金ID',
  `created` datetime NOT NULL DEFAULT current_timestamp(),
  `plan_id` int(11) DEFAULT 0 COMMENT ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

```


# 下準備

u('id')//ユーザーのID

##管理画面より
・PAYJPのKEYとSECRETを取得し PayjpsController config に設定
・https://yourdomain.com/payjps/webhook/ の webhook を作成
・継続課金の場合、10000,10001のIDのプランを先に作っておく



# 共通

・初回のみクレジットカード決済を入力させる
・次回以降は入力の必要なし
・処理は webhook を使わず遷移先で行う

・都度課金

・サイト特有の処理は _tudoSyori() にて行う




・定額課金

・サイト特有の処理は  _keizokuSyori() にて行う
定額プランは どれか 1つしか入れないものとする。
継続決済の2回目以降の自動処理の場合のみwebhookを使う。


PayjpsController.php

```

<?php

namespace App\Controller;
use App\Controller\AppController;

use Cake\Controller\Controller;
use Cake\Event\Event;
use Cake\ORM\TableRegistry;
use Cake\Cache\Cache;


use Cake\Mailer\Email;

use Cake\Core\Configure;
use Cake\ORM\Query;

use Cake\Utility\Hash;

use PDO;
use Cake\Datasource\ConnectionManager;
use Cake\I18n\Time;

class PayjpsController extends AppController
{

    public $config = [
        'secret' => PAY_JP_SECRET,//your
        'key' => PAY_JP_KEY//your
    ];


// あなたのコントローラで下記のように呼び出す
    public function initialize()
    {
        parent::initialize();
    }

    public function reset()
    {

        $this->Payjps->deleteAll([
            'id' => u('id'),
        ]);

        return $this->redirect($this->referer());
    }


    public function test()
    {


        $res = $this->Payjps->find()
            ->where([
                'id' => u('id'),
            ])
            ->first();




        //そのまま送る
        
        $config = $this->config;


        $this->set(
            compact(
                'res',
                'config'
            )
        );
        
    }

//  都度課金があった場合どのような処理をするか
    public function _tudoSyori()
    {
//        $_POST['user_id']
//        pd($_POST);



        return number_format($_POST['amount'])." 円の支払い完了";


    }

    //  継続課金があった場合どのような処理をするか
    //  2回目以降の時は webhook からこれを動かす
    public function _keizokuSyori()
    {
        $customer_id = "";
        if(!empty($_POST['customer_id'])){
            $customer_id = $_POST['customer_id'];
        }

        if(!empty($_POST['data']['customer'])){
            $customer_id = $_POST['data']['customer'];
        }

        $res = true;
        if(!empty($_POST['plan_id'])){
            $res = "プラン : ".$_POST['plan_id']." に入りました";
        }

        return $res;

    }


    public function tudokakin()
    {

//        初回決済
        if (!empty($_POST['customer_id'])) {

            //            顧客化されている場合
            $customer_id = $_POST['customer_id'];

        } else {

            //顧客化
            $customer_id = $this->_addCustomer();

            $data = [
                'id' => $_POST['user_id'],
                'customer_id' => $customer_id
            ];

            $valid = $this->Payjps->newEntity($data);
            $this->Payjps->save($valid);

        }

        \Payjp\Payjp::setApiKey($this->config['secret']);


        try {
            $res = \Payjp\Charge::create(
                [
                    "customer" => $customer_id,
                    "amount" => $_POST['amount'],//支払額
                    "currency" => 'jpy'
                ]
            );
            if (isset($res['error'])) {
                throw new Exception();
            }
        } catch (Exception $e) {
            // カードが拒否された場合
            echo $res['error']['message'];
            exit;
        }


        $syori = $this->_tudoSyori();


        $this->Flash->set($syori);
        return $this->redirect($this->referer());

        $this->autoRender = false;
    }


    public function keizoku()
    {

        if (!empty($_POST['customer_id'])) {
            //            顧客化されている場合
            $customer_id = $_POST['customer_id'];
        } else {
            //        初回決済
            //顧客化
            $customer_id = $this->_addCustomer();

            $data = [
                'id' => $_POST['user_id'],
                'customer_id' => $customer_id
            ];

            $valid = $this->Payjps->newEntity($data);
            $this->Payjps->save($valid);

        }



        //トークンで支払う
        \Payjp\Payjp::setApiKey($this->config['secret']);

        try {
            $res = \Payjp\Subscription::create(
                [
                    "customer" => $customer_id,
                    "plan" => $_POST['plan_id']//管理画面で先にプランを作っておく
                ]
            );
            if (isset($res['error'])) {
                throw new Exception();
            }
        } catch (Exception $e) {
            // カードが拒否された場合
            echo $res['error']['message'];
            exit;
        }





//        ユーザー情報を一旦保存
        $data = [
            'id' => $_POST['user_id'],
            'customer_id' => $customer_id,
            'teiki_id' => $res->id,
            'plan_id' => $_POST['plan_id'],
        ];

        $valid = $this->Payjps->newEntity($data);
        $this->Payjps->save($valid);

        $syori = $this->_keizokuSyori();

        $this->Flash->set($syori);
        return $this->redirect($this->referer());

        $this->autoRender = false;
    }

    public function _addCustomer()
    {
//        フォームから送られてくる。
//         [payjp-token] => tok_ef86a1b8303ddbf08c42cc6cba06

        //トークンで支払う
        \Payjp\Payjp::setApiKey($this->config['secret']);

        $customer = \Payjp\Customer::create(array(
            "card" => $_POST['payjp-token']
        ));

        return $customer->id;
    }



    //    退会処理
    public function taikai()
    {
        \Payjp\Payjp::setApiKey($this->config['secret']);
        $su = \Payjp\Subscription::retrieve($_GET['teiki_id']);
        $su->delete();


        $_SESSION['Payjp']['plan_id'] = 0;


        $this->Payjps->updateAll(
            [
                'plan_id' => 0,
                'teiki_id' => ''
            ],//にする
            [
                'teiki_id' => $_GET['teiki_id']
            ]//これを
        ); // 条件


        $this->Flash->set("解約しました");
        return $this->redirect($this->referer());

        $this->autoRender = false;
    }

    public function webhook()
    {

        if(!empty($_POST['type'])){

//            初回課金時
            if($_POST['type'] == "subscription.created"){

            }

//            2回目以降課金時
            if($_POST['type'] == "subscription.renewed"){
                $this->_keizokuSyori();
            }



        }

        $this->autoRender = false;
    }






}


```


payjps/test.ctp

```


<?php

//同じページに複数フォームを設置できないので
//0 は 継続決済 , 1 は都度課金とする
$kakin_mode = 0;

?>

<h1>payjpのテスト</h1>


<? if ($kakin_mode): ?>

    <h2>都度決済</h2>


    <form action="/payjps/tudokakin/" method="post">

        <input type="radio" name="amount" value="1000"> 1,000 円の都度課金
        <input type="radio" name="amount" value="5000" checked> 5,000 円の都度課金
        <input type="radio" name="amount" value="10000"> 10,000 円の都度課金

        <input type="hidden" id="user_id" name="user_id" value="<?=u('id');?>">

        <? if (!empty($res->customer_id)): ?>
        <input type="hidden" id="customer_id" name="customer_id" value="<?=$res->customer_id;?>">
            <div>
                <button type="submit">2回目以降の決済</button>
            </div>
        <? else: ?>
        <input type="hidden" id="customer_id" name="customer_id" value="">
            <script src="https://checkout.pay.jp/" class="payjp-button" data-key="<?=$config['key'];?>" data-text="都度課金 (単発)"></script>
        <? endif; ?>

    </form>




<? else: ?>


    <h2>継続決済</h2>

    <? if (!empty($res->teiki_id)): ?>

        <p>解約フォームへ</p>
        <a href="/payjps/taikai/?teiki_id=<?=$res->teiki_id;?>">現在有料プランに入っています。退会しますか？</a><br>


    <? else: ?>

        <form action="/payjps/keizoku/" method="post">
            <input type="radio" name="plan_id" value="10000" checked> 30日掲載プラン
            <input type="radio" name="plan_id" value="10001"> 365日掲載プラン
            <input type="hidden" id="user_id" name="user_id" value="<?=u('id');?>">


            <? if (!empty($res->customer_id)): ?>
                <input type="hidden" id="customer_id" name="customer_id" value="<?=$res->customer_id;?>">
                <div>
                    <button type="submit">定額プラン申し込み (2回目以降の決済)</button>
                </div>
            <? else: ?>
            <input type="hidden" id="customer_id" name="customer_id" value="">
                <script src="https://checkout.pay.jp/" class="payjp-button" data-key="<?=$config['key'];?>" data-text="定期プラン申し込み"></script>

            <? endif; ?>

        </form>



    <? endif; ?>





<? endif; ?>



<? if (!empty($res->customer_id)): ?>
    <? if (!empty($res->teiki_id)): ?>
        <? else: ?>
        <a href="/payjps/reset/">カードの有効期限などが切れ場合リセットする</a>
    <? endif; ?>
<? endif; ?>



```























---