PayPalでダウンロード販売する法

個人でちょっとしたダウンロード販売を行いたいと思ったとき、便利な決済サービスが PayPalです。
・初期費用、登録審査など一切不要。個人でも簡単に登録できる。
・カード決済の手数料が安い。他のカード決済サービスと比較しても、かなり安い部類。
PayPalを使えば、誰でも簡単にカード決済付きネットショップが開設できる!
そう思って PayPalの仕組みを調べてみたところ、やはり実現することが難しい機能も幾つかありました。
PayPalの使い方については、あちこちのサイトにも書かれているので、ここでは要望の割に実現するのが難しいであろう、
 ・支払いデータ転送(PDT)
 ・即時支払い通知(IPN)
の使い方をメモしておきます。
いずれもの機能も、活用するためには PHP等で処理を行うページを作る必要があります。

※ここで説明している仕組み(IPN)を実現したのが「らくらくダウンロード」というサイトです。
※ >> http://www.easypay.jp/
※ 無料で利用できるので、まずはお試しあれ。(2010/07/01追記)
※また、誰でも簡単に設置できるカートも用意しました。
* jCart日本語版 >> [id:rikunora2:20100723]
※ この「らくらくダウンロード+jCart」で、本当に、誰でも、無料で、お手軽に
※ ダウンロードショッピングサイトができちゃいます! (2010/07/23追記)

PayPalを用いてダウンロード販売サイトを作ろうと思ったとき、まず直面するのは、
PayPalで決済を済ませたお客さんを、ダウンロードのページに導くことができない」
という問題です。
通常、インターネット上のダウンロード販売は、こんな流れになっています。
 [1:買い物かご] -> [2:カード決済] -> [3:ダウンロードページ]
PayPalの場合、[1:買い物かご]は自分のホームページ、[2:カード決済]はPayPalサイト、ということになるでしょう。
問題となるのは、PayPalの場合、[2:カード決済] -> [3:ダウンロードページ] の道筋をどうするか、ということです。
PayPalの決済を終えた先に、「ありがとうございました」ページを用意することまではできます。
しかし、その際に、何を買った、どのお客さんが「ありがとうございました」ページに来たのか、簡単に区別することができないのです。
これだと(自動的な方法で)ダウンロード販売を実現することができません。


                                                                                                                                              • -

* PDT -- Payment Data Transfer
ダウンロード販売には欠かせない、[2:カード決済]で決済したお客さんの情報を [3:ダウンロードページ] にまで持ってくる機能、
それが「支払いデータ転送(PDT)」です。
通常、PayPalにアカウントを作った直後の状態では、この PDT機能は有効になっていません。
PDTを有効にするには、PayPalの管理画面にログインして、
 [マイアカウント]->[個人設定]->[販売の設定]->[ウェブ ペイメントの設定]
に行って、次の設定を行います。
・自動復帰: オン
・復帰URL: ここに[3:ダウンロードページ]となるURLを入力します。
 たぶん https://hoge.moge.co.jp/download.php みたいになると思います。
・支払いデータ転送: オン
「支払いデータ転送」の下に、IDトークン: という長い暗号のような文字列が出ています。
この「IDトークン」は、DTPで不正なアクセスを拒否するために使用します。

肝心のダウンロードページですが、PayPalからの決済情報を受け取るサンプルが、以下のURLに用意されています。
* Code Samples -- Payment Data Transfer
>> https://www.paypal.com/us/cgi-bin/webscr?cmd=p/xcl/rec/pdt-code
このサンプルを元に開発するのが早いと思います。
サンプルとほとんど同じものですが、以下に私が試したPHPソースを張っておきます。

//	決済完了、ダウンロード
//	PDTの仕組みを使ってPayPal決済情報を受け取る.
//	内容については責任持たないので、利用は自己責任で。

// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-synch';

$tx_token = $_GET['tx'];
$auth_token = "* サイトで発行されるIDトークンをここに貼り付ける *";
$req .= "&tx=$tx_token&at=$auth_token";

