【WordPress】stripe決済による商品購入でパスワード付与を自動化

<h2>要件</h2>

・Wordpressで作成したサイト上の商品を購入したときに、
　Stripe決済を用いて購入からパスワード付与までの流れを自動化する。

・パスワード認証は最初の1回のみとする。（同じIPからのアクセスなら2回目以降は認証不要）

・別のIPからアクセスがあった場合はパスワードを再発行する。

<h2>前提</h2>

今回は制作コストを抑えるため、
stripeとの連携は「Stripe Payments」プラグインを使用しました。

<h2>パスワード付きの商品閲覧ページを編集する</h2>

商品ごとにパスワード付きの閲覧ページを作成します。

今回は購入者に個別のパスワードを動的に発行するので、
ここでは適当なパスワードを設定しておきます。
（後ほどこのパスワードでは認証できないようにする。）

```php:function.php
//パスワード認証画面
function my_password_form() {
	$message = '';
	if( $_GET["re"] ){
		$message = '<p><br>通常と異なるアクセスを検知したためパスワードを再発行します。<br>ご登録のメールアドレスへメールを送信致しましたので、確認の上再度ログインしてください。</p>';
	}
	else if( $_GET["false"] ){
		$message = '<p><br>メールアドレス、またはパスワードに誤りがあります</p>';
	}
	else{
		$message = '<p><br>このコンテンツは保護されています。<br>閲覧するには以下にメールアドレスとパスワードを入力してください。</p>';
	}
	return $message.
    '<form class="post_password" action="' . home_url() . '/wp-login.php?action=postpass" method="post">
    メールアドレス：<input name="post_email" type="text" />
    　　パスワード：<input name="post_password" type="password" />
	 <br>
    <input type="submit" name="Submit" value="' . esc_attr__("パスワード送信") . '" />
    </form>';
}
add_filter('the_password_form', 'my_password_form');
```

<h2>サンキューページの編集とパスワード付与機能の実装</h2>

商品を購入してもらったときに表示するページの文面と、
その内部で行うパスワード生成とメール生成を実装します。

