勉強したことのメモ

webプログラマが勉強したことのメモ。

PHPとStripeで定額課金(サブスクリプション)実装

      2021/03/19

PHPとStripeで1ヶ月ごとに500円でサイト内のサービスを使い放題のようなサブスクリプション機能を実装したかった。以下に方法のメモ。

 

Stripeの導入

Stripeのアカウント作成やライブラリのインストールは過去記事を参照。

 

商品(コース)の作成

Stripeのダッシュボードにログインし「商品」→「商品を追加」→「商品を保存」の順に進み、商品(コース)を作成する。今回はテストのため、1日ごとに500円の決済が発生する商品を作成した。

尚、商品を作成すると「price_」から始まるAPI IDと「prod_」から始まるIDが発行され、今回必要なのはAPI IDになる。

 

決済処理

決済ページ(HTML)

<!DOCTYPE html>
<html lang="ja">
<head>
</head>
<body>
	<form id="form_payment" action="add.php" method="post">
		Name:<input id="name" type="text" name="name">
		Email:<input id="email" type="text" name="email">
		<!-- ここのdivタブがカード入力フォームに置き換わります。 -->
		<div id="card-element" class="MyCardElement"></div>
		<!-- ここにエラーメッセージが表示されます。 -->
		<div id="card-errors" role="alert"></div>
		<button id="button">Submit</button>
	</form>

	<script src="https://js.stripe.com/v3/"></script>
	<script>
	const stripe = Stripe('公開可能なAPIキー');

	// 入力フォームを生成します。スタイルを指定することもできます。
	const elements = stripe.elements();
	const cardElement = elements.create("card");

	// 先程のdivタブにマウントします。
	cardElement.mount("#card-element");

	// クレジットカード番号や有効期限の入力に合わせてエラーメッセージを出力します。
	cardElement.addEventListener('change', ({error}) => {
		const displayError = document.getElementById('card-errors');
		if (error) {
			displayError.textContent = error.message;
		} else {
			displayError.textContent = '';
		}
	});

	const submit = document.getElementById('button');
	const name = document.getElementById('name');
	const email = document.getElementById('email');

	// 登録ボタンがクリックされたら、API通信をおこなう
	submit.addEventListener('click', async(e) => {
		e.preventDefault();
		const {paymentMethod, error} = await stripe.createPaymentMethod({
			type: 'card',
			card: cardElement,
			billing_details: {
				// 顧客名emailアドレスはなくてもOK
				name: name.value,
				email: email.value,
			},
		});
		// 通信エラー時
		if (error) {
			console.error(error)
		} else {
			// 成功したらトークンが返されるので、hiddenに埋め込む
			const form = document.getElementById('form_payment');
			const hiddenToken = document.createElement('input');
			hiddenToken.setAttribute('type', 'hidden');
			hiddenToken.setAttribute('value', paymentMethod.id);
			hiddenToken.setAttribute('name', 'token');
			form.appendChild(hiddenToken);
			form.submit();
		}
	});
</script>
</body>
</html>

商品名や料金説明等は適宜追記する。

 

決済処理ページ(PHP)

<?php
require './vendor/autoload.php';
\Stripe\Stripe::setApiKey('シークレットキー');

if($_SERVER['REQUEST_METHOD'] == 'POST') {

	$name = $_POST['name'];
	$email = $_POST['email'];
	$token = $_POST['token'];

	// 顧客情報を登録
	$customer = \Stripe\Customer::create([
		'payment_method' => $token, // 登録する支払い方法
		'name' => $name,
		'email' => $email,
		'invoice_settings' => [
			'default_payment_method' => $token, // デフォルトで使用する支払い方法。必須。
		],
	]);

	// 顧客をプランに登録する
	$subscription = \Stripe\Subscription::create([
		// 先程登録した顧客情報のID
		'customer' => $customer->id,
		'items' => [
			[
			  'plan' => 'API ID',
			],
		],
	]);
	// subsctiprionIDは解約時に必要のため、DBに保存しておく
	$sub_id = $subscription->id;

}

