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; ?>