Paypalには様々な決済方法が用意されていますが、最もスタンダードなソリューションは「エクスプレスチェックアウト(Express Checkout)」です。「ウェブペイメントスタンダード」も機能的にはほぼ同じですが、カートシステムを構築するのであればAPIの使えるエクスプレスチェックアウトを使う方がよいでしょう。
Paypalの決済処理はやや複雑です。エクスプレスチェックアウトを使ったCakePHP用のモジュールを作成しましたので、ご紹介いたします。
※ご利用の場合にはPaypalのマニュアルも確認しながら行ってください。
まず、Model「Paypal.php」を用意します。このモデルを用いて、決済の処理を行います。
PearライブラリRequestを読み込んでいるので、Pearも用意してください。
<?php
class Paypal extends AppModel {
var $name = 'Paypal';
var $useTable = false;
var $apiServer = PAYPAL_API_SERVER;
var $expressCheckoutUrl = PAYPAL_EXPRESS_CHECKOUT_URL;
var $version = '63.0';
var $username = PAYPAL_API_USERNAME;
var $password = PAYPAL_API_PASSWORD;
var $signature = PAYPAL_API_SIGNATURE;
var $returnURL;
var $cancelURL;
var $errorMsg = array();
function __construct() {
vendor('Request');
$this->returnURL = FULL_BASE_URL.'/mypages/confirm';
$this->cancelURL = FULL_BASE_URL.'/mypages/cancel';
}
function setExpressCheckout ($item_amount, $request='Sale', $options=array()) {
$params = array(
'METHOD' => 'SetExpressCheckout',
'VERSION' => $this->version,
'USER' => $this->username,
'PWD' =>$this->password,
'SIGNATURE' => $this->signature,
'PAYMENTREQUEST_0_AMT' => $item_amount,
'PAYMENTREQUEST_0_CURRENCYCODE' => 'JPY',
'PAYMENTREQUEST_0_ITEMAMT' => $item_amount,
'PAYMENTREQUEST_0_PAYMENTACTION' => $request,
'LOCALECODE' => 'JP'
);
$params = am($params, $options);
foreach ($params as &$p) $p = h($p);
$nvpStr = http_build_query($params);
$req = new HTTP_Request($this->apiServer.'?'.$nvpStr);
$req->setMethod(HTTP_REQUEST_METHOD_GET);
if (!PEAR::isError($req->sendRequest())) {
$rawResult = $req->getResponseBody();
parse_str($rawResult, $result);
if ($result['ACK'] == 'Success') {
$this->expressCheckoutUrl .= "&token={$result['TOKEN']}";
$this->expressCheckoutUrl .= '&useraction=commit';
return $result;
} else {
for ($i=0; $i<=9; $i++) {
if (!empty($result['L_ERRORCODE'.$i])) {
$this->errorMsg[$i] = $result['L_LONGMESSAGE'.$i];
$this->log("Set - {$result['CORRELATIONID']} - {$result['L_ERRORCODE'.$i]}", 'paypal');
}
}
return false;
}
}
}
function getExpressCheckoutDetails($token=null, $options=array()) {
$params = array(
'METHOD' => 'GetExpressCheckoutDetails',
'VERSION' => $this->version,
'USER' => $this->username,
'PWD' =>$this->password,
'SIGNATURE' => $this->signature,
'TOKEN' => $token
);
$params = am($params, $options);
foreach ($params as &$p) $p = h($p);
$nvpStr = http_build_query($params);
$req = new HTTP_Request($this->apiServer.'?'.$nvpStr);
$req->setMethod(HTTP_REQUEST_METHOD_GET);
if (!PEAR::isError($req->sendRequest())) {
$rawResult = $req->getResponseBody();
parse_str($rawResult, $result);
if ($result['ACK'] == 'Success') {
return $result;
} else {
for ($i=0; $i<=9; $i++) {
if (!empty($result['L_ERRORCODE'.$i])) {
$this->errorMsg[$i] = $result['L_LONGMESSAGE'.$i];
$this->log("Get - {$result['CORRELATIONID']} - {$result['L_ERRORCODE'.$i]}", 'paypal');
}
}
return false;
}
}
}
function doExpressCheckout($token, $payerId, $amount, $request='Sale', $options=array()) {
$params = array(
'METHOD' => 'DoExpressCheckoutPayment',
'VERSION' => $this->version,
'USER' => $this->username,
'PWD' =>$this->password,
'SIGNATURE' => $this->signature,
'TOKEN' => $token,
'PAYERID' => $payerId,
'PAYMENTREQUEST_0_AMT' => $amount,
'PAYMENTREQUEST_0_CURRENCYCODE' => 'JPY',
'PAYMENTREQUEST_0_PAYMENTACTION' => $request
);
$params = am($params, $options);
foreach ($params as &$p) $p = h($p);
$nvpStr = http_build_query($params);
$req = new HTTP_Request($this->apiServer.'?'.$nvpStr);
$req->setMethod(HTTP_REQUEST_METHOD_GET);
if (!PEAR::isError($req->sendRequest())) {
$rawResult = $req->getResponseBody();
parse_str($rawResult, $result);
if ($result['ACK'] == 'Success') {
return $result;
} else {
for ($i=0; $i<=9; $i++) {
if (!empty($result['L_ERRORCODE'.$i])) {
$this->errorMsg[$i] = $result['L_LONGMESSAGE'.$i];
$this->log("Do - {$params['PAYERID']} - {$result['CORRELATIONID']} - {$result['L_ERRORCODE'.$i]}", 'paypal');
}
}
return false;
}
}
}
function doCapture($transactionId, $amount=0, $complete=false, $options=array()) {
$params = array(
'METHOD' => 'DoCapture',
'VERSION' => $this->version,
'USER' => $this->username,
'PWD' =>$this->password,
'SIGNATURE' => $this->signature,
'AUTHORIZATIONID' => $transactionId,
'AMT' => $amount,
'CURRENCYCODE' => 'JPY',
'COMPLETETYPE' => ($complete) ? 'Complete' : 'NotComplete'
);
$params = am($params, $options);
foreach ($params as &$p) $p = h($p);
$nvpStr = http_build_query($params);
$req = new HTTP_Request($this->apiServer.'?'.$nvpStr);
$req->setMethod(HTTP_REQUEST_METHOD_GET);
if (!PEAR::isError($req->sendRequest())) {
$rawResult = $req->getResponseBody();
parse_str($rawResult, $result);
if ($result['ACK'] == 'Success') {
return $result;
} else {
for ($i=0; $i<=9; $i++) {
if (!empty($result['L_ERRORCODE'.$i])) {
$this->errorMsg[$i] = $result['L_LONGMESSAGE'.$i];
$this->log('DoCap - '.$result['CORRELATIONID'].' - '.$result['L_ERRORCODE'.$i], 'paypal');
}
}
return false;
}
}
}
function doVoid($transactionId, $options=array()) {
$params = array(
'METHOD' => 'DoVoid',
'VERSION' => $this->version,
'USER' => $this->username,
'PWD' =>$this->password,
'SIGNATURE' => $this->signature,
'AUTHORIZATIONID' => $transactionId,
'CURRENCYCODE' => 'JPY',
);
$params = am($params, $options);
foreach ($params as &$p) $p = h($p);
$nvpStr = http_build_query($params);
$req = new HTTP_Request($this->apiServer.'?'.$nvpStr);
$req->setMethod(HTTP_REQUEST_METHOD_GET);
if (!PEAR::isError($req->sendRequest())) {
$rawResult = $req->getResponseBody();
parse_str($rawResult, $result);
if ($result['ACK'] == 'Success') {
return $result;
} else {
for ($i=0; $i<=9; $i++) {
if (!empty($result['L_ERRORCODE'.$i])) {
$this->errorMsg[$i] = $result['L_LONGMESSAGE'.$i];
$this->log('DoVoid - '.$params['AUTHORIZATIONID'].' - '.$result['L_ERRORCODE'.$i], 'paypal');
}
}
return false;
}
}
}
}
?>
PaypalのIP、パスワードなどの設定は、Bootstrap.phpで行うと良いと思います。
if (preg_match ('|cake_dev|',ROOT)){ //テスト環境
define('PAYPAL_API_SERVER', 'https://api-3t.sandbox.paypal.com/nvp');
define('PAYPAL_EXPRESS_CHECKOUT_URL', 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout');
define('PAYPAL_API_USERNAME', '***_biz_api1.***.com');
define('PAYPAL_API_PASSWORD', '***');
define('PAYPAL_API_SIGNATURE', '***');
}else{
define('PAYPAL_API_SERVER', 'https://api-3t.paypal.com/nvp');
define('PAYPAL_EXPRESS_CHECKOUT_URL', 'https://www.paypal.com/cgi-bin/webscr?cmd=_express-checkout');
define('PAYPAL_API_USERNAME', '***.***.com');
define('PAYPAL_API_PASSWORD', '***');
define('PAYPAL_API_SIGNATURE', '***');
}
Controllerでは、まずさきほどのModelを呼び出します。
var $uses = array('Paypal');
大まかな処理の流れは、
となります。
カートページから確認ページに遷移する直前にチェックアウト取引の開始をします。問題なければ、確認ページへリダイレクトします。
if ($setResult = $this->Paypal->setExpressCheckout($amount, 'Sale', $setOptions)) {
$this->params['data']['paypal']['express_checkout_url'] = $this->Paypal->expressCheckoutUrl;
} else {
$this->set('ppErrors', $this->Paypal->errorMsg);
}
$this->Session->write('mypage_cart', $this->params['data']);
$this->redirect('/mypages/confirm');
また、このとき$setOptionsに、オプションをセットすることができます。例えば、下記のように書くことができます。$setOptionsと、setExpressCheckoutの$paramsで、Paypalに渡すデータをセットしているので、構築するカートに合わせて調整してください。
$setOptions = array( 'NOSHIPPING' => '1', 'ALLOWNOTE' => '0', 'L_PAYMENTREQUEST_0_NAME0' => $this->data['Item']['title'], 'L_PAYMENTREQUEST_0_DESC0' => $this->data['Item']['description'], 'L_PAYMENTREQUEST_0_AMT0' => $this->data['Item']['amount'], 'RETURNURL' => FULL_BASE_URL.'/mypages/confirm', 'CANCELURL' => FULL_BASE_URL.'/mypages/cancel' );
次に、確認ページですが、カートの情報を確認して「決済へ」というボタンをクリックしたときの処理を走らせた後に、チェックアウトURLにリダイレクトさせます。
$this->redirect($this->data['paypal']['express_checkout_url']); exit;
これでユーザはPaypalに移動し、決済を行います。決済が終了すると、returnURL に指定したURLにParam付きで帰ってきます。
さあ、最後の確認処理です。tokenを確認し、値があれば保存用のパラメータに内容をセットしています。
if ($getResult = $this->Paypal->getExpressCheckoutDetails($this->params['url']['token'])) {
$this->data['Item']['pp_name'] = $getResult['FIRSTNAME'].' '.$getResult['LASTNAME'];
$this->data['Item']['pp_email'] = $getResult['EMAIL'];
$this->data['Item']['pp_amt'] = $getResult['AMT'];
if ($doResult = $this->Paypal->doExpressCheckout($getResult['TOKEN'], $getResult['PAYERID'], $getResult['AMT'])) {
$this->data['Item']['transaction_id'] = $doResult['PAYMENTINFO_0_TRANSACTIONID'];
}
} else {
// エラー
die('error unknown');
}
あとは返ってきた内容を保存したり、ユーザに完了メールを送信して処理は終了となります。
※このControllerでは、PaypalモデルのなかでdoCaptureとdoVoidを使用していません。
doCaptureは、発送後に支払いを回収するため、doVoidはキャンセルのために呼び出します。
PayPal API導入・活用ガイド
PayPal Express Checkout Integration Guide(英語マニュアル:PDF)
Paypal APIの最新バージョンの確認