決済日時、メールアドレス、「sub_」から始まるsubsctiprionIDをMySQLなどに適宜格納しておく。特にsubsctiprionIDは必須。

 

解約処理

解約処理ページ(PHP)

<?php
require './vendor/autoload.php';

\Stripe\Stripe::setApiKey('シークレットキー');

//subsctiprionID
$sub_id = 'sub_xxxxxxxxxxxx';

// 解約したあとも有効期限までは、プランに入会したまま
\Stripe\Subscription::update(
  $sub_id,
  [
    'cancel_at_period_end' => true,
  ]
);

 

別の実装方法

決済までであればもっと簡単な実装方法もある。Stripeのダッシュボードにログインし「設定」→「Payments内にあるCheckoutの設定」→「Checkout クライアント専用組み込みを有効」→「許可」→「ドメインを入力して保存」と進む。

さらに「商品」→「料金体系の…となっているメニュー部分」→「checkoutのコードスニペットを取得」→「成功時のURLとキャンセル時のURLを入力して保存」とする。最後のURLを入力するダイアログでJavaScriptのタグが表示されるのでそのタグを決済ページに貼り付けるだけで良い。

ただ、この方法だとsubsctiprionIDの取得方法が分からなかった。そうすると解約もどうすればいいのか分からない。「テスト用クレカ&実在するメアド」で登録テストしてみたものの確認メールのようなものは届かなかった。

 

その他

正常に定額決済に登録されているか確認する方法

Stripeのダッシュボードにログインし「顧客」メニューを開くと登録されているユーザーが一覧表示される。

またユーザー部分をクリックすると詳細が表示され、どのコースに登録されているか等が確認できる。

できれば「商品」→「詳細」を開いた際にも該当商品に登録しているユーザーが見られればありがたいが、そのような項目は見受けられなかった。

 

subsctiprionIDの確認方法

Stripeのダッシュボードにログインし「顧客」→「適当なユーザーを選択」→「支払い部分を選択」するとページ下部に「イベントとログ」という表示欄があり、イベントデータの中に「sub_」から始まるsubsctiprionIDが確認できる。

ただ、イベントデータはデフォルトでは省略されており「○行すべてを表示する」ボタンをクリックしないと確認できなかったので注意する。

 

所感

定額決済だと単発決済とは違って解約が発生する為、subsctiprionIDは必須として入会日や更新日などもデータベースに残しておく必要がありそうでまあ色々面倒くさそう。また、例えばだが「今月入会した場合は○%割引」「解約しても更新予定日までは使用可能」等、色々柔軟に対応できるみたいだがこれも実装するのは大変そうである。できれば単発決済のみが良いなぁとは思うものの、恐らくは需要がある決済方法だと考えられるため最低限の部分は今回メモした方法で乗り切りたいところ。

 

参考サイト

https://qiita.com/azukiazusa/items/584d69a373214769880c

 - PHP

  関連記事

PHPで他サーバにファイルをアップロードする

formで送信した内容をチェックした上で、他サーバにファイルをアップロードしたか ...

PHPでのcookie

aaa.comにログインフォームがあって、IDパスを入れて ログインするとbbb ...

MySQLのエラーメール送付

MySQLエラーを取得してエラーメール送付。 <? error_repor ...

PHPで複数の変数に同じ値を代入する

PHPのソースで $a = $b = $c = 12; みたいな見たことの無い代 ...

PHPで実行時間の測定

$start_time = microtime(true); /***測定したい ...

PHPでベーシック認証をかける方法

PHPでBasic認証をかけているソースを拝見する機会があり、衝撃だったのでメモ ...

フォルダにリンク制限をかける

imgというフォルダがあり、直接URLを叩いても 中身を見られないけど、同一サー ...

フォーム管理画面テンプレPHP

<?php ini_set( 'display_errors', 0 ); ...

PHPでのend~~について

endforとかendifとか使いたかったけど、構文の書き方が 分からなかったの ...

独自タグからタグへの変換function

独自タグは##IMG1##みたいな感じ。 画像ファイル名は「ファイルパス/seq ...