// post back to PayPal system to validate
$header .= "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
$fp = fsockopen ('www.sandbox.paypal.com', 80, $errno, $errstr, 30);	// テストサイト
// $fp = fsockopen ('www.paypal.com', 80, $errno, $errstr, 30);	// 本番
// $fp = fsockopen ('ssl://www.paypal.com', 443, $errno, $errstr, 30);
	// できれば HTTPS にした方が、セキュリティが高まる.

if (!$fp) {
// HTTP ERROR
	echo "ERROR: HTTP error, [" . $errno . "] " . $errstr . "\n";
	exit(1);
}

fputs ($fp, $header . $req);

// read the body data
$res = '';
$headerdone = false;
while (!feof($fp)) {
	$line = fgets ($fp, 1024);
	if (strcmp($line, "\r\n") == 0) {
		// read the header
		$headerdone = true;
	}
	else if ($headerdone) {
		// header has been read. now read the contents
		$res .= $line;
	}
}

// parse the data
$lines = explode("\n", $res);
$keyarray = array();
if (strcmp ($lines[0], "SUCCESS") == 0) {
	for ($i=1; $i


	毎度ありがとうございました。
\n"; var_dump( $keyarray ); // どんなパラメータが来るのか、ダンプしてみる. ?>;



                                                                                                                                              • -

* IPN -- Instant Payment Notification
とりあえず、上の PDTでダウンロード販売は実現できるのですが、
これだけだとお客さんがダウンロードに失敗したときや、決済の直後にブラウザを切ってしまい、
ダウンロードページに行かなかったときに、連絡を行うことができません。
そこで、たとえお客さんが途中でブラウザを切ってしまっても、確実に決済完了した信号を受け取る仕組みが必要となります。
それが、「即時支払い通知(IPN)」です。
IPN というのは、お客さんが PayPalの決済を行ったことを、直接ショップにお知らせする機能です。
* 即時支払い通知(IPN) Instant Payment Notifications
>> https://cms.paypal.com/us/cgi-bin/?&cmd=_render-content&content_ID=developer/e_howto_admin_IPNIntro
なので、IPNのお知らせを自動的に受け取れば、次のような流れでダウンロード販売が実現できます。

お客さん [1:買い物かご] -> [2:カード決済] -> (ここでお客さんがブラウザを閉じた)
ショップ ->[4: IPNお知らせを受信] -> [5: お客さんにメールを送る]
お客さん ->[6: メールを受け取る] -> [7: メールの指示に従って、ダウンロードページへ]

それでは、[4: IPNお知らせを受信]は、どのようにすれば良いのでしょうか。
IPNお知らせは、http、つまり Webへのアクセスという形でやってきます。
なので、IPNお知らせを受信するための、専用のWebページを作る必要があります。
このページに、IPNお知らせを受け取るための PHPで書かれたサンプルが用意されていますので、
まずはこのサンプルをコピーしてきて修正するのが早道でしょう。
* Implementing an IPN Listener
>> https://cms.paypal.com/us/cgi-bin/?&cmd=_render-content&content_ID=developer/e_howto_admin_IPNImplementation
 [4: IPNお知らせを受信] -> [5: お客さんにメールを送る]
というところは PHPなどで自作する必要があります。

サンプルとほとんど一緒なんですけど、以下に、IPNお知らせ受信ページのPHPソースを張っておきます。
以下のコードでは、受信した情報をログファイルに書き出しています。

//	PayPalのIPN機能を受けるハンドラー
//	これによって決済完了を知ることができるのだ.
//	内容については責任持たないので、利用は自己責任で。

define("IPNLOGFILE", "* ここにログファイル名を書く *");

$PAYPAL_SITE = "www.paypal.com";	// 本番サイト
// $PAYPAL_SITE = "www.sandbox.paypal.com";	// テストサイト

// Read the post from PayPal and add 'cmd' 
$req = 'cmd=_notify-validate'; 
if(function_exists('get_magic_quotes_gpc')){
	$get_magic_quotes_exits = true;
}
// Handle escape characters, which depends on setting of magic quotes 
// POSTで受けた値を、そのまま GET形式に書き直す => これをPayPal側のサイトに返す.
foreach ($_POST as $key => $value){
	if($get_magic_quotes_exists == true && get_magic_quotes_gpc() == 1){
		$value = urlencode(stripslashes($value)); 
	} else { 
		$value = urlencode($value); 
	}
	$req .= "&$key=$value";  
}

// Open LogFile
$log = fopen( IPNLOGFILE, "a");	// 追記オープン
if( ! $log ){
	print "ERROR: Open Log File, " . IPNLOGFILE ;
	exit(1);
}

// Post back to PayPal to validate 
$header = "POST /cgi-bin/webscr HTTP/1.0\r\n"; 
$header .= "Content-Type: application/x-www-form-urlencoded\r\n"; 
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n"; 

$fp = fsockopen( $PAYPAL_SITE, 80, $errno, $errstr, 30 );
	// PAYPAL_SITE の 80 番ポートに接続、タイムアウトを30秒に設定
	// エラーがあれば $errno, $errstr に入る。
// Process validation from PayPal 
if (! $fp) { // HTTP ERROR  
	fwrite( $log, "ERROR: Cannot open ". $PAYPAL_SITE . " [". $errno . "] " . $errstr );
	exit(1);
}

fputs( $fp, $header . $req );	// こちらから応答を返す.

// そして、その結果を待つ.
while( ! feof( $fp ) ) {
	
	$res = fgets( $fp, 1024 );	// その応答結果を受け取る.
	
	// 戻りは "VERIFIED" か "INVALID" のいずれかになる.
	// 成功の場合.
	if (strcmp ($res, "VERIFIED") == 0) { 
		// * TODO * ここでいろんなチェックを行うこと.
		// Check the payment_status is Completed 
		// Check that txn_id has not been previously processed 
		// Check that receiver_email is your Primary PayPal email 
		// Check that payment_amount/payment_currency are correct 
		
		// POSTパラメータを書き出す.
		$text = "";
		foreach ($_POST as $key => $value){ 
			$text .= $key . " = " .$value ."\n"; 
		}
		fwrite( $log, $text );
	}
	// 失敗の場合、とにかく値をログに書き出す.
	else if (strcmp ($res, "INVALID") == 0) { 
		// POSTパラメータを書き出す.
		$text = "";
		foreach ($_POST as $key => $value){ 
			$text .= $key . " = " .$value ."\n"; 
		}
		fwrite( $log, $text );
	}
//	else{ // 内容以外の行は読み飛ばす.
//	}
}
fclose( $fp );
fclose( $log );
exit( 0 );


上の IPNお知らせを受信ページが完成したら、PayPalから受信ページに向かって信号を送るように設定します。
PayPalの管理画面にログインして、
 [マイアカウント]->[個人設定]->[販売の設定]->[即時支払い通知の設定]
に行って、「通知URL」という項目に、上で作った IPNお知らせを受信ページのURLを入力します。
そして「IPNメッセージを受信する(有効)」にチェックを入れましょう。
これで、PayPal上で決済が行われるたびに、自作のIPNお知らせを受信ページに向かって、お知らせが送られてくるはずです。


                                                                                                                                              • -

* 開発者用サイト -- Sandbox
上のような仕組みを実際に試してみるには、クレジットカードを使って何かお買い物をしなければなりません。
それはやってられないよ、というときのために、PayPalでは開発専用のテストサイトを用意しています。
* PayPal sandbox -- 開発者用サイト
>> https://developer.paypal.com/
開発するには、こちらのサイトのアカウントを取得しましょう。

IPN の仕組みをテストするには、
[左側メニュー] -> [Test Tools] -> [Instant Payment Notification (IPN) Simulator.]
ここから、IPNお知らせ信号を擬似的に送ることができます。
[IPN handler URL] に、自作のお知らせを受信ページを入力して、
[Transaction type] は、適当なもの、eCheck - complete とかを選んで、後は見よう見まねでやってください。

PDT, IPNの情報がまとまって書いてあるページは、意外に少なかった。(探し方が悪かったのかもしれないが)
例えば、ここに載っていた。
* Paypal「PDT」「IPN」を使った決済とバックエンドの統合(2)
>> http://blog.katsuma.tv/2007/06/paypal_pdt_ipn_code.html