3回に渡って自分の備忘録もかねて書かせていただく「Stripe×PHP(Laravel)」のブログ記事、2回目の今回は「クレジットカード登録・更新編」になります。
前回の記事では、Laravelを使って Stripeでクレジットカード登録や決済処理を行うための事前準備として、クレジットカード登録フォーム設置の部分まで進めさせていただきました!
今回は、前回の続きとして、クレジットカード登録情報・更新・削除処理について、お話を進めてみたいと思います!
※3回分のブログにさせていただく内容の最終形コードは、上記Githubに掲載しております。こちらの内容を元にお話を進めていきたいと思います!
3回の内容については、以下の通りの区分になっています!
目次
クレジットカード登録処理を実装
サーバサイド側の処理を記述する
それでは、前回の記事の続きとして、サーバサイド側にクレジットカード登録処理の記述をしていきます!
サーバサイド側において必ず抑えておきたいポイントとしては、Stripeにおいてクレジットカード情報がどのような紐付け方で保存されるのか、という部分になります。
当たり前かと言えば当たり前なのですが、「全てのクレジットカードは、それぞれ特定のCustomer(顧客)に紐づいている」ということをしっかりと理解することがポイントになってきます。
つまり、クレジットカードを保存するにも、入力した人自体がStripe上に顧客として登録されないことには、クレジットカード情報を保存することはできない、ということになります。
そのため、サーバサイド側でStripeを使ってクレジットカードの登録処理を行う際は、「カード情報入力者がStripe上にCustomer(顧客)として登録されているか否か」によって、行うべき処理が変わってくるということに注意してください!
まずは、関連する部分における、コードの全容を掲載します!
(3回のブログ記事に渡ってご説明する内容の中で最終的に作成いただくアプリの全体の仕様や画面遷移についてはhttps://arrown-blog.com/php-laravel-stripe-creditcard/#iを参照してみてください!)
/* app/http/Controllers/User/PaymentController.phpの内容をそのまま抜粋 */
<?php
namespace App\Http\Controllers\User;
use Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\User;
use App\Payment;
class PaymentController extends Controller
{
//
public function getCurrentPayment(){
$user = Auth::user(); //要するにUser情報を取得したい
$defaultCard = Payment::getDefaultcard($user);
return view('user.payment.index', compact('user', 'defaultCard'));
}
public function getPaymentForm(){
$user = Auth::user(); //要するにUser情報を取得したい
return view('user.payment.form');
}
public function storePaymentInfo(Request $request){
/**
* フロントエンドから送信されてきたtokenを取得
* これがないと一切のカード登録が不可
**/
$token = $request->stripeToken;
$user = Auth::user(); //要するにUser情報を取得したい
$ret = null;
/**
* 当該ユーザーがtokenもっていない場合Stripe上でCustomer(顧客)を作る必要がある
* これがないと一切のカード登録が不可
**/
if ($token) {
/**
* Stripe上にCustomer(顧客)が存在しているかどうかによって処理内容が変わる。
*
* 「初めての登録」の場合は、Stripe上に「Customer(顧客」と呼ばれる単位の登録をして、その後に
* クレジットカードの登録が必要なので、一連の処理を内包しているPaymentモデル内のsetCustomer関数を実行
*
* 「2回目以降」の登録(別のカードを登録など)の場合は、「Customer(顧客」を新しく登録してしまうと二重顧客登録になるため、
* 既存のカード情報を取得→削除→新しいカード情報の登録という流れに。
*
**/
if (!$user->stripe_id) {
$result = Payment::setCustomer($token, $user);
/* card error */
if(!$result){
$errors = "カード登録に失敗しました。入力いただいた内容に相違がないかを確認いただき、問題ない場合は別のカードで登録を行ってみてください。";
return redirect('/user/payment/form')->with('errors', $errors);
}
} else {
$defaultCard = Payment::getDefaultcard($user);
if (isset($defaultCard['id'])) {
Payment::deleteCard($user);
}
$result = Payment::updateCustomer($token, $user);
/* card error */
if(!$result){
$errors = "カード登録に失敗しました。入力いただいた内容に相違がないかを確認いただき、問題ない場合は別のカードで登録を行ってみてください。";
return redirect('/user/payment/form')->with('errors', $errors);
}
}
} else {
return redirect('/user/payment/form')->with('errors', '申し訳ありません、通信状況の良い場所で再度ご登録をしていただくか、しばらく立ってから再度登録を行ってみてください。');
}
return redirect('/user/payment')->with("success", "カード情報の登録が完了しました。");
}
public function deletePaymentInfo(){
$user = User::find(Auth::id());
$result = Payment::deleteCard($user);
if($result){
return redirect('/user/payment')->with('success', 'カード情報の削除が完了しました。');
}else{
return redirect('/user/payment')->with('errors', 'カード情報の削除に失敗しました。恐れ入りますが、通信状況の良い場所で再度お試しいただくか、しばらく経ってから再度お試しください。');
}
}
}
/* App/Payment.phpの内容をそのまま抜粋*/
/* 今回UserテーブルにStripeトークンを保存するためのカラムを増やしてtoken(暗号)情報を保存するようにしていますが、Stripeとのやりとりについては、別モデルを作成して、処理をまとめることに。(それが正しいかはさておき、、、)*/
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use App\User;
use Auth;
class Payment extends Model
{
/**
* Stripe上に「顧客」を登録するための関数
*
* @param String $token・・・・・Stripe上のtoken(フロントエンドで作成)
* @param object $user ・・・・・カード登録をするユーザーの情報
* @param object $customer・・・Stripe上に登録する顧客オブジェクト
*/
public static function setCustomer($token, $user)
{
\Stripe\Stripe::setApiKey(\Config::get('payment.stripe_secret_key'));
//Stripe上に顧客情報をtokenを使用することで保存
try {
$customer = \Stripe\Customer::create([
'card' => $token,
'name' => $user->name,
'description' => $user->id
]);
} catch(\Stripe\Exception\CardException $e) {
/*
* カード登録失敗時には現段階では一律で別の登録カードを入れていただくように
* 促すメッセージで統一。
* カードエラーの類としては以下があるとのこと
* 1、カードが決済に失敗しました
* 2、セキュリティーコードが間違っています
* 3、有効期限が間違っています
* 4、処理中にエラーが発生しました
* */
return false;
}
$targetCustomer = null;
if (isset($customer->id)) {
$targetCustomer = User::find(Auth::id());//要するに当該顧客のデータをUserテーブルから引っ張りたい
$targetCustomer->stripe_id = $customer->id;
$targetCustomer->update();
return true;
}
return false;
}
/**
* Stripe上の「顧客」情報を更新するための関数
*
* @param String $token・・・・・Stripe上のtoken(フロントエンドで作成)
* @param object $user ・・・・・カード登録をするユーザーの情報
* @param object $customer・・・Stripe上に登録されている顧客オブジェクト
* @param object $card・・・・・Stripe上に登録されているクレジットカード情報のオブジェクト
*/
public static function updateCustomer($token, $user)
{
\Stripe\Stripe::setApiKey(\Config::get('payment.stripe_secret_key'));
try {
$customer = \Stripe\Customer::retrieve($user->stripe_id);
$card = $customer->sources->create(['source' => $token]);
if (isset($customer)) {
$customer->default_source = $card["id"];
$customer->save();
return true;
}
} catch(\Stripe\Exception\CardException $e) {
/*
* カード登録失敗時には現段階では一律で別の登録カードを入れていただくように
* 促すメッセージで統一。(メッセージ自体はController側で制御しています)
* カードエラーの類としては
* 1、カードが決済に失敗しました
* 2、セキュリティーコードが間違っています
* 3、有効期限が間違っています
* 4、処理中にエラーが発生しました
* */
return false;
}
return true;
}
/**
* Stripe上に現在登録されている顧客の「使用カード」の情報を取得するための関数
*
* @param String $token・・・・・Stripe上のtoken(フロントエンドで作成)
* @param object $user ・・・・・カード登録をするユーザーの情報
* @param object $customer・・・Stripe上に登録されている顧客オブジェクト
* @param object $default_card・・・・・Stripe上から取得した顧客の「使用カード」オブジェクト
*/
protected static function getDefaultcard($user)
{
\Stripe\Stripe::setApiKey(\Config::get('payment.stripe_secret_key'));
$default_card = null;
if (!is_null($user->stripe_id)) {
$customer = \Stripe\Customer::retrieve($user->stripe_id);
if (isset($customer['default_source']) && $customer['default_source']) {
$card = $customer->sources->data[0];
$default_card = [
'number' => str_repeat('*', 8) . $card->last4,
'brand' => $card->brand,
'exp_month' => $card->exp_month,
'exp_year' => $card->exp_year,
'name' => $card->name,
'id' => $card->id,
];
}
}
return $default_card;
}
/**
* Stripe上に現在登録されている顧客のカード情報を削除するための関数
*
* @param object $user ・・・・・カード削除をするユーザーの情報
* @param object $customer・・・Stripe上に登録されている顧客オブジェクト
*/
protected static function deleteCard($user)
{
\Stripe\Stripe::setApiKey(\Config::get('payment.stripe_secret_key'));
$customer = \Stripe\Customer::retrieve($user->stripe_id);
$card = $customer->sources->data[0];
var_dump($card,"カード");
/* card情報が存在していれば削除 */
if ($card) {
\Stripe\Customer::deleteSource(
$user->stripe_id,
$card->id
);
return true;
}
return false;
}
}
/* route/web.phpから関連部分を抜粋 */
Route::get('/user/payment', 'User\PaymentController@getCurrentPayment')->name('user.payment');
Route::get('/user/payment/form', 'User\PaymentController@getPaymentForm')->name('user.payment.form');
Route::post('/user/payment/store', 'User\PaymentController@storePaymentInfo')->name('user.payment.store');
Route::post('/user/payment/destroy', 'User\PaymentController@deletePaymentInfo')->name('user.payment.destroy');
コードとしては以上のようなものになります。
ちなみにStripeにおいては、カード情報の更新とは「既存のカードを削除→新しいカード情報を登録」というものを意味するとのことです、(実際にStripeのカスタマーセンターの方に伺いました)
- 顧客新規登録
- 顧客情報更新(事実上カード登録・更新)
- 現在のカード情報を取得
- カード情報を削除
以上の機能をそれぞれ関数化してうまく組み合わせることによって、カード登録だけでなく、カード情報の更新・カード情報の削除にも対応ができるようになります!
ちなみにStripeでは、1顧客あたり複数のカード登録を行うこともできるようになっています!
特筆すべきStripe APIの関数について解説
さて、コードの全容を掲載したはいいものの、これではただコードを掲載しただけになってしまいますので、
- 顧客新規登録
- 顧客情報更新(事実上カード登録・更新)
- 現在のカード情報を取得
- カード情報を削除
それぞれの処理に使用した関数について、できるだけ解説を交えられたらと思います!
共通処理編
まず、どの処理にしてもそうなのですが、必ず冒頭に以下を記述するようにしましょう。
\Stripe\
Stripe
::setApiKey(\
Config
::get('payment.stripe_secret_key'));
要するに、.envに記載したstripeのシークレットキーを読み込むための記述になります。(直接setApiKey()の中にシークレットキーを書いても動きます)
この記述があることで、以降StripeのAPIで用意された関数を利用することができるようになりますので、必ず忘れないでくださいね!(上記サンプルではお同じような記述を何度か書いていますが、処理をまとめるのもありだと思います)
カード登録編(顧客作成含む。setCustomer関数内中心)
何度かお伝えしているように、Stripeでは、全てのカード情報はそれぞれ特定の顧客(Customer)に紐づいています。
そのため、初めてStripe上に情報を登録する際は、顧客登録が必要になります。
Stripe APIのリファレンスでいうと、上記の\Stripe\Customer::create()
という関数を利用することで、顧客登録を行うことが可能です。
この\Stripe\Customer::create()
なんですが、()
の中に配列形式で情報を入れていくと、「どんな顧客情報を登録したいのか」を具体的に設定することができるようになっています。
\Stripe\Customer::create()
に保存する情報の1つとして、カード情報をtokenとして保存しておけば、顧客作成とカード登録を一気に行うことができるようになります。
そして、顧客作成・カード情報登録が成功すれば、あとは自サイトのデータベースにStripeの顧客 IDのみを保存すればOK!という状態になります。
if (isset($customer->id)) {
$targetCustomer = User::find(Auth::id());//要するに当該顧客のデータをUserテーブルから引っ張りたい
$targetCustomer->stripe_id = $customer->id;
$targetCustomer->update();
return true;
}
上記コードの部分は、当該ユーザーの情報をUserテーブルから引っ張って、そのUserのStripe上のIDを保存している、という処理を表す部分になっています。
カード情報表示編(getDefaultcard関数内中心)
現在のStripe顧客情報取得については、上記APIページにあるように\Stripe\Customer::retrieve()
の関数を使用します。
文字通り「顧客情報を取得」する際に使用するStripe APIの関数で、retrieve()の()の中に、Stripeの顧客 IDを指定します。
(これは、自サイトのデータベースに保存したstripe_idを当てはめれば OKなわけですね!)
$customer = \Stripe\Customer::retrieve($user->stripe_id);
これで、$customer変数の中に、Stripe上の顧客情報が代入・保存される形となります。
そして、今回の記事では「1顧客あたり1枚のクレジットカード登録」という仕様を前提にしてお話を進めています。
純粋に当該顧客のカード情報をページ上に表示したい場合は、顧客に付随したカード情報はhttps://stripe.com/docs/api/customers/retrieveのページにもあるように、$customerの中のsourcesプロパティ内に存在しています。
$card = $customer->sources->data[0];
$default_card = [
'number' => str_repeat('*', 8) . $card->last4,
'brand' => $card->brand,
'exp_month' => $card->exp_month,
'exp_year' => $card->exp_year,
'name' => $card->name,
'id' => $card->id,
];
上記コードにおいては、Stripeの顧客情報が保存されている$customer変数の中から、カード情報が格納されている場所に$customer->sources->data[0]
という形でアクセスし、実際のカード情報を変数 $cardに保存します。
あとは、ページ上に表示したい情報を連想配列形式で用意し、必要な情報を当てはめていっています。
カード情報削除編(deleteCard関数内中心)
次に、カード情報の削除についてです。
まず「顧客情報を取得する」というのが1番最初にくるという考え方は、カード情報表示の時にと変わりありません。
$customer = \Stripe\Customer::retrieve($user->stripe_id);
$card = $customer->sources->data[0];
そのためまずは、\Stripe\Customer::retrieve()
の関数を使って顧客情報を取得・保存し、さらにカード情報をその中から取得、変数に保存しています。
上記ページにもあるように、クレジットカード情報の削除には\Stripe\Customer::deleteSource()
という関数を使います。
()の中に、「顧客ID、カードID」を順番に指定してあげることで、該当のカードを削除することができるようになっています。
if ($card) {
\Stripe\Customer::deleteSource( // 注意1参照
$user->stripe_id,
$card->id
);
return true;
}
//*注意1 \Stripe\Customer::deleteSource()とも書ける
上記コードの部分において、実際にカード情報を削除する記述を行っています。
カード情報更新編(updateCustomer関数内中心)
最後に、カード情報の更新についてです。
何度も申し上げていますが、Stripeにおけるカード情報更新は、「現在の顧客(カード)情報を取得」→「現在のカード情報を削除」→「新しいカード情報を登録」という流れになります。
この流れをまさに体現しているのが、storePaymentInfo関数内の下記コードの部分になります。
$defaultCard = Payment::getDefaultcard($user);
if (isset($defaultCard['id'])) {
Payment::deleteCard($user);
}
$result = Payment::updateCustomer($token, $user);
getDefaultcard関数とdeleteCard関数についてはその中身について先述してご紹介させていただいていますので、残るはupdateCustomer関数になります。
$customer = \Stripe\Customer::retrieve($user->stripe_id);
$card = $customer->sources->create(['source' => $token]);
冒頭「retrieve関数」を使って顧客情報を取得するのは今までと同じです。
そして$card = $customer->sources->create(['source' => $token]);
の部分で新たに カード情報を顧客に紐付けして、カードIDを発行します。
if (isset($customer)) {
$customer->default_source = $card["id"];
$customer->save();
return true;
}
最後に上記の部分です。
Stripeでは「default_card」という定義が存在しています。
このdefault_cardというのは、1顧客に対して複数カード登録がなされている場合に「使用カードとして指定するのはどれか」を設定するためのものです。
カード登録が1枚であれど、default_cardを設定する必要はあるようなので、上記コードにて設定し、Stripe上において、顧客情報を更新(save())しています。
テスト環境において使用するクレジットカードにおいて
実際にクレジットカード決済を作成すると、本当にうまくいっているのかどうかを試したくなりますよね。
とはいえ、実際のクレジットカードを使用してしまうと、無駄な課金を発生させてしまったりして、予期せぬトラブルになりかねません。
そんな時に使えるのが「テストカード」です!
上記ページにテストカード一覧が掲載されていました。
テスト環境において、クレジットカード登録や決済を試す際は、テストカード一覧に掲載されたカードを試してみてくださいね!
(※どのみちテスト環境においては、テストカードしか使用することができません。)
Stripeを使ってクレジットカード登録の実装にトライしてみて思ったこと
今回Stripeを使ってクレジットカード登録の実装にトライしてみて思ったことがいくつかあります。
ざっと抜粋すると
- 公式ドキュメント(特にAPI)を読めないと結構大変。
- とはいえ、PHPの基本(配列やオブジェクトあたり)が分かっていれば、応用は結構効くと思った。
- Stripeのカスタマーサポートの方は本当に親切なのでStripeをおすすめ できるポイントとして強みと言える。
- エラー発生時の分岐処理をしっかり!
PHPのデータ構造(配列やオブジェクト)を理解し、データの中身を読めるようであれば、APIドキュメントを読んでいけば、クレジットカード登録周りについてはそこまで壁は高くないのかな??と思ったところがありました。
ただ、「エラー」が起きた時の処理を厳格にやらないと大変なことになってもおかしくない分野なので、その点は本当注意する必要があります。
まとめ
ということで、今回は「Stripe × PHPでクレジットカード登録・決済処理機能を実装!(クレジットカード情報登録・更新編)」という記事を書かせていただきました!
初めて書く内容なので、わかりやすく記事を書くことができるのが不安な部分もありますが、そこはアウトプットファーストの精神で、引き続き頑張っていきたいと思います!
次回は、決済処理編について書いていきたいと思います!