paypalを使って電子書籍のダウンロード販売

電子書籍元年が何度もきたおかげで、電子書籍(デジタルコンテンツ)のダウンロード販売がDRMもついてAmazonや楽天kobo、iBookstore、KADOKAWA☆BOOKWALKERに並べることができる時代となった。
また、電子書籍専門というわけではなく、デジタルコンテンツをダウンロード販売できるサイトも百花繚乱雨後筍で、コンテンツさえ用意できれば個人で簡単に販売ができる。
「デジタルコンテンツをダウンロード販売できるサイトを比較してみた」
http://writing-san.blog.jp/archives/32017213.html
また、ワードプレスにはダウンロード販売のためのプラグインまであって、販売チャンネルの選択肢はたくさんある。
「Easy Digital Downloads - ダウンロード販売サイトを簡単に作れるWordPressプラグイン」
http://netaone.com/wp/easy-digital-downloads/
集客力販売力、販売手数料や手間ひまを考えて自分に合うところにコンテンツを置けばいいし、そうすれば販売ページへのURLや購入ボタンをSNSや自分のサイトに貼りつけて告知できる。
ぶっちゃけ、わざわざ自分でダウンロード販売の仕組みとか作るのは時間と労力の無駄なのでオススメしない。
面白そうだ、という好奇心自己満足。それと、もしかすると、何らかの事情で他社サービスにコンテンツを置くわけにはいかないような場合に。
てことなので、以下はわたしの技術メモ。備忘録。
内容的には2010年の雑記とPayPalとのやり取りなどはほぼ同じ。
「paypalと電子書籍のダウンロード販売(その1)」
http://t2aki.doncha.net/?id=1277017233
「paypalと電子書籍のダウンロード販売(その2)」
http://t2aki.doncha.net/?id=1277130006
今回の雑記では自サバ側のこともメモしておく。
【じぶんちでの設定】
コンテンツをサーバーの所定のフォルダにアップロード。
サーバーのデータベースにコンテンツの商品登録。ここで商品にIDを付ける。
以下3つのスクリプトを用意
・ご購入ありがとうございますページのスクリプト
・IPN受信&データベース登録用のスクリプト
・ダウンロード用スクリプト
【PayPalでの購入ボタン作成・各種設定】
「トップ」→「販売ツール」のメニューにある「売り手の設定」から
→「PayPalボタン」
[今すぐ購入]サンプルボタンあたりを雛形に「ボタンの編集」で商品名や値段などを入れる。ここではオプションの商品ID(=データベースで決められたID)を入れるのを忘れずに。
ボタンを作ったら「コードをコピー」して自分のサイトの購入ボタンを設置したいところにコードをペーストする。
→「ウェブサイトの設定」(ウェブペイメントの設定)
・ウェブペイメントの自動復帰「オン」
・復帰URL:ご購入ありがとうございますページのURLを記入
・支払いデータ転送「オン」※IDトークンをコピーしておく
・暗号化されていないウェブペイメントの受領拒否「オフ」
・PayPalアカウントオプションサービス「オン」
・連絡先電話番号「オフ」
・エクスプレスチェックアウトの設定「いいえ」
→「即時支払い通知」(IPN)
・通知URL:IPNを受信するスクリプトのURL
・メッセージの配布:有効
→「PayPalボタンの言語コード化」
・「詳細オプション」→「UTF-8」
【PayPalとのやりとり】