```php:function.php
//ランダムなパスワード生成
function random($length = 8){
    return substr(str_shuffle(str_repeat('023456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ', $length)), 0, $length);
}
//サンキューページ
function asp_custom_thank_you_msg ($output, $txn_data){
	$pwd = random();
	require_once( "wp-load.php" );
	global $wpdb;
	//URL→ID取得
	$url = $txn_data["item_url"];
	$pid = url_to_postid( $url );
	//テーブル作成
	require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
	$table_name = $wpdb->prefix . "email_passwords";
	$charset_collate = $wpdb->get_charset_collate();
	$sql = "CREATE TABLE IF NOT EXISTS $table_name (
		seq mediumint(9) NOT NULL AUTO_INCREMENT,
		pid text NOT NULL,
		email text NOT NULL,
		pwd text NOT NULL,
		ip text NOT NULL,
		UNIQUE KEY seq (seq)
	) $charset_collate;";
	dbDelta( $sql );
	$datas = $wpdb->get_results("SELECT * FROM $table_name WHERE pid = '{$pid}' AND email = '{$txn_data["stripeEmail"]}'");
	if( count($datas) == 0 ){
		//保存
		$wpdb->insert(
			$table_name,
			array(
				'pid' => $pid,
				'email' => $txn_data["stripeEmail"],
				'pwd' => $pwd,
				'ip' => ""
			)
		);
	}
	//メール送信
	mb_language("Japanese");
	mb_internal_encoding("UTF-8");
	//送信元メールアドレス
	$fromemail = "◯◯◯";
	$header = "From: {$fromemail}n";
	$to = $txn_data["stripeEmail"];
	$title = "◯◯◯◯◯◯";
	//自動返信メール内容
	$content = "◯◯◯◯◯◯◯◯◯◯◯◯◯◯◯";
	mb_send_mail($to, $title, $content, $header);
	//サンキューページのメッセージ
	$output = "ご購入ありがとうございます。<br>
					<br>商品名：{item_name}<br>
					価格：{item_price_curr}<br>
					<br>
					商品URL・ID・パスワードを、<br>
					{$txn_data["stripeEmail"]} にお送り致しましたのでご確認ください。<br><br>メールが見当たらない場合、迷惑メールフォルダやゴミ箱に振り分けされている可能性がありますのでご確認ください。";
    return $output;
}
add_filter('asp_stripe_payments_checkout_page_result', 'asp_custom_thank_you_msg', 10, 2);

<h2>パスワード認証まわりのシステムを実装</h2>

ユーザーがパスワード認証を行ったときの処理を書いていきます。

```php.function.php
//パスワード認証
function custom_auth_save() {
	include 'wp-load.php';
	global $wpdb;
	$refurl = wp_get_referer();
	if( strpos($refurl , "?")>0 ){
		$refurl = explode( "?" , $refurl )[0];
	}
	//パスワードの一致確認
	$postid = url_to_postid( $refurl );
	$table_name = $wpdb->prefix . "email_passwords";
	$pwds = $wpdb->get_results("SELECT * FROM {$table_name} WHERE pid = '{$postid}' AND email = '{$_POST['post_email']}' AND pwd = '{$_POST['post_password']}'");
	//一致
	if( count($pwds) > 0 ){
		$pwds = $pwds[0];
		//認証
		if( $pwds->ip == "" ){
			//保存
			$value = array( "ip" => $_SERVER["REMOTE_ADDR"] );
			$where = array( "seq" => $pwds->seq );
			$wpdb->update($table_name, $value, $where);
			wp_safe_redirect( $refurl );
			exit();
		}
		//パスワード変更＋メール送信
		else{
			$pwd = random();
			mb_language("Japanese");
			mb_internal_encoding("UTF-8");
			//送信元メールアドレス
			$fromemail = "◯◯◯";
			$header = "From: {$fromemail}n";
			$to = $pwds->email;
			//パスワード再発行メールタイトル
			$title = "パスワードを再発行しました。";
			$producturl = get_permalink( $pwds->pid );
			//パスワード再発行メール内容
			$content = "◯◯◯";
			mb_send_mail($to, $title, $content, $header);
			//認証保存
			$value = array( "ip" => "" , "pwd" => $pwd );
			$where = array( "seq" => $pwds->seq );
			$wpdb->update($table_name, $value, $where);
			wp_safe_redirect( $refurl."?re=1" );
			exit();
		}
	}
	else{
		wp_safe_redirect( $refurl."?false=1" );
		exit();
	}
}
add_action( 'login_form_postpass', 'custom_auth_save' );
```

ユーザーが送信したメールアドレス・パスワードで、
データベースを検索して一致すれば認証を通します。

このとき、IPの紐付けが行われていなければ初回認証と見なし、
IPをデータベースに保存しておきます。

もしすでにIPが紐付け（データベースに保存）されていれば、
漏洩 or 別環境からの認証と見なし、パスワードの再発行を行っています。

<h2>パスワード認証状態のチェック</h2>

一度パスワード認証を行ったら、同じIPからのアクセスでは再認証を必要としないようにします。

```php:funxtion.php
//パスワード認証状態のチェック
function nendebcom_post_password_required( $required, $post ) {
	if ( !$required ) {
		return $required;
	}
	if( strpos( $_SERVER['REQUEST_URI'] , "action=edit" ) === false ){
		include 'wp-load.php';
		global $wpdb;
		$ip = $_SERVER["REMOTE_ADDR"];
		$table_name = $wpdb->prefix . "email_passwords";
		$pwds = $wpdb->get_results("SELECT * FROM {$table_name} WHERE pid = '".$post->ID."' AND ip = '{$ip}'");
		if( count($pwds) > 0 ){
			return false;
		}
	}
	return true;
}
add_filter( 'post_password_required', 'nendebcom_post_password_required', 10, 2 );
```

データベース（email_passwords）から、投稿IDとユーザーのIPをセットして
取り出すことが出来たら、認証済みと見なしています。