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の最新バージョンの確認
2016-04-22