ユーザーの動きは以下の3つ。
1)ユーザーがオレオレサーバーの購入ボタンをクリックすると
2)PayPalの決済ページに飛んでそこでお支払い
3)お支払いが終わるとオレオレサーバーの購入ありがとうございましたページに戻ってくる。
ユーザーのお支払い終了と同時にPayPalからオレオレサーバーに購入データが飛んでくる。
購入データは以下の2種類。
データを取得して解析するためのサンプルコードがPayPalに用意されていて、そのまんま利用させてもらう。
3)PDT(Payment Data Transfer)
図では赤い矢印がひとつだけど、データのやりとりの実際は。
→PayPalからオレオレサーバーへ
・ユーザーが購入ありがとうございますページにリダイレクトされてくる時に、トランザクションを持ってアクセスしてくる
→オレオレサーバーからPayPalへ
・トランザクションと管理ページの「支払いデータ転送」の項目に記載されているIDトークン、コマンドをPOSTでPayPalにリクエスト
→PayPalからオレオレサーバーへ
・POSTした内容が正しければ一行目に「SUCCESS」と書かれたデータを返してくる
このPDTデータは
SUCCESS
first_nameJane+Doe
lst_name=Smith
payment_status=Completed
など、1行にひとつ「ネーム=バリュー」形式、NVP形式のデータとなっている。
※ユーザーが支払いを終えて待たずにすぐブラウザを落としたりするとデータ取得できない。購入ありがとうございますページにリダイレクトされてやってきて初めてデータのやり取りが生じる。
PDTデータ取得&解析のサブルーチン
※HTMLデコードと文字コードをutf8にしているところ以外はサンプルコードのまんま。
sub paypal_pdt{
my $self = shift;
my $args = shift;
return if( ! $args->{tx} );
my $paypal = ’https://’ . $self->{paypal}->{server} . $self->{paypal}->{gate};
my $query = join(’&’, "cmd=_notify-synch", "tx=$args->{tx}", "at=$self->{paypal}->{auth_token}");
my $ua = new LWP::UserAgent;
my $req = new HTTP::Request("POST", $paypal);
$req->content_type("application/x-www-form-urlencoded");
$req->content($query);
my $res = $ua->request($req);
return if($res->is_error);
my @response = split("\n", $self->html_decode($res->content) );
my $status = shift(@response);
my %tx;
if($status eq "SUCCESS"){
foreach( @response){
my ($key, $val) = split("=", $_);
$val = Encode::decode(’utf8’, $val) if ! Encode::is_utf8($val);
$tx{$key} = $val;
}
return \%tx;
}
elsif( $status eq ’FAIL’){
return;
}
else{
return;
}
}
PDTデータのpayer_emailやitem_nameなどを「ご購入ありがとうございます」ページの「~様」や「~をご購入いただきありがとうございました」などの個別の表示に使う。
PDTデータのitem_number(商品ID)でダウンロード商品なのか、別の商品なのかを判定して、ダウンロード商品の場合はダウンロードURLを表示する。ダウンロードURLはユーザーのemailやtransactionidなど一意のものから作成している。
4)IPN(Instant Payment Notification)
ユーザーから見える言わば表側のPDTと違って、こちらは裏側。
ユーザーがお支払いを終えると管理ページで指定したURLにデータが飛んでくるので取りこぼしがない。
PDTはユーザーに見せるご購入ありがとうございますページに使う程度で、オレオレサーバーのデータベースに購入の記録を残すためにはこちら、IPNを使う。
図では赤い矢印がひとつだけど、データのやりとりの実際は。
→PayPalからオレオレサーバーへ
・購入データが送られてくる。いわゆるWEBのフォームデータで「&」で繋がれた「ネーム=バリュー」形式
→オレオレサーバーからPayPalへ
・送られてきた購入データにコマンドをひとつつけてPayPalにPOSTする
→PayPalからオレオレサーバーへ
・「VERIFIED」か「INVALID」か一行返ってくる。これ以外は調べる必要があるらしいが滅多になさそうだし、PayPalの管理画面で確認すれば良い。
IPNデータ取得&解析のサブルーチン
エラーはIPNのデータをつけてメールするように。
VIRIFIED(データが正しい)場合でも以下の4点を確認する。
・支払いのステータス「payment_status」が完了「Completed」であることを確認
・すでに完了した取引の悪用防止のために「txn_id」が過去のものと重複していないことを確認
・不正アカウントに支払いされないように「receiver_email」がPayPalアカウントに登録したメールアドレスであることを確認
・価格が変更されていないか確認(商品IDなども)
確認できたらオレオレサーバーのデータベースに必要情報を登録して、ユーザーにお礼とダウンロードURLを書いたメールを送信する(自分にも同じものを送信)
※データ確認の部分やメールの部分以外はサンプルコードのまんま。
sub paypal_ipn{
my $self = shift;
my $args = shift;
my ($s,$ms,$h, $d,$m,$y) = localtime(time); ++$m; $y+=1900; my $ymd = sprintf qq{%s/%02d/%02d [%02d:%02d:%02d]}, $y, $m, $d, $h, $ms, $s;
my $query;
read (STDIN, $query, $ENV{’CONTENT_LENGTH’});
if(! $query ){
print qq{Content-type:text/plain\n\n};
return;
}
$query .= ’&cmd=_notify-validate’;
my $ua = new LWP::UserAgent;
my $paypal = ’https://’ . $self->{paypal}->{server} . $self->{paypal}->{gate};
my $req = new HTTP::Request("POST" , $paypal);
$req->content_type("application/x-www-form-urlencoded");
$req->content($query);
my $res = $ua->request($req);
my %ipn;
my @pairs = split(/&/,$query);
my $count = 0;
foreach my $pair ( @pairs ){
my ($key, $val) = split(/=/,$pair);
$val =~ tr/+/ /;
$val =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
$val = Encode::decode(’utf8’, $val) if ! Encode::is_utf8($val);
$ipn{$key} = $val;
$count++;
}
my $err;
if($res->is_error){
$err = sprintf qq{%s\nHTTP ERROR: %s\n\n%s\n}, $ymd, $res->status_line, $query;
}
elsif( $res->content eq ’VERIFIED’){
my $msg = $self->update_ipn({ipn=>\%ipn});
if($msg){
$err = sprintf qq{[VERIFIED and Error]%s\n%s\n\n%s\n}, $ymd, $msg, $query;
}
else{
my $body = sprintf(qq{
%s様
「%s」ご購入ありがとうございます。
%spaypal/%s?download=%s
こちらのURLからダウンロードしてください。
・ダウンロード回数は%s回
・ダウンロード期間は%s日
となっています。
よろしくお願いいたします。
});
$self->send_paypal_download({
addr=>$ipn{’payer_email’},
subject=>’【’ . $ipn{’item_name’} . ’】ダウンロードURLのお知らせ’,
body=>$body
});
$self->send_paypal_download({
addr=>$ipn{’root_email’},
subject=>’【’ . $ipn{’item_name’} . ’】ダウンロードURLのお知らせ’,
body=>$body
});
}
}
elsif( $res->content eq ’INVALID’){
$err = sprintf qq{[INVALID]%s\n\n%s\n}, $ymd, $query;
}
else{
$err = sprintf qq{[UNKNOWN ERROR]%s\n\n%s\n}, $ymd, $query;
}
if($err){
$self->send_alert_mail({
addr=>$self->{root_email},
subject=>’[PAYPAL]IPN ERROR!!’,
body=>$err
});
}
print qq{Content-Type: text/plain\n\n};
exit;
}
【ダウンロードについて】
ダウンロードはダウンロード用のスクリプトがファイルを返すようにしてある。
たとえば、わたしが自分だけで作ったコンテンツなんかはどうでもいいんだけど、表紙が依頼原稿だったり、アンソロジーでほかの人の原稿が入っていたりするとそうもいかない。回数や期間を無制限にするわけにはいかない。
なので、ファイルの置き場所=URLをそのままユーザーにお知らせできない。
ダウンロード用のスクリプトを噛ませて
・ダウンロードのURLは購入者ごとに違うものを作る
・ダウンロード回数を制御する
・ダウンロード期間を制御する
といったことを仕込んだ。
また、ダウンロードごとにIPやUserAgentを記録すれば、不正なアクセスやダウンロードできない事故などの問題解決にも役立つ。
以前の雑記にも書いたように、PayPalは管理ページに記録が残ってるし取引が生じたらいちいちメールも飛んでくるので、致命的な問題にはならないはず。サポートもびっくりするというか恐縮するぐらいに厚い。
残念なことに(法律的に?)日本では単純にクレジットカードだけで支払いはできなくなった(ペイパルへのアカウント・会員登録が必須となった)けど、個人での少額決済に手軽に使えるので助かるなあ。

